@tanstack/ai 0.41.0 → 0.43.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/README.md +15 -1
- package/dist/esm/activities/chat/adapter.js +23 -16
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agent-loop-strategies.d.ts +10 -4
- package/dist/esm/activities/chat/agent-loop-strategies.js +75 -17
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
- package/dist/esm/activities/chat/cancel.d.ts +40 -0
- package/dist/esm/activities/chat/cancel.js +54 -0
- package/dist/esm/activities/chat/cancel.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +28 -16
- package/dist/esm/activities/chat/index.js +2100 -1744
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
- package/dist/esm/activities/chat/mcp/manager.js +90 -77
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
- package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
- package/dist/esm/activities/chat/messages.js +397 -346
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.js +17 -15
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
- package/dist/esm/activities/chat/middleware/compose.js +623 -531
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.js +12 -5
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
- package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
- package/dist/esm/activities/chat/middleware/locks.js +71 -0
- package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
- package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
- package/dist/esm/activities/chat/middleware/run-store.js +176 -0
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
- package/dist/esm/activities/chat/middleware/validate.js +23 -28
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
- package/dist/esm/activities/chat/stream/json-parser.js +39 -25
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
- package/dist/esm/activities/chat/stream/processor.js +1341 -1542
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.js +69 -53
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
- package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
- package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
- package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/error-payload.js +85 -47
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +22 -15
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +4 -0
- package/dist/esm/activities/generateAudio/index.js +141 -105
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +22 -15
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +4 -0
- package/dist/esm/activities/generateImage/index.js +155 -111
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +22 -15
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
- package/dist/esm/activities/generateSpeech/index.js +159 -110
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +22 -15
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
- package/dist/esm/activities/generateTranscription/index.js +159 -100
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +36 -29
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +143 -19
- package/dist/esm/activities/generateVideo/index.js +456 -279
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/snap.js +60 -48
- package/dist/esm/activities/generateVideo/snap.js.map +1 -1
- package/dist/esm/activities/index.js +8 -34
- package/dist/esm/activities/middleware/index.d.ts +1 -1
- package/dist/esm/activities/middleware/run.d.ts +10 -0
- package/dist/esm/activities/middleware/run.js +53 -29
- package/dist/esm/activities/middleware/run.js.map +1 -1
- package/dist/esm/activities/middleware/types.d.ts +44 -6
- package/dist/esm/activities/stream-generation-result.d.ts +4 -1
- package/dist/esm/activities/stream-generation-result.js +79 -44
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +22 -15
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.d.ts +27 -0
- package/dist/esm/activities/summarize/index.js +268 -102
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -1
- package/dist/esm/adapter-internals.js +4 -11
- package/dist/esm/client.d.ts +25 -3
- package/dist/esm/client.js +131 -64
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/custom-events.d.ts +76 -0
- package/dist/esm/custom-events.js +37 -0
- package/dist/esm/custom-events.js.map +1 -0
- package/dist/esm/delivery-detach.d.ts +50 -0
- package/dist/esm/delivery-detach.js +71 -0
- package/dist/esm/delivery-detach.js.map +1 -0
- package/dist/esm/delivery-disconnect.d.ts +62 -0
- package/dist/esm/delivery-disconnect.js +81 -0
- package/dist/esm/delivery-disconnect.js.map +1 -0
- package/dist/esm/extend-adapter.js +19 -17
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +23 -5
- package/dist/esm/index.js +30 -97
- package/dist/esm/interrupt-resume.d.ts +71 -0
- package/dist/esm/interrupt-resume.js +438 -0
- package/dist/esm/interrupt-resume.js.map +1 -0
- package/dist/esm/interrupt-serialization.d.ts +12 -0
- package/dist/esm/interrupt-serialization.js +178 -0
- package/dist/esm/interrupt-serialization.js.map +1 -0
- package/dist/esm/interrupts.d.ts +84 -0
- package/dist/esm/interrupts.js +31 -0
- package/dist/esm/interrupts.js.map +1 -0
- package/dist/esm/locks.d.ts +10 -0
- package/dist/esm/locks.js +2 -0
- package/dist/esm/logger/console-logger.js +101 -78
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +104 -89
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +54 -49
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/logger/types.d.ts +1 -1
- package/dist/esm/middlewares/content-guard.js +142 -148
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/index.js +2 -6
- package/dist/esm/middlewares/otel.js +598 -732
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.js +47 -40
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/realtime/event-emitter.js +24 -25
- package/dist/esm/realtime/event-emitter.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +5 -9
- package/dist/esm/realtime/index.js +29 -6
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/scope.d.ts +47 -0
- package/dist/esm/stream-durability.d.ts +171 -0
- package/dist/esm/stream-durability.js +295 -0
- package/dist/esm/stream-durability.js.map +1 -0
- package/dist/esm/stream-to-response.d.ts +178 -13
- package/dist/esm/stream-to-response.js +663 -115
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +30 -16
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/system-prompts.js +27 -21
- package/dist/esm/system-prompts.js.map +1 -1
- package/dist/esm/tool-registry.js +72 -45
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/tools/provider-tool.js +14 -5
- package/dist/esm/tools/provider-tool.js.map +1 -1
- package/dist/esm/types.d.ts +332 -21
- package/dist/esm/types.js +2 -0
- package/dist/esm/utilities/ag-ui-wire.js +79 -93
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +26 -4
- package/dist/esm/utilities/chat-params.js +218 -92
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/errors.js +28 -18
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +46 -41
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/numbers.js +13 -10
- package/dist/esm/utilities/numbers.js.map +1 -1
- package/dist/esm/utilities/provider-executed.js +20 -11
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/sampling-keys.js +31 -19
- package/dist/esm/utilities/sampling-keys.js.map +1 -1
- package/dist/esm/utilities/tool-result.js +42 -30
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/dist/esm/utilities/usage.js +27 -9
- package/dist/esm/utilities/usage.js.map +1 -1
- package/dist/esm/utils.js +26 -18
- package/dist/esm/utils.js.map +1 -1
- package/package.json +10 -6
- package/skills/ai-core/SKILL.md +69 -18
- package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
- package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
- package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
- package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +156 -11
- package/skills/ai-core/client-persistence/SKILL.md +277 -0
- package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
- package/skills/ai-core/debug-logging/SKILL.md +1 -1
- package/skills/ai-core/locks/SKILL.md +143 -0
- package/skills/ai-core/media-generation/SKILL.md +144 -12
- package/skills/ai-core/middleware/SKILL.md +258 -33
- package/skills/ai-core/structured-outputs/SKILL.md +1 -1
- package/skills/ai-core/tool-calling/SKILL.md +54 -59
- package/src/activities/chat/agent-loop-strategies.ts +10 -4
- package/src/activities/chat/cancel.ts +81 -0
- package/src/activities/chat/index.ts +1152 -153
- package/src/activities/chat/mcp/manager.ts +4 -4
- package/src/activities/chat/mcp/types.ts +2 -2
- package/src/activities/chat/messages.ts +5 -3
- package/src/activities/chat/middleware/builder.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +186 -9
- package/src/activities/chat/middleware/index.ts +26 -0
- package/src/activities/chat/middleware/locks.ts +102 -0
- package/src/activities/chat/middleware/pending-turn.ts +47 -0
- package/src/activities/chat/middleware/run-disconnect.ts +62 -0
- package/src/activities/chat/middleware/run-store.ts +412 -0
- package/src/activities/chat/middleware/types.ts +62 -1
- package/src/activities/chat/stream/processor.ts +189 -5
- package/src/activities/chat/tools/approval-schema.ts +205 -0
- package/src/activities/chat/tools/tool-calls.ts +106 -13
- package/src/activities/chat/tools/tool-definition.ts +210 -39
- package/src/activities/generateAudio/index.ts +20 -3
- package/src/activities/generateImage/index.ts +20 -3
- package/src/activities/generateSpeech/index.ts +25 -3
- package/src/activities/generateTranscription/index.ts +26 -3
- package/src/activities/generateVideo/index.ts +345 -82
- package/src/activities/middleware/index.ts +2 -0
- package/src/activities/middleware/run.ts +31 -0
- package/src/activities/middleware/types.ts +49 -5
- package/src/activities/stream-generation-result.ts +30 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -0
- package/src/activities/summarize/index.ts +200 -10
- package/src/adapter-internals.ts +10 -1
- package/src/client.ts +244 -0
- package/src/custom-events.ts +107 -0
- package/src/delivery-detach.ts +72 -0
- package/src/delivery-disconnect.ts +84 -0
- package/src/index.ts +138 -0
- package/src/interrupt-resume.ts +824 -0
- package/src/interrupt-serialization.ts +183 -0
- package/src/interrupts.ts +146 -0
- package/src/locks.ts +17 -0
- package/src/logger/types.ts +1 -1
- package/src/middlewares/otel.ts +1 -0
- package/src/realtime/index.ts +5 -9
- package/src/scope.ts +47 -0
- package/src/stream-durability.ts +598 -0
- package/src/stream-to-response.ts +1051 -95
- package/src/strip-to-spec-middleware.ts +3 -3
- package/src/types.ts +416 -24
- package/src/utilities/chat-params.ts +245 -55
- package/dist/esm/activities/index.js.map +0 -1
- package/dist/esm/adapter-internals.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/middlewares/index.js.map +0 -1
|
@@ -8,10 +8,11 @@ description: >
|
|
|
8
8
|
execution order. NOT onEnd/onFinish callbacks on chat() — use middleware.
|
|
9
9
|
type: sub-skill
|
|
10
10
|
library: tanstack-ai
|
|
11
|
-
library_version: '0.
|
|
11
|
+
library_version: '0.42.0'
|
|
12
12
|
sources:
|
|
13
13
|
- 'TanStack/ai:docs/advanced/middleware.md'
|
|
14
14
|
- 'TanStack/ai:docs/sandbox/observability.md'
|
|
15
|
+
- 'TanStack/ai:docs/persistence/overview.md'
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
# Middleware
|
|
@@ -57,6 +58,7 @@ Every hook receives a `ChatMiddlewareContext` as its first argument, which provi
|
|
|
57
58
|
| `onStructuredOutputConfig` | Once at the structured-output boundary (only when `chat({ outputSchema })`) | `StructuredOutputMiddlewareConfig` (return partial) |
|
|
58
59
|
| `onStart` | Once after initial `onConfig` | none |
|
|
59
60
|
| `onIteration` | Start of each agent loop iteration | `IterationInfo` |
|
|
61
|
+
| `onShouldContinue` | Whether to start another agent-loop iteration (AND with strategy; `false` stops) | `AgentLoopState` |
|
|
60
62
|
| `onChunk` | Every streamed chunk | `StreamChunk` (return void/chunk/chunk[]/null) |
|
|
61
63
|
| `onBeforeToolCall` | Before each tool executes | `ToolCallHookContext` (return decision or void) |
|
|
62
64
|
| `onAfterToolCall` | After each tool executes | `AfterToolCallInfo` |
|
|
@@ -116,17 +118,25 @@ onStructuredOutputConfig?: (
|
|
|
116
118
|
| void
|
|
117
119
|
| null
|
|
118
120
|
| Partial<StructuredOutputMiddlewareConfig>
|
|
119
|
-
| Promise<void | Partial<StructuredOutputMiddlewareConfig>>
|
|
121
|
+
| Promise<void | null | Partial<StructuredOutputMiddlewareConfig>>
|
|
120
122
|
```
|
|
121
123
|
|
|
122
124
|
**`StructuredOutputMiddlewareConfig` shape:**
|
|
123
125
|
|
|
124
126
|
```ts
|
|
125
|
-
interface StructuredOutputMiddlewareConfig extends
|
|
127
|
+
interface StructuredOutputMiddlewareConfig extends Omit<
|
|
128
|
+
ChatMiddlewareConfig,
|
|
129
|
+
'tools'
|
|
130
|
+
> {
|
|
126
131
|
outputSchema: JSONSchema // The JSON Schema being sent to the provider
|
|
127
132
|
}
|
|
128
133
|
```
|
|
129
134
|
|
|
135
|
+
Note the `Omit<…, 'tools'>`: there is **no `config.tools`** on this hook. The
|
|
136
|
+
structured-output call is the final, tool-free call, so reading or returning
|
|
137
|
+
`tools` here is a compile error, not a no-op. Transform tools in `onConfig`
|
|
138
|
+
instead.
|
|
139
|
+
|
|
130
140
|
**Ordering rule:**
|
|
131
141
|
|
|
132
142
|
- `onStructuredOutputConfig` fires **before** `onConfig` at the structured-output boundary.
|
|
@@ -211,11 +221,18 @@ const toolGuard: ChatMiddleware = {
|
|
|
211
221
|
return { type: 'abort', reason: 'Dangerous operation blocked' }
|
|
212
222
|
}
|
|
213
223
|
|
|
214
|
-
// Enforce default arguments
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
args
|
|
224
|
+
// Enforce default arguments. `hookCtx.args` is `unknown` — the provider
|
|
225
|
+
// sent it — so narrow before reading it. No `as` casts.
|
|
226
|
+
if (hookCtx.toolName === 'search') {
|
|
227
|
+
const args =
|
|
228
|
+
typeof hookCtx.args === 'object' && hookCtx.args !== null
|
|
229
|
+
? hookCtx.args
|
|
230
|
+
: {}
|
|
231
|
+
if (!('limit' in args)) {
|
|
232
|
+
return {
|
|
233
|
+
type: 'transformArgs',
|
|
234
|
+
args: { ...args, limit: 10 },
|
|
235
|
+
}
|
|
219
236
|
}
|
|
220
237
|
}
|
|
221
238
|
|
|
@@ -346,6 +363,52 @@ const stream = chat({
|
|
|
346
363
|
| `onUsage` | Sequential | All run in order |
|
|
347
364
|
| `onFinish/onAbort/onError` | Sequential | All run in order |
|
|
348
365
|
|
|
366
|
+
## Pattern: tool-call budget (app-owned)
|
|
367
|
+
|
|
368
|
+
Not a built-in. Cap fan-out with `onBeforeToolCall` skip + `onShouldContinue`.
|
|
369
|
+
See `docs/chat/agentic-cycle.md` ("Tool-call budgets").
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { chat, maxIterations, type ChatMiddleware } from '@tanstack/ai'
|
|
373
|
+
|
|
374
|
+
function toolCallBudget(opts: {
|
|
375
|
+
max?: number
|
|
376
|
+
maxPerTurn?: number
|
|
377
|
+
}): ChatMiddleware {
|
|
378
|
+
let perTurn = 0
|
|
379
|
+
return {
|
|
380
|
+
onIteration: () => {
|
|
381
|
+
perTurn = 0
|
|
382
|
+
},
|
|
383
|
+
onToolPhaseComplete: () => {
|
|
384
|
+
perTurn = 0
|
|
385
|
+
},
|
|
386
|
+
onBeforeToolCall: () => {
|
|
387
|
+
if (opts.maxPerTurn == null) return undefined
|
|
388
|
+
if (++perTurn > opts.maxPerTurn) {
|
|
389
|
+
return {
|
|
390
|
+
type: 'skip',
|
|
391
|
+
result: {
|
|
392
|
+
error: `Skipped: exceeded maxToolCallsPerTurn (${opts.maxPerTurn})`,
|
|
393
|
+
},
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return undefined
|
|
397
|
+
},
|
|
398
|
+
onShouldContinue: (_ctx, state) =>
|
|
399
|
+
opts.max != null && state.toolCallCount >= opts.max ? false : undefined,
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
chat({
|
|
404
|
+
adapter,
|
|
405
|
+
messages,
|
|
406
|
+
tools: [weatherTool],
|
|
407
|
+
agentLoopStrategy: maxIterations(20),
|
|
408
|
+
middleware: [toolCallBudget({ maxPerTurn: 10, max: 20 })],
|
|
409
|
+
})
|
|
410
|
+
```
|
|
411
|
+
|
|
349
412
|
## Built-in: toolCacheMiddleware
|
|
350
413
|
|
|
351
414
|
Caches tool call results by name + arguments. Import from `@tanstack/ai/middlewares`:
|
|
@@ -372,6 +435,148 @@ Options: `maxSize` (default 100), `ttl` (default Infinity), `toolNames` (default
|
|
|
372
435
|
`keyFn` (custom cache key), `storage` (custom backend like Redis). See
|
|
373
436
|
`docs/advanced/middleware.md` for custom storage examples.
|
|
374
437
|
|
|
438
|
+
## Server State Persistence: withPersistence
|
|
439
|
+
|
|
440
|
+
`withPersistence(persistence)` (from `@tanstack/ai-persistence`) is a
|
|
441
|
+
`ChatMiddleware` that persists **state** for `chat()` — thread messages, run
|
|
442
|
+
records (status/timing/usage/errors), and interrupt state — to a backend store.
|
|
443
|
+
Add it to the `middleware` array like any other middleware. It never mutates the
|
|
444
|
+
chunk stream; replaying a dropped/reloaded _stream_ is a separate transport-layer
|
|
445
|
+
concern (see ai-core/chat-experience/SKILL.md resumability, not this middleware).
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import {
|
|
449
|
+
chat,
|
|
450
|
+
chatParamsFromRequest,
|
|
451
|
+
toServerSentEventsResponse,
|
|
452
|
+
} from '@tanstack/ai'
|
|
453
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
454
|
+
import { withPersistence, memoryPersistence } from '@tanstack/ai-persistence'
|
|
455
|
+
|
|
456
|
+
// memoryPersistence() is the in-process reference backend (dev/tests). For a
|
|
457
|
+
// durable one, implement the store contracts against your database — see the
|
|
458
|
+
// @tanstack/ai-persistence skills.
|
|
459
|
+
const persistence = memoryPersistence()
|
|
460
|
+
|
|
461
|
+
export async function POST(request: Request) {
|
|
462
|
+
const params = await chatParamsFromRequest(request)
|
|
463
|
+
|
|
464
|
+
const stream = chat({
|
|
465
|
+
adapter: openaiText('gpt-5.5'),
|
|
466
|
+
messages: params.messages,
|
|
467
|
+
threadId: params.threadId,
|
|
468
|
+
runId: params.runId,
|
|
469
|
+
...(params.resume ? { resume: params.resume } : {}),
|
|
470
|
+
middleware: [withPersistence(persistence)],
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
return toServerSentEventsResponse(stream)
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Authoritative-history contract
|
|
478
|
+
|
|
479
|
+
The middleware treats each request's `messages` as the source of truth for the
|
|
480
|
+
thread:
|
|
481
|
+
|
|
482
|
+
- **Non-empty `messages`** → on a successful finish (and at an interrupt
|
|
483
|
+
boundary) the middleware **overwrites** the entire stored thread with that
|
|
484
|
+
array. Post the **complete** transcript, never just the newest message(s) — a
|
|
485
|
+
delta would replace and destroy the stored history.
|
|
486
|
+
- **Empty `messages`** → the middleware **loads** the stored thread and runs the
|
|
487
|
+
turn from the server's copy. This is how you continue a conversation without
|
|
488
|
+
resending history from the client.
|
|
489
|
+
|
|
490
|
+
### Backends
|
|
491
|
+
|
|
492
|
+
`@tanstack/ai-persistence` ships **contracts, not a database backend**. It
|
|
493
|
+
provides the four store interfaces (`messages`, `runs`, `interrupts`,
|
|
494
|
+
`metadata`), the middleware that drives them, `memoryPersistence()` for
|
|
495
|
+
dev/tests, and a conformance testkit. For anything durable you implement the
|
|
496
|
+
stores against your own database and pass the result to `withPersistence`.
|
|
497
|
+
|
|
498
|
+
Annotate your factory with a named shape (`ChatPersistence` /
|
|
499
|
+
`ChatTranscriptPersistence`) — bare `AIPersistence` is the all-optional bag and
|
|
500
|
+
`withPersistence` rejects it.
|
|
501
|
+
|
|
502
|
+
Locks are separate from state and are **not** a `stores` key: wire a
|
|
503
|
+
`LockStore` with `withLocks(lockStore)`.
|
|
504
|
+
|
|
505
|
+
The `runs` store in that list is typed against `RunStore`, which ships in
|
|
506
|
+
`@tanstack/ai` alongside `RunRecord`, `RunStatus`, `TerminalRunStatus`,
|
|
507
|
+
`RunError`, `isTerminalRunStatus`, `defineRunStore`, and `InMemoryRunStore`. A
|
|
508
|
+
`RunRecord` tracks one run: `runId`, `threadId`, `status`, `startedAt`, plus
|
|
509
|
+
optional `finishedAt`, `error`, `usage`, `sandboxKey`, `detachedSince`,
|
|
510
|
+
`cancelRequested`, and `driverEpoch`. A backend must round-trip **all** of
|
|
511
|
+
them: `cancelRequested` is the durable out-of-band cancel channel
|
|
512
|
+
(`requestRunCancel` writes it, `wasCancelRequested` reads it), and
|
|
513
|
+
`driverEpoch` is the monotonic fencing token each host bumps when it claims a
|
|
514
|
+
run, so a superseded host can discover it lost. Omit either and a durable
|
|
515
|
+
sandboxed run loses a mechanism silently — Stop stops reaching a remote
|
|
516
|
+
driver, or nothing fences a dead host's writes.
|
|
517
|
+
`error` is a structured `RunError` (`{ message: string, code?: string }`), not
|
|
518
|
+
a bare string: `message` is the provider's prose, `code` is the stable,
|
|
519
|
+
machine-branchable classification a consumer switches on. Only
|
|
520
|
+
`createOrResume`, `update`, `get`, and `findActiveRun` are required on a
|
|
521
|
+
`RunStore`; `listByThread` and `listReclaimable` are optional, so a backend can
|
|
522
|
+
leave either out and callers feature-detect
|
|
523
|
+
(`store.listReclaimable?.(opts)`). Shape your own store with
|
|
524
|
+
`defineRunStore` for autocomplete without a separate `: RunStore` annotation,
|
|
525
|
+
matching `defineLock`; `defineRunStore<const T extends RunStore>(store: T): T`
|
|
526
|
+
returns the argument's own type, so an optional method your store implements
|
|
527
|
+
stays known-present on the result instead of collapsing to `| undefined`.
|
|
528
|
+
`isTerminalRunStatus(status)` is a type predicate narrowing `RunStatus` to
|
|
529
|
+
`TerminalRunStatus`, so code inside the guard can pass `status` where a
|
|
530
|
+
`TerminalRunStatus` is required without a cast. When a backend omits an
|
|
531
|
+
optional `RunStore` method, declare the omission when running the conformance
|
|
532
|
+
testkit (`ai-persistence/stores`'s `skipMethods` option) rather than leaving it
|
|
533
|
+
undeclared.
|
|
534
|
+
|
|
535
|
+
### `StreamDurability.snapshot()`
|
|
536
|
+
|
|
537
|
+
A `StreamDurability` (the event-log backend `memoryStream` / `durableStream`
|
|
538
|
+
implement, and what `@tanstack/ai-sandbox`'s run driver resolves per run — its
|
|
539
|
+
`RunDeps.durability` / `sandboxRunDriver({ durability })` is a factory
|
|
540
|
+
`(runId) => StreamDurability`, because one log is bound to one run) requires a
|
|
541
|
+
`snapshot()` method alongside `append`, `read`, and `close`:
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
snapshot: () => Promise<Array<{ offset: TOffset; chunk: StreamChunk }>>
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
It returns everything stored for a run right now, in append order, then
|
|
548
|
+
resolves. Use it, not `read()`, when a caller needs to inspect a run's stored
|
|
549
|
+
prefix and get an answer back: `read()` tails and only resolves once the log
|
|
550
|
+
is terminalized with `close()` or the caller aborts, so it never resolves
|
|
551
|
+
against a producer that crashed without calling `close()`, and its log stays
|
|
552
|
+
open indefinitely. `snapshot()` resolves immediately with what is stored,
|
|
553
|
+
including while the log is still open, and resolves to an empty array for a
|
|
554
|
+
run with nothing stored yet.
|
|
555
|
+
|
|
556
|
+
**Full guidance lives in the package's own skills** — start at
|
|
557
|
+
`node_modules/@tanstack/ai-persistence/skills/ai-persistence/SKILL.md`,
|
|
558
|
+
which routes to the server, client, stores, locks, and adapter-recipe
|
|
559
|
+
(Drizzle / Prisma / Cloudflare) sub-skills.
|
|
560
|
+
|
|
561
|
+
### Resume reconstruction is the middleware's job (server-authoritative path)
|
|
562
|
+
|
|
563
|
+
When a thread has pending interrupts, the middleware **records** them and
|
|
564
|
+
**gates** new input: a request that carries pending interrupts must include a
|
|
565
|
+
`resume` batch that references them, or `onConfig` throws. On a valid resume
|
|
566
|
+
batch the middleware also **builds `ChatResumeToolState`** (approvals /
|
|
567
|
+
client-tool results) and **clears `config.resume`** so the chat engine skips
|
|
568
|
+
its ephemeral reconstruction — that path needs client message history the
|
|
569
|
+
persistence flow deliberately omits when the server owns the transcript.
|
|
570
|
+
Resumes accepted in `onConfig` are committed (marked resolved/cancelled) only
|
|
571
|
+
once the run reaches a successful boundary, so a provider failure between
|
|
572
|
+
accepting a resume and finishing leaves the interrupt pending and a retry with
|
|
573
|
+
the same resume succeeds.
|
|
574
|
+
|
|
575
|
+
> A companion `withGenerationPersistence(persistence)` tracks run records for
|
|
576
|
+
> non-chat generation activities (image, audio, TTS, video, transcription).
|
|
577
|
+
|
|
578
|
+
Source: docs/persistence/overview.md
|
|
579
|
+
|
|
375
580
|
## Sandbox File-Event Hooks (`sandbox` group)
|
|
376
581
|
|
|
377
582
|
Declare a `sandbox: ChatSandboxHooks` group on `defineChatMiddleware` to react
|
|
@@ -507,35 +712,38 @@ has no effect on the stream output.
|
|
|
507
712
|
|
|
508
713
|
Source: docs/advanced/middleware.md
|
|
509
714
|
|
|
510
|
-
### b. MEDIUM: Middleware exceptions breaking the stream
|
|
715
|
+
### b. MEDIUM: Middleware exceptions breaking the stream — in `onChunk` / `onConfig`
|
|
716
|
+
|
|
717
|
+
Know which hooks the framework already guards. **The terminal hooks
|
|
718
|
+
(`onFinish`, `onAbort`, `onError`) are individually wrapped** by core's
|
|
719
|
+
`runTerminalHook`: a throw there is logged on the `errors` channel and the next
|
|
720
|
+
middleware's terminal hook still runs, so a failed analytics `POST` in `onFinish`
|
|
721
|
+
cannot break the stream or replace the abort reason. Guarding those is about
|
|
722
|
+
keeping your own bookkeeping intact, not about protecting the run.
|
|
723
|
+
|
|
724
|
+
**`onChunk` and `onConfig` are NOT guarded, deliberately** — they are transforms
|
|
725
|
+
on the data path, where swallowing a throw would forward a chunk or a config the
|
|
726
|
+
middleware had decided to reject. A throw from either fails the whole stream. That
|
|
727
|
+
is where an unhandled error actually costs you a response:
|
|
511
728
|
|
|
512
729
|
```typescript
|
|
513
|
-
// WRONG -- unhandled error kills the entire streaming response
|
|
730
|
+
// WRONG -- an unhandled error in onChunk kills the entire streaming response
|
|
514
731
|
const fragile: ChatMiddleware = {
|
|
515
|
-
name: 'fragile-
|
|
516
|
-
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
732
|
+
name: 'fragile-chunk-logger',
|
|
733
|
+
onChunk: (ctx, chunk) => {
|
|
734
|
+
// A logger that throws on an unexpected chunk shape takes the stream with it
|
|
735
|
+
logChunk(chunk)
|
|
736
|
+
},
|
|
737
|
+
onConfig: (ctx, config) => {
|
|
738
|
+
// Same for a config transform that reads an env var that is not set
|
|
739
|
+
return { model: requireEnv('MODEL_OVERRIDE') }
|
|
522
740
|
},
|
|
523
741
|
}
|
|
524
742
|
|
|
525
|
-
// CORRECT --
|
|
743
|
+
// CORRECT -- own the failure inside the unguarded hooks
|
|
526
744
|
const resilient: ChatMiddleware = {
|
|
527
|
-
name: 'resilient-
|
|
528
|
-
onFinish: (ctx, info) => {
|
|
529
|
-
// Option 1: defer (non-blocking, errors are isolated)
|
|
530
|
-
ctx.defer(
|
|
531
|
-
fetch('/api/analytics', {
|
|
532
|
-
method: 'POST',
|
|
533
|
-
body: JSON.stringify({ duration: info.duration }),
|
|
534
|
-
}),
|
|
535
|
-
)
|
|
536
|
-
},
|
|
745
|
+
name: 'resilient-chunk-logger',
|
|
537
746
|
onChunk: (ctx, chunk) => {
|
|
538
|
-
// Option 2: try-catch for synchronous/critical hooks
|
|
539
747
|
try {
|
|
540
748
|
logChunk(chunk)
|
|
541
749
|
} catch (err) {
|
|
@@ -543,17 +751,34 @@ const resilient: ChatMiddleware = {
|
|
|
543
751
|
}
|
|
544
752
|
// Return void to pass through
|
|
545
753
|
},
|
|
754
|
+
onConfig: (ctx, config) => {
|
|
755
|
+
const override = process.env.MODEL_OVERRIDE
|
|
756
|
+
// Decide, do not throw: no override means no transform.
|
|
757
|
+
return override === undefined ? undefined : { model: override }
|
|
758
|
+
},
|
|
759
|
+
onFinish: (ctx, info) => {
|
|
760
|
+
// Already guarded by core — but prefer ctx.defer() anyway, so a slow
|
|
761
|
+
// analytics call does not delay the terminal fan-out at all.
|
|
762
|
+
ctx.defer(
|
|
763
|
+
fetch('/api/analytics', {
|
|
764
|
+
method: 'POST',
|
|
765
|
+
body: JSON.stringify({ duration: info.duration }),
|
|
766
|
+
}),
|
|
767
|
+
)
|
|
768
|
+
},
|
|
546
769
|
}
|
|
547
770
|
```
|
|
548
771
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
772
|
+
Rule: put the try-catch where the framework has none — `onChunk` and `onConfig`
|
|
773
|
+
(and the other transform hooks: `onStructuredOutputConfig`, `onBeforeToolCall`,
|
|
774
|
+
`onAfterToolCall`). For async side effects in the terminal hooks, prefer
|
|
775
|
+
`ctx.defer()`, which runs after the terminal hook and isolates failures.
|
|
552
776
|
|
|
553
|
-
Source: docs/advanced/middleware.md
|
|
777
|
+
Source: docs/advanced/middleware.md, `packages/ai/src/activities/chat/middleware/compose.ts`
|
|
554
778
|
|
|
555
779
|
## Cross-References
|
|
556
780
|
|
|
557
781
|
- See also: **ai-core/chat-experience/SKILL.md** -- Middleware hooks into the chat lifecycle
|
|
558
782
|
- See also: **ai-core/structured-outputs/SKILL.md** -- Middleware now wraps the final structured-output call; use `onStructuredOutputConfig` for JSON-Schema transforms
|
|
559
783
|
- See also: **ai-core/ag-ui-protocol/SKILL.md** -- Reading the `sandbox.file` / `sandbox.file.diff` `CUSTOM` chunks the sandbox runtime emits alongside these `sandbox` hooks, via `ChatStream`'s typed `KnownCustomEvent` narrowing
|
|
784
|
+
- See also: **`@tanstack/ai-persistence` skills** (`skills/ai-persistence/SKILL.md` in that package) -- Full persistence suite (`withPersistence`, client storage, store contracts, adapter recipes, locks). This file only sketches server `withPersistence`.
|
|
@@ -13,7 +13,7 @@ description: >
|
|
|
13
13
|
part. convertSchemaToJsonSchema() for manual schema conversion.
|
|
14
14
|
type: sub-skill
|
|
15
15
|
library: tanstack-ai
|
|
16
|
-
library_version: '0.
|
|
16
|
+
library_version: '0.42.0'
|
|
17
17
|
sources:
|
|
18
18
|
- 'TanStack/ai:docs/structured-outputs/overview.md'
|
|
19
19
|
- 'TanStack/ai:docs/structured-outputs/one-shot.md'
|
|
@@ -4,12 +4,12 @@ description: >
|
|
|
4
4
|
Isomorphic tool system: toolDefinition() with Zod schemas,
|
|
5
5
|
.server() and .client() implementations, passing tools to both
|
|
6
6
|
chat() on server and useChat/clientTools on client, tool approval
|
|
7
|
-
flows with needsApproval and
|
|
7
|
+
flows with needsApproval and bound interrupts (resolveInterrupt), lazy tool
|
|
8
8
|
discovery with lazy:true, rendering ToolCallPart and ToolResultPart
|
|
9
9
|
in UI.
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: tanstack-ai
|
|
12
|
-
library_version: '0.
|
|
12
|
+
library_version: '0.42.0'
|
|
13
13
|
sources:
|
|
14
14
|
- 'TanStack/ai:docs/tools/tools.md'
|
|
15
15
|
- 'TanStack/ai:docs/tools/server-tools.md'
|
|
@@ -108,14 +108,14 @@ function ChatPage() {
|
|
|
108
108
|
connection: fetchServerSentEvents("/api/chat"),
|
|
109
109
|
tools,
|
|
110
110
|
});
|
|
111
|
-
type Messages = InferChatMessages<typeof chatOptions>;
|
|
112
|
-
|
|
113
111
|
const { messages, sendMessage } = useChat(chatOptions);
|
|
112
|
+
// InferChatMessages ties part types to the configured tools when needed:
|
|
113
|
+
// type Messages = InferChatMessages<typeof chatOptions>
|
|
114
114
|
|
|
115
115
|
return (
|
|
116
116
|
<div>
|
|
117
117
|
<span>Cart: {cartCount}</span>
|
|
118
|
-
{
|
|
118
|
+
{messages.map((msg) => (
|
|
119
119
|
<div key={msg.id}>
|
|
120
120
|
{msg.parts.map((part) => {
|
|
121
121
|
if (part.type === "text") return <p>{part.content}</p>;
|
|
@@ -239,9 +239,11 @@ function ChatPage() {
|
|
|
239
239
|
|
|
240
240
|
### Pattern 3: Tool with Approval Flow
|
|
241
241
|
|
|
242
|
-
Set `needsApproval: true` in the definition. Execution pauses
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
Set `needsApproval: true` in the definition. Execution pauses with
|
|
243
|
+
`RUN_FINISHED.outcome.type === 'interrupt'`. The primary client API is bound
|
|
244
|
+
`interrupts` + `resolveInterrupt` / `resolveInterrupts` / `cancel`.
|
|
245
|
+
`addToolApprovalResponse` and `pendingInterrupts` remain as deprecated
|
|
246
|
+
compatibility shims during migration.
|
|
245
247
|
|
|
246
248
|
```typescript
|
|
247
249
|
import { toolDefinition } from '@tanstack/ai'
|
|
@@ -265,59 +267,40 @@ export const sendEmail = sendEmailDef.server(async ({ to, subject, body }) => {
|
|
|
265
267
|
})
|
|
266
268
|
```
|
|
267
269
|
|
|
268
|
-
|
|
270
|
+
Server route must forward `resume` / `parentRunId` (via `chatParamsFromRequest`
|
|
271
|
+
or equivalent). Client -- render bound interrupts:
|
|
269
272
|
|
|
270
273
|
```typescript
|
|
271
274
|
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
|
|
272
275
|
|
|
273
276
|
function ChatPage() {
|
|
274
|
-
const { messages,
|
|
277
|
+
const { messages, interrupts, sendMessage } = useChat({
|
|
275
278
|
connection: fetchServerSentEvents("/api/chat"),
|
|
276
279
|
});
|
|
277
280
|
|
|
278
281
|
return (
|
|
279
282
|
<div>
|
|
283
|
+
{interrupts.map((interrupt) => {
|
|
284
|
+
if (interrupt.kind !== "tool-approval") return null;
|
|
285
|
+
return (
|
|
286
|
+
<div key={interrupt.id}>
|
|
287
|
+
<p>Approve "{interrupt.toolName}"?</p>
|
|
288
|
+
<pre>{JSON.stringify(interrupt.originalArgs, null, 2)}</pre>
|
|
289
|
+
<button onClick={() => interrupt.resolveInterrupt(true)}>
|
|
290
|
+
Approve
|
|
291
|
+
</button>
|
|
292
|
+
<button onClick={() => interrupt.resolveInterrupt(false)}>
|
|
293
|
+
Deny
|
|
294
|
+
</button>
|
|
295
|
+
<button onClick={() => interrupt.cancel()}>Cancel</button>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
})}
|
|
280
299
|
{messages.map((msg) => (
|
|
281
300
|
<div key={msg.id}>
|
|
282
|
-
{msg.parts.map((part) =>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
part.type === "tool-call" &&
|
|
286
|
-
part.state === "approval-requested" &&
|
|
287
|
-
part.approval
|
|
288
|
-
) {
|
|
289
|
-
return (
|
|
290
|
-
<div key={part.id}>
|
|
291
|
-
<p>Approve "{part.name}"?</p>
|
|
292
|
-
{/* `part.input` is the parsed, typed object (populated once
|
|
293
|
-
the arguments are complete, as they are at approval
|
|
294
|
-
time); `part.arguments` remains the raw JSON string. */}
|
|
295
|
-
<pre>{JSON.stringify(part.input, null, 2)}</pre>
|
|
296
|
-
<button
|
|
297
|
-
onClick={() =>
|
|
298
|
-
addToolApprovalResponse({
|
|
299
|
-
id: part.approval!.id,
|
|
300
|
-
approved: true,
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
>
|
|
304
|
-
Approve
|
|
305
|
-
</button>
|
|
306
|
-
<button
|
|
307
|
-
onClick={() =>
|
|
308
|
-
addToolApprovalResponse({
|
|
309
|
-
id: part.approval!.id,
|
|
310
|
-
approved: false,
|
|
311
|
-
})
|
|
312
|
-
}
|
|
313
|
-
>
|
|
314
|
-
Deny
|
|
315
|
-
</button>
|
|
316
|
-
</div>
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
return null;
|
|
320
|
-
})}
|
|
301
|
+
{msg.parts.map((part) =>
|
|
302
|
+
part.type === "text" ? <p key={part.content}>{part.content}</p> : null
|
|
303
|
+
)}
|
|
321
304
|
</div>
|
|
322
305
|
))}
|
|
323
306
|
</div>
|
|
@@ -325,14 +308,24 @@ function ChatPage() {
|
|
|
325
308
|
}
|
|
326
309
|
```
|
|
327
310
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
311
|
+
Batch all pending approvals with `resolveInterrupts` (void — submission is
|
|
312
|
+
async; watch `resuming` / `interruptErrors`):
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// Payloadless tool-approvals only
|
|
316
|
+
resolveInterrupts(true)
|
|
317
|
+
|
|
318
|
+
// Or per-item:
|
|
319
|
+
resolveInterrupts((interrupt) => {
|
|
320
|
+
if (interrupt.kind === 'tool-approval') {
|
|
321
|
+
interrupt.resolveInterrupt(true)
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Migration: `pendingInterrupts` aliases `interrupts`; `addToolApprovalResponse`
|
|
327
|
+
forwards to the matching bound approval when present. Prefer the bound methods
|
|
328
|
+
above for new code. See `docs/interrupts/`.
|
|
336
329
|
|
|
337
330
|
### Pattern 4: Lazy Tool Discovery
|
|
338
331
|
|
|
@@ -375,6 +368,8 @@ export async function POST(request: Request) {
|
|
|
375
368
|
adapter: openaiText('gpt-5.5'),
|
|
376
369
|
messages,
|
|
377
370
|
tools: [getProducts, compareProducts],
|
|
371
|
+
// maxIterations bounds model turns, not tool calls. For tool budgets,
|
|
372
|
+
// use middleware onBeforeToolCall + onShouldContinue (see agentic-cycle docs).
|
|
378
373
|
agentLoopStrategy: maxIterations(20),
|
|
379
374
|
})
|
|
380
375
|
return toServerSentEventsResponse(stream)
|
|
@@ -647,7 +642,7 @@ import { anthropicText } from '@tanstack/ai-anthropic'
|
|
|
647
642
|
export async function POST(request: Request) {
|
|
648
643
|
const { messages } = await request.json()
|
|
649
644
|
const stream = chat({
|
|
650
|
-
adapter: anthropicText('claude-sonnet-4-
|
|
645
|
+
adapter: anthropicText('claude-sonnet-4-6'),
|
|
651
646
|
messages,
|
|
652
647
|
tools: [
|
|
653
648
|
codeExecutionTool(
|
|
@@ -683,7 +678,7 @@ import { openaiText } from '@tanstack/ai-openai'
|
|
|
683
678
|
export async function POST(request: Request) {
|
|
684
679
|
const { messages } = await request.json()
|
|
685
680
|
const stream = chat({
|
|
686
|
-
adapter: openaiText('gpt-5.
|
|
681
|
+
adapter: openaiText('gpt-5.5'),
|
|
687
682
|
messages,
|
|
688
683
|
tools: [
|
|
689
684
|
shellTool({
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { AgentLoopStrategy } from '../../types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Creates a strategy that continues for a maximum number of
|
|
4
|
+
* Creates a strategy that continues for a maximum number of **model turns**
|
|
5
|
+
* (iterations), not tool calls.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* One iteration can still emit many parallel tool calls. For a tool-call
|
|
8
|
+
* budget, use middleware with `onBeforeToolCall` (per-turn cap) and
|
|
9
|
+
* `onShouldContinue` (cumulative run budget) — see the docs recipe under
|
|
10
|
+
* Agentic Cycle.
|
|
11
|
+
*
|
|
12
|
+
* @param max - Maximum number of model turns to allow
|
|
7
13
|
* @returns AgentLoopStrategy that stops after max iterations
|
|
8
14
|
*
|
|
9
15
|
* @example
|
|
@@ -13,7 +19,7 @@ import type { AgentLoopStrategy } from '../../types'
|
|
|
13
19
|
* model: "gpt-4o",
|
|
14
20
|
* messages: [...],
|
|
15
21
|
* tools: [weatherTool],
|
|
16
|
-
* agentLoopStrategy: maxIterations(3), // Max 3
|
|
22
|
+
* agentLoopStrategy: maxIterations(3), // Max 3 model turns
|
|
17
23
|
* });
|
|
18
24
|
* ```
|
|
19
25
|
*/
|
|
@@ -60,7 +66,7 @@ export function untilFinishReason(
|
|
|
60
66
|
* All strategies must return true to continue
|
|
61
67
|
*
|
|
62
68
|
* @param strategies - Array of strategies to combine
|
|
63
|
-
* @returns AgentLoopStrategy that continues only if all strategies
|
|
69
|
+
* @returns AgentLoopStrategy that continues only if all strategies agree
|
|
64
70
|
*
|
|
65
71
|
* @example
|
|
66
72
|
* ```typescript
|