@tanstack/ai 0.47.3 → 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 +115 -25
- 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 +186 -101
- 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 +56 -53
- 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 +11 -4
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/stream-to-response.js +8 -5
- 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/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 +2 -2
- package/src/activities/chat/adapter.ts +4 -4
- package/src/activities/chat/index.ts +398 -400
- package/src/activities/chat/messages.ts +174 -34
- package/src/activities/chat/stream/processor.ts +289 -218
- 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 +94 -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 +11 -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/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
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import { devtoolsMiddleware } from '@tanstack/ai-event-client'
|
|
9
9
|
import { undoNullWidening } from '@tanstack/ai-utils'
|
|
10
|
-
import { stripToSpecMiddleware } from '../../strip-to-spec-middleware'
|
|
11
10
|
import { streamToText } from '../../stream-to-response.js'
|
|
12
11
|
import { resolveDebugOption } from '../../logger/resolve'
|
|
13
12
|
import { EventType } from '../../types'
|
|
@@ -33,6 +32,15 @@ import {
|
|
|
33
32
|
canonicalInterruptJson,
|
|
34
33
|
digestInterruptJson,
|
|
35
34
|
} from '../../interrupt-serialization'
|
|
35
|
+
import { rebuildTokenUsage } from '../../utilities/ag-ui-usage'
|
|
36
|
+
import { uiMessagesToWire } from '../../utilities/ag-ui-wire'
|
|
37
|
+
import {
|
|
38
|
+
tanstackMetadata,
|
|
39
|
+
withTanstackMetadata,
|
|
40
|
+
} from '../../utilities/merge-metadata'
|
|
41
|
+
import { normalizeStreamChunk } from '../../utilities/normalize-stream-chunk'
|
|
42
|
+
import { restorePublicUsage } from '../../utilities/restore-inbound-chunk'
|
|
43
|
+
import type { AdapterYieldChunk } from '../../utilities/adapter-yield-chunk'
|
|
36
44
|
import { normalizeToolResult } from '../../utilities/tool-result'
|
|
37
45
|
import { isProviderExecutedToolCall } from '../../utilities/provider-executed'
|
|
38
46
|
import { LazyToolManager } from './tools/lazy-tool-manager'
|
|
@@ -57,7 +65,7 @@ import { isCancelRequestedReason } from './cancel'
|
|
|
57
65
|
import {
|
|
58
66
|
convertMessagesToModelMessages,
|
|
59
67
|
generateMessageId,
|
|
60
|
-
|
|
68
|
+
modelMessagesToUIMessages,
|
|
61
69
|
safeJsonStringify,
|
|
62
70
|
} from './messages'
|
|
63
71
|
import { MiddlewareRunner } from './middleware/compose'
|
|
@@ -88,6 +96,7 @@ import type {
|
|
|
88
96
|
import type {
|
|
89
97
|
AgentLoopStrategy,
|
|
90
98
|
AnyTool,
|
|
99
|
+
ChatStream,
|
|
91
100
|
ConstrainedModelMessage,
|
|
92
101
|
CustomEvent,
|
|
93
102
|
InferSchemaType,
|
|
@@ -107,9 +116,10 @@ import type {
|
|
|
107
116
|
TextOptions,
|
|
108
117
|
ToolCall,
|
|
109
118
|
ToolCallArgsEvent,
|
|
119
|
+
ReasoningEncryptedValueEvent,
|
|
110
120
|
ToolCallEndEvent,
|
|
121
|
+
ToolCallResultEvent,
|
|
111
122
|
ToolCallStartEvent,
|
|
112
|
-
TypedStreamChunk,
|
|
113
123
|
UIMessage,
|
|
114
124
|
} from '../../types'
|
|
115
125
|
import type {
|
|
@@ -514,7 +524,7 @@ export interface TextActivityOptions<
|
|
|
514
524
|
outputSchema?: TSchema
|
|
515
525
|
/**
|
|
516
526
|
* Whether to stream the text result.
|
|
517
|
-
* When true (default), returns
|
|
527
|
+
* When true (default), returns a ChatStream for streaming output.
|
|
518
528
|
* When false, returns a Promise<string> with the collected text content.
|
|
519
529
|
*
|
|
520
530
|
* Note: If outputSchema is provided, this option is ignored and the result
|
|
@@ -636,10 +646,7 @@ export function createChatOptions<
|
|
|
636
646
|
* - If outputSchema is provided without explicit stream:true:
|
|
637
647
|
* Promise<InferSchemaType<TSchema>>.
|
|
638
648
|
* - If stream is explicitly false (no schema): Promise<string>.
|
|
639
|
-
* - Otherwise (default):
|
|
640
|
-
*
|
|
641
|
-
* When tools with typed schemas are provided, the stream chunks include
|
|
642
|
-
* type-safe `toolName` and `input` fields on tool call events.
|
|
649
|
+
* - Otherwise (default): ChatStream.
|
|
643
650
|
*
|
|
644
651
|
* `[TStream] extends [true]` is used (not `TStream extends true`) so that the
|
|
645
652
|
* default `boolean` value of `TStream` does *not* match the streaming branch.
|
|
@@ -659,13 +666,9 @@ export type TextActivityResult<
|
|
|
659
666
|
: Promise<InferSchemaType<TSchema>>
|
|
660
667
|
: [TStream] extends [false]
|
|
661
668
|
? Promise<string>
|
|
662
|
-
:
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
? TTools
|
|
666
|
-
: ReadonlyArray<AnyTool>
|
|
667
|
-
>
|
|
668
|
-
>
|
|
669
|
+
: TTools extends infer _TTools
|
|
670
|
+
? ChatStream
|
|
671
|
+
: ChatStream
|
|
669
672
|
|
|
670
673
|
// ===========================
|
|
671
674
|
// ChatEngine Implementation
|
|
@@ -804,7 +807,6 @@ class TextEngine<
|
|
|
804
807
|
[]
|
|
805
808
|
private currentThinkingContent = ''
|
|
806
809
|
private currentThinkingSignature = ''
|
|
807
|
-
private hasSeenReasoningEvents = false
|
|
808
810
|
private eventOptions?: Record<string, unknown> | undefined
|
|
809
811
|
private eventToolNames?: Array<string>
|
|
810
812
|
private finishedEvent: RunFinishedEvent | null = null
|
|
@@ -963,17 +965,12 @@ class TextEngine<
|
|
|
963
965
|
this.runIdOverride = config.params.runId
|
|
964
966
|
this.parentRunIdOverride = config.params.parentRunId
|
|
965
967
|
|
|
966
|
-
// Initialize middleware — devtools first
|
|
967
|
-
//
|
|
968
|
-
//
|
|
969
|
-
// The strip middleware ensures the yielded public stream is AG-UI spec-compliant.
|
|
968
|
+
// Initialize middleware — devtools first. Spec stripping for the AG-UI
|
|
969
|
+
// wire happens in toServerSentEventsStream, so in-process chat()
|
|
970
|
+
// consumers still see TokenUsage and tool aliases.
|
|
970
971
|
const allMiddleware: Array<
|
|
971
972
|
ChatMiddleware<TContext, InterruptDefinition<any, any, any, any>>
|
|
972
|
-
> = [
|
|
973
|
-
devtoolsMiddleware(),
|
|
974
|
-
...(config.middleware || []),
|
|
975
|
-
stripToSpecMiddleware(),
|
|
976
|
-
]
|
|
973
|
+
> = [devtoolsMiddleware(), ...(config.middleware || [])]
|
|
977
974
|
this.middlewareRunner = new MiddlewareRunner(allMiddleware, logger)
|
|
978
975
|
this.middlewareAbortController = new AbortController()
|
|
979
976
|
this.toolAbortSignal = combineAbortSignals(
|
|
@@ -1156,7 +1153,10 @@ class TextEngine<
|
|
|
1156
1153
|
finishReason: this.lastFinishReason,
|
|
1157
1154
|
duration: Date.now() - this.streamStartTime,
|
|
1158
1155
|
content: this.accumulatedContent,
|
|
1159
|
-
usage:
|
|
1156
|
+
usage: rebuildTokenUsage(
|
|
1157
|
+
this.finishedEvent?.usage,
|
|
1158
|
+
tanstackMetadata(this.finishedEvent ?? undefined)?.usage,
|
|
1159
|
+
),
|
|
1160
1160
|
})
|
|
1161
1161
|
}
|
|
1162
1162
|
return
|
|
@@ -1304,7 +1304,10 @@ class TextEngine<
|
|
|
1304
1304
|
finishReason: this.lastFinishReason,
|
|
1305
1305
|
duration: Date.now() - this.streamStartTime,
|
|
1306
1306
|
content: this.accumulatedContent,
|
|
1307
|
-
usage:
|
|
1307
|
+
usage: rebuildTokenUsage(
|
|
1308
|
+
this.finishedEvent?.usage,
|
|
1309
|
+
tanstackMetadata(this.finishedEvent ?? undefined)?.usage,
|
|
1310
|
+
),
|
|
1308
1311
|
})
|
|
1309
1312
|
}
|
|
1310
1313
|
}
|
|
@@ -1316,18 +1319,17 @@ class TextEngine<
|
|
|
1316
1319
|
typeof error.continuationRunId === 'string'
|
|
1317
1320
|
) {
|
|
1318
1321
|
this.terminalHookCalled = true
|
|
1319
|
-
yield {
|
|
1322
|
+
yield* this.pipeThroughMiddleware({
|
|
1320
1323
|
type: EventType.RUN_FINISHED,
|
|
1321
1324
|
timestamp: Date.now(),
|
|
1322
1325
|
threadId: this.threadId,
|
|
1323
1326
|
runId: this.runIdOverride ?? this.requestId,
|
|
1324
|
-
finishReason: 'stop',
|
|
1325
1327
|
outcome: { type: 'success' },
|
|
1326
1328
|
result: {
|
|
1327
1329
|
replayed: true,
|
|
1328
1330
|
continuationRunId: error.continuationRunId,
|
|
1329
1331
|
},
|
|
1330
|
-
}
|
|
1332
|
+
})
|
|
1331
1333
|
return
|
|
1332
1334
|
}
|
|
1333
1335
|
const interruptFailure = structuralInterruptFailure(error)
|
|
@@ -1429,7 +1431,7 @@ class TextEngine<
|
|
|
1429
1431
|
this.accumulatedThinking = []
|
|
1430
1432
|
this.currentThinkingContent = ''
|
|
1431
1433
|
this.currentThinkingSignature = ''
|
|
1432
|
-
|
|
1434
|
+
|
|
1433
1435
|
this.finishedEvent = null
|
|
1434
1436
|
this.streamedToolErrorResults.clear()
|
|
1435
1437
|
|
|
@@ -1494,7 +1496,7 @@ class TextEngine<
|
|
|
1494
1496
|
)
|
|
1495
1497
|
}
|
|
1496
1498
|
|
|
1497
|
-
for await (const
|
|
1499
|
+
for await (const raw of this.adapter.chatStream({
|
|
1498
1500
|
model: this.params.model,
|
|
1499
1501
|
messages: this.messages,
|
|
1500
1502
|
tools: toolsWithJsonSchemas,
|
|
@@ -1518,9 +1520,7 @@ class TextEngine<
|
|
|
1518
1520
|
|
|
1519
1521
|
this.totalChunkCount++
|
|
1520
1522
|
|
|
1521
|
-
|
|
1522
|
-
// BEFORE middleware, so fields like finishReason, delta, etc. are available
|
|
1523
|
-
this.handleStreamChunk(chunk)
|
|
1523
|
+
this.handleStreamChunk(raw)
|
|
1524
1524
|
|
|
1525
1525
|
// Native combined mode: synthesize `structured-output.start` BEFORE
|
|
1526
1526
|
// the first TEXT_MESSAGE_START so the client-side StreamProcessor
|
|
@@ -1530,11 +1530,11 @@ class TextEngine<
|
|
|
1530
1530
|
// and emitting at run-start would wrap tool-call commentary into a
|
|
1531
1531
|
// structured-output part too.
|
|
1532
1532
|
if (
|
|
1533
|
-
|
|
1534
|
-
|
|
1533
|
+
raw.type === EventType.CUSTOM &&
|
|
1534
|
+
raw.name === 'structured-output.start'
|
|
1535
1535
|
) {
|
|
1536
1536
|
this.combinedStartEmitted = true
|
|
1537
|
-
const startValue =
|
|
1537
|
+
const startValue = raw.value
|
|
1538
1538
|
if (
|
|
1539
1539
|
startValue &&
|
|
1540
1540
|
typeof startValue === 'object' &&
|
|
@@ -1546,27 +1546,27 @@ class TextEngine<
|
|
|
1546
1546
|
}
|
|
1547
1547
|
}
|
|
1548
1548
|
|
|
1549
|
-
let outboundChunk:
|
|
1549
|
+
let outboundChunk: AdapterYieldChunk = raw
|
|
1550
1550
|
if (
|
|
1551
1551
|
this.finalStructuredOutput?.source === 'event' &&
|
|
1552
|
-
|
|
1553
|
-
|
|
1552
|
+
raw.type === EventType.CUSTOM &&
|
|
1553
|
+
raw.name === 'structured-output.complete'
|
|
1554
1554
|
) {
|
|
1555
|
-
const parsed = readStructuredOutputCompleteValue(
|
|
1555
|
+
const parsed = readStructuredOutputCompleteValue(raw.value)
|
|
1556
1556
|
if (parsed) {
|
|
1557
1557
|
const object = this.finalStructuredOutput.normalize
|
|
1558
1558
|
? this.finalStructuredOutput.normalize(parsed.object)
|
|
1559
1559
|
: parsed.object
|
|
1560
1560
|
this.structuredOutputResult = { data: object, rawText: parsed.raw }
|
|
1561
1561
|
this.combinedCompleteEmitted = true
|
|
1562
|
-
const value =
|
|
1562
|
+
const value = raw.value
|
|
1563
1563
|
const completeMessageId = readCustomEventMessageId(value)
|
|
1564
1564
|
if (completeMessageId) {
|
|
1565
1565
|
this.combinedStructuredMessageId = completeMessageId
|
|
1566
1566
|
this.captureStructuredOutputMessageIdentity(completeMessageId)
|
|
1567
1567
|
}
|
|
1568
1568
|
if (object !== parsed.object && value && typeof value === 'object') {
|
|
1569
|
-
outboundChunk = { ...
|
|
1569
|
+
outboundChunk = { ...raw, value: { ...value, object } }
|
|
1570
1570
|
}
|
|
1571
1571
|
}
|
|
1572
1572
|
}
|
|
@@ -1576,34 +1576,27 @@ class TextEngine<
|
|
|
1576
1576
|
this.finalStructuredOutput.yieldChunks &&
|
|
1577
1577
|
this.finalStructuredOutput.source !== 'event' &&
|
|
1578
1578
|
!this.combinedStartEmitted &&
|
|
1579
|
-
|
|
1579
|
+
raw.type === EventType.TEXT_MESSAGE_START
|
|
1580
1580
|
) {
|
|
1581
1581
|
this.combinedStartEmitted = true
|
|
1582
1582
|
const messageId =
|
|
1583
|
-
typeof
|
|
1584
|
-
?
|
|
1583
|
+
typeof raw.messageId === 'string' && raw.messageId !== ''
|
|
1584
|
+
? raw.messageId
|
|
1585
1585
|
: generateMessageId()
|
|
1586
1586
|
this.combinedStructuredMessageId = messageId
|
|
1587
1587
|
const synthStart: StreamChunk = {
|
|
1588
1588
|
type: EventType.CUSTOM,
|
|
1589
1589
|
name: 'structured-output.start',
|
|
1590
1590
|
value: { messageId },
|
|
1591
|
-
model: this.params.model,
|
|
1592
1591
|
timestamp: Date.now(),
|
|
1593
|
-
threadId: this.threadId,
|
|
1594
|
-
...(this.runIdOverride ? { runId: this.runIdOverride } : {}),
|
|
1595
1592
|
}
|
|
1596
1593
|
const synthOutputs = await this.middlewareRunner.runOnChunk(
|
|
1597
1594
|
this.middlewareCtx,
|
|
1598
1595
|
synthStart,
|
|
1599
1596
|
)
|
|
1600
|
-
|
|
1601
|
-
yield outputChunk
|
|
1602
|
-
this.middlewareCtx.chunkIndex++
|
|
1603
|
-
}
|
|
1597
|
+
yield* this.emitPublicChunks(synthOutputs)
|
|
1604
1598
|
}
|
|
1605
1599
|
|
|
1606
|
-
// Pipe chunk through middleware (devtools middleware observes; strip-to-spec cleans)
|
|
1607
1600
|
const outputChunks = await this.middlewareRunner.runOnChunk(
|
|
1608
1601
|
this.middlewareCtx,
|
|
1609
1602
|
outboundChunk,
|
|
@@ -1620,32 +1613,36 @@ class TextEngine<
|
|
|
1620
1613
|
this.finalStructuredOutput.yieldChunks &&
|
|
1621
1614
|
this.finalStructuredOutput.nativeCombined !== true
|
|
1622
1615
|
for (const outputChunk of outputChunks) {
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1616
|
+
for (const spec of normalizeStreamChunk(
|
|
1617
|
+
outputChunk as AdapterYieldChunk,
|
|
1618
|
+
)) {
|
|
1619
|
+
restorePublicUsage(spec)
|
|
1620
|
+
if (
|
|
1621
|
+
suppressAgentLifecycle &&
|
|
1622
|
+
(spec.type === EventType.RUN_STARTED ||
|
|
1623
|
+
spec.type === EventType.RUN_FINISHED)
|
|
1624
|
+
) {
|
|
1625
|
+
continue
|
|
1626
|
+
}
|
|
1627
|
+
if (spec.type === EventType.RUN_FINISHED) {
|
|
1628
|
+
this.deferredModelRunFinishedChunks.push(spec)
|
|
1629
|
+
continue
|
|
1630
|
+
}
|
|
1631
|
+
if (this.shouldDeferToolCallRunFinished(spec)) {
|
|
1632
|
+
this.deferredToolCallRunFinishedChunks.push(spec)
|
|
1633
|
+
continue
|
|
1634
|
+
}
|
|
1635
|
+
if (spec.type === EventType.RUN_STARTED) {
|
|
1636
|
+
this.hasPublicRunStarted = true
|
|
1637
|
+
}
|
|
1638
|
+
this.logger.output(`type=${spec.type}`, { chunk: spec })
|
|
1639
|
+
yield spec
|
|
1640
|
+
this.middlewareCtx.chunkIndex++
|
|
1640
1641
|
}
|
|
1641
|
-
this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk })
|
|
1642
|
-
yield outputChunk
|
|
1643
|
-
this.middlewareCtx.chunkIndex++
|
|
1644
1642
|
}
|
|
1645
1643
|
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
await this.middlewareRunner.runOnUsage(this.middlewareCtx, chunk.usage)
|
|
1644
|
+
if (raw.type === EventType.RUN_FINISHED) {
|
|
1645
|
+
await this.runOnUsageFromChunk(raw)
|
|
1649
1646
|
}
|
|
1650
1647
|
|
|
1651
1648
|
// Drain any sandbox.file events emitted while processing this chunk.
|
|
@@ -1660,7 +1657,7 @@ class TextEngine<
|
|
|
1660
1657
|
yield* this.drainSandboxFileQueue()
|
|
1661
1658
|
}
|
|
1662
1659
|
|
|
1663
|
-
private handleStreamChunk(chunk:
|
|
1660
|
+
private handleStreamChunk(chunk: AdapterYieldChunk): void {
|
|
1664
1661
|
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.
|
|
1665
1662
|
switch (chunk.type) {
|
|
1666
1663
|
// AG-UI Events
|
|
@@ -1681,6 +1678,9 @@ class TextEngine<
|
|
|
1681
1678
|
case 'TOOL_CALL_END':
|
|
1682
1679
|
this.handleToolCallEndEvent(chunk)
|
|
1683
1680
|
break
|
|
1681
|
+
case 'TOOL_CALL_RESULT':
|
|
1682
|
+
this.handleToolCallResultEvent(chunk)
|
|
1683
|
+
break
|
|
1684
1684
|
case 'RUN_FINISHED':
|
|
1685
1685
|
this.handleRunFinishedEvent(chunk)
|
|
1686
1686
|
break
|
|
@@ -1698,8 +1698,8 @@ class TextEngine<
|
|
|
1698
1698
|
this.handleReasoningMessageContentEvent(chunk)
|
|
1699
1699
|
break
|
|
1700
1700
|
|
|
1701
|
-
case '
|
|
1702
|
-
|
|
1701
|
+
case 'REASONING_ENCRYPTED_VALUE':
|
|
1702
|
+
this.handleReasoningEncryptedValueEvent(chunk)
|
|
1703
1703
|
break
|
|
1704
1704
|
|
|
1705
1705
|
case 'REASONING_START':
|
|
@@ -1721,8 +1721,12 @@ class TextEngine<
|
|
|
1721
1721
|
// ===========================
|
|
1722
1722
|
|
|
1723
1723
|
private handleTextMessageContentEvent(chunk: TextMessageContentEvent): void {
|
|
1724
|
-
|
|
1725
|
-
|
|
1724
|
+
const extra = chunk as AdapterYieldChunk
|
|
1725
|
+
// Adapters still emit leftover cumulative `content` on RAW yields.
|
|
1726
|
+
// Alignment suppresses already-delivered deltas, so this snapshot is
|
|
1727
|
+
// what a takeover saves as the full assistant text.
|
|
1728
|
+
if (typeof extra.content === 'string' && extra.content !== '') {
|
|
1729
|
+
this.accumulatedContent = extra.content
|
|
1726
1730
|
} else {
|
|
1727
1731
|
this.accumulatedContent += chunk.delta
|
|
1728
1732
|
}
|
|
@@ -1751,6 +1755,26 @@ class TextEngine<
|
|
|
1751
1755
|
this.captureStreamMessageIdentity(chunk.parentMessageId)
|
|
1752
1756
|
}
|
|
1753
1757
|
this.toolCallManager.addToolCallStartEvent(chunk)
|
|
1758
|
+
const metadata = chunk.metadata
|
|
1759
|
+
const thoughtSignature =
|
|
1760
|
+
metadata != null &&
|
|
1761
|
+
typeof metadata === 'object' &&
|
|
1762
|
+
'thoughtSignature' in metadata &&
|
|
1763
|
+
typeof metadata.thoughtSignature === 'string' &&
|
|
1764
|
+
metadata.thoughtSignature !== ''
|
|
1765
|
+
? metadata.thoughtSignature
|
|
1766
|
+
: undefined
|
|
1767
|
+
if (thoughtSignature === undefined) return
|
|
1768
|
+
const call = this.toolCallManager
|
|
1769
|
+
.getToolCalls()
|
|
1770
|
+
.find((candidate) => candidate.id === chunk.toolCallId)
|
|
1771
|
+
if (!call) return
|
|
1772
|
+
call.metadata = {
|
|
1773
|
+
...(call.metadata != null && typeof call.metadata === 'object'
|
|
1774
|
+
? call.metadata
|
|
1775
|
+
: {}),
|
|
1776
|
+
thoughtSignature,
|
|
1777
|
+
}
|
|
1754
1778
|
}
|
|
1755
1779
|
|
|
1756
1780
|
private handleToolCallArgsEvent(chunk: ToolCallArgsEvent): void {
|
|
@@ -1759,7 +1783,23 @@ class TextEngine<
|
|
|
1759
1783
|
|
|
1760
1784
|
private handleToolCallEndEvent(chunk: ToolCallEndEvent): void {
|
|
1761
1785
|
this.toolCallManager.completeToolCall(chunk)
|
|
1762
|
-
|
|
1786
|
+
const end = chunk as AdapterYieldChunk
|
|
1787
|
+
const state = end.state ?? tanstackMetadata(end)?.state
|
|
1788
|
+
if (state !== 'output-error' || end.result === undefined) return
|
|
1789
|
+
this.handleToolCallResultEvent({
|
|
1790
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
1791
|
+
toolCallId: chunk.toolCallId,
|
|
1792
|
+
content: Array.isArray(end.result)
|
|
1793
|
+
? JSON.stringify(end.result)
|
|
1794
|
+
: end.result,
|
|
1795
|
+
messageId: chunk.toolCallId,
|
|
1796
|
+
metadata: { tanstack: { state: 'output-error' } },
|
|
1797
|
+
})
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
private handleToolCallResultEvent(chunk: ToolCallResultEvent): void {
|
|
1801
|
+
const isOutputError = tanstackMetadata(chunk)?.state === 'output-error'
|
|
1802
|
+
if (!isOutputError) return
|
|
1763
1803
|
|
|
1764
1804
|
const toolCall = this.toolCallManager
|
|
1765
1805
|
.getToolCalls()
|
|
@@ -1769,15 +1809,35 @@ class TextEngine<
|
|
|
1769
1809
|
this.streamedToolErrorResults.set(chunk.toolCallId, {
|
|
1770
1810
|
toolCallId: chunk.toolCallId,
|
|
1771
1811
|
toolName: toolCall.function.name,
|
|
1772
|
-
result: chunk.
|
|
1773
|
-
...(chunk.input !== undefined && { input: chunk.input }),
|
|
1812
|
+
result: chunk.content,
|
|
1774
1813
|
state: 'output-error',
|
|
1775
1814
|
})
|
|
1776
1815
|
}
|
|
1777
1816
|
|
|
1778
|
-
private handleRunFinishedEvent(chunk:
|
|
1779
|
-
this.finishedEvent = chunk
|
|
1780
|
-
|
|
1817
|
+
private handleRunFinishedEvent(chunk: AdapterYieldChunk): void {
|
|
1818
|
+
this.finishedEvent = chunk as RunFinishedEvent
|
|
1819
|
+
const raw = chunk
|
|
1820
|
+
const top = raw.finishReason
|
|
1821
|
+
this.lastFinishReason =
|
|
1822
|
+
top === 'stop' ||
|
|
1823
|
+
top === 'length' ||
|
|
1824
|
+
top === 'content_filter' ||
|
|
1825
|
+
top === 'tool_calls' ||
|
|
1826
|
+
top === null
|
|
1827
|
+
? top
|
|
1828
|
+
: (tanstackMetadata(chunk)?.finishReason ?? null)
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
private async runOnUsageFromChunk(
|
|
1832
|
+
chunk: RunFinishedEvent | AdapterYieldChunk,
|
|
1833
|
+
): Promise<void> {
|
|
1834
|
+
const rebuilt = rebuildTokenUsage(
|
|
1835
|
+
chunk.usage,
|
|
1836
|
+
tanstackMetadata(chunk)?.usage,
|
|
1837
|
+
)
|
|
1838
|
+
if (rebuilt) {
|
|
1839
|
+
await this.middlewareRunner.runOnUsage(this.middlewareCtx, rebuilt)
|
|
1840
|
+
}
|
|
1781
1841
|
}
|
|
1782
1842
|
|
|
1783
1843
|
private handleRunErrorEvent(
|
|
@@ -1785,14 +1845,10 @@ class TextEngine<
|
|
|
1785
1845
|
): void {
|
|
1786
1846
|
this.earlyTermination = true
|
|
1787
1847
|
if (this.finalizationError === null) {
|
|
1788
|
-
const message = chunk.message ||
|
|
1848
|
+
const message = chunk.message || 'Run failed'
|
|
1789
1849
|
this.finalizationError = {
|
|
1790
1850
|
message,
|
|
1791
|
-
...(chunk.code !== undefined
|
|
1792
|
-
? { code: chunk.code }
|
|
1793
|
-
: chunk.error?.code !== undefined
|
|
1794
|
-
? { code: chunk.error.code }
|
|
1795
|
-
: {}),
|
|
1851
|
+
...(chunk.code !== undefined ? { code: chunk.code } : {}),
|
|
1796
1852
|
}
|
|
1797
1853
|
}
|
|
1798
1854
|
}
|
|
@@ -1814,21 +1870,8 @@ class TextEngine<
|
|
|
1814
1870
|
this.finalizeCurrentThinkingStep()
|
|
1815
1871
|
}
|
|
1816
1872
|
|
|
1817
|
-
private handleStepFinishedEvent(
|
|
1818
|
-
chunk
|
|
1819
|
-
): void {
|
|
1820
|
-
if (!this.hasSeenReasoningEvents) {
|
|
1821
|
-
if (chunk.delta) {
|
|
1822
|
-
this.currentThinkingContent += chunk.delta
|
|
1823
|
-
} else if (chunk.content) {
|
|
1824
|
-
if (chunk.content.startsWith(this.currentThinkingContent)) {
|
|
1825
|
-
this.currentThinkingContent = chunk.content
|
|
1826
|
-
} else if (!this.currentThinkingContent.startsWith(chunk.content)) {
|
|
1827
|
-
this.currentThinkingContent += chunk.content
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
if (chunk.signature) {
|
|
1873
|
+
private handleStepFinishedEvent(chunk: AdapterYieldChunk): void {
|
|
1874
|
+
if (typeof chunk.signature === 'string' && chunk.signature !== '') {
|
|
1832
1875
|
this.currentThinkingSignature = chunk.signature
|
|
1833
1876
|
}
|
|
1834
1877
|
}
|
|
@@ -1836,10 +1879,29 @@ class TextEngine<
|
|
|
1836
1879
|
private handleReasoningMessageContentEvent(
|
|
1837
1880
|
chunk: Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,
|
|
1838
1881
|
): void {
|
|
1839
|
-
this.hasSeenReasoningEvents = true
|
|
1840
1882
|
this.currentThinkingContent += chunk.delta
|
|
1841
1883
|
}
|
|
1842
1884
|
|
|
1885
|
+
private handleReasoningEncryptedValueEvent(
|
|
1886
|
+
chunk: ReasoningEncryptedValueEvent,
|
|
1887
|
+
): void {
|
|
1888
|
+
if (chunk.subtype === 'tool-call') {
|
|
1889
|
+
const call = this.messages
|
|
1890
|
+
.flatMap((message) => message.toolCalls ?? [])
|
|
1891
|
+
.find((toolCall) => toolCall.id === chunk.entityId)
|
|
1892
|
+
if (call) {
|
|
1893
|
+
call.metadata = {
|
|
1894
|
+
...(call.metadata != null && typeof call.metadata === 'object'
|
|
1895
|
+
? call.metadata
|
|
1896
|
+
: {}),
|
|
1897
|
+
thoughtSignature: chunk.encryptedValue,
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
return
|
|
1901
|
+
}
|
|
1902
|
+
this.currentThinkingSignature = chunk.encryptedValue
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1843
1905
|
/**
|
|
1844
1906
|
* Tools available for execution this turn. The discovery tool is dropped
|
|
1845
1907
|
* from the advertised set (`this.tools`) once every lazy tool is discovered,
|
|
@@ -2244,7 +2306,7 @@ class TextEngine<
|
|
|
2244
2306
|
private shouldDeferToolCallRunFinished(chunk: StreamChunk): boolean {
|
|
2245
2307
|
return (
|
|
2246
2308
|
chunk.type === EventType.RUN_FINISHED &&
|
|
2247
|
-
this.
|
|
2309
|
+
this.lastFinishReason === 'tool_calls' &&
|
|
2248
2310
|
this.tools.length > 0 &&
|
|
2249
2311
|
this.toolCallManager.hasToolCalls()
|
|
2250
2312
|
)
|
|
@@ -2276,7 +2338,6 @@ class TextEngine<
|
|
|
2276
2338
|
type: EventType.RUN_STARTED,
|
|
2277
2339
|
runId: finishEvent.runId,
|
|
2278
2340
|
threadId: finishEvent.threadId,
|
|
2279
|
-
model: finishEvent.model,
|
|
2280
2341
|
timestamp: Date.now(),
|
|
2281
2342
|
})
|
|
2282
2343
|
}
|
|
@@ -2289,10 +2350,7 @@ class TextEngine<
|
|
|
2289
2350
|
// `stop` is a finished run, not another tool cycle. `tool_calls` here
|
|
2290
2351
|
// makes the client auto-send after afterTools, so reject looks stuck.
|
|
2291
2352
|
this.lastFinishReason = 'stop'
|
|
2292
|
-
const finishEvent =
|
|
2293
|
-
...this.createSyntheticFinishedEvent(),
|
|
2294
|
-
finishReason: 'stop' as const,
|
|
2295
|
-
}
|
|
2353
|
+
const finishEvent = this.createSyntheticFinishedEvent('stop')
|
|
2296
2354
|
yield* this.emitSyntheticRunStarted(finishEvent)
|
|
2297
2355
|
yield* this.pipeThroughMiddleware({
|
|
2298
2356
|
...finishEvent,
|
|
@@ -2307,7 +2365,7 @@ class TextEngine<
|
|
|
2307
2365
|
|
|
2308
2366
|
private shouldExecuteToolPhase(): boolean {
|
|
2309
2367
|
return (
|
|
2310
|
-
this.
|
|
2368
|
+
this.lastFinishReason === 'tool_calls' &&
|
|
2311
2369
|
this.tools.length > 0 &&
|
|
2312
2370
|
this.toolCallManager.hasToolCalls()
|
|
2313
2371
|
)
|
|
@@ -2689,41 +2747,18 @@ class TextEngine<
|
|
|
2689
2747
|
}
|
|
2690
2748
|
|
|
2691
2749
|
private buildMessagesSnapshotChunk(): StreamChunk {
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
? undefined
|
|
2699
|
-
: JSON.stringify(message.content)
|
|
2700
|
-
const id =
|
|
2701
|
-
message.id ||
|
|
2702
|
-
`snapshot_${this.runIdOverride ?? this.requestId}_${index}`
|
|
2703
|
-
const parts =
|
|
2704
|
-
message.role === 'assistant' &&
|
|
2705
|
-
(message.thinking?.length || message.structuredOutput)
|
|
2706
|
-
? modelMessageToUIMessage(message, id).parts
|
|
2707
|
-
: undefined
|
|
2708
|
-
return {
|
|
2709
|
-
id,
|
|
2710
|
-
role: message.role,
|
|
2711
|
-
...(content !== undefined ? { content } : {}),
|
|
2712
|
-
...(parts ? { parts } : {}),
|
|
2713
|
-
...('toolCalls' in message && message.toolCalls
|
|
2714
|
-
? { toolCalls: message.toolCalls }
|
|
2715
|
-
: {}),
|
|
2716
|
-
...('toolCallId' in message && message.toolCallId
|
|
2717
|
-
? { toolCallId: message.toolCallId }
|
|
2718
|
-
: {}),
|
|
2719
|
-
} as MessagesSnapshotEvent['messages'][number]
|
|
2720
|
-
},
|
|
2721
|
-
)
|
|
2750
|
+
const withIds = this.messages.map((message, index) => ({
|
|
2751
|
+
...message,
|
|
2752
|
+
id:
|
|
2753
|
+
message.id ||
|
|
2754
|
+
`snapshot_${this.runIdOverride ?? this.requestId}_${index}`,
|
|
2755
|
+
}))
|
|
2722
2756
|
return {
|
|
2723
2757
|
type: EventType.MESSAGES_SNAPSHOT,
|
|
2724
2758
|
timestamp: Date.now(),
|
|
2725
|
-
|
|
2726
|
-
|
|
2759
|
+
messages: uiMessagesToWire(
|
|
2760
|
+
modelMessagesToUIMessages(withIds),
|
|
2761
|
+
) as MessagesSnapshotEvent['messages'],
|
|
2727
2762
|
}
|
|
2728
2763
|
}
|
|
2729
2764
|
|
|
@@ -2803,18 +2838,21 @@ class TextEngine<
|
|
|
2803
2838
|
|
|
2804
2839
|
private buildInterruptRunErrorChunk(error: unknown): StreamChunk {
|
|
2805
2840
|
const failure = this.interruptFailure(error)
|
|
2806
|
-
return
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2841
|
+
return withTanstackMetadata(
|
|
2842
|
+
{
|
|
2843
|
+
type: EventType.RUN_ERROR,
|
|
2844
|
+
timestamp: Date.now(),
|
|
2845
|
+
message: failure.message,
|
|
2846
|
+
code: failure.code,
|
|
2847
|
+
},
|
|
2848
|
+
{
|
|
2849
|
+
runId: this.runIdOverride ?? this.requestId,
|
|
2850
|
+
threadId: this.threadId,
|
|
2851
|
+
...(failure.errors !== undefined
|
|
2852
|
+
? { interruptErrors: failure.errors }
|
|
2853
|
+
: {}),
|
|
2854
|
+
},
|
|
2855
|
+
) as StreamChunk
|
|
2818
2856
|
}
|
|
2819
2857
|
|
|
2820
2858
|
private async *emitInterruptRunError(
|
|
@@ -2852,10 +2890,11 @@ class TextEngine<
|
|
|
2852
2890
|
)
|
|
2853
2891
|
let terminalOutputs: Array<StreamChunk>
|
|
2854
2892
|
try {
|
|
2855
|
-
terminalOutputs =
|
|
2856
|
-
this.
|
|
2857
|
-
|
|
2858
|
-
|
|
2893
|
+
terminalOutputs = [
|
|
2894
|
+
...this.emitPublicChunks(
|
|
2895
|
+
await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal),
|
|
2896
|
+
),
|
|
2897
|
+
]
|
|
2859
2898
|
} catch (error) {
|
|
2860
2899
|
yield* this.emitInterruptRunError(error)
|
|
2861
2900
|
return false
|
|
@@ -2866,13 +2905,11 @@ class TextEngine<
|
|
|
2866
2905
|
yield* this.pipeThroughMiddleware({
|
|
2867
2906
|
type: EventType.STATE_SNAPSHOT,
|
|
2868
2907
|
timestamp: Date.now(),
|
|
2869
|
-
model: this.params.model,
|
|
2870
2908
|
snapshot: this.params.state,
|
|
2871
2909
|
})
|
|
2872
2910
|
}
|
|
2873
2911
|
for (const output of terminalOutputs) {
|
|
2874
2912
|
yield this.publicInterruptTerminal(output)
|
|
2875
|
-
this.middlewareCtx.chunkIndex++
|
|
2876
2913
|
}
|
|
2877
2914
|
return true
|
|
2878
2915
|
}
|
|
@@ -3016,18 +3053,17 @@ class TextEngine<
|
|
|
3016
3053
|
|
|
3017
3054
|
private buildToolResultChunks(
|
|
3018
3055
|
results: Array<ToolResult>,
|
|
3019
|
-
|
|
3056
|
+
_finishEvent: RunFinishedEvent,
|
|
3020
3057
|
argsMap?: Map<string, string>,
|
|
3021
|
-
): Array<
|
|
3022
|
-
const chunks: Array<
|
|
3058
|
+
): Array<AdapterYieldChunk> {
|
|
3059
|
+
const chunks: Array<AdapterYieldChunk> = []
|
|
3023
3060
|
|
|
3024
3061
|
for (const result of results) {
|
|
3025
3062
|
// `content` is the canonical value for the tool `ModelMessage` — it may
|
|
3026
3063
|
// be an `Array<ContentPart>` (multimodal) which the adapters convert to
|
|
3027
3064
|
// structured provider output on the next iteration. `wireContent` is the
|
|
3028
|
-
// string form emitted on
|
|
3029
|
-
//
|
|
3030
|
-
// multimodal array travels via the message itself, not the wire event.
|
|
3065
|
+
// string form emitted on TOOL_CALL_RESULT.content (string-only per the
|
|
3066
|
+
// AG-UI spec); the multimodal array travels via the message itself.
|
|
3031
3067
|
const content = normalizeToolResult(result.result)
|
|
3032
3068
|
const wireContent =
|
|
3033
3069
|
typeof content === 'string' ? content : JSON.stringify(content)
|
|
@@ -3035,12 +3071,10 @@ class TextEngine<
|
|
|
3035
3071
|
// argsMap is set only on continuation re-executions, where the adapter
|
|
3036
3072
|
// never streamed these calls. Otherwise it already emitted END, so a
|
|
3037
3073
|
// second one here would be an orphan that fails verifyEvents (#519).
|
|
3038
|
-
// When we do emit END, attach parsed `input`/`output` for TypedStreamChunk.
|
|
3039
3074
|
if (argsMap) {
|
|
3040
3075
|
chunks.push({
|
|
3041
|
-
type:
|
|
3076
|
+
type: EventType.TOOL_CALL_START,
|
|
3042
3077
|
timestamp: Date.now(),
|
|
3043
|
-
model: finishEvent.model,
|
|
3044
3078
|
toolCallId: result.toolCallId,
|
|
3045
3079
|
toolCallName: result.toolName,
|
|
3046
3080
|
toolName: result.toolName,
|
|
@@ -3048,39 +3082,35 @@ class TextEngine<
|
|
|
3048
3082
|
|
|
3049
3083
|
const args = argsMap.get(result.toolCallId) ?? '{}'
|
|
3050
3084
|
chunks.push({
|
|
3051
|
-
type:
|
|
3085
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
3052
3086
|
timestamp: Date.now(),
|
|
3053
|
-
model: finishEvent.model,
|
|
3054
3087
|
toolCallId: result.toolCallId,
|
|
3055
3088
|
delta: args,
|
|
3056
|
-
|
|
3057
|
-
} as StreamChunk)
|
|
3089
|
+
})
|
|
3058
3090
|
|
|
3059
3091
|
chunks.push({
|
|
3060
|
-
type:
|
|
3092
|
+
type: EventType.TOOL_CALL_END,
|
|
3061
3093
|
timestamp: Date.now(),
|
|
3062
|
-
model: finishEvent.model,
|
|
3063
3094
|
toolCallId: result.toolCallId,
|
|
3064
|
-
toolCallName: result.toolName,
|
|
3065
|
-
toolName: result.toolName,
|
|
3066
|
-
result: wireContent,
|
|
3067
|
-
...(result.input !== undefined && { input: result.input }),
|
|
3068
|
-
...(result.output !== undefined && { output: result.output }),
|
|
3069
|
-
...(result.state !== undefined && { state: result.state }),
|
|
3070
3095
|
})
|
|
3071
3096
|
}
|
|
3072
3097
|
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3098
|
+
const parentMessageId = [...this.messages]
|
|
3099
|
+
.reverse()
|
|
3100
|
+
.find((message) => message.role === 'assistant')?.id
|
|
3101
|
+
const resultChunk = {
|
|
3102
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
3076
3103
|
timestamp: Date.now(),
|
|
3077
|
-
|
|
3078
|
-
messageId: this.createId('tool-result'),
|
|
3104
|
+
messageId: parentMessageId || result.toolCallId,
|
|
3079
3105
|
toolCallId: result.toolCallId,
|
|
3080
3106
|
content: wireContent,
|
|
3081
|
-
role: 'tool',
|
|
3082
|
-
|
|
3083
|
-
|
|
3107
|
+
role: 'tool' as const,
|
|
3108
|
+
}
|
|
3109
|
+
chunks.push(
|
|
3110
|
+
(result.state === 'output-error'
|
|
3111
|
+
? withTanstackMetadata(resultChunk, { state: result.state })
|
|
3112
|
+
: resultChunk) as StreamChunk,
|
|
3113
|
+
)
|
|
3084
3114
|
|
|
3085
3115
|
// If a placeholder tool message exists for this toolCallId (created by
|
|
3086
3116
|
// uiMessageToModelMessages for an approval-responded part with no
|
|
@@ -3218,15 +3248,18 @@ class TextEngine<
|
|
|
3218
3248
|
return pending
|
|
3219
3249
|
}
|
|
3220
3250
|
|
|
3221
|
-
private createSyntheticFinishedEvent(
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3251
|
+
private createSyntheticFinishedEvent(
|
|
3252
|
+
finishReason: 'stop' | 'tool_calls' = 'tool_calls',
|
|
3253
|
+
): RunFinishedEvent {
|
|
3254
|
+
return withTanstackMetadata(
|
|
3255
|
+
{
|
|
3256
|
+
type: EventType.RUN_FINISHED,
|
|
3257
|
+
runId: this.runIdOverride ?? this.requestId,
|
|
3258
|
+
threadId: this.threadId,
|
|
3259
|
+
timestamp: Date.now(),
|
|
3260
|
+
},
|
|
3261
|
+
{ finishReason, model: this.params.model },
|
|
3262
|
+
) as RunFinishedEvent
|
|
3230
3263
|
}
|
|
3231
3264
|
|
|
3232
3265
|
private async shouldContinue(): Promise<boolean> {
|
|
@@ -3505,14 +3538,11 @@ class TextEngine<
|
|
|
3505
3538
|
type: EventType.CUSTOM,
|
|
3506
3539
|
name: 'structured-output.start',
|
|
3507
3540
|
value: { messageId: idForStart },
|
|
3508
|
-
model: this.params.model,
|
|
3509
3541
|
timestamp,
|
|
3510
|
-
threadId: this.threadId,
|
|
3511
|
-
...(this.runIdOverride ? { runId: this.runIdOverride } : {}),
|
|
3512
3542
|
}
|
|
3513
3543
|
}
|
|
3514
3544
|
|
|
3515
|
-
const
|
|
3545
|
+
const runChunkMiddleware = (
|
|
3516
3546
|
synthChunk: StreamChunk,
|
|
3517
3547
|
): Promise<Array<StreamChunk>> =>
|
|
3518
3548
|
this.middlewareRunner.runOnChunk(this.middlewareCtx, synthChunk)
|
|
@@ -3522,152 +3552,153 @@ class TextEngine<
|
|
|
3522
3552
|
let runErrorYielded = false
|
|
3523
3553
|
|
|
3524
3554
|
// Pipe chunks through middleware; yield to consumer only when yieldChunks=true
|
|
3525
|
-
for await (const
|
|
3555
|
+
for await (const raw of providerStream) {
|
|
3526
3556
|
// Honor cancellation between chunks (mirrors streamModelResponse).
|
|
3527
3557
|
if (this.isCancelled()) {
|
|
3528
3558
|
break
|
|
3529
3559
|
}
|
|
3530
3560
|
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
chunk.type === EventType.CUSTOM &&
|
|
3535
|
-
chunk.name === 'structured-output.start'
|
|
3536
|
-
) {
|
|
3537
|
-
startEmitted = true
|
|
3538
|
-
}
|
|
3539
|
-
|
|
3540
|
-
// Capture the assistant messageId off any text-message event so the
|
|
3541
|
-
// synthesized start (when needed) uses the SAME id the deltas carry
|
|
3542
|
-
if (!structuredMessageId) {
|
|
3543
|
-
const extracted = extractMessageId(chunk)
|
|
3544
|
-
if (extracted) {
|
|
3545
|
-
structuredMessageId = extracted
|
|
3546
|
-
this.captureStructuredOutputMessageIdentity(extracted)
|
|
3547
|
-
}
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
|
-
// Synthesis only matters for the streaming client path — the agentic
|
|
3551
|
-
// Promise path consumes chunks internally and returns a Promise, so
|
|
3552
|
-
// there's no client-side StreamProcessor to route deltas for.
|
|
3553
|
-
if (this.finalStructuredOutput.yieldChunks) {
|
|
3554
|
-
// Synthesize start before the FIRST TEXT_MESSAGE_* event
|
|
3561
|
+
{
|
|
3562
|
+
const chunk = raw
|
|
3563
|
+
// Detect adapter-emitted structured-output.start so we don't duplicate
|
|
3555
3564
|
if (
|
|
3556
3565
|
!startEmitted &&
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
chunk.type === EventType.TEXT_MESSAGE_END)
|
|
3566
|
+
chunk.type === EventType.CUSTOM &&
|
|
3567
|
+
chunk.name === 'structured-output.start'
|
|
3560
3568
|
) {
|
|
3561
3569
|
startEmitted = true
|
|
3562
|
-
const synthStart = buildSynthesizedStart(chunk.timestamp)
|
|
3563
|
-
const synthOutputs = await pipeThroughMiddleware(synthStart)
|
|
3564
|
-
for (const outputChunk of synthOutputs) {
|
|
3565
|
-
yield outputChunk
|
|
3566
|
-
this.middlewareCtx.chunkIndex++
|
|
3567
|
-
}
|
|
3568
3570
|
}
|
|
3569
3571
|
|
|
3570
|
-
//
|
|
3571
|
-
//
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
for (const outputChunk of synthOutputs) {
|
|
3578
|
-
yield outputChunk
|
|
3579
|
-
this.middlewareCtx.chunkIndex++
|
|
3572
|
+
// Capture the assistant messageId off any text-message event so the
|
|
3573
|
+
// synthesized start (when needed) uses the SAME id the deltas carry
|
|
3574
|
+
if (!structuredMessageId) {
|
|
3575
|
+
const extracted = extractMessageId(chunk)
|
|
3576
|
+
if (extracted) {
|
|
3577
|
+
structuredMessageId = extracted
|
|
3578
|
+
this.captureStructuredOutputMessageIdentity(extracted)
|
|
3580
3579
|
}
|
|
3581
3580
|
}
|
|
3582
|
-
}
|
|
3583
3581
|
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
// The chunk forwarded to middleware/consumers. Replaced below only for
|
|
3600
|
-
// the structured-output.complete event, whose `object` we normalize
|
|
3601
|
-
// (un-widen) so streaming consumers see the same cleaned payload the
|
|
3602
|
-
// Promise<T> path validates and returns.
|
|
3603
|
-
let outboundChunk: StreamChunk = chunk
|
|
3582
|
+
// Synthesis only matters for the streaming client path — the agentic
|
|
3583
|
+
// Promise path consumes chunks internally and returns a Promise, so
|
|
3584
|
+
// there's no client-side StreamProcessor to route deltas for.
|
|
3585
|
+
if (this.finalStructuredOutput.yieldChunks) {
|
|
3586
|
+
// Synthesize start before the FIRST TEXT_MESSAGE_* event
|
|
3587
|
+
if (
|
|
3588
|
+
!startEmitted &&
|
|
3589
|
+
(chunk.type === EventType.TEXT_MESSAGE_START ||
|
|
3590
|
+
chunk.type === EventType.TEXT_MESSAGE_CONTENT ||
|
|
3591
|
+
chunk.type === EventType.TEXT_MESSAGE_END)
|
|
3592
|
+
) {
|
|
3593
|
+
startEmitted = true
|
|
3594
|
+
const synthStart = buildSynthesizedStart(chunk.timestamp)
|
|
3595
|
+
yield* this.emitPublicChunks(await runChunkMiddleware(synthStart))
|
|
3596
|
+
}
|
|
3604
3597
|
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
? this.finalStructuredOutput.normalize(parsed.object)
|
|
3613
|
-
: parsed.object
|
|
3614
|
-
this.structuredOutputResult = {
|
|
3615
|
-
data: object,
|
|
3616
|
-
rawText: parsed.raw,
|
|
3617
|
-
...(parsed.reasoning !== undefined
|
|
3618
|
-
? { reasoning: parsed.reasoning }
|
|
3619
|
-
: {}),
|
|
3598
|
+
// Synthesize start before a pre-delta RUN_ERROR so the client can
|
|
3599
|
+
// construct an errored placeholder structured-output part instead
|
|
3600
|
+
// of a silent UI.
|
|
3601
|
+
if (!startEmitted && chunk.type === EventType.RUN_ERROR) {
|
|
3602
|
+
startEmitted = true
|
|
3603
|
+
const synthStart = buildSynthesizedStart(chunk.timestamp)
|
|
3604
|
+
yield* this.emitPublicChunks(await runChunkMiddleware(synthStart))
|
|
3620
3605
|
}
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
// 7a. Targeted state updates only.
|
|
3609
|
+
// We deliberately do NOT call `handleStreamChunk(chunk)` here — that
|
|
3610
|
+
// would mutate agent-loop state with finalization data:
|
|
3611
|
+
// - TEXT_MESSAGE_CONTENT deltas would pollute `accumulatedContent`
|
|
3612
|
+
// (raw JSON would leak into `info.content` on onFinish)
|
|
3613
|
+
// - RUN_FINISHED would overwrite `finishedEvent` + `lastFinishReason`
|
|
3614
|
+
// (finalization's 'stop' would overwrite the agent-loop's real
|
|
3615
|
+
// finish reason)
|
|
3616
|
+
// - STEP_FINISHED would pollute `currentThinkingContent`
|
|
3617
|
+
// Finalization is a separate phase from the agent loop; its state must
|
|
3618
|
+
// not cross-contaminate. The explicit branches below capture the only
|
|
3619
|
+
// bits we actually need from this stream.
|
|
3620
|
+
// All narrowing below is via the discriminated-union `chunk.type`
|
|
3621
|
+
// — no `as` casts.
|
|
3622
|
+
|
|
3623
|
+
// The chunk forwarded to middleware/consumers. Replaced below only for
|
|
3624
|
+
// the structured-output.complete event, whose `object` we normalize
|
|
3625
|
+
// (un-widen) so streaming consumers see the same cleaned payload the
|
|
3626
|
+
// Promise<T> path validates and returns.
|
|
3627
|
+
let outboundChunk: StreamChunk = chunk
|
|
3628
|
+
|
|
3629
|
+
if (
|
|
3630
|
+
chunk.type === EventType.CUSTOM &&
|
|
3631
|
+
chunk.name === 'structured-output.complete'
|
|
3632
|
+
) {
|
|
3633
|
+
const parsed = readStructuredOutputCompleteValue(chunk.value)
|
|
3634
|
+
if (parsed) {
|
|
3635
|
+
const object = this.finalStructuredOutput.normalize
|
|
3636
|
+
? this.finalStructuredOutput.normalize(parsed.object)
|
|
3637
|
+
: parsed.object
|
|
3638
|
+
this.structuredOutputResult = {
|
|
3639
|
+
data: object,
|
|
3640
|
+
rawText: parsed.raw,
|
|
3641
|
+
...(parsed.reasoning !== undefined
|
|
3642
|
+
? { reasoning: parsed.reasoning }
|
|
3643
|
+
: {}),
|
|
3644
|
+
}
|
|
3645
|
+
// Rewrite the outbound event so the yielded chunk carries the
|
|
3646
|
+
// normalized object (the original `chunk.value` still holds the
|
|
3647
|
+
// widened one). Preserve every other field — `raw`, `reasoning` —
|
|
3648
|
+
// by spreading the original value.
|
|
3649
|
+
const value = chunk.value
|
|
3650
|
+
if (
|
|
3651
|
+
object !== parsed.object &&
|
|
3652
|
+
value &&
|
|
3653
|
+
typeof value === 'object'
|
|
3654
|
+
) {
|
|
3655
|
+
outboundChunk = { ...chunk, value: { ...value, object } }
|
|
3656
|
+
}
|
|
3628
3657
|
}
|
|
3629
3658
|
}
|
|
3630
|
-
}
|
|
3631
3659
|
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
}
|
|
3660
|
+
if (chunk.type === EventType.RUN_FINISHED) {
|
|
3661
|
+
await this.runOnUsageFromChunk(chunk)
|
|
3662
|
+
}
|
|
3636
3663
|
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3664
|
+
if (chunk.type === EventType.RUN_ERROR) {
|
|
3665
|
+
// RunErrorEvent already exposes `message` and `code` after narrowing.
|
|
3666
|
+
this.finalizationError = {
|
|
3667
|
+
message: chunk.message,
|
|
3668
|
+
...(chunk.code ? { code: chunk.code } : {}),
|
|
3669
|
+
...(fallbackAdapterError !== undefined
|
|
3670
|
+
? { cause: fallbackAdapterError }
|
|
3671
|
+
: {}),
|
|
3672
|
+
}
|
|
3645
3673
|
}
|
|
3646
|
-
}
|
|
3647
3674
|
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3675
|
+
// 7b. Pipe through middleware
|
|
3676
|
+
const outputChunks = await this.middlewareRunner.runOnChunk(
|
|
3677
|
+
this.middlewareCtx,
|
|
3678
|
+
outboundChunk,
|
|
3679
|
+
)
|
|
3653
3680
|
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3681
|
+
// 7c. Decide consumer visibility — only yieldChunks=true callers get them.
|
|
3682
|
+
// We do NOT strip the finalization stream's RUN_STARTED/RUN_FINISHED:
|
|
3683
|
+
// they are the single outer lifecycle pair the consumer sees (the
|
|
3684
|
+
// agent-loop's pair was suppressed in streamModelResponse when
|
|
3685
|
+
// finalStructuredOutput.yieldChunks is true).
|
|
3686
|
+
if (this.finalStructuredOutput.yieldChunks) {
|
|
3687
|
+
for (const spec of this.emitPublicChunks(outputChunks)) {
|
|
3688
|
+
if (spec.type === EventType.RUN_ERROR) {
|
|
3689
|
+
runErrorYielded = true
|
|
3690
|
+
}
|
|
3691
|
+
yield spec
|
|
3663
3692
|
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
// 7d. Terminate on error
|
|
3696
|
+
if (this.finalizationError) {
|
|
3697
|
+
break
|
|
3666
3698
|
}
|
|
3667
3699
|
}
|
|
3668
3700
|
|
|
3669
|
-
|
|
3670
|
-
if (this.finalizationError) {
|
|
3701
|
+
if (this.isCancelled() || this.finalizationError) {
|
|
3671
3702
|
break
|
|
3672
3703
|
}
|
|
3673
3704
|
}
|
|
@@ -3730,39 +3761,21 @@ class TextEngine<
|
|
|
3730
3761
|
// `StructuredOutputPart` instead of dropping it as an orphan error.
|
|
3731
3762
|
if (!startEmitted) {
|
|
3732
3763
|
const synthStart = buildSynthesizedStart()
|
|
3733
|
-
|
|
3734
|
-
for (const outputChunk of startOutputs) {
|
|
3735
|
-
yield outputChunk
|
|
3736
|
-
this.middlewareCtx.chunkIndex++
|
|
3737
|
-
}
|
|
3764
|
+
yield* this.emitPublicChunks(await runChunkMiddleware(synthStart))
|
|
3738
3765
|
startEmitted = true
|
|
3739
3766
|
}
|
|
3740
3767
|
|
|
3741
3768
|
const errChunk: StreamChunk = {
|
|
3742
3769
|
type: EventType.RUN_ERROR,
|
|
3743
|
-
runId: this.runIdOverride ?? this.requestId,
|
|
3744
|
-
model: this.params.model,
|
|
3745
3770
|
timestamp: Date.now(),
|
|
3746
|
-
threadId: this.threadId,
|
|
3747
3771
|
message: this.finalizationError.message,
|
|
3748
3772
|
...(this.finalizationError.code
|
|
3749
3773
|
? { code: this.finalizationError.code }
|
|
3750
3774
|
: {}),
|
|
3751
|
-
error: {
|
|
3752
|
-
message: this.finalizationError.message,
|
|
3753
|
-
...(this.finalizationError.code
|
|
3754
|
-
? { code: this.finalizationError.code }
|
|
3755
|
-
: {}),
|
|
3756
|
-
},
|
|
3757
3775
|
}
|
|
3758
|
-
|
|
3759
|
-
this.middlewareCtx,
|
|
3760
|
-
errChunk,
|
|
3776
|
+
yield* this.emitPublicChunks(
|
|
3777
|
+
await this.middlewareRunner.runOnChunk(this.middlewareCtx, errChunk),
|
|
3761
3778
|
)
|
|
3762
|
-
for (const outputChunk of outputChunks) {
|
|
3763
|
-
yield outputChunk
|
|
3764
|
-
this.middlewareCtx.chunkIndex++
|
|
3765
|
-
}
|
|
3766
3779
|
}
|
|
3767
3780
|
}
|
|
3768
3781
|
|
|
@@ -3878,19 +3891,11 @@ class TextEngine<
|
|
|
3878
3891
|
type: EventType.CUSTOM,
|
|
3879
3892
|
name: 'structured-output.start',
|
|
3880
3893
|
value: { messageId },
|
|
3881
|
-
model: this.params.model,
|
|
3882
3894
|
timestamp: Date.now(),
|
|
3883
|
-
threadId: this.threadId,
|
|
3884
|
-
...(this.runIdOverride ? { runId: this.runIdOverride } : {}),
|
|
3885
3895
|
}
|
|
3886
|
-
|
|
3887
|
-
this.middlewareCtx,
|
|
3888
|
-
synthStart,
|
|
3896
|
+
yield* this.emitPublicChunks(
|
|
3897
|
+
await this.middlewareRunner.runOnChunk(this.middlewareCtx, synthStart),
|
|
3889
3898
|
)
|
|
3890
|
-
for (const outputChunk of startOutputs) {
|
|
3891
|
-
yield outputChunk
|
|
3892
|
-
this.middlewareCtx.chunkIndex++
|
|
3893
|
-
}
|
|
3894
3899
|
}
|
|
3895
3900
|
|
|
3896
3901
|
// On success, emit the synthetic `structured-output.complete` carrying
|
|
@@ -3915,19 +3920,14 @@ class TextEngine<
|
|
|
3915
3920
|
? { messageId: this.combinedStructuredMessageId }
|
|
3916
3921
|
: {}),
|
|
3917
3922
|
},
|
|
3918
|
-
model: this.params.model,
|
|
3919
3923
|
timestamp: Date.now(),
|
|
3920
|
-
threadId: this.threadId,
|
|
3921
|
-
...(this.runIdOverride ? { runId: this.runIdOverride } : {}),
|
|
3922
3924
|
}
|
|
3923
|
-
|
|
3924
|
-
this.
|
|
3925
|
-
|
|
3925
|
+
yield* this.emitPublicChunks(
|
|
3926
|
+
await this.middlewareRunner.runOnChunk(
|
|
3927
|
+
this.middlewareCtx,
|
|
3928
|
+
completeChunk,
|
|
3929
|
+
),
|
|
3926
3930
|
)
|
|
3927
|
-
for (const outputChunk of completeOutputs) {
|
|
3928
|
-
yield outputChunk
|
|
3929
|
-
this.middlewareCtx.chunkIndex++
|
|
3930
|
-
}
|
|
3931
3931
|
}
|
|
3932
3932
|
|
|
3933
3933
|
// On failure, emit a synthetic RUN_ERROR so the streaming consumer's
|
|
@@ -3935,29 +3935,15 @@ class TextEngine<
|
|
|
3935
3935
|
if (this.finalizationError) {
|
|
3936
3936
|
const errChunk: StreamChunk = {
|
|
3937
3937
|
type: EventType.RUN_ERROR,
|
|
3938
|
-
runId: this.runIdOverride ?? this.requestId,
|
|
3939
|
-
model: this.params.model,
|
|
3940
3938
|
timestamp: Date.now(),
|
|
3941
|
-
threadId: this.threadId,
|
|
3942
3939
|
message: this.finalizationError.message,
|
|
3943
3940
|
...(this.finalizationError.code
|
|
3944
3941
|
? { code: this.finalizationError.code }
|
|
3945
3942
|
: {}),
|
|
3946
|
-
error: {
|
|
3947
|
-
message: this.finalizationError.message,
|
|
3948
|
-
...(this.finalizationError.code
|
|
3949
|
-
? { code: this.finalizationError.code }
|
|
3950
|
-
: {}),
|
|
3951
|
-
},
|
|
3952
3943
|
}
|
|
3953
|
-
|
|
3954
|
-
this.middlewareCtx,
|
|
3955
|
-
errChunk,
|
|
3944
|
+
yield* this.emitPublicChunks(
|
|
3945
|
+
await this.middlewareRunner.runOnChunk(this.middlewareCtx, errChunk),
|
|
3956
3946
|
)
|
|
3957
|
-
for (const outputChunk of errOutputs) {
|
|
3958
|
-
yield outputChunk
|
|
3959
|
-
this.middlewareCtx.chunkIndex++
|
|
3960
|
-
}
|
|
3961
3947
|
}
|
|
3962
3948
|
}
|
|
3963
3949
|
|
|
@@ -4404,23 +4390,36 @@ class TextEngine<
|
|
|
4404
4390
|
}
|
|
4405
4391
|
|
|
4406
4392
|
/**
|
|
4407
|
-
*
|
|
4408
|
-
* and
|
|
4393
|
+
* Spec-normalize middleware output, then yield to the public iterable.
|
|
4394
|
+
* Engine state and `onChunk` already saw the raw chunk.
|
|
4395
|
+
*/
|
|
4396
|
+
private *emitPublicChunks(
|
|
4397
|
+
outputs: Array<StreamChunk>,
|
|
4398
|
+
): Generator<StreamChunk, void, void> {
|
|
4399
|
+
for (const output of outputs) {
|
|
4400
|
+
for (const spec of normalizeStreamChunk(output as AdapterYieldChunk)) {
|
|
4401
|
+
restorePublicUsage(spec)
|
|
4402
|
+
if (spec.type === EventType.RUN_STARTED) {
|
|
4403
|
+
this.hasPublicRunStarted = true
|
|
4404
|
+
}
|
|
4405
|
+
yield spec
|
|
4406
|
+
this.middlewareCtx.chunkIndex++
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
/**
|
|
4412
|
+
* Pipe a single internal chunk through middleware, then spec-normalize
|
|
4413
|
+
* before the public `for await` stream.
|
|
4409
4414
|
*/
|
|
4410
4415
|
private async *pipeThroughMiddleware(
|
|
4411
|
-
chunk:
|
|
4416
|
+
chunk: AdapterYieldChunk,
|
|
4412
4417
|
): AsyncGenerator<StreamChunk, void, void> {
|
|
4413
|
-
const
|
|
4418
|
+
const afterMw = await this.middlewareRunner.runOnChunk(
|
|
4414
4419
|
this.middlewareCtx,
|
|
4415
4420
|
chunk,
|
|
4416
4421
|
)
|
|
4417
|
-
|
|
4418
|
-
if (outputChunk.type === EventType.RUN_STARTED) {
|
|
4419
|
-
this.hasPublicRunStarted = true
|
|
4420
|
-
}
|
|
4421
|
-
yield outputChunk
|
|
4422
|
-
this.middlewareCtx.chunkIndex++
|
|
4423
|
-
}
|
|
4422
|
+
yield* this.emitPublicChunks(afterMw)
|
|
4424
4423
|
}
|
|
4425
4424
|
|
|
4426
4425
|
/**
|
|
@@ -4470,9 +4469,8 @@ class TextEngine<
|
|
|
4470
4469
|
value: Record<string, any>,
|
|
4471
4470
|
): CustomEvent {
|
|
4472
4471
|
return {
|
|
4473
|
-
type:
|
|
4472
|
+
type: EventType.CUSTOM,
|
|
4474
4473
|
timestamp: Date.now(),
|
|
4475
|
-
model: this.params.model,
|
|
4476
4474
|
name: eventName,
|
|
4477
4475
|
value,
|
|
4478
4476
|
}
|
|
@@ -4983,7 +4981,7 @@ async function* fallbackStructuredOutputStream(
|
|
|
4983
4981
|
adapter: AnyTextAdapter,
|
|
4984
4982
|
options: StructuredOutputOptions<Record<string, unknown>>,
|
|
4985
4983
|
onAdapterError?: (err: unknown) => void,
|
|
4986
|
-
): AsyncIterable<
|
|
4984
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
4987
4985
|
const { chatOptions } = options
|
|
4988
4986
|
// Synthesize run/thread/message IDs only when the caller didn't supply them.
|
|
4989
4987
|
// Prefix `fallback-` (not `mock-`) because this is production fallback code
|