@tanstack/ai 0.47.2 → 0.48.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 +2 -1
- package/dist/esm/activities/chat/adapter.d.ts +5 -4
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +4 -7
- package/dist/esm/activities/chat/index.js +201 -243
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +126 -31
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +38 -17
- package/dist/esm/activities/chat/stream/processor.js +197 -110
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +3 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +15 -10
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +6 -6
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +7 -8
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +59 -54
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -0
- package/dist/esm/adapter-internals.js +3 -1
- package/dist/esm/client.d.ts +8 -1
- package/dist/esm/client.js +7 -2
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +6 -2
- package/dist/esm/middlewares/otel.js +12 -5
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/stream-to-response.js +13 -6
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/stream-to-websocket.js +4 -2
- package/dist/esm/stream-to-websocket.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.d.ts +10 -13
- package/dist/esm/strip-to-spec-middleware.js +24 -22
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/types.d.ts +82 -305
- package/dist/esm/utilities/adapter-yield-chunk.d.ts +31 -0
- package/dist/esm/utilities/ag-ui-usage.d.ts +24 -0
- package/dist/esm/utilities/ag-ui-usage.js +66 -0
- package/dist/esm/utilities/ag-ui-usage.js.map +1 -0
- package/dist/esm/utilities/ag-ui-wire.d.ts +14 -7
- package/dist/esm/utilities/ag-ui-wire.js +71 -30
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +3 -3
- package/dist/esm/utilities/chat-params.js +10 -30
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/chunk-ids.d.ts +5 -0
- package/dist/esm/utilities/chunk-ids.js +25 -0
- package/dist/esm/utilities/chunk-ids.js.map +1 -0
- package/dist/esm/utilities/errors.d.ts +9 -0
- package/dist/esm/utilities/errors.js +13 -1
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/merge-metadata.d.ts +14 -0
- package/dist/esm/utilities/merge-metadata.js +43 -0
- package/dist/esm/utilities/merge-metadata.js.map +1 -0
- package/dist/esm/utilities/normalize-stream-chunk.d.ts +3 -0
- package/dist/esm/utilities/normalize-stream-chunk.js +100 -0
- package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -0
- package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
- package/dist/esm/utilities/reasoning-encrypted-value.js +16 -0
- package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -0
- package/dist/esm/utilities/restore-inbound-chunk.d.ts +15 -0
- package/dist/esm/utilities/restore-inbound-chunk.js +43 -0
- package/dist/esm/utilities/restore-inbound-chunk.js.map +1 -0
- package/dist/esm/utilities/spec-event-keys.d.ts +2 -0
- package/dist/esm/utilities/spec-event-keys.js +56 -0
- package/dist/esm/utilities/spec-event-keys.js.map +1 -0
- package/dist/esm/utilities/structured-output-events.d.ts +3 -3
- package/dist/esm/utilities/structured-output-events.js.map +1 -1
- package/package.json +3 -3
- package/src/activities/chat/adapter.ts +4 -4
- package/src/activities/chat/index.ts +398 -400
- package/src/activities/chat/messages.ts +183 -36
- package/src/activities/chat/stream/processor.ts +313 -239
- package/src/activities/chat/tools/tool-calls.ts +23 -22
- package/src/activities/generateVideo/index.ts +7 -6
- package/src/activities/stream-generation-result.ts +8 -12
- package/src/activities/summarize/chat-stream-summarize.ts +98 -70
- package/src/adapter-internals.ts +2 -0
- package/src/client.ts +13 -0
- package/src/index.ts +6 -0
- package/src/middlewares/otel.ts +16 -8
- package/src/stream-to-response.ts +16 -4
- package/src/stream-to-websocket.ts +3 -1
- package/src/strip-to-spec-middleware.ts +48 -24
- package/src/types.ts +109 -393
- package/src/utilities/adapter-yield-chunk.ts +30 -0
- package/src/utilities/ag-ui-usage.test.ts +194 -0
- package/src/utilities/ag-ui-usage.ts +148 -0
- package/src/utilities/ag-ui-wire.ts +149 -31
- package/src/utilities/chat-params.ts +22 -39
- package/src/utilities/chunk-ids.ts +24 -0
- package/src/utilities/errors.ts +23 -0
- package/src/utilities/merge-metadata.test.ts +117 -0
- package/src/utilities/merge-metadata.ts +59 -0
- package/src/utilities/normalize-stream-chunk.test.ts +423 -0
- package/src/utilities/normalize-stream-chunk.ts +186 -0
- package/src/utilities/reasoning-encrypted-value.ts +18 -0
- package/src/utilities/restore-inbound-chunk.test.ts +133 -0
- package/src/utilities/restore-inbound-chunk.ts +72 -0
- package/src/utilities/spec-event-keys.test.ts +34 -0
- package/src/utilities/spec-event-keys.ts +74 -0
- package/src/utilities/structured-output-events.ts +3 -3
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { normalizeToolResult } from '../../../utilities/tool-result'
|
|
2
|
+
import { tanstackMetadata } from '../../../utilities/merge-metadata'
|
|
3
|
+
import type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'
|
|
2
4
|
import { isStandardSchema, parseWithStandardSchema } from './schema-converter'
|
|
3
5
|
import type { ToolApprovalResolution } from '../../../interrupts'
|
|
4
6
|
import type {
|
|
@@ -231,10 +233,8 @@ export class ToolCallManager<
|
|
|
231
233
|
* Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)
|
|
232
234
|
*/
|
|
233
235
|
addToolCallStartEvent(event: ToolCallStartEvent): void {
|
|
234
|
-
const index = event.index ?? this.toolCallsMap.size
|
|
235
|
-
const
|
|
236
|
-
Pick<ToolCallStartEvent, 'toolName'>
|
|
237
|
-
const name = runtimeEvent.toolCallName ?? runtimeEvent.toolName
|
|
236
|
+
const index = (event as AdapterYieldChunk).index ?? this.toolCallsMap.size
|
|
237
|
+
const name = event.toolCallName ?? event.toolName
|
|
238
238
|
this.toolCallsMap.set(index, {
|
|
239
239
|
id: event.toolCallId,
|
|
240
240
|
type: 'function',
|
|
@@ -250,10 +250,14 @@ export class ToolCallManager<
|
|
|
250
250
|
* Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)
|
|
251
251
|
*/
|
|
252
252
|
addToolCallArgsEvent(event: ToolCallArgsEvent): void {
|
|
253
|
-
|
|
253
|
+
const extra = event as AdapterYieldChunk
|
|
254
254
|
for (const [, toolCall] of this.toolCallsMap.entries()) {
|
|
255
255
|
if (toolCall.id === event.toolCallId) {
|
|
256
|
-
|
|
256
|
+
if (typeof extra.args === 'string' && extra.args !== '') {
|
|
257
|
+
toolCall.function.arguments = extra.args
|
|
258
|
+
} else {
|
|
259
|
+
toolCall.function.arguments += event.delta
|
|
260
|
+
}
|
|
257
261
|
break
|
|
258
262
|
}
|
|
259
263
|
}
|
|
@@ -264,16 +268,13 @@ export class ToolCallManager<
|
|
|
264
268
|
* Called when TOOL_CALL_END is received
|
|
265
269
|
*/
|
|
266
270
|
completeToolCall(event: ToolCallEndEvent): void {
|
|
267
|
-
for (const
|
|
268
|
-
if (toolCall.id
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
break
|
|
276
|
-
}
|
|
271
|
+
for (const toolCall of this.toolCallsMap.values()) {
|
|
272
|
+
if (toolCall.id !== event.toolCallId) continue
|
|
273
|
+
if (event.input === undefined) return
|
|
274
|
+
const normalized =
|
|
275
|
+
event.input && typeof event.input === 'object' ? event.input : {}
|
|
276
|
+
toolCall.function.arguments = JSON.stringify(normalized)
|
|
277
|
+
return
|
|
277
278
|
}
|
|
278
279
|
}
|
|
279
280
|
|
|
@@ -301,7 +302,7 @@ export class ToolCallManager<
|
|
|
301
302
|
async *executeTools(
|
|
302
303
|
finishEvent: RunFinishedEvent,
|
|
303
304
|
...contextArgs: ExecuteToolsContextArgs<TContext>
|
|
304
|
-
): AsyncGenerator<
|
|
305
|
+
): AsyncGenerator<AdapterYieldChunk, Array<ModelMessage>, void> {
|
|
305
306
|
const toolCallsArray = this.getToolCalls()
|
|
306
307
|
const toolResults: Array<ModelMessage> = []
|
|
307
308
|
const hasRuntimeContext = contextArgs.length > 0
|
|
@@ -313,9 +314,6 @@ export class ToolCallManager<
|
|
|
313
314
|
let toolResultContent: string | Array<ContentPart>
|
|
314
315
|
let toolResultState: ToolOutputState | undefined
|
|
315
316
|
// Holds the parsed/validated execution output before serialization.
|
|
316
|
-
// Surfaced on the emitted `TOOL_CALL_END` event as `output` so
|
|
317
|
-
// consumers can read it typed (via `TypedStreamChunk` distribution
|
|
318
|
-
// over the tools array) without re-parsing `result`.
|
|
319
317
|
// Stays `undefined` when the tool has no `execute` (client-only
|
|
320
318
|
// tools) or when execution throws.
|
|
321
319
|
let toolOutput: unknown
|
|
@@ -397,7 +395,10 @@ export class ToolCallManager<
|
|
|
397
395
|
toolCallId: toolCall.id,
|
|
398
396
|
toolCallName: toolCall.function.name,
|
|
399
397
|
toolName: toolCall.function.name,
|
|
400
|
-
model:
|
|
398
|
+
model: (() => {
|
|
399
|
+
const model = tanstackMetadata(finishEvent)?.model
|
|
400
|
+
return typeof model === 'string' ? model : undefined
|
|
401
|
+
})(),
|
|
401
402
|
timestamp: Date.now(),
|
|
402
403
|
// Typed parsed output (undefined for failed exec / client-only tools).
|
|
403
404
|
...(toolOutput !== undefined ? { output: toolOutput } : {}),
|
|
@@ -433,7 +434,7 @@ export interface ToolResult {
|
|
|
433
434
|
duration?: number
|
|
434
435
|
/**
|
|
435
436
|
* Parsed tool input (after JSON parse + optional Standard Schema validation).
|
|
436
|
-
*
|
|
437
|
+
* Parsed tool input after JSON parse + optional Standard Schema validation.
|
|
437
438
|
*/
|
|
438
439
|
input?: unknown
|
|
439
440
|
/**
|
|
@@ -33,6 +33,8 @@ import type {
|
|
|
33
33
|
GenerationMiddlewareContext,
|
|
34
34
|
} from '../middleware/types'
|
|
35
35
|
import type { VideoAdapter } from './adapter'
|
|
36
|
+
import { normalizeStreamChunk } from '../../utilities/normalize-stream-chunk'
|
|
37
|
+
import type { AdapterYieldChunk } from '../../utilities/adapter-yield-chunk'
|
|
36
38
|
import type {
|
|
37
39
|
MediaPrompt,
|
|
38
40
|
MediaPromptFor,
|
|
@@ -728,13 +730,13 @@ async function* runStreamingVideoGeneration<
|
|
|
728
730
|
timestamp: Date.now(),
|
|
729
731
|
}
|
|
730
732
|
|
|
731
|
-
yield {
|
|
733
|
+
yield* normalizeStreamChunk({
|
|
732
734
|
type: 'RUN_FINISHED',
|
|
733
735
|
runId,
|
|
734
736
|
threadId: wireThreadId,
|
|
735
737
|
finishReason: 'stop',
|
|
736
738
|
timestamp: Date.now(),
|
|
737
|
-
} as
|
|
739
|
+
} as AdapterYieldChunk)
|
|
738
740
|
return
|
|
739
741
|
}
|
|
740
742
|
|
|
@@ -768,15 +770,14 @@ async function* runStreamingVideoGeneration<
|
|
|
768
770
|
code: payload.code,
|
|
769
771
|
source: 'generateVideo',
|
|
770
772
|
})
|
|
771
|
-
yield {
|
|
773
|
+
yield* normalizeStreamChunk({
|
|
772
774
|
type: 'RUN_ERROR',
|
|
773
775
|
runId,
|
|
774
776
|
threadId: wireThreadId,
|
|
775
777
|
message: payload.message,
|
|
776
|
-
code: payload.code,
|
|
777
|
-
error: payload,
|
|
778
|
+
...(payload.code !== undefined ? { code: payload.code } : {}),
|
|
778
779
|
timestamp: Date.now(),
|
|
779
|
-
} as
|
|
780
|
+
} as AdapterYieldChunk)
|
|
780
781
|
} finally {
|
|
781
782
|
abortControls.clear()
|
|
782
783
|
if (!settled) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { EventType } from '@ag-ui/core'
|
|
8
8
|
import { toRunErrorPayload } from './error-payload'
|
|
9
9
|
import type { StreamChunk } from '../types'
|
|
10
|
+
import { normalizeStreamChunk } from '../utilities/normalize-stream-chunk'
|
|
10
11
|
|
|
11
12
|
function createId(prefix: string): string {
|
|
12
13
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
@@ -74,31 +75,26 @@ export async function* streamGenerationResult<TResult>(
|
|
|
74
75
|
timestamp: Date.now(),
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
yield {
|
|
78
|
+
yield* normalizeStreamChunk({
|
|
78
79
|
type: EventType.RUN_FINISHED,
|
|
79
80
|
runId,
|
|
80
81
|
threadId,
|
|
81
82
|
finishReason: 'stop',
|
|
82
83
|
timestamp: Date.now(),
|
|
83
|
-
}
|
|
84
|
+
})
|
|
84
85
|
} catch (error: unknown) {
|
|
85
86
|
const payload = toRunErrorPayload(error, 'Generation failed')
|
|
86
87
|
// `code` is omitted entirely when undefined so the event matches the
|
|
87
|
-
// AG-UI `code?: string` shape under `exactOptionalPropertyTypes`.
|
|
88
|
-
// deprecated nested `error` form preserves the same conditional
|
|
89
|
-
// structure for backward compatibility.
|
|
88
|
+
// AG-UI `code?: string` shape under `exactOptionalPropertyTypes`.
|
|
90
89
|
const codeFields =
|
|
91
90
|
payload.code !== undefined ? { code: payload.code } : undefined
|
|
92
|
-
yield {
|
|
91
|
+
yield* normalizeStreamChunk({
|
|
93
92
|
type: EventType.RUN_ERROR,
|
|
93
|
+
runId,
|
|
94
|
+
threadId,
|
|
94
95
|
message: payload.message,
|
|
95
96
|
...codeFields,
|
|
96
|
-
// Deprecated nested form for backward compatibility
|
|
97
|
-
error: {
|
|
98
|
-
message: payload.message,
|
|
99
|
-
...codeFields,
|
|
100
|
-
},
|
|
101
97
|
timestamp: Date.now(),
|
|
102
|
-
}
|
|
98
|
+
})
|
|
103
99
|
}
|
|
104
100
|
}
|
|
@@ -1,14 +1,58 @@
|
|
|
1
1
|
import { EventType } from '@ag-ui/core'
|
|
2
2
|
import { toRunErrorPayload } from '../error-payload'
|
|
3
3
|
import { MAX_TOKENS_KEYS } from '../../utilities/sampling-keys'
|
|
4
|
+
import { rebuildTokenUsage } from '../../utilities/ag-ui-usage'
|
|
5
|
+
import type { AdapterYieldChunk } from '../../utilities/adapter-yield-chunk'
|
|
6
|
+
import { tanstackMetadata } from '../../utilities/merge-metadata'
|
|
7
|
+
import { normalizeStreamChunk } from '../../utilities/normalize-stream-chunk'
|
|
4
8
|
import { BaseSummarizeAdapter } from './adapter'
|
|
5
9
|
import type {
|
|
6
10
|
StreamChunk,
|
|
7
11
|
SummarizationOptions,
|
|
8
12
|
SummarizationResult,
|
|
9
13
|
TextOptions,
|
|
14
|
+
TokenUsage,
|
|
10
15
|
} from '../../types'
|
|
11
16
|
|
|
17
|
+
function consumeSpecSummarizeChunk(
|
|
18
|
+
chunk: StreamChunk,
|
|
19
|
+
state: { summary: string; model: string; usage: TokenUsage },
|
|
20
|
+
): void {
|
|
21
|
+
if (chunk.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
22
|
+
if (chunk.delta) state.summary += chunk.delta
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tanstack = tanstackMetadata(chunk)
|
|
27
|
+
if (
|
|
28
|
+
(chunk.type === EventType.RUN_STARTED ||
|
|
29
|
+
chunk.type === EventType.RUN_FINISHED ||
|
|
30
|
+
chunk.type === EventType.TEXT_MESSAGE_START) &&
|
|
31
|
+
typeof tanstack?.model === 'string'
|
|
32
|
+
) {
|
|
33
|
+
state.model = tanstack.model
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (chunk.type === EventType.RUN_FINISHED) {
|
|
37
|
+
const rebuilt = rebuildTokenUsage(chunk.usage, tanstack?.usage)
|
|
38
|
+
if (rebuilt) state.usage = rebuilt
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function throwRunError(
|
|
43
|
+
chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,
|
|
44
|
+
): never {
|
|
45
|
+
const message =
|
|
46
|
+
typeof chunk.message === 'string' && chunk.message.length > 0
|
|
47
|
+
? chunk.message
|
|
48
|
+
: 'Summarization failed'
|
|
49
|
+
const err = new Error(message)
|
|
50
|
+
if (typeof chunk.code === 'string') {
|
|
51
|
+
;(err as Error & { code?: string }).code = chunk.code
|
|
52
|
+
}
|
|
53
|
+
throw err
|
|
54
|
+
}
|
|
55
|
+
|
|
12
56
|
/**
|
|
13
57
|
* Minimal contract for a text adapter that supports `chatStream`. Lets
|
|
14
58
|
* `ChatStreamSummarizeAdapter` work with any text adapter without coupling
|
|
@@ -21,7 +65,7 @@ import type {
|
|
|
21
65
|
* `SummarizationOptions<TProviderOptions>` on the wrapper itself.
|
|
22
66
|
*/
|
|
23
67
|
export interface ChatStreamCapable {
|
|
24
|
-
chatStream: (options: TextOptions<any>) => AsyncIterable<
|
|
68
|
+
chatStream: (options: TextOptions<any>) => AsyncIterable<AdapterYieldChunk>
|
|
25
69
|
}
|
|
26
70
|
|
|
27
71
|
/**
|
|
@@ -38,6 +82,7 @@ export interface ChatStreamCapable {
|
|
|
38
82
|
* - Groq: `max_completion_tokens`
|
|
39
83
|
* - Gemini: `maxOutputTokens`
|
|
40
84
|
* - OpenRouter: `maxCompletionTokens`
|
|
85
|
+
* - LLM Gateway: `max_tokens`
|
|
41
86
|
* - Ollama: nested `options.num_predict` (no entry — see `applyMaxLength`)
|
|
42
87
|
*/
|
|
43
88
|
const MAX_TOKENS_KEY_BY_ADAPTER: Record<string, string> = {
|
|
@@ -47,6 +92,9 @@ const MAX_TOKENS_KEY_BY_ADAPTER: Record<string, string> = {
|
|
|
47
92
|
groq: 'max_completion_tokens',
|
|
48
93
|
gemini: 'maxOutputTokens',
|
|
49
94
|
openrouter: 'maxCompletionTokens',
|
|
95
|
+
// LLM Gateway exposes an OpenAI-compatible Chat Completions surface whose
|
|
96
|
+
// only output cap is `max_tokens` — it does not read `max_completion_tokens`.
|
|
97
|
+
llmgateway: 'max_tokens',
|
|
50
98
|
}
|
|
51
99
|
|
|
52
100
|
/**
|
|
@@ -197,10 +245,12 @@ export class ChatStreamSummarizeAdapter<
|
|
|
197
245
|
): Promise<SummarizationResult> {
|
|
198
246
|
const systemPrompt = this.buildSummarizationPrompt(options)
|
|
199
247
|
|
|
200
|
-
let summary = ''
|
|
201
248
|
const id = this.generateId()
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
const state = {
|
|
250
|
+
summary: '',
|
|
251
|
+
model: options.model,
|
|
252
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
253
|
+
}
|
|
204
254
|
|
|
205
255
|
options.logger.request(
|
|
206
256
|
`activity=summarize provider=${this.name} model=${options.model} text-length=${options.text.length} maxLength=${options.maxLength ?? 'unset'}`,
|
|
@@ -208,41 +258,15 @@ export class ChatStreamSummarizeAdapter<
|
|
|
208
258
|
)
|
|
209
259
|
|
|
210
260
|
try {
|
|
211
|
-
for await (const
|
|
261
|
+
for await (const raw of this.textAdapter.chatStream(
|
|
212
262
|
this.buildTextOptions(options, systemPrompt),
|
|
213
263
|
)) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
summary += chunk.delta
|
|
221
|
-
}
|
|
222
|
-
model = chunk.model || model
|
|
223
|
-
}
|
|
224
|
-
if (chunk.type === 'RUN_FINISHED') {
|
|
225
|
-
if (chunk.usage) {
|
|
226
|
-
usage = chunk.usage
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// Surface failures: the underlying chatStream emits RUN_ERROR instead
|
|
230
|
-
// of throwing, so without this branch summarize() would return an
|
|
231
|
-
// empty summary and pretend a failed run succeeded.
|
|
232
|
-
if (chunk.type === 'RUN_ERROR') {
|
|
233
|
-
const message =
|
|
234
|
-
(chunk.error && typeof chunk.error.message === 'string'
|
|
235
|
-
? chunk.error.message
|
|
236
|
-
: null) ?? 'Summarization failed'
|
|
237
|
-
const code =
|
|
238
|
-
chunk.error && typeof chunk.error.code === 'string'
|
|
239
|
-
? chunk.error.code
|
|
240
|
-
: undefined
|
|
241
|
-
const err = new Error(message)
|
|
242
|
-
if (code) {
|
|
243
|
-
;(err as Error & { code?: string }).code = code
|
|
244
|
-
}
|
|
245
|
-
throw err
|
|
264
|
+
for (const chunk of normalizeStreamChunk(raw as AdapterYieldChunk)) {
|
|
265
|
+
// Surface failures: the underlying chatStream emits RUN_ERROR instead
|
|
266
|
+
// of throwing, so without this branch summarize() would return an
|
|
267
|
+
// empty summary and pretend a failed run succeeded.
|
|
268
|
+
if (chunk.type === EventType.RUN_ERROR) throwRunError(chunk)
|
|
269
|
+
consumeSpecSummarizeChunk(chunk, state)
|
|
246
270
|
}
|
|
247
271
|
}
|
|
248
272
|
} catch (error: unknown) {
|
|
@@ -255,7 +279,12 @@ export class ChatStreamSummarizeAdapter<
|
|
|
255
279
|
throw error
|
|
256
280
|
}
|
|
257
281
|
|
|
258
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
id,
|
|
284
|
+
model: state.model,
|
|
285
|
+
summary: state.summary,
|
|
286
|
+
usage: state.usage,
|
|
287
|
+
}
|
|
259
288
|
}
|
|
260
289
|
|
|
261
290
|
override async *summarizeStream(
|
|
@@ -269,46 +298,45 @@ export class ChatStreamSummarizeAdapter<
|
|
|
269
298
|
)
|
|
270
299
|
|
|
271
300
|
const id = this.generateId()
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
301
|
+
const state = {
|
|
302
|
+
summary: '',
|
|
303
|
+
model: options.model,
|
|
304
|
+
usage: {
|
|
305
|
+
promptTokens: 0,
|
|
306
|
+
completionTokens: 0,
|
|
307
|
+
totalTokens: 0,
|
|
308
|
+
} satisfies SummarizationResult['usage'],
|
|
278
309
|
}
|
|
279
310
|
|
|
280
311
|
try {
|
|
281
|
-
for await (const
|
|
312
|
+
for await (const raw of this.textAdapter.chatStream(
|
|
282
313
|
this.buildTextOptions(options, systemPrompt),
|
|
283
314
|
)) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
315
|
+
for (const chunk of normalizeStreamChunk(raw as AdapterYieldChunk)) {
|
|
316
|
+
// Accumulate the same way `summarize()` does so consumers see deltas
|
|
317
|
+
// AND the terminal `generation:result` event below carries the same
|
|
318
|
+
// final summary that non-streaming returns.
|
|
319
|
+
consumeSpecSummarizeChunk(chunk, state)
|
|
320
|
+
|
|
321
|
+
// Emit the GenerationClient-shaped result event just before the
|
|
322
|
+
// terminal RUN_FINISHED so subscribers (useSummarize) populate
|
|
323
|
+
// `result` before flipping `status` to success.
|
|
324
|
+
if (chunk.type === EventType.RUN_FINISHED) {
|
|
325
|
+
yield {
|
|
326
|
+
type: EventType.CUSTOM,
|
|
327
|
+
name: 'generation:result',
|
|
328
|
+
value: {
|
|
329
|
+
id,
|
|
330
|
+
model: state.model,
|
|
331
|
+
summary: state.summary,
|
|
332
|
+
usage: state.usage,
|
|
333
|
+
} satisfies SummarizationResult,
|
|
334
|
+
timestamp: Date.now(),
|
|
335
|
+
}
|
|
292
336
|
}
|
|
293
|
-
if (chunk.model) model = chunk.model
|
|
294
|
-
}
|
|
295
337
|
|
|
296
|
-
|
|
297
|
-
// terminal RUN_FINISHED so subscribers (useSummarize) populate
|
|
298
|
-
// `result` before flipping `status` to success.
|
|
299
|
-
if (chunk.type === 'RUN_FINISHED') {
|
|
300
|
-
if (chunk.usage) usage = chunk.usage
|
|
301
|
-
if (chunk.model) model = chunk.model
|
|
302
|
-
yield {
|
|
303
|
-
type: EventType.CUSTOM,
|
|
304
|
-
name: 'generation:result',
|
|
305
|
-
value: { id, model, summary, usage } satisfies SummarizationResult,
|
|
306
|
-
model,
|
|
307
|
-
timestamp: Date.now(),
|
|
308
|
-
}
|
|
338
|
+
yield chunk
|
|
309
339
|
}
|
|
310
|
-
|
|
311
|
-
yield chunk
|
|
312
340
|
}
|
|
313
341
|
} catch (error: unknown) {
|
|
314
342
|
options.logger.errors(`${this.name}.summarizeStream fatal`, {
|
package/src/adapter-internals.ts
CHANGED
package/src/client.ts
CHANGED
|
@@ -297,6 +297,17 @@ export type {
|
|
|
297
297
|
} from './activities/chat/stream/index'
|
|
298
298
|
|
|
299
299
|
export { uiMessagesToWire } from './utilities/ag-ui-wire'
|
|
300
|
+
export {
|
|
301
|
+
mergeMetadata,
|
|
302
|
+
tanstackMetadata,
|
|
303
|
+
withTanstackMetadata,
|
|
304
|
+
} from './utilities/merge-metadata'
|
|
305
|
+
export { fromSpecTokenUsage, toSpecTokenUsage } from './utilities/ag-ui-usage'
|
|
306
|
+
export type { SpecTokenUsage } from './utilities/ag-ui-usage'
|
|
307
|
+
export { normalizeStreamChunk } from './utilities/normalize-stream-chunk'
|
|
308
|
+
export { restoreInboundChunk } from './utilities/restore-inbound-chunk'
|
|
309
|
+
export type { AdapterYieldChunk } from './utilities/adapter-yield-chunk'
|
|
310
|
+
export { getChunkRunId, getChunkThreadId } from './utilities/chunk-ids'
|
|
300
311
|
export type { WireMessage } from './utilities/ag-ui-wire'
|
|
301
312
|
|
|
302
313
|
export type {
|
|
@@ -326,6 +337,8 @@ export type {
|
|
|
326
337
|
StreamChunk,
|
|
327
338
|
StructuredOutputPart,
|
|
328
339
|
TextPart,
|
|
340
|
+
TanStackMessageMetadata,
|
|
341
|
+
TanStackRunMetadata,
|
|
329
342
|
ThinkingPart,
|
|
330
343
|
ToolCall,
|
|
331
344
|
ToolCallPart,
|
package/src/index.ts
CHANGED
|
@@ -459,6 +459,12 @@ export { generationParamsFromBody, generationParamsFromRequest } from './client'
|
|
|
459
459
|
|
|
460
460
|
// AG-UI wire serialization (used internally by @tanstack/ai-client)
|
|
461
461
|
export { uiMessagesToWire } from './utilities/ag-ui-wire'
|
|
462
|
+
export { mergeMetadata, withTanstackMetadata } from './utilities/merge-metadata'
|
|
463
|
+
export { fromSpecTokenUsage, toSpecTokenUsage } from './utilities/ag-ui-usage'
|
|
464
|
+
export type { SpecTokenUsage } from './utilities/ag-ui-usage'
|
|
465
|
+
export { normalizeStreamChunk } from './utilities/normalize-stream-chunk'
|
|
466
|
+
export type { AdapterYieldChunk } from './utilities/adapter-yield-chunk'
|
|
467
|
+
export { getChunkRunId, getChunkThreadId } from './utilities/chunk-ids'
|
|
462
468
|
export type { WireMessage } from './utilities/ag-ui-wire'
|
|
463
469
|
export {
|
|
464
470
|
isContentPart,
|
package/src/middlewares/otel.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from '../utilities/sampling-keys'
|
|
11
11
|
import { firstNumber } from '../utilities/numbers'
|
|
12
12
|
import { errorMessage, errorTypeName } from '../utilities/errors'
|
|
13
|
+
import { rebuildTokenUsage } from '../utilities/ag-ui-usage'
|
|
14
|
+
import { tanstackMetadata } from '../utilities/merge-metadata'
|
|
13
15
|
import { usageAttributes } from './usage-attributes'
|
|
14
16
|
import type {
|
|
15
17
|
AttributeValue,
|
|
@@ -631,26 +633,32 @@ export function otelMiddleware(
|
|
|
631
633
|
}
|
|
632
634
|
|
|
633
635
|
if (chunk.type !== 'RUN_FINISHED') return
|
|
636
|
+
const tanstack = tanstackMetadata(chunk)
|
|
637
|
+
const extra = chunk as {
|
|
638
|
+
finishReason?: string | null
|
|
639
|
+
model?: string
|
|
640
|
+
}
|
|
641
|
+
const finishReason = extra.finishReason ?? tanstack?.finishReason
|
|
642
|
+
const model = extra.model ?? tanstack?.model
|
|
634
643
|
// Capture for the root-span finish_reasons attribute set in onFinish,
|
|
635
644
|
// which receives base-shaped info without a finishReason field.
|
|
636
|
-
if (
|
|
645
|
+
if (finishReason) state.lastFinishReason = finishReason
|
|
637
646
|
const span = state.currentIterationSpan
|
|
638
647
|
if (!span) return
|
|
639
648
|
|
|
640
|
-
if (
|
|
641
|
-
span.setAttribute('gen_ai.response.finish_reasons', [
|
|
642
|
-
chunk.finishReason,
|
|
643
|
-
])
|
|
649
|
+
if (finishReason) {
|
|
650
|
+
span.setAttribute('gen_ai.response.finish_reasons', [finishReason])
|
|
644
651
|
}
|
|
645
|
-
if (
|
|
652
|
+
if (model) span.setAttribute('gen_ai.response.model', model)
|
|
646
653
|
|
|
647
654
|
// Set usage attributes on the iteration span directly from the chunk
|
|
648
655
|
// so they're available before `onUsage` fires. Histogram recording is
|
|
649
656
|
// deliberately NOT done here — the chat runner always invokes
|
|
650
657
|
// `runOnUsage` when `chunk.usage` is present, and `onUsage` is the
|
|
651
658
|
// canonical place for the metric. Recording in both would double-count.
|
|
652
|
-
|
|
653
|
-
|
|
659
|
+
const tokenUsage = rebuildTokenUsage(chunk.usage, tanstack?.usage)
|
|
660
|
+
if (tokenUsage) {
|
|
661
|
+
span.setAttributes(usageAttributes(tokenUsage))
|
|
654
662
|
}
|
|
655
663
|
|
|
656
664
|
if (captureContent && state.assistantTextBuffer.length > 0) {
|
|
@@ -8,7 +8,9 @@ import { wasRunDetached } from './delivery-detach'
|
|
|
8
8
|
import { notifyRunDisconnected } from './delivery-disconnect'
|
|
9
9
|
import { resolveResumeRunId } from './stream-durability'
|
|
10
10
|
import { EventType } from './types'
|
|
11
|
+
import { toWireChunk } from './strip-to-spec-middleware'
|
|
11
12
|
import { resolveDebugOption } from './logger/resolve'
|
|
13
|
+
import { runErrorEventToError } from './utilities/errors'
|
|
12
14
|
import type { LockStore } from './activities/chat/middleware/locks'
|
|
13
15
|
import type {
|
|
14
16
|
RunRecord,
|
|
@@ -46,6 +48,10 @@ export async function streamToText(
|
|
|
46
48
|
let accumulatedContent = ''
|
|
47
49
|
|
|
48
50
|
for await (const chunk of stream) {
|
|
51
|
+
if (chunk.type === 'RUN_ERROR') {
|
|
52
|
+
throw runErrorEventToError(chunk)
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) {
|
|
50
56
|
accumulatedContent += chunk.delta
|
|
51
57
|
}
|
|
@@ -282,10 +288,13 @@ function sseEncoders(
|
|
|
282
288
|
encodeChunk: (chunk, index) => {
|
|
283
289
|
const id = getId?.(chunk, index)
|
|
284
290
|
const idLine = id === undefined ? '' : `id: ${id}\n`
|
|
285
|
-
|
|
291
|
+
const wire = toWireChunk(chunk)
|
|
292
|
+
return encoder.encode(`${idLine}data: ${JSON.stringify(wire)}\n\n`)
|
|
286
293
|
},
|
|
287
294
|
encodeError: (error) =>
|
|
288
|
-
encoder.encode(
|
|
295
|
+
encoder.encode(
|
|
296
|
+
`data: ${JSON.stringify(toWireChunk(runErrorChunk(error)))}\n\n`,
|
|
297
|
+
),
|
|
289
298
|
}
|
|
290
299
|
}
|
|
291
300
|
|
|
@@ -1064,12 +1073,15 @@ function ndjsonEncoders(
|
|
|
1064
1073
|
return {
|
|
1065
1074
|
encodeChunk: (chunk, index) => {
|
|
1066
1075
|
const id = getId?.(chunk, index)
|
|
1076
|
+
const wire = toWireChunk(chunk)
|
|
1067
1077
|
const line =
|
|
1068
|
-
id === undefined
|
|
1078
|
+
id === undefined
|
|
1079
|
+
? JSON.stringify(wire)
|
|
1080
|
+
: JSON.stringify({ id, chunk: wire })
|
|
1069
1081
|
return encoder.encode(`${line}\n`)
|
|
1070
1082
|
},
|
|
1071
1083
|
encodeError: (error) =>
|
|
1072
|
-
encoder.encode(`${JSON.stringify(runErrorChunk(error))}\n`),
|
|
1084
|
+
encoder.encode(`${JSON.stringify(toWireChunk(runErrorChunk(error)))}\n`),
|
|
1073
1085
|
}
|
|
1074
1086
|
}
|
|
1075
1087
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { chatParamsFromRequestBody } from './utilities/chat-params'
|
|
2
2
|
import { durableStreamSource, runErrorChunk } from './stream-to-response'
|
|
3
|
+
import { toWireChunk } from './strip-to-spec-middleware'
|
|
3
4
|
import { resolveDebugOption } from './logger/resolve'
|
|
4
5
|
import type { StreamDurability } from './stream-durability'
|
|
5
6
|
import type { DebugOption } from './logger/types'
|
|
@@ -35,7 +36,8 @@ export function encodeWsFrame(
|
|
|
35
36
|
chunk: StreamChunk,
|
|
36
37
|
id: string | undefined,
|
|
37
38
|
): string {
|
|
38
|
-
|
|
39
|
+
const wire = toWireChunk(chunk)
|
|
40
|
+
return JSON.stringify(id === undefined ? wire : { id, chunk: wire })
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|