@tanstack/ai 0.52.3 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +14 -13
  2. package/dist/esm/activities/chat/index.js +5 -3
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/generateLiveVideo/adapter.d.ts +69 -0
  5. package/dist/esm/activities/generateLiveVideo/adapter.js +23 -0
  6. package/dist/esm/activities/generateLiveVideo/adapter.js.map +1 -0
  7. package/dist/esm/activities/generateLiveVideo/index.d.ts +99 -0
  8. package/dist/esm/activities/generateLiveVideo/index.js +162 -0
  9. package/dist/esm/activities/generateLiveVideo/index.js.map +1 -0
  10. package/dist/esm/activities/generateVideo/index.js +3 -1
  11. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  12. package/dist/esm/activities/generateWorld/adapter.d.ts +69 -0
  13. package/dist/esm/activities/generateWorld/adapter.js +23 -0
  14. package/dist/esm/activities/generateWorld/adapter.js.map +1 -0
  15. package/dist/esm/activities/generateWorld/index.d.ts +99 -0
  16. package/dist/esm/activities/generateWorld/index.js +162 -0
  17. package/dist/esm/activities/generateWorld/index.js.map +1 -0
  18. package/dist/esm/activities/index.d.ts +8 -2
  19. package/dist/esm/activities/index.js +11 -7
  20. package/dist/esm/activities/middleware/types.d.ts +1 -1
  21. package/dist/esm/activities/summarize/chat-stream-summarize.js +2 -1
  22. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  23. package/dist/esm/byok/define-provider.d.ts +6 -0
  24. package/dist/esm/byok/define-provider.js +2 -1
  25. package/dist/esm/byok/define-provider.js.map +1 -1
  26. package/dist/esm/byok/get-key.d.ts +7 -0
  27. package/dist/esm/byok/get-key.js +8 -1
  28. package/dist/esm/byok/get-key.js.map +1 -1
  29. package/dist/esm/byok/server.d.ts +1 -1
  30. package/dist/esm/byok/server.js +2 -2
  31. package/dist/esm/client.d.ts +4 -2
  32. package/dist/esm/client.js +3 -1
  33. package/dist/esm/client.js.map +1 -1
  34. package/dist/esm/index.d.ts +4 -2
  35. package/dist/esm/index.js +3 -1
  36. package/dist/esm/middlewares/otel.js +3 -1
  37. package/dist/esm/middlewares/otel.js.map +1 -1
  38. package/dist/esm/types.d.ts +112 -0
  39. package/package.json +2 -2
  40. package/skills/ai-core/adapter-configuration/SKILL.md +103 -54
  41. package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +39 -21
  42. package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +5 -0
  43. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +14 -6
  44. package/skills/ai-core/adapter-configuration/references/grok-adapter.md +33 -25
  45. package/skills/ai-core/adapter-configuration/references/groq-adapter.md +7 -2
  46. package/skills/ai-core/adapter-configuration/references/ollama-adapter.md +25 -12
  47. package/skills/ai-core/adapter-configuration/references/openai-adapter.md +19 -9
  48. package/skills/ai-core/adapter-configuration/references/openrouter-adapter.md +34 -21
  49. package/skills/ai-core/ag-ui-protocol/SKILL.md +16 -10
  50. package/skills/ai-core/chat-experience/SKILL.md +228 -108
  51. package/skills/ai-core/client-persistence/SKILL.md +21 -9
  52. package/skills/ai-core/custom-backend-integration/SKILL.md +86 -52
  53. package/skills/ai-core/debug-logging/SKILL.md +100 -18
  54. package/skills/ai-core/locks/SKILL.md +35 -7
  55. package/skills/ai-core/media-generation/SKILL.md +114 -49
  56. package/skills/ai-core/middleware/SKILL.md +174 -69
  57. package/skills/ai-core/structured-outputs/SKILL.md +99 -49
  58. package/skills/ai-core/tool-calling/SKILL.md +245 -158
  59. package/src/activities/chat/index.ts +6 -7
  60. package/src/activities/generateLiveVideo/adapter.ts +99 -0
  61. package/src/activities/generateLiveVideo/index.ts +339 -0
  62. package/src/activities/generateVideo/index.ts +3 -4
  63. package/src/activities/generateWorld/adapter.ts +96 -0
  64. package/src/activities/generateWorld/index.ts +339 -0
  65. package/src/activities/index.ts +44 -0
  66. package/src/activities/middleware/types.ts +2 -0
  67. package/src/activities/summarize/chat-stream-summarize.ts +2 -0
  68. package/src/byok/define-provider.ts +7 -0
  69. package/src/byok/get-key.ts +18 -0
  70. package/src/byok/server.ts +1 -1
  71. package/src/client.ts +8 -0
  72. package/src/index.ts +8 -0
  73. package/src/middlewares/otel.ts +2 -0
  74. package/src/types.ts +128 -0
