@tanstack/ai 0.20.1 → 0.21.1
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.d.ts +1 -1
- package/dist/esm/activities/chat/index.js +357 -258
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -1
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +35 -3
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +12 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +13 -4
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/otel.js +1 -4
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/utilities/ag-ui-wire.js +4 -1
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/middleware/SKILL.md +124 -18
- package/skills/ai-core/structured-outputs/SKILL.md +13 -0
- package/src/activities/chat/index.ts +653 -370
- package/src/activities/chat/messages.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +65 -5
- package/src/activities/chat/middleware/index.ts +1 -0
- package/src/activities/chat/middleware/types.ts +53 -2
- package/src/activities/chat/stream/processor.ts +2 -2
- package/src/activities/chat/tools/schema-converter.ts +22 -7
- package/src/activities/generateImage/index.ts +3 -3
- package/src/extend-adapter.ts +1 -1
- package/src/index.ts +5 -1
- package/src/middlewares/content-guard.ts +1 -1
- package/src/middlewares/otel.ts +7 -10
- package/src/strip-to-spec-middleware.ts +1 -4
- package/src/utilities/ag-ui-wire.ts +2 -1
|
@@ -31,11 +31,11 @@ const stream = chat({
|
|
|
31
31
|
onStart: (ctx) => {
|
|
32
32
|
console.log('Chat started:', ctx.model)
|
|
33
33
|
},
|
|
34
|
-
onFinish: (ctx) => {
|
|
35
|
-
trackAnalytics({ model: ctx.model, tokens:
|
|
34
|
+
onFinish: (ctx, info) => {
|
|
35
|
+
trackAnalytics({ model: ctx.model, tokens: info.usage?.totalTokens })
|
|
36
36
|
},
|
|
37
|
-
onError: (ctx) => {
|
|
38
|
-
reportError(
|
|
37
|
+
onError: (ctx, info) => {
|
|
38
|
+
reportError(info.error)
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
41
|
],
|
|
@@ -50,23 +50,82 @@ Every hook receives a `ChatMiddlewareContext` as its first argument, which provi
|
|
|
50
50
|
`requestId`, `streamId`, `phase`, `iteration`, `chunkIndex`, `model`, `provider`,
|
|
51
51
|
`signal`, `abort()`, `defer()`, and more.
|
|
52
52
|
|
|
53
|
-
| Hook
|
|
54
|
-
|
|
|
55
|
-
| `onConfig`
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
53
|
+
| Hook | When | Second Argument |
|
|
54
|
+
| -------------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
55
|
+
| `onConfig` | Once at startup (`init`) + once per iteration (`beforeModel`) + once at structured-output boundary | `ChatMiddlewareConfig` (return partial to merge) |
|
|
56
|
+
| `onStructuredOutputConfig` | Once at the structured-output boundary (only when `chat({ outputSchema })`) | `StructuredOutputMiddlewareConfig` (return partial) |
|
|
57
|
+
| `onStart` | Once after initial `onConfig` | none |
|
|
58
|
+
| `onIteration` | Start of each agent loop iteration | `IterationInfo` |
|
|
59
|
+
| `onChunk` | Every streamed chunk | `StreamChunk` (return void/chunk/chunk[]/null) |
|
|
60
|
+
| `onBeforeToolCall` | Before each tool executes | `ToolCallHookContext` (return decision or void) |
|
|
61
|
+
| `onAfterToolCall` | After each tool executes | `AfterToolCallInfo` |
|
|
62
|
+
| `onToolPhaseComplete` | After all tool calls in an iteration | `ToolPhaseCompleteInfo` |
|
|
63
|
+
| `onUsage` | When `RUN_FINISHED` includes usage data | `UsageInfo` |
|
|
64
|
+
| `onFinish` | Run completed normally | `FinishInfo` |
|
|
65
|
+
| `onAbort` | Run was aborted | `AbortInfo` |
|
|
66
|
+
| `onError` | Unhandled error occurred | `ErrorInfo` |
|
|
66
67
|
|
|
67
68
|
Terminal hooks (`onFinish`, `onAbort`, `onError`) are **mutually exclusive** -- exactly
|
|
68
69
|
one fires per `chat()` invocation.
|
|
69
70
|
|
|
71
|
+
### Phase values
|
|
72
|
+
|
|
73
|
+
`ctx.phase` is one of:
|
|
74
|
+
|
|
75
|
+
| Phase | When |
|
|
76
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
77
|
+
| `'init'` | Initial setup (before the first `onConfig` snapshot is built). |
|
|
78
|
+
| `'beforeModel'` | Right before each agent-loop adapter call (`onConfig` re-fires here). |
|
|
79
|
+
| `'modelStream'` | During model streaming chunks within the agent loop. |
|
|
80
|
+
| `'beforeTools'` | Before tool execution phase. |
|
|
81
|
+
| `'afterTools'` | After tool execution phase. |
|
|
82
|
+
| `'structuredOutput'` | During the final structured-output adapter call (set for all chunks from `adapter.structuredOutputStream` or the synthesized fallback). Triggered only when `chat({ outputSchema })` is invoked; one phase transition per `chat()` invocation. |
|
|
83
|
+
|
|
84
|
+
**Structured-output lifecycle rules** (when `chat({ outputSchema })` is used):
|
|
85
|
+
|
|
86
|
+
- `onStructuredOutputConfig` fires **before** `onConfig` at the structured-output boundary.
|
|
87
|
+
- `onConfig` re-fires at the same boundary with `ctx.phase === 'structuredOutput'`, receiving the post-`onStructuredOutputConfig` view of the config (minus `outputSchema`).
|
|
88
|
+
- `onChunk` and `onUsage` fire for every chunk and usage event emitted by the structured-output call, with `ctx.phase === 'structuredOutput'`.
|
|
89
|
+
- `onIteration` does **not** fire for finalization — it is agent-loop-only.
|
|
90
|
+
- `onFinish` fires once at the end of the whole `chat()` invocation, **after** the structured-output finalization completes (not after the agent loop). Terminal-hook exclusivity still holds (one of `onFinish` / `onAbort` / `onError`).
|
|
91
|
+
- **Terminal `info` and structured-output:** `info.usage` / `info.finishReason` / `info.content` reflect the **agent loop's** terminal state, NOT the finalization step. Finalization state is intentionally segregated to keep agent-loop semantics clean. For a tools-less `chat({ outputSchema })` run, `info.usage` is `undefined` and `info.finishReason` is `null` (no agent-loop iteration produced `RUN_FINISHED`). To capture finalization tokens, use `onUsage` — it fires for both agent-loop iterations and the final call. For the structured-output result itself, observe the `structured-output.complete` CUSTOM event in `onChunk`.
|
|
92
|
+
|
|
93
|
+
## onStructuredOutputConfig
|
|
94
|
+
|
|
95
|
+
A dedicated config hook that fires **only** at the structured-output boundary
|
|
96
|
+
(when `chat({ outputSchema })` is invoked). Use it to transform the JSON Schema
|
|
97
|
+
sent to the provider (inject `$defs`, strip vendor-incompatible keywords) or to
|
|
98
|
+
apply structured-output-specific config changes that should not affect the
|
|
99
|
+
agent-loop adapter calls.
|
|
100
|
+
|
|
101
|
+
**Signature:**
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
onStructuredOutputConfig?: (
|
|
105
|
+
ctx: ChatMiddlewareContext,
|
|
106
|
+
config: StructuredOutputMiddlewareConfig,
|
|
107
|
+
) =>
|
|
108
|
+
| void
|
|
109
|
+
| null
|
|
110
|
+
| Partial<StructuredOutputMiddlewareConfig>
|
|
111
|
+
| Promise<void | Partial<StructuredOutputMiddlewareConfig>>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**`StructuredOutputMiddlewareConfig` shape:**
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
interface StructuredOutputMiddlewareConfig extends ChatMiddlewareConfig {
|
|
118
|
+
outputSchema: JSONSchema // The JSON Schema being sent to the provider
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Ordering rule:**
|
|
123
|
+
|
|
124
|
+
- `onStructuredOutputConfig` fires **before** `onConfig` at the structured-output boundary.
|
|
125
|
+
- `onConfig` re-fires at the same boundary with `ctx.phase === 'structuredOutput'`, receiving the post-`onStructuredOutputConfig` view of the config (minus `outputSchema`).
|
|
126
|
+
- Use `onConfig` for general-purpose transforms that apply to every adapter call (agent-loop iterations and the final structured-output call).
|
|
127
|
+
- Use `onStructuredOutputConfig` when you need to transform the JSON Schema or apply structured-output-specific behavior.
|
|
128
|
+
|
|
70
129
|
## Core Patterns
|
|
71
130
|
|
|
72
131
|
### Pattern 1: Analytics and Logging Middleware
|
|
@@ -173,7 +232,52 @@ const toolGuard: ChatMiddleware = {
|
|
|
173
232
|
| `{ type: 'skip', result }` | Skip execution, use provided result (used by `toolCacheMiddleware`) |
|
|
174
233
|
| `{ type: 'abort', reason? }` | Abort the entire chat run |
|
|
175
234
|
|
|
176
|
-
### Pattern 3:
|
|
235
|
+
### Pattern 3: Structured-Output Middleware
|
|
236
|
+
|
|
237
|
+
When `chat({ outputSchema })` is used, the final structured-output adapter call
|
|
238
|
+
now flows through the same middleware chain as the agent loop (with
|
|
239
|
+
`ctx.phase === 'structuredOutput'`). Before this change, the final call bypassed
|
|
240
|
+
middleware entirely — `onChunk`, `onUsage`, `onConfig`, and terminal hooks did
|
|
241
|
+
not see it.
|
|
242
|
+
|
|
243
|
+
**Example A — Observability (tracing every chunk, including finalization):**
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import type { ChatMiddleware } from '@tanstack/ai'
|
|
247
|
+
|
|
248
|
+
const tracing: ChatMiddleware = {
|
|
249
|
+
name: 'tracing',
|
|
250
|
+
onChunk(ctx, chunk) {
|
|
251
|
+
span.addEvent('chunk', { phase: ctx.phase, type: chunk.type })
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
This middleware now observes every chunk from the final structured-output call,
|
|
257
|
+
attributed to `ctx.phase === 'structuredOutput'`. Before the fix, the final
|
|
258
|
+
adapter call bypassed middleware entirely — `tracing` would only see agent-loop
|
|
259
|
+
chunks.
|
|
260
|
+
|
|
261
|
+
**Example B — Schema rewriting (inject shared `$defs`):**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import type { ChatMiddleware } from '@tanstack/ai'
|
|
265
|
+
|
|
266
|
+
const injectDefs: ChatMiddleware = {
|
|
267
|
+
name: 'inject-defs',
|
|
268
|
+
onStructuredOutputConfig(_ctx, config) {
|
|
269
|
+
return {
|
|
270
|
+
outputSchema: { ...config.outputSchema, $defs: { ...sharedDefs } },
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`onStructuredOutputConfig` is the right hook here because it has direct access
|
|
277
|
+
to `config.outputSchema` and runs only on the structured-output boundary —
|
|
278
|
+
schema rewrites do not leak into the agent-loop adapter calls.
|
|
279
|
+
|
|
280
|
+
### Pattern 4: Multiple Middleware Composition
|
|
177
281
|
|
|
178
282
|
Middleware executes in array order (left-to-right). Ordering matters for hooks that
|
|
179
283
|
pipe or short-circuit:
|
|
@@ -222,6 +326,7 @@ const stream = chat({
|
|
|
222
326
|
| Hook | Composition | Effect of Order |
|
|
223
327
|
| -------------------------- | --------------------------------------------- | ------------------------------------------ |
|
|
224
328
|
| `onConfig` | **Piped** -- each receives previous output | Earlier middleware transforms first |
|
|
329
|
+
| `onStructuredOutputConfig` | **Piped** -- each receives previous output | Earlier middleware transforms first |
|
|
225
330
|
| `onStart` | Sequential | All run in order |
|
|
226
331
|
| `onChunk` | **Piped** -- chunks flow through each | If first drops a chunk, later never see it |
|
|
227
332
|
| `onBeforeToolCall` | **First-win** -- first non-void decision wins | Earlier middleware has priority |
|
|
@@ -334,3 +439,4 @@ Source: docs/advanced/middleware.md
|
|
|
334
439
|
## Cross-References
|
|
335
440
|
|
|
336
441
|
- See also: **ai-core/chat-experience/SKILL.md** -- Middleware hooks into the chat lifecycle
|
|
442
|
+
- See also: **ai-core/structured-outputs/SKILL.md** -- Middleware now wraps the final structured-output call; use `onStructuredOutputConfig` for JSON-Schema transforms
|
|
@@ -480,8 +480,21 @@ already installed.
|
|
|
480
480
|
|
|
481
481
|
Source: maintainer interview
|
|
482
482
|
|
|
483
|
+
## Middleware coverage
|
|
484
|
+
|
|
485
|
+
The final structured-output adapter call runs through the same middleware
|
|
486
|
+
pipeline as the agent loop. `onChunk` observes chunks attributed to
|
|
487
|
+
`ctx.phase === 'structuredOutput'`; `onUsage` fires for the final call's
|
|
488
|
+
tokens; `onFinish` fires once at the end of the whole `chat()` invocation,
|
|
489
|
+
after the structured-output result is available.
|
|
490
|
+
|
|
491
|
+
For schema-aware middleware (e.g., transforming the JSON Schema before the
|
|
492
|
+
provider call, stripping system prompts), use the dedicated
|
|
493
|
+
`onStructuredOutputConfig` hook. See [middleware skill](../middleware/SKILL.md).
|
|
494
|
+
|
|
483
495
|
## Cross-References
|
|
484
496
|
|
|
485
497
|
- See also: **ai-core/chat-experience/SKILL.md** — Base `useChat` surface; the structured-output additions documented here layer on top.
|
|
486
498
|
- See also: **ai-core/adapter-configuration/SKILL.md** — Adapter handles structured-output strategy transparently.
|
|
487
499
|
- See also: **ai-core/tool-calling/SKILL.md** — Combine `tools` with `outputSchema` for an agent loop that runs tools first and returns a typed object. Tool-approval and client-tool flows compose with structured runs without extra wiring; see [docs/structured-outputs/with-tools.md](https://github.com/TanStack/ai/blob/main/docs/structured-outputs/with-tools.md).
|
|
500
|
+
- See also: **ai-core/middleware/SKILL.md** — `onStructuredOutputConfig` hook and the `structuredOutput` phase for observing/transforming the final structured-output call.
|