diff --git a/.changeset/grok-responses-api-encrypted-reasoning.md b/.changeset/grok-responses-api-encrypted-reasoning.md new file mode 100644 index 000000000..bff942cc0 --- /dev/null +++ b/.changeset/grok-responses-api-encrypted-reasoning.md @@ -0,0 +1,29 @@ +--- +'@tanstack/ai-grok': minor +--- + +Switch the Grok text adapter from xAI's Chat Completions API (`/v1/chat/completions`) to the Responses API (`/v1/responses`), and request encrypted reasoning content by default. + +**What changed** + +- The text adapter now calls `client.responses.create(...)` for both streaming and structured output. +- Streaming emits the full set of AG-UI `REASONING_*` events (`REASONING_START`, `REASONING_MESSAGE_START`, `REASONING_MESSAGE_CONTENT`, `REASONING_MESSAGE_END`, `REASONING_END`) plus the existing legacy `STEP_*` events when the model returns reasoning. +- Function tools now use the Responses-API flat shape (`{ type: 'function', name, description, parameters, strict }`) instead of the Chat Completions nested wrapper. +- Structured output uses `text.format` instead of `response_format`. +- Every request defaults to `store: false` and `include: ['reasoning.encrypted_content']` so encrypted reasoning items are requested in stateless Responses-API workflows (zero-data-retention compatible). Pass `modelOptions: { store: true, include: [] }` to opt back into server-side storage. +- The adapter now fails early if `modelOptions.reasoning.effort` is used with models that xAI currently rejects for that parameter (for example `grok-4.2`), avoiding a less clear provider 400. `grok-4-2-non-reasoning` accepts no reasoning options because it is a non-reasoning model. + +**Breaking — `GrokTextProviderOptions`** + +The provider options surface now mirrors `@tanstack/ai-openai` (the Responses-API shape). If you reach into `modelOptions` directly: + +- `max_tokens` → use the top-level `maxTokens` on `chat()` / `generate()` (mapped to `max_output_tokens`) +- `top_p` → use the top-level `topP` +- `frequency_penalty`, `presence_penalty`, `stop` → not supported on `/v1/responses`; remove +- `user` → unchanged + +New fields available in `modelOptions`: `include`, `store`, `previous_response_id`, `reasoning`, `parallel_tool_calls`, `tool_choice`, `max_tool_calls`, `text`, `truncation`, `stream_options`, `metadata`. + +Note: xAI does not currently support every `reasoning` sub-option on every reasoning-capable model. For example, `grok-4.2` rejects `reasoning.effort`. + +If you only use `chat()` / `generate()` / `summarize()` / `useChat()` (the common path), no changes are required. diff --git a/docs/adapters/grok.md b/docs/adapters/grok.md index b08cf4091..e8feee2ac 100644 --- a/docs/adapters/grok.md +++ b/docs/adapters/grok.md @@ -2,18 +2,21 @@ title: Grok (xAI) id: grok-adapter order: 5 -description: "Use xAI Grok models with TanStack AI — Grok 4.1, Grok 4, Grok 3, and Grok 2 Image generation via @tanstack/ai-grok." +description: "Use xAI Grok models with TanStack AI — Grok 4.3, Grok 4.2, image generation, speech, transcription, and xAI server-side tools via @tanstack/ai-grok." keywords: - tanstack ai - grok - xai - - grok 4 - - grok 4.1 + - grok 4.3 + - grok 4.2 + - responses api + - server-side tools - image generation - adapter --- -The Grok adapter provides access to xAI's Grok models, including Grok 4.1, Grok 4, Grok 3, and image generation with Grok 2 Image. +The Grok adapter provides access to xAI's Grok models through `@tanstack/ai-grok`. +Text generation uses xAI's **Responses API** (`/v1/responses`), including streaming text, reasoning events, structured output, app-defined function tools, and xAI server-side tools. ## Installation @@ -21,6 +24,12 @@ The Grok adapter provides access to xAI's Grok models, including Grok 4.1, Grok npm install @tanstack/ai-grok ``` +Set your xAI API key: + +```bash +XAI_API_KEY=xai-... +``` + ## Basic Usage ```typescript @@ -28,18 +37,18 @@ import { chat } from "@tanstack/ai"; import { grokText } from "@tanstack/ai-grok"; const stream = chat({ - adapter: grokText("grok-4"), + adapter: grokText("grok-4.3"), messages: [{ role: "user", content: "Hello!" }], }); ``` -## Basic Usage - Custom API Key +## Custom API Key ```typescript import { chat } from "@tanstack/ai"; import { createGrokText } from "@tanstack/ai-grok"; -const adapter = createGrokText("grok-4", process.env.XAI_API_KEY!); +const adapter = createGrokText("grok-4.3", process.env.XAI_API_KEY!); const stream = chat({ adapter, @@ -56,28 +65,30 @@ const config: Omit = { baseURL: "https://api.x.ai/v1", // Optional, this is the default }; -const adapter = createGrokText("grok-4", process.env.XAI_API_KEY!, config); +const adapter = createGrokText("grok-4.3", process.env.XAI_API_KEY!, config); ``` -## Example: Chat Completion +## Chat Route Example ```typescript -import { chat, toStreamResponse } from "@tanstack/ai"; +import { chat, toServerSentEventsResponse } from "@tanstack/ai"; import { grokText } from "@tanstack/ai-grok"; export async function POST(request: Request) { const { messages } = await request.json(); const stream = chat({ - adapter: grokText("grok-4"), + adapter: grokText("grok-4.3"), messages, }); - return toStreamResponse(stream); + return toServerSentEventsResponse(stream); } ``` -## Example: With Tools +## App-Defined Function Tools + +Use `toolDefinition()` for tools that run in your application. The Grok adapter converts these to strict Responses API function tools. ```typescript import { chat, toolDefinition } from "@tanstack/ai"; @@ -93,158 +104,246 @@ const getWeatherDef = toolDefinition({ }); const getWeather = getWeatherDef.server(async ({ location }) => { - // Fetch weather data - return { temperature: 72, conditions: "sunny" }; + return { location, temperature: 72, conditions: "sunny" }; }); const stream = chat({ - adapter: grokText("grok-4-1-fast-reasoning"), - messages, + adapter: grokText("grok-4.3"), + messages: [{ role: "user", content: "What's the weather in Berlin?" }], tools: [getWeather], }); ``` +## xAI Server-Side Tools + +Grok also supports provider-native server-side tools through the `@tanstack/ai-grok/tools` subpath. These tools run on xAI's side rather than in your application process. + +```typescript +import { chat } from "@tanstack/ai"; +import { grokText } from "@tanstack/ai-grok"; +import { + codeExecutionTool, + fileSearchTool, + mcpTool, + webSearchTool, + xSearchTool, +} from "@tanstack/ai-grok/tools"; + +const stream = chat({ + adapter: grokText("grok-4.3"), + messages: [{ role: "user", content: "Search the web and summarize the latest xAI news." }], + tools: [webSearchTool()], +}); +``` + +### Web Search + +```typescript +import { webSearchTool } from "@tanstack/ai-grok/tools"; + +const tools = [webSearchTool()]; +``` + +### X Search + +```typescript +import { xSearchTool } from "@tanstack/ai-grok/tools"; + +const tools = [ + xSearchTool({ + allowed_x_handles: ["xai"], + enable_image_understanding: true, + }), +]; +``` + +### Code Execution + +```typescript +import { codeExecutionTool } from "@tanstack/ai-grok/tools"; + +const tools = [codeExecutionTool()]; +``` + +### Code Interpreter + +```typescript +import { codeInterpreterTool } from "@tanstack/ai-grok/tools"; + +const tools = [codeInterpreterTool({ type: "auto" })]; +``` + +### File Search + +```typescript +import { fileSearchTool } from "@tanstack/ai-grok/tools"; + +const tools = [ + fileSearchTool({ + type: "file_search", + vector_store_ids: ["vs_123"], + max_num_results: 5, + }), +]; +``` + +### Collections Search + +```typescript +import { collectionsSearchTool } from "@tanstack/ai-grok/tools"; + +const tools = [ + collectionsSearchTool({ + vector_store_ids: ["vs_123"], + }), +]; +``` + +### MCP + +```typescript +import { mcpTool } from "@tanstack/ai-grok/tools"; + +const tools = [ + mcpTool({ + server_url: "https://example.com/mcp", + server_label: "example-mcp", + }), +]; +``` + +Use either `server_url` or `connector_id` for MCP tools, not both. + ## Model Options -Grok supports various provider-specific options: +Common generation controls live on the top-level `chat()` call: ```typescript const stream = chat({ - adapter: grokText("grok-4"), + adapter: grokText("grok-4.3"), + messages, + temperature: 0.7, + topP: 0.9, + maxTokens: 1024, +}); +``` + +Responses API-specific options go in `modelOptions`: + +```typescript +const stream = chat({ + adapter: grokText("grok-4.3"), messages, modelOptions: { - frequency_penalty: 0.5, - presence_penalty: 0.5, - stop: ["END"], + store: false, + include: ["reasoning.encrypted_content"], + parallel_tool_calls: true, }, }); ``` -## Summarization +The adapter defaults to `store: false` and `include: ["reasoning.encrypted_content"]` so xAI returns encrypted reasoning blobs when the model emits reasoning. The adapter requests these blobs by default; it does not yet automatically replay encrypted reasoning items on subsequent turns. -Summarize long text content: +Not every xAI model accepts every reasoning sub-option. For example, `grok-4.3` and `grok-4.2` reject `reasoning.effort`, so the adapter fails early if you pass that unsupported option for those models. + +## Structured Output + +Use `outputSchema` for structured output. The Grok adapter sends this through the Responses API `text.format` JSON Schema configuration. + +```typescript +import { chat } from "@tanstack/ai"; +import { grokText } from "@tanstack/ai-grok"; +import { z } from "zod"; + +const result = await chat({ + adapter: grokText("grok-4.3"), + messages: [{ role: "user", content: "Describe Tokyo." }], + outputSchema: z.object({ + name: z.string(), + country: z.string(), + population: z.number(), + }), +}); +``` + +## Summarization ```typescript import { summarize } from "@tanstack/ai"; import { grokSummarize } from "@tanstack/ai-grok"; const result = await summarize({ - adapter: grokSummarize("grok-4"), + adapter: grokSummarize("grok-4.3"), text: "Your long text to summarize...", maxLength: 100, style: "concise", // "concise" | "bullet-points" | "paragraph" }); - -console.log(result.summary); ``` ## Image Generation -Generate images with Grok 2 Image: - ```typescript import { generateImage } from "@tanstack/ai"; import { grokImage } from "@tanstack/ai-grok"; const result = await generateImage({ - adapter: grokImage("grok-2-image-1212"), + adapter: grokImage("grok-imagine-image"), prompt: "A futuristic cityscape at sunset", numberOfImages: 1, }); - -console.log(result.images); ``` -## Environment Variables +## Speech and Transcription -Set your API key in environment variables: +The package also includes xAI TTS and transcription adapters: -```bash -XAI_API_KEY=xai-... +```typescript +import { generateSpeech, generateTranscription } from "@tanstack/ai"; +import { grokSpeech, grokTranscription } from "@tanstack/ai-grok"; ``` -## Implementation Notes - -### Why Chat Completions API (Not Responses API) - -The Grok adapter uses xAI's **Chat Completions API** (`/v1/chat/completions`) rather than the Responses API (`/v1/responses`). This is an intentional architectural decision: - -1. **User-Defined Tools**: The Chat Completions API supports user-defined function tools, which is essential for TanStack AI's tool calling functionality. The Responses API only supports xAI's server-side tools (web search, X search, code execution). - -2. **Full Tool Calling Support**: With Chat Completions, you can define custom tools with Zod schemas that run in your application. The Responses API restricts you to xAI's built-in tools only. - -3. **Streaming Compatibility**: The streaming event format differs significantly between the two APIs. Chat Completions uses `delta.tool_calls` with argument accumulation, while Responses API uses `response.output_item.added` and `response.function_call_arguments.done`. - -4. **OpenAI SDK Compatibility**: xAI's Chat Completions API is fully compatible with the OpenAI SDK, making integration straightforward while maintaining full feature parity for tool calling. - -If you need xAI's server-side tools (web search, X/Twitter search, code execution), you would need to use the Responses API directly. However, for most use cases requiring custom tool definitions, the Chat Completions API is the correct choice. - ## API Reference ### `grokText(model, config?)` -Creates a Grok text adapter using environment variables. - -**Parameters:** - -- `model` - The model name (e.g., `'grok-4'`, `'grok-4-1-fast-reasoning'`) -- `config.baseURL?` - Custom base URL (optional) - -**Returns:** A Grok text adapter instance. +Creates a Grok text adapter using `XAI_API_KEY` from the environment. ### `createGrokText(model, apiKey, config?)` Creates a Grok text adapter with an explicit API key. -**Parameters:** - -- `model` - The model name -- `apiKey` - Your xAI API key -- `config.baseURL?` - Custom base URL (optional) - -**Returns:** A Grok text adapter instance. +### `grokSummarize(model, config?)` / `createGrokSummarize(model, apiKey, config?)` -### `grokSummarize(model, config?)` +Creates a Grok summarization adapter. -Creates a Grok summarization adapter using environment variables. +### `grokImage(model, config?)` / `createGrokImage(model, apiKey, config?)` -**Returns:** A Grok summarize adapter instance. +Creates a Grok image adapter. -### `createGrokSummarize(model, apiKey, config?)` +### `grokSpeech(model, config?)` / `createGrokSpeech(model, apiKey, config?)` -Creates a Grok summarization adapter with an explicit API key. +Creates a Grok text-to-speech adapter. -**Returns:** A Grok summarize adapter instance. +### `grokTranscription(model, config?)` / `createGrokTranscription(model, apiKey, config?)` -### `grokImage(model, config?)` +Creates a Grok speech-to-text adapter. -Creates a Grok image generation adapter using environment variables. +## Migrating from 0.7.x (Chat Completions API) -**Returns:** A Grok image adapter instance. +`@tanstack/ai-grok` 0.8+ targets xAI's `/v1/responses` endpoint instead of `/v1/chat/completions`. If you only use `chat()` / `generate()` / `summarize()` / `useChat()`, no changes are required. -### `createGrokImage(model, apiKey, config?)` +If you pass `modelOptions` directly: -Creates a Grok image generation adapter with an explicit API key. - -**Returns:** A Grok image adapter instance. - -## Limitations - -- **Text-to-Speech**: Grok does not support text-to-speech. Use OpenAI for TTS. -- **Transcription**: Grok does not support audio transcription. Use OpenAI's Whisper. -- **Responses API Tools**: Server-side tools (web search, X search, code execution) are not supported through this adapter. Use the Chat Completions API with custom tools instead. +| Before (Chat Completions) | After (Responses API) | +| --- | --- | +| `modelOptions: { max_tokens }` | top-level `maxTokens` (mapped to `max_output_tokens`) | +| `modelOptions: { top_p }` | top-level `topP` | +| `modelOptions: { frequency_penalty, presence_penalty, stop }` | not supported on `/v1/responses`; remove | +| `modelOptions: { user }` | unchanged | ## Next Steps - [Getting Started](../getting-started/quick-start) - Learn the basics -- [Tools Guide](../tools/tools) - Learn about tools +- [Tools Guide](../tools/tools) - Learn about app-defined tools +- [Provider Tools](../tools/provider-tools.md) - Learn about provider-native tools - [Other Adapters](./openai) - Explore other providers - -## Provider Tools - -Grok does not currently expose provider-specific tool factories. -Define your own tools with `toolDefinition()` from `@tanstack/ai`. - -See [Tools](../tools/tools.md) for the general tool-definition flow, or -[Provider Tools](../tools/provider-tools.md) for other providers' -native-tool offerings. diff --git a/docs/code-mode/code-mode.md b/docs/code-mode/code-mode.md index aa2f9ddbe..4cac17077 100644 --- a/docs/code-mode/code-mode.md +++ b/docs/code-mode/code-mode.md @@ -246,7 +246,7 @@ Code Mode asks the model to write valid TypeScript that calls your tools through | Rank | Model | Stars | Acc | Comp | TS | CME | Latency | Tokens | |------|-------|:-----:|:---:|:----:|:--:|:---:|--------:|-------:| -| 1 | `grok:grok-4-1-fast-non-reasoning` | ★★★ | 10 | 9 | 6 | 10 | 7.0s | — | +| 1 | `grok:grok-4-2-non-reasoning` | ★★★ | 10 | 9 | 6 | 10 | 7.0s | — | | 2 | `ollama:gpt-oss:20b` | ★★★ | 10 | 8 | 6 | 5 | 45.1s | 23.6k | | 3 | `anthropic:claude-haiku-4-5` | ★★★ | 10 | 10 | 7 | 10 | 9.4s | 8.5k | | 4 | `gemini:gemini-2.5-flash` | ★★★ | 10 | 7 | 5 | 9 | 7.3s | 6.9k | diff --git a/docs/community-adapters/cencori.md b/docs/community-adapters/cencori.md index f1de31217..057c61171 100644 --- a/docs/community-adapters/cencori.md +++ b/docs/community-adapters/cencori.md @@ -119,7 +119,7 @@ const anthropic = cencori("claude-3-5-sonnet"); const google = cencori("gemini-2.5-flash"); // xAI -const grok = cencori("grok-3"); +const grok = cencori("grok-4.3"); // DeepSeek const deepseek = cencori("deepseek-v3.2"); @@ -134,7 +134,7 @@ All responses use the same unified format regardless of provider. | OpenAI | `gpt-5`, `gpt-4o`, `gpt-4o-mini`, `o3`, `o1` | | Anthropic | `claude-opus-4`, `claude-sonnet-4`, `claude-3-5-sonnet` | | Google | `gemini-3-pro`, `gemini-2.5-flash`, `gemini-2.0-flash` | -| xAI | `grok-4`, `grok-3` | +| xAI | `grok-4.3`, `grok-4.2` | | Mistral | `mistral-large`, `codestral`, `devstral` | | DeepSeek | `deepseek-v3.2`, `deepseek-reasoner` | | + More | Groq, Cohere, Perplexity, Together, Qwen, OpenRouter | diff --git a/docs/community-adapters/cloudflare.md b/docs/community-adapters/cloudflare.md index a34e54860..e1bee4639 100644 --- a/docs/community-adapters/cloudflare.md +++ b/docs/community-adapters/cloudflare.md @@ -210,7 +210,7 @@ const anthropic = createAnthropicChat("claude-sonnet-4-5", { binding: env.AI.gateway("my-gateway-id"), }); -const grok = createGrokChat("grok-4", { +const grok = createGrokChat("grok-4.3", { binding: env.AI.gateway("my-gateway-id"), }); diff --git a/examples/ts-react-chat/src/lib/model-selection.ts b/examples/ts-react-chat/src/lib/model-selection.ts index 95c122cd4..b88312c62 100644 --- a/examples/ts-react-chat/src/lib/model-selection.ts +++ b/examples/ts-react-chat/src/lib/model-selection.ts @@ -117,23 +117,18 @@ export const MODEL_OPTIONS: Array = [ // Grok { provider: 'grok', - model: 'grok-4', - label: 'Grok - Grok 4', + model: 'grok-4.3', + label: 'Grok - Grok 4.3', }, { provider: 'grok', - model: 'grok-4-fast-non-reasoning', - label: 'Grok - Grok 4 Fast', + model: 'grok-4.2', + label: 'Grok - Grok 4.2', }, { provider: 'grok', - model: 'grok-3', - label: 'Grok - Grok 3', - }, - { - provider: 'grok', - model: 'grok-3-mini', - label: 'Grok - Grok 3 Mini', + model: 'grok-4-2-non-reasoning', + label: 'Grok - Grok 4.2 Non-Reasoning', }, ] diff --git a/examples/ts-react-chat/src/routes/api.tanchat.ts b/examples/ts-react-chat/src/routes/api.tanchat.ts index f571fd9c7..cdf09a7c4 100644 --- a/examples/ts-react-chat/src/routes/api.tanchat.ts +++ b/examples/ts-react-chat/src/routes/api.tanchat.ts @@ -165,7 +165,7 @@ export const Route = createFileRoute('/api/tanchat')({ }), grok: () => createChatOptions({ - adapter: grokText((model || 'grok-3') as 'grok-3'), + adapter: grokText((model || 'grok-4.3') as 'grok-4.3'), modelOptions: {}, }), groq: () => diff --git a/examples/ts-react-chat/src/routes/generations.structured-output.tsx b/examples/ts-react-chat/src/routes/generations.structured-output.tsx index 9b123308f..c30de9b9d 100644 --- a/examples/ts-react-chat/src/routes/generations.structured-output.tsx +++ b/examples/ts-react-chat/src/routes/generations.structured-output.tsx @@ -11,7 +11,7 @@ const OPENROUTER_MODELS = [ { value: 'anthropic/claude-opus-4.7', label: 'Claude Opus 4.7' }, { value: 'anthropic/claude-sonnet-4.6', label: 'Claude Sonnet 4.6' }, { value: 'google/gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro (Preview)' }, - { value: 'x-ai/grok-4.1-fast', label: 'Grok 4.1 Fast' }, + { value: 'x-ai/grok-4.3', label: 'Grok 4.3' }, ] as const interface RecommendationResult { diff --git a/knip.json b/knip.json index 1fa06e311..8b19d8e4e 100644 --- a/knip.json +++ b/knip.json @@ -11,6 +11,7 @@ "packages/typescript/ai-code-mode-skills/test-cli/**", ".claude/worktrees/**", "packages/typescript/ai-openai/live-tests/**", + "packages/typescript/ai-grok/live-tests/**", "packages/typescript/ai-openai/src/**/*.test.ts", "packages/typescript/ai-openai/src/audio/audio-provider-options.ts", "packages/typescript/ai-openai/src/audio/transcribe-provider-options.ts", diff --git a/packages/typescript/ai-code-mode/README.md b/packages/typescript/ai-code-mode/README.md index 21eeac005..bd1cfded5 100644 --- a/packages/typescript/ai-code-mode/README.md +++ b/packages/typescript/ai-code-mode/README.md @@ -181,7 +181,7 @@ The table below is transcribed from canonical `models-eval/results.json` (sessio | Anthropic | `claude-haiku-4-5` | cloud | ★★★ | 10 | 10 | 6 | 7 | 3 | 2 | 5 | | OpenAI | `gpt-4o-mini` | cloud | ★★★ | 10 | 8 | 7 | 9 | 3 | 1 | 5 | | Gemini | `gemini-2.5-flash` | cloud | ★★★ | 10 | 8 | 7 | 10 | 4 | 2 | 5 | -| xAI | `grok-4-1-fast-non-reasoning` | cloud | ★★★ | 10 | 8 | 6 | 10 | 4 | 5 | 5 | +| xAI | `grok-4-2-non-reasoning` | cloud | ★★★ | 10 | 8 | 6 | 10 | 4 | 5 | 5 | | Groq | `llama-3.3-70b-versatile` | cloud | ★★★ | 10 | 7 | 6 | 9 | 5 | 3 | 4 | | Groq | `qwen/qwen3-32b` | cloud | ★★☆ | 10 | 8 | 5 | 4 | 1 | 2 | 5 | diff --git a/packages/typescript/ai-code-mode/models-eval/eval-config.ts b/packages/typescript/ai-code-mode/models-eval/eval-config.ts index 2c017f3ad..6d2e2606f 100644 --- a/packages/typescript/ai-code-mode/models-eval/eval-config.ts +++ b/packages/typescript/ai-code-mode/models-eval/eval-config.ts @@ -68,7 +68,7 @@ export const EVAL_MODELS: Array = [ { name: 'Gemini 2.5 Flash', model: 'gemini:gemini-2.5-flash' }, // --- Grok (xAI) --- - { name: 'Grok 4.1 Fast', model: 'grok:grok-4-1-fast-non-reasoning' }, + { name: 'Grok 4.2 Non-Reasoning', model: 'grok:grok-4-2-non-reasoning' }, // --- Groq --- { name: 'Llama 3.3 70B (Groq)', model: 'groq:llama-3.3-70b-versatile' }, diff --git a/packages/typescript/ai-code-mode/models-eval/results.json b/packages/typescript/ai-code-mode/models-eval/results.json index b9f31353f..78bdeec00 100644 --- a/packages/typescript/ai-code-mode/models-eval/results.json +++ b/packages/typescript/ai-code-mode/models-eval/results.json @@ -223,10 +223,10 @@ } }, { - "name": "Grok 4.1 Fast", - "model": "grok:grok-4-1-fast-non-reasoning", + "name": "Grok 4.2 Non-Reasoning", + "model": "grok:grok-4-2-non-reasoning", "provider": "grok", - "modelId": "grok-4-1-fast-non-reasoning", + "modelId": "grok-4-2-non-reasoning", "modelCategory": "cloud", "durationMs": 6998, "ttftMs": 6454, diff --git a/packages/typescript/ai-code-mode/models-eval/run-eval.ts b/packages/typescript/ai-code-mode/models-eval/run-eval.ts index 1de9a5409..7ffb03d34 100644 --- a/packages/typescript/ai-code-mode/models-eval/run-eval.ts +++ b/packages/typescript/ai-code-mode/models-eval/run-eval.ts @@ -204,7 +204,7 @@ function getTextAdapter( case 'gemini': return geminiText(modelId as 'gemini-2.5-flash') case 'grok': - return grokText(modelId as 'grok-3-mini') + return grokText(modelId as 'grok-4-2-non-reasoning') case 'groq': return groqText(modelId as 'llama-3.3-70b-versatile') } diff --git a/packages/typescript/ai-grok/README.md b/packages/typescript/ai-grok/README.md index b4f87f9fe..3817e3d34 100644 --- a/packages/typescript/ai-grok/README.md +++ b/packages/typescript/ai-grok/README.md @@ -28,11 +28,10 @@ export XAI_API_KEY="xai-..." import { grokText } from '@tanstack/ai-grok' import { generate } from '@tanstack/ai' -const adapter = grokText() +const adapter = grokText('grok-4.3') const result = await generate({ adapter, - model: 'grok-3', messages: [ { role: 'user', content: 'Explain quantum computing in simple terms' }, ], @@ -47,11 +46,10 @@ console.log(result.text) import { grokSummarize } from '@tanstack/ai-grok' import { summarize } from '@tanstack/ai' -const adapter = grokSummarize() +const adapter = grokSummarize('grok-4.3') const result = await summarize({ adapter, - model: 'grok-3', text: 'Long article text...', style: 'bullet-points', }) @@ -65,11 +63,10 @@ console.log(result.summary) import { grokImage } from '@tanstack/ai-grok' import { generateImages } from '@tanstack/ai' -const adapter = grokImage() +const adapter = grokImage('grok-imagine-image') const result = await generateImages({ adapter, - model: 'grok-2-image-1212', prompt: 'A beautiful sunset over mountains', numberOfImages: 1, size: '1024x1024', @@ -83,34 +80,62 @@ console.log(result.images[0].url) ```typescript import { createGrokText } from '@tanstack/ai-grok' -const adapter = createGrokText('xai-your-api-key-here') +const adapter = createGrokText('grok-4.3', 'xai-your-api-key-here') ``` ## Supported Models ### Chat Models -- `grok-4` - Latest flagship model -- `grok-3` - Previous generation model -- `grok-3-mini` - Smaller, faster model -- `grok-4-fast` - Fast inference model -- `grok-4.1-fast` - Production-focused fast model -- `grok-2-vision-1212` - Vision-capable model (text + image input) +- `grok-4.3` - Latest flagship reasoning model +- `grok-4.2` - Previous flagship reasoning model +- `grok-4-2-non-reasoning` - Non-reasoning model ### Image Models -- `grok-2-image-1212` - Image generation model +- `grok-imagine-image` - Image generation ## Features -- ✅ Streaming chat completions -- ✅ Structured output (JSON Schema) -- ✅ Function/tool calling +- ✅ xAI **Responses API** (`/v1/responses`) +- ✅ Streaming chat with reasoning (`REASONING_*` AG-UI events) +- ✅ Requests encrypted reasoning content by default (`store: false` + `include: ['reasoning.encrypted_content']`) for stateless Responses-API workflows +- ✅ Structured output (JSON Schema via `text.format`) +- ✅ Function/tool calling (Responses-API flat function tools, `strict: true`) - ✅ Multimodal input (text + images for vision models) - ✅ Image generation - ✅ Text summarization - ❌ Embeddings (not supported by xAI) +## Provider options + +Common knobs (`temperature`, `topP`, `maxTokens`, `metadata`) live on the top-level `chat()` / `generate()` call. Pass anything Responses-API-specific in `modelOptions`: + +```typescript +import { chat } from '@tanstack/ai' +import { grokText } from '@tanstack/ai-grok' + +await chat({ + adapter: grokText('grok-4.3'), + messages: [{ role: 'user', content: 'Walk me through your reasoning.' }], + temperature: 0.7, + maxTokens: 1024, + modelOptions: { + // Not every xAI model accepts every reasoning sub-option. For example, + // grok-4.3 and grok-4.2 reject reasoning.effort and should rely on default reasoning. + parallel_tool_calls: true, + // store and include are set to encrypted-reasoning defaults automatically. + // Override here if you want server-side response storage instead: + // store: true, + // include: [], + }, +}) +``` + +### Encrypted reasoning defaults + +By default the adapter sends `store: false` and `include: ['reasoning.encrypted_content']`. This makes the response stateless and asks xAI to return an encrypted blob alongside each reasoning item when the model emits reasoning. The adapter does not yet replay those reasoning items automatically on the next turn; this default currently guarantees retrieval, not full round-trip continuity. To opt back into server-side response storage, pass `modelOptions: { store: true, include: [] }`. + ## Tree-Shakeable Adapters This package uses tree-shakeable adapters, so you only import what you need: diff --git a/packages/typescript/ai-grok/live-tests/README.md b/packages/typescript/ai-grok/live-tests/README.md new file mode 100644 index 000000000..8cbc7d820 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/README.md @@ -0,0 +1,55 @@ +# Grok Adapter Live Tests + +Live integration tests for the Grok adapter against xAI's `/v1/responses` API. + +## Setup + +1. Create a `.env.local` file in this directory with your xAI API key: + +``` +XAI_API_KEY=xai-... +# Optional, only needed for file_search / collections_search live tests +XAI_VECTOR_STORE_ID=vs_... +``` + +2. Install dependencies from the workspace root: + +```bash +pnpm install +``` + +## Running Tests + +Run individual tests: + +```bash +pnpm test # Basic streaming chat +pnpm test:tools # Tool calling with required parameters +pnpm test:tools-optional # Tool calling with optional parameters +pnpm test:tools-empty # Tool calling with empty object schema +pnpm test:structured # Structured output (JSON Schema) +pnpm test:reasoning # Reasoning events + encrypted content +pnpm test:multi-turn # Multi-turn conversation +pnpm test:builtin-tools # Built-in web_search + x_search +pnpm test:server-tools # code_execution, code_interpreter, file_search, collections_search +``` + +Run all tests: + +```bash +pnpm test:all +``` + +## What's Tested + +| Script | What it verifies | +| --- | --- | +| `streaming.ts` | AG-UI event lifecycle (RUN_STARTED, TEXT_MESSAGE_*, RUN_FINISHED) | +| `tool-test.ts` | Tool calls detected, function name captured, arguments parsed | +| `tool-test-optional.ts` | Optional tool parameters handled correctly | +| `tool-test-empty-object.ts` | Empty-schema tools work (e.g. "list all items") | +| `structured-output.ts` | `text.format` JSON Schema produces valid typed output | +| `reasoning.ts` | REASONING_* AG-UI events emitted, encrypted_content returned | +| `multi-turn.ts` | Assistant messages converted to Responses-API input correctly | +| `builtin-tools.ts` | Built-in server-side tool wiring for `web_search` and `x_search` | +| `server-tools.ts` | Additional server-side tools: `code_execution`, `code_interpreter`, `file_search`, `collections_search` | diff --git a/packages/typescript/ai-grok/live-tests/builtin-tools.ts b/packages/typescript/ai-grok/live-tests/builtin-tools.ts new file mode 100644 index 000000000..92541097b --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/builtin-tools.ts @@ -0,0 +1,118 @@ +import { + codeExecutionTool, + codeInterpreterTool, + collectionsSearchTool, + fileSearchTool, + mcpTool, + webSearchTool, + xSearchTool, +} from '../src/tools' +import { + loadApiKey, + resolveModel, + streamChat, + supportsBuiltInServerTools, + textFromChunks, +} from './helpers' +import type { StreamChunk, Tool } from '@tanstack/ai' + +const apiKey = loadApiKey() +const model = resolveModel() + +async function runCase( + name: string, + tools: Array, + prompt: string, + options?: { requireToolUse?: boolean }, +) { + const chunks: Array = [] + for await (const chunk of streamChat({ + model, + apiKey, + messages: [{ role: 'user', content: prompt }], + tools, + maxTokens: 64, + modelOptions: options?.requireToolUse ? { tool_choice: 'required' } : {}, + })) { + chunks.push(chunk) + } + + const hasRunFinished = chunks.some((chunk) => chunk.type === 'RUN_FINISHED') + if (!hasRunFinished) { + throw new Error(`${name}: RUN_FINISHED not emitted`) + } + + const text = textFromChunks(chunks) + + console.log(`PASS: ${name}`) + console.log(` response: ${JSON.stringify(text.slice(0, 120))}`) +} + +async function main() { + console.log(`Testing Grok built-in server-side tools — model=${model}\n`) + + if (!supportsBuiltInServerTools(model)) { + console.log( + `SKIP: ${model} does not advertise built-in server-side tools in this adapter yet`, + ) + return + } + + await runCase('web_search', [webSearchTool()], 'Say hi.') + + await runCase( + 'x_search', + [xSearchTool({ allowed_x_handles: ['xai'] })], + 'Say hi.', + ) + + await runCase('code_execution', [codeExecutionTool()], 'Say hi.') + + await runCase( + 'code_interpreter', + [codeInterpreterTool({ type: 'auto' })], + 'Say hi.', + ) + + const vectorStoreId = process.env.XAI_VECTOR_STORE_ID + if (vectorStoreId) { + await runCase( + 'file_search', + [fileSearchTool({ type: 'file_search', vector_store_ids: [vectorStoreId] })], + 'Use file_search and answer with a short greeting.', + ) + + await runCase( + 'collections_search', + [ + collectionsSearchTool({ + vector_store_ids: [vectorStoreId], + }), + ], + 'Use collections_search and answer with a short greeting.', + ) + } else { + console.log('SKIP: file_search / collections_search (set XAI_VECTOR_STORE_ID)') + } + + const mcpServerUrl = process.env.XAI_MCP_SERVER_URL + if (mcpServerUrl) { + await runCase( + 'mcp', + [ + mcpTool({ + server_url: mcpServerUrl, + server_label: 'test-mcp', + }), + ], + 'Use mcp and answer with a short greeting.', + ) + } else { + console.log('SKIP: mcp (set XAI_MCP_SERVER_URL)') + } +} + +main().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/helpers.ts b/packages/typescript/ai-grok/live-tests/helpers.ts new file mode 100644 index 000000000..11cade447 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/helpers.ts @@ -0,0 +1,160 @@ +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { chat } from '@tanstack/ai' +import { createGrokText } from '../src/adapters/text' +import type { GrokChatModel } from '../src/model-meta' +import type { GrokMessageMetadataByModality } from '../src/message-types' +import type { + ConstrainedModelMessage, + RunFinishedEvent, + StreamChunk, + TextMessageContentEvent, + Tool, + ToolCallEndEvent, + ToolCallStartEvent, +} from '@tanstack/ai' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export function loadApiKey(): string { + try { + const envContent = readFileSync(join(__dirname, '.env.local'), 'utf-8') + envContent.split('\n').forEach((line) => { + const match = line.match(/^([^=]+)=(.*)$/) + if (match) { + process.env[match[1]!.trim()] = match[2]!.trim() + } + }) + } catch { + // .env.local not found, will use process.env + } + + const apiKey = process.env.XAI_API_KEY + if (!apiKey) { + console.error( + 'XAI_API_KEY not found. Create a .env.local file or set it in your environment.', + ) + process.exit(1) + } + return apiKey +} + +export function assert(condition: boolean, message: string): asserts condition { + if (!condition) { + console.error(`FAIL: ${message}`) + process.exit(1) + } +} + +export function resolveModel(): GrokChatModel { + return parseModels(process.argv.slice(2))[0] ?? 'grok-4.3' +} + +export function createLiveTestAdapter(model: GrokChatModel, apiKey: string) { + return createGrokText(model, apiKey) +} + +function isGrokChatModel(model: string): model is GrokChatModel { + return ['grok-4.2', 'grok-4-2-non-reasoning', 'grok-4.3'].includes( + model, + ) +} + +export function parseModels(args: Array): Array { + const modelFlagIndex = args.indexOf('--model') + if (modelFlagIndex >= 0) { + const model = args[modelFlagIndex + 1] + if (!model) { + throw new Error('--model requires a value') + } + if (!isGrokChatModel(model)) { + throw new Error(`Unknown Grok chat model: ${model}`) + } + return [model] + } + + const modelsFlagIndex = args.indexOf('--models') + if (modelsFlagIndex >= 0) { + const raw = args[modelsFlagIndex + 1] + if (!raw) { + throw new Error('--models requires a comma-separated value') + } + return raw + .split(',') + .map((m) => m.trim()) + .filter(Boolean) + .map((model) => { + if (!isGrokChatModel(model)) { + throw new Error(`Unknown Grok chat model: ${model}`) + } + return model + }) + } + + return ['grok-4.3'] +} + +export function supportsClientToolCalling(_model: string): boolean { + return true +} + +export function supportsBuiltInServerTools(_model: string): boolean { + return true +} + +type GrokLiveTestMessage = ConstrainedModelMessage<{ + inputModalities: readonly ['text', 'image'] + messageMetadataByModality: GrokMessageMetadataByModality +}> + +export function streamChat(options: { + model: GrokChatModel + apiKey: string + messages: Array + tools?: Array + maxTokens?: number + modelOptions?: Parameters[0]['modelOptions'] +}): AsyncIterable { + return chat({ + adapter: createLiveTestAdapter(options.model, options.apiKey), + messages: options.messages, + tools: options.tools, + maxTokens: options.maxTokens, + modelOptions: options.modelOptions, + stream: true, + }) as unknown as AsyncIterable +} + +export function textFromChunks(chunks: Array): string { + return chunks + .filter((chunk): chunk is TextMessageContentEvent => + chunk.type === 'TEXT_MESSAGE_CONTENT' + ) + .map((chunk) => chunk.delta) + .join('') +} + +export function findRunFinished( + chunks: Array, +): RunFinishedEvent | undefined { + return chunks.find( + (chunk): chunk is RunFinishedEvent => chunk.type === 'RUN_FINISHED', + ) +} + +export function findToolCallStart( + chunks: Array, +): ToolCallStartEvent | undefined { + return chunks.find( + (chunk): chunk is ToolCallStartEvent => chunk.type === 'TOOL_CALL_START', + ) +} + +export function findToolCallEnd( + chunks: Array, +): ToolCallEndEvent | undefined { + return chunks.find( + (chunk): chunk is ToolCallEndEvent => chunk.type === 'TOOL_CALL_END', + ) +} diff --git a/packages/typescript/ai-grok/live-tests/multi-turn.ts b/packages/typescript/ai-grok/live-tests/multi-turn.ts new file mode 100644 index 000000000..de1847ce1 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/multi-turn.ts @@ -0,0 +1,37 @@ +import { assert, loadApiKey, streamChat, textFromChunks } from './helpers' +import type { StreamChunk } from '@tanstack/ai' + +const apiKey = loadApiKey() + +async function testMultiTurn() { + console.log('Testing Grok multi-turn conversation (Responses API)\n') + + const chunks: Array = [] + for await (const chunk of streamChat({ + model: 'grok-4.3', + apiKey, + messages: [ + { role: 'user', content: 'My name is Alice.' }, + { role: 'assistant', content: 'Nice to meet you, Alice!' }, + { role: 'user', content: 'What is my name?' }, + ], + maxTokens: 32, + })) { + chunks.push(chunk) + } + + const text = textFromChunks(chunks) + + assert( + text.toLowerCase().includes('alice'), + `Expected "alice" in response, got: ${text.slice(0, 80)}`, + ) + + console.log(`PASS: multi-turn conversation`) + console.log(` Response: "${text.trim().slice(0, 60)}"`) +} + +testMultiTurn().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/package.json b/packages/typescript/ai-grok/live-tests/package.json new file mode 100644 index 000000000..ad118f319 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/package.json @@ -0,0 +1,24 @@ +{ + "name": "ai-grok-live-tests", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "tsx streaming.ts", + "test:tools": "tsx tool-test.ts", + "test:tools-optional": "tsx tool-test-optional.ts", + "test:tools-empty": "tsx tool-test-empty-object.ts", + "test:structured": "tsx structured-output.ts", + "test:reasoning": "tsx reasoning.ts", + "test:multi-turn": "tsx multi-turn.ts", + "test:builtin-tools": "tsx builtin-tools.ts", + "test:server-tools": "tsx server-tools.ts", + "test:all": "tsx streaming.ts && tsx tool-test.ts && tsx tool-test-optional.ts && tsx tool-test-empty-object.ts && tsx structured-output.ts && tsx reasoning.ts && tsx multi-turn.ts && tsx builtin-tools.ts && tsx server-tools.ts" + }, + "dependencies": { + "@tanstack/ai": "workspace:*" + }, + "devDependencies": { + "tsx": "^4.19.2" + } +} diff --git a/packages/typescript/ai-grok/live-tests/reasoning.ts b/packages/typescript/ai-grok/live-tests/reasoning.ts new file mode 100644 index 000000000..f0ea9bb58 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/reasoning.ts @@ -0,0 +1,116 @@ +import { assert, loadApiKey, streamChat } from './helpers' +import type { StreamChunk } from '@tanstack/ai' + +type RawReasoningItem = { + type?: string + encrypted_content?: unknown +} + +type RawResponse = { + output: Array +} + +const apiKey = loadApiKey() + +async function testReasoning() { + console.log('Testing Grok reasoning events + encrypted content (Responses API)\n') + + const chunks: Array = [] + for await (const chunk of streamChat({ + model: 'grok-4.3', + apiKey, + messages: [ + { + role: 'user', + content: '3 + 4 * 2 = ? Show your step-by-step reasoning.', + }, + ], + maxTokens: 256, + })) { + chunks.push(chunk) + } + + const types = chunks.map((chunk) => String(chunk.type)) + const reasoningEvents = types.filter( + (type) => type === 'REASONING_MESSAGE_CONTENT', + ) + + if (reasoningEvents.length > 0) { + console.log( + `PASS: REASONING events emitted (${reasoningEvents.length} content chunks)`, + ) + + const reasoningStartIdx = types.indexOf('REASONING_START') + const reasoningEndIdx = types.indexOf('REASONING_END') + assert(reasoningStartIdx > -1, 'REASONING_START not found') + assert( + reasoningEndIdx > reasoningStartIdx, + 'REASONING_END should come after REASONING_START', + ) + + const textStartIdx = types.indexOf('TEXT_MESSAGE_START') + if (textStartIdx > -1) { + assert( + reasoningEndIdx < textStartIdx, + 'REASONING_END should come before TEXT_MESSAGE_START', + ) + console.log('PASS: Reasoning closes before text starts') + } + } else { + console.log('SKIP: No REASONING events this turn (model discretion)') + } + + console.log('\nVerifying encrypted_content via raw API call...') + const resp = await fetch('https://api.x.ai/v1/responses', { + method: 'POST', + headers: { + 'content-type': 'application/json', + authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model: 'grok-4.3', + input: [ + { + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: 'Is 7 prime? Brief.' }], + }, + ], + store: false, + include: ['reasoning.encrypted_content'], + max_output_tokens: 128, + stream: false, + }), + }) + + const rawBody = await resp.text() + assert(resp.ok, `API returned ${resp.status}: ${rawBody.slice(0, 200)}`) + + const raw = JSON.parse(rawBody) as RawResponse + const reasoning = raw.output.filter((item) => item.type === 'reasoning') + const withBlob = reasoning.filter( + (item) => + typeof item.encrypted_content === 'string' && + item.encrypted_content.length > 0, + ) + + if (reasoning.length === 0) { + console.log('SKIP: No reasoning items in raw response (model discretion)') + } else if (withBlob.length === 0) { + console.error( + `FAIL: ${reasoning.length} reasoning items but none had encrypted_content`, + ) + process.exit(1) + } else { + const [first] = withBlob + assert(typeof first?.encrypted_content === 'string', 'Missing blob') + console.log( + `PASS: encrypted_content present (${withBlob.length}/${reasoning.length} items, blob_len=${first.encrypted_content.length})`, + ) + } +} + +testReasoning().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/run.ts b/packages/typescript/ai-grok/live-tests/run.ts new file mode 100644 index 000000000..e2b54964c --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/run.ts @@ -0,0 +1,66 @@ +import { execFileSync } from 'node:child_process' +import { + parseModels, + supportsBuiltInServerTools, + supportsClientToolCalling, +} from './helpers' + +const models = parseModels(process.argv.slice(2)) + +const scripts = [ + 'streaming.ts', + 'tool-test.ts', + 'tool-test-optional.ts', + 'tool-test-empty-object.ts', + 'builtin-tools.ts', + 'structured-output.ts', + 'reasoning.ts', + 'multi-turn.ts', +] as const + +let failures = 0 + +for (const model of models) { + console.log(`\n=== Running Grok live-tests for model: ${model} ===`) + + for (const script of scripts) { + if ( + (script === 'tool-test.ts' || + script === 'tool-test-optional.ts' || + script === 'tool-test-empty-object.ts') && + !supportsClientToolCalling(model) + ) { + console.log(`\n→ ${script}`) + console.log( + `SKIP: ${model} does not support client-side tool calling`, + ) + continue + } + + if (script === 'builtin-tools.ts' && !supportsBuiltInServerTools(model)) { + console.log(`\n→ ${script}`) + console.log( + `SKIP: ${model} does not advertise built-in server-side tools in this adapter`, + ) + continue + } + + console.log(`\n→ ${script}`) + try { + execFileSync('pnpm', ['exec', 'tsx', script, '--model', model], { + stdio: 'inherit', + env: process.env, + }) + } catch { + failures++ + console.error(`FAILED: ${script} on model=${model}`) + } + } +} + +if (failures > 0) { + console.error(`\n${failures} live test run(s) failed.`) + process.exit(1) +} + +console.log(`\nAll live test runs passed for: ${models.join(', ')}`) diff --git a/packages/typescript/ai-grok/live-tests/server-tools.ts b/packages/typescript/ai-grok/live-tests/server-tools.ts new file mode 100644 index 000000000..59da93a60 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/server-tools.ts @@ -0,0 +1,100 @@ +import { + codeExecutionTool, + codeInterpreterTool, + collectionsSearchTool, + fileSearchTool, +} from '../src/tools' +import { assert, loadApiKey, streamChat, textFromChunks } from './helpers' +import type { StreamChunk, Tool } from '@tanstack/ai' + +const apiKey = loadApiKey() +const vectorStoreId = process.env.XAI_VECTOR_STORE_ID + +async function runCase( + name: string, + prompt: string, + tools: Array, + validate: (text: string) => void, +) { + const chunks: Array = [] + for await (const chunk of streamChat({ + model: 'grok-4.3', + apiKey, + messages: [{ role: 'user', content: prompt }], + tools, + maxTokens: 192, + })) { + chunks.push(chunk) + } + + assert( + chunks.some((chunk) => chunk.type === 'RUN_FINISHED'), + `${name}: RUN_FINISHED not emitted`, + ) + + const text = textFromChunks(chunks) + + validate(text) + console.log(`PASS: ${name}`) + console.log(` response: ${JSON.stringify(text.slice(0, 160))}`) +} + +async function main() { + console.log('Testing additional Grok server-side tools\n') + + await runCase( + 'code_execution', + 'Use code_execution to compute the sum of integers from 1 to 10. Answer only with the result.', + [codeExecutionTool()], + (text) => { + assert( + /55/.test(text), + `code_execution: expected result 55, got ${JSON.stringify(text)}`, + ) + }, + ) + + await runCase( + 'code_interpreter', + 'Use code_interpreter to compute 12 * 12. Answer only with the result.', + [codeInterpreterTool({ type: 'auto' })], + (text) => { + assert( + /144/.test(text), + `code_interpreter: expected result 144, got ${JSON.stringify(text)}`, + ) + }, + ) + + if (!vectorStoreId) { + console.log('SKIP: file_search / collections_search (set XAI_VECTOR_STORE_ID)') + return + } + + await runCase( + 'file_search', + 'Use file_search on the provided collection and answer with a short greeting if the search works.', + [fileSearchTool({ type: 'file_search', vector_store_ids: [vectorStoreId] })], + (text) => { + assert(text.trim().length > 0, 'file_search: empty response') + }, + ) + + await runCase( + 'collections_search', + 'Use collections_search on the provided collection and answer with a short greeting if the search works.', + [ + collectionsSearchTool({ + vector_store_ids: [vectorStoreId], + }), + ], + (text) => { + assert(text.trim().length > 0, 'collections_search: empty response') + }, + ) +} + +main().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/streaming.ts b/packages/typescript/ai-grok/live-tests/streaming.ts new file mode 100644 index 000000000..cca26714d --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/streaming.ts @@ -0,0 +1,64 @@ +import { + assert, + findRunFinished, + loadApiKey, + streamChat, + textFromChunks, +} from './helpers' +import type { StreamChunk } from '@tanstack/ai' + +const apiKey = loadApiKey() + +async function testBasicStreaming() { + console.log('Testing Grok basic streaming (Responses API)\n') + + const chunks: Array = [] + for await (const chunk of streamChat({ + model: 'grok-4.3', + apiKey, + messages: [{ role: 'user', content: 'Reply with exactly: hello' }], + maxTokens: 32, + })) { + chunks.push(chunk) + } + + const types = new Set(chunks.map((chunk) => String(chunk.type))) + const required = [ + 'RUN_STARTED', + 'TEXT_MESSAGE_START', + 'TEXT_MESSAGE_CONTENT', + 'TEXT_MESSAGE_END', + 'RUN_FINISHED', + ] + const missing = required.filter((type) => !types.has(type)) + assert( + missing.length === 0, + `Missing AG-UI events: ${missing.join(', ')}\nSeen: ${[...types].join(', ')}`, + ) + + const text = textFromChunks(chunks) + assert( + text.toLowerCase().includes('hello'), + `Expected "hello" in response, got: ${text.slice(0, 80)}`, + ) + + const finished = findRunFinished(chunks) + assert(!!finished, 'Missing RUN_FINISHED event') + assert(!!finished.usage, 'Missing usage') + assert(finished.usage.promptTokens > 0, 'Missing usage.promptTokens') + assert(finished.usage.completionTokens > 0, 'Missing usage.completionTokens') + assert( + finished.finishReason === 'stop', + `Expected finishReason=stop, got: ${finished.finishReason}`, + ) + + console.log(`PASS: streaming`) + console.log(` Response: "${text.trim().slice(0, 60)}"`) + console.log(` Usage: ${JSON.stringify(finished.usage)}`) + console.log(` Events: ${chunks.length} chunks, ${types.size} unique types`) +} + +testBasicStreaming().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/structured-output.ts b/packages/typescript/ai-grok/live-tests/structured-output.ts new file mode 100644 index 000000000..da4f70d9b --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/structured-output.ts @@ -0,0 +1,60 @@ +import { chat } from '@tanstack/ai' +import { createGrokText } from '../src/index' +import { assert, loadApiKey } from './helpers' + +const apiKey = loadApiKey() + +type TokyoOutput = { + name?: unknown + population?: unknown + country?: unknown +} + +async function testStructuredOutput() { + console.log('Testing Grok structured output (Responses API)\n') + + const adapter = createGrokText('grok-4.3', apiKey) + + const schema = { + type: 'object', + properties: { + name: { type: 'string' }, + population: { type: 'number' }, + country: { type: 'string' }, + }, + required: ['name', 'population', 'country'] as Array, + additionalProperties: false, + } as const + + const result = await chat({ + adapter, + messages: [ + { + role: 'user', + content: + 'Return JSON for Tokyo with name, population (approx), and country.', + }, + ], + outputSchema: schema, + maxTokens: 128, + }) + + const data = result as TokyoOutput + assert(typeof data.name === 'string', `name not string: ${JSON.stringify(data)}`) + assert( + typeof data.population === 'number', + `population not number: ${JSON.stringify(data)}`, + ) + assert( + typeof data.country === 'string', + `country not string: ${JSON.stringify(data)}`, + ) + + console.log(`PASS: structured output`) + console.log(` Data: ${JSON.stringify(data)}`) +} + +testStructuredOutput().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/tool-test-empty-object.ts b/packages/typescript/ai-grok/live-tests/tool-test-empty-object.ts new file mode 100644 index 000000000..9c64209fe --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/tool-test-empty-object.ts @@ -0,0 +1,66 @@ +import { toolDefinition } from '@tanstack/ai' +import { + assert, + findToolCallEnd, + findToolCallStart, + loadApiKey, + streamChat, + textFromChunks, +} from './helpers' +import type { StreamChunk } from '@tanstack/ai' + +const apiKey = loadApiKey() + +async function testToolEmptyObject() { + console.log('Testing Grok tool calling with empty object schema (Responses API)\n') + + const getGuitars = toolDefinition({ + name: 'getGuitars', + description: 'Get all guitars from the catalog', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + } as const, + }).server(() => { + console.log(' Tool executed (no arguments)') + return [ + { id: '1', name: 'Fender Stratocaster', price: 1299 }, + { id: '2', name: 'Gibson Les Paul', price: 2499 }, + ] + }) + + const chunks: Array = [] + for await (const chunk of streamChat({ + model: 'grok-4.3', + apiKey, + messages: [ + { role: 'user', content: 'List all available guitars. Use getGuitars.' }, + ], + tools: [getGuitars], + maxTokens: 512, + })) { + chunks.push(chunk) + } + + const toolStart = findToolCallStart(chunks) + assert(!!toolStart, 'No TOOL_CALL_START event found') + assert( + toolStart.toolName === 'getGuitars', + `Wrong tool name: ${toolStart.toolName}`, + ) + + const toolEnd = findToolCallEnd(chunks) + assert(!!toolEnd, 'No TOOL_CALL_END event found') + + const text = textFromChunks(chunks) + console.log(` Final response: "${text.trim().slice(0, 80)}"`) + + console.log('\nPASS: tool calling with empty object schema') +} + +testToolEmptyObject().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/tool-test-optional.ts b/packages/typescript/ai-grok/live-tests/tool-test-optional.ts new file mode 100644 index 000000000..f024e03d2 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/tool-test-optional.ts @@ -0,0 +1,71 @@ +import { toolDefinition } from '@tanstack/ai' +import { assert, findToolCallEnd, loadApiKey, streamChat } from './helpers' +import type { StreamChunk } from '@tanstack/ai' + +const apiKey = loadApiKey() + +async function testToolOptionalParams() { + console.log('Testing Grok tool calling with optional parameters (Responses API)\n') + + const getTemperature = toolDefinition({ + name: 'get_temperature', + description: + 'Get the current temperature for a location. Unit defaults to fahrenheit if not specified.', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string', description: 'The city to check' }, + unit: { + type: 'string', + enum: ['celsius', 'fahrenheit'], + description: 'Temperature unit (optional)', + }, + }, + required: ['location'], + additionalProperties: false, + } as const, + }).server((args: unknown) => { + const { location, unit } = args as { + location: string + unit?: 'celsius' | 'fahrenheit' + } + const u = unit || 'fahrenheit' + console.log( + ` Tool executed: location="${location}", unit="${u}" (${unit ? 'provided' : 'defaulted'})`, + ) + return `The temperature in ${location} is 72 ${u === 'celsius' ? 'C' : 'F'}` + }) + + const chunks: Array = [] + for await (const chunk of streamChat({ + model: 'grok-4.3', + apiKey, + messages: [ + { + role: 'user', + content: 'What is the temperature in Paris? Use get_temperature.', + }, + ], + tools: [getTemperature], + maxTokens: 256, + })) { + chunks.push(chunk) + } + + const toolEnd = findToolCallEnd(chunks) + assert(!!toolEnd, 'No TOOL_CALL_END event found') + + const input = toolEnd.input as { location?: unknown } + assert( + typeof input.location === 'string', + `Expected location string, got: ${JSON.stringify(input)}`, + ) + console.log(` Tool args: ${JSON.stringify(input)}`) + + console.log('\nPASS: tool calling with optional parameters') +} + +testToolOptionalParams().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/live-tests/tool-test.ts b/packages/typescript/ai-grok/live-tests/tool-test.ts new file mode 100644 index 000000000..fe1be8836 --- /dev/null +++ b/packages/typescript/ai-grok/live-tests/tool-test.ts @@ -0,0 +1,82 @@ +import { toolDefinition } from '@tanstack/ai' +import { + assert, + findToolCallEnd, + findToolCallStart, + loadApiKey, + streamChat, + textFromChunks, +} from './helpers' +import type { StreamChunk } from '@tanstack/ai' + +const apiKey = loadApiKey() + +async function testToolCalling() { + console.log('Testing Grok tool calling with required parameters (Responses API)\n') + + const getTemperature = toolDefinition({ + name: 'get_temperature', + description: 'Get the current temperature for a specific location', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string', description: 'The city to check' }, + unit: { + type: 'string', + enum: ['celsius', 'fahrenheit'], + description: 'Temperature unit', + }, + }, + required: ['location', 'unit'], + additionalProperties: false, + } as const, + }).server((args: unknown) => { + const { location, unit } = args as { + location: string + unit: 'celsius' | 'fahrenheit' + } + console.log(` Tool executed: location="${location}", unit="${unit}"`) + return `The temperature in ${location} is 72 ${unit === 'celsius' ? 'C' : 'F'}` + }) + + const chunks: Array = [] + for await (const chunk of streamChat({ + model: 'grok-4.3', + apiKey, + messages: [ + { + role: 'user', + content: + 'What is the temperature in San Francisco in fahrenheit? Use get_temperature.', + }, + ], + tools: [getTemperature], + maxTokens: 256, + })) { + chunks.push(chunk) + } + + const toolStart = findToolCallStart(chunks) + assert(!!toolStart, 'No TOOL_CALL_START event found') + console.log(` Tool name: ${toolStart.toolName}`) + + const toolEnd = findToolCallEnd(chunks) + assert(!!toolEnd, 'No TOOL_CALL_END event found') + + const input = toolEnd.input as { location?: unknown } + assert( + typeof input.location === 'string', + `Expected location string, got: ${JSON.stringify(input)}`, + ) + console.log(` Tool args: ${JSON.stringify(input)}`) + + const text = textFromChunks(chunks) + console.log(` Final response: "${text.trim().slice(0, 80)}"`) + + console.log('\nPASS: tool calling with required parameters') +} + +testToolCalling().catch((error) => { + console.error('ERROR:', error.message) + process.exit(1) +}) diff --git a/packages/typescript/ai-grok/src/adapters/image.ts b/packages/typescript/ai-grok/src/adapters/image.ts index 21e2e0048..dd49493b9 100644 --- a/packages/typescript/ai-grok/src/adapters/image.ts +++ b/packages/typescript/ai-grok/src/adapters/image.ts @@ -28,7 +28,7 @@ export interface GrokImageConfig extends GrokClientConfig {} * Grok Image Generation Adapter * * Tree-shakeable adapter for Grok image generation functionality. - * Supports grok-2-image-1212 model. + * Supports grok-imagine-image model. * * Features: * - Model-specific type-safe provider options @@ -139,14 +139,14 @@ export class GrokImageAdapter< * Creates a Grok image adapter with explicit API key. * Type resolution happens here at the call site. * - * @param model - The model name (e.g., 'grok-2-image-1212') + * @param model - The model name (e.g., 'grok-imagine-image') * @param apiKey - Your xAI API key * @param config - Optional additional configuration * @returns Configured Grok image adapter instance with resolved types * * @example * ```typescript - * const adapter = createGrokImage('grok-2-image-1212', "xai-..."); + * const adapter = createGrokImage('grok-imagine-image', "xai-..."); * * const result = await generateImage({ * adapter, @@ -170,7 +170,7 @@ export function createGrokImage( * - `process.env` (Node.js) * - `window.env` (Browser with injected env) * - * @param model - The model name (e.g., 'grok-2-image-1212') + * @param model - The model name (e.g., 'grok-imagine-image') * @param config - Optional configuration (excluding apiKey which is auto-detected) * @returns Configured Grok image adapter instance with resolved types * @throws Error if XAI_API_KEY is not found in environment @@ -178,7 +178,7 @@ export function createGrokImage( * @example * ```typescript * // Automatically uses XAI_API_KEY from environment - * const adapter = grokImage('grok-2-image-1212'); + * const adapter = grokImage('grok-imagine-image'); * * const result = await generateImage({ * adapter, diff --git a/packages/typescript/ai-grok/src/adapters/summarize.ts b/packages/typescript/ai-grok/src/adapters/summarize.ts index eadaaf9e6..4fbd9a48b 100644 --- a/packages/typescript/ai-grok/src/adapters/summarize.ts +++ b/packages/typescript/ai-grok/src/adapters/summarize.ts @@ -161,14 +161,14 @@ export class GrokSummarizeAdapter< * Creates a Grok summarize adapter with explicit API key. * Type resolution happens here at the call site. * - * @param model - The model name (e.g., 'grok-3', 'grok-4') + * @param model - The model name (e.g., 'grok-4.3', 'grok-4.2') * @param apiKey - Your xAI API key * @param config - Optional additional configuration * @returns Configured Grok summarize adapter instance with resolved types * * @example * ```typescript - * const adapter = createGrokSummarize('grok-3', "xai-..."); + * const adapter = createGrokSummarize('grok-4.3', "xai-..."); * ``` */ export function createGrokSummarize( @@ -187,7 +187,7 @@ export function createGrokSummarize( * - `process.env` (Node.js) * - `window.env` (Browser with injected env) * - * @param model - The model name (e.g., 'grok-3', 'grok-4') + * @param model - The model name (e.g., 'grok-4.3', 'grok-4.2') * @param config - Optional configuration (excluding apiKey which is auto-detected) * @returns Configured Grok summarize adapter instance with resolved types * @throws Error if XAI_API_KEY is not found in environment @@ -195,7 +195,7 @@ export function createGrokSummarize( * @example * ```typescript * // Automatically uses XAI_API_KEY from environment - * const adapter = grokSummarize('grok-3'); + * const adapter = grokSummarize('grok-4.3'); * * await summarize({ * adapter, diff --git a/packages/typescript/ai-grok/src/adapters/text.ts b/packages/typescript/ai-grok/src/adapters/text.ts index e185c5ecf..ebde836ae 100644 --- a/packages/typescript/ai-grok/src/adapters/text.ts +++ b/packages/typescript/ai-grok/src/adapters/text.ts @@ -20,6 +20,7 @@ import type { } from '@tanstack/ai/adapters' import type { InternalLogger } from '@tanstack/ai/adapter-internals' import type OpenAI_SDK from 'openai' +import type { Responses } from 'openai/resources' import type { ContentPart, Modality, @@ -27,10 +28,7 @@ import type { StreamChunk, TextOptions, } from '@tanstack/ai' -import type { - ExternalTextProviderOptions as GrokTextProviderOptions, - InternalTextProviderOptions, -} from '../text/text-provider-options' +import type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../text/text-provider-options' import type { GrokImageMetadata, GrokMessageMetadataByModality, @@ -57,11 +55,26 @@ export interface GrokTextConfig extends GrokClientConfig {} */ export type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../text/text-provider-options' +/** + * Defaults applied to every Responses-API request unless the caller overrides + * them via `modelOptions`. They request encrypted reasoning content by default + * in stateless workflows (zero-data-retention friendly). + * + * Note: this adapter currently guarantees retrieval of encrypted reasoning + * blobs, not automatic replay on subsequent turns. + */ +const DEFAULT_INCLUDE: Array = [ + 'reasoning.encrypted_content', +] +const DEFAULT_STORE = false + /** * Grok Text (Chat) Adapter * - * Tree-shakeable adapter for Grok chat/text completion functionality. - * Uses OpenAI-compatible Chat Completions API (not Responses API). + * Targets xAI's Responses API (`POST /v1/responses`) — the same protocol + * shape as OpenAI's Responses API. Encrypted reasoning content is requested + * by default so multi-turn reasoning works without server-side conversation + * storage. */ export class GrokTextAdapter< TModel extends (typeof GROK_CHAT_MODELS)[number], @@ -88,13 +101,12 @@ export class GrokTextAdapter< } async *chatStream( - options: TextOptions, + options: TextOptions, ): AsyncIterable { const requestParams = this.mapTextOptionsToGrok(options) const timestamp = Date.now() const { logger } = options - // AG-UI lifecycle tracking (mutable state object for ESLint compatibility) const aguiState = { runId: options.runId ?? generateId(this.name), threadId: options.threadId ?? generateId(this.name), @@ -108,16 +120,21 @@ export class GrokTextAdapter< `activity=chat provider=grok model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`, { provider: 'grok', model: this.model }, ) - const stream = await this.client.chat.completions.create({ - ...requestParams, - stream: true, - }) + const stream = await this.client.responses.create( + { + ...requestParams, + stream: true, + }, + { + headers: options.request?.headers, + signal: options.request?.signal, + }, + ) - yield* this.processGrokStreamChunks(stream, options, aguiState, logger) + yield* this.processGrokResponsesStream(stream, options, aguiState, logger) } catch (error: unknown) { const err = error as Error & { code?: string } - // Emit RUN_STARTED if not yet emitted if (!aguiState.hasEmittedRunStarted) { aguiState.hasEmittedRunStarted = true yield asChunk({ @@ -129,7 +146,6 @@ export class GrokTextAdapter< }) } - // Emit AG-UI RUN_ERROR yield asChunk({ type: 'RUN_ERROR', runId: aguiState.runId, @@ -151,25 +167,22 @@ export class GrokTextAdapter< } /** - * Generate structured output using Grok's JSON Schema response format. - * Uses stream: false to get the complete response in one call. + * Generate structured output using xAI's Responses-API JSON Schema config. * - * Grok has strict requirements for structured output (via OpenAI-compatible API): + * Grok inherits OpenAI's strict-mode requirements: * - All properties must be in the `required` array * - Optional fields should have null added to their type union * - additionalProperties must be false for all objects * * The outputSchema is already JSON Schema (converted in the ai layer). - * We apply Grok-specific transformations for structured output compatibility. */ async structuredOutput( - options: StructuredOutputOptions, + options: StructuredOutputOptions, ): Promise> { const { chatOptions, outputSchema } = options const requestParams = this.mapTextOptionsToGrok(chatOptions) const { logger } = chatOptions - // Apply Grok-specific transformations for structured output compatibility const jsonSchema = makeGrokStructuredOutputCompatible( outputSchema, outputSchema.required || [], @@ -180,23 +193,27 @@ export class GrokTextAdapter< `activity=chat provider=grok model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`, { provider: 'grok', model: this.model }, ) - const response = await this.client.chat.completions.create({ - ...requestParams, - stream: false, - response_format: { - type: 'json_schema', - json_schema: { - name: 'structured_output', - schema: jsonSchema, - strict: true, + const response = await this.client.responses.create( + { + ...requestParams, + stream: false, + text: { + format: { + type: 'json_schema', + name: 'structured_output', + schema: jsonSchema, + strict: true, + }, }, }, - }) + { + headers: chatOptions.request?.headers, + signal: chatOptions.request?.signal, + }, + ) - // Extract text content from the response - const rawText = response.choices[0]?.message.content || '' + const rawText = this.extractTextFromResponse(response) - // Parse the JSON response let parsed: unknown try { parsed = JSON.parse(rawText) @@ -206,8 +223,8 @@ export class GrokTextAdapter< ) } - // Transform null values to undefined to match original Zod schema expectations - // Grok returns null for optional fields we made nullable in the schema + // Grok returns null for fields we made nullable in the schema; convert + // them back to undefined so the original Zod expectations line up. const transformed = transformNullsToUndefined(parsed) return { @@ -223,8 +240,30 @@ export class GrokTextAdapter< } } - private async *processGrokStreamChunks( - stream: AsyncIterable, + /** + * Walk a non-streaming Responses-API response and concatenate every + * `output_text` content part. + */ + private extractTextFromResponse( + response: OpenAI_SDK.Responses.Response, + ): string { + let textContent = '' + + for (const item of response.output) { + if (item.type === 'message') { + for (const part of item.content) { + if (part.type === 'output_text') { + textContent += part.text + } + } + } + } + + return textContent + } + + private async *processGrokResponsesStream( + stream: AsyncIterable, options: TextOptions, aguiState: { runId: string @@ -235,188 +274,508 @@ export class GrokTextAdapter< }, logger: InternalLogger, ): AsyncIterable { + const { runId, threadId, messageId, timestamp } = aguiState + let chunkCount = 0 + let model: string = options.model + let accumulatedContent = '' - const timestamp = aguiState.timestamp + let accumulatedReasoning = '' + let hasStreamedContentDeltas = false + let hasStreamedReasoningDeltas = false let hasEmittedTextMessageStart = false + let hasEmittedStepStarted = false + let stepId: string | null = null + let reasoningMessageId: string | null = null + let hasClosedReasoning = false - // Track tool calls being streamed (arguments come in chunks) - const toolCallsInProgress = new Map< - number, - { - id: string - name: string - arguments: string - started: boolean // Track if TOOL_CALL_START has been emitted - } + // tool call metadata captured from response.output_item.added so we can + // attach the function name when arguments deltas arrive. + const toolCallMetadata = new Map< + string, + { index: number; name: string; started: boolean } >() + const genId = () => generateId(this.name) + + const emitRunStarted = (currentModel: string): StreamChunk | null => { + if (aguiState.hasEmittedRunStarted) return null + aguiState.hasEmittedRunStarted = true + return asChunk({ + type: 'RUN_STARTED', + runId, + threadId, + model: currentModel, + timestamp, + }) + } + try { for await (const chunk of stream) { - logger.provider(`provider=grok`, { chunk }) - const choice = chunk.choices[0] + chunkCount++ + logger.provider(`provider=grok type=${chunk.type}`, { chunk }) - if (!choice) continue + const started = emitRunStarted(model || options.model) + if (started) yield started - // Emit RUN_STARTED on first chunk - if (!aguiState.hasEmittedRunStarted) { - aguiState.hasEmittedRunStarted = true - yield asChunk({ - type: 'RUN_STARTED', - runId: aguiState.runId, - threadId: aguiState.threadId, - model: chunk.model || options.model, + const handleContentPart = ( + contentPart: + | OpenAI_SDK.Responses.ResponseOutputText + | OpenAI_SDK.Responses.ResponseOutputRefusal + | OpenAI_SDK.Responses.ResponseContentPartAddedEvent.ReasoningText, + ): StreamChunk => { + if (contentPart.type === 'output_text') { + accumulatedContent += contentPart.text + return asChunk({ + type: 'TEXT_MESSAGE_CONTENT', + messageId, + model: model || options.model, + timestamp, + delta: contentPart.text, + content: accumulatedContent, + }) + } + + if (contentPart.type === 'reasoning_text') { + accumulatedReasoning += contentPart.text + const currentStepId = stepId || genId() + return asChunk({ + type: 'STEP_FINISHED', + stepName: currentStepId, + stepId: currentStepId, + model: model || options.model, + timestamp, + delta: contentPart.text, + content: accumulatedReasoning, + }) + } + + return asChunk({ + type: 'RUN_ERROR', + runId, + message: contentPart.refusal, + model: model || options.model, timestamp, + error: { message: contentPart.refusal }, }) } - const delta = choice.delta - const deltaContent = delta.content - const deltaToolCalls = delta.tool_calls + if ( + chunk.type === 'response.created' || + chunk.type === 'response.incomplete' || + chunk.type === 'response.failed' + ) { + model = chunk.response.model + // Reset per-response streaming flags. xAI may emit multiple + // response.* lifecycles in a single SSE stream during retries. + hasStreamedContentDeltas = false + hasStreamedReasoningDeltas = false + hasEmittedTextMessageStart = false + hasEmittedStepStarted = false + reasoningMessageId = null + hasClosedReasoning = false + accumulatedContent = '' + accumulatedReasoning = '' - // Handle content delta - if (deltaContent) { - // Emit TEXT_MESSAGE_START on first text content - if (!hasEmittedTextMessageStart) { - hasEmittedTextMessageStart = true + if (chunk.response.error) { yield asChunk({ - type: 'TEXT_MESSAGE_START', - messageId: aguiState.messageId, - model: chunk.model || options.model, + type: 'RUN_ERROR', + runId, + message: chunk.response.error.message, + code: chunk.response.error.code, + model: chunk.response.model, timestamp, - role: 'assistant', + error: chunk.response.error, }) } + if (chunk.response.incomplete_details) { + const incompleteMessage = + chunk.response.incomplete_details.reason ?? '' + yield asChunk({ + type: 'RUN_ERROR', + runId, + message: incompleteMessage, + model: chunk.response.model, + timestamp, + error: { message: incompleteMessage }, + }) + } + } - accumulatedContent += deltaContent + if (chunk.type === 'response.output_text.delta' && chunk.delta) { + const textDelta = Array.isArray(chunk.delta) + ? chunk.delta.join('') + : typeof chunk.delta === 'string' + ? chunk.delta + : '' - // Emit AG-UI TEXT_MESSAGE_CONTENT - yield asChunk({ - type: 'TEXT_MESSAGE_CONTENT', - messageId: aguiState.messageId, - model: chunk.model || options.model, - timestamp, - delta: deltaContent, - content: accumulatedContent, - }) - } + if (textDelta) { + if (reasoningMessageId && !hasClosedReasoning) { + hasClosedReasoning = true + yield asChunk({ + type: 'REASONING_MESSAGE_END', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) + yield asChunk({ + type: 'REASONING_END', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) + } - // Handle tool calls - they come in as deltas - if (deltaToolCalls) { - for (const toolCallDelta of deltaToolCalls) { - const index = toolCallDelta.index - - // Initialize or update the tool call in progress - if (!toolCallsInProgress.has(index)) { - toolCallsInProgress.set(index, { - id: toolCallDelta.id || '', - name: toolCallDelta.function?.name || '', - arguments: '', - started: false, + if (!hasEmittedTextMessageStart) { + hasEmittedTextMessageStart = true + yield asChunk({ + type: 'TEXT_MESSAGE_START', + messageId, + model: model || options.model, + timestamp, + role: 'assistant', }) } - const toolCall = toolCallsInProgress.get(index)! + accumulatedContent += textDelta + hasStreamedContentDeltas = true + yield asChunk({ + type: 'TEXT_MESSAGE_CONTENT', + messageId, + model: model || options.model, + timestamp, + delta: textDelta, + content: accumulatedContent, + }) + } + } - // Update with any new data from the delta - if (toolCallDelta.id) { - toolCall.id = toolCallDelta.id - } - if (toolCallDelta.function?.name) { - toolCall.name = toolCallDelta.function.name + if (chunk.type === 'response.reasoning_text.delta' && chunk.delta) { + const reasoningDelta = Array.isArray(chunk.delta) + ? chunk.delta.join('') + : typeof chunk.delta === 'string' + ? chunk.delta + : '' + + if (reasoningDelta) { + if (!hasEmittedStepStarted) { + hasEmittedStepStarted = true + stepId = genId() + reasoningMessageId = genId() + + yield asChunk({ + type: 'REASONING_START', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) + yield asChunk({ + type: 'REASONING_MESSAGE_START', + messageId: reasoningMessageId, + role: 'reasoning' as const, + model: model || options.model, + timestamp, + }) + + // Legacy STEP events (kept for compatibility during transition) + yield asChunk({ + type: 'STEP_STARTED', + stepName: stepId, + stepId, + model: model || options.model, + timestamp, + stepType: 'thinking', + }) } - if (toolCallDelta.function?.arguments) { - toolCall.arguments += toolCallDelta.function.arguments + + accumulatedReasoning += reasoningDelta + hasStreamedReasoningDeltas = true + + yield asChunk({ + type: 'REASONING_MESSAGE_CONTENT', + messageId: reasoningMessageId!, + delta: reasoningDelta, + model: model || options.model, + timestamp, + }) + + const resolvedStepId1 = stepId || genId() + yield asChunk({ + type: 'STEP_FINISHED', + stepName: resolvedStepId1, + stepId: resolvedStepId1, + model: model || options.model, + timestamp, + delta: reasoningDelta, + content: accumulatedReasoning, + }) + } + } + + if ( + chunk.type === 'response.reasoning_summary_text.delta' && + chunk.delta + ) { + const summaryDelta = + typeof chunk.delta === 'string' ? chunk.delta : '' + + if (summaryDelta) { + if (!hasEmittedStepStarted) { + hasEmittedStepStarted = true + stepId = genId() + reasoningMessageId = genId() + + yield asChunk({ + type: 'REASONING_START', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) + yield asChunk({ + type: 'REASONING_MESSAGE_START', + messageId: reasoningMessageId, + role: 'reasoning' as const, + model: model || options.model, + timestamp, + }) + + yield asChunk({ + type: 'STEP_STARTED', + stepName: stepId, + stepId, + model: model || options.model, + timestamp, + stepType: 'thinking', + }) } - // Emit TOOL_CALL_START when we have id and name - if (toolCall.id && toolCall.name && !toolCall.started) { - toolCall.started = true + accumulatedReasoning += summaryDelta + hasStreamedReasoningDeltas = true + + yield asChunk({ + type: 'REASONING_MESSAGE_CONTENT', + messageId: reasoningMessageId!, + delta: summaryDelta, + model: model || options.model, + timestamp, + }) + + const resolvedStepId2 = stepId || genId() + yield asChunk({ + type: 'STEP_FINISHED', + stepName: resolvedStepId2, + stepId: resolvedStepId2, + model: model || options.model, + timestamp, + delta: summaryDelta, + content: accumulatedReasoning, + }) + } + } + + if (chunk.type === 'response.content_part.added') { + const contentPart = chunk.part + + if (contentPart.type === 'output_text') { + if (reasoningMessageId && !hasClosedReasoning) { + hasClosedReasoning = true + yield asChunk({ + type: 'REASONING_MESSAGE_END', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) yield asChunk({ - type: 'TOOL_CALL_START', - toolCallId: toolCall.id, - toolCallName: toolCall.name, - toolName: toolCall.name, - model: chunk.model || options.model, + type: 'REASONING_END', + messageId: reasoningMessageId, + model: model || options.model, timestamp, - index, }) } - // Emit TOOL_CALL_ARGS for argument deltas - if (toolCallDelta.function?.arguments && toolCall.started) { + if (!hasEmittedTextMessageStart) { + hasEmittedTextMessageStart = true yield asChunk({ - type: 'TOOL_CALL_ARGS', - toolCallId: toolCall.id, - model: chunk.model || options.model, + type: 'TEXT_MESSAGE_START', + messageId, + model: model || options.model, timestamp, - delta: toolCallDelta.function.arguments, + role: 'assistant', }) } } + + if (contentPart.type === 'reasoning_text' && !hasEmittedStepStarted) { + hasEmittedStepStarted = true + stepId = genId() + reasoningMessageId = genId() + + yield asChunk({ + type: 'REASONING_START', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) + yield asChunk({ + type: 'REASONING_MESSAGE_START', + messageId: reasoningMessageId, + role: 'reasoning' as const, + model: model || options.model, + timestamp, + }) + + yield asChunk({ + type: 'STEP_STARTED', + stepName: stepId, + stepId, + model: model || options.model, + timestamp, + stepType: 'thinking', + }) + } + + yield handleContentPart(contentPart) } - // Handle finish reason - if (choice.finish_reason) { - // Emit all completed tool calls + if (chunk.type === 'response.content_part.done') { + const contentPart = chunk.part + + // Skip if we've already streamed this content via deltas — the done + // event is just the closing marker. + if (contentPart.type === 'output_text' && hasStreamedContentDeltas) { + continue + } if ( - choice.finish_reason === 'tool_calls' || - toolCallsInProgress.size > 0 + contentPart.type === 'reasoning_text' && + hasStreamedReasoningDeltas ) { - for (const [, toolCall] of toolCallsInProgress) { - // Parse arguments for TOOL_CALL_END - let parsedInput: unknown = {} - try { - parsedInput = toolCall.arguments - ? JSON.parse(toolCall.arguments) - : {} - } catch { - parsedInput = {} - } - - // Emit AG-UI TOOL_CALL_END - yield asChunk({ - type: 'TOOL_CALL_END', - toolCallId: toolCall.id, - toolCallName: toolCall.name, - toolName: toolCall.name, - model: chunk.model || options.model, - timestamp, - input: parsedInput, + continue + } + + yield handleContentPart(contentPart) + } + + if (chunk.type === 'response.output_item.added') { + const item = chunk.item + if (item.type === 'function_call' && item.id) { + if (!toolCallMetadata.has(item.id)) { + toolCallMetadata.set(item.id, { + index: chunk.output_index, + name: item.name || '', + started: false, }) } + yield asChunk({ + type: 'TOOL_CALL_START', + toolCallId: item.id, + toolCallName: item.name || '', + toolName: item.name || '', + model: model || options.model, + timestamp, + index: chunk.output_index, + }) + toolCallMetadata.get(item.id)!.started = true + } + } + + if ( + chunk.type === 'response.function_call_arguments.delta' && + chunk.delta + ) { + yield asChunk({ + type: 'TOOL_CALL_ARGS', + toolCallId: chunk.item_id, + model: model || options.model, + timestamp, + delta: chunk.delta, + }) + } + + if (chunk.type === 'response.function_call_arguments.done') { + const { item_id } = chunk + const metadata = toolCallMetadata.get(item_id) + const name = metadata?.name || '' + + let parsedInput: unknown = {} + try { + const parsed = chunk.arguments ? JSON.parse(chunk.arguments) : {} + parsedInput = parsed && typeof parsed === 'object' ? parsed : {} + } catch (parseError) { + logger.errors( + `grok: malformed function_call arguments for ${name}, defaulting to {}`, + { error: parseError, rawArguments: chunk.arguments }, + ) + parsedInput = {} } - const computedFinishReason = - choice.finish_reason === 'tool_calls' || - toolCallsInProgress.size > 0 - ? 'tool_calls' - : 'stop' + yield asChunk({ + type: 'TOOL_CALL_END', + toolCallId: item_id, + toolCallName: name, + toolName: name, + model: model || options.model, + timestamp, + input: parsedInput, + }) + } + + if (chunk.type === 'response.completed') { + if (reasoningMessageId && !hasClosedReasoning) { + hasClosedReasoning = true + yield asChunk({ + type: 'REASONING_MESSAGE_END', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) + yield asChunk({ + type: 'REASONING_END', + messageId: reasoningMessageId, + model: model || options.model, + timestamp, + }) + } - // Emit TEXT_MESSAGE_END if we had text content if (hasEmittedTextMessageStart) { yield asChunk({ type: 'TEXT_MESSAGE_END', - messageId: aguiState.messageId, - model: chunk.model || options.model, + messageId, + model: model || options.model, timestamp, }) } - // Emit AG-UI RUN_FINISHED + const hasFunctionCalls = chunk.response.output.some( + (item: unknown) => + (item as { type: string }).type === 'function_call', + ) + yield asChunk({ type: 'RUN_FINISHED', - runId: aguiState.runId, - threadId: aguiState.threadId, - model: chunk.model || options.model, + runId, + threadId, + model: model || options.model, + timestamp, + usage: { + promptTokens: chunk.response.usage?.input_tokens || 0, + completionTokens: chunk.response.usage?.output_tokens || 0, + totalTokens: chunk.response.usage?.total_tokens || 0, + }, + finishReason: hasFunctionCalls ? 'tool_calls' : 'stop', + }) + } + + if (chunk.type === 'error') { + yield asChunk({ + type: 'RUN_ERROR', + runId, + message: chunk.message, + code: chunk.code ?? undefined, + model: model || options.model, timestamp, - usage: chunk.usage - ? { - promptTokens: chunk.usage.prompt_tokens || 0, - completionTokens: chunk.usage.completion_tokens || 0, - totalTokens: chunk.usage.total_tokens || 0, - } - : undefined, - finishReason: computedFinishReason, + error: { + message: chunk.message, + code: chunk.code ?? undefined, + }, }) } } @@ -424,13 +783,13 @@ export class GrokTextAdapter< const err = error as Error & { code?: string } logger.errors('grok stream ended with error', { error, - source: 'grok.processGrokStreamChunks', + source: 'grok.processGrokResponsesStream', + totalChunks: chunkCount, }) - // Emit AG-UI RUN_ERROR yield asChunk({ type: 'RUN_ERROR', - runId: aguiState.runId, + runId, model: options.model, timestamp, message: err.message || 'Unknown error occurred', @@ -444,21 +803,23 @@ export class GrokTextAdapter< } /** - * Maps common options to Grok-specific Chat Completions format + * Build the Responses-API request body. Applies the encrypted-reasoning + * defaults (`store: false`, `include: ['reasoning.encrypted_content']`) + * unless the caller explicitly overrides them via `modelOptions`. */ private mapTextOptionsToGrok( options: TextOptions, - ): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming { + ): Omit { const modelOptions = options.modelOptions as - | Omit< - InternalTextProviderOptions, - 'max_tokens' | 'tools' | 'temperature' | 'input' | 'top_p' - > + | GrokTextProviderOptions | undefined + const input = this.convertMessagesToInput(options.messages) + if (modelOptions) { validateTextProviderOptions({ ...modelOptions, + input, model: options.model, }) } @@ -467,110 +828,125 @@ export class GrokTextAdapter< ? convertToolsToProviderFormat(options.tools) : undefined - // Build messages array with system prompts - const messages: Array = - [] - - // Add system prompts first - if (options.systemPrompts && options.systemPrompts.length > 0) { - messages.push({ - role: 'system', - content: options.systemPrompts.join('\n'), - }) - } - - // Convert messages - for (const message of options.messages) { - messages.push(this.convertMessageToGrok(message)) - } + // Caller wins: spread `modelOptions` last for explicit overrides, but + // preserve the encrypted-reasoning defaults if they didn't pass values. + const callerInclude = modelOptions?.include + const include = + callerInclude === undefined ? DEFAULT_INCLUDE : callerInclude + const store = + modelOptions?.store === undefined ? DEFAULT_STORE : modelOptions.store - return { + const requestParams: Omit< + OpenAI_SDK.Responses.ResponseCreateParams, + 'stream' + > = { model: options.model, - messages, temperature: options.temperature, - max_tokens: options.maxTokens, + max_output_tokens: options.maxTokens, top_p: options.topP, - tools: tools as Array, - stream: true, - stream_options: { include_usage: true }, + metadata: options.metadata, + instructions: options.systemPrompts?.join('\n'), + ...modelOptions, + include, + store, + input, + tools: tools as Array | undefined, } + + return requestParams } - private convertMessageToGrok( - message: ModelMessage, - ): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam { - // Handle tool messages - if (message.role === 'tool') { - return { - role: 'tool', - tool_call_id: message.toolCallId || '', - content: - typeof message.content === 'string' - ? message.content - : JSON.stringify(message.content), + private convertMessagesToInput( + messages: Array, + ): Responses.ResponseInput { + const result: Responses.ResponseInput = [] + + for (const message of messages) { + if (message.role === 'tool') { + result.push({ + type: 'function_call_output', + call_id: message.toolCallId || '', + output: + typeof message.content === 'string' + ? message.content + : JSON.stringify(message.content), + }) + continue } - } - // Handle assistant messages - if (message.role === 'assistant') { - const toolCalls = message.toolCalls?.map((tc) => ({ - id: tc.id, - type: 'function' as const, - function: { - name: tc.function.name, - arguments: - typeof tc.function.arguments === 'string' - ? tc.function.arguments - : JSON.stringify(tc.function.arguments), - }, - })) + if (message.role === 'assistant') { + if (message.toolCalls && message.toolCalls.length > 0) { + for (const toolCall of message.toolCalls) { + const argumentsString = + typeof toolCall.function.arguments === 'string' + ? toolCall.function.arguments + : JSON.stringify(toolCall.function.arguments) - return { - role: 'assistant', - content: this.extractTextContent(message.content), - ...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}), + result.push({ + type: 'function_call', + call_id: toolCall.id, + name: toolCall.function.name, + arguments: argumentsString, + }) + } + } + + if (message.content) { + const contentStr = this.extractTextContent(message.content) + if (contentStr) { + result.push({ + type: 'message', + role: 'assistant', + content: contentStr, + }) + } + } + + continue } - } - // Handle user messages - support multimodal content - const contentParts = this.normalizeContent(message.content) + const contentParts = this.normalizeContent(message.content) + const grokContent: Array = [] - // If only text, use simple string format - if (contentParts.length === 1 && contentParts[0]?.type === 'text') { - return { - role: 'user', - content: contentParts[0].content, + for (const part of contentParts) { + if (part.type === 'text') { + grokContent.push({ type: 'input_text', text: part.content }) + } else if (part.type === 'image') { + const imageMetadata = part.metadata as GrokImageMetadata | undefined + if (part.source.type === 'url') { + grokContent.push({ + type: 'input_image', + image_url: part.source.value, + detail: imageMetadata?.detail || 'auto', + }) + } else { + const imageValue = part.source.value + const imageUrl = imageValue.startsWith('data:') + ? imageValue + : `data:${part.source.mimeType};base64,${imageValue}` + grokContent.push({ + type: 'input_image', + image_url: imageUrl, + detail: imageMetadata?.detail || 'auto', + }) + } + } + // audio / video / document parts are silently dropped; xAI's + // /v1/responses endpoint does not accept them for chat models. } - } - // Otherwise, use array format for multimodal - const parts: Array = - [] - for (const part of contentParts) { - if (part.type === 'text') { - parts.push({ type: 'text', text: part.content }) - } else if (part.type === 'image') { - const imageMetadata = part.metadata as GrokImageMetadata | undefined - // For base64 data, construct a data URI using the mimeType from source - const imageValue = part.source.value - const imageUrl = - part.source.type === 'data' && !imageValue.startsWith('data:') - ? `data:${part.source.mimeType};base64,${imageValue}` - : imageValue - parts.push({ - type: 'image_url', - image_url: { - url: imageUrl, - detail: imageMetadata?.detail || 'auto', - }, - }) + if (grokContent.length === 0) { + grokContent.push({ type: 'input_text', text: '' }) } - } - return { - role: 'user', - content: parts.length > 0 ? parts : '', + result.push({ + type: 'message', + role: 'user', + content: grokContent, + }) } + + return result } /** @@ -601,7 +977,6 @@ export class GrokTextAdapter< if (typeof content === 'string') { return content } - // It's an array of ContentPart return content .filter((p) => p.type === 'text') .map((p) => p.content) @@ -611,17 +986,14 @@ export class GrokTextAdapter< /** * Creates a Grok text adapter with explicit API key. - * Type resolution happens here at the call site. * - * @param model - The model name (e.g., 'grok-3', 'grok-4') + * @param model - The model name (e.g., 'grok-4.3', 'grok-4.2') * @param apiKey - Your xAI API key * @param config - Optional additional configuration - * @returns Configured Grok text adapter instance with resolved types * * @example * ```typescript - * const adapter = createGrokText('grok-3', "xai-..."); - * // adapter has type-safe providerOptions for grok-3 + * const adapter = createGrokText('grok-4.3', "xai-..."); * ``` */ export function createGrokText< @@ -635,22 +1007,14 @@ export function createGrokText< } /** - * Creates a Grok text adapter with automatic API key detection from environment variables. - * Type resolution happens here at the call site. - * - * Looks for `XAI_API_KEY` in: - * - `process.env` (Node.js) - * - `window.env` (Browser with injected env) + * Creates a Grok text adapter with automatic API key detection from + * `XAI_API_KEY` in `process.env` (Node) or `window.env` (Browser). * - * @param model - The model name (e.g., 'grok-3', 'grok-4') - * @param config - Optional configuration (excluding apiKey which is auto-detected) - * @returns Configured Grok text adapter instance with resolved types * @throws Error if XAI_API_KEY is not found in environment * * @example * ```typescript - * // Automatically uses XAI_API_KEY from environment - * const adapter = grokText('grok-3'); + * const adapter = grokText('grok-4.3'); * * const stream = chat({ * adapter, diff --git a/packages/typescript/ai-grok/src/image/image-provider-options.ts b/packages/typescript/ai-grok/src/image/image-provider-options.ts index 9b0d9ee59..909bd1726 100644 --- a/packages/typescript/ai-grok/src/image/image-provider-options.ts +++ b/packages/typescript/ai-grok/src/image/image-provider-options.ts @@ -2,11 +2,11 @@ * Grok Image Generation Provider Options * * These are provider-specific options for Grok image generation. - * Grok uses the grok-2-image-1212 model for image generation. + * Grok uses grok-imagine-image for image generation. */ /** - * Supported sizes for grok-2-image-1212 model + * Supported sizes for Grok image models */ export type GrokImageSize = '1024x1024' | '1536x1024' | '1024x1536' @@ -22,7 +22,7 @@ export interface GrokImageBaseProviderOptions { } /** - * Provider options for grok-2-image-1212 model + * Provider options for Grok image models */ export interface GrokImageProviderOptions extends GrokImageBaseProviderOptions { /** @@ -43,14 +43,14 @@ export interface GrokImageProviderOptions extends GrokImageBaseProviderOptions { * Type-only map from model name to its specific provider options. */ export type GrokImageModelProviderOptionsByName = { - 'grok-2-image-1212': GrokImageProviderOptions + 'grok-imagine-image': GrokImageProviderOptions } /** * Type-only map from model name to its supported sizes. */ export type GrokImageModelSizeByName = { - 'grok-2-image-1212': GrokImageSize + 'grok-imagine-image': GrokImageSize } /** @@ -72,7 +72,7 @@ export function validateImageSize( if (!size) return const validSizes: Record> = { - 'grok-2-image-1212': ['1024x1024', '1536x1024', '1024x1536'], + 'grok-imagine-image': ['1024x1024', '1536x1024', '1024x1536'], } const modelSizes = validSizes[model] @@ -97,7 +97,7 @@ export function validateNumberOfImages( ): void { if (numberOfImages === undefined) return - // grok-2-image-1212 supports 1-10 images per request + // Grok image endpoints support 1-10 images per request if (numberOfImages < 1 || numberOfImages > 10) { throw new Error( `Number of images must be between 1 and 10. Requested: ${numberOfImages}`, @@ -112,7 +112,7 @@ export const validatePrompt = (options: ImageValidationOptions) => { // Grok image model supports up to 4000 characters if (options.prompt.length > 4000) { throw new Error( - 'For grok-2-image-1212, prompt length must be less than or equal to 4000 characters.', + 'For Grok image models, prompt length must be less than or equal to 4000 characters.', ) } } diff --git a/packages/typescript/ai-grok/src/model-meta.ts b/packages/typescript/ai-grok/src/model-meta.ts index e10cc5d6b..284ff57c6 100644 --- a/packages/typescript/ai-grok/src/model-meta.ts +++ b/packages/typescript/ai-grok/src/model-meta.ts @@ -1,13 +1,30 @@ +import type { + ExternalTextProviderOptions as ExternalGrokTextProviderOptions, + ExternalTextProviderOptionsWithoutEffort as ExternalGrokTextProviderOptionsWithoutEffort, +} from './text/text-provider-options' + /** * Model metadata interface for documentation and type inference */ +const GROK_SERVER_SIDE_TOOLS = [ + 'web_search', + 'x_search', + 'code_execution', + 'code_interpreter', + 'file_search', + 'collections_search', + 'mcp', +] as const + +type GrokServerSideToolKind = (typeof GROK_SERVER_SIDE_TOOLS)[number] + interface ModelMeta { name: string supports: { input: Array<'text' | 'image' | 'audio' | 'video' | 'document'> output: Array<'text' | 'image' | 'audio' | 'video'> capabilities?: Array<'reasoning' | 'tool_calling' | 'structured_outputs'> - tools?: ReadonlyArray + tools?: ReadonlyArray } max_input_tokens?: number max_output_tokens?: number @@ -24,264 +41,79 @@ interface ModelMeta { } } -const GROK_4_1_FAST_REASONING = { - name: 'grok-4-1-fast-reasoning', - context_window: 2_000_000, - supports: { - input: ['text', 'image'], - output: ['text'], - capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 0.2, - cached: 0.05, - }, - output: { - normal: 0.5, - }, - }, -} as const satisfies ModelMeta - -const GROK_4_1_FAST_NON_REASONING = { - name: 'grok-4-1-fast-non-reasoning', - context_window: 2_000_000, - supports: { - input: ['text', 'image'], - output: ['text'], - capabilities: ['structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 0.2, - cached: 0.05, - }, - output: { - normal: 0.5, - }, - }, -} as const satisfies ModelMeta - -const GROK_CODE_FAST_1 = { - name: 'grok-code-fast-1', - context_window: 256_000, - supports: { - input: ['text'], - output: ['text'], - capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 1.5, - }, - }, -} as const satisfies ModelMeta - -const GROK_4_FAST_REASONING = { - name: 'grok-4-fast-reasoning', +const GROK_4_2 = { + name: 'grok-4.2', context_window: 2_000_000, supports: { input: ['text', 'image'], output: ['text'], capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], - tools: [] as const, + tools: GROK_SERVER_SIDE_TOOLS, }, pricing: { input: { - normal: 0.2, - cached: 0.05, + normal: 2, + cached: 0.2, }, output: { - normal: 0.5, + normal: 6, }, }, } as const satisfies ModelMeta -const GROK_4_FAST_NON_REASONING = { - name: 'grok-4-fast-non-reasoning', +const GROK_4_2_NON_REASONING = { + name: 'grok-4-2-non-reasoning', context_window: 2_000_000, supports: { input: ['text', 'image'], output: ['text'], capabilities: ['structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 0.2, - cached: 0.05, - }, - output: { - normal: 0.5, - }, - }, -} as const satisfies ModelMeta - -const GROK_4 = { - name: 'grok-4', - context_window: 256_000, - supports: { - input: ['text', 'image'], - output: ['text'], - capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 3, - cached: 0.75, - }, - output: { - normal: 15, - }, - }, -} as const satisfies ModelMeta - -const GROK_3_MINI = { - name: 'grok-3-mini', - context_window: 131_072, - supports: { - input: ['text'], - output: ['text'], - capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 0.3, - cached: 0.075, - }, - output: { - normal: 0.5, - }, - }, -} as const satisfies ModelMeta - -const GROK_3 = { - name: 'grok-3', - context_window: 131_072, - supports: { - input: ['text'], - output: ['text'], - capabilities: ['structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 3, - cached: 0.75, - }, - output: { - normal: 15, - }, - }, -} as const satisfies ModelMeta - -const GROK_2_VISION = { - name: 'grok-2-vision-1212', - context_window: 32_768, - supports: { - input: ['text', 'image'], - output: ['text'], - capabilities: ['structured_outputs', 'tool_calling'], - tools: [] as const, + tools: GROK_SERVER_SIDE_TOOLS, }, pricing: { input: { normal: 2, + cached: 0.2, }, output: { - normal: 10, + normal: 6, }, }, } as const satisfies ModelMeta -const GROK_2_IMAGE = { - name: 'grok-2-image-1212', +const GROK_IMAGINE_IMAGE = { + name: 'grok-imagine-image', supports: { input: ['text'], output: ['image'], }, - pricing: { - input: { - normal: 0.07, - }, - output: { - normal: 0.07, - }, - }, } as const satisfies ModelMeta /** * Grok Chat Models * Based on xAI's available models as of 2025 */ -const GROK_4_20 = { - name: 'grok-4.20', - context_window: 2_000_000, - supports: { - input: ['text', 'image', 'document'], - output: ['text'], - capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 6, - }, - }, -} as const satisfies ModelMeta - -const GROK_4_20_MULTI_AGENT = { - name: 'grok-4.20-multi-agent', +const GROK_4_3 = { + name: 'grok-4.3', context_window: 2_000_000, supports: { - input: ['text', 'image', 'document'], + input: ['text', 'image'], output: ['text'], capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], - tools: [] as const, - }, - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 6, - }, + tools: GROK_SERVER_SIDE_TOOLS, }, } as const satisfies ModelMeta export const GROK_CHAT_MODELS = [ - GROK_4_1_FAST_REASONING.name, - GROK_4_1_FAST_NON_REASONING.name, - GROK_CODE_FAST_1.name, - GROK_4_FAST_REASONING.name, - GROK_4_FAST_NON_REASONING.name, - GROK_4.name, - GROK_3.name, - GROK_3_MINI.name, - GROK_2_VISION.name, - - GROK_4_20.name, - GROK_4_20_MULTI_AGENT.name, + GROK_4_2.name, + GROK_4_2_NON_REASONING.name, + GROK_4_3.name, ] as const /** * Grok Image Generation Models */ -export const GROK_IMAGE_MODELS = [GROK_2_IMAGE.name] as const +export const GROK_IMAGE_MODELS = [GROK_IMAGINE_IMAGE.name] as const // xAI's `/v1/tts` endpoint is endpoint-addressed and does not take a `model` // parameter. This synthetic identifier satisfies the SDK's `TTSOptions.model` @@ -345,17 +177,9 @@ export type GrokRealtimeModel = (typeof GROK_REALTIME_MODELS)[number] * Used for type inference when constructing multimodal messages. */ export type GrokModelInputModalitiesByName = { - [GROK_4_1_FAST_REASONING.name]: typeof GROK_4_1_FAST_REASONING.supports.input - [GROK_4_1_FAST_NON_REASONING.name]: typeof GROK_4_1_FAST_NON_REASONING.supports.input - [GROK_CODE_FAST_1.name]: typeof GROK_CODE_FAST_1.supports.input - [GROK_4_FAST_REASONING.name]: typeof GROK_4_FAST_REASONING.supports.input - [GROK_4_FAST_NON_REASONING.name]: typeof GROK_4_FAST_NON_REASONING.supports.input - [GROK_4.name]: typeof GROK_4.supports.input - [GROK_3.name]: typeof GROK_3.supports.input - [GROK_3_MINI.name]: typeof GROK_3_MINI.supports.input - [GROK_2_VISION.name]: typeof GROK_2_VISION.supports.input - [GROK_4_20.name]: typeof GROK_4_20.supports.input - [GROK_4_20_MULTI_AGENT.name]: typeof GROK_4_20_MULTI_AGENT.supports.input + [GROK_4_2.name]: typeof GROK_4_2.supports.input + [GROK_4_2_NON_REASONING.name]: typeof GROK_4_2_NON_REASONING.supports.input + [GROK_4_3.name]: typeof GROK_4_3.supports.input } /** @@ -363,49 +187,35 @@ export type GrokModelInputModalitiesByName = { * Since Grok uses OpenAI-compatible API, we reuse OpenAI provider options. */ export type GrokChatModelProviderOptionsByName = { - [K in (typeof GROK_CHAT_MODELS)[number]]: GrokProviderOptions + [GROK_4_2.name]: ExternalGrokTextProviderOptionsWithoutEffort + [GROK_4_2_NON_REASONING.name]: ExternalGrokTextProviderOptions + [GROK_4_3.name]: ExternalGrokTextProviderOptionsWithoutEffort } /** * Type-only map from Grok chat model name to its supported provider tools. - * Grok exposes no provider-specific tool factories, so every model gets an - * empty tuple. This ensures that passing an Anthropic/OpenAI ProviderTool to - * a Grok adapter produces a compile-time type error. + * Keyed on each model's `.name` field. Value is the `typeof supports.tools` + * tuple from each model constant. */ export type GrokChatModelToolCapabilitiesByName = { - [GROK_4_1_FAST_REASONING.name]: typeof GROK_4_1_FAST_REASONING.supports.tools - [GROK_4_1_FAST_NON_REASONING.name]: typeof GROK_4_1_FAST_NON_REASONING.supports.tools - [GROK_CODE_FAST_1.name]: typeof GROK_CODE_FAST_1.supports.tools - [GROK_4_FAST_REASONING.name]: typeof GROK_4_FAST_REASONING.supports.tools - [GROK_4_FAST_NON_REASONING.name]: typeof GROK_4_FAST_NON_REASONING.supports.tools - [GROK_4.name]: typeof GROK_4.supports.tools - [GROK_3.name]: typeof GROK_3.supports.tools - [GROK_3_MINI.name]: typeof GROK_3_MINI.supports.tools - [GROK_2_VISION.name]: typeof GROK_2_VISION.supports.tools - [GROK_4_20.name]: typeof GROK_4_20.supports.tools - [GROK_4_20_MULTI_AGENT.name]: typeof GROK_4_20_MULTI_AGENT.supports.tools + [GROK_4_2.name]: typeof GROK_4_2.supports.tools + [GROK_4_2_NON_REASONING.name]: typeof GROK_4_2_NON_REASONING.supports.tools + [GROK_4_3.name]: typeof GROK_4_3.supports.tools } /** - * Grok-specific provider options - * Based on OpenAI-compatible API options + * Grok-specific provider options. + * + * Targets xAI's Responses API (`/v1/responses`). Common knobs (temperature, + * topP, maxTokens, metadata) live on the top-level `chat()` / `generate()` + * call as part of `TextOptions` — pass anything Responses-API-specific + * (`include`, `store`, `reasoning`, `previous_response_id`, `tool_choice`, + * `parallel_tool_calls`, `text`, etc.) inside `modelOptions`. + * + * The shape lives in `text/text-provider-options.ts`; we re-export it here + * so model-aware type inference picks it up. */ -export interface GrokProviderOptions { - /** Temperature for response generation (0-2) */ - temperature?: number - /** Maximum tokens in the response */ - max_tokens?: number - /** Top-p sampling parameter */ - top_p?: number - /** Frequency penalty (-2.0 to 2.0) */ - frequency_penalty?: number - /** Presence penalty (-2.0 to 2.0) */ - presence_penalty?: number - /** Stop sequences */ - stop?: string | Array - /** A unique identifier representing your end-user */ - user?: string -} +export type GrokProviderOptions = ExternalGrokTextProviderOptions // =========================== // Type Resolution Helpers diff --git a/packages/typescript/ai-grok/src/text/text-provider-options.ts b/packages/typescript/ai-grok/src/text/text-provider-options.ts index a05222ff1..7f74f8a86 100644 --- a/packages/typescript/ai-grok/src/text/text-provider-options.ts +++ b/packages/typescript/ai-grok/src/text/text-provider-options.ts @@ -1,77 +1,182 @@ +import type OpenAI from 'openai' import type { FunctionTool } from '../tools/function-tool' /** * Grok Text Provider Options * - * Grok uses an OpenAI-compatible Chat Completions API. - * However, not all OpenAI features may be supported by Grok. + * The Grok text adapter targets xAI's `/v1/responses` endpoint, which is + * Responses-API compatible. These options mirror the option fragments used by + * the OpenAI Responses adapter so callers can opt into reasoning, structured + * output, parallel tool calling, encrypted-reasoning round-tripping, and so on. + * + * Common knobs (temperature, topP, maxTokens, metadata) live on the top-level + * `chat()` / `generate()` call as part of `TextOptions`. Everything below goes + * inside `modelOptions`. */ /** - * Base provider options for Grok text/chat models + * Base Responses-API options for Grok text/chat models. */ export interface GrokBaseOptions { /** - * A unique identifier representing your end-user. - * Can help xAI to monitor and detect abuse. + * Specify additional output data to include in the model response. + * + * Notably `reasoning.encrypted_content` returns an encrypted version of the + * reasoning tokens so they can be replayed in subsequent stateless requests. + * The Grok adapter sets `['reasoning.encrypted_content']` by default; pass + * `[]` (or omit specific entries) to opt out. + */ + include?: Array | null + + /** + * The unique ID of a previous response to chain from. Use this to create + * multi-turn conversations on the server. + */ + previous_response_id?: string | null + + /** + * Whether to store the generated model response on the server for later + * retrieval. The Grok adapter defaults to `false` so that encrypted reasoning + * content is the round-trip mechanism (compatible with zero data retention + * setups). Pass `true` to opt back into server-side storage. + */ + store?: boolean | null + + /** + * The truncation strategy to use for the model response. + * + * - `auto`: drop earlier conversation items if the input exceeds the + * model's context window. + * - `disabled` (default): fail the request with a 400 if the input is too + * long. + */ + truncation?: 'auto' | 'disabled' | null + + /** + * A unique identifier representing your end-user. Helps xAI detect abuse. */ user?: string } +type ReasoningSummary = 'auto' | 'concise' | 'detailed' +type SupportedReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' + /** - * Grok-specific provider options for text/chat - * Based on OpenAI-compatible API options + * Full reasoning controls for models that accept explicit effort selection. */ -export interface GrokTextProviderOptions extends GrokBaseOptions { - /** - * Temperature for response generation (0-2) - * Higher values make output more random, lower values more focused - */ - temperature?: number +export interface GrokReasoningOptions { + reasoning?: { + /** Guides how much chain-of-thought computation to spend. */ + effort?: SupportedReasoningEffort + /** A summary of the reasoning performed by the model. */ + summary?: ReasoningSummary + } | null +} + +/** + * Limited reasoning controls for models that reason automatically but reject an + * explicit `reasoning.effort` parameter. + */ +export interface GrokReasoningOptionsWithoutEffort { + reasoning?: { + /** A summary of the reasoning performed by the model. */ + summary?: ReasoningSummary + } | null +} + +/** + * Structured output configuration. Prefer the top-level `outputSchema` on + * `chat()` / `generate()` for the common case; this escape hatch is here for + * advanced use of Grok's `text.format` field (e.g. plain text formatting hints). + */ +export interface GrokStructuredOutputOptions { + text?: OpenAI.Responses.ResponseTextConfig +} + +export interface GrokToolsOptions { /** - * Top-p sampling parameter (0-1) - * Alternative to temperature, nucleus sampling + * The maximum number of total tool calls the model may make in this response. */ - top_p?: number + max_tool_calls?: number | null + /** - * Maximum tokens in the response + * Whether to allow the model to run tool calls in parallel. Defaults to + * provider behavior (typically `true`). */ - max_tokens?: number + parallel_tool_calls?: boolean | null + /** - * Frequency penalty (-2.0 to 2.0) + * How the model should select which tool (or tools) to use. */ - frequency_penalty?: number + tool_choice?: OpenAI.Responses.ResponseCreateParams['tool_choice'] +} + +export interface GrokStreamingOptions { /** - * Presence penalty (-2.0 to 2.0) + * Options for streaming responses. Only set when `stream: true`. */ - presence_penalty?: number + stream_options?: { + /** + * Disable obfuscation padding on streaming deltas to save bandwidth on + * trusted network paths. + */ + include_obfuscation?: boolean + } | null +} + +export interface GrokMetadataOptions { /** - * Stop sequences + * Set of up to 16 key-value pairs that can be attached to the response. + * Keys: max 64 chars. Values: max 512 chars. */ - stop?: string | Array + metadata?: Record | null } +export type GrokSharedTextProviderOptions = + GrokBaseOptions & + GrokStructuredOutputOptions & + GrokToolsOptions & + GrokStreamingOptions & + GrokMetadataOptions + +export type ExternalTextProviderOptions = + GrokSharedTextProviderOptions & GrokReasoningOptions + +export type ExternalTextProviderOptionsWithoutEffort = + GrokSharedTextProviderOptions & GrokReasoningOptionsWithoutEffort + /** - * Internal options interface for validation - * Used internally by the adapter + * Internal options interface used inside the adapter while building the + * Responses-API request body. Not part of the public surface. */ -export interface InternalTextProviderOptions extends GrokTextProviderOptions { +export interface InternalTextProviderOptions extends ExternalTextProviderOptions { + input: string | OpenAI.Responses.ResponseInput + instructions?: string | null + max_output_tokens?: number | null model: string stream?: boolean + temperature?: number | null + top_p?: number | null tools?: Array } /** - * External provider options (what users pass in) - */ -export type ExternalTextProviderOptions = GrokTextProviderOptions - -/** - * Validates text provider options + * Validates text provider options. Runtime guard kept as defense-in-depth so a + * provider 400 is converted into a clearer local SDK error. */ export function validateTextProviderOptions( - _options: InternalTextProviderOptions, + options: InternalTextProviderOptions, ): void { - // Basic validation can be added here if needed - // For now, Grok API will handle validation + if (options.reasoning?.effort) { + const unsupportedReasoningEffortModels = new Set([ + 'grok-4.3', + 'grok-4.2', + ]) + + if (unsupportedReasoningEffortModels.has(options.model)) { + throw new Error( + `${options.model} does not support modelOptions.reasoning.effort on xAI's Responses API. Remove reasoning.effort for this model and rely on the model's default reasoning behavior.`, + ) + } + } } diff --git a/packages/typescript/ai-grok/src/tools/code-execution-tool.ts b/packages/typescript/ai-grok/src/tools/code-execution-tool.ts new file mode 100644 index 000000000..f20987ce1 --- /dev/null +++ b/packages/typescript/ai-grok/src/tools/code-execution-tool.ts @@ -0,0 +1,60 @@ +import type OpenAI from 'openai' +import type { ProviderTool, Tool } from '@tanstack/ai' + +// xAI accepts `code_execution` as a built-in tool name. Keep the shape minimal +// until the provider publishes a richer public schema. +export interface CodeExecutionToolConfig { + type: 'code_execution' +} + +export type CodeExecutionToolOptions = Omit + +/** @deprecated Renamed to `CodeExecutionToolConfig`. Will be removed in a future release. */ +export type CodeExecutionTool = CodeExecutionToolConfig + +export type GrokCodeExecutionTool = ProviderTool<'grok', 'code_execution'> +export type CodeInterpreterToolConfig = OpenAI.Responses.Tool.CodeInterpreter +export type GrokCodeInterpreterTool = ProviderTool<'grok', 'code_interpreter'> + +export function convertCodeExecutionToolToAdapterFormat( + tool: Tool, +): CodeExecutionToolConfig { + const metadata = tool.metadata as Partial | undefined + return { + type: 'code_execution', + ...metadata, + } +} + +export function convertCodeInterpreterToolToAdapterFormat( + tool: Tool, +): CodeInterpreterToolConfig { + const metadata = tool.metadata as CodeInterpreterToolConfig + return { + type: 'code_interpreter', + container: metadata.container, + } +} + +export function codeExecutionTool( + toolData: CodeExecutionToolOptions = {}, +): GrokCodeExecutionTool { + return { + name: 'code_execution', + description: 'Run Python code in a sandboxed environment', + metadata: { type: 'code_execution', ...toolData }, + } as unknown as GrokCodeExecutionTool +} + +export function codeInterpreterTool( + container: CodeInterpreterToolConfig['container'], +): GrokCodeInterpreterTool { + return { + name: 'code_interpreter', + description: 'Run Python code in a sandboxed environment', + metadata: { + type: 'code_interpreter', + container, + }, + } as unknown as GrokCodeInterpreterTool +} diff --git a/packages/typescript/ai-grok/src/tools/file-search-tool.ts b/packages/typescript/ai-grok/src/tools/file-search-tool.ts new file mode 100644 index 000000000..c5e5530a6 --- /dev/null +++ b/packages/typescript/ai-grok/src/tools/file-search-tool.ts @@ -0,0 +1,83 @@ +import type OpenAI from 'openai' +import type { ProviderTool, Tool } from '@tanstack/ai' + +const validateVectorStoreIds = (ids: Array | undefined) => { + if (!ids || ids.length === 0) { + throw new Error('vector_store_ids must contain at least one id.') + } +} + +export type FileSearchToolConfig = OpenAI.Responses.FileSearchTool +export type FileSearchToolOptions = FileSearchToolConfig + +export interface CollectionsSearchToolConfig { + type: 'collections_search' + vector_store_ids: Array + max_num_results?: number +} + +export type CollectionsSearchToolOptions = Omit< + CollectionsSearchToolConfig, + 'type' +> + +/** @deprecated Renamed to `FileSearchToolConfig`. Will be removed in a future release. */ +export type FileSearchTool = FileSearchToolConfig +/** @deprecated Renamed to `CollectionsSearchToolConfig`. Will be removed in a future release. */ +export type CollectionsSearchTool = CollectionsSearchToolConfig + +export type GrokFileSearchTool = ProviderTool<'grok', 'file_search'> +export type GrokCollectionsSearchTool = ProviderTool<'grok', 'collections_search'> + +export function convertFileSearchToolToAdapterFormat( + tool: Tool, +): FileSearchToolConfig { + const metadata = tool.metadata as FileSearchToolConfig + validateVectorStoreIds(metadata.vector_store_ids) + return { + type: 'file_search', + vector_store_ids: metadata.vector_store_ids, + max_num_results: metadata.max_num_results, + ranking_options: metadata.ranking_options, + filters: metadata.filters, + } +} + +export function convertCollectionsSearchToolToAdapterFormat( + tool: Tool, +): CollectionsSearchToolConfig { + const metadata = tool.metadata as CollectionsSearchToolConfig + validateVectorStoreIds(metadata.vector_store_ids) + return { + type: 'collections_search', + vector_store_ids: metadata.vector_store_ids, + max_num_results: metadata.max_num_results, + } +} + +export function fileSearchTool( + toolData: FileSearchToolConfig, +): GrokFileSearchTool { + validateVectorStoreIds(toolData.vector_store_ids) + return { + name: 'file_search', + description: 'Search files in vector stores', + metadata: { + ...toolData, + }, + } as unknown as GrokFileSearchTool +} + +export function collectionsSearchTool( + toolData: CollectionsSearchToolOptions, +): GrokCollectionsSearchTool { + validateVectorStoreIds(toolData.vector_store_ids) + return { + name: 'collections_search', + description: 'Search uploaded document collections', + metadata: { + type: 'collections_search', + ...toolData, + }, + } as unknown as GrokCollectionsSearchTool +} diff --git a/packages/typescript/ai-grok/src/tools/function-tool.ts b/packages/typescript/ai-grok/src/tools/function-tool.ts index 646fb8953..8186bd6ae 100644 --- a/packages/typescript/ai-grok/src/tools/function-tool.ts +++ b/packages/typescript/ai-grok/src/tools/function-tool.ts @@ -2,23 +2,24 @@ import { makeGrokStructuredOutputCompatible } from '../utils/schema-converter' import type { JSONSchema, Tool } from '@tanstack/ai' import type OpenAI from 'openai' -// Use Chat Completions API tool format (not Responses API) -export type FunctionTool = OpenAI.Chat.Completions.ChatCompletionTool +/** + * Responses-API function tool shape (flat, no nested `function` object). + * The Grok text adapter targets `/v1/responses`, which uses this format. + */ +export type FunctionTool = OpenAI.Responses.FunctionTool /** - * Converts a standard Tool to Grok ChatCompletionTool format. + * Converts a standard Tool to a Grok Responses-API function tool. * - * Tool schemas are already converted to JSON Schema in the ai layer. + * Tool schemas arrive as JSON Schema (already converted in the ai layer). * We apply Grok-specific transformations for strict mode: - * - All properties in required array - * - Optional fields made nullable - * - additionalProperties: false + * - All properties moved into the `required` array + * - Optional fields become nullable + * - `additionalProperties: false` * - * This enables strict mode for all tools automatically. + * Strict mode is always on so the model returns clean, validated arguments. */ export function convertFunctionToolToAdapterFormat(tool: Tool): FunctionTool { - // Tool schemas are already converted to JSON Schema in the ai layer - // Apply Grok-specific transformations for strict mode const inputSchema = (tool.inputSchema ?? { type: 'object', properties: {}, @@ -30,16 +31,13 @@ export function convertFunctionToolToAdapterFormat(tool: Tool): FunctionTool { inputSchema.required || [], ) - // Ensure additionalProperties is false for strict mode jsonSchema.additionalProperties = false return { type: 'function', - function: { - name: tool.name, - description: tool.description, - parameters: jsonSchema, - strict: true, // Always use strict mode since our schema converter handles the requirements - }, + name: tool.name, + description: tool.description, + parameters: jsonSchema, + strict: true, } satisfies FunctionTool } diff --git a/packages/typescript/ai-grok/src/tools/index.ts b/packages/typescript/ai-grok/src/tools/index.ts index c90334153..9ac1637ca 100644 --- a/packages/typescript/ai-grok/src/tools/index.ts +++ b/packages/typescript/ai-grok/src/tools/index.ts @@ -1,5 +1,72 @@ +import type { CodeExecutionToolConfig, CodeInterpreterToolConfig } from './code-execution-tool' +import type { CollectionsSearchToolConfig, FileSearchToolConfig } from './file-search-tool' +import type { FunctionTool } from './function-tool' +import type { MCPToolConfig } from './mcp-tool' +import type { WebSearchToolConfig } from './web-search-tool' +import type { XSearchToolConfig } from './x-search-tool' + +export type GrokTool = + | CodeExecutionToolConfig + | CodeInterpreterToolConfig + | CollectionsSearchToolConfig + | FileSearchToolConfig + | FunctionTool + | MCPToolConfig + | WebSearchToolConfig + | XSearchToolConfig + +export { + codeExecutionTool, + codeInterpreterTool, + convertCodeExecutionToolToAdapterFormat, + convertCodeInterpreterToolToAdapterFormat, + type CodeExecutionToolConfig, + type CodeExecutionToolOptions, + type CodeExecutionTool, + type GrokCodeExecutionTool, + type CodeInterpreterToolConfig, + type GrokCodeInterpreterTool, +} from './code-execution-tool' +export { + collectionsSearchTool, + convertCollectionsSearchToolToAdapterFormat, + convertFileSearchToolToAdapterFormat, + fileSearchTool, + type CollectionsSearchToolConfig, + type CollectionsSearchToolOptions, + type CollectionsSearchTool, + type FileSearchToolConfig, + type FileSearchToolOptions, + type FileSearchTool, + type GrokCollectionsSearchTool, + type GrokFileSearchTool, +} from './file-search-tool' export { convertFunctionToolToAdapterFormat, type FunctionTool, } from './function-tool' +export { + convertMCPToolToAdapterFormat, + mcpTool, + validateMCPTool, + type GrokMCPTool, + type MCPTool, + type MCPToolConfig, +} from './mcp-tool' +export { + convertWebSearchToolToAdapterFormat, + webSearchTool, + type GrokWebSearchTool, + type WebSearchTool, + type WebSearchToolConfig, + type WebSearchToolOptions, +} from './web-search-tool' +export { + convertXSearchToolToAdapterFormat, + xSearchTool, + type GrokXSearchTool, + type XSearchTool, + type XSearchToolConfig, + type XSearchToolOptions, +} from './x-search-tool' export { convertToolsToProviderFormat } from './tool-converter' diff --git a/packages/typescript/ai-grok/src/tools/mcp-tool.ts b/packages/typescript/ai-grok/src/tools/mcp-tool.ts new file mode 100644 index 000000000..db93d9bc9 --- /dev/null +++ b/packages/typescript/ai-grok/src/tools/mcp-tool.ts @@ -0,0 +1,40 @@ +import type OpenAI from 'openai' +import type { ProviderTool, Tool } from '@tanstack/ai' + +export type MCPToolConfig = OpenAI.Responses.Tool.Mcp + +/** @deprecated Renamed to `MCPToolConfig`. Will be removed in a future release. */ +export type MCPTool = MCPToolConfig + +export type GrokMCPTool = ProviderTool<'grok', 'mcp'> + +export function validateMCPTool(tool: MCPToolConfig) { + if (!tool.server_url && !tool.connector_id) { + throw new Error('Either server_url or connector_id must be provided.') + } + if (tool.connector_id && tool.server_url) { + throw new Error('Only one of server_url or connector_id can be provided.') + } +} + +export function convertMCPToolToAdapterFormat(tool: Tool): MCPToolConfig { + const metadata = tool.metadata as Omit + const mcpTool: MCPToolConfig = { + type: 'mcp', + ...metadata, + } + validateMCPTool(mcpTool) + return mcpTool +} + +export function mcpTool(toolData: Omit): GrokMCPTool { + validateMCPTool({ ...toolData, type: 'mcp' }) + return { + name: 'mcp', + description: toolData.server_description || '', + metadata: { + type: 'mcp', + ...toolData, + }, + } as unknown as GrokMCPTool +} diff --git a/packages/typescript/ai-grok/src/tools/tool-converter.ts b/packages/typescript/ai-grok/src/tools/tool-converter.ts index 969fdb72d..e66085db0 100644 --- a/packages/typescript/ai-grok/src/tools/tool-converter.ts +++ b/packages/typescript/ai-grok/src/tools/tool-converter.ts @@ -1,17 +1,48 @@ +import { + convertCodeExecutionToolToAdapterFormat, + convertCodeInterpreterToolToAdapterFormat, +} from './code-execution-tool' +import { + convertCollectionsSearchToolToAdapterFormat, + convertFileSearchToolToAdapterFormat, +} from './file-search-tool' import { convertFunctionToolToAdapterFormat } from './function-tool' -import type { FunctionTool } from './function-tool' +import { convertMCPToolToAdapterFormat } from './mcp-tool' +import { convertWebSearchToolToAdapterFormat } from './web-search-tool' +import { convertXSearchToolToAdapterFormat } from './x-search-tool' +import type { GrokTool } from './index' import type { Tool } from '@tanstack/ai' /** - * Converts an array of standard Tools to Grok-specific format - * Grok uses OpenAI-compatible API, so we primarily support function tools + * Converts standard TanStack AI tools to xAI Responses-API tool format. + * + * Regular application tools become strict function tools. Grok provider-tool + * factories set `metadata.type` to a native xAI server-side tool type; those are + * forwarded in their provider-native shape instead. */ export function convertToolsToProviderFormat( tools: Array, -): Array { +): Array { return tools.map((tool) => { - // For Grok, all tools are converted as function tools - // Grok uses OpenAI-compatible API which primarily supports function tools - return convertFunctionToolToAdapterFormat(tool) + const toolType = (tool.metadata as { type?: string } | undefined)?.type + + switch (toolType) { + case 'code_execution': + return convertCodeExecutionToolToAdapterFormat(tool) + case 'code_interpreter': + return convertCodeInterpreterToolToAdapterFormat(tool) + case 'collections_search': + return convertCollectionsSearchToolToAdapterFormat(tool) + case 'file_search': + return convertFileSearchToolToAdapterFormat(tool) + case 'mcp': + return convertMCPToolToAdapterFormat(tool) + case 'web_search': + return convertWebSearchToolToAdapterFormat(tool) + case 'x_search': + return convertXSearchToolToAdapterFormat(tool) + default: + return convertFunctionToolToAdapterFormat(tool) + } }) } diff --git a/packages/typescript/ai-grok/src/tools/web-search-tool.ts b/packages/typescript/ai-grok/src/tools/web-search-tool.ts new file mode 100644 index 000000000..08cb0a85f --- /dev/null +++ b/packages/typescript/ai-grok/src/tools/web-search-tool.ts @@ -0,0 +1,26 @@ +import type OpenAI from 'openai' +import type { ProviderTool, Tool } from '@tanstack/ai' + +export type WebSearchToolConfig = OpenAI.Responses.WebSearchTool +export type WebSearchToolOptions = Omit + +/** @deprecated Renamed to `WebSearchToolConfig`. Will be removed in a future release. */ +export type WebSearchTool = WebSearchToolConfig + +export type GrokWebSearchTool = ProviderTool<'grok', 'web_search'> + +export function convertWebSearchToolToAdapterFormat( + tool: Tool, +): WebSearchToolConfig { + return tool.metadata as WebSearchToolConfig +} + +export function webSearchTool( + toolData: WebSearchToolOptions = {}, +): GrokWebSearchTool { + return { + name: 'web_search', + description: 'Search the web', + metadata: { type: 'web_search', ...toolData }, + } as unknown as GrokWebSearchTool +} diff --git a/packages/typescript/ai-grok/src/tools/x-search-tool.ts b/packages/typescript/ai-grok/src/tools/x-search-tool.ts new file mode 100644 index 000000000..2cf726b4b --- /dev/null +++ b/packages/typescript/ai-grok/src/tools/x-search-tool.ts @@ -0,0 +1,35 @@ +import type { ProviderTool, Tool } from '@tanstack/ai' + +export interface XSearchToolConfig { + type: 'x_search' + allowed_x_handles?: Array + excluded_x_handles?: Array + from_date?: string + to_date?: string + enable_image_understanding?: boolean + enable_video_understanding?: boolean +} + +export type XSearchToolOptions = Omit + +/** @deprecated Renamed to `XSearchToolConfig`. Will be removed in a future release. */ +export type XSearchTool = XSearchToolConfig + +export type GrokXSearchTool = ProviderTool<'grok', 'x_search'> + +export function convertXSearchToolToAdapterFormat( + tool: Tool, +): XSearchToolConfig { + const metadata = tool.metadata as XSearchToolConfig + return metadata +} + +export function xSearchTool( + toolData: XSearchToolOptions = {}, +): GrokXSearchTool { + return { + name: 'x_search', + description: 'Search X posts, profiles, and threads', + metadata: { type: 'x_search', ...toolData }, + } as unknown as GrokXSearchTool +} diff --git a/packages/typescript/ai-grok/tests/grok-adapter.test.ts b/packages/typescript/ai-grok/tests/grok-adapter.test.ts index f992cfadb..401d7627e 100644 --- a/packages/typescript/ai-grok/tests/grok-adapter.test.ts +++ b/packages/typescript/ai-grok/tests/grok-adapter.test.ts @@ -1,30 +1,31 @@ import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' import { resolveDebugOption } from '@tanstack/ai/adapter-internals' -import { createGrokText, grokText } from '../src/adapters/text' +import { + GrokTextAdapter, + createGrokText, + grokText, +} from '../src/adapters/text' import { createGrokImage, grokImage } from '../src/adapters/image' import { createGrokSummarize, grokSummarize } from '../src/adapters/summarize' +import { + codeExecutionTool, + fileSearchTool, + mcpTool, + webSearchTool, + xSearchTool, +} from '../src/tools' +import type OpenAI from 'openai' import type { StreamChunk, Tool } from '@tanstack/ai' +import type { GrokTextProviderOptions } from '../src/adapters/text' // Test helper: a silent logger for test chatStream calls. const testLogger = resolveDebugOption(false) -// Declare mockCreate at module level -let mockCreate: ReturnType - -// Mock the OpenAI SDK -vi.mock('openai', () => { - return { - default: class { - chat = { - completions: { - create: (...args: Array) => mockCreate(...args), - }, - } - }, - } -}) +const weatherTool: Tool = { + name: 'lookup_weather', + description: 'Return the forecast for a location', +} -// Helper to create async iterable from chunks function createAsyncIterable(chunks: Array): AsyncIterable { return { [Symbol.asyncIterator]() { @@ -41,57 +42,56 @@ function createAsyncIterable(chunks: Array): AsyncIterable { } } -// Helper to setup the mock SDK client for streaming responses -function setupMockSdkClient( - streamChunks: Array>, - nonStreamResponse?: Record, -) { - mockCreate = vi.fn().mockImplementation((params) => { - if (params.stream) { - return Promise.resolve(createAsyncIterable(streamChunks)) - } - return Promise.resolve(nonStreamResponse) - }) -} - -const weatherTool: Tool = { - name: 'lookup_weather', - description: 'Return the forecast for a location', +/** + * Wires a fresh GrokTextAdapter with its internal OpenAI SDK `responses.create` + * replaced by a vitest mock. Returns the mock so each test can configure + * stream/non-stream behavior and assert on the request payload. + */ +function buildAdapter(model: 'grok-4.3' | 'grok-4.2' = 'grok-4.3') { + const adapter = new GrokTextAdapter({ apiKey: 'test-api-key' }, model) + const responsesCreate = vi.fn() + const testAdapter = adapter as unknown as { + client: Pick + } + testAdapter.client = { + responses: { create: responsesCreate }, + } as unknown as Pick + return { adapter, responsesCreate } } -describe('Grok adapters', () => { +describe('Grok adapters - factory', () => { afterEach(() => { vi.unstubAllEnvs() }) describe('Text adapter', () => { it('creates a text adapter with explicit API key', () => { - const adapter = createGrokText('grok-3', 'test-api-key') + const adapter = createGrokText('grok-4.3', 'test-api-key') expect(adapter).toBeDefined() expect(adapter.kind).toBe('text') expect(adapter.name).toBe('grok') - expect(adapter.model).toBe('grok-3') + expect(adapter.model).toBe('grok-4.3') }) it('creates a text adapter from environment variable', () => { vi.stubEnv('XAI_API_KEY', 'env-api-key') - const adapter = grokText('grok-4-0709') + const adapter = grokText('grok-4.3') expect(adapter).toBeDefined() expect(adapter.kind).toBe('text') - expect(adapter.model).toBe('grok-4-0709') + expect(adapter.model).toBe('grok-4.3') }) it('throws if XAI_API_KEY is not set when using grokText', () => { vi.stubEnv('XAI_API_KEY', '') - expect(() => grokText('grok-3')).toThrow('XAI_API_KEY is required') + expect(() => grokText('grok-4.3')).toThrow('XAI_API_KEY is required') }) it('allows custom baseURL override', () => { - const adapter = createGrokText('grok-3', 'test-api-key', { + const adapter = createGrokText('grok-4.3', 'test-api-key', { baseURL: 'https://custom.api.example.com/v1', }) @@ -101,18 +101,18 @@ describe('Grok adapters', () => { describe('Image adapter', () => { it('creates an image adapter with explicit API key', () => { - const adapter = createGrokImage('grok-2-image-1212', 'test-api-key') + const adapter = createGrokImage('grok-imagine-image', 'test-api-key') expect(adapter).toBeDefined() expect(adapter.kind).toBe('image') expect(adapter.name).toBe('grok') - expect(adapter.model).toBe('grok-2-image-1212') + expect(adapter.model).toBe('grok-imagine-image') }) it('creates an image adapter from environment variable', () => { vi.stubEnv('XAI_API_KEY', 'env-api-key') - const adapter = grokImage('grok-2-image-1212') + const adapter = grokImage('grok-imagine-image') expect(adapter).toBeDefined() expect(adapter.kind).toBe('image') @@ -121,7 +121,7 @@ describe('Grok adapters', () => { it('throws if XAI_API_KEY is not set when using grokImage', () => { vi.stubEnv('XAI_API_KEY', '') - expect(() => grokImage('grok-2-image-1212')).toThrow( + expect(() => grokImage('grok-imagine-image')).toThrow( 'XAI_API_KEY is required', ) }) @@ -129,18 +129,18 @@ describe('Grok adapters', () => { describe('Summarize adapter', () => { it('creates a summarize adapter with explicit API key', () => { - const adapter = createGrokSummarize('grok-3', 'test-api-key') + const adapter = createGrokSummarize('grok-4.3', 'test-api-key') expect(adapter).toBeDefined() expect(adapter.kind).toBe('summarize') expect(adapter.name).toBe('grok') - expect(adapter.model).toBe('grok-3') + expect(adapter.model).toBe('grok-4.3') }) it('creates a summarize adapter from environment variable', () => { vi.stubEnv('XAI_API_KEY', 'env-api-key') - const adapter = grokSummarize('grok-4-0709') + const adapter = grokSummarize('grok-4.3') expect(adapter).toBeDefined() expect(adapter.kind).toBe('summarize') @@ -149,470 +149,500 @@ describe('Grok adapters', () => { it('throws if XAI_API_KEY is not set when using grokSummarize', () => { vi.stubEnv('XAI_API_KEY', '') - expect(() => grokSummarize('grok-3')).toThrow('XAI_API_KEY is required') + expect(() => grokSummarize('grok-4.3')).toThrow('XAI_API_KEY is required') }) }) }) -describe('Grok AG-UI event emission', () => { +describe('Grok text adapter - Responses API request body', () => { beforeEach(() => { vi.clearAllMocks() }) - afterEach(() => { - vi.unstubAllEnvs() - }) - - it('emits RUN_STARTED as the first event', async () => { - const streamChunks = [ - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: { content: 'Hello' }, - finish_reason: null, - }, - ], - }, - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: {}, - finish_reason: 'stop', + it('targets responses.create (not chat.completions.create) with Responses-API params', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.created', + response: { id: 'resp-1', model: 'grok-4.3' }, + }, + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [], + usage: { input_tokens: 5, output_tokens: 1, total_tokens: 6 }, }, - ], - usage: { - prompt_tokens: 5, - completion_tokens: 1, - total_tokens: 6, }, - }, - ] + ]), + ) - setupMockSdkClient(streamChunks) - const adapter = createGrokText('grok-3', 'test-api-key') const chunks: Array = [] - for await (const chunk of adapter.chatStream({ - model: 'grok-3', + model: 'grok-4.3', messages: [{ role: 'user', content: 'Hello' }], + systemPrompts: ['Stay concise'], + maxTokens: 256, + temperature: 0.7, + topP: 0.9, logger: testLogger, })) { chunks.push(chunk) } - expect(chunks[0]?.type).toBe('RUN_STARTED') - if (chunks[0]?.type === 'RUN_STARTED') { - expect(chunks[0].runId).toBeDefined() - expect(chunks[0].model).toBe('grok-3') - } - }) + expect(responsesCreate).toHaveBeenCalledTimes(1) + const [payload] = responsesCreate.mock.calls[0] - it('emits TEXT_MESSAGE_START before TEXT_MESSAGE_CONTENT', async () => { - const streamChunks = [ + expect(payload).toMatchObject({ + model: 'grok-4.3', + temperature: 0.7, + top_p: 0.9, + max_output_tokens: 256, + instructions: 'Stay concise', + stream: true, + }) + + // Messages must be converted to Responses-API ResponseInput shape. + expect(payload.input).toEqual([ { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: { content: 'Hello' }, - finish_reason: null, - }, - ], + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: 'Hello' }], }, - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: {}, - finish_reason: 'stop', + ]) + }) + + it('enables encrypted reasoning round-trip by default (store=false, include=["reasoning.encrypted_content"])', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [], + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, }, - ], - usage: { - prompt_tokens: 5, - completion_tokens: 1, - total_tokens: 6, }, - }, - ] - - setupMockSdkClient(streamChunks) - const adapter = createGrokText('grok-3', 'test-api-key') - const chunks: Array = [] + ]), + ) - for await (const chunk of adapter.chatStream({ - model: 'grok-3', - messages: [{ role: 'user', content: 'Hello' }], + for await (const _ of adapter.chatStream({ + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Hi' }], logger: testLogger, })) { - chunks.push(chunk) + // drain } - const textStartIndex = chunks.findIndex( - (c) => c.type === 'TEXT_MESSAGE_START', - ) - const textContentIndex = chunks.findIndex( - (c) => c.type === 'TEXT_MESSAGE_CONTENT', - ) + const [payload] = responsesCreate.mock.calls[0] + expect(payload.store).toBe(false) + expect(payload.include).toEqual(['reasoning.encrypted_content']) + }) - expect(textStartIndex).toBeGreaterThan(-1) - expect(textContentIndex).toBeGreaterThan(-1) - expect(textStartIndex).toBeLessThan(textContentIndex) + it('honors caller-provided store and include over the encrypted-reasoning defaults', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [], + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, + }, + }, + ]), + ) - const textStart = chunks[textStartIndex] - if (textStart?.type === 'TEXT_MESSAGE_START') { - expect(textStart.messageId).toBeDefined() - expect(textStart.role).toBe('assistant') + for await (const _ of adapter.chatStream({ + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Hi' }], + modelOptions: { store: true, include: [] }, + logger: testLogger, + })) { + // drain } + + const [payload] = responsesCreate.mock.calls[0] + expect(payload.store).toBe(true) + expect(payload.include).toEqual([]) }) - it('emits TEXT_MESSAGE_END and RUN_FINISHED at the end', async () => { - const streamChunks = [ - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: { content: 'Hello' }, - finish_reason: null, - }, - ], - }, - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: {}, - finish_reason: 'stop', + it.each(['grok-4.3', 'grok-4.2'] as const)( + 'fails early when reasoning.effort is used with %s', + async (model) => { + const { adapter, responsesCreate } = buildAdapter(model) + + await expect(async () => { + for await (const _ of adapter.chatStream({ + model, + messages: [{ role: 'user', content: 'Reason please' }], + modelOptions: { + reasoning: { effort: 'low' }, + } as GrokTextProviderOptions, + logger: testLogger, + })) { + // drain + } + }).rejects.toThrow(new RegExp(`${model} does not support .*reasoning\\.effort`)) + + expect(responsesCreate).not.toHaveBeenCalled() + }, + ) + + it('emits Responses-API function tools (flat shape, strict)', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [], + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, }, - ], - usage: { - prompt_tokens: 5, - completion_tokens: 1, - total_tokens: 6, }, - }, - ] - - setupMockSdkClient(streamChunks) - const adapter = createGrokText('grok-3', 'test-api-key') - const chunks: Array = [] + ]), + ) - for await (const chunk of adapter.chatStream({ - model: 'grok-3', - messages: [{ role: 'user', content: 'Hello' }], + for await (const _ of adapter.chatStream({ + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Weather?' }], + tools: [weatherTool], logger: testLogger, })) { - chunks.push(chunk) + // drain } - const textEndChunk = chunks.find((c) => c.type === 'TEXT_MESSAGE_END') - expect(textEndChunk).toBeDefined() - if (textEndChunk?.type === 'TEXT_MESSAGE_END') { - expect(textEndChunk.messageId).toBeDefined() - } + const [payload] = responsesCreate.mock.calls[0] + expect(payload.tools).toBeDefined() + expect(payload.tools[0]).toMatchObject({ + type: 'function', + name: 'lookup_weather', + description: 'Return the forecast for a location', + strict: true, + }) + // Critically: NOT the Chat-Completions nested `function` wrapper. + expect(payload.tools[0].function).toBeUndefined() + }) - const runFinishedChunk = chunks.find((c) => c.type === 'RUN_FINISHED') - expect(runFinishedChunk).toBeDefined() - if (runFinishedChunk?.type === 'RUN_FINISHED') { - expect(runFinishedChunk.runId).toBeDefined() - expect(runFinishedChunk.finishReason).toBe('stop') - expect(runFinishedChunk.usage).toMatchObject({ - promptTokens: 5, - completionTokens: 1, - totalTokens: 6, - }) + it('emits xAI server-side tools in provider-native Responses API shape', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [], + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, + }, + }, + ]), + ) + + for await (const _ of adapter.chatStream({ + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Search and run code' }], + tools: [ + webSearchTool(), + xSearchTool({ + allowed_x_handles: ['xai'], + enable_image_understanding: true, + }), + codeExecutionTool(), + fileSearchTool({ type: 'file_search', vector_store_ids: ['vs_test'] }), + mcpTool({ server_url: 'https://example.com/mcp', server_label: 'example' }), + ], + logger: testLogger, + })) { + // drain } - }) - it('emits AG-UI tool call events', async () => { - const streamChunks = [ + const [payload] = responsesCreate.mock.calls[0] + expect(payload.tools).toEqual([ + { type: 'web_search' }, { - id: 'chatcmpl-456', - model: 'grok-3', - choices: [ - { - delta: { - tool_calls: [ - { - index: 0, - id: 'call_abc123', - type: 'function', - function: { - name: 'lookup_weather', - arguments: '{"location":', - }, - }, - ], - }, - finish_reason: null, - }, - ], + type: 'x_search', + allowed_x_handles: ['xai'], + enable_image_understanding: true, }, + { type: 'code_execution' }, { - id: 'chatcmpl-456', - model: 'grok-3', - choices: [ - { - delta: { - tool_calls: [ - { - index: 0, - function: { - arguments: '"Berlin"}', - }, - }, - ], - }, - finish_reason: null, - }, - ], + type: 'file_search', + vector_store_ids: ['vs_test'], + max_num_results: undefined, + ranking_options: undefined, + filters: undefined, }, { - id: 'chatcmpl-456', - model: 'grok-3', - choices: [ - { - delta: {}, - finish_reason: 'tool_calls', + type: 'mcp', + server_url: 'https://example.com/mcp', + server_label: 'example', + }, + ]) + }) +}) + +describe('Grok text adapter - AG-UI event emission (Responses API stream)', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('emits RUN_STARTED then TEXT_MESSAGE_START/CONTENT/END then RUN_FINISHED', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.created', + response: { id: 'resp-1', model: 'grok-4.3' }, + }, + { + type: 'response.output_text.delta', + delta: 'Hello', + }, + { + type: 'response.output_text.delta', + delta: ' world', + }, + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [], + usage: { input_tokens: 5, output_tokens: 2, total_tokens: 7 }, }, - ], - usage: { - prompt_tokens: 10, - completion_tokens: 5, - total_tokens: 15, }, - }, - ] + ]), + ) - setupMockSdkClient(streamChunks) - const adapter = createGrokText('grok-3', 'test-api-key') const chunks: Array = [] - for await (const chunk of adapter.chatStream({ - model: 'grok-3', - messages: [{ role: 'user', content: 'Weather in Berlin?' }], - tools: [weatherTool], + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Say hi' }], logger: testLogger, })) { chunks.push(chunk) } - // Check AG-UI tool events - const toolStartChunk = chunks.find((c) => c.type === 'TOOL_CALL_START') - expect(toolStartChunk).toBeDefined() - if (toolStartChunk?.type === 'TOOL_CALL_START') { - expect(toolStartChunk.toolCallId).toBe('call_abc123') - expect(toolStartChunk.toolName).toBe('lookup_weather') - } + const types = chunks.map((c) => c.type as string) + + expect(types[0]).toBe('RUN_STARTED') - const toolArgsChunks = chunks.filter((c) => c.type === 'TOOL_CALL_ARGS') - expect(toolArgsChunks.length).toBeGreaterThan(0) + const startIdx = types.indexOf('TEXT_MESSAGE_START') + const firstContentIdx = types.indexOf('TEXT_MESSAGE_CONTENT') + const endIdx = types.indexOf('TEXT_MESSAGE_END') + const finishedIdx = types.indexOf('RUN_FINISHED') - const toolEndChunk = chunks.find((c) => c.type === 'TOOL_CALL_END') - expect(toolEndChunk).toBeDefined() - if (toolEndChunk?.type === 'TOOL_CALL_END') { - expect(toolEndChunk.toolCallId).toBe('call_abc123') - expect(toolEndChunk.toolName).toBe('lookup_weather') - expect(toolEndChunk.input).toEqual({ location: 'Berlin' }) + expect(startIdx).toBeGreaterThan(-1) + expect(firstContentIdx).toBeGreaterThan(startIdx) + expect(endIdx).toBeGreaterThan(firstContentIdx) + expect(finishedIdx).toBeGreaterThan(endIdx) + + const contentChunks = chunks.filter( + (c) => c.type === 'TEXT_MESSAGE_CONTENT', + ) + expect(contentChunks).toHaveLength(2) + if (contentChunks[1]?.type === 'TEXT_MESSAGE_CONTENT') { + expect(contentChunks[1].content).toBe('Hello world') } - // Check finish reason - const runFinishedChunk = chunks.find((c) => c.type === 'RUN_FINISHED') - if (runFinishedChunk?.type === 'RUN_FINISHED') { - expect(runFinishedChunk.finishReason).toBe('tool_calls') + const runFinished = chunks.find((c) => c.type === 'RUN_FINISHED') + if (runFinished?.type === 'RUN_FINISHED') { + expect(runFinished.finishReason).toBe('stop') + expect(runFinished.usage).toMatchObject({ + promptTokens: 5, + completionTokens: 2, + totalTokens: 7, + }) } }) - it('emits RUN_ERROR on stream error', async () => { - const streamChunks = [ - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: { content: 'Hello' }, - finish_reason: null, - }, - ], - }, - ] - - // Create an async iterable that throws mid-stream - const errorIterable = { - [Symbol.asyncIterator]() { - let index = 0 - return { - async next() { - if (index < streamChunks.length) { - return { value: streamChunks[index++]!, done: false } - } - throw new Error('Stream interrupted') + it('emits REASONING_* events when the response stream contains reasoning_text deltas', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.created', + response: { id: 'resp-1', model: 'grok-4.3' }, + }, + { + type: 'response.reasoning_text.delta', + delta: 'Thinking carefully...', + }, + { + type: 'response.output_text.delta', + delta: 'Answer', + }, + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [], + usage: { input_tokens: 4, output_tokens: 2, total_tokens: 6 }, }, - } - }, - } - - mockCreate = vi.fn().mockResolvedValue(errorIterable) + }, + ]), + ) - const adapter = createGrokText('grok-3', 'test-api-key') const chunks: Array = [] - for await (const chunk of adapter.chatStream({ - model: 'grok-3', - messages: [{ role: 'user', content: 'Hello' }], + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Reason' }], logger: testLogger, })) { chunks.push(chunk) } - // Should emit RUN_ERROR - const runErrorChunk = chunks.find((c) => c.type === 'RUN_ERROR') - expect(runErrorChunk).toBeDefined() - if (runErrorChunk?.type === 'RUN_ERROR') { - expect(runErrorChunk.error.message).toBe('Stream interrupted') + const types = chunks.map((c) => c.type as string) + expect(types).toContain('REASONING_START') + expect(types).toContain('REASONING_MESSAGE_START') + expect(types).toContain('REASONING_MESSAGE_CONTENT') + // Reasoning must be closed before text starts. + const reasoningEndIdx = types.indexOf('REASONING_END') + const textStartIdx = types.indexOf('TEXT_MESSAGE_START') + expect(reasoningEndIdx).toBeGreaterThan(-1) + expect(textStartIdx).toBeGreaterThan(reasoningEndIdx) + + const reasoningContent = chunks.find( + (c) => c.type === 'REASONING_MESSAGE_CONTENT', + ) + if (reasoningContent?.type === 'REASONING_MESSAGE_CONTENT') { + expect(reasoningContent.delta).toBe('Thinking carefully...') } }) - it('emits proper AG-UI event sequence', async () => { - const streamChunks = [ - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: { content: 'Hello world' }, - finish_reason: null, + it('emits AG-UI tool call events from Responses-API function_call streaming', async () => { + const { adapter, responsesCreate } = buildAdapter() + responsesCreate.mockResolvedValue( + createAsyncIterable([ + { + type: 'response.created', + response: { id: 'resp-1', model: 'grok-4.3' }, + }, + { + type: 'response.output_item.added', + output_index: 0, + item: { + type: 'function_call', + id: 'call_abc123', + name: 'lookup_weather', }, - ], - }, - { - id: 'chatcmpl-123', - model: 'grok-3', - choices: [ - { - delta: {}, - finish_reason: 'stop', + }, + { + type: 'response.function_call_arguments.delta', + item_id: 'call_abc123', + delta: '{"location":', + }, + { + type: 'response.function_call_arguments.delta', + item_id: 'call_abc123', + delta: '"Berlin"}', + }, + { + type: 'response.function_call_arguments.done', + item_id: 'call_abc123', + arguments: '{"location":"Berlin"}', + }, + { + type: 'response.completed', + response: { + id: 'resp-1', + model: 'grok-4.3', + output: [ + { + type: 'function_call', + id: 'call_abc123', + name: 'lookup_weather', + arguments: '{"location":"Berlin"}', + }, + ], + usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 }, }, - ], - usage: { - prompt_tokens: 5, - completion_tokens: 2, - total_tokens: 7, }, - }, - ] + ]), + ) - setupMockSdkClient(streamChunks) - const adapter = createGrokText('grok-3', 'test-api-key') const chunks: Array = [] - for await (const chunk of adapter.chatStream({ - model: 'grok-3', - messages: [{ role: 'user', content: 'Hello' }], + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Weather in Berlin?' }], + tools: [weatherTool], logger: testLogger, })) { chunks.push(chunk) } - // Verify proper AG-UI event sequence - const eventTypes = chunks.map((c) => c.type) - - // Should start with RUN_STARTED - expect(eventTypes[0]).toBe('RUN_STARTED') - - // Should have TEXT_MESSAGE_START before TEXT_MESSAGE_CONTENT - const textStartIndex = eventTypes.indexOf('TEXT_MESSAGE_START') - const textContentIndex = eventTypes.indexOf('TEXT_MESSAGE_CONTENT') - expect(textStartIndex).toBeGreaterThan(-1) - expect(textContentIndex).toBeGreaterThan(textStartIndex) - - // Should have TEXT_MESSAGE_END before RUN_FINISHED - const textEndIndex = eventTypes.indexOf('TEXT_MESSAGE_END') - const runFinishedIndex = eventTypes.indexOf('RUN_FINISHED') - expect(textEndIndex).toBeGreaterThan(-1) - expect(runFinishedIndex).toBeGreaterThan(textEndIndex) - - // Verify RUN_FINISHED has proper data - const runFinishedChunk = chunks.find((c) => c.type === 'RUN_FINISHED') - if (runFinishedChunk?.type === 'RUN_FINISHED') { - expect(runFinishedChunk.finishReason).toBe('stop') - expect(runFinishedChunk.usage).toBeDefined() + const start = chunks.find((c) => c.type === 'TOOL_CALL_START') + expect(start).toBeDefined() + if (start?.type === 'TOOL_CALL_START') { + expect(start.toolCallId).toBe('call_abc123') + expect(start.toolName).toBe('lookup_weather') + } + + const argsChunks = chunks.filter((c) => c.type === 'TOOL_CALL_ARGS') + expect(argsChunks.length).toBe(2) + + const end = chunks.find((c) => c.type === 'TOOL_CALL_END') + expect(end).toBeDefined() + if (end?.type === 'TOOL_CALL_END') { + expect(end.toolCallId).toBe('call_abc123') + expect(end.toolName).toBe('lookup_weather') + expect(end.input).toEqual({ location: 'Berlin' }) + } + + const runFinished = chunks.find((c) => c.type === 'RUN_FINISHED') + if (runFinished?.type === 'RUN_FINISHED') { + expect(runFinished.finishReason).toBe('tool_calls') } }) - it('streams content with correct accumulated values', async () => { - const streamChunks = [ - { - id: 'chatcmpl-stream', - model: 'grok-3', - choices: [ - { - delta: { content: 'Hello ' }, - finish_reason: null, - }, - ], - }, - { - id: 'chatcmpl-stream', - model: 'grok-3', - choices: [ - { - delta: { content: 'world' }, - finish_reason: null, - }, - ], - }, - { - id: 'chatcmpl-stream', - model: 'grok-3', - choices: [ - { - delta: {}, - finish_reason: 'stop', + it('emits RUN_ERROR when the stream throws mid-flight', async () => { + const { adapter, responsesCreate } = buildAdapter() + const errorIterable = { + [Symbol.asyncIterator]() { + let yielded = false + return { + async next() { + if (!yielded) { + yielded = true + return { + value: { + type: 'response.created', + response: { id: 'resp-1', model: 'grok-4.3' }, + }, + done: false, + } + } + throw new Error('Stream interrupted') }, - ], - usage: { - prompt_tokens: 5, - completion_tokens: 2, - total_tokens: 7, - }, + } }, - ] + } + responsesCreate.mockResolvedValue(errorIterable) - setupMockSdkClient(streamChunks) - const adapter = createGrokText('grok-3', 'test-api-key') const chunks: Array = [] - for await (const chunk of adapter.chatStream({ - model: 'grok-3', - messages: [{ role: 'user', content: 'Say hello' }], + model: 'grok-4.3', + messages: [{ role: 'user', content: 'Hi' }], logger: testLogger, })) { chunks.push(chunk) } - // Check TEXT_MESSAGE_CONTENT events have correct accumulated content - const contentChunks = chunks.filter( - (c) => c.type === 'TEXT_MESSAGE_CONTENT', - ) - expect(contentChunks.length).toBe(2) - - const firstContent = contentChunks[0] - if (firstContent?.type === 'TEXT_MESSAGE_CONTENT') { - expect(firstContent.delta).toBe('Hello ') - expect(firstContent.content).toBe('Hello ') - } - - const secondContent = contentChunks[1] - if (secondContent?.type === 'TEXT_MESSAGE_CONTENT') { - expect(secondContent.delta).toBe('world') - expect(secondContent.content).toBe('Hello world') + const runError = chunks.find((c) => c.type === 'RUN_ERROR') + expect(runError).toBeDefined() + if (runError?.type === 'RUN_ERROR') { + expect(runError.message).toBe('Stream interrupted') } }) }) diff --git a/packages/typescript/ai-grok/tests/tools-per-model-type-safety.test.ts b/packages/typescript/ai-grok/tests/tools-per-model-type-safety.test.ts new file mode 100644 index 000000000..59bb529c3 --- /dev/null +++ b/packages/typescript/ai-grok/tests/tools-per-model-type-safety.test.ts @@ -0,0 +1,71 @@ +import { beforeAll, describe, it } from 'vitest' +import { toolDefinition } from '@tanstack/ai' +import { grokText } from '../src' +import { + codeExecutionTool, + codeInterpreterTool, + collectionsSearchTool, + fileSearchTool, + mcpTool, + webSearchTool, + xSearchTool, +} from '../src/tools' +import type { TextActivityOptions } from '@tanstack/ai/adapters' + +function typedTools>( + adapter: TAdapter, + tools: TextActivityOptions['tools'], +) { + return { adapter, tools } +} + +beforeAll(() => { + process.env['XAI_API_KEY'] = 'xai-test-dummy' +}) + +const userTool = toolDefinition({ + name: 'echo', + description: 'echoes input', + inputSchema: { + type: 'object', + properties: { msg: { type: 'string' } }, + required: ['msg'], + additionalProperties: false, + } as const, +}).server(async (args) => { + const { msg } = args as { msg: string } + return msg +}) + +describe('Grok per-model tool gating', () => { + it('grok-4.3 accepts xAI server-side provider tools', () => { + const adapter = grokText('grok-4.3') + typedTools(adapter, [ + userTool, + webSearchTool(), + xSearchTool({ allowed_x_handles: ['xai'] }), + codeExecutionTool(), + codeInterpreterTool({ type: 'auto' }), + fileSearchTool({ type: 'file_search', vector_store_ids: ['vs_123'] }), + collectionsSearchTool({ vector_store_ids: ['vs_123'] }), + mcpTool({ + server_label: 'my-server', + server_url: 'https://example.com/mcp', + }), + ]) + }) + + it('grok-4-2-non-reasoning accepts server-side provider tools', () => { + const adapter = grokText('grok-4-2-non-reasoning') + typedTools(adapter, [ + webSearchTool(), + xSearchTool({ enable_image_understanding: true }), + codeExecutionTool(), + collectionsSearchTool({ vector_store_ids: ['vs_123'] }), + mcpTool({ + server_label: 'my-server', + server_url: 'https://example.com/mcp', + }), + ]) + }) +}) diff --git a/packages/typescript/ai/skills/ai-core/adapter-configuration/SKILL.md b/packages/typescript/ai/skills/ai-core/adapter-configuration/SKILL.md index 04d7e8742..164f27e67 100644 --- a/packages/typescript/ai/skills/ai-core/adapter-configuration/SKILL.md +++ b/packages/typescript/ai/skills/ai-core/adapter-configuration/SKILL.md @@ -84,7 +84,7 @@ import { ollamaText } from '@tanstack/ai-ollama' const adapter = openaiText('gpt-5.2') const adapter2 = anthropicText('claude-sonnet-4-6') const adapter3 = geminiText('gemini-2.5-pro') -const adapter4 = grokText('grok-4') +const adapter4 = grokText('grok-4.3') const adapter5 = groqText('llama-3.3-70b-versatile') const adapter6 = openRouterText('anthropic/claude-sonnet-4') const adapter7 = ollamaText('llama3.3') diff --git a/packages/typescript/ai/skills/ai-core/adapter-configuration/references/grok-adapter.md b/packages/typescript/ai/skills/ai-core/adapter-configuration/references/grok-adapter.md index 971bcba41..16ea956dd 100644 --- a/packages/typescript/ai/skills/ai-core/adapter-configuration/references/grok-adapter.md +++ b/packages/typescript/ai/skills/ai-core/adapter-configuration/references/grok-adapter.md @@ -23,18 +23,13 @@ import { grokImage } from '@tanstack/ai-grok' ## Key Chat Models -| Model | Context Window | Notes | -| ----------------------------- | -------------- | ---------------------------- | -| `grok-4-1-fast-reasoning` | 2M | Latest, fast reasoning | -| `grok-4-1-fast-non-reasoning` | 2M | Latest, no reasoning | -| `grok-code-fast-1` | 256K | Code-specialized, reasoning | -| `grok-4` | 256K | Full reasoning, tool calling | -| `grok-4-fast-reasoning` | 2M | Fast reasoning variant | -| `grok-3` | 131K | Previous gen, no reasoning | -| `grok-3-mini` | 131K | Budget reasoning | -| `grok-2-vision-1212` | 32K | Vision input | - -Image model: `grok-2-image-1212` +| Model | Context Window | Notes | +| ------------------------ | -------------- | ---------------------------- | +| `grok-4.3` | 2M | Latest reasoning model | +| `grok-4.2` | 2M | Reasoning model | +| `grok-4-2-non-reasoning` | 2M | Non-reasoning model | + +Image model: `grok-imagine-image` ## Provider-Specific modelOptions @@ -42,7 +37,7 @@ Grok uses an OpenAI-compatible API. Options are straightforward: ```typescript chat({ - adapter: grokText('grok-4'), + adapter: grokText('grok-4.3'), messages, modelOptions: { temperature: 0.7, @@ -68,10 +63,8 @@ The adapter uses the OpenAI SDK with xAI's base URL (`https://api.x.ai/v1`). ## Gotchas - Uses the OpenAI SDK under the hood with a custom `baseURL`. -- `grok-4-1-fast-non-reasoning` and `grok-4-fast-non-reasoning` explicitly - do NOT support reasoning. Other grok-4+ models do. -- `grok-2-vision-1212` is the only model with image input support in the - older generation. -- The grok-4-1 fast models have a massive 2M context window. +- `grok-4-2-non-reasoning` explicitly does NOT support reasoning. +- Current chat models support text and image input. +- The Grok 4.2/4.3 models have a 2M context window. - Provider options are simpler than OpenAI's (no Responses API features, no structured outputs config, no metadata). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb41b0817..bef70c463 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -589,7 +589,7 @@ importers: version: 0.561.0(react@19.2.3) nitro: specifier: latest - version: 3.0.260415-beta(chokidar@5.0.0)(dotenv@17.2.3)(giget@2.0.0)(jiti@2.6.1)(rollup@4.60.1)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 3.0.260429-beta(chokidar@5.0.0)(dotenv@17.2.3)(giget@2.0.0)(jiti@2.6.1)(rollup@4.60.1)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) radix-ui: specifier: ^1.4.3 version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -1671,7 +1671,7 @@ importers: dependencies: '@copilotkit/aimock': specifier: latest - version: 1.14.0 + version: 1.16.4(vitest@4.1.4(@types/node@24.10.3)(happy-dom@20.0.11)(jsdom@27.3.0(postcss@8.5.9))(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))) '@tailwindcss/vite': specifier: ^4.1.18 version: 4.1.18(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -1819,7 +1819,7 @@ importers: version: 1.159.5(crossws@0.4.5(srvx@0.11.15))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/start': specifier: ^1.120.20 - version: 1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) + version: 1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) highlight.js: specifier: ^11.11.1 version: 11.11.1 @@ -2002,11 +2002,6 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.0': resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} @@ -2066,10 +2061,6 @@ packages: resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -2139,10 +2130,6 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@cloudflare/kv-asset-handler@0.4.1': - resolution: {integrity: sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==} - engines: {node: '>=18.0.0'} - '@cloudflare/kv-asset-handler@0.4.2': resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} @@ -2189,10 +2176,18 @@ packages: '@cloudflare/workers-types@4.20260317.1': resolution: {integrity: sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ==} - '@copilotkit/aimock@1.14.0': - resolution: {integrity: sha512-1NqwWEameArC7HWT7UHBlkq3pNlCA0eHBocaeL6mS5CULolT9XFL27tC9jJ+OSmREzLwkKbFYaAl2SssaXexVA==} - engines: {node: '>=20.15.0'} + '@copilotkit/aimock@1.16.4': + resolution: {integrity: sha512-DA9WjJWpi2Yh36ltsnfMycj+BbifSS9G0pyHw0JjQZQPm41+FziGIdl2gusBtwYebStypQ4v9Jj2rjqjJqqtvQ==} + engines: {node: '>=24.0.0'} hasBin: true + peerDependencies: + jest: '>=29' + vitest: '>=3' + peerDependenciesMeta: + jest: + optional: true + vitest: + optional: true '@crazydos/vue-markdown@1.1.4': resolution: {integrity: sha512-0I1QMP59LJ3aEjE7bolgvPU4JAFt+pykdDo5674CbsCwFo7OVFos50+MPhGdWflCz1mac5t152lB1qvV/tR/rw==} @@ -3280,9 +3275,6 @@ packages: '@types/node': optional: true - '@ioredis/commands@1.4.0': - resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} - '@ioredis/commands@1.5.0': resolution: {integrity: sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==} @@ -4884,15 +4876,6 @@ packages: '@rolldown/pluginutils@1.0.0-rc.17': resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} - '@rollup/plugin-alias@5.1.1': - resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/plugin-alias@6.0.0': resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==} engines: {node: '>=20.19.0'} @@ -4902,15 +4885,6 @@ packages: rollup: optional: true - '@rollup/plugin-commonjs@28.0.9': - resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==} - engines: {node: '>=16.0.0 || 14 >= 14.17'} - peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/plugin-commonjs@29.0.0': resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} @@ -5876,10 +5850,6 @@ packages: resolution: {integrity: sha512-fR1GGpp6v3dVKu4KIAjEh+Sd0qGLQd/wvCOVHeopSY6aFidXKCzwrS5cBOBqoPPWTKmn6CdW1a0CzFr5Furdog==} engines: {node: '>=12'} - '@tanstack/router-core@1.157.16': - resolution: {integrity: sha512-eJuVgM7KZYTTr4uPorbUzUflmljMVcaX2g6VvhITLnHmg9SBx9RAgtQ1HmT+72mzyIbRSlQ1q0fY/m+of/fosA==} - engines: {node: '>=12'} - '@tanstack/router-core@1.159.4': resolution: {integrity: sha512-MFzPH39ijNO83qJN3pe7x4iAlhZyqgao3sJIzv3SJ4Pnk12xMnzuDzIAQT/1WV6JolPQEcw0Wr4L5agF8yxoeg==} engines: {node: '>=12'} @@ -6531,11 +6501,6 @@ packages: cpu: [x64] os: [win32] - '@vercel/nft@0.30.4': - resolution: {integrity: sha512-wE6eAGSXScra60N2l6jWvNtVK0m+sh873CpfZW4KI2v8EHuUQp+mSEi4T+IcdPCSEDgCdAS/7bizbhQlkjzrSA==} - engines: {node: '>=18'} - hasBin: true - '@vercel/nft@1.3.0': resolution: {integrity: sha512-i4EYGkCsIjzu4vorDUbqglZc5eFtQI2syHb++9ZUDm6TU4edVywGpVnYDein35x9sevONOn9/UabfQXuNXtuzQ==} engines: {node: '>=20'} @@ -7074,14 +7039,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - c12@3.3.2: - resolution: {integrity: sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A==} - peerDependencies: - magicast: '*' - peerDependenciesMeta: - magicast: - optional: true - c12@3.3.3: resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} peerDependencies: @@ -7304,9 +7261,6 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - cookie-es@2.0.0: - resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} - cookie-es@2.0.1: resolution: {integrity: sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==} @@ -8275,10 +8229,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - globby@15.0.0: - resolution: {integrity: sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==} - engines: {node: '>=20'} - globby@16.1.0: resolution: {integrity: sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==} engines: {node: '>=20'} @@ -8548,10 +8498,6 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - ioredis@5.8.2: - resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} - engines: {node: '>=12.22.0'} - ioredis@5.9.2: resolution: {integrity: sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==} engines: {node: '>=12.22.0'} @@ -9486,16 +9432,16 @@ packages: xml2js: optional: true - nitro@3.0.260415-beta: - resolution: {integrity: sha512-J0ntJERWtIdvweZdmkCiF8eOFvP9fIAJR2gpeIDrHbAlYavK41WQfADo/YoZ/LF7RMTZBiPaH/pt2s/nPru9Iw==} + nitro@3.0.260429-beta: + resolution: {integrity: sha512-KweLVCUN5X9v9g+4yxAyRcz3FcOlnjmt9FyrAIWDxJETJmNT7I0JV0clgsONjo2nI0U5gwedXYA3RaNtF5XWzg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@vercel/queue': ^0.1.4 + '@vercel/queue': ^0.1.6 dotenv: '*' giget: '*' jiti: ^2.6.1 - rollup: ^4.60.1 + rollup: ^4.60.2 vite: ^7 || ^8 xml2js: ^0.6.2 zephyr-agent: ^0.2.0 @@ -9517,16 +9463,6 @@ packages: zephyr-agent: optional: true - nitropack@2.12.9: - resolution: {integrity: sha512-t6qqNBn2UDGMWogQuORjbL2UPevB8PvIPsPHmqvWpeGOlPr4P8Oc5oA8t3wFwGmaolM2M/s2SwT23nx9yARmOg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - xml2js: ^0.6.2 - peerDependenciesMeta: - xml2js: - optional: true - nitropack@2.13.1: resolution: {integrity: sha512-2dDj89C4wC2uzG7guF3CnyG+zwkZosPEp7FFBGHB3AJo11AywOolWhyQJFHDzve8COvGxJaqscye9wW2IrUsNw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -9833,10 +9769,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - path-type@6.0.0: - resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} - engines: {node: '>=18'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -10432,12 +10364,6 @@ packages: peerDependencies: seroval: ^1.0 - seroval-plugins@1.4.0: - resolution: {integrity: sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - seroval-plugins@1.5.0: resolution: {integrity: sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==} engines: {node: '>=10'} @@ -10448,10 +10374,6 @@ packages: resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} - seroval@1.4.0: - resolution: {integrity: sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==} - engines: {node: '>=10'} - seroval@1.5.0: resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} engines: {node: '>=10'} @@ -11083,9 +11005,6 @@ packages: uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} @@ -11098,9 +11017,6 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - unctx@2.4.1: - resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==} - unctx@2.5.0: resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==} @@ -11116,10 +11032,6 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} - engines: {node: '>=20.18.1'} - undici@7.21.0: resolution: {integrity: sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==} engines: {node: '>=20.18.1'} @@ -11134,10 +11046,6 @@ packages: unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - unicorn-magic@0.4.0: resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} engines: {node: '>=20'} @@ -11145,10 +11053,6 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unimport@5.5.0: - resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==} - engines: {node: '>=18.12.0'} - unimport@5.6.0: resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==} engines: {node: '>=18.12.0'} @@ -11427,9 +11331,6 @@ packages: resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==} hasBin: true - unwasm@0.3.11: - resolution: {integrity: sha512-Vhp5gb1tusSQw5of/g3Q697srYgMXvwMgXMjcG4ZNga02fDX9coxJ9fAb0Ci38hM2Hv/U1FXRPGgjP2BYqhNoQ==} - unwasm@0.5.3: resolution: {integrity: sha512-keBgTSfp3r6+s9ZcSma+0chwxQdmLbB5+dAD9vjtB21UTMYuKAxHXCU1K2CbCtnP09EaWeRvACnXk0EJtUx+hw==} @@ -12058,10 +11959,10 @@ snapshots: '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.0 '@babel/template': 7.27.2 '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -12166,10 +12067,6 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.29.0 - '@babel/parser@7.28.5': - dependencies: - '@babel/types': 7.29.0 - '@babel/parser@7.29.0': dependencies: '@babel/types': 7.29.0 @@ -12229,26 +12126,21 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.0 '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -12408,10 +12300,6 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@cloudflare/kv-asset-handler@0.4.1': - dependencies: - mime: 3.0.0 - '@cloudflare/kv-asset-handler@0.4.2': {} '@cloudflare/unenv-preset@2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260317.1)': @@ -12437,7 +12325,9 @@ snapshots: '@cloudflare/workers-types@4.20260317.1': {} - '@copilotkit/aimock@1.14.0': {} + '@copilotkit/aimock@1.16.4(vitest@4.1.4(@types/node@24.10.3)(happy-dom@20.0.11)(jsdom@27.3.0(postcss@8.5.9))(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))': + optionalDependencies: + vitest: 4.1.4(@types/node@24.10.3)(happy-dom@20.0.11)(jsdom@27.3.0(postcss@8.5.9))(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@crazydos/vue-markdown@1.1.4(vue@3.5.25(typescript@5.9.3))': dependencies: @@ -13136,8 +13026,6 @@ snapshots: optionalDependencies: '@types/node': 24.10.3 - '@ioredis/commands@1.4.0': {} - '@ioredis/commands@1.5.0': {} '@isaacs/balanced-match@4.0.1': {} @@ -14609,29 +14497,13 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.17': {} - '@rollup/plugin-alias@5.1.1(rollup@4.57.1)': - optionalDependencies: - rollup: 4.57.1 - - '@rollup/plugin-alias@6.0.0(rollup@4.57.1)': - optionalDependencies: - rollup: 4.57.1 - - '@rollup/plugin-commonjs@28.0.9(rollup@4.57.1)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - commondir: 1.0.1 - estree-walker: 2.0.2 - fdir: 6.5.0(picomatch@4.0.4) - is-reference: 1.2.1 - magic-string: 0.30.21 - picomatch: 4.0.4 + '@rollup/plugin-alias@6.0.0(rollup@4.60.1)': optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/plugin-commonjs@29.0.0(rollup@4.57.1)': + '@rollup/plugin-commonjs@29.0.0(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) commondir: 1.0.1 estree-walker: 2.0.2 fdir: 6.5.0(picomatch@4.0.4) @@ -14639,54 +14511,46 @@ snapshots: magic-string: 0.30.21 picomatch: 4.0.4 optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/plugin-inject@5.0.5(rollup@4.57.1)': + '@rollup/plugin-inject@5.0.5(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) estree-walker: 2.0.2 magic-string: 0.30.21 optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/plugin-json@6.1.0(rollup@4.57.1)': + '@rollup/plugin-json@6.1.0(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/plugin-node-resolve@16.0.3(rollup@4.57.1)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.11 optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/plugin-replace@6.0.3(rollup@4.57.1)': + '@rollup/plugin-replace@6.0.3(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) magic-string: 0.30.21 optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/plugin-terser@0.4.4(rollup@4.57.1)': + '@rollup/plugin-terser@0.4.4(rollup@4.60.1)': dependencies: serialize-javascript: 6.0.2 smob: 1.5.0 terser: 5.44.1 optionalDependencies: - rollup: 4.57.1 - - '@rollup/pluginutils@5.3.0(rollup@4.57.1)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.4 - optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 '@rollup/pluginutils@5.3.0(rollup@4.60.1)': dependencies: @@ -15296,14 +15160,14 @@ snapshots: '@tanstack/devtools-event-bus@0.3.3': dependencies: - ws: 8.18.3 + ws: 8.19.0 transitivePeerDependencies: - bufferutil - utf-8-validate '@tanstack/devtools-event-bus@0.4.1': dependencies: - ws: 8.18.3 + ws: 8.19.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -15363,7 +15227,7 @@ snapshots: '@tanstack/devtools-event-bus': 0.4.1 chalk: 5.6.2 launch-editor: 2.12.0 - picomatch: 4.0.3 + picomatch: 4.0.4 vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - bufferutil @@ -15381,7 +15245,7 @@ snapshots: '@tanstack/devtools-event-bus': 0.4.1 chalk: 5.6.2 launch-editor: 2.12.0 - picomatch: 4.0.3 + picomatch: 4.0.4 vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - bufferutil @@ -15427,7 +15291,7 @@ snapshots: '@babel/traverse': 7.28.5 '@babel/types': 7.29.0 '@tanstack/router-utils': 1.131.2 - babel-dead-code-elimination: 1.0.10 + babel-dead-code-elimination: 1.0.12 tiny-invariant: 1.3.3 vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: @@ -15440,7 +15304,7 @@ snapshots: '@babel/traverse': 7.28.5 '@babel/types': 7.29.0 '@tanstack/router-utils': 1.141.0 - babel-dead-code-elimination: 1.0.10 + babel-dead-code-elimination: 1.0.12 pathe: 2.0.3 tiny-invariant: 1.3.3 vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -15684,11 +15548,11 @@ snapshots: - webpack - xml2js - '@tanstack/react-start-router-manifest@1.120.19(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)': + '@tanstack/react-start-router-manifest@1.120.19(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)': dependencies: - '@tanstack/router-core': 1.157.16 + '@tanstack/router-core': 1.159.4 tiny-invariant: 1.3.3 - vinxi: 0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vinxi: 0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -15842,8 +15706,8 @@ snapshots: '@tanstack/history': 1.131.2 '@tanstack/store': 0.7.7 cookie-es: 1.2.2 - seroval: 1.4.0 - seroval-plugins: 1.4.0(seroval@1.4.0) + seroval: 1.5.0 + seroval-plugins: 1.5.0(seroval@1.5.0) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 @@ -15851,17 +15715,7 @@ snapshots: dependencies: '@tanstack/history': 1.141.0 '@tanstack/store': 0.8.0 - cookie-es: 2.0.0 - seroval: 1.4.0 - seroval-plugins: 1.4.0(seroval@1.4.0) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@tanstack/router-core@1.157.16': - dependencies: - '@tanstack/history': 1.154.14 - '@tanstack/store': 0.8.0 - cookie-es: 2.0.0 + cookie-es: 2.0.1 seroval: 1.5.0 seroval-plugins: 1.5.0(seroval@1.5.0) tiny-invariant: 1.3.3 @@ -15871,7 +15725,7 @@ snapshots: dependencies: '@tanstack/history': 1.154.14 '@tanstack/store': 0.8.0 - cookie-es: 2.0.0 + cookie-es: 2.0.1 seroval: 1.5.0 seroval-plugins: 1.5.0(seroval@1.5.0) tiny-invariant: 1.3.3 @@ -15947,7 +15801,7 @@ snapshots: '@tanstack/router-generator': 1.131.50 '@tanstack/router-utils': 1.131.2 '@tanstack/virtual-file-routes': 1.131.2 - babel-dead-code-elimination: 1.0.10 + babel-dead-code-elimination: 1.0.12 chokidar: 3.6.0 unplugin: 2.3.11 zod: 3.25.76 @@ -15970,7 +15824,7 @@ snapshots: '@tanstack/router-generator': 1.141.1 '@tanstack/router-utils': 1.141.0 '@tanstack/virtual-file-routes': 1.141.0 - babel-dead-code-elimination: 1.0.10 + babel-dead-code-elimination: 1.0.12 chokidar: 3.6.0 unplugin: 2.3.11 zod: 3.25.76 @@ -16083,7 +15937,7 @@ snapshots: '@babel/traverse': 7.28.5 '@babel/types': 7.29.0 '@tanstack/directive-functions-plugin': 1.131.2(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - babel-dead-code-elimination: 1.0.10 + babel-dead-code-elimination: 1.0.12 tiny-invariant: 1.3.3 transitivePeerDependencies: - supports-color @@ -16099,7 +15953,7 @@ snapshots: '@babel/traverse': 7.28.5 '@babel/types': 7.29.0 '@tanstack/directive-functions-plugin': 1.141.0(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - babel-dead-code-elimination: 1.0.10 + babel-dead-code-elimination: 1.0.12 tiny-invariant: 1.3.3 transitivePeerDependencies: - supports-color @@ -16201,11 +16055,11 @@ snapshots: '@tanstack/store': 0.8.0 solid-js: 1.9.10 - '@tanstack/start-api-routes@1.120.19(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)': + '@tanstack/start-api-routes@1.120.19(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)': dependencies: - '@tanstack/router-core': 1.157.16 - '@tanstack/start-server-core': 1.141.1(crossws@0.4.5(srvx@0.11.15)) - vinxi: 0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + '@tanstack/router-core': 1.159.4 + '@tanstack/start-server-core': 1.159.4(crossws@0.4.5(srvx@0.11.15)) + vinxi: 0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -16264,7 +16118,7 @@ snapshots: dependencies: '@tanstack/router-core': 1.141.1 '@tanstack/start-storage-context': 1.141.1 - seroval: 1.4.0 + seroval: 1.5.0 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 @@ -16277,21 +16131,21 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/start-config@1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2)': + '@tanstack/start-config@1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2)': dependencies: '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start-plugin': 1.131.50(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@vitejs/plugin-react@4.7.0(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(rolldown@1.0.0-rc.17)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - '@tanstack/router-generator': 1.141.1 + '@tanstack/router-generator': 1.159.4 '@tanstack/router-plugin': 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/server-functions-plugin': 1.141.0(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/start-server-functions-handler': 1.120.19(crossws@0.4.5(srvx@0.11.15)) '@vitejs/plugin-react': 4.7.0(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) import-meta-resolve: 4.2.0 - nitropack: 2.12.9(rolldown@1.0.0-rc.17) + nitropack: 2.13.1(rolldown@1.0.0-rc.17) ofetch: 1.5.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - vinxi: 0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vinxi: 0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) zod: 3.25.76 transitivePeerDependencies: @@ -16358,12 +16212,12 @@ snapshots: '@tanstack/start-server-core': 1.131.50 '@types/babel__code-frame': 7.0.6 '@types/babel__core': 7.20.5 - babel-dead-code-elimination: 1.0.10 + babel-dead-code-elimination: 1.0.12 cheerio: 1.1.2 h3: 1.13.0 - nitropack: 2.12.9(rolldown@1.0.0-rc.17) + nitropack: 2.13.1(rolldown@1.0.0-rc.17) pathe: 2.0.3 - ufo: 1.6.1 + ufo: 1.6.3 vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vitefu: 1.1.1(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) xmlbuilder2: 3.1.1 @@ -16422,7 +16276,7 @@ snapshots: pathe: 2.0.3 srvx: 0.8.16 tinyglobby: 0.2.16 - ufo: 1.6.1 + ufo: 1.6.3 vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vitefu: 1.1.1(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) xmlbuilder2: 4.0.3 @@ -16450,7 +16304,7 @@ snapshots: cheerio: 1.1.2 exsolve: 1.0.8 pathe: 2.0.3 - srvx: 0.11.2 + srvx: 0.11.15 tinyglobby: 0.2.16 ufo: 1.6.3 vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -16480,7 +16334,7 @@ snapshots: cheerio: 1.1.2 exsolve: 1.0.8 pathe: 2.0.3 - srvx: 0.11.2 + srvx: 0.11.15 tinyglobby: 0.2.16 ufo: 1.6.3 vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -16510,7 +16364,7 @@ snapshots: cheerio: 1.1.2 exsolve: 1.0.8 pathe: 2.0.3 - srvx: 0.11.2 + srvx: 0.11.15 tinyglobby: 0.2.16 ufo: 1.6.3 vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -16535,7 +16389,7 @@ snapshots: isbot: 5.1.32 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - unctx: 2.4.1 + unctx: 2.5.0 '@tanstack/start-server-core@1.141.1(crossws@0.4.5(srvx@0.11.15))': dependencies: @@ -16544,7 +16398,7 @@ snapshots: '@tanstack/start-client-core': 1.141.1 '@tanstack/start-storage-context': 1.141.1 h3-v2: h3@2.0.0-beta.5(crossws@0.4.5(srvx@0.11.15)) - seroval: 1.4.0 + seroval: 1.5.0 tiny-invariant: 1.3.3 transitivePeerDependencies: - crossws @@ -16588,9 +16442,9 @@ snapshots: '@tanstack/start-server-functions-handler@1.120.19(crossws@0.4.5(srvx@0.11.15))': dependencies: - '@tanstack/router-core': 1.157.16 - '@tanstack/start-client-core': 1.141.1 - '@tanstack/start-server-core': 1.141.1(crossws@0.4.5(srvx@0.11.15)) + '@tanstack/router-core': 1.159.4 + '@tanstack/start-client-core': 1.159.4 + '@tanstack/start-server-core': 1.159.4(crossws@0.4.5(srvx@0.11.15)) tiny-invariant: 1.3.3 transitivePeerDependencies: - crossws @@ -16606,8 +16460,8 @@ snapshots: '@tanstack/start-server-functions-ssr@1.120.19(crossws@0.4.5(srvx@0.11.15))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@tanstack/server-functions-plugin': 1.141.0(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - '@tanstack/start-client-core': 1.141.1 - '@tanstack/start-server-core': 1.141.1(crossws@0.4.5(srvx@0.11.15)) + '@tanstack/start-client-core': 1.159.4 + '@tanstack/start-server-core': 1.159.4(crossws@0.4.5(srvx@0.11.15)) '@tanstack/start-server-functions-fetcher': 1.131.50 tiny-invariant: 1.3.3 transitivePeerDependencies: @@ -16627,13 +16481,13 @@ snapshots: dependencies: '@tanstack/router-core': 1.159.4 - '@tanstack/start@1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2)': + '@tanstack/start@1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2)': dependencies: '@tanstack/react-start-client': 1.141.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/react-start-router-manifest': 1.120.19(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + '@tanstack/react-start-router-manifest': 1.120.19(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) '@tanstack/react-start-server': 1.141.1(crossws@0.4.5(srvx@0.11.15))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/start-api-routes': 1.120.19(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - '@tanstack/start-config': 1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) + '@tanstack/start-api-routes': 1.120.19(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + '@tanstack/start-config': 1.120.20(@types/node@24.10.3)(crossws@0.4.5(srvx@0.11.15))(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) '@tanstack/start-server-functions-client': 1.131.50(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/start-server-functions-handler': 1.120.19(crossws@0.4.5(srvx@0.11.15)) '@tanstack/start-server-functions-server': 1.131.2(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -16782,8 +16636,8 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 @@ -17078,29 +16932,10 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vercel/nft@0.30.4(rollup@4.57.1)': - dependencies: - '@mapbox/node-pre-gyp': 2.0.3 - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 10.5.0 - graceful-fs: 4.2.11 - node-gyp-build: 4.8.4 - picomatch: 4.0.4 - resolve-from: 5.0.0 - transitivePeerDependencies: - - encoding - - rollup - - supports-color - - '@vercel/nft@1.3.0(rollup@4.57.1)': + '@vercel/nft@1.3.0(rollup@4.60.1)': dependencies: '@mapbox/node-pre-gyp': 2.0.3 - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) async-sema: 3.1.1 @@ -17132,7 +16967,7 @@ snapshots: node-forge: 1.3.3 pathe: 1.1.2 std-env: 3.10.0 - ufo: 1.6.1 + ufo: 1.6.3 untun: 0.1.3 uqr: 0.1.2 @@ -17361,7 +17196,7 @@ snapshots: '@vue/shared': 3.5.25 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.6 + postcss: 8.5.9 source-map-js: 1.2.1 '@vue/compiler-ssr@3.5.25': @@ -17823,23 +17658,6 @@ snapshots: bytes@3.1.2: {} - c12@3.3.2(magicast@0.5.2): - dependencies: - chokidar: 4.0.3 - confbox: 0.2.2 - defu: 6.1.4 - dotenv: 17.2.3 - exsolve: 1.0.8 - giget: 2.0.0 - jiti: 2.6.1 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 2.0.0 - pkg-types: 2.3.0 - rc9: 2.1.2 - optionalDependencies: - magicast: 0.5.2 - c12@3.3.3(magicast@0.5.2): dependencies: chokidar: 5.0.0 @@ -17927,7 +17745,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.16.0 + undici: 7.24.4 whatwg-mimetype: 4.0.0 chokidar@3.6.0: @@ -18060,8 +17878,6 @@ snapshots: cookie-es@1.2.2: {} - cookie-es@2.0.0: {} - cookie-es@2.0.1: {} cookie-signature@1.2.2: {} @@ -18985,7 +18801,7 @@ snapshots: dependencies: magic-string: 0.30.21 mlly: 1.8.0 - rollup: 4.57.1 + rollup: 4.60.1 flat-cache@4.0.1: dependencies: @@ -19185,15 +19001,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@15.0.0: - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - fast-glob: 3.3.3 - ignore: 7.0.5 - path-type: 6.0.0 - slash: 5.1.0 - unicorn-magic: 0.3.0 - globby@16.1.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -19259,7 +19066,7 @@ snapshots: iron-webcrypto: 1.2.1 ohash: 1.1.6 radix3: 1.1.2 - ufo: 1.6.1 + ufo: 1.6.3 uncrypto: 0.1.3 unenv: 1.10.0 @@ -19547,20 +19354,6 @@ snapshots: internmap@2.0.3: {} - ioredis@5.8.2: - dependencies: - '@ioredis/commands': 1.4.0 - cluster-key-slot: 1.1.2 - debug: 4.4.3 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - ioredis@5.9.2: dependencies: '@ioredis/commands': 1.5.0 @@ -20014,7 +19807,7 @@ snapshots: node-forge: 1.3.3 pathe: 1.1.2 std-env: 3.10.0 - ufo: 1.6.1 + ufo: 1.6.3 untun: 0.1.3 uqr: 0.1.2 @@ -20602,7 +20395,7 @@ snapshots: acorn: 8.15.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.1 + ufo: 1.6.3 motion-dom@11.18.1: dependencies: @@ -20689,7 +20482,7 @@ snapshots: - sqlite3 - uploadthing - nitro@3.0.260415-beta(chokidar@5.0.0)(dotenv@17.2.3)(giget@2.0.0)(jiti@2.6.1)(rollup@4.60.1)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): + nitro@3.0.260429-beta(chokidar@5.0.0)(dotenv@17.2.3)(giget@2.0.0)(jiti@2.6.1)(rollup@4.60.1)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: consola: 3.4.2 crossws: 0.4.5(srvx@0.11.15) @@ -20742,119 +20535,17 @@ snapshots: - sqlite3 - uploadthing - nitropack@2.12.9(rolldown@1.0.0-rc.17): - dependencies: - '@cloudflare/kv-asset-handler': 0.4.1 - '@rollup/plugin-alias': 5.1.1(rollup@4.57.1) - '@rollup/plugin-commonjs': 28.0.9(rollup@4.57.1) - '@rollup/plugin-inject': 5.0.5(rollup@4.57.1) - '@rollup/plugin-json': 6.1.0(rollup@4.57.1) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.57.1) - '@rollup/plugin-replace': 6.0.3(rollup@4.57.1) - '@rollup/plugin-terser': 0.4.4(rollup@4.57.1) - '@vercel/nft': 0.30.4(rollup@4.57.1) - archiver: 7.0.1 - c12: 3.3.2(magicast@0.5.2) - chokidar: 4.0.3 - citty: 0.1.6 - compatx: 0.2.0 - confbox: 0.2.2 - consola: 3.4.2 - cookie-es: 2.0.0 - croner: 9.1.0 - crossws: 0.3.5 - db0: 0.3.4 - defu: 6.1.4 - destr: 2.0.5 - dot-prop: 10.1.0 - esbuild: 0.25.12 - escape-string-regexp: 5.0.0 - etag: 1.8.1 - exsolve: 1.0.8 - globby: 15.0.0 - gzip-size: 7.0.0 - h3: 1.15.5 - hookable: 5.5.3 - httpxy: 0.1.7 - ioredis: 5.8.2 - jiti: 2.6.1 - klona: 2.0.6 - knitwork: 1.3.0 - listhen: 1.9.0 - magic-string: 0.30.21 - magicast: 0.5.2 - mime: 4.1.0 - mlly: 1.8.0 - node-fetch-native: 1.6.7 - node-mock-http: 1.0.4 - ofetch: 1.5.1 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 2.0.0 - pkg-types: 2.3.0 - pretty-bytes: 7.1.0 - radix3: 1.1.2 - rollup: 4.57.1 - rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.17)(rollup@4.57.1) - scule: 1.3.0 - semver: 7.7.4 - serve-placeholder: 2.0.2 - serve-static: 2.2.0 - source-map: 0.7.6 - std-env: 3.10.0 - ufo: 1.6.1 - ultrahtml: 1.6.0 - uncrypto: 0.1.3 - unctx: 2.4.1 - unenv: 2.0.0-rc.24 - unimport: 5.5.0 - unplugin-utils: 0.3.1 - unstorage: 1.17.4(db0@0.3.4)(ioredis@5.8.2) - untyped: 2.0.0 - unwasm: 0.3.11 - youch: 4.1.0-beta.13 - youch-core: 0.3.3 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@electric-sql/pglite' - - '@libsql/client' - - '@netlify/blobs' - - '@planetscale/database' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - aws4fetch - - bare-abort-controller - - better-sqlite3 - - drizzle-orm - - encoding - - idb-keyval - - mysql2 - - react-native-b4a - - rolldown - - sqlite3 - - supports-color - - uploadthing - nitropack@2.13.1(rolldown@1.0.0-rc.17): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@rollup/plugin-alias': 6.0.0(rollup@4.57.1) - '@rollup/plugin-commonjs': 29.0.0(rollup@4.57.1) - '@rollup/plugin-inject': 5.0.5(rollup@4.57.1) - '@rollup/plugin-json': 6.1.0(rollup@4.57.1) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.57.1) - '@rollup/plugin-replace': 6.0.3(rollup@4.57.1) - '@rollup/plugin-terser': 0.4.4(rollup@4.57.1) - '@vercel/nft': 1.3.0(rollup@4.57.1) + '@rollup/plugin-alias': 6.0.0(rollup@4.60.1) + '@rollup/plugin-commonjs': 29.0.0(rollup@4.60.1) + '@rollup/plugin-inject': 5.0.5(rollup@4.60.1) + '@rollup/plugin-json': 6.1.0(rollup@4.60.1) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.60.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.60.1) + '@rollup/plugin-terser': 0.4.4(rollup@4.60.1) + '@vercel/nft': 1.3.0(rollup@4.60.1) archiver: 7.0.1 c12: 3.3.3(magicast@0.5.2) chokidar: 5.0.0 @@ -20862,14 +20553,14 @@ snapshots: compatx: 0.2.0 confbox: 0.2.2 consola: 3.4.2 - cookie-es: 2.0.0 + cookie-es: 2.0.1 croner: 9.1.0 crossws: 0.3.5 db0: 0.3.4 defu: 6.1.4 destr: 2.0.5 dot-prop: 10.1.0 - esbuild: 0.27.3 + esbuild: 0.27.7 escape-string-regexp: 5.0.0 etag: 1.8.1 exsolve: 1.0.8 @@ -20896,8 +20587,8 @@ snapshots: pkg-types: 2.3.0 pretty-bytes: 7.1.0 radix3: 1.1.2 - rollup: 4.57.1 - rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.17)(rollup@4.57.1) + rollup: 4.60.1 + rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.17)(rollup@4.60.1) scule: 1.3.0 semver: 7.7.4 serve-placeholder: 2.0.2 @@ -21085,7 +20776,7 @@ snapshots: dependencies: destr: 2.0.5 node-fetch-native: 1.6.7 - ufo: 1.6.1 + ufo: 1.6.3 ofetch@2.0.0-alpha.3: {} @@ -21343,8 +21034,6 @@ snapshots: path-type@4.0.0: {} - path-type@6.0.0: {} - pathe@1.1.2: {} pathe@2.0.3: {} @@ -21970,7 +21659,7 @@ snapshots: magic-string: 0.30.21 rollup: 4.60.1 - rollup-plugin-visualizer@6.0.5(rolldown@1.0.0-rc.17)(rollup@4.57.1): + rollup-plugin-visualizer@6.0.5(rolldown@1.0.0-rc.17)(rollup@4.60.1): dependencies: open: 8.4.2 picomatch: 4.0.4 @@ -21978,7 +21667,7 @@ snapshots: yargs: 17.7.2 optionalDependencies: rolldown: 1.0.0-rc.17 - rollup: 4.57.1 + rollup: 4.60.1 rollup@4.53.3: dependencies: @@ -22172,18 +21861,12 @@ snapshots: dependencies: seroval: 1.3.2 - seroval-plugins@1.4.0(seroval@1.4.0): - dependencies: - seroval: 1.4.0 - seroval-plugins@1.5.0(seroval@1.5.0): dependencies: seroval: 1.5.0 seroval@1.3.2: {} - seroval@1.4.0: {} - seroval@1.5.0: {} serve-placeholder@2.0.2: @@ -22876,8 +22559,6 @@ snapshots: uc.micro@2.1.0: {} - ufo@1.6.1: {} - ufo@1.6.3: {} ultrahtml@1.6.0: {} @@ -22889,13 +22570,6 @@ snapshots: uncrypto@0.1.3: {} - unctx@2.4.1: - dependencies: - acorn: 8.15.0 - estree-walker: 3.0.3 - magic-string: 0.30.21 - unplugin: 2.3.11 - unctx@2.5.0: dependencies: acorn: 8.15.0 @@ -22911,8 +22585,6 @@ snapshots: undici-types@7.16.0: {} - undici@7.16.0: {} - undici@7.21.0: {} undici@7.24.4: {} @@ -22929,8 +22601,6 @@ snapshots: dependencies: pathe: 2.0.3 - unicorn-magic@0.3.0: {} - unicorn-magic@0.4.0: {} unified@11.0.5: @@ -22943,23 +22613,6 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unimport@5.5.0: - dependencies: - acorn: 8.15.0 - escape-string-regexp: 5.0.0 - estree-walker: 3.0.3 - local-pkg: 1.1.2 - magic-string: 0.30.21 - mlly: 1.8.0 - pathe: 2.0.3 - picomatch: 4.0.4 - pkg-types: 2.3.0 - scule: 1.3.0 - strip-literal: 3.1.0 - tinyglobby: 0.2.16 - unplugin: 2.3.11 - unplugin-utils: 0.3.1 - unimport@5.6.0: dependencies: acorn: 8.15.0 @@ -23064,20 +22717,6 @@ snapshots: dependencies: rolldown: 1.0.0-beta.53 - unstorage@1.17.4(db0@0.3.4)(ioredis@5.8.2): - dependencies: - anymatch: 3.1.3 - chokidar: 5.0.0 - destr: 2.0.5 - h3: 1.15.5 - lru-cache: 11.2.4 - node-fetch-native: 1.6.7 - ofetch: 1.5.1 - ufo: 1.6.3 - optionalDependencies: - db0: 0.3.4 - ioredis: 5.8.2 - unstorage@1.17.4(db0@0.3.4)(ioredis@5.9.2): dependencies: anymatch: 3.1.3 @@ -23120,15 +22759,6 @@ snapshots: knitwork: 1.3.0 scule: 1.3.0 - unwasm@0.3.11: - dependencies: - knitwork: 1.3.0 - magic-string: 0.30.21 - mlly: 1.8.0 - pathe: 2.0.3 - pkg-types: 2.3.0 - unplugin: 2.3.11 - unwasm@0.5.3: dependencies: exsolve: 1.0.8 @@ -23205,7 +22835,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vinxi@0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vinxi@0.5.3(@types/node@24.10.3)(db0@0.3.4)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-rc.17)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@babel/core': 7.28.5 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) @@ -23227,7 +22857,7 @@ snapshots: hookable: 5.5.3 http-proxy: 1.18.1 micromatch: 4.0.8 - nitropack: 2.12.9(rolldown@1.0.0-rc.17) + nitropack: 2.13.1(rolldown@1.0.0-rc.17) node-fetch-native: 1.6.7 path-to-regexp: 6.3.0 pathe: 1.1.2 @@ -23235,10 +22865,10 @@ snapshots: resolve: 1.22.11 serve-placeholder: 2.0.2 serve-static: 1.16.2 - ufo: 1.6.1 - unctx: 2.4.1 + ufo: 1.6.3 + unctx: 2.5.0 unenv: 1.10.0 - unstorage: 1.17.4(db0@0.3.4)(ioredis@5.8.2) + unstorage: 1.17.4(db0@0.3.4)(ioredis@5.9.2) vite: 6.4.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) zod: 3.25.76 transitivePeerDependencies: @@ -23376,8 +23006,8 @@ snapshots: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.6 - rollup: 4.57.1 + postcss: 8.5.9 + rollup: 4.60.1 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 24.10.3 @@ -23424,12 +23054,12 @@ snapshots: vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.1 - tinyglobby: 0.2.15 + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.9 + rollup: 4.60.1 + tinyglobby: 0.2.16 optionalDependencies: '@types/node': 24.10.3 fsevents: 2.3.3 @@ -23441,12 +23071,12 @@ snapshots: vite@7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.1 - tinyglobby: 0.2.15 + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.9 + rollup: 4.60.1 + tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.0.1 fsevents: 2.3.3 @@ -23807,7 +23437,7 @@ snapshots: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 '@speed-highlight/core': 1.2.12 - cookie-es: 2.0.0 + cookie-es: 2.0.1 youch-core: 0.3.3 zimmerframe@1.1.4: {} diff --git a/scripts/sync-provider-models.ts b/scripts/sync-provider-models.ts index 5f0199cc7..f515a42bc 100644 --- a/scripts/sync-provider-models.ts +++ b/scripts/sync-provider-models.ts @@ -189,7 +189,7 @@ function stripPrefix(prefix: string, modelId: string): string { /** * Convert a model ID (after prefix stripping) to a TypeScript constant name. - * E.g. 'gpt-6' -> 'GPT_6', 'grok-4.20-multi-agent' -> 'GROK_4_20_MULTI_AGENT' + * E.g. 'gpt-6' -> 'GPT_6', 'grok-4.3' -> 'GROK_4_3' */ function toConstName(prefix: string, modelId: string): string { const stripped = stripPrefix(prefix, modelId) diff --git a/testing/e2e/src/lib/media-providers.ts b/testing/e2e/src/lib/media-providers.ts index 6887660b2..f90f6bd4d 100644 --- a/testing/e2e/src/lib/media-providers.ts +++ b/testing/e2e/src/lib/media-providers.ts @@ -45,7 +45,7 @@ export function createImageAdapter( httpOptions: { baseUrl: llmockBase(aimockPort), headers }, }), grok: () => - createGrokImage('grok-2-image-1212', DUMMY_KEY, { + createGrokImage('grok-imagine-image', DUMMY_KEY, { baseURL: openaiUrl(aimockPort), defaultHeaders: headers, }), diff --git a/testing/e2e/src/lib/providers.ts b/testing/e2e/src/lib/providers.ts index 35b720b61..e64359313 100644 --- a/testing/e2e/src/lib/providers.ts +++ b/testing/e2e/src/lib/providers.ts @@ -18,7 +18,7 @@ const defaultModels: Record = { gemini: 'gemini-2.0-flash', ollama: 'mistral', groq: 'llama-3.3-70b-versatile', - grok: 'grok-3', + grok: 'grok-4.3', openrouter: 'openai/gpt-4o', } @@ -78,7 +78,7 @@ export function createTextAdapter( }), grok: () => createChatOptions({ - adapter: createGrokText(model as 'grok-3', DUMMY_KEY, { + adapter: createGrokText(model as 'grok-4.3', DUMMY_KEY, { baseURL: openaiUrl, defaultHeaders: testHeaders, }), diff --git a/testing/e2e/src/routes/api.summarize.ts b/testing/e2e/src/routes/api.summarize.ts index e5912edf9..1c0d36afc 100644 --- a/testing/e2e/src/routes/api.summarize.ts +++ b/testing/e2e/src/routes/api.summarize.ts @@ -25,7 +25,7 @@ function createSummarizeAdapter(provider: Provider) { }), ollama: () => createOllamaSummarize('mistral', LLMOCK_BASE), grok: () => - createGrokSummarize('grok-3', DUMMY_KEY, { baseURL: LLMOCK_OPENAI }), + createGrokSummarize('grok-4.3', DUMMY_KEY, { baseURL: LLMOCK_OPENAI }), openrouter: () => createOpenaiSummarize('gpt-4o', DUMMY_KEY, { baseURL: LLMOCK_OPENAI }), } diff --git a/testing/panel/src/lib/model-selection.ts b/testing/panel/src/lib/model-selection.ts index 470c6a52d..1680dd879 100644 --- a/testing/panel/src/lib/model-selection.ts +++ b/testing/panel/src/lib/model-selection.ts @@ -82,23 +82,18 @@ export const MODEL_OPTIONS: Array = [ // Grok { provider: 'grok', - model: 'grok-4', - label: 'Grok - Grok 4 - slow thinking', + model: 'grok-4.3', + label: 'Grok - Grok 4.3', }, { provider: 'grok', - model: 'grok-4-fast-non-reasoning', - label: 'Grok - Grok 4 Fast', + model: 'grok-4.2', + label: 'Grok - Grok 4.2', }, { provider: 'grok', - model: 'grok-3', - label: 'Grok - Grok 3', - }, - { - provider: 'grok', - model: 'grok-3-mini', - label: 'Grok - Grok 3 Mini', + model: 'grok-4-2-non-reasoning', + label: 'Grok - Grok 4.2 Non-Reasoning', }, // OpenRouter diff --git a/testing/panel/src/routes/api.chat.ts b/testing/panel/src/routes/api.chat.ts index 11ee577cb..ea084dc1c 100644 --- a/testing/panel/src/routes/api.chat.ts +++ b/testing/panel/src/routes/api.chat.ts @@ -182,7 +182,7 @@ export const Route = createFileRoute('/api/chat')({ }), grok: () => createChatOptions({ - adapter: grokText((model || 'grok-3') as any), + adapter: grokText((model || 'grok-4.3') as any), }), ollama: () => createChatOptions({ diff --git a/testing/panel/src/routes/api.structured.ts b/testing/panel/src/routes/api.structured.ts index 3b692aa17..de2582a37 100644 --- a/testing/panel/src/routes/api.structured.ts +++ b/testing/panel/src/routes/api.structured.ts @@ -66,7 +66,7 @@ export const Route = createFileRoute('/api/structured')({ const defaultModels: Record = { anthropic: 'claude-sonnet-4-5', gemini: 'gemini-2.0-flash', - grok: 'grok-3-mini', + grok: 'grok-4.3', ollama: 'mistral:7b', openai: 'gpt-4o', openrouter: 'openai/gpt-4o', diff --git a/testing/panel/src/routes/api.summarize.ts b/testing/panel/src/routes/api.summarize.ts index ee3ce70f0..2c9c3ac01 100644 --- a/testing/panel/src/routes/api.summarize.ts +++ b/testing/panel/src/routes/api.summarize.ts @@ -1,5 +1,5 @@ import { createFileRoute } from '@tanstack/react-router' -import { summarize, createSummarizeOptions } from '@tanstack/ai' +import { summarize } from '@tanstack/ai' import { anthropicSummarize } from '@tanstack/ai-anthropic' import { geminiSummarize } from '@tanstack/ai-gemini' import { grokSummarize } from '@tanstack/ai-grok' @@ -36,7 +36,7 @@ export const Route = createFileRoute('/api/summarize')({ const defaultModels: Record = { anthropic: 'claude-sonnet-4-5', gemini: 'gemini-2.0-flash', - grok: 'grok-3-mini', + grok: 'grok-4.3', ollama: 'mistral:7b', openai: 'gpt-4o-mini', openrouter: 'openai/gpt-4o-mini', @@ -45,37 +45,16 @@ export const Route = createFileRoute('/api/summarize')({ // Determine the actual model being used const actualModel = model || defaultModels[provider] - // Pre-define typed adapter configurations with full type inference - // Model is passed to the adapter factory function for type-safe autocomplete const adapterConfig = { - anthropic: () => - createSummarizeOptions({ - adapter: anthropicSummarize(actualModel as any), - }), - gemini: () => - createSummarizeOptions({ - adapter: geminiSummarize(actualModel as any), - }), - grok: () => - createSummarizeOptions({ - adapter: grokSummarize(actualModel as any), - }), - ollama: () => - createSummarizeOptions({ - adapter: ollamaSummarize(actualModel), - }), - openai: () => - createSummarizeOptions({ - adapter: openaiSummarize(actualModel as any), - }), - openrouter: () => - createSummarizeOptions({ - adapter: openRouterSummarize(actualModel as any), - }), + anthropic: () => anthropicSummarize(actualModel as any), + gemini: () => geminiSummarize(actualModel as any), + grok: () => grokSummarize(actualModel as any), + ollama: () => ollamaSummarize(actualModel), + openai: () => openaiSummarize(actualModel as any), + openrouter: () => openRouterSummarize(actualModel as any), } - // Get typed adapter options using createSummarizeOptions pattern - const options = adapterConfig[provider]() + const adapter = adapterConfig[provider]() console.log( `>> summarize with model: ${actualModel} on provider: ${provider} (stream: ${stream})`, @@ -88,7 +67,7 @@ export const Route = createFileRoute('/api/summarize')({ async start(controller) { try { const streamResult = summarize({ - ...options, + adapter, text, maxLength, style, @@ -131,7 +110,7 @@ export const Route = createFileRoute('/api/summarize')({ // Non-streaming mode const result = await summarize({ - ...options, + adapter, text, maxLength, style, diff --git a/testing/panel/src/routes/structured.tsx b/testing/panel/src/routes/structured.tsx index b79ed20ec..d257b1ca0 100644 --- a/testing/panel/src/routes/structured.tsx +++ b/testing/panel/src/routes/structured.tsx @@ -24,7 +24,7 @@ const PROVIDERS: Array = [ { id: 'openai', name: 'OpenAI (GPT-4o)' }, { id: 'anthropic', name: 'Anthropic (Claude Sonnet)' }, { id: 'gemini', name: 'Gemini (2.0 Flash)' }, - { id: 'grok', name: 'Grok (Grok 3 Mini)' }, + { id: 'grok', name: 'Grok (Grok 4.3)' }, { id: 'ollama', name: 'Ollama (Mistral 7B)' }, { id: 'openrouter', name: 'OpenRouter (GPT-4o)' }, ] diff --git a/testing/panel/src/routes/summarize.tsx b/testing/panel/src/routes/summarize.tsx index 790731079..cf2b8bcb7 100644 --- a/testing/panel/src/routes/summarize.tsx +++ b/testing/panel/src/routes/summarize.tsx @@ -19,7 +19,7 @@ const PROVIDERS: Array = [ { id: 'openai', name: 'OpenAI (GPT-4o Mini)' }, { id: 'anthropic', name: 'Anthropic (Claude Sonnet)' }, { id: 'gemini', name: 'Gemini (2.0 Flash)' }, - { id: 'grok', name: 'Grok (Grok 3 Mini)' }, + { id: 'grok', name: 'Grok (Grok 4.3)' }, { id: 'ollama', name: 'Ollama (Mistral 7B)' }, { id: 'openrouter', name: 'OpenRouter (GPT-4o Mini)' }, ] diff --git a/testing/panel/tests/vendor-config.ts b/testing/panel/tests/vendor-config.ts index 5373d6fba..0a46f4766 100644 --- a/testing/panel/tests/vendor-config.ts +++ b/testing/panel/tests/vendor-config.ts @@ -79,7 +79,7 @@ export const PROVIDERS: ProviderConfig[] = [ id: 'grok', name: 'Grok', envKey: 'XAI_API_KEY', - defaultModel: 'grok-3-mini', + defaultModel: 'grok-4.3', supportsBasicInference: true, supportsTools: true, supportsSummarization: true,