diff --git a/.changeset/streaming-structured-output.md b/.changeset/streaming-structured-output.md deleted file mode 100644 index d5a3d65e8..000000000 --- a/.changeset/streaming-structured-output.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -'@tanstack/ai': minor -'@tanstack/openai-base': minor -'@tanstack/ai-openai': minor -'@tanstack/ai-grok': minor -'@tanstack/ai-groq': minor -'@tanstack/ai-openrouter': minor -'@tanstack/ai-react': minor -'@tanstack/ai-vue': minor -'@tanstack/ai-solid': minor -'@tanstack/ai-svelte': minor -'@tanstack/ai-anthropic': patch -'@tanstack/ai-gemini': patch -'@tanstack/ai-ollama': patch ---- - -Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. - -## Core — `@tanstack/ai` - -- New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. -- `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. -- `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. -- `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). -- `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). - -## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` - -`useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: - -- `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. -- `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. - -Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. - -Reactivity primitive per framework: - -| Framework | `partial` type | `final` type | -| ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | -| React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | -| Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | -| Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | -| Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | - -`DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. - -## Base — `@tanstack/openai-base` - -- Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: - - ```diff - - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. -- New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. -- Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. - -The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. - -## Provider adapters - -| Adapter | API | Reasoning surface | -| ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | -| `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | -| `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | -| `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | -| `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | -| `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | -| `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | - -All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. - -`@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. - -## Decouple `@tanstack/ai-openrouter` from the OpenAI base - -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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. - -`ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. - -## Summarize subsystem - -Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). - -`SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. diff --git a/.changeset/update-fal-client-1.10.md b/.changeset/update-fal-client-1.10.md deleted file mode 100644 index 8a2e12e02..000000000 --- a/.changeset/update-fal-client-1.10.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@tanstack/ai': patch -'@tanstack/ai-fal': patch ---- - -Update `@fal-ai/client` dependency to `^1.10.1`. Picks up upstream retry-on-transport-error and proxy-runtime-gate improvements. No public adapter API changes — `fal.config`, `fal.subscribe`, `fal.queue.{submit,status,result}` are unchanged. - -The fal type-meta narrowing now covers four cases per model, each strict: - -- `aspect_ratio` + `resolution` → `"16:9_1080p"` style template literal -- `aspect_ratio` only → the aspect-ratio union (`"16:9" | "9:16" | …`) -- `resolution` only → the resolution union (`"1080p" | "1440p" | "2160p"`) — new -- neither → `undefined` (the model has no size knob, so you must omit `size`) - -For example, `fal-ai/ltx-2/text-to-video/fast` (resolution-only) now type-checks `size: '2160p'`, and `fal-ai/kling-video/v2.6/pro/image-to-video` (neither field) refuses any `size` value at compile time. - -The "neither" case uses `undefined` instead of `never` so the adapter class still satisfies the generic `VideoAdapter` (method-parameter contravariance: `any` can't flow into `never` but it does flow into `undefined`). - -To support the `undefined` slot, `@tanstack/ai`'s `BaseImageAdapter`/`BaseVideoAdapter` (and the matching `ImageGenerationOptions`/`VideoGenerationOptions` types) widen their `TSize` constraint from `extends string` to `extends string | undefined`. The default remains `string`, so existing adapters and call sites are unaffected. - -`Extract<…['aspect_ratio'], string>` filters out the new `AspectRatio` object type that upstream 1.10 introduced on `AgeModifyInput`/`CityTeleportInput`, keeping the template-literal sizes valid for those endpoints. diff --git a/examples/ts-svelte-chat/CHANGELOG.md b/examples/ts-svelte-chat/CHANGELOG.md index 2ccf5c501..05a81d892 100644 --- a/examples/ts-svelte-chat/CHANGELOG.md +++ b/examples/ts-svelte-chat/CHANGELOG.md @@ -1,5 +1,18 @@ # ts-svelte-chat +## 0.1.42 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-openai@0.9.0 + - @tanstack/ai-svelte@0.8.0 + - @tanstack/ai-anthropic@0.8.7 + - @tanstack/ai-gemini@0.10.4 + - @tanstack/ai-ollama@0.6.14 + - @tanstack/ai-client@0.9.2 + ## 0.1.41 ### Patch Changes diff --git a/examples/ts-svelte-chat/package.json b/examples/ts-svelte-chat/package.json index 3c5e5ddf5..94ff5d905 100644 --- a/examples/ts-svelte-chat/package.json +++ b/examples/ts-svelte-chat/package.json @@ -1,7 +1,7 @@ { "name": "ts-svelte-chat", "private": true, - "version": "0.1.41", + "version": "0.1.42", "type": "module", "scripts": { "dev": "vite dev --port 3000", diff --git a/examples/ts-vue-chat/CHANGELOG.md b/examples/ts-vue-chat/CHANGELOG.md index fff18882d..a60afc5e4 100644 --- a/examples/ts-vue-chat/CHANGELOG.md +++ b/examples/ts-vue-chat/CHANGELOG.md @@ -1,5 +1,19 @@ # ts-vue-chat +## 0.1.42 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-openai@0.9.0 + - @tanstack/ai-vue@0.8.0 + - @tanstack/ai-anthropic@0.8.7 + - @tanstack/ai-gemini@0.10.4 + - @tanstack/ai-ollama@0.6.14 + - @tanstack/ai-client@0.9.2 + - @tanstack/ai-vue-ui@0.1.34 + ## 0.1.41 ### Patch Changes diff --git a/examples/ts-vue-chat/package.json b/examples/ts-vue-chat/package.json index bb438f476..49de98129 100644 --- a/examples/ts-vue-chat/package.json +++ b/examples/ts-vue-chat/package.json @@ -1,6 +1,6 @@ { "name": "ts-vue-chat", - "version": "0.1.41", + "version": "0.1.42", "private": true, "type": "module", "scripts": { diff --git a/examples/vanilla-chat/CHANGELOG.md b/examples/vanilla-chat/CHANGELOG.md index b522b9713..fd2d2714a 100644 --- a/examples/vanilla-chat/CHANGELOG.md +++ b/examples/vanilla-chat/CHANGELOG.md @@ -1,5 +1,12 @@ # vanilla-chat +## 0.0.38 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/ai-client@0.9.2 + ## 0.0.37 ### Patch Changes diff --git a/examples/vanilla-chat/package.json b/examples/vanilla-chat/package.json index 7d6da639c..63fc38985 100644 --- a/examples/vanilla-chat/package.json +++ b/examples/vanilla-chat/package.json @@ -2,7 +2,7 @@ "name": "vanilla-chat", "private": true, "type": "module", - "version": "0.0.37", + "version": "0.0.38", "scripts": { "start": "vite --port 3001", "dev": "vite --port 3001", diff --git a/packages/typescript/ai-anthropic/CHANGELOG.md b/packages/typescript/ai-anthropic/CHANGELOG.md index 7046fd959..7ac73df90 100644 --- a/packages/typescript/ai-anthropic/CHANGELOG.md +++ b/packages/typescript/ai-anthropic/CHANGELOG.md @@ -1,5 +1,89 @@ # @tanstack/ai-anthropic +## 0.8.7 + +### Patch Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + ## 0.8.6 ### Patch Changes diff --git a/packages/typescript/ai-anthropic/package.json b/packages/typescript/ai-anthropic/package.json index 5da56144c..b602b2b13 100644 --- a/packages/typescript/ai-anthropic/package.json +++ b/packages/typescript/ai-anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-anthropic", - "version": "0.8.6", + "version": "0.8.7", "description": "Anthropic Claude adapter for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-client/CHANGELOG.md b/packages/typescript/ai-client/CHANGELOG.md index 64d2652c6..44d5519ad 100644 --- a/packages/typescript/ai-client/CHANGELOG.md +++ b/packages/typescript/ai-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/ai-client +## 0.9.2 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-event-client@0.3.1 + ## 0.9.1 ### Patch Changes diff --git a/packages/typescript/ai-client/package.json b/packages/typescript/ai-client/package.json index 55c0eedec..e8ac5cde9 100644 --- a/packages/typescript/ai-client/package.json +++ b/packages/typescript/ai-client/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-client", - "version": "0.9.1", + "version": "0.9.2", "description": "Framework-agnostic headless client for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-code-mode-skills/CHANGELOG.md b/packages/typescript/ai-code-mode-skills/CHANGELOG.md index bc08b87c5..00c1e0f07 100644 --- a/packages/typescript/ai-code-mode-skills/CHANGELOG.md +++ b/packages/typescript/ai-code-mode-skills/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/ai-code-mode-skills +## 0.1.11 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-code-mode@0.1.11 + ## 0.1.10 ### Patch Changes diff --git a/packages/typescript/ai-code-mode-skills/package.json b/packages/typescript/ai-code-mode-skills/package.json index e9c90d1b9..dd4089922 100644 --- a/packages/typescript/ai-code-mode-skills/package.json +++ b/packages/typescript/ai-code-mode-skills/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-code-mode-skills", - "version": "0.1.10", + "version": "0.1.11", "description": "Persistent skill library for TanStack AI Code Mode - LLM-created reusable code snippets", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-code-mode/CHANGELOG.md b/packages/typescript/ai-code-mode/CHANGELOG.md index 16bac1841..a8f7182b9 100644 --- a/packages/typescript/ai-code-mode/CHANGELOG.md +++ b/packages/typescript/ai-code-mode/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/ai-code-mode +## 0.1.11 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + ## 0.1.10 ### Patch Changes diff --git a/packages/typescript/ai-code-mode/models-eval/CHANGELOG.md b/packages/typescript/ai-code-mode/models-eval/CHANGELOG.md index 1620171c7..13d73f4b8 100644 --- a/packages/typescript/ai-code-mode/models-eval/CHANGELOG.md +++ b/packages/typescript/ai-code-mode/models-eval/CHANGELOG.md @@ -1,5 +1,20 @@ # @tanstack/ai-code-mode-models-eval +## 0.0.16 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-openai@0.9.0 + - @tanstack/ai-grok@0.8.0 + - @tanstack/ai-groq@0.2.0 + - @tanstack/ai-anthropic@0.8.7 + - @tanstack/ai-gemini@0.10.4 + - @tanstack/ai-ollama@0.6.14 + - @tanstack/ai-code-mode@0.1.11 + - @tanstack/ai-isolate-node@0.1.11 + ## 0.0.15 ### Patch Changes diff --git a/packages/typescript/ai-code-mode/models-eval/package.json b/packages/typescript/ai-code-mode/models-eval/package.json index 976dda1c8..b77f6a3df 100644 --- a/packages/typescript/ai-code-mode/models-eval/package.json +++ b/packages/typescript/ai-code-mode/models-eval/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-code-mode-models-eval", - "version": "0.0.15", + "version": "0.0.16", "private": true, "type": "module", "description": "Dev benchmark: local/cloud models vs code-mode database-demo gold (not published)", diff --git a/packages/typescript/ai-code-mode/package.json b/packages/typescript/ai-code-mode/package.json index 4e9d87f41..669c51409 100644 --- a/packages/typescript/ai-code-mode/package.json +++ b/packages/typescript/ai-code-mode/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-code-mode", - "version": "0.1.10", + "version": "0.1.11", "description": "Code Mode for TanStack AI - LLM-driven code execution in secure sandboxes", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-devtools/CHANGELOG.md b/packages/typescript/ai-devtools/CHANGELOG.md index 6817e513d..efbae6b66 100644 --- a/packages/typescript/ai-devtools/CHANGELOG.md +++ b/packages/typescript/ai-devtools/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/ai-devtools-core +## 0.3.28 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-event-client@0.3.1 + ## 0.3.27 ### Patch Changes diff --git a/packages/typescript/ai-devtools/package.json b/packages/typescript/ai-devtools/package.json index 1900668aa..15b6b0c4b 100644 --- a/packages/typescript/ai-devtools/package.json +++ b/packages/typescript/ai-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-devtools-core", - "version": "0.3.27", + "version": "0.3.28", "description": "Core TanStack AI Devtools", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-elevenlabs/CHANGELOG.md b/packages/typescript/ai-elevenlabs/CHANGELOG.md index c9756b433..c01656641 100644 --- a/packages/typescript/ai-elevenlabs/CHANGELOG.md +++ b/packages/typescript/ai-elevenlabs/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/ai-elevenlabs +## 0.2.4 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-client@0.9.2 + ## 0.2.3 ### Patch Changes diff --git a/packages/typescript/ai-elevenlabs/package.json b/packages/typescript/ai-elevenlabs/package.json index e90cb1336..9b15e0982 100644 --- a/packages/typescript/ai-elevenlabs/package.json +++ b/packages/typescript/ai-elevenlabs/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-elevenlabs", - "version": "0.2.3", + "version": "0.2.4", "description": "ElevenLabs adapter for TanStack AI realtime voice", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-event-client/CHANGELOG.md b/packages/typescript/ai-event-client/CHANGELOG.md index 81af62a47..17a3e3b22 100644 --- a/packages/typescript/ai-event-client/CHANGELOG.md +++ b/packages/typescript/ai-event-client/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/ai-event-client +## 0.3.1 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + ## 0.3.0 ### Minor Changes diff --git a/packages/typescript/ai-event-client/package.json b/packages/typescript/ai-event-client/package.json index 4d8dd33d0..cc084f528 100644 --- a/packages/typescript/ai-event-client/package.json +++ b/packages/typescript/ai-event-client/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-event-client", - "version": "0.3.0", + "version": "0.3.1", "description": "Event client for TanStack AI devtools integration", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-fal/CHANGELOG.md b/packages/typescript/ai-fal/CHANGELOG.md index 9a5211d4b..5e284413a 100644 --- a/packages/typescript/ai-fal/CHANGELOG.md +++ b/packages/typescript/ai-fal/CHANGELOG.md @@ -1,5 +1,28 @@ # @tanstack/ai-fal +## 0.7.4 + +### Patch Changes + +- Update `@fal-ai/client` dependency to `^1.10.1`. Picks up upstream retry-on-transport-error and proxy-runtime-gate improvements. No public adapter API changes — `fal.config`, `fal.subscribe`, `fal.queue.{submit,status,result}` are unchanged. ([#556](https://github.com/TanStack/ai/pull/556)) + + The fal type-meta narrowing now covers four cases per model, each strict: + - `aspect_ratio` + `resolution` → `"16:9_1080p"` style template literal + - `aspect_ratio` only → the aspect-ratio union (`"16:9" | "9:16" | …`) + - `resolution` only → the resolution union (`"1080p" | "1440p" | "2160p"`) — new + - neither → `undefined` (the model has no size knob, so you must omit `size`) + + For example, `fal-ai/ltx-2/text-to-video/fast` (resolution-only) now type-checks `size: '2160p'`, and `fal-ai/kling-video/v2.6/pro/image-to-video` (neither field) refuses any `size` value at compile time. + + The "neither" case uses `undefined` instead of `never` so the adapter class still satisfies the generic `VideoAdapter` (method-parameter contravariance: `any` can't flow into `never` but it does flow into `undefined`). + + To support the `undefined` slot, `@tanstack/ai`'s `BaseImageAdapter`/`BaseVideoAdapter` (and the matching `ImageGenerationOptions`/`VideoGenerationOptions` types) widen their `TSize` constraint from `extends string` to `extends string | undefined`. The default remains `string`, so existing adapters and call sites are unaffected. + + `Extract<…['aspect_ratio'], string>` filters out the new `AspectRatio` object type that upstream 1.10 introduced on `AgeModifyInput`/`CityTeleportInput`, keeping the template-literal sizes valid for those endpoints. + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + ## 0.7.3 ### Patch Changes diff --git a/packages/typescript/ai-fal/package.json b/packages/typescript/ai-fal/package.json index e5482bd3f..c08b7e387 100644 --- a/packages/typescript/ai-fal/package.json +++ b/packages/typescript/ai-fal/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-fal", - "version": "0.7.3", + "version": "0.7.4", "description": "fal.ai adapter for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-gemini/CHANGELOG.md b/packages/typescript/ai-gemini/CHANGELOG.md index 95ad1b2cb..6527c5910 100644 --- a/packages/typescript/ai-gemini/CHANGELOG.md +++ b/packages/typescript/ai-gemini/CHANGELOG.md @@ -1,5 +1,89 @@ # @tanstack/ai-gemini +## 0.10.4 + +### Patch Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + ## 0.10.3 ### Patch Changes diff --git a/packages/typescript/ai-gemini/package.json b/packages/typescript/ai-gemini/package.json index 7b99786ef..a8e18895f 100644 --- a/packages/typescript/ai-gemini/package.json +++ b/packages/typescript/ai-gemini/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-gemini", - "version": "0.10.3", + "version": "0.10.4", "description": "Google Gemini adapter for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-grok/CHANGELOG.md b/packages/typescript/ai-grok/CHANGELOG.md index 044828cc7..ed286262a 100644 --- a/packages/typescript/ai-grok/CHANGELOG.md +++ b/packages/typescript/ai-grok/CHANGELOG.md @@ -1,5 +1,92 @@ # @tanstack/ai-grok +## 0.8.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/openai-base@0.3.0 + ## 0.7.3 ### Patch Changes diff --git a/packages/typescript/ai-grok/package.json b/packages/typescript/ai-grok/package.json index 559b8f740..867fb910b 100644 --- a/packages/typescript/ai-grok/package.json +++ b/packages/typescript/ai-grok/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-grok", - "version": "0.7.3", + "version": "0.8.0", "description": "Grok (xAI) adapter for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-groq/CHANGELOG.md b/packages/typescript/ai-groq/CHANGELOG.md index d6977b311..ba01e91aa 100644 --- a/packages/typescript/ai-groq/CHANGELOG.md +++ b/packages/typescript/ai-groq/CHANGELOG.md @@ -1,5 +1,92 @@ # @tanstack/ai-groq +## 0.2.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/openai-base@0.3.0 + ## 0.1.11 ### Patch Changes diff --git a/packages/typescript/ai-groq/package.json b/packages/typescript/ai-groq/package.json index dee3e860e..97be33af6 100644 --- a/packages/typescript/ai-groq/package.json +++ b/packages/typescript/ai-groq/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-groq", - "version": "0.1.11", + "version": "0.2.0", "type": "module", "description": "Groq adapter for TanStack AI", "author": "", diff --git a/packages/typescript/ai-isolate-cloudflare/CHANGELOG.md b/packages/typescript/ai-isolate-cloudflare/CHANGELOG.md index 9b5d072db..a46d6eb61 100644 --- a/packages/typescript/ai-isolate-cloudflare/CHANGELOG.md +++ b/packages/typescript/ai-isolate-cloudflare/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/ai-isolate-cloudflare +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/ai-code-mode@0.1.11 + ## 0.2.1 ### Patch Changes diff --git a/packages/typescript/ai-isolate-cloudflare/package.json b/packages/typescript/ai-isolate-cloudflare/package.json index cab17cd63..9e22c9888 100644 --- a/packages/typescript/ai-isolate-cloudflare/package.json +++ b/packages/typescript/ai-isolate-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-isolate-cloudflare", - "version": "0.2.1", + "version": "0.2.2", "description": "Cloudflare Workers driver for TanStack AI Code Mode - execute code on the edge", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-isolate-node/CHANGELOG.md b/packages/typescript/ai-isolate-node/CHANGELOG.md index 3c84d942b..9c6a79f86 100644 --- a/packages/typescript/ai-isolate-node/CHANGELOG.md +++ b/packages/typescript/ai-isolate-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/ai-isolate-node +## 0.1.11 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/ai-code-mode@0.1.11 + ## 0.1.10 ### Patch Changes diff --git a/packages/typescript/ai-isolate-node/package.json b/packages/typescript/ai-isolate-node/package.json index de9289c49..d140a963d 100644 --- a/packages/typescript/ai-isolate-node/package.json +++ b/packages/typescript/ai-isolate-node/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-isolate-node", - "version": "0.1.10", + "version": "0.1.11", "description": "Node.js isolated-vm driver for TanStack AI Code Mode", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-isolate-quickjs/CHANGELOG.md b/packages/typescript/ai-isolate-quickjs/CHANGELOG.md index bbbee1fd4..a88b484f2 100644 --- a/packages/typescript/ai-isolate-quickjs/CHANGELOG.md +++ b/packages/typescript/ai-isolate-quickjs/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/ai-isolate-quickjs +## 0.1.11 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/ai-code-mode@0.1.11 + ## 0.1.10 ### Patch Changes diff --git a/packages/typescript/ai-isolate-quickjs/package.json b/packages/typescript/ai-isolate-quickjs/package.json index 3b016f3c4..9f5c47817 100644 --- a/packages/typescript/ai-isolate-quickjs/package.json +++ b/packages/typescript/ai-isolate-quickjs/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-isolate-quickjs", - "version": "0.1.10", + "version": "0.1.11", "description": "QuickJS WASM driver for TanStack AI Code Mode - runs everywhere", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-ollama/CHANGELOG.md b/packages/typescript/ai-ollama/CHANGELOG.md index aad6d0757..9b7b2b031 100644 --- a/packages/typescript/ai-ollama/CHANGELOG.md +++ b/packages/typescript/ai-ollama/CHANGELOG.md @@ -1,5 +1,89 @@ # @tanstack/ai-ollama +## 0.6.14 + +### Patch Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + ## 0.6.13 ### Patch Changes diff --git a/packages/typescript/ai-ollama/package.json b/packages/typescript/ai-ollama/package.json index 2b48fa855..80f22d7bf 100644 --- a/packages/typescript/ai-ollama/package.json +++ b/packages/typescript/ai-ollama/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-ollama", - "version": "0.6.13", + "version": "0.6.14", "description": "Ollama adapter for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-openai/CHANGELOG.md b/packages/typescript/ai-openai/CHANGELOG.md index 366c64ee2..c3abca947 100644 --- a/packages/typescript/ai-openai/CHANGELOG.md +++ b/packages/typescript/ai-openai/CHANGELOG.md @@ -1,5 +1,93 @@ # @tanstack/ai-openai +## 0.9.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/openai-base@0.3.0 + - @tanstack/ai-client@0.9.2 + ## 0.8.5 ### Patch Changes diff --git a/packages/typescript/ai-openai/package.json b/packages/typescript/ai-openai/package.json index 9a3a24895..c25c82c5c 100644 --- a/packages/typescript/ai-openai/package.json +++ b/packages/typescript/ai-openai/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-openai", - "version": "0.8.5", + "version": "0.9.0", "description": "OpenAI adapter for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-openrouter/CHANGELOG.md b/packages/typescript/ai-openrouter/CHANGELOG.md index 47a44a567..60ca5b81c 100644 --- a/packages/typescript/ai-openrouter/CHANGELOG.md +++ b/packages/typescript/ai-openrouter/CHANGELOG.md @@ -1,5 +1,91 @@ # @tanstack/ai-openrouter +## 0.9.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + ## 0.8.5 ### Patch Changes diff --git a/packages/typescript/ai-openrouter/package.json b/packages/typescript/ai-openrouter/package.json index a8c82d4ec..c791a902f 100644 --- a/packages/typescript/ai-openrouter/package.json +++ b/packages/typescript/ai-openrouter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-openrouter", - "version": "0.8.5", + "version": "0.9.0", "description": "OpenRouter adapter for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-preact/CHANGELOG.md b/packages/typescript/ai-preact/CHANGELOG.md index b50a4e1b0..8b79ec917 100644 --- a/packages/typescript/ai-preact/CHANGELOG.md +++ b/packages/typescript/ai-preact/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/ai-preact +## 0.6.23 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-client@0.9.2 + ## 0.6.22 ### Patch Changes diff --git a/packages/typescript/ai-preact/package.json b/packages/typescript/ai-preact/package.json index 2d7f576fe..c1b0083a3 100644 --- a/packages/typescript/ai-preact/package.json +++ b/packages/typescript/ai-preact/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-preact", - "version": "0.6.22", + "version": "0.6.23", "description": "Preact hooks for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-react-ui/CHANGELOG.md b/packages/typescript/ai-react-ui/CHANGELOG.md index db149494f..fb3760675 100644 --- a/packages/typescript/ai-react-ui/CHANGELOG.md +++ b/packages/typescript/ai-react-ui/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/ai-react-ui +## 0.6.4 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec)]: + - @tanstack/ai-react@0.9.0 + - @tanstack/ai-client@0.9.2 + ## 0.6.3 ### Patch Changes diff --git a/packages/typescript/ai-react-ui/package.json b/packages/typescript/ai-react-ui/package.json index c8b065d85..79c84559a 100644 --- a/packages/typescript/ai-react-ui/package.json +++ b/packages/typescript/ai-react-ui/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-react-ui", - "version": "0.6.3", + "version": "0.6.4", "description": "Headless React components for building AI chat interfaces", "module": "./dist/esm/index.js", "types": "./dist/esm/index.d.ts", diff --git a/packages/typescript/ai-react/CHANGELOG.md b/packages/typescript/ai-react/CHANGELOG.md index 2a281e167..fb0f59240 100644 --- a/packages/typescript/ai-react/CHANGELOG.md +++ b/packages/typescript/ai-react/CHANGELOG.md @@ -1,5 +1,92 @@ # @tanstack/ai-react +## 0.9.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-client@0.9.2 + ## 0.8.2 ### Patch Changes diff --git a/packages/typescript/ai-react/package.json b/packages/typescript/ai-react/package.json index 0b6749375..db4af86f7 100644 --- a/packages/typescript/ai-react/package.json +++ b/packages/typescript/ai-react/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-react", - "version": "0.8.2", + "version": "0.9.0", "description": "React hooks for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-solid-ui/CHANGELOG.md b/packages/typescript/ai-solid-ui/CHANGELOG.md index 4164d8085..fb07d5fd7 100644 --- a/packages/typescript/ai-solid-ui/CHANGELOG.md +++ b/packages/typescript/ai-solid-ui/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/ai-solid-ui +## 0.6.4 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec)]: + - @tanstack/ai-solid@0.8.0 + - @tanstack/ai-client@0.9.2 + ## 0.6.3 ### Patch Changes diff --git a/packages/typescript/ai-solid-ui/package.json b/packages/typescript/ai-solid-ui/package.json index 95c1f511f..28be6a87e 100644 --- a/packages/typescript/ai-solid-ui/package.json +++ b/packages/typescript/ai-solid-ui/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-solid-ui", - "version": "0.6.3", + "version": "0.6.4", "description": "Headless Solid components for building AI chat interfaces", "module": "./src/index.ts", "types": "./src/index.ts", diff --git a/packages/typescript/ai-solid/CHANGELOG.md b/packages/typescript/ai-solid/CHANGELOG.md index baabfd0bf..b3a376265 100644 --- a/packages/typescript/ai-solid/CHANGELOG.md +++ b/packages/typescript/ai-solid/CHANGELOG.md @@ -1,5 +1,92 @@ # @tanstack/ai-solid +## 0.8.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-client@0.9.2 + ## 0.7.2 ### Patch Changes diff --git a/packages/typescript/ai-solid/package.json b/packages/typescript/ai-solid/package.json index 5573e7666..ce0ab1e09 100644 --- a/packages/typescript/ai-solid/package.json +++ b/packages/typescript/ai-solid/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-solid", - "version": "0.7.2", + "version": "0.8.0", "description": "Solid hooks for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-svelte/CHANGELOG.md b/packages/typescript/ai-svelte/CHANGELOG.md index 614e016b2..4f26fd654 100644 --- a/packages/typescript/ai-svelte/CHANGELOG.md +++ b/packages/typescript/ai-svelte/CHANGELOG.md @@ -1,5 +1,92 @@ # @tanstack/ai-svelte +## 0.8.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-client@0.9.2 + ## 0.7.2 ### Patch Changes diff --git a/packages/typescript/ai-svelte/package.json b/packages/typescript/ai-svelte/package.json index 3d5288450..fac570246 100644 --- a/packages/typescript/ai-svelte/package.json +++ b/packages/typescript/ai-svelte/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-svelte", - "version": "0.7.2", + "version": "0.8.0", "description": "Svelte bindings for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai-vue-ui/CHANGELOG.md b/packages/typescript/ai-vue-ui/CHANGELOG.md index b54fa4eda..6242a72de 100644 --- a/packages/typescript/ai-vue-ui/CHANGELOG.md +++ b/packages/typescript/ai-vue-ui/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/ai-vue-ui +## 0.1.34 + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec)]: + - @tanstack/ai-vue@0.8.0 + ## 0.1.33 ### Patch Changes diff --git a/packages/typescript/ai-vue-ui/package.json b/packages/typescript/ai-vue-ui/package.json index aeeb715d9..7efd11a02 100644 --- a/packages/typescript/ai-vue-ui/package.json +++ b/packages/typescript/ai-vue-ui/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-vue-ui", - "version": "0.1.33", + "version": "0.1.34", "description": "Headless Vue components for building AI chat interfaces", "module": "./src/index.ts", "types": "./src/index.ts", diff --git a/packages/typescript/ai-vue/CHANGELOG.md b/packages/typescript/ai-vue/CHANGELOG.md index 55143b42e..d68b8aee5 100644 --- a/packages/typescript/ai-vue/CHANGELOG.md +++ b/packages/typescript/ai-vue/CHANGELOG.md @@ -1,5 +1,92 @@ # @tanstack/ai-vue +## 0.8.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + - @tanstack/ai-client@0.9.2 + ## 0.7.2 ### Patch Changes diff --git a/packages/typescript/ai-vue/package.json b/packages/typescript/ai-vue/package.json index 039fa657d..93d5ad8e7 100644 --- a/packages/typescript/ai-vue/package.json +++ b/packages/typescript/ai-vue/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai-vue", - "version": "0.7.2", + "version": "0.8.0", "description": "Vue hooks for TanStack AI", "author": "", "license": "MIT", diff --git a/packages/typescript/ai/CHANGELOG.md b/packages/typescript/ai/CHANGELOG.md index d7de7bb9b..e638a489e 100644 --- a/packages/typescript/ai/CHANGELOG.md +++ b/packages/typescript/ai/CHANGELOG.md @@ -1,5 +1,107 @@ # @tanstack/ai +## 0.17.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Update `@fal-ai/client` dependency to `^1.10.1`. Picks up upstream retry-on-transport-error and proxy-runtime-gate improvements. No public adapter API changes — `fal.config`, `fal.subscribe`, `fal.queue.{submit,status,result}` are unchanged. ([#556](https://github.com/TanStack/ai/pull/556)) + + The fal type-meta narrowing now covers four cases per model, each strict: + - `aspect_ratio` + `resolution` → `"16:9_1080p"` style template literal + - `aspect_ratio` only → the aspect-ratio union (`"16:9" | "9:16" | …`) + - `resolution` only → the resolution union (`"1080p" | "1440p" | "2160p"`) — new + - neither → `undefined` (the model has no size knob, so you must omit `size`) + + For example, `fal-ai/ltx-2/text-to-video/fast` (resolution-only) now type-checks `size: '2160p'`, and `fal-ai/kling-video/v2.6/pro/image-to-video` (neither field) refuses any `size` value at compile time. + + The "neither" case uses `undefined` instead of `never` so the adapter class still satisfies the generic `VideoAdapter` (method-parameter contravariance: `any` can't flow into `never` but it does flow into `undefined`). + + To support the `undefined` slot, `@tanstack/ai`'s `BaseImageAdapter`/`BaseVideoAdapter` (and the matching `ImageGenerationOptions`/`VideoGenerationOptions` types) widen their `TSize` constraint from `extends string` to `extends string | undefined`. The default remains `string`, so existing adapters and call sites are unaffected. + + `Extract<…['aspect_ratio'], string>` filters out the new `AspectRatio` object type that upstream 1.10 introduced on `AgeModifyInput`/`CityTeleportInput`, keeping the template-literal sizes valid for those endpoints. + +- Updated dependencies []: + - @tanstack/ai-event-client@0.3.1 + ## 0.16.0 ### Minor Changes diff --git a/packages/typescript/ai/package.json b/packages/typescript/ai/package.json index 228c69805..38dbde919 100644 --- a/packages/typescript/ai/package.json +++ b/packages/typescript/ai/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/ai", - "version": "0.16.0", + "version": "0.17.0", "description": "Core TanStack AI library - Open source AI SDK", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/typescript/openai-base/CHANGELOG.md b/packages/typescript/openai-base/CHANGELOG.md index e6aaa3e75..d102bd57e 100644 --- a/packages/typescript/openai-base/CHANGELOG.md +++ b/packages/typescript/openai-base/CHANGELOG.md @@ -1,5 +1,91 @@ # @tanstack/openai-base +## 0.3.0 + +### Minor Changes + +- Streaming structured output across the OpenAI-compatible providers, an OpenAI Chat Completions sibling adapter, a summarize-subsystem unification, and the decoupling of `@tanstack/ai-openrouter` from the shared OpenAI base. ([#527](https://github.com/TanStack/ai/pull/527)) + + ## Core — `@tanstack/ai` + - New `chat({ outputSchema, stream: true })` overload returning `StructuredOutputStream>`. The stream yields raw JSON deltas via `TEXT_MESSAGE_CONTENT` plus a terminal `CUSTOM` `structured-output.complete` event whose `value.object` is typed against the caller's schema with no helper or cast required. + - `StructuredOutputStream` is a discriminated union over three tagged `CUSTOM` variants — `structured-output.complete`, `approval-requested`, and `tool-input-available` (new `ApprovalRequestedEvent` / `ToolInputAvailableEvent` interfaces exported from `@tanstack/ai`). Narrowing on `chunk.type === 'CUSTOM' && chunk.name === ''` resolves `chunk.value` to the exact shape per variant. The bare `CustomEvent` (with `value: any`) is deliberately excluded to keep the narrow from collapsing to `any`; user-emitted events via the `emitCustomEvent` context 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`), typed `RUN_ERROR` on empty content, mid-stream provider errors terminate cleanly, schema-validation failures carry `runId / model / timestamp`. + - `fallbackStructuredOutputStream` in the activity layer is the single source of truth for adapters that don't implement `structuredOutputStream` natively; `BaseTextAdapter` no longer ships a default. + - `ChatStreamSummarizeAdapter.summarizeStream` accumulates summary text and emits a terminal `CUSTOM` `generation:result` event before the final `RUN_FINISHED`. Fixes `useSummarize` never populating `result` over streaming connections (the client only sets `result` on that specific CUSTOM event). + - `SummarizationOptions` is now generic in `TProviderOptions` and `modelOptions` is plumbed through end-to-end (previously silently dropped by `runSummarize` / `runStreamingSummarize`). + + ## Framework hooks — `@tanstack/ai-react`, `@tanstack/ai-vue`, `@tanstack/ai-solid`, `@tanstack/ai-svelte` + + `useChat` (React/Vue/Solid) and `createChat` (Svelte) now accept an `outputSchema` option mirroring `chat({ outputSchema })` on the server. When supplied, the hook's return adds two managed reactive fields: + - `partial` — the live progressive object, typed `DeepPartial>`. Updated from `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON`. Resets on every new run. + - `final` — the validated terminal payload from the `structured-output.complete` event, typed `InferSchemaType | null`. `null` until the run completes. + + Both fields are typed against the schema with no helper or cast — each hook is generic on `TSchema` and conditionally adds the fields to the return type. Without `outputSchema`, the return type is unchanged. Works the same for streaming and non-streaming endpoints — for non-streaming, `partial` stays `{}` and `final` snaps when the single terminal event arrives. Reasoning text and tool calls aren't surfaced as separate hook fields — they're already on `messages[…].parts` (as `ThinkingPart`, `ToolCallPart`, `ToolResultPart`), same as a normal chat. When `outputSchema` is set, the assistant's `TextPart` contains the raw JSON the model produced; filter `text` parts out of your message renderer and let the structured view (driven by `partial` / `final`) replace it. + + Reactivity primitive per framework: + + | Framework | `partial` type | `final` type | + | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------ | + | React (`@tanstack/ai-react`) | `DeepPartial` (plain state) | `T \| null` (plain state) | + | Vue (`@tanstack/ai-vue`) | `Readonly>>` | `Readonly>` | + | Solid (`@tanstack/ai-solid`) | `Accessor>` | `Accessor` | + | Svelte (`@tanstack/ai-svelte`) | `readonly partial: DeepPartial` (rune-backed getter) | `readonly final: T \| null` (rune-backed getter) | + + `DeepPartial` is exported from each framework package for callers who want to annotate handlers explicitly. + + ## Base — `@tanstack/openai-base` + - Package renamed from `@tanstack/ai-openai-compatible` (which remains published for pinned lockfiles but receives no further updates). Imports change: + + ```diff + - 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 `structuredOutputStream` on both bases. Chat Completions uses `response_format: { type: 'json_schema', strict: true }` + `stream: true`; Responses uses `text.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 `openai` SDK directly and imports types from `openai/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 abstract `callChatCompletion*` / `callResponse*` hooks are gone — the base constructor now takes a pre-built `OpenAI` client (`new OpenAIBaseChatCompletionsTextAdapter(model, name, openaiClient)`) and calls `client.chat.completions.create` / `client.responses.create` itself. + - New protected `isAbortError(error)` hook duck-types abort detection so `RUN_ERROR { code: 'aborted' }` is emitted consistently across SDK error types — subclasses with proprietary error classes (e.g. `@openrouter/sdk`'s `RequestAbortedError`) override. + - Per-chunk `logger.provider(...)` debug logging now fires inside `structuredOutputStream` loops, matching the existing pattern in `chatStream` for end-to-end introspection in debug mode. + + The other extension hooks (`extractReasoning`, `extractTextFromResponse`, `processStreamChunks`, `makeStructuredOutputCompatible`, `transformStructuredOutput`, `mapOptionsToRequest`, `convertMessage`) remain. Groq's `processStreamChunks` and `makeStructuredOutputCompatible` overrides (for `x_groq.usage` promotion and Groq's structured-output schema quirks) are unchanged. + + ## Provider adapters + + | Adapter | API | Reasoning surface | + | ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | + | `@tanstack/ai-openai` `openaiText` | Responses | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` (requires `reasoning.summary: 'auto'`) | + | `@tanstack/ai-openai` `openaiChatCompletions` (new) | Chat Completions | reasoning emitted silently — Chat Completions has no `reasoning.summary` opt-in | + | `@tanstack/ai-grok` `grokText` | Chat Completions | `delta.reasoning_content` (DeepSeek convention; not typed by OpenAI SDK) | + | `@tanstack/ai-groq` `groqText` | Chat Completions | `delta.reasoning` (requires `reasoning_format: 'parsed'`; not typed by groq-sdk) | + | `@tanstack/ai-openrouter` `openRouterText` | Chat Completions | `delta.reasoningDetails` (camelCase) | + | `@tanstack/ai-openrouter` `openRouterResponsesText` (beta) | Responses (beta) | `response.reasoning_text.delta` + `response.reasoning_summary_text.delta` via `normalizeStreamEvent` | + + All six emit the contractual `REASONING_*` lifecycle (`REASONING_START` → `REASONING_MESSAGE_START` → `REASONING_MESSAGE_CONTENT` deltas → `REASONING_MESSAGE_END` → `REASONING_END`) and close it before `TEXT_MESSAGE_START`. Accumulated reasoning is also surfaced on `structured-output.complete.value.reasoning` for consumers that only subscribe to the terminal event. OpenRouter SDK's proprietary `RequestAbortedError` is mapped (alongside DOM `AbortError`) to `code: 'aborted'` in the two openrouter adapters. + + `@tanstack/ai-openai` also exports a new `OpenAIChatCompletionsTextAdapter` / `openaiChatCompletions` / `createOpenaiChatCompletions` factory — a sibling to the existing Responses adapter for callers who want the older `/v1/chat/completions` wire format against the OpenAI SDK. + + ## Decouple `@tanstack/ai-openrouter` from the OpenAI base + + 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 extends `BaseTextAdapter` directly and inlines its own stream processors (`OpenRouterTextAdapter` for chat-completions, `OpenRouterResponsesTextAdapter` for the Responses beta), reading OpenRouter's camelCase types natively. The `@tanstack/openai-base` and `openai` dependencies are removed from ai-openrouter; only `@openrouter/sdk`, `@tanstack/ai`, and `@tanstack/ai-utils` remain. The ~300 LOC of inbound/outbound shape converters (`toOpenRouterRequest`, `toChatCompletion`, `adaptOpenRouterStreamChunks`, `toSnakeResponseResult`, …) are gone. Internal: duck-typed `as { ... }` casts on stream chunks in `OpenRouterResponsesTextAdapter` are 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`), `:variant` model suffixing, `RequestAbortedError` propagation, and the OpenRouter-specific structured-output null-preservation all behave the same. + + `ai-ollama` remains on `BaseTextAdapter` directly — its native API uses a different wire format from Chat Completions and was never on the shared base. + + ## Summarize subsystem + + Anthropic, Gemini, Ollama, and OpenRouter previously each shipped a bespoke 200–300 LOC summarize adapter. They now construct a `ChatStreamSummarizeAdapter` (formerly `ChatStreamWrapperAdapter`, 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 `*SummarizeProviderOptions` interfaces (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`) or remove the explicit annotation (it'll be inferred from the adapter argument). + + `SummarizeAdapter` interface methods are now generic in `TProviderOptions`. `summarize` and `summarizeStream` previously took `SummarizationOptions` (defaulted, so `modelOptions` was effectively `Record` regardless of the adapter's typed shape). They now take `SummarizationOptions`. Source-compatible for callers that didn't specify the generic; type-tighter for implementers and downstream consumers. `SummarizationOptions`, `SummarizeAdapter`, `BaseSummarizeAdapter`, and `ChatStreamSummarizeAdapter` previously had a mixed `Record` / `Record` / `object` set of defaults for `TProviderOptions`; they now uniformly default to `Record`. + +### Patch Changes + +- Updated dependencies [[`98979f7`](https://github.com/TanStack/ai/commit/98979f7e72f4b5bfb816fb14b60a12871f8c4bec), [`02527c2`](https://github.com/TanStack/ai/commit/02527c28c3285829535cd486e529e659260b3c5d)]: + - @tanstack/ai@0.17.0 + > Renamed from `@tanstack/openai-base` in 0.3.0. See the [README](./README.md) for context. ## 0.2.1 diff --git a/packages/typescript/openai-base/package.json b/packages/typescript/openai-base/package.json index e60b54b07..85c4212fb 100644 --- a/packages/typescript/openai-base/package.json +++ b/packages/typescript/openai-base/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/openai-base", - "version": "0.2.1", + "version": "0.3.0", "description": "Shared base adapters for OpenAI-SDK-backed providers in TanStack AI (Chat Completions and Responses)", "author": "", "license": "MIT", diff --git a/packages/typescript/preact-ai-devtools/CHANGELOG.md b/packages/typescript/preact-ai-devtools/CHANGELOG.md index a721400b3..cf8050da3 100644 --- a/packages/typescript/preact-ai-devtools/CHANGELOG.md +++ b/packages/typescript/preact-ai-devtools/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/preact-ai-devtools +## 0.1.32 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/ai-devtools-core@0.3.28 + ## 0.1.31 ### Patch Changes diff --git a/packages/typescript/preact-ai-devtools/package.json b/packages/typescript/preact-ai-devtools/package.json index d7e41213f..d94f313cf 100644 --- a/packages/typescript/preact-ai-devtools/package.json +++ b/packages/typescript/preact-ai-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/preact-ai-devtools", - "version": "0.1.31", + "version": "0.1.32", "description": "Preact Devtools for TanStack AI.", "author": "tannerlinsley", "license": "MIT", diff --git a/packages/typescript/react-ai-devtools/CHANGELOG.md b/packages/typescript/react-ai-devtools/CHANGELOG.md index 348d2088d..b076ab685 100644 --- a/packages/typescript/react-ai-devtools/CHANGELOG.md +++ b/packages/typescript/react-ai-devtools/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-ai-devtools +## 0.2.32 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/ai-devtools-core@0.3.28 + ## 0.2.31 ### Patch Changes diff --git a/packages/typescript/react-ai-devtools/package.json b/packages/typescript/react-ai-devtools/package.json index 00dedd8fa..812b0a665 100644 --- a/packages/typescript/react-ai-devtools/package.json +++ b/packages/typescript/react-ai-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-ai-devtools", - "version": "0.2.31", + "version": "0.2.32", "description": "React Devtools for TanStack AI.", "author": "tannerlinsley", "license": "MIT", diff --git a/packages/typescript/solid-ai-devtools/CHANGELOG.md b/packages/typescript/solid-ai-devtools/CHANGELOG.md index 63bdeae8d..8326fb49a 100644 --- a/packages/typescript/solid-ai-devtools/CHANGELOG.md +++ b/packages/typescript/solid-ai-devtools/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/solid-ai-devtools +## 0.2.32 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/ai-devtools-core@0.3.28 + ## 0.2.31 ### Patch Changes diff --git a/packages/typescript/solid-ai-devtools/package.json b/packages/typescript/solid-ai-devtools/package.json index 7e48d51e5..7ee0861d3 100644 --- a/packages/typescript/solid-ai-devtools/package.json +++ b/packages/typescript/solid-ai-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-ai-devtools", - "version": "0.2.31", + "version": "0.2.32", "description": "Solid TanStack AI Devtools", "author": "", "license": "MIT",