Commit 27c9aeb
feat: extract @tanstack/openai-base and @tanstack/ai-utils packages (#409)
* feat(ai-utils): add @tanstack/ai-utils package with shared utilities
Introduces the @tanstack/ai-utils package providing shared, zero-dependency utilities (generateId, getApiKeyFromEnv, transformNullsToUndefined, defineModelMeta) for use across TanStack AI adapter packages.
* fix(ai-utils): align with canonical adapter patterns
- Fix getApiKeyFromEnv to check globalThis.window.env before process.env, matching all existing adapters
- Fix generateId to use substring(7) instead of substring(2,10) for consistent random part length
- Add JSDoc to transformNullsToUndefined explaining the null→undefined JSON Schema reason
- Add missing test cases for empty output modalities and negative output pricing
* feat(openai-base): add @tanstack/openai-base with schema converter, tools, and types
* feat(openai-base): add Chat Completions text adapter base class
Extract the streaming Chat Completions pipeline from ai-grok into a
reusable OpenAICompatibleChatCompletionsTextAdapter. Providers that use
the OpenAI Chat Completions API (/v1/chat/completions) can extend this
class and only need to set baseURL, lock type parameters, and override
methods for provider-specific quirks.
Protected override points: mapOptionsToRequest, convertMessage,
convertContentPart, processStreamChunks, makeStructuredOutputCompatible,
normalizeContent, extractTextContent.
Also adds Chat Completions-specific tool converter (distinct from the
existing Responses API tool converter).
* feat(openai-base): add Responses API text adapter base class
Extract and generalize the OpenAI Responses API text adapter into
OpenAICompatibleResponsesTextAdapter. This handles the full Responses
API streaming pipeline (9+ event types), including reasoning/thinking
tokens, tool call streaming, and structured output via text.format.
Also adds responses-tool-converter for the flat Responses API tool
format (distinct from Chat Completions' nested function format).
* feat(openai-base): add image, summarize, transcription, TTS, and video adapter base classes
* refactor(ai-openai): delegate to @tanstack/openai-base and @tanstack/ai-utils
Migrate ai-openai to extend base classes from openai-base and delegate
utility functions to ai-utils, eliminating ~1,800 lines of duplicated
code while maintaining zero breaking changes.
Changes:
- utils/client.ts: generateId and getOpenAIApiKeyFromEnv delegate to ai-utils
- utils/schema-converter.ts: transformNullsToUndefined and makeOpenAIStructuredOutputCompatible delegate to ai-utils/openai-base
- tools/*: all 14 tool files re-export from openai-base
- adapters/text.ts: extends OpenAICompatibleResponsesTextAdapter, overrides mapOptionsToRequest for OpenAI-specific tool conversion and validation
- adapters/image.ts: extends OpenAICompatibleImageAdapter, overrides validation methods
- adapters/summarize.ts: extends OpenAICompatibleSummarizeAdapter
- adapters/tts.ts: extends OpenAICompatibleTTSAdapter, overrides validation
- adapters/transcription.ts: extends OpenAICompatibleTranscriptionAdapter
- adapters/video.ts: extends OpenAICompatibleVideoAdapter, overrides validation and request building
- openai-base config.ts: removed explicit baseURL to avoid null incompatibility
All 127 existing tests pass, types check clean, build succeeds.
* refactor(ai-grok): delegate to @tanstack/openai-base and @tanstack/ai-utils
* refactor: migrate ai-groq, ai-openrouter, ai-ollama to shared utilities
Replace duplicated generateId, env-var lookup, and null-transform logic
in ai-groq, ai-openrouter, and ai-ollama with imports from @tanstack/ai-utils
and @tanstack/openai-base. makeGroqStructuredOutputCompatible now delegates
to makeStructuredOutputCompatible and applies the Groq-specific quirk of
removing empty required arrays.
* style: format files with prettier
* refactor: migrate ai-anthropic, ai-gemini, ai-fal, ai-elevenlabs to @tanstack/ai-utils
Replace duplicated generateId and getXxxApiKeyFromEnv implementations in
ai-anthropic, ai-gemini, ai-fal, and ai-elevenlabs with imports from
@tanstack/ai-utils. All provider-specific wrapper function names preserved
for backwards compatibility.
* chore: add changesets for openai-base extraction
* fix: address CodeRabbit review comments on openai-base extraction
- Fix schema-converter default required parameter and null-widening for nested types
- Fix removeEmptyRequired to recurse into anyOf/oneOf/allOf/additionalProperties (groq)
- Forward modelOptions, request headers/signal in chat-completions-text adapter
- Remove stream_options leak into non-streaming structured output calls
- Use call_id instead of internal id for tool call correlation (responses-text)
- Make transcription verbose_json default provider-agnostic via protected override
- Add runtime guards for ArrayBuffer and atob in transcription adapter
- Derive TTS outputFormat from merged request after modelOptions spread
- Add downloadContent probe and fix expires_at seconds-to-milliseconds (video)
- Fix mcp-tool type ordering so metadata cannot override type: 'mcp'
- Add tests proving all fixes work
* fix: resolve eslint and knip failures from full test suite
- Fix unnecessary type assertions in chat-completions-text and responses-text
- Fix eslint import order in ai-groq client.ts
- Fix unnecessary condition in ai-groq schema-converter combinator recursion
- Fix array-type lint error in openai-base provider-options
- Remove unused files in ai-grok (tools/index.ts, tool-converter.ts, function-tool.ts)
- Remove unused exports (createOpenAIClient, generateId, validateTextProviderOptions, InternalTextProviderOptions)
* ci: apply automated fixes
* fix: address CodeRabbit review comments
- responses-text: remove unsafe `any` cast for call_id, fix overly
restrictive item.id guard on function_call handling
- transcription: add ensureFileSupport() and decodeBase64() helpers
for cross-environment safety (Node < 20, missing atob)
- schema-converter.test: rename misleading oneOf test name
- env.test: use unique generated keys for deterministic assertions
* fix: address code review findings
- tool-choice: widen MCPToolChoice.server_label from 'deepwiki' literal to string
- gemini client: restore error message mentioning both GOOGLE_API_KEY and GEMINI_API_KEY
- chat-completions-text/responses-text: use console.error for stream errors
- chat-completions-text: spread modelOptions first so explicit fields win
- package.json: remove duplicate workspace deps from devDependencies
* fix: remove unnecessary type assertions in transcription adapter
* ci: apply automated fixes
* fix(openai-base): align rebased openai-base with current main
Post-merge fixes landed on top of the rebase onto main:
- Thread TToolCapabilities generic through OpenAICompatibleChatCompletionsTextAdapter
and OpenAICompatibleResponsesTextAdapter so ai-openai / ai-grok / ai-groq /
ai-openrouter pass their own tool-capability maps while the base stays
brand-agnostic.
- Align AG-UI event yields and adapter signatures with main's EventType-enum-typed
StreamChunk: wrap event yields through an asChunk helper; align BaseImage/TTS/
Transcription super() calls with (model, config) signature; switch GeneratedImage
construction to the url-xor-b64Json discriminated shape; propagate options.logger
through summarize → chatStream.
- Responses API tool-call events now use item.id as toolCallId and emit both
toolCallName and toolName, matching main's contract.
- Prefer videos.retrieve URL for video content (retrieve-returns-url path) —
aligns with main's behavior and works against providers that don't implement
/content.
- ai-utils: preserve the full random suffix in generateId (pre-rebase regression
would have failed ai-fal's entropy test).
- Recreate ai-grok/src/tools/index.ts as a thin re-export of openai-base's
Chat Completions tool converter (vite entry expects it).
* fix: address unresolved CodeRabbit findings + audit hygiene regression
CodeRabbit findings:
- ai-groq schema-converter: recursively normalise nested empty-object
schemas in properties, items, anyOf/oneOf/allOf, additionalProperties.
Top-level patch missed nested z.object({}) branches that openai-base's
transformer skipped, leading to Groq strict-mode validation failures.
Adds 3 regression tests.
- openai-base chat-completions-text: capture usage from any chunk
including the terminal usage-only chunk emitted with
stream_options.include_usage so RUN_FINISHED carries accurate token
counts. Previously the terminal chunk was skipped via
'if (!choice) continue' and RUN_FINISHED emitted earlier without usage.
Also emit a synthetic RUN_FINISHED if the stream ended without a
finish_reason chunk.
- openai-base chat-completions-text: fail fast on unsupported content
parts in convertMessage instead of silently dropping them and sending
an empty user prompt that masks a capability mismatch.
Audit-hygiene regression carried forward from main #465:
- openai-base {chat-completions-text,responses-text}: replace seven
console.error blocks across chatStream / structuredOutput /
processStreamChunks catch sites with logger.errors(...) using
toRunErrorPayload. Raw SDK errors can carry request metadata
(including auth headers); main #465 had already removed this from
ai-openai/text.ts before the extraction but the base classes lifted
the unsafe form. Tests updated to inject testLogger via
resolveDebugOption(false) on every chatStream / structuredOutput call,
matching the pattern from ai-ollama tests after #467 made logger
required on TextOptions.
Verification:
- pnpm test:lib : 31 projects pass
- pnpm test:types: 32 projects pass
- pnpm test:eslint: 31 projects pass
- pnpm build : 33 projects pass
* fix: address Round 1 cr-loop findings (subject-scope bucket-a)
Six-agent unbiased review surfaced behavioural and audit-hygiene
regressions across the extracted base classes. Fixes:
openai-base/adapters/chat-completions-text.ts
- Defer RUN_FINISHED until end-of-stream so the trailing usage-only
chunk that OpenAI emits AFTER finish_reason (when
stream_options.include_usage is on) is captured. Previous fix emitted
RUN_FINISHED with usage: undefined and dropped the trailing chunk.
- Forward upstream finish_reason ('length', 'content_filter', etc.)
instead of collapsing all non-tool reasons to 'stop'.
- Surface tool-args JSON parse failures via logger.errors so a model
emitting malformed JSON for tool arguments is debuggable instead of
silently invoking the tool with input: {}.
- Fix mapOptionsToRequest precedence: `{...modelOptions, temperature:
options.temperature, ...}` clobbered `modelOptions.temperature` with
undefined whenever the caller didn't set the top-level option. Now
spreads top-level fields only when defined.
- Throw on user message with zero content parts instead of silently
sending content: '' (a paid request with no input).
openai-base/adapters/responses-text.ts
- Only reset accumulators on response.created. Previous code reset on
response.failed/incomplete too, which left TEXT_MESSAGE_START events
unbalanced when a response failed mid-stream. On terminal failure
events we now emit TEXT_MESSAGE_END before RUN_ERROR.
- Synthesize a terminal RUN_FINISHED if the stream ends without
response.completed (truncated upstream connection), matching the
chat-completions adapter's behavior so consumers always see a
terminal event for every started run.
- Surface tool-args JSON parse failures via logger.errors (parallel to
chat-completions fix).
- Distinguish refusals from unsupported content_part types in
handleContentPart instead of always reporting 'Unknown refusal'.
- Fix mapOptionsToRequest precedence: previously `...modelOptions` was
spread LAST, silently shadowing the canonical top-level fields. Now
matches the chat-completions adapter (modelOptions first, then
defined top-level options), so callers tuning either backend see
identical behaviour.
- extractTextFromResponse now throws a distinct refusal error instead
of returning '' and letting JSON.parse('') produce a confusing
'Failed to parse structured output as JSON. Content: ' message.
openai-base/adapters/summarize.ts
- generateId(this.name) for the result id (was hard-coded to '').
- Throw when chatStream emits RUN_ERROR instead of pretending a failed
run succeeded with summary: ''.
openai-base/adapters/{image,transcription,tts}.ts
- Wrap SDK calls in try/catch + logger.errors with toRunErrorPayload
so raw SDK errors (which can carry request metadata including auth
headers) never reach user-supplied loggers. Matches the audit
hygiene main #465 applied to ai-openai/text.ts before the
extraction.
- tts.ts: cross-runtime ArrayBuffer→base64 helper so the adapter works
in browser/edge runtimes that lack Buffer.
- transcription.ts: confidence calculation no longer treats
avg_logprob === 0 (perfect-confidence) as missing.
openai-base/tools/file-search-tool.ts
- validateMaxNumResults(0) now correctly throws (was skipped because
`if (maxNumResults && ...)` short-circuited on falsy zero).
ai-openai/adapters/transcription.ts
- shouldDefaultToVerbose returns true ONLY for whisper-1. The
gpt-4o-transcribe* models reject 'verbose_json' with HTTP 400, so
defaulting them to verbose was a guaranteed-failure regression. The
previous logic was inverted.
ai-openai/tools/{file-search,image-generation}-tool.ts
- Drop locally duplicated validators; import from openai-base. The
local copy of validateMaxNumResults had the same falsy-zero bug.
ai-openai/tools/computer-use-tool.ts
- Document that the brand discriminator ('computer_use') intentionally
differs from the runtime tool name ('computer_use_preview'). The
brand matches the model-meta capability union; the runtime name
matches the OpenAI SDK literal that the special-tool dispatcher
switches on.
ai-utils/src/transforms.ts
- Document transformNullsToUndefined's actual behavior (object keys
are removed, top-level null becomes undefined, arrays preserve
positional null). Restrict scope to JSON-shaped values; class
instances/Date/Map/Set are not preserved by Object.entries recursion.
ai-elevenlabs/src/utils/client.ts
- Wire getElevenLabsApiKeyFromEnv and generateId through
@tanstack/ai-utils so the dependency added during the merge is
actually consumed (was unused after main's #504 SDK migration).
ai-openrouter/package.json
- Move @tanstack/ai from dependencies to devDependencies (it's
already declared as a peerDependency). Avoids dual-instance hazards
where the workspace package gets installed twice. Also bump the
peer range to workspace:^ to match every other adapter.
.changeset/refactor-providers-to-shared-packages.md
- Correct the description: only ai-openai/ai-grok/ai-groq inherit
from @tanstack/openai-base; the other six providers only consume
@tanstack/ai-utils. Previous wording implied all nine adapters
delegated to openai-base.
Verification:
- pnpm test:lib : 31 projects pass (116+ unit tests)
- pnpm test:types : 32 projects pass
- pnpm test:eslint : 31 projects pass
- pnpm build : 33 projects pass
* fix: address Round 2 cr-loop findings
Round 2 confirmation surfaced bucket-(a) issues across multiple
agents — many on code paths I had missed in Round 1.
ai-openai/src/adapters/text.ts
- mapOptionsToRequest: restructure to mirror the base
OpenAICompatibleResponsesTextAdapter precedence (modelOptions first,
then conditional spreads of explicit top-level options when defined).
The previous override spread `...modelOptions` LAST and wrote
`temperature: options.temperature` unconditionally, which both
silently shadowed canonical fields with modelOptions values AND
clobbered modelOptions.temperature with undefined whenever the caller
didn't set the top-level option. Five separate review agents
flagged this as a real regression vs. the base class fix.
openai-base/src/adapters/responses-text.ts
- response.failed / response.incomplete now sets runFinishedEmitted
after RUN_ERROR so the post-loop synthetic terminal block doesn't
emit a duplicate RUN_FINISHED on top of an error event. Also
coalesces the (error + incomplete_details) double-emit into a
single RUN_ERROR with merged message + code.
- response.completed now forwards `incomplete_details.reason` (length /
content_filter / etc.) as finishReason instead of collapsing to
'stop', mirroring the chat-completions adapter's preservation of
upstream finish reasons.
- Top-level error event sets runFinishedEmitted (terminal).
- structuredOutput strips streaming-only fields (stream / stream_options)
from a subclass override of mapOptionsToRequest before the
non-streaming call, parallel to chat-completions's cleanup. A
subclass returning stream_options on the streaming-shaped result
would otherwise 4xx the structured-output call.
- mapOptionsToRequest: `tools` is now a conditional spread so a caller's
modelOptions.tools survives when options.tools is undefined.
- convertContentPartToInput audio branch wraps raw base64 in a data
URL using part.source.mimeType, mirroring the image branch.
`input_file` rejects bare base64 strings.
- convertMessagesToInput throws on a user message with no content
parts instead of injecting `text: ''` (parallel to chat-completions
fail-loud behavior — paid request with no input mask intent).
openai-base/src/adapters/chat-completions-text.ts
- mapOptionsToRequest: `tools` conditional spread (same fix as
responses-text). Drop the now-redundant `as Array<...>` cast that
ESLint correctly flagged as unnecessary.
openai-base/src/adapters/summarize.ts
- TEXT_MESSAGE_CONTENT branch only appends delta when defined,
preventing a content-less chunk with an undefined delta from
concatenating literal 'undefined' to the summary.
openai-base/src/adapters/video.ts
- Replace both `Buffer.from(buffer).toString('base64')` call sites with
a cross-runtime `arrayBufferToBase64` helper (atob/btoa fallback for
browser/edge). Buffer is Node-only — the previous code threw
ReferenceError on Workers / Edge / browsers.
openai-base/src/tools/{image-generation,web-search,web-search-preview}-tool.ts
- Convert tool functions: spread `metadata` first, then force the
literal `type` constant last. The previous order let a hand-built
tool with the wrong metadata.type override the discriminator while
the dispatcher had already routed by tool.name, producing a payload
whose runtime type didn't match the route. Mirrors the existing
mcpTool pattern.
openai-base/tests/{chat-completions,responses}-text.test.ts
- Drop the extra outer `logger: testLogger` from the
'throws on invalid JSON response' structuredOutput tests; logger is
supposed to live on chatOptions only and the outer copy was a Round 1
patch-script artifact.
ai-utils/src/transforms.ts
- transformNullsToUndefined doc: clarify that array elements recurse
via this same function, so a top-level null element becomes
undefined (not null). Correct the class-instance description: native
built-ins like Date/Map/Set become {}, but arbitrary class instances
produce a plain-object snapshot of own enumerable string properties,
not a uniform empty object.
.changeset/refactor-providers-to-shared-packages.md
- Correct the description: ai-groq does NOT inherit from
@tanstack/openai-base text adapter (it speaks groq-sdk and keeps its
own BaseTextAdapter-derived adapter); it only consumes the schema
converter and tool converters.
Verification:
- pnpm test:types : 32 projects pass
- pnpm test:lib : 31 projects pass (116+ unit tests)
- pnpm test:eslint : 31 projects pass
- pnpm build : 33 projects pass
* fix: address Round 3 cr-loop findings (lifecycle + edge cases)
Round 3 confirmation surfaced subtler tool-call lifecycle and
terminal-event idempotency gaps that earlier rounds missed.
openai-base/adapters/chat-completions-text.ts
- Move RUN_STARTED emission BEFORE the `if (!choice) continue` guard so
a stream that arrives entirely as usage-only chunks (no choices) still
emits RUN_STARTED. Without this the post-loop synthetic terminal block
(which gates on hasEmittedRunStarted) skipped emission entirely,
producing zero events for the run.
- Skip non-started entries in the finish_reason TOOL_CALL_END loop and
in the post-loop synthetic close. A delta with index but no id/name
populates toolCallsInProgress without ever emitting TOOL_CALL_START;
emitting TOOL_CALL_END for those entries violated AG-UI lifecycle
(END without matching START) and produced empty toolCallId payloads
consumers couldn't pair.
- Clear toolCallsInProgress after emitting TOOL_CALL_END, and gate the
synthetic finishReason on a separate pendingToolCount counter rather
than `toolCallsInProgress.size > 0`. Previously, a tool-then-clean-stop
run reported finishReason: 'tool_calls' because the map was never
cleared after the in-loop emission.
- parsedInput now coerces non-object JSON to {} (parallel to the
Responses adapter's existing guard). A bare string/number/null in the
arguments wire would otherwise propagate to tool execution as the
TOOL_CALL_END.input.
- Close any started tool calls in the post-loop synthetic block on
truncated streams (no finish_reason ever arrives) so consumers see a
matching TOOL_CALL_END for every TOOL_CALL_START.
- convertMessage: throw on a single-text user message whose content is
empty (paid request with no input). Previously the early-return
bypassed the multipart fail-loud guard.
- convertMessage: assistant tool-only messages now use `content: null`
instead of `content: ''`, matching the OpenAI Chat Completions
contract; empty-string content interacts oddly with tokenization on
some backends.
- convertContentPart image branch defaults the data-URI MIME to
`application/octet-stream` when source.mimeType is undefined, instead
of interpolating literal "undefined" into the URI.
openai-base/adapters/responses-text.ts
- response.failed / response.incomplete now ALWAYS emit RUN_ERROR (even
when both `error` and `incomplete_details` are missing) and `return`
out of the loop. Previously, an incomplete event with no detail
silently coerced the run to RUN_FINISHED { finishReason: 'stop' } via
the post-loop synthetic block, masking the failure. The early return
also stops further chunks from being processed after a terminal event.
- response.output_item.added: emit TOOL_CALL_START only when the
metadata's started flag is false. A duplicate output_item.added for
the same item id (retried streams / replay) no longer emits a second
TOOL_CALL_START.
- response.function_call_arguments.delta: drop the `args` field. Its
`metadata ? undefined : chunk.delta` polarity was inverted (populated
only on orphan-tool-call paths), the chat-completions adapter never
emitted it, and consumers should accumulate `delta` themselves.
- response.function_call_arguments.done: skip TOOL_CALL_END (and log)
when the metadata's started flag is false, mirroring the chat-
completions adapter's started-guard. Emitting END without a matching
START broke AG-UI consumers' lifecycle pairing.
- handleContentPart RUN_ERROR is now treated as terminal: set
runFinishedEmitted = true and `return` out of the loop. Previously
the loop kept processing and the synthetic terminal block could emit
a second RUN_FINISHED after a refusal.
- Mark hasStreamedContentDeltas / hasStreamedReasoningDeltas in the
content_part.added handler so a subsequent matching content_part.done
doesn't re-emit the same text (the existing skip guard for done
events depended on these flags being true).
- handleContentPart's reasoning_text branch caches the fallback stepId
(`if (!stepId) stepId = generateId(...)`) instead of generating a
fresh id on each call. Multiple reasoning content parts arriving
without a preceding STEP_STARTED no longer emit different stepIds.
- convertContentPartToInput image and audio: default the data-URI MIME
to application/octet-stream when source.mimeType is undefined.
openai-base/adapters/{chat-completions,responses}-tool-converter.ts
- Shallow-copy the schemaConverter result before mutating
additionalProperties. A subclass-supplied passthrough converter
could otherwise have its caller's schema mutated by the
`jsonSchema.additionalProperties = false` assignment.
openai-base/adapters/video.ts
- getVideoUrl SDK fall-through (content/getContent/download): convert
the binary response to a data URL like the downloadContent path
already does. Previously the bottom `return { url: response.url }`
returned the API endpoint URL (or undefined for non-Response shapes)
in place of a usable video URL.
openai-base/package.json
- Drop `zod` from peerDependencies and devDependencies. Grep confirms
no zod imports anywhere in the package; the peer dep produced a
spurious peer-warning on every consumer.
Verification:
- pnpm test:types : 32 projects pass
- pnpm test:lib : 31 projects pass (116+ unit tests)
- pnpm test:eslint : 31 projects pass
- pnpm build : 33 projects pass
Deferred bucket (b/c) follow-ups (not in this commit):
- Include tests/ in ai-utils + openai-base tsconfig.json (requires
fixing ~20 pre-existing test-file TS issues: EventType enum literals
vs string literals, possibly-undefined narrowing, mock typing).
- ai-fal peer-dep workspace:* → workspace:^ (pre-existing on main).
- defineModelMeta gaps (output.cached, NaN, integer context_window).
- transformNullsToUndefined runtime guard for non-plain objects.
* chore(examples/ts-react-chat): refresh model picker
Update MODEL_OPTIONS with current flagship models:
- OpenAI: GPT-5.2, 5.2 Pro, 5.1, 5/Mini/Nano, 4.1
- Anthropic: Claude Opus 4.7, 4.6, Sonnet 4.6/4.5, Haiku 4.5
(cleaner non-dated form matching the model-meta canonical names)
- OpenRouter: now spans openai/, anthropic/, google/, x-ai/, and
meta-llama/ slugs so the OpenRouter adapter can demonstrate
routing across multiple upstream providers from one API
- Default model is now OpenAI GPT-5.2 (was GPT-4o)
* refactor(openai-base, ai-utils, ai-openai, ai-grok): address PR #409 review feedback
- Eliminate `toCompatibleConfig` casts: `OpenAIClientConfig`/`GrokClientConfig`
extend `OpenAICompatibleClientConfig` directly; ai-grok keeps a
`withGrokDefaults()` helper for its xAI baseURL default.
- Fix tool-call streaming bugs:
- chat-completions-text: track `emittedAnyToolCallEnd` at outer scope so a
`tool_calls` finish_reason without a started/ended pair downgrades to `stop`.
- responses-text: guard `function_call_arguments.delta` with
`metadata?.started` to match the `.done` handler.
- Schema converter: `originalRequired` is optional and falls back to
`schema.required`; adapters now pass the unmodified array (not `|| []`)
so root-level required fields are no longer silently widened to `null`.
- Forward request options: shared `extractRequestOptions` helper threads
`headers`/`signal` from `Request | RequestInit` into both Chat Completions
and Responses SDK calls without per-call casts.
- Restore logger calls dropped during the refactor: `logger.request` before
every SDK call (image/summarize/transcription/tts/video/responses-text/
chat-completions-text), `logger.provider` per-chunk in both streaming
adapters, and try/catch + `logger.errors` around `summarize`,
`summarizeStream`, and `createVideoJob`.
- Move base64 helpers into `@tanstack/ai-utils` (`arrayBufferToBase64`,
`base64ToArrayBuffer`) with `Uint8Array.toBase64`/`fromBase64` fast paths
and `Buffer`/`atob`/`btoa` fallbacks; remove the per-adapter copies.
- Add `warnIfLargeMediaBuffer` in video.ts to surface base64-encoding-an-
unbounded-blob OOM risks on Workers/Lambda.
- Replace `client as any` in video.ts with a typed `getVideosClient()`
accessor; drop the arbitrary 1-10 cap in base `validateNumberOfImages`.
- Delete dead code: `openai-base/src/types/message-metadata.ts`,
`provider-options.ts`, and the unused `defineModelMeta` in ai-utils.
- Brand-via-base: every ai-openai tool factory now delegates to its
`@tanstack/openai-base` counterpart and brands the result, removing ten
duplicate function bodies.
- `satisfies` instead of `as` for the structured-output response narrow.
- Add `@types/node` dev dep on ai-utils so `process` typechecks.
- Tests: new `media-adapters.test.ts` (image / summarize / transcription /
tts / video) in openai-base, new `base64.test.ts` (incl. 1.5 MiB
roundtrip) in ai-utils.
All test suites green: ai-utils 18, openai-base 71, ai-groq 17,
ai-openai 131, ai-grok 53. `test:types`, `test:eslint`, and `build`
clean for every affected package.
* Removed openai and zod as direct dependencies of grok adapter
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com>1 parent 4a943d6 commit 27c9aeb
107 files changed
Lines changed: 8422 additions & 3582 deletions
File tree
- .changeset
- examples/ts-react-chat/src/lib
- packages/typescript
- ai-anthropic
- src/utils
- ai-elevenlabs
- src/utils
- ai-fal
- src/utils
- ai-gemini
- src/utils
- ai-grok
- src
- adapters
- text
- tools
- utils
- tests
- ai-groq
- src/utils
- tests
- ai-ollama
- src/utils
- ai-openai
- src
- adapters
- tools
- utils
- ai-openrouter
- src/utils
- ai-utils
- src
- tests
- openai-base
- src
- adapters
- tools
- types
- utils
- tests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
18 | 25 | | |
19 | 26 | | |
20 | | - | |
21 | 27 | | |
22 | 28 | | |
23 | 29 | | |
24 | 30 | | |
25 | | - | |
26 | | - | |
| 31 | + | |
| 32 | + | |
27 | 33 | | |
28 | 34 | | |
29 | 35 | | |
30 | | - | |
31 | | - | |
| 36 | + | |
| 37 | + | |
32 | 38 | | |
33 | 39 | | |
34 | 40 | | |
35 | | - | |
36 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
37 | 48 | | |
38 | 49 | | |
39 | 50 | | |
40 | | - | |
41 | | - | |
| 51 | + | |
| 52 | + | |
42 | 53 | | |
43 | 54 | | |
44 | 55 | | |
45 | 56 | | |
46 | 57 | | |
47 | | - | |
48 | | - | |
| 58 | + | |
| 59 | + | |
49 | 60 | | |
50 | 61 | | |
51 | 62 | | |
| |||
54 | 65 | | |
55 | 66 | | |
56 | 67 | | |
57 | | - | |
58 | | - | |
| 68 | + | |
| 69 | + | |
59 | 70 | | |
60 | 71 | | |
61 | | - | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
62 | 113 | | |
63 | 114 | | |
64 | | - | |
65 | | - | |
| 115 | + | |
| 116 | + | |
66 | 117 | | |
67 | 118 | | |
68 | 119 | | |
69 | | - | |
70 | | - | |
| 120 | + | |
| 121 | + | |
71 | 122 | | |
72 | 123 | | |
73 | 124 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
| 26 | + | |
40 | 27 | | |
41 | 28 | | |
42 | 29 | | |
43 | 30 | | |
44 | 31 | | |
45 | 32 | | |
46 | | - | |
| 33 | + | |
47 | 34 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
| 53 | + | |
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
2 | 6 | | |
3 | 7 | | |
4 | 8 | | |
| |||
39 | 43 | | |
40 | 44 | | |
41 | 45 | | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
| 46 | + | |
50 | 47 | | |
51 | 48 | | |
52 | 49 | | |
| |||
80 | 77 | | |
81 | 78 | | |
82 | 79 | | |
83 | | - | |
84 | | - | |
85 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
86 | 83 | | |
87 | 84 | | |
88 | 85 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
| 50 | + | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | 9 | | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
| 10 | + | |
40 | 11 | | |
41 | 12 | | |
42 | 13 | | |
| |||
48 | 19 | | |
49 | 20 | | |
50 | 21 | | |
51 | | - | |
| 22 | + | |
52 | 23 | | |
53 | 24 | | |
54 | 25 | | |
| |||
0 commit comments