- Update model metadata from OpenRouter API (#581)
- Updated dependencies [
2e0e2eb]:- @tanstack/ai@0.19.0
- @tanstack/openai-base@0.3.2
-
Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of
@tanstack/ai-openrouterfrom the shared OpenAI base. (#527)- New
chat({ outputSchema, stream: true })overload returningStructuredOutputStream<InferSchemaType<TSchema>>. The stream yields raw JSON deltas viaTEXT_MESSAGE_CONTENTplus a terminalCUSTOMstructured-output.completeevent whosevalue.objectis typed against the caller's schema with no helper or cast required. StructuredOutputStream<T>is a discriminated union over three taggedCUSTOMvariants —structured-output.complete<T>,approval-requested, andtool-input-available(newApprovalRequestedEvent/ToolInputAvailableEventinterfaces exported from@tanstack/ai). Narrowing onchunk.type === 'CUSTOM' && chunk.name === '<literal>'resolveschunk.valueto the exact shape per variant. The bareCustomEvent(withvalue: any) is deliberately excluded to keep the narrow from collapsing toany; user-emitted events via theemitCustomEventcontext API still flow at runtime and are documented as a small residual gap.- Activity-layer hardening: always-finalise after the stream loop (no silent hangs on missing
finishReason), typedRUN_ERRORon empty content, mid-stream provider errors terminate cleanly, schema-validation failures carryrunId / model / timestamp. fallbackStructuredOutputStreamin the activity layer is the single source of truth for adapters that don't implementstructuredOutputStreamnatively;BaseTextAdapterno longer ships a default.ChatStreamSummarizeAdapter.summarizeStreamaccumulates summary text and emits a terminalCUSTOMgeneration:resultevent before the finalRUN_FINISHED. FixesuseSummarizenever populatingresultover streaming connections (the client only setsresulton that specific CUSTOM event).SummarizationOptionsis now generic inTProviderOptionsandmodelOptionsis plumbed through end-to-end (previously silently dropped byrunSummarize/runStreamingSummarize).
useChat(React/Vue/Solid) andcreateChat(Svelte) now accept anoutputSchemaoption mirroringchat({ outputSchema })on the server. When supplied, the hook's return adds two managed reactive fields:partial— the live progressive object, typedDeepPartial<InferSchemaType<typeof outputSchema>>. Updated fromTEXT_MESSAGE_CONTENTdeltas viaparsePartialJSON. Resets on every new run.final— the validated terminal payload from thestructured-output.completeevent, typedInferSchemaType<typeof outputSchema> | null.nulluntil the run completes.
Both fields are typed against the schema with no helper or cast — each hook is generic on
TSchemaand conditionally adds the fields to the return type. WithoutoutputSchema, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming,partialstays{}andfinalsnaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already onmessages[…].parts(asThinkingPart,ToolCallPart,ToolResultPart), same as a normal chat. WhenoutputSchemais set, the assistant'sTextPartcontains the raw JSON the model produced; filtertextparts out of your message renderer and let the structured view (driven bypartial/final) replace it.Reactivity primitive per framework:
Framework partialtypefinaltypeReact ( @tanstack/ai-react)DeepPartial<T>(plain state)T | null(plain state)Vue ( @tanstack/ai-vue)Readonly<ShallowRef<DeepPartial<T>>>Readonly<ShallowRef<T | null>>Solid ( @tanstack/ai-solid)Accessor<DeepPartial<T>>Accessor<T | null>Svelte ( @tanstack/ai-svelte)readonly partial: DeepPartial<T>(rune-backed getter)readonly final: T | null(rune-backed getter)DeepPartial<T>is exported from each framework package for callers who want to annotate handlers explicitly.-
Package renamed from
@tanstack/ai-openai-compatible(which remains published for pinned lockfiles but receives no further updates). Imports change:- import { OpenAICompatibleChatCompletionsTextAdapter } from '@tanstack/ai-openai-compatible' + import { OpenAIBaseChatCompletionsTextAdapter } from '@tanstack/openai-base' - import { OpenAICompatibleResponsesTextAdapter } from '@tanstack/ai-openai-compatible' + import { OpenAIBaseResponsesTextAdapter } from '@tanstack/openai-base'
-
Centralised
structuredOutputStreamon both bases. Chat Completions usesresponse_format: { type: 'json_schema', strict: true }+stream: true; Responses usestext.format: { type: 'json_schema', strict: true }+stream: true. Subclasses (ai-openai,ai-grok,ai-groq) inherit it; OpenRouter implements its own (see below). -
Base now adopts the
openaiSDK directly and imports types fromopenai/resources/.... The previously-vendored ~720 LOC of wire-format types (ChatCompletion,ResponseStreamEvent, etc.) is removed; consumers that imported wire types from the package should import them from the openai SDK instead. The abstractcallChatCompletion*/callResponse*hooks are gone — the base constructor now takes a pre-builtOpenAIclient (new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)) and callsclient.chat.completions.create/client.responses.createitself. -
New protected
isAbortError(error)hook duck-types abort detection soRUN_ERROR { code: 'aborted' }is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g.@openrouter/sdk'sRequestAbortedError) override. -
Per-chunk
logger.provider(...)debug logging now fires insidestructuredOutputStreamloops, matching the existing pattern inchatStreamfor end-to-end introspection in debug mode.
The other extension hooks (
extractReasoning,extractTextFromResponse,processStreamChunks,makeStructuredOutputCompatible,transformStructuredOutput,mapOptionsToRequest,convertMessage) remain. Groq'sprocessStreamChunksandmakeStructuredOutputCompatibleoverrides (forx_groq.usagepromotion and Groq's structured-output schema quirks) are unchanged.Adapter API Reasoning surface @tanstack/ai-openaiopenaiTextResponses response.reasoning_text.delta+response.reasoning_summary_text.delta(requiresreasoning.summary: 'auto')@tanstack/ai-openaiopenaiChatCompletions(new)Chat Completions reasoning emitted silently — Chat Completions has no reasoning.summaryopt-in@tanstack/ai-grokgrokTextChat Completions delta.reasoning_content(DeepSeek convention; not typed by OpenAI SDK)@tanstack/ai-groqgroqTextChat Completions delta.reasoning(requiresreasoning_format: 'parsed'; not typed by groq-sdk)@tanstack/ai-openrouteropenRouterTextChat Completions delta.reasoningDetails(camelCase)@tanstack/ai-openrouteropenRouterResponsesText(beta)Responses (beta) response.reasoning_text.delta+response.reasoning_summary_text.deltavianormalizeStreamEventAll six emit the contractual
REASONING_*lifecycle (REASONING_START→REASONING_MESSAGE_START→REASONING_MESSAGE_CONTENTdeltas →REASONING_MESSAGE_END→REASONING_END) and close it beforeTEXT_MESSAGE_START. Accumulated reasoning is also surfaced onstructured-output.complete.value.reasoningfor consumers that only subscribe to the terminal event. OpenRouter SDK's proprietaryRequestAbortedErroris mapped (alongside DOMAbortError) tocode: 'aborted'in the two openrouter adapters.@tanstack/ai-openaialso exports a newOpenAIChatCompletionsTextAdapter/openaiChatCompletions/createOpenaiChatCompletionsfactory — a sibling to the existing Responses adapter for callers who want the older/v1/chat/completionswire format against the OpenAI SDK.OpenRouter ships its own SDK (
@openrouter/sdk) with a camelCase shape, so inheriting from the OpenAI-shaped base forced a snake_case ↔ camelCase round-trip on every request and stream event. ai-openrouter now extendsBaseTextAdapterdirectly and inlines its own stream processors (OpenRouterTextAdapterfor chat-completions,OpenRouterResponsesTextAdapterfor the Responses beta), reading OpenRouter's camelCase types natively. The@tanstack/openai-baseandopenaidependencies are removed from ai-openrouter; only@openrouter/sdk,@tanstack/ai, and@tanstack/ai-utilsremain. The ~300 LOC of inbound/outbound shape converters (toOpenRouterRequest,toChatCompletion,adaptOpenRouterStreamChunks,toSnakeResponseResult, …) are gone. Internal: duck-typedas { ... }casts on stream chunks inOpenRouterResponsesTextAdapterare replaced with direct narrowing via the SDK's discriminated unions.Public OpenRouter API is unchanged:
openRouterText,openRouterResponsesText,createOpenRouterText,createOpenRouterResponsesText, the OpenRouter tool factories, provider routing surface (provider,models,plugins,variant,transforms), app attribution headers (httpReferer,appTitle),:variantmodel suffixing,RequestAbortedErrorpropagation, and the OpenRouter-specific structured-output null-preservation all behave the same.ai-ollamaremains onBaseTextAdapterdirectly — its native API uses a different wire format from Chat Completions and was never on the shared base.Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a
ChatStreamSummarizeAdapter(formerlyChatStreamWrapperAdapter, renamed and exported from@tanstack/ai/activities) wrapping their own text adapter, matching the existing OpenAI/Grok pattern. Removes ~600 LOC of duplicated logic across the six providers and ensures behavioural parity.Bespoke
*SummarizeProviderOptionsinterfaces (e.g.OpenAISummarizeProviderOptions,AnthropicSummarizeProviderOptions,GeminiSummarizeProviderOptions,OllamaSummarizeProviderOptions,OpenRouterSummarizeProviderOptions) are removed from the provider packages' public exports. Consumers who imported them should switch to inferring the type from the adapter (InferTextProviderOptions<typeof adapter>) or remove the explicit annotation (it'll be inferred from the adapter argument).SummarizeAdapterinterface methods are now generic inTProviderOptions.summarizeandsummarizeStreampreviously tookSummarizationOptions(defaulted, somodelOptionswas effectivelyRecord<string, any>regardless of the adapter's typed shape). They now takeSummarizationOptions<TProviderOptions>. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers.SummarizationOptions,SummarizeAdapter,BaseSummarizeAdapter, andChatStreamSummarizeAdapterpreviously had a mixedRecord<string, any>/Record<string, unknown>/objectset of defaults forTProviderOptions; they now uniformly default toRecord<string, unknown>. - New
- Updated dependencies [
87f305c]:- @tanstack/ai@0.16.0
- @tanstack/openai-base@0.2.1
-
Internal refactor: every provider now delegates
getApiKeyFromEnv/generateId/transformNullsToUndefined/ModelMetahelpers to the new@tanstack/ai-utilspackage.ai-openaiandai-grokadditionally inherit OpenAI-compatible adapter base classes (Chat Completions / Responses text, image, summarize, transcription, TTS, video) from the new@tanstack/openai-basepackage;ai-groqkeeps its ownBaseTextAdapter-derived text adapter (Groq uses thegroq-sdk, not the OpenAI SDK) but consumes@tanstack/openai-base's schema converter and tool converters. The remaining providers (ai-anthropic,ai-gemini,ai-ollama,ai-openrouter,ai-fal,ai-elevenlabs) only consume@tanstack/ai-utilsbecause they speak provider-native protocols, not OpenAI-compatible ones. No breaking changes — all public APIs remain identical. (#409) -
Updated dependencies [
27c9aeb,27c9aeb]:- @tanstack/ai-utils@0.2.0
- @tanstack/openai-base@0.2.0
-
feat(ai-grok): add audio and speech adapters for xAI (#506)
Add three new tree-shakeable adapters that wrap xAI's audio APIs:
grokSpeech/createGrokSpeech— text-to-speech viaPOST /v1/tts. Supports the 5 xAI voices (eve,ara,rex,sal,leo), MP3/WAV/PCM/μ-law/A-law codecs, and thelanguage,sample_rate,bit_rate,optimize_streaming_latency,text_normalizationprovider options.grokTranscription/createGrokTranscription— speech-to-text viaPOST /v1/stt. Passes throughlanguage,diarize,multichannel,channels,audio_format, andsample_rate; maps xAI's word-level timestamps toTranscriptionResult.words.grokRealtime/grokRealtimeToken— Voice Agent (realtime) adapter forwss://api.x.ai/v1/realtimewith ephemeral tokens via/v1/realtime/client_secrets. Supports thegrok-voice-fast-1.0andgrok-voice-think-fast-1.0models.
New model identifier exports:
GROK_TTS_MODELS,GROK_TRANSCRIPTION_MODELS,GROK_REALTIME_MODELSand their corresponding types.
-
Tighten
GeneratedImageandGeneratedAudioto enforce exactly one ofurlorb64Jsonvia a mutually-exclusiveGeneratedMediaSourceunion. (#463)Both types previously declared
url?andb64Json?as independently optional, which allowed meaningless{}values and objects that set both fields. They now require exactly one:type GeneratedMediaSource = | { url: string; b64Json?: never } | { b64Json: string; url?: never }
Existing read patterns like
img.url || \data:image/png;base64,${img.b64Json}`continue to work unchanged. The only runtime-visible change is that the@tanstack/ai-openrouterand@tanstack/ai-falimage adapters no longer populateurlwith a synthesizeddata:image/png;base64,...URI when the provider returns base64 — they return{ b64Json }only. Consumers that want a data URI should build it fromb64Json` at render time. -
Updated dependencies [
54523f5,54523f5,af9eb7b,54523f5]:- @tanstack/ai@0.14.0
-
Wire each adapter's text, summarize, image, speech, transcription, and video paths through the new
InternalLoggerfrom@tanstack/ai/adapter-internals:logger.request(...)before each SDK call,logger.provider(...)for every chunk received, andlogger.errors(...)in catch blocks. Migrates all pre-existing ad-hocconsole.*calls in adapter catch blocks (including the OpenAI and ElevenLabs realtime adapters) onto the structured logger. No adapter factory or config-shape changes. (#467) -
Updated dependencies [
c1fd96f]:- @tanstack/ai@0.13.0
-
Expose the
/toolssubpath and add an emptysupports.tools: []channel per model so Grok adapters participate in the core tool-capability type gating. No provider-specific tool factories are exposed yet — define your own tools withtoolDefinition()from@tanstack/ai. (#466) -
Updated dependencies [
e32583e]:- @tanstack/ai@0.12.0
-
Align stream output with
@tanstack/ai's AG-UI-compliant event shapes: emitREASONING_*events alongsideSTEP_*, threadthreadId/runIdthroughRUN_STARTED/RUN_FINISHED, and return flatRunErrorEventshape. Cast raw events through an internalasChunkhelper so they line up with the re-exported@ag-ui/coreEventTypeenum. No changes to adapter factory signatures or config shapes. (#474) -
Updated dependencies [
12d43e5]:- @tanstack/ai@0.11.0
- Update model metadata from OpenRouter API (#433)
- Updated dependencies [
54abae0]:- @tanstack/ai@0.10.0
- Updated dependencies [
842e119]:- @tanstack/ai@0.9.0
- Updated dependencies [
f62eeb0]:- @tanstack/ai@0.8.0
- Updated dependencies [
86be1c8]:- @tanstack/ai@0.7.0
- Add in opus 4.6 and enhance acceptable config options by providers (#278)
-
re-release adapter packages (#263)
-
add multiple modalities support to the client (#263)
-
Updated dependencies [
0158d14]:- @tanstack/ai@0.4.0
-
feat: Add AG-UI protocol events to streaming system (#244)
All text adapters now emit AG-UI protocol events only:
RUN_STARTED/RUN_FINISHED- Run lifecycle eventsTEXT_MESSAGE_START/TEXT_MESSAGE_CONTENT/TEXT_MESSAGE_END- Text message streamingTOOL_CALL_START/TOOL_CALL_ARGS/TOOL_CALL_END- Tool call streaming
Only AG-UI event types are supported; previous legacy chunk formats (
content,tool_call,done, etc.) are no longer accepted.
- Updated dependencies [
e52135f]:- @tanstack/ai@0.3.0
- Add Grok (xAI) adapter support with
@tanstack/ai-grokpackage. This adapter provides access to xAI's Grok models including Grok 4.1, Grok 4, Grok 3, and image generation with Grok 2 Image. (#183)
- Initial release of Grok (xAI) adapter for TanStack AI