@tanstack/ai 0.52.2 → 0.53.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.
- package/dist/esm/activities/chat/index.js +1 -1
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +10 -11
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +2 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/byok/define-provider.d.ts +6 -0
- package/dist/esm/byok/define-provider.js +2 -1
- package/dist/esm/byok/define-provider.js.map +1 -1
- package/dist/esm/byok/get-key.d.ts +7 -0
- package/dist/esm/byok/get-key.js +8 -1
- package/dist/esm/byok/get-key.js.map +1 -1
- package/dist/esm/byok/server.d.ts +1 -1
- package/dist/esm/byok/server.js +2 -2
- package/package.json +1 -1
- package/skills/ai-core/adapter-configuration/SKILL.md +32 -12
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +5 -0
- package/skills/ai-core/structured-outputs/SKILL.md +1 -0
- package/src/activities/chat/index.ts +1 -1
- package/src/activities/chat/messages.ts +16 -14
- package/src/activities/summarize/chat-stream-summarize.ts +2 -0
- package/src/byok/define-provider.ts +7 -0
- package/src/byok/get-key.ts +18 -0
- package/src/byok/server.ts +1 -1
|
@@ -73,18 +73,19 @@ top-level options on `chat()`. See the per-provider table in
|
|
|
73
73
|
Each provider has a dedicated package with tree-shakeable adapter factories.
|
|
74
74
|
The text adapter is the primary one for chat/completions:
|
|
75
75
|
|
|
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`)
|
|
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`) |
|
|
88
|
+
| Cloudflare | `@tanstack/ai-cloudflare` | `cloudflareText` | `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_API_TOKEN`, or `{ binding: env.AI }` in a Worker |
|
|
88
89
|
|
|
89
90
|
> **BytePlus uses two keys.** `byteplusText` / `byteplusVideo` /
|
|
90
91
|
> `byteplusImage` read `ARK_API_KEY` (ModelArk, `Authorization: Bearer`), but
|
|
@@ -417,6 +418,25 @@ compatible providers speak.
|
|
|
417
418
|
> Verify the provider's current `baseURL` and model ids against its live docs —
|
|
418
419
|
> they drift. See `docs/adapters/openai-compatible.md` for the full provider table.
|
|
419
420
|
|
|
421
|
+
## Behind a proxy or gateway
|
|
422
|
+
|
|
423
|
+
Every adapter's client config accepts `baseURL` and `defaultHeaders`. Use these
|
|
424
|
+
two names to route any adapter through Cloudflare AI Gateway, Vercel AI Gateway,
|
|
425
|
+
or a corporate proxy. The adapter maps them onto the vendor SDK's own option
|
|
426
|
+
names (Gemini `httpOptions`, Mistral `serverURL`, Ollama `host`, Cohere and
|
|
427
|
+
ElevenLabs `baseUrl`/`headers`). The vendor names still work; when both are
|
|
428
|
+
set, `baseURL` and `defaultHeaders` win.
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
const gateway = {
|
|
432
|
+
baseURL: 'https://gateway.example.com/google-ai-studio',
|
|
433
|
+
defaultHeaders: {
|
|
434
|
+
'cf-aig-authorization': `Bearer ${process.env.GATEWAY_TOKEN}`,
|
|
435
|
+
},
|
|
436
|
+
}
|
|
437
|
+
createGeminiChat('gemini-3.8-flash', apiKey, { ...gateway })
|
|
438
|
+
```
|
|
439
|
+
|
|
420
440
|
## Common Mistakes
|
|
421
441
|
|
|
422
442
|
### a. HIGH: Confusing legacy monolithic with tree-shakeable adapter
|
|
@@ -95,3 +95,8 @@ OPENAI_API_KEY
|
|
|
95
95
|
`effort: 'low'` or higher to enable reasoning.
|
|
96
96
|
- `o3-pro` only supports `high` reasoning effort.
|
|
97
97
|
- `conversation` and `previous_response_id` cannot be used together.
|
|
98
|
+
- Reasoning models (`o*`, `gpt-5*` except `*-chat-latest`, `codex-mini-latest`)
|
|
99
|
+
pair each `function_call` with a `reasoning` item. The adapter requests
|
|
100
|
+
`include: ['reasoning.encrypted_content']` for those models and replays that
|
|
101
|
+
item on the next turn. Pre-5 chat models are left unchanged. If you persist
|
|
102
|
+
history by hand, keep `thinking[].signature`.
|
|
@@ -192,6 +192,7 @@ The terminal event is a `CUSTOM` chunk: `{ type: 'CUSTOM', name: 'structured-out
|
|
|
192
192
|
| `@tanstack/ai-groq` | Legacy `structuredOutputStream` only (no tools — Groq's API rejects schema + tools + stream) |
|
|
193
193
|
| `@tanstack/ai-bedrock` | Separate native `structuredOutputStream` finalization through Converse or an OpenAI-compatible API |
|
|
194
194
|
| `@tanstack/ai-byteplus` | Native combined mode on supported models; unsupported models emit `RUN_ERROR` |
|
|
195
|
+
| `@tanstack/ai-cloudflare` | Native `structuredOutputStream` without tools; with tools, a separate finalization call (Workers AI models answer the tool turn in prose) |
|
|
195
196
|
| `@tanstack/ai-claude-code` | Combined + event source — `--json-schema` on the same harness turn. Read `useChat().final`. See Pattern 6. |
|
|
196
197
|
| `@tanstack/ai-codex` | Combined + event source — `--output-schema` on the same harness turn. Read `useChat().final`. See Pattern 6. |
|
|
197
198
|
| `@tanstack/ai-opencode` | Combined + event source — prompt-and-parse. Read `useChat().final`. See Pattern 6. |
|
|
@@ -1870,7 +1870,7 @@ class TextEngine<
|
|
|
1870
1870
|
}
|
|
1871
1871
|
|
|
1872
1872
|
private finalizeCurrentThinkingStep(): void {
|
|
1873
|
-
if (this.currentThinkingContent) {
|
|
1873
|
+
if (this.currentThinkingContent || this.currentThinkingSignature) {
|
|
1874
1874
|
this.accumulatedThinking.push({
|
|
1875
1875
|
content: this.currentThinkingContent,
|
|
1876
1876
|
...(this.currentThinkingSignature && {
|
|
@@ -173,10 +173,10 @@ export function convertMessagesToModelMessages(
|
|
|
173
173
|
|
|
174
174
|
if (role === 'reasoning') {
|
|
175
175
|
const content = (msg as { content?: string }).content
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const signature = encryptedValueFrom(msg)
|
|
177
|
+
if (content || signature !== undefined) {
|
|
178
178
|
pendingThinking.push({
|
|
179
|
-
content,
|
|
179
|
+
content: typeof content === 'string' ? content : '',
|
|
180
180
|
...(signature !== undefined ? { signature } : {}),
|
|
181
181
|
})
|
|
182
182
|
}
|
|
@@ -593,7 +593,7 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
593
593
|
break
|
|
594
594
|
|
|
595
595
|
case 'thinking':
|
|
596
|
-
if (part.content) {
|
|
596
|
+
if (part.content || part.signature) {
|
|
597
597
|
// Provider-executed tools have no tool-result part, so thinking
|
|
598
598
|
// after them has to start the next segment or it replays first.
|
|
599
599
|
if (current.toolCalls.some(isProviderExecutedToolCall)) {
|
|
@@ -712,7 +712,7 @@ export function modelMessageToUIMessage(
|
|
|
712
712
|
|
|
713
713
|
if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {
|
|
714
714
|
for (const thinking of modelMessage.thinking) {
|
|
715
|
-
if (!thinking.content) continue
|
|
715
|
+
if (!thinking.content && !thinking.signature) continue
|
|
716
716
|
parts.push({
|
|
717
717
|
type: 'thinking',
|
|
718
718
|
content: thinking.content,
|
|
@@ -917,18 +917,20 @@ export function aguiSnapshotMessageToUIMessage(
|
|
|
917
917
|
})
|
|
918
918
|
case 'reasoning': {
|
|
919
919
|
const signature = encryptedValueFrom(message)
|
|
920
|
+
const content = typeof message.content === 'string' ? message.content : ''
|
|
920
921
|
return applySnapshotMetadata(message, {
|
|
921
922
|
id,
|
|
922
923
|
role: 'assistant',
|
|
923
|
-
parts:
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
924
|
+
parts:
|
|
925
|
+
content || signature !== undefined
|
|
926
|
+
? [
|
|
927
|
+
{
|
|
928
|
+
type: 'thinking' as const,
|
|
929
|
+
content,
|
|
930
|
+
...(signature !== undefined ? { signature } : {}),
|
|
931
|
+
},
|
|
932
|
+
]
|
|
933
|
+
: [],
|
|
932
934
|
})
|
|
933
935
|
}
|
|
934
936
|
case 'activity':
|
|
@@ -95,6 +95,8 @@ const MAX_TOKENS_KEY_BY_ADAPTER: Record<string, string> = {
|
|
|
95
95
|
// LLM Gateway exposes an OpenAI-compatible Chat Completions surface whose
|
|
96
96
|
// only output cap is `max_tokens` — it does not read `max_completion_tokens`.
|
|
97
97
|
llmgateway: 'max_tokens',
|
|
98
|
+
// Workers AI's OpenAI-compatible Chat Completions surface reads `max_tokens`.
|
|
99
|
+
cloudflare: 'max_tokens',
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
/**
|
|
@@ -12,6 +12,11 @@ export interface ByokProvider<TId extends string = string> {
|
|
|
12
12
|
* values here. This object is imported on the client.
|
|
13
13
|
*/
|
|
14
14
|
readonly env?: ReadonlyArray<string>
|
|
15
|
+
/**
|
|
16
|
+
* Other descriptors this credential needs. A send for this provider also
|
|
17
|
+
* carries their headers and prompts for each one that is missing.
|
|
18
|
+
*/
|
|
19
|
+
readonly with?: ReadonlyArray<ByokProvider>
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -22,6 +27,7 @@ export type ByokProviderInit<TId extends string> = {
|
|
|
22
27
|
readonly id: undefined extends TId ? never : TId
|
|
23
28
|
readonly label: string
|
|
24
29
|
readonly env?: string | ReadonlyArray<string>
|
|
30
|
+
readonly with?: ReadonlyArray<ByokProvider>
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
function normalizeEnv(
|
|
@@ -42,5 +48,6 @@ export function defineByokProvider<const TId extends string>(
|
|
|
42
48
|
id: provider.id,
|
|
43
49
|
label: provider.label,
|
|
44
50
|
...(env ? { env } : {}),
|
|
51
|
+
...(provider.with ? { with: provider.with } : {}),
|
|
45
52
|
}
|
|
46
53
|
}
|
package/src/byok/get-key.ts
CHANGED
|
@@ -25,3 +25,21 @@ export function getByokKey(
|
|
|
25
25
|
}
|
|
26
26
|
return null
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read several keys at once, one per name. Same rules as {@link getByokKey}
|
|
31
|
+
* for each entry. Use it for a credential made of more than one value.
|
|
32
|
+
*/
|
|
33
|
+
export function getByokKeys<
|
|
34
|
+
const TProviders extends Record<string, ProviderId | ByokProvider>,
|
|
35
|
+
>(
|
|
36
|
+
request: Request,
|
|
37
|
+
providers: TProviders,
|
|
38
|
+
): { [K in keyof TProviders]: string | null } {
|
|
39
|
+
return Object.fromEntries(
|
|
40
|
+
Object.entries(providers).map(([name, provider]) => [
|
|
41
|
+
name,
|
|
42
|
+
getByokKey(request, provider),
|
|
43
|
+
]),
|
|
44
|
+
) as { [K in keyof TProviders]: string | null }
|
|
45
|
+
}
|
package/src/byok/server.ts
CHANGED