@@ -1865,6 +1865,118 @@ export interface VideoUrlResult {
1865
1865
  /** Persisted artifact references for generated assets, when available */
1866
1866
  artifacts?: Array<PersistedArtifactRef>;
1867
1867
  }
1868
+ /**
1869
+ * Options for world generation (live, prompt-steerable sessions).
1870
+ *
1871
+ * @experimental World generation is an experimental feature and may change.
1872
+ */
1873
+ export interface WorldGenerationOptions<TProviderOptions extends object = object> {
1874
+ /** The model to use for world generation */
1875
+ model: string;
1876
+ /** Natural-language description of the world or scene */
1877
+ prompt: string;
1878
+ /**
1879
+ * Provider mint options. Reactor resolution/seed/audio are browser
1880
+ * `sendCommand` fields, not token-mint fields.
1881
+ */
1882
+ modelOptions?: TProviderOptions;
1883
+ /**
1884
+ * Internal logger threaded from the generateWorld() entry point. Adapters
1885
+ * must call logger.request() before the SDK call and logger.errors() in
1886
+ * catch blocks.
1887
+ */
1888
+ logger: InternalLogger;
1889
+ /**
1890
+ * Effective abort signal composed by the activity from caller `abortSignal`
1891
+ * and/or `timeout`. Adapters should forward this to the provider SDK when
1892
+ * supported. Request-specific — never store on a global client config.
1893
+ */
1894
+ abortSignal?: AbortSignal;
1895
+ }
1896
+ /**
1897
+ * Result of world generation. JSON-serializable so a server route can return
1898
+ * it to a browser. The browser uses `token` + `model` to open the live
1899
+ * session (set the prompt, start streaming, steer mid-run).
1900
+ *
1901
+ * @experimental World generation is an experimental feature and may change.
1902
+ */
1903
+ export interface WorldGenerationResult {
1904
+ /** Unique identifier for this generation */
1905
+ id: string;
1906
+ /** Model used for generation (provider connect slug) */
1907
+ model: string;
1908
+ /** Short-lived session token for the client connection */
1909
+ token: string;
1910
+ /** Token expiry as milliseconds since epoch */
1911
+ expiresAt: number;
1912
+ /** Prompt the client should send when it starts the session */
1913
+ prompt: string;
1914
+ /** Session status after the server half finishes */
1915
+ status: 'ready' | 'waiting';
1916
+ /** Provider session id, when the adapter created one */
1917
+ sessionId?: string;
1918
+ /** Token usage / billing, when the adapter can report it */
1919
+ usage?: TokenUsage;
1920
+ }
1921
+ /**
1922
+ * Options for live generation (prompt-steerable video sessions).
1923
+ *
1924
+ * @experimental Live generation is an experimental feature and may change.
1925
+ */
1926
+ export interface LiveVideoGenerationOptions<TProviderOptions extends object = object> {
1927
+ /** The model to use for live generation */
1928
+ model: string;
1929
+ /** Natural-language description of the shot or scene */
1930
+ prompt: string;
1931
+ /**
1932
+ * Provider mint options. For fal live this is `tokenDuration`. Reactor
1933
+ * resolution/seed/audio are browser `sendCommand` fields.
1934
+ */
1935
+ modelOptions?: TProviderOptions;
1936
+ /**
1937
+ * Internal logger threaded from the generateLiveVideo() entry point. Adapters
1938
+ * must call logger.request() before the SDK call and logger.errors() in
1939
+ * catch blocks.
1940
+ */
1941
+ logger: InternalLogger;
1942
+ /**
1943
+ * Effective abort signal composed by the activity from caller `abortSignal`
1944
+ * and/or `timeout`. Adapters should forward this to the provider SDK when
1945
+ * supported. Request-specific — never store on a global client config.
1946
+ */
1947
+ abortSignal?: AbortSignal;
1948
+ }
1949
+ /**
1950
+ * Result of live generation. JSON-serializable so a server route can return
1951
+ * it to a browser.
1952
+ *
1953
+ * Reactor: connect with `token` and `model` (the connect slug).
1954
+ * fal: `model` is the WMA app id. Open `wma(model)` through a server proxy
1955
+ * that attaches `FAL_KEY`. Do not send `token` as Key credentials.
1956
+ *
1957
+ * @experimental Live generation is an experimental feature and may change.
1958
+ */
1959
+ export interface LiveVideoGenerationResult {
1960
+ /** Unique identifier for this generation */
1961
+ id: string;
1962
+ /**
1963
+ * Connect id for the browser client. Reactor: `reactor/helios`.
1964
+ * fal: WMA app id `fal-ai/minimax-h3-max-director`.
1965
+ */
1966
+ model: string;
1967
+ /** Short-lived session token. Reactor uses this to connect. fal does not. */
1968
+ token: string;
1969
+ /** Token expiry as milliseconds since epoch */
1970
+ expiresAt: number;
1971
+ /** Prompt the client should send when it starts the session */
1972
+ prompt: string;
1973
+ /** Session status after the server half finishes */
1974
+ status: 'ready' | 'waiting';
1975
+ /** Provider session id, when the adapter created one */
1976
+ sessionId?: string;
1977
+ /** Token usage / billing, when the adapter can report it */
1978
+ usage?: TokenUsage;
1979
+ }
1868
1980
  /**
1869
1981
  * Options for text-to-speech generation.
1870
1982
  * These are the common options supported across providers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.52.3",
3
+ "version": "0.54.0",
4
4
  "description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -88,7 +88,7 @@
88
88
  "@ag-ui/core": "0.1.1-canary.beta.0",
89
89
  "@standard-schema/spec": "^1.1.0",
90
90
  "partial-json": "^0.1.7",
91
- "@tanstack/ai-event-client": "^0.11.2",
91
+ "@tanstack/ai-event-client": "^0.11.3",
92
92
  "@tanstack/ai-utils": "^0.4.0"
93
93
  },
94
94
  "peerDependencies": {
@@ -45,16 +45,20 @@ Create an adapter and use it with `chat()`:
45
45
  import { chat, toServerSentEventsResponse } from '@tanstack/ai'
46
46
  import { openaiText } from '@tanstack/ai-openai'
47
47
 
48
- const stream = chat({
49
- adapter: openaiText('gpt-5.2'),
50
- messages,
51
- modelOptions: {
52
- temperature: 0.7,
53
- max_output_tokens: 1000,
54
- },
55
- })
48
+ export async function POST(request: Request) {
49
+ const { messages } = await request.json()
50
+
51
+ const stream = chat({
52
+ adapter: openaiText('gpt-5.2'),
53
+ messages,
54
+ modelOptions: {
55
+ temperature: 0.7,
56
+ max_output_tokens: 1000,
57
+ },
58
+ })
56
59
 
57
- return toServerSentEventsResponse(stream)
60
+ return toServerSentEventsResponse(stream)
61
+ }
58
62
  ```
59
63
 
60
64
  The adapter factory function takes the model name as a string literal and an
@@ -73,18 +77,19 @@ top-level options on `chat()`. See the per-provider table in
73
77
  Each provider has a dedicated package with tree-shakeable adapter factories.
74
78
  The text adapter is the primary one for chat/completions:
75
79
 
76
- | Provider | Package | Factory | Env Var |
77
- | ----------------- | -------------------------------- | ------------------------------------------- | ------------------------------------------------- |
78
- | OpenAI | `@tanstack/ai-openai` | `openaiText` | `OPENAI_API_KEY` |
79
- | Anthropic | `@tanstack/ai-anthropic` | `anthropicText` | `ANTHROPIC_API_KEY` |
80
- | Gemini | `@tanstack/ai-gemini` | `geminiText` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |
81
- | Grok (xAI) | `@tanstack/ai-grok` | `grokText` | `XAI_API_KEY` |
82
- | Groq | `@tanstack/ai-groq` | `groqText` | `GROQ_API_KEY` |
83
- | OpenRouter | `@tanstack/ai-openrouter` | `openRouterText` | `OPENROUTER_API_KEY` |
84
- | Ollama | `@tanstack/ai-ollama` | `ollamaText` | `OLLAMA_HOST` (default: `http://localhost:11434`) |
85
- | Bedrock | `@tanstack/ai-bedrock` | `bedrockText` | `BEDROCK_API_KEY` or `AWS_BEARER_TOKEN_BEDROCK` |
86
- | BytePlus | `@tanstack/ai-byteplus` | `byteplusText` | `ARK_API_KEY` (falls back to `BYTEPLUS_API_KEY`) |
87
- | OpenAI-compatible | `@tanstack/ai-openai/compatible` | `openaiCompatible` / `openaiCompatibleText` | provider-specific (passed via `apiKey`) |
80
+ | Provider | Package | Factory | Env Var |
81
+ | ----------------- | -------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------- |
82
+ | OpenAI | `@tanstack/ai-openai` | `openaiText` | `OPENAI_API_KEY` |
83
+ | Anthropic | `@tanstack/ai-anthropic` | `anthropicText` | `ANTHROPIC_API_KEY` |
84
+ | Gemini | `@tanstack/ai-gemini` | `geminiText` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |
85
+ | Grok (xAI) | `@tanstack/ai-grok` | `grokText` | `XAI_API_KEY` |
86
+ | Groq | `@tanstack/ai-groq` | `groqText` | `GROQ_API_KEY` |
87
+ | OpenRouter | `@tanstack/ai-openrouter` | `openRouterText` | `OPENROUTER_API_KEY` |
88
+ | Ollama | `@tanstack/ai-ollama` | `ollamaText` | `OLLAMA_HOST` (default: `http://localhost:11434`) |
89
+ | Bedrock | `@tanstack/ai-bedrock` | `bedrockText` | `BEDROCK_API_KEY` or `AWS_BEARER_TOKEN_BEDROCK` |
90
+ | BytePlus | `@tanstack/ai-byteplus` | `byteplusText` | `ARK_API_KEY` (falls back to `BYTEPLUS_API_KEY`) |
91
+ | OpenAI-compatible | `@tanstack/ai-openai/compatible` | `openaiCompatible` / `openaiCompatibleText` | provider-specific (passed via `apiKey`) |
92
+ | Cloudflare | `@tanstack/ai-cloudflare` | `cloudflareText` | `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_API_TOKEN`, or `{ binding: env.AI }` in a Worker |
88
93
 
89
94
  > **BytePlus uses two keys.** `byteplusText` / `byteplusVideo` /
90
95
  > `byteplusImage` read `ARK_API_KEY` (ModelArk, `Authorization: Bearer`), but
@@ -94,7 +99,7 @@ The text adapter is the primary one for chat/completions:
94
99
 
95
100
  ```typescript
96
101
  // Each factory takes model as first arg, optional config as second
97
- import { openaiText } from '@tanstack/ai-openai'
102
+ import { openaiText, createOpenaiChat } from '@tanstack/ai-openai'
98
103
  import { anthropicText } from '@tanstack/ai-anthropic'
99
104
  import { geminiText } from '@tanstack/ai-gemini'
100
105
  import { grokText } from '@tanstack/ai-grok'
@@ -108,17 +113,16 @@ import { byteplusText } from '@tanstack/ai-byteplus'
108
113
  const adapter = openaiText('gpt-5.2')
109
114
  const adapter2 = anthropicText('claude-sonnet-4-6')
110
115
  const adapter3 = geminiText('gemini-2.5-pro')
111
- const adapter4 = grokText('grok-4')
116
+ const adapter4 = grokText('grok-4.6')
112
117
  const adapter5 = groqText('llama-3.3-70b-versatile')
113
118
  const adapter6 = openRouterText('anthropic/claude-sonnet-4')
114
- const adapter7 = ollamaText('llama3.3')
119
+ const adapter7 = ollamaText('llama3.3:latest')
115
120
  const adapter8 = bedrockText('us.anthropic.claude-3-7-sonnet-20250219-v1:0')
116
121
  const adapter9 = byteplusText('seed-2-0-lite-260428')
117
122
 
118
- // Optional: pass explicit API key
119
- const adapterWithKey = openaiText('gpt-5.2', {
120
- apiKey: 'sk-...',
121
- })
123
+ // Optional: pass an explicit API key via the create* sibling
124
+ // (the plain factory reads it from the environment)
125
+ const adapterWithKey = createOpenaiChat('gpt-5.2', 'sk-...')
122
126
  ```
123
127
 
124
128
  `@tanstack/ai-bedrock` (Amazon Bedrock) branches on `config.api`:
@@ -136,26 +140,32 @@ input or configuration:
136
140
 
137
141
  ```typescript
138
142
  import { chat, toServerSentEventsResponse } from '@tanstack/ai'
139
- import type { TextAdapter } from '@tanstack/ai/adapters'
143
+ import type { ModelMessage } from '@tanstack/ai'
140
144
  import { openaiText } from '@tanstack/ai-openai'
141
145
  import { anthropicText } from '@tanstack/ai-anthropic'
142
146
  import { geminiText } from '@tanstack/ai-gemini'
143
147
 
144
148
  // Define a map of provider+model to adapter factory calls
145
- const adapters: Record<string, () => TextAdapter> = {
149
+ const adapters = {
146
150
  'openai/gpt-5.2': () => openaiText('gpt-5.2'),
147
151
  'anthropic/claude-sonnet-4-6': () => anthropicText('claude-sonnet-4-6'),
148
152
  'gemini/gemini-2.5-pro': () => geminiText('gemini-2.5-pro'),
149
153
  }
150
154
 
151
- export function handleChat(providerModel: string, messages: Array<any>) {
152
- const createAdapter = adapters[providerModel]
153
- if (!createAdapter) {
155
+ function isKnownProviderModel(key: string): key is keyof typeof adapters {
156
+ return key in adapters
157
+ }
158
+
159
+ export function handleChat(
160
+ providerModel: string,
161
+ messages: Array<ModelMessage>,
162
+ ) {
163
+ if (!isKnownProviderModel(providerModel)) {
154
164
  throw new Error(`Unknown provider/model: ${providerModel}`)
155
165
  }
156
166
 
157
167
  const stream = chat({
158
- adapter: createAdapter(),
168
+ adapter: adapters[providerModel](),
159
169
  messages,
160
170
  })
161
171
 
@@ -173,6 +183,10 @@ import { openaiText } from '@tanstack/ai-openai'
173
183
  import { anthropicText } from '@tanstack/ai-anthropic'
174
184
  import { geminiText } from '@tanstack/ai-gemini'
175
185
 
186
+ const messages = [
187
+ { role: 'user' as const, content: 'Plan a database migration.' },
188
+ ]
189
+
176
190
  // OpenAI: reasoning with effort and summary
177
191
  const openaiStream = chat({
178
192
  adapter: openaiText('gpt-5.2'),
@@ -198,16 +212,18 @@ const anthropicStream = chat({
198
212
  },
199
213
  })
200
214
 
201
- // Anthropic: adaptive thinking (claude-sonnet-4-6 and newer)
215
+ // Anthropic: adaptive thinking (Sonnet 5, Fable 5, Opus 4.7+) — depth is
216
+ // tuned with output_config.effort instead of a token budget
202
217
  const adaptiveStream = chat({
203
- adapter: anthropicText('claude-sonnet-4-6'),
218
+ adapter: anthropicText('claude-sonnet-5'),
204
219
  messages,
205
220
  modelOptions: {
206
221
  max_tokens: 16000,
207
222
  thinking: {
208
223
  type: 'adaptive',
224
+ display: 'summarized', // stream the reasoning text (default 'omitted')
209
225
  },
210
- effort: 'high', // 'max' | 'high' | 'medium' | 'low'
226
+ output_config: { effort: 'high' }, // 'low' | 'medium' | 'high' | 'xhigh' | 'max'
211
227
  },
212
228
  })
213
229
 
@@ -262,6 +278,14 @@ inside `modelOptions` using each provider's **native** key. They are not
262
278
  top-level fields on `chat()`/`ai()`/`generate()`.
263
279
 
264
280
  ```typescript
281
+ import { chat } from '@tanstack/ai'
282
+ import { openaiText } from '@tanstack/ai-openai'
283
+ import { anthropicText } from '@tanstack/ai-anthropic'
284
+ import { geminiText } from '@tanstack/ai-gemini'
285
+ import { ollamaText } from '@tanstack/ai-ollama'
286
+
287
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
288
+
265
289
  // OpenAI — native keys
266
290
  chat({
267
291
  adapter: openaiText('gpt-5.2'),
@@ -284,8 +308,9 @@ chat({
284
308
  })
285
309
 
286
310
  // Ollama — NESTED under modelOptions.options
311
+ // (use the `family:tag` id — a bare `llama3.3` falls back to untyped options)
287
312
  chat({
288
- adapter: ollamaText('llama3.3'),
313
+ adapter: ollamaText('llama3.3:latest'),
289
314
  messages,
290
315
  modelOptions: {
291
316
  options: { temperature: 0.7, top_p: 0.9, num_predict: 1000 },
@@ -300,7 +325,7 @@ Per-provider sampling keys (all live inside `modelOptions`):
300
325
  | OpenAI | `temperature` | `top_p` | `max_output_tokens` |
301
326
  | Anthropic | `temperature` | `top_p` | `max_tokens` |
302
327
  | Gemini | `temperature` | `topP` | `maxOutputTokens` |
303
- | Grok (xAI) | `temperature` | `top_p` | `max_tokens` |
328
+ | Grok (xAI) | `temperature` | `top_p` | `max_output_tokens` |
304
329
  | Groq | `temperature` | `top_p` | `max_completion_tokens` |
305
330
  | OpenRouter (chat) | `temperature` | `topP` | `maxCompletionTokens` |
306
331
  | Ollama | `temperature` | `top_p` | `num_predict` (nested in `options`) |
@@ -325,7 +350,16 @@ some sampling options use provider-native names. Ollama nests all sampling under
325
350
  Adapters can declare an optional capability method:
326
351
 
327
352
  ```ts
328
- supportsCombinedToolsAndSchema?(modelOptions?: TProviderOptions): boolean
353
+ import { AnthropicTextAdapter } from '@tanstack/ai-anthropic'
354
+
355
+ // The TextAdapter contract:
356
+ // supportsCombinedToolsAndSchema?: (modelOptions?: TProviderOptions) => boolean
357
+ // Subclasses override it to narrow the capability:
358
+ class LegacyPathAnthropic extends AnthropicTextAdapter<'claude-sonnet-4-6'> {
359
+ override supportsCombinedToolsAndSchema(): boolean {
360
+ return false
361
+ }
362
+ }
329
363
  ```
330
364
 
331
365
  When `true`, the engine wires `outputSchema` into the regular
@@ -337,16 +371,16 @@ runs.
337
371
 
338
372
  Current per-adapter status (#605):
339
373
 
340
- | Adapter | Returns |
341
- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
342
- | `openaiText` / `openaiChatCompletions` | `true` (all supported models) |
343
- | `anthropicText` | `true` for Claude 4.5+ (gated by `ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS`), `false` otherwise |
344
- | `geminiText` | `true` for Gemini 3.x (gated by `GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS`), `false` otherwise |
345
- | `grokText` | `true` for Grok 4 family (gated by `GROK_COMBINED_TOOLS_AND_SCHEMA_MODELS`), `false` otherwise |
346
- | `groqText` | `false` (Groq API rejects schema + tools + stream) |
347
- | `openRouterText` / `openRouterResponsesText` | `false` (per-call resolution is a follow-up) |
348
- | `ollamaText` | `false` (constrained-decoding vs tool-call grammar conflict) |
349
- | `byteplusText` | Per model — `true` only for the 10 ids in `BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS`, `false` otherwise |
374
+ | Adapter | Returns |
375
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
376
+ | `openaiText` / `openaiChatCompletions` | `true` (all supported models) |
377
+ | `anthropicText` | `true` for Claude 4.5+ (gated by `ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS`), `false` otherwise |
378
+ | `geminiText` | `true` for Gemini 3.x (gated by `GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS`), `false` otherwise |
379
+ | `grokText` | `true` (all chat models — inherits the OpenAI Responses base; no per-model gate) |
380
+ | `groqText` | `false` (Groq API rejects schema + tools + stream) |
381
+ | `openRouterText` / `openRouterResponsesText` | Per model — `true` only when the model and every `modelOptions.models` fallback are in `OPENROUTER_COMBINED_TOOLS_AND_SCHEMA_MODELS` |
382
+ | `ollamaText` | `false` (constrained-decoding vs tool-call grammar conflict) |
383
+ | `byteplusText` | Per model — `true` only for the 10 ids in `BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS`, `false` otherwise |
350
384
 
351
385
  Subclasses can override to narrow the capability. When extending an
352
386
  adapter for a custom model that doesn't support the combination, return
@@ -370,7 +404,9 @@ dedicated package required.
370
404
 
371
405
  ```typescript
372
406
  import { openaiCompatible } from '@tanstack/ai-openai/compatible'
373
- import { createModel } from '@tanstack/ai'
407
+ import { chat, createModel } from '@tanstack/ai'
408
+
409
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
374
410
 
375
411
  // Provider-factory: configure baseURL + apiKey + models ONCE,
376
412
  // then select a model per call (the model arg is a type-safe union).
@@ -399,6 +435,9 @@ For a single model, use the one-shot helper:
399
435
 
400
436
  ```typescript
401
437
  import { openaiCompatibleText } from '@tanstack/ai-openai/compatible'
438
+ import { chat } from '@tanstack/ai'
439
+
440
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
402
441
 
403
442
  chat({
404
443
  adapter: openaiCompatibleText('deepseek-chat', {
@@ -427,13 +466,17 @@ ElevenLabs `baseUrl`/`headers`). The vendor names still work; when both are
427
466
  set, `baseURL` and `defaultHeaders` win.
428
467
 
429
468
  ```typescript
469
+ import { createGeminiChat } from '@tanstack/ai-gemini'
470
+
430
471
  const gateway = {
431
472
  baseURL: 'https://gateway.example.com/google-ai-studio',
432
473
  defaultHeaders: {
433
474
  'cf-aig-authorization': `Bearer ${process.env.GATEWAY_TOKEN}`,
434
475
  },
435
476
  }
436
- createGeminiChat('gemini-3.8-flash', apiKey, { ...gateway })
477
+ createGeminiChat('gemini-3.8-flash', process.env.GOOGLE_API_KEY!, {
478
+ ...gateway,
479
+ })
437
480
  ```
438
481
 
439
482
  ## Common Mistakes
@@ -443,13 +486,19 @@ createGeminiChat('gemini-3.8-flash', apiKey, { ...gateway })
443
486
  The legacy `openai()` (and `anthropic()`, etc.) monolithic adapters are
444
487
  deprecated. They take the model in `chat()`, not in the factory.
445
488
 
446
- ```typescript
447
- // WRONG: Legacy monolithic adapter pattern
489
+ ```typescript ignore
490
+ // WRONG: Legacy monolithic adapter pattern (no longer exported)
448
491
  import { openai } from '@tanstack/ai-openai'
449
492
  chat({ adapter: openai(), model: 'gpt-5.2', messages })
493
+ ```
450
494
 
495
+ ```typescript
451
496
  // CORRECT: Tree-shakeable adapter, model in factory
497
+ import { chat } from '@tanstack/ai'
452
498
  import { openaiText } from '@tanstack/ai-openai'
499
+
500
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
501
+
453
502
  chat({ adapter: openaiText('gpt-5.2'), messages })
454
503
  ```
455
504
 
@@ -21,45 +21,58 @@ import { anthropicText } from '@tanstack/ai-anthropic'
21
21
 
22
22
  ## Key Chat Models
23
23
 
24
- | Model | Context Window | Max Output | Notes |
25
- | ------------------- | -------------- | ---------- | ------------------------------------------- |
26
- | `claude-fable-5` | 1M | 128K | Most capable; thinking always on (adaptive) |
27
- | `claude-sonnet-5` | 1M | 128K | Best balance; adaptive thinking by default |
28
- | `claude-opus-4-8` | 1M | 128K | Opus tier; adaptive thinking, no sampling |
29
- | `claude-opus-4-7` | 1M | 128K | Older Opus; adaptive thinking, no sampling |
30
- | `claude-opus-4-6` | 200K | 128K | Older Opus, adaptive + budget thinking |
31
- | `claude-sonnet-4-6` | 1M | 64K | Previous gen balanced, adaptive + budget |
32
- | `claude-sonnet-4-5` | 200K | 64K | Previous gen balanced |
33
- | `claude-opus-4-5` | 200K | 32K | Previous gen most capable |
34
- | `claude-opus-4-1` | 200K | 64K | Deprecated (retires 2026-08-05) |
35
- | `claude-haiku-4-5` | 200K | 64K | Fast and affordable |
24
+ | Model | Context Window | Max Output | Notes |
25
+ | -------------------- | -------------- | ---------- | ------------------------------------------- |
26
+ | `claude-fable-5-1` | 1M | 128K | Newest; thinking always on (adaptive) |
27
+ | `claude-fable-5` | 1M | 128K | Most capable; thinking always on (adaptive) |
28
+ | `claude-opus-5` | 1M | 128K | Opus tier; budget thinking + sampling |
29
+ | `claude-opus-5-fast` | 1M | 128K | Fast-mode Opus 5; same options as opus-5 |
30
+ | `claude-sonnet-5` | 1M | 128K | Best balance; adaptive thinking by default |
31
+ | `claude-opus-4-8` | 1M | 128K | Opus tier; adaptive thinking, no sampling |
32
+ | `claude-opus-4-7` | 1M | 128K | Older Opus; adaptive thinking, no sampling |
33
+ | `claude-opus-4-6` | 200K | 128K | Older Opus, adaptive + budget thinking |
34
+ | `claude-sonnet-4-6` | 1M | 64K | Previous gen balanced, adaptive + budget |
35
+ | `claude-sonnet-4-5` | 200K | 64K | Previous gen balanced |
36
+ | `claude-opus-4-5` | 200K | 32K | Previous gen most capable |
37
+ | `claude-opus-4-1` | 200K | 64K | Deprecated (retires 2026-08-05) |
38
+ | `claude-haiku-4-5` | 200K | 64K | Fast and affordable |
36
39
 
37
40
  Note: Model IDs use the format `claude-sonnet-5`, `claude-opus-4-8`, etc.
38
- Retired models (Claude 3.x, Sonnet 3.7, Opus 4 / Sonnet 4) and the `-fast`
39
- variant ids were removed — every registered id resolves against the
40
- first-party Anthropic API.
41
+ Retired models (Claude 3.x, Sonnet 3.7, Opus 4 / Sonnet 4) were removed —
42
+ every registered id resolves against the first-party Anthropic API.
43
+ `claude-opus-5-fast` is the only `-fast` id that remains.
44
+
45
+ `output_config.effort` is typed only on the adaptive-era models
46
+ (`claude-opus-4-7`, `claude-opus-4-8`, `claude-sonnet-5`, `claude-fable-5`,
47
+ `claude-fable-5-1`). There is no top-level `effort` option on any model;
48
+ `claude-opus-4-6` / `claude-sonnet-4-6` accept `thinking: { type: 'adaptive' }`
49
+ but no effort knob.
41
50
 
42
51
  ## Provider-Specific modelOptions
43
52
 
44
53
  ```typescript
54
+ import { chat } from '@tanstack/ai'
55
+ import { anthropicText } from '@tanstack/ai-anthropic'
56
+
57
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
58
+
45
59
  chat({
46
60
  adapter: anthropicText('claude-sonnet-4-6'),
47
61
  messages,
48
62
  modelOptions: {
49
63
  // Sampling
50
64
  temperature: 0.7,
51
- top_p: 0.9, // cannot be combined with temperature
65
+ // top_p: 0.9, // cannot be combined with temperature
52
66
  max_tokens: 16000,
53
67
  // Extended thinking (budget-based)
54
68
  thinking: {
55
69
  type: 'enabled',
56
70
  budget_tokens: 8000, // must be >= 1024 and < max_tokens
57
71
  },
58
- // Adaptive thinking (claude-sonnet-4-6, claude-opus-4-6+)
59
- thinking: {
60
- type: 'adaptive',
61
- },
62
- effort: 'high', // 'max' | 'high' | 'medium' | 'low'
72
+ // Adaptive thinking (claude-sonnet-4-6, claude-opus-4-6+) — the
73
+ // alternative to the budget shape above; effort is tuned via
74
+ // output_config.effort on the adaptive-era models (see below)
75
+ // thinking: { type: 'adaptive' },
63
76
  // Service tier
64
77
  service_tier: 'auto', // 'auto' | 'standard_only'
65
78
  // Stop sequences
@@ -99,6 +112,11 @@ ANTHROPIC_API_KEY
99
112
  The per-model types restrict `modelOptions` on the newest models:
100
113
 
101
114
  ```typescript
115
+ import { chat } from '@tanstack/ai'
116
+ import { anthropicText } from '@tanstack/ai-anthropic'
117
+
118
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
119
+
102
120
  chat({
103
121
  adapter: anthropicText('claude-sonnet-5'), // or 'claude-fable-5', 'claude-opus-4-8'
104
122
  messages,
@@ -62,6 +62,11 @@ Media models: `BYTEPLUS_VIDEO_MODELS` (Seedance —
62
62
  ## Provider-Specific modelOptions
63
63
 
64
64
  ```typescript
65
+ import { chat } from '@tanstack/ai'
66
+ import { byteplusText } from '@tanstack/ai-byteplus'
67
+
68
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
69
+
65
70
  chat({
66
71
  adapter: byteplusText('dola-seed-2-1-turbo-260628'),
67
72
  messages,
@@ -38,6 +38,15 @@ Most Gemini text models accept `text`, `image`, `audio`, `video`, and `document`
38
38
  ## Provider-Specific modelOptions
39
39
 
40
40
  ```typescript
41
+ import { chat } from '@tanstack/ai'
42
+ import {
43
+ geminiText,
44
+ HarmBlockThreshold,
45
+ HarmCategory,
46
+ } from '@tanstack/ai-gemini'
47
+
48
+ const messages = [{ role: 'user' as const, content: 'Hello' }]
49
+
41
50
  chat({
42
51
  adapter: geminiText('gemini-2.5-pro'),
43
52
  messages,
@@ -47,15 +56,14 @@ chat({
47
56
  includeThoughts: true,
48
57
  thinkingBudget: 4096,
49
58
  },
50
- // Thinking (level-based, advanced models)
51
- thinkingConfig: {
52
- thinkingLevel: 'THINKING_LEVEL_HIGH',
53
- },
59
+ // Thinking (level-based, advanced models) — the alternative to the
60
+ // budget shape above:
61
+ // thinkingConfig: { thinkingLevel: 'THINKING_LEVEL_HIGH' },
54
62
  // Safety settings
55
63
  safetySettings: [
56
64
  {
57
- category: 'HARM_CATEGORY_HATE_SPEECH',
58
- threshold: 'BLOCK_MEDIUM_AND_ABOVE',
65
+ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
66
+ threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
59
67
  },
60
68
  ],
61
69
  // Tool config