@tanstack/ai 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/esm/activities/chat/adapter.js +23 -16
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agent-loop-strategies.d.ts +5 -36
- package/dist/esm/activities/chat/agent-loop-strategies.js +75 -21
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
- package/dist/esm/activities/chat/cancel.d.ts +40 -0
- package/dist/esm/activities/chat/cancel.js +54 -0
- package/dist/esm/activities/chat/cancel.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +28 -21
- package/dist/esm/activities/chat/index.js +2100 -1813
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
- package/dist/esm/activities/chat/mcp/manager.js +90 -77
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
- package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
- package/dist/esm/activities/chat/messages.js +397 -346
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.js +17 -15
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
- package/dist/esm/activities/chat/middleware/compose.js +623 -531
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.js +12 -5
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
- package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
- package/dist/esm/activities/chat/middleware/locks.js +71 -0
- package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
- package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
- package/dist/esm/activities/chat/middleware/run-store.js +176 -0
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
- package/dist/esm/activities/chat/middleware/validate.js +23 -28
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
- package/dist/esm/activities/chat/stream/json-parser.js +39 -25
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
- package/dist/esm/activities/chat/stream/processor.js +1341 -1542
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.js +69 -53
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
- package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
- package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
- package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/error-payload.js +85 -47
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +22 -15
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +4 -0
- package/dist/esm/activities/generateAudio/index.js +141 -105
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +22 -15
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +4 -0
- package/dist/esm/activities/generateImage/index.js +155 -111
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +22 -15
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
- package/dist/esm/activities/generateSpeech/index.js +159 -110
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +22 -15
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
- package/dist/esm/activities/generateTranscription/index.js +159 -100
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +36 -29
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +143 -19
- package/dist/esm/activities/generateVideo/index.js +456 -279
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/snap.js +60 -48
- package/dist/esm/activities/generateVideo/snap.js.map +1 -1
- package/dist/esm/activities/index.js +8 -34
- package/dist/esm/activities/middleware/index.d.ts +1 -1
- package/dist/esm/activities/middleware/run.d.ts +10 -0
- package/dist/esm/activities/middleware/run.js +53 -29
- package/dist/esm/activities/middleware/run.js.map +1 -1
- package/dist/esm/activities/middleware/types.d.ts +44 -6
- package/dist/esm/activities/stream-generation-result.d.ts +4 -1
- package/dist/esm/activities/stream-generation-result.js +79 -44
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +22 -15
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.d.ts +27 -0
- package/dist/esm/activities/summarize/index.js +268 -102
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -1
- package/dist/esm/adapter-internals.js +4 -11
- package/dist/esm/client.d.ts +25 -3
- package/dist/esm/client.js +131 -64
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/custom-events.d.ts +76 -0
- package/dist/esm/custom-events.js +37 -0
- package/dist/esm/custom-events.js.map +1 -0
- package/dist/esm/delivery-detach.d.ts +50 -0
- package/dist/esm/delivery-detach.js +71 -0
- package/dist/esm/delivery-detach.js.map +1 -0
- package/dist/esm/delivery-disconnect.d.ts +62 -0
- package/dist/esm/delivery-disconnect.js +81 -0
- package/dist/esm/delivery-disconnect.js.map +1 -0
- package/dist/esm/extend-adapter.js +19 -17
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +24 -6
- package/dist/esm/index.js +30 -98
- package/dist/esm/interrupt-resume.d.ts +71 -0
- package/dist/esm/interrupt-resume.js +438 -0
- package/dist/esm/interrupt-resume.js.map +1 -0
- package/dist/esm/interrupt-serialization.d.ts +12 -0
- package/dist/esm/interrupt-serialization.js +178 -0
- package/dist/esm/interrupt-serialization.js.map +1 -0
- package/dist/esm/interrupts.d.ts +84 -0
- package/dist/esm/interrupts.js +31 -0
- package/dist/esm/interrupts.js.map +1 -0
- package/dist/esm/locks.d.ts +10 -0
- package/dist/esm/locks.js +2 -0
- package/dist/esm/logger/console-logger.js +101 -78
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +104 -89
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +54 -49
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/logger/types.d.ts +1 -1
- package/dist/esm/middlewares/content-guard.js +142 -148
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/index.js +2 -6
- package/dist/esm/middlewares/otel.js +598 -732
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.js +47 -40
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/realtime/event-emitter.js +24 -25
- package/dist/esm/realtime/event-emitter.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +5 -9
- package/dist/esm/realtime/index.js +29 -6
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/scope.d.ts +47 -0
- package/dist/esm/stream-durability.d.ts +171 -0
- package/dist/esm/stream-durability.js +295 -0
- package/dist/esm/stream-durability.js.map +1 -0
- package/dist/esm/stream-to-response.d.ts +178 -13
- package/dist/esm/stream-to-response.js +663 -115
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +30 -16
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/system-prompts.js +27 -21
- package/dist/esm/system-prompts.js.map +1 -1
- package/dist/esm/tool-registry.js +72 -45
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/tools/provider-tool.js +14 -5
- package/dist/esm/tools/provider-tool.js.map +1 -1
- package/dist/esm/types.d.ts +321 -42
- package/dist/esm/types.js +2 -0
- package/dist/esm/utilities/ag-ui-wire.js +79 -93
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +26 -4
- package/dist/esm/utilities/chat-params.js +218 -92
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/errors.js +28 -18
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +46 -41
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/numbers.js +13 -10
- package/dist/esm/utilities/numbers.js.map +1 -1
- package/dist/esm/utilities/provider-executed.js +20 -11
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/sampling-keys.js +31 -19
- package/dist/esm/utilities/sampling-keys.js.map +1 -1
- package/dist/esm/utilities/tool-result.js +42 -30
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/dist/esm/utilities/usage.js +27 -9
- package/dist/esm/utilities/usage.js.map +1 -1
- package/dist/esm/utils.js +26 -18
- package/dist/esm/utils.js.map +1 -1
- package/package.json +10 -6
- package/skills/ai-core/SKILL.md +69 -18
- package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
- package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
- package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
- package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +98 -11
- package/skills/ai-core/client-persistence/SKILL.md +277 -0
- package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
- package/skills/ai-core/debug-logging/SKILL.md +1 -1
- package/skills/ai-core/locks/SKILL.md +143 -0
- package/skills/ai-core/media-generation/SKILL.md +144 -12
- package/skills/ai-core/middleware/SKILL.md +258 -33
- package/skills/ai-core/structured-outputs/SKILL.md +1 -1
- package/skills/ai-core/tool-calling/SKILL.md +54 -61
- package/src/activities/chat/agent-loop-strategies.ts +5 -39
- package/src/activities/chat/cancel.ts +81 -0
- package/src/activities/chat/index.ts +1091 -200
- package/src/activities/chat/mcp/manager.ts +4 -4
- package/src/activities/chat/mcp/types.ts +2 -2
- package/src/activities/chat/messages.ts +5 -3
- package/src/activities/chat/middleware/builder.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +186 -9
- package/src/activities/chat/middleware/index.ts +26 -0
- package/src/activities/chat/middleware/locks.ts +102 -0
- package/src/activities/chat/middleware/pending-turn.ts +47 -0
- package/src/activities/chat/middleware/run-disconnect.ts +62 -0
- package/src/activities/chat/middleware/run-store.ts +412 -0
- package/src/activities/chat/middleware/types.ts +62 -1
- package/src/activities/chat/stream/processor.ts +189 -5
- package/src/activities/chat/tools/approval-schema.ts +205 -0
- package/src/activities/chat/tools/tool-calls.ts +106 -13
- package/src/activities/chat/tools/tool-definition.ts +210 -39
- package/src/activities/generateAudio/index.ts +20 -3
- package/src/activities/generateImage/index.ts +20 -3
- package/src/activities/generateSpeech/index.ts +25 -3
- package/src/activities/generateTranscription/index.ts +26 -3
- package/src/activities/generateVideo/index.ts +345 -82
- package/src/activities/middleware/index.ts +2 -0
- package/src/activities/middleware/run.ts +31 -0
- package/src/activities/middleware/types.ts +49 -5
- package/src/activities/stream-generation-result.ts +30 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -0
- package/src/activities/summarize/index.ts +200 -10
- package/src/adapter-internals.ts +10 -1
- package/src/client.ts +244 -0
- package/src/custom-events.ts +107 -0
- package/src/delivery-detach.ts +72 -0
- package/src/delivery-disconnect.ts +84 -0
- package/src/index.ts +138 -1
- package/src/interrupt-resume.ts +824 -0
- package/src/interrupt-serialization.ts +183 -0
- package/src/interrupts.ts +146 -0
- package/src/locks.ts +17 -0
- package/src/logger/types.ts +1 -1
- package/src/middlewares/otel.ts +1 -0
- package/src/realtime/index.ts +5 -9
- package/src/scope.ts +47 -0
- package/src/stream-durability.ts +598 -0
- package/src/stream-to-response.ts +1051 -95
- package/src/strip-to-spec-middleware.ts +3 -3
- package/src/types.ts +405 -45
- package/src/utilities/chat-params.ts +245 -55
- package/dist/esm/activities/index.js.map +0 -1
- package/dist/esm/adapter-internals.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/middlewares/index.js.map +0 -1
|
@@ -1,1548 +1,1347 @@
|
|
|
1
|
-
import { generateMessageId, uiMessageToModelMessages, aguiSnapshotMessageToUIMessage } from "../messages.js";
|
|
2
1
|
import { normalizeToolResult } from "../../../utilities/tool-result.js";
|
|
3
2
|
import { isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
|
|
3
|
+
import { aguiSnapshotMessageToUIMessage, generateMessageId, uiMessageToModelMessages } from "../messages.js";
|
|
4
4
|
import { defaultJSONParser } from "./json-parser.js";
|
|
5
|
-
import {
|
|
5
|
+
import { appendStructuredOutputDelta, completeStructuredOutputPart, errorStructuredOutputPart, updateTextPart, updateThinkingPart, updateToolCallApproval, updateToolCallApprovalResponse, updateToolCallPart, updateToolCallWithOutput, updateToolResultPart } from "./message-updaters.js";
|
|
6
6
|
import { ImmediateStrategy } from "./strategies.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
raw: part.raw,
|
|
1340
|
-
...part.partial !== void 0 ? { partial: part.partial } : {},
|
|
1341
|
-
...part.data !== void 0 ? { data: part.data } : {},
|
|
1342
|
-
...part.reasoning !== void 0 ? { reasoning: part.reasoning } : {},
|
|
1343
|
-
...part.errorMessage !== void 0 ? { errorMessage: part.errorMessage } : {},
|
|
1344
|
-
...delta !== void 0 ? { delta } : {}
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
/**
|
|
1348
|
-
* Emit messages change event
|
|
1349
|
-
*/
|
|
1350
|
-
emitMessagesChange() {
|
|
1351
|
-
this.events.onMessagesChange?.([...this.messages]);
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Finalize the stream — complete all pending operations.
|
|
1355
|
-
*
|
|
1356
|
-
* Called when the async iterable ends (stream closed). Acts as the final
|
|
1357
|
-
* safety net: completes any remaining tool calls, flushes un-emitted text,
|
|
1358
|
-
* and fires onStreamEnd.
|
|
1359
|
-
*
|
|
1360
|
-
* @see docs/chat-architecture.md#single-shot-text-response — Finalization step
|
|
1361
|
-
*/
|
|
1362
|
-
finalizeStream() {
|
|
1363
|
-
let lastAssistantMessage;
|
|
1364
|
-
for (const messageId of this.activeMessageIds) {
|
|
1365
|
-
const state = this.getMessageState(messageId);
|
|
1366
|
-
if (!state) continue;
|
|
1367
|
-
this.completeAllToolCallsForMessage(messageId);
|
|
1368
|
-
if (state.currentSegmentText !== state.lastEmittedText) {
|
|
1369
|
-
this.emitTextUpdateForMessage(messageId);
|
|
1370
|
-
}
|
|
1371
|
-
state.isComplete = true;
|
|
1372
|
-
const msg = this.messages.find((m) => m.id === messageId);
|
|
1373
|
-
if (msg && msg.role === "assistant") {
|
|
1374
|
-
lastAssistantMessage = msg;
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
for (const messageId of this.structuredMessageIds) {
|
|
1378
|
-
this.flushStructuredOutputUpdate(messageId);
|
|
1379
|
-
this.messages = errorStructuredOutputPart(
|
|
1380
|
-
this.messages,
|
|
1381
|
-
messageId,
|
|
1382
|
-
"Stream ended without structured-output.complete"
|
|
1383
|
-
);
|
|
1384
|
-
this.emitStructuredOutputChange(messageId, "error");
|
|
1385
|
-
}
|
|
1386
|
-
this.structuredMessageIds.clear();
|
|
1387
|
-
this.structuredOutputUpdateBatches.clear();
|
|
1388
|
-
this.activeMessageIds.clear();
|
|
1389
|
-
if (lastAssistantMessage && !this.hasError) {
|
|
1390
|
-
if (this.isWhitespaceOnlyMessage(lastAssistantMessage)) {
|
|
1391
|
-
this.messages = this.messages.filter(
|
|
1392
|
-
(m) => m.id !== lastAssistantMessage.id
|
|
1393
|
-
);
|
|
1394
|
-
this.emitMessagesChange();
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
if (lastAssistantMessage) {
|
|
1399
|
-
this.events.onStreamEnd?.(lastAssistantMessage);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
/**
|
|
1403
|
-
* Get completed tool calls in API format (aggregated across all messages)
|
|
1404
|
-
*/
|
|
1405
|
-
getCompletedToolCalls() {
|
|
1406
|
-
const result = [];
|
|
1407
|
-
for (const state of this.messageStates.values()) {
|
|
1408
|
-
for (const tc of state.toolCalls.values()) {
|
|
1409
|
-
if (tc.state === "input-complete") {
|
|
1410
|
-
result.push({
|
|
1411
|
-
id: tc.id,
|
|
1412
|
-
type: "function",
|
|
1413
|
-
function: {
|
|
1414
|
-
name: tc.name,
|
|
1415
|
-
arguments: tc.arguments
|
|
1416
|
-
},
|
|
1417
|
-
// Preserve provider metadata (e.g. Gemini thoughtSignature) on
|
|
1418
|
-
// ProcessorResult.toolCalls so callers using process()/getResult()
|
|
1419
|
-
// get the same round-trip support as the streaming UI path.
|
|
1420
|
-
...tc.metadata !== void 0 && { metadata: tc.metadata }
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
return result;
|
|
1426
|
-
}
|
|
1427
|
-
/**
|
|
1428
|
-
* Get current result (aggregated across all messages)
|
|
1429
|
-
*/
|
|
1430
|
-
getResult() {
|
|
1431
|
-
const toolCalls = this.getCompletedToolCalls();
|
|
1432
|
-
let content = "";
|
|
1433
|
-
let thinking = "";
|
|
1434
|
-
for (const state of this.messageStates.values()) {
|
|
1435
|
-
content += state.totalTextContent;
|
|
1436
|
-
for (const stepId of state.thinkingStepOrder) {
|
|
1437
|
-
thinking += state.thinkingSteps.get(stepId) ?? "";
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
return {
|
|
1441
|
-
content,
|
|
1442
|
-
thinking: thinking || void 0,
|
|
1443
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1444
|
-
finishReason: this.finishReason
|
|
1445
|
-
};
|
|
1446
|
-
}
|
|
1447
|
-
/**
|
|
1448
|
-
* Get current processor state (aggregated across all messages)
|
|
1449
|
-
*/
|
|
1450
|
-
getState() {
|
|
1451
|
-
let content = "";
|
|
1452
|
-
let thinking = "";
|
|
1453
|
-
const toolCalls = /* @__PURE__ */ new Map();
|
|
1454
|
-
const toolCallOrder = [];
|
|
1455
|
-
for (const state of this.messageStates.values()) {
|
|
1456
|
-
content += state.totalTextContent;
|
|
1457
|
-
for (const stepId of state.thinkingStepOrder) {
|
|
1458
|
-
thinking += state.thinkingSteps.get(stepId) ?? "";
|
|
1459
|
-
}
|
|
1460
|
-
for (const [id, tc] of state.toolCalls) {
|
|
1461
|
-
toolCalls.set(id, tc);
|
|
1462
|
-
}
|
|
1463
|
-
toolCallOrder.push(...state.toolCallOrder);
|
|
1464
|
-
}
|
|
1465
|
-
return {
|
|
1466
|
-
content,
|
|
1467
|
-
thinking,
|
|
1468
|
-
toolCalls,
|
|
1469
|
-
toolCallOrder,
|
|
1470
|
-
finishReason: this.finishReason,
|
|
1471
|
-
done: this.isDone
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
/**
|
|
1475
|
-
* Start recording chunks
|
|
1476
|
-
*/
|
|
1477
|
-
startRecording() {
|
|
1478
|
-
this.recordingEnabled = true;
|
|
1479
|
-
this.recordingStartTime = Date.now();
|
|
1480
|
-
this.recording = {
|
|
1481
|
-
version: "1.0",
|
|
1482
|
-
timestamp: this.recordingStartTime,
|
|
1483
|
-
chunks: []
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* Get the current recording
|
|
1488
|
-
*/
|
|
1489
|
-
getRecording() {
|
|
1490
|
-
return this.recording;
|
|
1491
|
-
}
|
|
1492
|
-
/**
|
|
1493
|
-
* Reset stream state (but keep messages)
|
|
1494
|
-
*/
|
|
1495
|
-
resetStreamState() {
|
|
1496
|
-
this.messageStates.clear();
|
|
1497
|
-
this.activeMessageIds.clear();
|
|
1498
|
-
this.activeRuns.clear();
|
|
1499
|
-
this.toolCallToMessage.clear();
|
|
1500
|
-
this.structuredMessageIds.clear();
|
|
1501
|
-
this.structuredOutputUpdateBatches.clear();
|
|
1502
|
-
this.pendingManualMessageId = null;
|
|
1503
|
-
this.pendingThinkingStepId = null;
|
|
1504
|
-
this.finishReason = null;
|
|
1505
|
-
this.hasError = false;
|
|
1506
|
-
this.isDone = false;
|
|
1507
|
-
this.chunkStrategy.reset?.();
|
|
1508
|
-
}
|
|
1509
|
-
/**
|
|
1510
|
-
* Full reset (including messages)
|
|
1511
|
-
*/
|
|
1512
|
-
reset() {
|
|
1513
|
-
this.resetStreamState();
|
|
1514
|
-
this.messages = [];
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Check if a message contains only whitespace text and no other meaningful parts
|
|
1518
|
-
* (no tool calls, tool results, thinking, etc.)
|
|
1519
|
-
*/
|
|
1520
|
-
isWhitespaceOnlyMessage(message) {
|
|
1521
|
-
if (message.parts.length === 0) return false;
|
|
1522
|
-
return message.parts.every(
|
|
1523
|
-
(part) => part.type === "text" && part.content.trim() === ""
|
|
1524
|
-
);
|
|
1525
|
-
}
|
|
1526
|
-
/**
|
|
1527
|
-
* Replay a recording through the processor
|
|
1528
|
-
*/
|
|
1529
|
-
static async replay(recording, options) {
|
|
1530
|
-
const processor = new StreamProcessor(options);
|
|
1531
|
-
return processor.process(createReplayStream(recording));
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
7
|
+
//#region src/activities/chat/stream/processor.ts
|
|
8
|
+
/**
|
|
9
|
+
* Unified Stream Processor
|
|
10
|
+
*
|
|
11
|
+
* Core stream processing engine that manages the full UIMessage[] conversation.
|
|
12
|
+
* Single source of truth for message state.
|
|
13
|
+
*
|
|
14
|
+
* Handles:
|
|
15
|
+
* - Full conversation management (UIMessage[])
|
|
16
|
+
* - Text content accumulation with configurable chunking strategies
|
|
17
|
+
* - Parallel tool calls with lifecycle state tracking
|
|
18
|
+
* - Tool results and approval flows
|
|
19
|
+
* - Thinking/reasoning content
|
|
20
|
+
* - Recording/replay for testing
|
|
21
|
+
* - Event-driven architecture for UI updates
|
|
22
|
+
* - Per-message stream state tracking for multi-message sessions
|
|
23
|
+
*
|
|
24
|
+
* @see docs/chat-architecture.md — Canonical reference for AG-UI chunk ordering,
|
|
25
|
+
* adapter contract, single-shot flows, and expected UIMessage output.
|
|
26
|
+
*/
|
|
27
|
+
var STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE = 12;
|
|
28
|
+
/**
|
|
29
|
+
* StreamProcessor - State machine for processing AI response streams
|
|
30
|
+
*
|
|
31
|
+
* Manages the full UIMessage[] conversation and emits events on changes.
|
|
32
|
+
* Trusts the adapter contract: adapters emit clean AG-UI events in the
|
|
33
|
+
* correct order.
|
|
34
|
+
*
|
|
35
|
+
* State tracking:
|
|
36
|
+
* - Full message array
|
|
37
|
+
* - Per-message stream state (text, tool calls, thinking)
|
|
38
|
+
* - Multiple concurrent message streams
|
|
39
|
+
* - Tool call completion via TOOL_CALL_END events
|
|
40
|
+
*
|
|
41
|
+
* @see docs/chat-architecture.md#streamprocessor-internal-state — State field reference
|
|
42
|
+
* @see docs/chat-architecture.md#adapter-contract — What this class expects from adapters
|
|
43
|
+
*/
|
|
44
|
+
var StreamProcessor = class StreamProcessor {
|
|
45
|
+
chunkStrategy;
|
|
46
|
+
events;
|
|
47
|
+
jsonParser;
|
|
48
|
+
recordingEnabled;
|
|
49
|
+
messages = [];
|
|
50
|
+
messageStates = /* @__PURE__ */ new Map();
|
|
51
|
+
activeMessageIds = /* @__PURE__ */ new Set();
|
|
52
|
+
toolCallToMessage = /* @__PURE__ */ new Map();
|
|
53
|
+
pendingManualMessageId = null;
|
|
54
|
+
pendingThinkingStepId = null;
|
|
55
|
+
structuredMessageIds = /* @__PURE__ */ new Set();
|
|
56
|
+
structuredOutputUpdateBatches = /* @__PURE__ */ new Map();
|
|
57
|
+
activeRuns = /* @__PURE__ */ new Set();
|
|
58
|
+
finishReason = null;
|
|
59
|
+
hasError = false;
|
|
60
|
+
isDone = false;
|
|
61
|
+
recording = null;
|
|
62
|
+
recordingStartTime = 0;
|
|
63
|
+
constructor(options = {}) {
|
|
64
|
+
this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy();
|
|
65
|
+
this.events = options.events || {};
|
|
66
|
+
this.jsonParser = options.jsonParser || defaultJSONParser;
|
|
67
|
+
this.recordingEnabled = options.recording ?? false;
|
|
68
|
+
if (options.initialMessages) this.messages = [...options.initialMessages];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Set the messages array (e.g., from persisted state)
|
|
72
|
+
*/
|
|
73
|
+
setMessages(messages) {
|
|
74
|
+
this.messages = [...messages];
|
|
75
|
+
this.emitMessagesChange();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Add a user message to the conversation.
|
|
79
|
+
* Supports both simple string content and multimodal content arrays.
|
|
80
|
+
*
|
|
81
|
+
* @param content - The message content (string or array of content parts)
|
|
82
|
+
* @param id - Optional custom message ID (generated if not provided)
|
|
83
|
+
* @returns The created UIMessage
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* // Simple text message
|
|
88
|
+
* processor.addUserMessage('Hello!')
|
|
89
|
+
*
|
|
90
|
+
* // Multimodal message with image
|
|
91
|
+
* processor.addUserMessage([
|
|
92
|
+
* { type: 'text', content: 'What is in this image?' },
|
|
93
|
+
* { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }
|
|
94
|
+
* ])
|
|
95
|
+
*
|
|
96
|
+
* // With custom ID
|
|
97
|
+
* processor.addUserMessage('Hello!', 'custom-id-123')
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
addUserMessage(content, id) {
|
|
101
|
+
const parts = typeof content === "string" ? [{
|
|
102
|
+
type: "text",
|
|
103
|
+
content
|
|
104
|
+
}] : content.map((part) => {
|
|
105
|
+
return part;
|
|
106
|
+
});
|
|
107
|
+
const userMessage = {
|
|
108
|
+
id: id ?? generateMessageId(),
|
|
109
|
+
role: "user",
|
|
110
|
+
parts,
|
|
111
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
112
|
+
};
|
|
113
|
+
this.messages = [...this.messages, userMessage];
|
|
114
|
+
this.emitMessagesChange();
|
|
115
|
+
return userMessage;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Prepare for a new assistant message stream.
|
|
119
|
+
* Does NOT create the message immediately -- the message is created lazily
|
|
120
|
+
* when the first content-bearing chunk arrives via ensureAssistantMessage().
|
|
121
|
+
* This prevents empty assistant messages from flickering in the UI when
|
|
122
|
+
* auto-continuation produces no content.
|
|
123
|
+
*/
|
|
124
|
+
prepareAssistantMessage() {
|
|
125
|
+
this.resetStreamState();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* @deprecated Use prepareAssistantMessage() instead. This eagerly creates
|
|
129
|
+
* an assistant message which can cause empty message flicker.
|
|
130
|
+
*/
|
|
131
|
+
startAssistantMessage(messageId) {
|
|
132
|
+
this.prepareAssistantMessage();
|
|
133
|
+
const { messageId: id } = this.ensureAssistantMessage(messageId);
|
|
134
|
+
this.pendingManualMessageId = id;
|
|
135
|
+
return id;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get the current assistant message ID (if one has been created).
|
|
139
|
+
* Returns null if prepareAssistantMessage() was called but no content
|
|
140
|
+
* has arrived yet.
|
|
141
|
+
*/
|
|
142
|
+
getCurrentAssistantMessageId() {
|
|
143
|
+
let lastId = null;
|
|
144
|
+
for (const [id, state] of this.messageStates) if (state.role === "assistant") lastId = id;
|
|
145
|
+
return lastId;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Add a tool result (called by client after handling onToolCall)
|
|
149
|
+
*/
|
|
150
|
+
addToolResult(toolCallId, output, error) {
|
|
151
|
+
const messageWithToolCall = this.messages.find((msg) => msg.parts.some((p) => p.type === "tool-call" && p.id === toolCallId));
|
|
152
|
+
if (!messageWithToolCall) {
|
|
153
|
+
console.warn(`[StreamProcessor] Could not find message with tool call ${toolCallId}`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
let updatedMessages = updateToolCallWithOutput(this.messages, toolCallId, output, error ? "error" : void 0, error);
|
|
157
|
+
const content = normalizeToolResult(output);
|
|
158
|
+
const toolResultState = error ? "error" : "complete";
|
|
159
|
+
updatedMessages = updateToolResultPart(updatedMessages, messageWithToolCall.id, toolCallId, content, toolResultState, error);
|
|
160
|
+
this.messages = updatedMessages;
|
|
161
|
+
this.emitMessagesChange();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Add an approval response (called by client after handling onApprovalRequest)
|
|
165
|
+
*/
|
|
166
|
+
addToolApprovalResponse(approvalId, approved) {
|
|
167
|
+
this.messages = updateToolCallApprovalResponse(this.messages, approvalId, approved);
|
|
168
|
+
this.emitMessagesChange();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get the conversation as ModelMessages (for sending to LLM)
|
|
172
|
+
*/
|
|
173
|
+
toModelMessages() {
|
|
174
|
+
const modelMessages = [];
|
|
175
|
+
for (const msg of this.messages) modelMessages.push(...uiMessageToModelMessages(msg));
|
|
176
|
+
return modelMessages;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get current messages
|
|
180
|
+
*/
|
|
181
|
+
getMessages() {
|
|
182
|
+
return this.messages;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if all tool calls in the last assistant message are complete
|
|
186
|
+
* Useful for auto-continue logic
|
|
187
|
+
*/
|
|
188
|
+
areAllToolsComplete() {
|
|
189
|
+
const lastAssistant = this.messages.findLast((m) => m.role === "assistant");
|
|
190
|
+
if (!lastAssistant) return true;
|
|
191
|
+
const toolParts = lastAssistant.parts.filter((p) => p.type === "tool-call");
|
|
192
|
+
if (toolParts.length === 0) return true;
|
|
193
|
+
const toolResultIds = new Set(lastAssistant.parts.filter((p) => p.type === "tool-result").map((p) => p.toolCallId));
|
|
194
|
+
return toolParts.every((part) => part.state === "complete" || part.state === "approval-responded" || part.output !== void 0 && !part.approval || toolResultIds.has(part.id) || isProviderExecutedToolCall(part));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Remove messages after a certain index (for reload/retry)
|
|
198
|
+
*/
|
|
199
|
+
removeMessagesAfter(index) {
|
|
200
|
+
const keptIds = new Set(this.messages.slice(0, index + 1).map((m) => m.id));
|
|
201
|
+
for (const id of this.structuredMessageIds) if (!keptIds.has(id)) this.structuredMessageIds.delete(id);
|
|
202
|
+
for (const id of this.structuredOutputUpdateBatches.keys()) if (!keptIds.has(id)) this.structuredOutputUpdateBatches.delete(id);
|
|
203
|
+
for (const id of this.messageStates.keys()) if (!keptIds.has(id)) this.messageStates.delete(id);
|
|
204
|
+
for (const [toolCallId, msgId] of this.toolCallToMessage) if (!keptIds.has(msgId)) this.toolCallToMessage.delete(toolCallId);
|
|
205
|
+
for (const id of this.activeMessageIds) if (!keptIds.has(id)) this.activeMessageIds.delete(id);
|
|
206
|
+
this.messages = this.messages.slice(0, index + 1);
|
|
207
|
+
this.emitMessagesChange();
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Clear all messages
|
|
211
|
+
*/
|
|
212
|
+
clearMessages() {
|
|
213
|
+
this.messages = [];
|
|
214
|
+
this.messageStates.clear();
|
|
215
|
+
this.activeMessageIds.clear();
|
|
216
|
+
this.toolCallToMessage.clear();
|
|
217
|
+
this.structuredMessageIds.clear();
|
|
218
|
+
this.structuredOutputUpdateBatches.clear();
|
|
219
|
+
this.pendingManualMessageId = null;
|
|
220
|
+
this.emitMessagesChange();
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Process a stream and emit events through handlers
|
|
224
|
+
*/
|
|
225
|
+
async process(stream) {
|
|
226
|
+
this.resetStreamState();
|
|
227
|
+
if (this.recordingEnabled) this.startRecording();
|
|
228
|
+
for await (const chunk of stream) this.processChunk(chunk);
|
|
229
|
+
this.finalizeStream();
|
|
230
|
+
if (this.recording) this.recording.result = this.getResult();
|
|
231
|
+
return this.getResult();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Process a single chunk from the stream.
|
|
235
|
+
*
|
|
236
|
+
* Central dispatch for all AG-UI events. Each event type maps to a specific
|
|
237
|
+
* handler. Events not listed in the switch are intentionally ignored
|
|
238
|
+
* (STEP_STARTED, STATE_SNAPSHOT, STATE_DELTA).
|
|
239
|
+
*
|
|
240
|
+
* @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering
|
|
241
|
+
*/
|
|
242
|
+
processChunk(chunk) {
|
|
243
|
+
if (this.recording) this.recording.chunks.push({
|
|
244
|
+
chunk,
|
|
245
|
+
timestamp: Date.now(),
|
|
246
|
+
index: this.recording.chunks.length
|
|
247
|
+
});
|
|
248
|
+
switch (chunk.type) {
|
|
249
|
+
case "TEXT_MESSAGE_START":
|
|
250
|
+
this.handleTextMessageStartEvent(chunk);
|
|
251
|
+
break;
|
|
252
|
+
case "TEXT_MESSAGE_CONTENT":
|
|
253
|
+
this.handleTextMessageContentEvent(chunk);
|
|
254
|
+
break;
|
|
255
|
+
case "TEXT_MESSAGE_END":
|
|
256
|
+
this.handleTextMessageEndEvent(chunk);
|
|
257
|
+
break;
|
|
258
|
+
case "TOOL_CALL_START":
|
|
259
|
+
this.handleToolCallStartEvent(chunk);
|
|
260
|
+
break;
|
|
261
|
+
case "TOOL_CALL_ARGS":
|
|
262
|
+
this.handleToolCallArgsEvent(chunk);
|
|
263
|
+
break;
|
|
264
|
+
case "TOOL_CALL_END":
|
|
265
|
+
this.handleToolCallEndEvent(chunk);
|
|
266
|
+
break;
|
|
267
|
+
case "RUN_FINISHED":
|
|
268
|
+
this.handleRunFinishedEvent(chunk);
|
|
269
|
+
break;
|
|
270
|
+
case "RUN_ERROR":
|
|
271
|
+
this.handleRunErrorEvent(chunk);
|
|
272
|
+
break;
|
|
273
|
+
case "STEP_FINISHED":
|
|
274
|
+
this.handleStepFinishedEvent(chunk);
|
|
275
|
+
break;
|
|
276
|
+
case "MESSAGES_SNAPSHOT":
|
|
277
|
+
this.handleMessagesSnapshotEvent(chunk);
|
|
278
|
+
break;
|
|
279
|
+
case "CUSTOM":
|
|
280
|
+
this.handleCustomEvent(chunk);
|
|
281
|
+
break;
|
|
282
|
+
case "RUN_STARTED":
|
|
283
|
+
this.handleRunStartedEvent(chunk);
|
|
284
|
+
break;
|
|
285
|
+
case "REASONING_START":
|
|
286
|
+
case "REASONING_MESSAGE_START":
|
|
287
|
+
case "REASONING_MESSAGE_END":
|
|
288
|
+
case "REASONING_END": break;
|
|
289
|
+
case "REASONING_MESSAGE_CONTENT":
|
|
290
|
+
this.handleReasoningMessageContentEvent(chunk);
|
|
291
|
+
break;
|
|
292
|
+
case "TOOL_CALL_RESULT":
|
|
293
|
+
this.handleToolCallResultEvent(chunk);
|
|
294
|
+
break;
|
|
295
|
+
case "STEP_STARTED":
|
|
296
|
+
this.handleStepStartedEvent(chunk);
|
|
297
|
+
break;
|
|
298
|
+
default: break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Create a new MessageStreamState for a message
|
|
303
|
+
*/
|
|
304
|
+
createMessageState(messageId, role) {
|
|
305
|
+
const state = {
|
|
306
|
+
id: messageId,
|
|
307
|
+
role,
|
|
308
|
+
totalTextContent: "",
|
|
309
|
+
currentSegmentText: "",
|
|
310
|
+
lastEmittedText: "",
|
|
311
|
+
hasSeenReasoningEvents: false,
|
|
312
|
+
thinkingSteps: /* @__PURE__ */ new Map(),
|
|
313
|
+
thinkingStepSignatures: /* @__PURE__ */ new Map(),
|
|
314
|
+
thinkingStepOrder: [],
|
|
315
|
+
currentThinkingStepId: null,
|
|
316
|
+
toolCalls: /* @__PURE__ */ new Map(),
|
|
317
|
+
toolCallOrder: [],
|
|
318
|
+
hasToolCallsSinceTextStart: false,
|
|
319
|
+
isComplete: false
|
|
320
|
+
};
|
|
321
|
+
this.messageStates.set(messageId, state);
|
|
322
|
+
return state;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get the MessageStreamState for a message
|
|
326
|
+
*/
|
|
327
|
+
getMessageState(messageId) {
|
|
328
|
+
return this.messageStates.get(messageId);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Promote a pending stepId from a STEP_STARTED that fired before the
|
|
332
|
+
* assistant message existed onto the given message state, so the next
|
|
333
|
+
* thinking event (STEP_FINISHED or REASONING_MESSAGE_CONTENT) attributes
|
|
334
|
+
* to the correct step.
|
|
335
|
+
*/
|
|
336
|
+
consumePendingThinkingStep(state) {
|
|
337
|
+
if (!this.pendingThinkingStepId) return;
|
|
338
|
+
const stepId = this.pendingThinkingStepId;
|
|
339
|
+
state.currentThinkingStepId = stepId;
|
|
340
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
341
|
+
state.thinkingSteps.set(stepId, "");
|
|
342
|
+
state.thinkingStepOrder.push(stepId);
|
|
343
|
+
}
|
|
344
|
+
this.pendingThinkingStepId = null;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get the most recent active assistant message ID.
|
|
348
|
+
* Used as fallback for events that don't include a messageId.
|
|
349
|
+
*/
|
|
350
|
+
getActiveAssistantMessageId() {
|
|
351
|
+
const ids = Array.from(this.activeMessageIds).reverse();
|
|
352
|
+
for (const id of ids) {
|
|
353
|
+
const state = this.messageStates.get(id);
|
|
354
|
+
if (state && state.role === "assistant") return id;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Ensure an active assistant message exists, creating one if needed.
|
|
360
|
+
* Used for backward compat when events arrive without prior TEXT_MESSAGE_START.
|
|
361
|
+
*
|
|
362
|
+
* On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that
|
|
363
|
+
* already exists in this.messages (e.g. from initialMessages or a prior
|
|
364
|
+
* MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we
|
|
365
|
+
* hydrate state from the existing message rather than creating a duplicate.
|
|
366
|
+
*/
|
|
367
|
+
ensureAssistantMessage(preferredId) {
|
|
368
|
+
if (preferredId) {
|
|
369
|
+
const state = this.getMessageState(preferredId);
|
|
370
|
+
if (state) return {
|
|
371
|
+
messageId: preferredId,
|
|
372
|
+
state
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const activeId = this.getActiveAssistantMessageId();
|
|
376
|
+
if (activeId) {
|
|
377
|
+
const state = this.getMessageState(activeId);
|
|
378
|
+
if (state) return {
|
|
379
|
+
messageId: activeId,
|
|
380
|
+
state
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (preferredId) {
|
|
384
|
+
const existingMsg = this.messages.find((m) => m.id === preferredId);
|
|
385
|
+
if (existingMsg) {
|
|
386
|
+
const state = this.createMessageState(preferredId, existingMsg.role);
|
|
387
|
+
this.activeMessageIds.add(preferredId);
|
|
388
|
+
const lastPart = existingMsg.parts.length > 0 ? existingMsg.parts[existingMsg.parts.length - 1] : null;
|
|
389
|
+
if (lastPart && lastPart.type === "text") {
|
|
390
|
+
state.currentSegmentText = lastPart.content;
|
|
391
|
+
state.lastEmittedText = lastPart.content;
|
|
392
|
+
state.totalTextContent = lastPart.content;
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
messageId: preferredId,
|
|
396
|
+
state
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const id = preferredId || generateMessageId();
|
|
401
|
+
const assistantMessage = {
|
|
402
|
+
id,
|
|
403
|
+
role: "assistant",
|
|
404
|
+
parts: [],
|
|
405
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
406
|
+
};
|
|
407
|
+
this.messages = [...this.messages, assistantMessage];
|
|
408
|
+
const state = this.createMessageState(id, "assistant");
|
|
409
|
+
this.activeMessageIds.add(id);
|
|
410
|
+
this.pendingManualMessageId = id;
|
|
411
|
+
this.events.onStreamStart?.();
|
|
412
|
+
this.emitMessagesChange();
|
|
413
|
+
return {
|
|
414
|
+
messageId: id,
|
|
415
|
+
state
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Handle TEXT_MESSAGE_START event
|
|
420
|
+
*/
|
|
421
|
+
handleTextMessageStartEvent(chunk) {
|
|
422
|
+
const { messageId, role } = chunk;
|
|
423
|
+
const uiRole = role === "user" || role === "system" ? role : "assistant";
|
|
424
|
+
if (this.pendingManualMessageId) {
|
|
425
|
+
const pendingId = this.pendingManualMessageId;
|
|
426
|
+
this.pendingManualMessageId = null;
|
|
427
|
+
if (pendingId !== messageId) {
|
|
428
|
+
this.messages = this.messages.map((msg) => msg.id === pendingId ? {
|
|
429
|
+
...msg,
|
|
430
|
+
id: messageId
|
|
431
|
+
} : msg);
|
|
432
|
+
const existingState = this.messageStates.get(pendingId);
|
|
433
|
+
if (existingState) {
|
|
434
|
+
existingState.id = messageId;
|
|
435
|
+
this.messageStates.delete(pendingId);
|
|
436
|
+
this.messageStates.set(messageId, existingState);
|
|
437
|
+
}
|
|
438
|
+
this.activeMessageIds.delete(pendingId);
|
|
439
|
+
this.activeMessageIds.add(messageId);
|
|
440
|
+
}
|
|
441
|
+
if (!this.messageStates.has(messageId)) {
|
|
442
|
+
this.createMessageState(messageId, uiRole);
|
|
443
|
+
this.activeMessageIds.add(messageId);
|
|
444
|
+
}
|
|
445
|
+
this.emitMessagesChange();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (this.messages.find((m) => m.id === messageId)) {
|
|
449
|
+
this.activeMessageIds.add(messageId);
|
|
450
|
+
const existingState = this.messageStates.get(messageId);
|
|
451
|
+
if (!existingState) this.createMessageState(messageId, uiRole);
|
|
452
|
+
else if (existingState.hasToolCallsSinceTextStart) {
|
|
453
|
+
if (existingState.currentSegmentText !== existingState.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
454
|
+
existingState.currentSegmentText = "";
|
|
455
|
+
existingState.lastEmittedText = "";
|
|
456
|
+
existingState.hasToolCallsSinceTextStart = false;
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const newMessage = {
|
|
461
|
+
id: messageId,
|
|
462
|
+
role: uiRole,
|
|
463
|
+
parts: [],
|
|
464
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
465
|
+
};
|
|
466
|
+
this.messages = [...this.messages, newMessage];
|
|
467
|
+
this.createMessageState(messageId, uiRole);
|
|
468
|
+
this.activeMessageIds.add(messageId);
|
|
469
|
+
this.events.onStreamStart?.();
|
|
470
|
+
this.emitMessagesChange();
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Handle TEXT_MESSAGE_END event
|
|
474
|
+
*/
|
|
475
|
+
handleTextMessageEndEvent(chunk) {
|
|
476
|
+
const { messageId } = chunk;
|
|
477
|
+
const state = this.getMessageState(messageId);
|
|
478
|
+
if (!state) return;
|
|
479
|
+
if (state.isComplete) return;
|
|
480
|
+
if (state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
481
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Handle MESSAGES_SNAPSHOT event
|
|
485
|
+
*/
|
|
486
|
+
handleMessagesSnapshotEvent(chunk) {
|
|
487
|
+
this.resetStreamState();
|
|
488
|
+
const prevMessages = this.messages;
|
|
489
|
+
const normalized = chunk.messages.map(aguiSnapshotMessageToUIMessage);
|
|
490
|
+
this.messages = this.reconcileSnapshotToolCalls(normalized, prevMessages);
|
|
491
|
+
this.emitMessagesChange();
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Reconcile a freshly normalized snapshot with the pre-snapshot message
|
|
495
|
+
* state so unreconstructable tool-call metadata is preserved.
|
|
496
|
+
*
|
|
497
|
+
* Post-pass (a): anchor `tool-result`-only assistant messages (the shape
|
|
498
|
+
* `aguiSnapshotMessageToUIMessage` emits for AG-UI `role: 'tool'` wire
|
|
499
|
+
* messages) into the message containing the matching `tool-call` part, or —
|
|
500
|
+
* when the snapshot supplies no such part — the nearest earlier anchorable
|
|
501
|
+
* assistant message, matching the in-stream fan-out shape
|
|
502
|
+
* `assistant: [text, tool-call, tool-result, ...]`. Detached messages with
|
|
503
|
+
* no earlier anchorable assistant are kept verbatim.
|
|
504
|
+
*
|
|
505
|
+
* Post-pass (b): when a `tool-result` part references a `toolCallId` whose
|
|
506
|
+
* `tool-call` part is absent from the snapshot, carry the `tool-call` part
|
|
507
|
+
* forward from the pre-snapshot state (state and output untouched) so a
|
|
508
|
+
* subsequent `addToolResult(toolCallId)` can still locate the call.
|
|
509
|
+
*
|
|
510
|
+
* Post-pass (c): AG-UI wire snapshots rebuild `tool-call` parts as
|
|
511
|
+
* `input-complete` without `output` (ModelMessage has no result field on
|
|
512
|
+
* the call). After anchoring results, copy each `tool-result` onto its
|
|
513
|
+
* matching `tool-call` (and prefer pre-snapshot complete/output when the
|
|
514
|
+
* snapshot is poorer) so server tools keep the same UI shape as client tools.
|
|
515
|
+
*/
|
|
516
|
+
reconcileSnapshotToolCalls(snapshot, prevMessages) {
|
|
517
|
+
const prevToolCalls = /* @__PURE__ */ new Map();
|
|
518
|
+
for (const msg of prevMessages) for (const part of msg.parts) if (part.type === "tool-call") prevToolCalls.set(part.id, part);
|
|
519
|
+
const snapshotToolCallIds = /* @__PURE__ */ new Set();
|
|
520
|
+
for (const msg of snapshot) for (const part of msg.parts) if (part.type === "tool-call") snapshotToolCallIds.add(part.id);
|
|
521
|
+
const reconciled = [];
|
|
522
|
+
for (const msg of snapshot) {
|
|
523
|
+
const toolResultPart = msg.role === "assistant" && msg.parts.length === 1 ? msg.parts.find((p) => p.type === "tool-result") : void 0;
|
|
524
|
+
if (!toolResultPart) {
|
|
525
|
+
reconciled.push(msg);
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const target = reconciled.findLast((m) => m.parts.some((p) => p.type === "tool-call" && p.id === toolResultPart.toolCallId)) ?? reconciled.findLast((m) => m.role === "assistant" && !(m.parts.length === 1 && m.parts[0]?.type === "tool-result"));
|
|
529
|
+
if (!target) {
|
|
530
|
+
if (!snapshotToolCallIds.has(toolResultPart.toolCallId)) console.warn(`[StreamProcessor] MESSAGES_SNAPSHOT contains a tool-result for "${toolResultPart.toolCallId}" but no matching tool-call exists in the snapshot, and there is no assistant message to anchor into; addToolResult("${toolResultPart.toolCallId}") will not be able to locate this call`);
|
|
531
|
+
reconciled.push(msg);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const parts = [...target.parts];
|
|
535
|
+
if (!snapshotToolCallIds.has(toolResultPart.toolCallId) && !parts.some((p) => p.type === "tool-call" && p.id === toolResultPart.toolCallId)) {
|
|
536
|
+
const prev = prevToolCalls.get(toolResultPart.toolCallId);
|
|
537
|
+
if (prev) {
|
|
538
|
+
parts.push({ ...prev });
|
|
539
|
+
snapshotToolCallIds.add(prev.id);
|
|
540
|
+
} else console.warn(`[StreamProcessor] MESSAGES_SNAPSHOT contains a tool-result for "${toolResultPart.toolCallId}" but no matching tool-call exists in the snapshot or the pre-snapshot state; addToolResult("${toolResultPart.toolCallId}") will not be able to locate this call`);
|
|
541
|
+
}
|
|
542
|
+
parts.push(toolResultPart);
|
|
543
|
+
target.parts = parts;
|
|
544
|
+
}
|
|
545
|
+
return this.enrichSnapshotToolCallsFromResults(reconciled, prevToolCalls);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Post-pass (c): fold `tool-result` content into sibling `tool-call` parts
|
|
549
|
+
* and prefer pre-snapshot complete/output when the snapshot rebuilt a
|
|
550
|
+
* poorer `input-complete` call (AG-UI ModelMessage has no result on calls).
|
|
551
|
+
*/
|
|
552
|
+
enrichSnapshotToolCallsFromResults(messages, prevToolCalls) {
|
|
553
|
+
const resultsByCallId = /* @__PURE__ */ new Map();
|
|
554
|
+
for (const msg of messages) for (const part of msg.parts) if (part.type === "tool-result") resultsByCallId.set(part.toolCallId, part);
|
|
555
|
+
return messages.map((msg) => {
|
|
556
|
+
const parts = msg.parts.map((part) => {
|
|
557
|
+
if (part.type !== "tool-call") return part;
|
|
558
|
+
const prev = prevToolCalls.get(part.id);
|
|
559
|
+
const result = resultsByCallId.get(part.id);
|
|
560
|
+
let next = part;
|
|
561
|
+
if (prev && (prev.output !== void 0 || prev.state === "complete" || prev.state === "error") && (part.output === void 0 || part.state === "input-complete" || part.state === "input-streaming" || part.state === "awaiting-input")) next = {
|
|
562
|
+
...part,
|
|
563
|
+
...prev.output !== void 0 ? { output: prev.output } : {},
|
|
564
|
+
state: prev.state,
|
|
565
|
+
...prev.approval !== void 0 ? { approval: prev.approval } : {},
|
|
566
|
+
...prev.metadata !== void 0 ? { metadata: prev.metadata } : {}
|
|
567
|
+
};
|
|
568
|
+
if (result && next.output === void 0) {
|
|
569
|
+
let output;
|
|
570
|
+
if (Array.isArray(result.content)) output = result.content;
|
|
571
|
+
else try {
|
|
572
|
+
output = JSON.parse(result.content);
|
|
573
|
+
} catch {
|
|
574
|
+
output = result.content;
|
|
575
|
+
}
|
|
576
|
+
const errorText = result.state === "error" ? this.extractToolResultError(output) : void 0;
|
|
577
|
+
next = {
|
|
578
|
+
...next,
|
|
579
|
+
output: errorText ? { error: errorText } : output,
|
|
580
|
+
state: result.state === "error" ? "error" : "complete"
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return next;
|
|
584
|
+
});
|
|
585
|
+
return parts.some((part, index) => part !== msg.parts[index]) ? {
|
|
586
|
+
...msg,
|
|
587
|
+
parts
|
|
588
|
+
} : msg;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Handle TEXT_MESSAGE_CONTENT event.
|
|
593
|
+
*
|
|
594
|
+
* Accumulates delta into both currentSegmentText (for UI emission) and
|
|
595
|
+
* totalTextContent (for ProcessorResult). Lazily creates the assistant
|
|
596
|
+
* UIMessage on first content. Uses updateTextPart() which replaces the
|
|
597
|
+
* last TextPart or creates a new one depending on part ordering.
|
|
598
|
+
*
|
|
599
|
+
* @see docs/chat-architecture.md#single-shot-text-response — Text accumulation step-by-step
|
|
600
|
+
* @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic
|
|
601
|
+
*/
|
|
602
|
+
handleTextMessageContentEvent(chunk) {
|
|
603
|
+
const { messageId, state } = this.ensureAssistantMessage(chunk.messageId);
|
|
604
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
605
|
+
if (this.structuredMessageIds.has(messageId)) {
|
|
606
|
+
let delta = chunk.delta || "";
|
|
607
|
+
if (delta === "" && chunk.content !== void 0 && chunk.content !== "") {
|
|
608
|
+
const existingRaw = (this.messages.find((m) => m.id === messageId)?.parts.find((p) => p.type === "structured-output") ?? { raw: "" }).raw;
|
|
609
|
+
if (chunk.content.startsWith(existingRaw)) delta = chunk.content.slice(existingRaw.length);
|
|
610
|
+
else if (existingRaw.startsWith(chunk.content)) delta = "";
|
|
611
|
+
else delta = chunk.content;
|
|
612
|
+
}
|
|
613
|
+
if (delta !== "") {
|
|
614
|
+
this.messages = appendStructuredOutputDelta(this.messages, messageId, delta);
|
|
615
|
+
state.totalTextContent += delta;
|
|
616
|
+
this.queueStructuredOutputUpdate(messageId, delta);
|
|
617
|
+
this.emitMessagesChange();
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const previousSegment = state.currentSegmentText;
|
|
622
|
+
if (state.hasToolCallsSinceTextStart && previousSegment.length > 0 && this.isNewTextSegment(chunk, previousSegment)) {
|
|
623
|
+
if (previousSegment !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
624
|
+
state.currentSegmentText = "";
|
|
625
|
+
state.lastEmittedText = "";
|
|
626
|
+
state.hasToolCallsSinceTextStart = false;
|
|
627
|
+
}
|
|
628
|
+
const currentText = state.currentSegmentText;
|
|
629
|
+
let nextText = currentText;
|
|
630
|
+
const delta = chunk.delta || "";
|
|
631
|
+
if (delta !== "") nextText = currentText + delta;
|
|
632
|
+
else if (chunk.content !== void 0 && chunk.content !== "") if (chunk.content.startsWith(currentText)) nextText = chunk.content;
|
|
633
|
+
else if (currentText.startsWith(chunk.content)) nextText = currentText;
|
|
634
|
+
else nextText = currentText + chunk.content;
|
|
635
|
+
const textDelta = nextText.slice(currentText.length);
|
|
636
|
+
state.currentSegmentText = nextText;
|
|
637
|
+
state.totalTextContent += textDelta;
|
|
638
|
+
const chunkPortion = chunk.delta || chunk.content || "";
|
|
639
|
+
if (this.chunkStrategy.shouldEmit(chunkPortion, state.currentSegmentText) && state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Handle TOOL_CALL_START event.
|
|
643
|
+
*
|
|
644
|
+
* Creates a new InternalToolCallState entry in the toolCalls Map and appends
|
|
645
|
+
* a ToolCallPart to the UIMessage. Duplicate toolCallId is a no-op.
|
|
646
|
+
*
|
|
647
|
+
* CRITICAL: This MUST be received before any TOOL_CALL_ARGS for the same
|
|
648
|
+
* toolCallId. Args for unknown IDs are silently dropped.
|
|
649
|
+
*
|
|
650
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — Tool call state transitions
|
|
651
|
+
* @see docs/chat-architecture.md#parallel-tool-calls-single-shot — Parallel tracking by ID
|
|
652
|
+
* @see docs/chat-architecture.md#adapter-contract — Ordering requirements
|
|
653
|
+
*/
|
|
654
|
+
handleToolCallStartEvent(chunk) {
|
|
655
|
+
const targetMessageId = chunk.parentMessageId ?? this.getActiveAssistantMessageId();
|
|
656
|
+
const { messageId, state } = this.ensureAssistantMessage(targetMessageId ?? void 0);
|
|
657
|
+
state.hasToolCallsSinceTextStart = true;
|
|
658
|
+
const toolCallId = chunk.toolCallId;
|
|
659
|
+
if (!state.toolCalls.get(toolCallId)) {
|
|
660
|
+
const initialState = "awaiting-input";
|
|
661
|
+
const toolName = chunk.toolCallName ?? chunk.toolName;
|
|
662
|
+
const chunkMetadata = chunk.metadata;
|
|
663
|
+
const newToolCall = {
|
|
664
|
+
id: chunk.toolCallId,
|
|
665
|
+
name: toolName,
|
|
666
|
+
arguments: "",
|
|
667
|
+
state: initialState,
|
|
668
|
+
parsedArguments: void 0,
|
|
669
|
+
index: chunk.index ?? state.toolCalls.size,
|
|
670
|
+
...chunkMetadata !== void 0 && { metadata: chunkMetadata }
|
|
671
|
+
};
|
|
672
|
+
state.toolCalls.set(toolCallId, newToolCall);
|
|
673
|
+
state.toolCallOrder.push(toolCallId);
|
|
674
|
+
this.toolCallToMessage.set(toolCallId, messageId);
|
|
675
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
676
|
+
id: chunk.toolCallId,
|
|
677
|
+
name: toolName,
|
|
678
|
+
arguments: "",
|
|
679
|
+
state: initialState,
|
|
680
|
+
...chunkMetadata !== void 0 && { metadata: chunkMetadata }
|
|
681
|
+
});
|
|
682
|
+
this.emitMessagesChange();
|
|
683
|
+
this.events.onToolCallStateChange?.(messageId, chunk.toolCallId, initialState, "");
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Handle TOOL_CALL_ARGS event.
|
|
688
|
+
*
|
|
689
|
+
* Appends the delta to the tool call's accumulated arguments string.
|
|
690
|
+
* Transitions state from awaiting-input → input-streaming on first non-empty delta.
|
|
691
|
+
* Attempts partial JSON parse on each update for UI preview.
|
|
692
|
+
*
|
|
693
|
+
* If toolCallId is not found in the Map (no preceding TOOL_CALL_START),
|
|
694
|
+
* this event is silently dropped.
|
|
695
|
+
*
|
|
696
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — Step-by-step tool call processing
|
|
697
|
+
*/
|
|
698
|
+
handleToolCallArgsEvent(chunk) {
|
|
699
|
+
const toolCallId = chunk.toolCallId;
|
|
700
|
+
const messageId = this.toolCallToMessage.get(toolCallId);
|
|
701
|
+
if (!messageId) return;
|
|
702
|
+
const state = this.getMessageState(messageId);
|
|
703
|
+
if (!state) return;
|
|
704
|
+
const existingToolCall = state.toolCalls.get(toolCallId);
|
|
705
|
+
if (!existingToolCall) return;
|
|
706
|
+
const wasAwaitingInput = existingToolCall.state === "awaiting-input";
|
|
707
|
+
existingToolCall.arguments += chunk.delta || "";
|
|
708
|
+
if (wasAwaitingInput && chunk.delta) existingToolCall.state = "input-streaming";
|
|
709
|
+
existingToolCall.parsedArguments = this.jsonParser.parse(existingToolCall.arguments);
|
|
710
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
711
|
+
id: existingToolCall.id,
|
|
712
|
+
name: existingToolCall.name,
|
|
713
|
+
arguments: existingToolCall.arguments,
|
|
714
|
+
state: existingToolCall.state
|
|
715
|
+
});
|
|
716
|
+
this.emitMessagesChange();
|
|
717
|
+
this.events.onToolCallStateChange?.(messageId, existingToolCall.id, existingToolCall.state, existingToolCall.arguments);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.
|
|
721
|
+
*
|
|
722
|
+
* This event has a DUAL ROLE:
|
|
723
|
+
* - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.
|
|
724
|
+
* - With `result`: Signals tool was executed and result is available (from TextEngine).
|
|
725
|
+
* Creates both output on the tool-call part AND a tool-result part.
|
|
726
|
+
*
|
|
727
|
+
* If `input` is provided, it overrides the accumulated string parse as the
|
|
728
|
+
* canonical parsed arguments.
|
|
729
|
+
*
|
|
730
|
+
* @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation
|
|
731
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
732
|
+
*/
|
|
733
|
+
handleToolCallEndEvent(chunk) {
|
|
734
|
+
const messageId = this.toolCallToMessage.get(chunk.toolCallId);
|
|
735
|
+
if (!messageId) return;
|
|
736
|
+
const msgState = this.getMessageState(messageId);
|
|
737
|
+
if (!msgState) return;
|
|
738
|
+
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId);
|
|
739
|
+
if (existingToolCall && existingToolCall.state !== "input-complete") {
|
|
740
|
+
if (chunk.input !== void 0 && !existingToolCall.arguments) try {
|
|
741
|
+
existingToolCall.arguments = JSON.stringify(chunk.input);
|
|
742
|
+
} catch {}
|
|
743
|
+
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId);
|
|
744
|
+
this.completeToolCall(messageId, index, existingToolCall);
|
|
745
|
+
if (chunk.input !== void 0) {
|
|
746
|
+
existingToolCall.parsedArguments = chunk.input;
|
|
747
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
748
|
+
id: existingToolCall.id,
|
|
749
|
+
name: existingToolCall.name,
|
|
750
|
+
arguments: existingToolCall.arguments,
|
|
751
|
+
state: "input-complete",
|
|
752
|
+
input: chunk.input,
|
|
753
|
+
...existingToolCall.metadata !== void 0 && { metadata: existingToolCall.metadata }
|
|
754
|
+
});
|
|
755
|
+
this.emitMessagesChange();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (chunk.result) {
|
|
759
|
+
let output;
|
|
760
|
+
if (Array.isArray(chunk.result)) output = chunk.result;
|
|
761
|
+
else try {
|
|
762
|
+
output = JSON.parse(chunk.result);
|
|
763
|
+
} catch {
|
|
764
|
+
output = chunk.result;
|
|
765
|
+
}
|
|
766
|
+
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, chunk.state === "output-error" ? "error" : void 0);
|
|
767
|
+
const resultState = chunk.state === "output-error" ? "error" : "complete";
|
|
768
|
+
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, chunk.result, resultState, resultState === "error" ? this.extractToolResultError(output) : void 0);
|
|
769
|
+
this.emitMessagesChange();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
extractToolResultError(output) {
|
|
773
|
+
if (output && typeof output === "object" && "error" in output && typeof output.error === "string") return output.error;
|
|
774
|
+
return typeof output === "string" ? output : "Tool execution failed";
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Handle TOOL_CALL_RESULT event (AG-UI spec).
|
|
778
|
+
*
|
|
779
|
+
* Creates a tool-result part and updates the tool-call output field,
|
|
780
|
+
* mirroring the logic from TOOL_CALL_END when it carries a result.
|
|
781
|
+
* This is the spec-compliant path for delivering tool results to the client.
|
|
782
|
+
*/
|
|
783
|
+
handleToolCallResultEvent(chunk) {
|
|
784
|
+
const messageId = this.toolCallToMessage.get(chunk.toolCallId);
|
|
785
|
+
if (!messageId) return;
|
|
786
|
+
let output;
|
|
787
|
+
try {
|
|
788
|
+
output = JSON.parse(chunk.content);
|
|
789
|
+
} catch {
|
|
790
|
+
output = chunk.content;
|
|
791
|
+
}
|
|
792
|
+
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, chunk.state === "output-error" ? "error" : void 0);
|
|
793
|
+
const resultState = chunk.state === "output-error" ? "error" : "complete";
|
|
794
|
+
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, chunk.content, resultState, resultState === "error" ? this.extractToolResultError(output) : void 0);
|
|
795
|
+
this.emitMessagesChange();
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Handle RUN_STARTED event.
|
|
799
|
+
*
|
|
800
|
+
* Registers the run so that RUN_FINISHED can determine whether other
|
|
801
|
+
* runs are still active before finalizing.
|
|
802
|
+
*/
|
|
803
|
+
handleRunStartedEvent(chunk) {
|
|
804
|
+
this.activeRuns.add(chunk.runId);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Handle RUN_FINISHED event.
|
|
808
|
+
*
|
|
809
|
+
* Records the finishReason and removes the run from activeRuns.
|
|
810
|
+
* Only finalizes when no more runs are active, so that concurrent
|
|
811
|
+
* runs don't interfere with each other.
|
|
812
|
+
*
|
|
813
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics
|
|
814
|
+
* @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory
|
|
815
|
+
*/
|
|
816
|
+
handleRunFinishedEvent(chunk) {
|
|
817
|
+
this.finishReason = chunk.finishReason ?? null;
|
|
818
|
+
this.activeRuns.delete(chunk.runId);
|
|
819
|
+
if (chunk.outcome?.type === "interrupt") this.handleInterrupts(chunk.outcome.interrupts);
|
|
820
|
+
if (this.activeRuns.size === 0) {
|
|
821
|
+
this.isDone = true;
|
|
822
|
+
this.completeAllToolCalls();
|
|
823
|
+
this.finalizeStream();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
handleInterrupts(interrupts) {
|
|
827
|
+
for (const interrupt of interrupts) {
|
|
828
|
+
const metadata = interrupt.metadata && typeof interrupt.metadata === "object" ? interrupt.metadata : {};
|
|
829
|
+
const kind = typeof metadata.kind === "string" ? metadata.kind : void 0;
|
|
830
|
+
const toolCallId = interrupt.toolCallId;
|
|
831
|
+
if (!toolCallId) continue;
|
|
832
|
+
const toolName = typeof metadata.toolName === "string" ? metadata.toolName : this.findToolCallName(toolCallId);
|
|
833
|
+
const input = Object.hasOwn(metadata, "input") ? metadata.input : {};
|
|
834
|
+
if (kind === "approval" || interrupt.reason === "approval_required") {
|
|
835
|
+
const resolvedMessageId = this.getActiveAssistantMessageId() ?? this.toolCallToMessage.get(toolCallId) ?? this.messages.find((m) => m.role === "assistant" && m.parts.some((p) => p.type === "tool-call" && p.id === toolCallId))?.id;
|
|
836
|
+
if (resolvedMessageId) {
|
|
837
|
+
this.messages = updateToolCallApproval(this.messages, resolvedMessageId, toolCallId, interrupt.id);
|
|
838
|
+
this.emitMessagesChange();
|
|
839
|
+
}
|
|
840
|
+
this.events.onApprovalRequest?.({
|
|
841
|
+
toolCallId,
|
|
842
|
+
toolName,
|
|
843
|
+
input,
|
|
844
|
+
approvalId: interrupt.id
|
|
845
|
+
});
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (kind === "client_tool" || interrupt.reason === "client_tool_input") this.events.onToolCall?.({
|
|
849
|
+
toolCallId,
|
|
850
|
+
toolName,
|
|
851
|
+
input
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
findToolCallName(toolCallId) {
|
|
856
|
+
for (const state of this.messageStates.values()) {
|
|
857
|
+
const toolCall = state.toolCalls.get(toolCallId);
|
|
858
|
+
if (toolCall) return toolCall.name;
|
|
859
|
+
}
|
|
860
|
+
return "";
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Handle RUN_ERROR event
|
|
864
|
+
*/
|
|
865
|
+
handleRunErrorEvent(chunk) {
|
|
866
|
+
this.hasError = true;
|
|
867
|
+
const runId = "runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : void 0;
|
|
868
|
+
if (runId) this.activeRuns.delete(runId);
|
|
869
|
+
else this.activeRuns.clear();
|
|
870
|
+
const { messageId } = this.ensureAssistantMessage();
|
|
871
|
+
const errorMessage = chunk.message || chunk.error?.message || "An error occurred";
|
|
872
|
+
if (!chunk.message && !chunk.error?.message) console.error("[StreamProcessor] RUN_ERROR with no message; original chunk:", chunk);
|
|
873
|
+
if (this.structuredMessageIds.has(messageId)) {
|
|
874
|
+
this.flushStructuredOutputUpdate(messageId);
|
|
875
|
+
this.messages = errorStructuredOutputPart(this.messages, messageId, errorMessage);
|
|
876
|
+
this.structuredMessageIds.delete(messageId);
|
|
877
|
+
this.emitStructuredOutputChange(messageId, "error");
|
|
878
|
+
this.emitMessagesChange();
|
|
879
|
+
}
|
|
880
|
+
const error = new Error(errorMessage);
|
|
881
|
+
const code = chunk.code ?? chunk.error?.code;
|
|
882
|
+
if (code !== void 0) Object.assign(error, { code });
|
|
883
|
+
if (chunk.rawEvent !== void 0) Object.assign(error, { rawEvent: chunk.rawEvent });
|
|
884
|
+
this.events.onError?.(error);
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Handle STEP_STARTED event (for thinking/reasoning content).
|
|
888
|
+
*
|
|
889
|
+
* Records the stepId so that subsequent STEP_FINISHED deltas accumulate
|
|
890
|
+
* into their own ThinkingPart. Does not create a message — the message
|
|
891
|
+
* is lazily created when the first STEP_FINISHED content arrives.
|
|
892
|
+
*/
|
|
893
|
+
handleStepStartedEvent(chunk) {
|
|
894
|
+
const stepId = chunk.stepId ?? generateMessageId();
|
|
895
|
+
const activeId = this.getActiveAssistantMessageId();
|
|
896
|
+
if (activeId) {
|
|
897
|
+
const state = this.getMessageState(activeId);
|
|
898
|
+
if (state) {
|
|
899
|
+
state.currentThinkingStepId = stepId;
|
|
900
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
901
|
+
state.thinkingSteps.set(stepId, "");
|
|
902
|
+
state.thinkingStepOrder.push(stepId);
|
|
903
|
+
}
|
|
904
|
+
this.pendingThinkingStepId = null;
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
this.pendingThinkingStepId = stepId;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Handle STEP_FINISHED event (for thinking/reasoning content).
|
|
912
|
+
*
|
|
913
|
+
* Accumulates delta into the current thinking step's content and updates
|
|
914
|
+
* the corresponding ThinkingPart in the UIMessage.
|
|
915
|
+
*
|
|
916
|
+
* @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow
|
|
917
|
+
*/
|
|
918
|
+
handleStepFinishedEvent(chunk) {
|
|
919
|
+
const { messageId, state } = this.ensureAssistantMessage(this.getActiveAssistantMessageId() ?? void 0);
|
|
920
|
+
if (state.hasSeenReasoningEvents) {
|
|
921
|
+
if (chunk.signature) {
|
|
922
|
+
const stepId = state.currentThinkingStepId ?? chunk.stepId;
|
|
923
|
+
if (!stepId) return;
|
|
924
|
+
const thinking = state.thinkingSteps.get(stepId);
|
|
925
|
+
if (thinking !== void 0) {
|
|
926
|
+
state.thinkingStepSignatures.set(stepId, chunk.signature);
|
|
927
|
+
this.messages = updateThinkingPart(this.messages, messageId, stepId, thinking, chunk.signature);
|
|
928
|
+
this.emitMessagesChange();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
this.consumePendingThinkingStep(state);
|
|
934
|
+
const stepId = state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId();
|
|
935
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
936
|
+
state.thinkingSteps.set(stepId, "");
|
|
937
|
+
state.thinkingStepOrder.push(stepId);
|
|
938
|
+
state.currentThinkingStepId = stepId;
|
|
939
|
+
}
|
|
940
|
+
const previous = state.thinkingSteps.get(stepId) ?? "";
|
|
941
|
+
let nextThinking = previous;
|
|
942
|
+
if (chunk.delta && chunk.delta !== "") nextThinking = previous + chunk.delta;
|
|
943
|
+
else if (chunk.content && chunk.content !== "") if (chunk.content.startsWith(previous)) nextThinking = chunk.content;
|
|
944
|
+
else if (previous.startsWith(chunk.content)) nextThinking = previous;
|
|
945
|
+
else nextThinking = previous + chunk.content;
|
|
946
|
+
state.thinkingSteps.set(stepId, nextThinking);
|
|
947
|
+
if (chunk.signature) state.thinkingStepSignatures.set(stepId, chunk.signature);
|
|
948
|
+
this.messages = updateThinkingPart(this.messages, messageId, stepId, nextThinking, state.thinkingStepSignatures.get(stepId));
|
|
949
|
+
this.emitMessagesChange();
|
|
950
|
+
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking);
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).
|
|
954
|
+
*
|
|
955
|
+
* Accumulates reasoning delta into thinking content and updates the
|
|
956
|
+
* corresponding ThinkingPart in the UIMessage.
|
|
957
|
+
*/
|
|
958
|
+
handleReasoningMessageContentEvent(chunk) {
|
|
959
|
+
const { messageId, state } = this.ensureAssistantMessage(this.getActiveAssistantMessageId() ?? void 0);
|
|
960
|
+
state.hasSeenReasoningEvents = true;
|
|
961
|
+
const delta = chunk.delta || "";
|
|
962
|
+
this.consumePendingThinkingStep(state);
|
|
963
|
+
const stepId = state.currentThinkingStepId ?? chunk.messageId;
|
|
964
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
965
|
+
state.thinkingSteps.set(stepId, "");
|
|
966
|
+
state.thinkingStepOrder.push(stepId);
|
|
967
|
+
state.currentThinkingStepId = stepId;
|
|
968
|
+
}
|
|
969
|
+
const nextThinking = (state.thinkingSteps.get(stepId) ?? "") + delta;
|
|
970
|
+
state.thinkingSteps.set(stepId, nextThinking);
|
|
971
|
+
this.messages = updateThinkingPart(this.messages, messageId, stepId, nextThinking, state.thinkingStepSignatures.get(stepId));
|
|
972
|
+
this.emitMessagesChange();
|
|
973
|
+
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Handle CUSTOM event.
|
|
977
|
+
*
|
|
978
|
+
* Handles custom events consumed by the processor:
|
|
979
|
+
* - 'tool-input-available': Legacy/replay-compatible input for client tool
|
|
980
|
+
* execution. Fires onToolCall.
|
|
981
|
+
* - 'approval-requested': Legacy/replay-compatible input for tool approval.
|
|
982
|
+
* Updates tool-call part state and fires onApprovalRequest.
|
|
983
|
+
*
|
|
984
|
+
* Current core streams represent user-actionable waits through
|
|
985
|
+
* RUN_FINISHED.outcome.type === 'interrupt'; these custom events are not the
|
|
986
|
+
* source of truth for new emissions.
|
|
987
|
+
*
|
|
988
|
+
* @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details
|
|
989
|
+
*/
|
|
990
|
+
handleCustomEvent(chunk) {
|
|
991
|
+
const messageId = this.getActiveAssistantMessageId();
|
|
992
|
+
if (chunk.name === "structured-output.start" && chunk.value) {
|
|
993
|
+
const targetId = chunk.value.messageId ?? messageId;
|
|
994
|
+
if (targetId) {
|
|
995
|
+
this.ensureAssistantMessage(targetId);
|
|
996
|
+
this.structuredMessageIds.add(targetId);
|
|
997
|
+
this.structuredOutputUpdateBatches.delete(targetId);
|
|
998
|
+
this.events.onStructuredOutputChange?.({
|
|
999
|
+
phase: "start",
|
|
1000
|
+
messageId: targetId,
|
|
1001
|
+
status: "streaming",
|
|
1002
|
+
raw: ""
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (chunk.name === "structured-output.complete" && chunk.value) {
|
|
1008
|
+
const v = chunk.value;
|
|
1009
|
+
const targetId = v.messageId ?? messageId;
|
|
1010
|
+
if (targetId) {
|
|
1011
|
+
this.flushStructuredOutputUpdate(targetId);
|
|
1012
|
+
this.messages = completeStructuredOutputPart(this.messages, targetId, v.object, v.raw ?? "", v.reasoning);
|
|
1013
|
+
this.structuredMessageIds.delete(targetId);
|
|
1014
|
+
this.emitStructuredOutputChange(targetId, "complete");
|
|
1015
|
+
this.emitMessagesChange();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (chunk.name === "tool-input-available" && chunk.value) {
|
|
1019
|
+
const { toolCallId, toolName, input } = chunk.value;
|
|
1020
|
+
this.events.onToolCall?.({
|
|
1021
|
+
toolCallId,
|
|
1022
|
+
toolName,
|
|
1023
|
+
input
|
|
1024
|
+
});
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (chunk.name === "approval-requested" && chunk.value) {
|
|
1028
|
+
const { toolCallId, toolName, input, approval } = chunk.value;
|
|
1029
|
+
const resolvedMessageId = messageId ?? this.toolCallToMessage.get(toolCallId);
|
|
1030
|
+
if (resolvedMessageId) {
|
|
1031
|
+
this.messages = updateToolCallApproval(this.messages, resolvedMessageId, toolCallId, approval.id);
|
|
1032
|
+
this.emitMessagesChange();
|
|
1033
|
+
}
|
|
1034
|
+
this.events.onApprovalRequest?.({
|
|
1035
|
+
toolCallId,
|
|
1036
|
+
toolName,
|
|
1037
|
+
input,
|
|
1038
|
+
approvalId: approval.id
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (chunk.name === "ui-resource" && chunk.value) {
|
|
1043
|
+
const v = chunk.value;
|
|
1044
|
+
const resolvedMessageId = this.toolCallToMessage.get(v.toolCallId) ?? messageId;
|
|
1045
|
+
if (resolvedMessageId) {
|
|
1046
|
+
const part = {
|
|
1047
|
+
type: "ui-resource",
|
|
1048
|
+
resource: v.resource,
|
|
1049
|
+
toolCallId: v.toolCallId,
|
|
1050
|
+
toolName: v.toolName,
|
|
1051
|
+
...v.serverId !== void 0 && { serverId: v.serverId },
|
|
1052
|
+
...v.meta !== void 0 && { meta: v.meta }
|
|
1053
|
+
};
|
|
1054
|
+
this.messages = this.messages.map((msg) => msg.id === resolvedMessageId ? {
|
|
1055
|
+
...msg,
|
|
1056
|
+
parts: [...msg.parts, part]
|
|
1057
|
+
} : msg);
|
|
1058
|
+
this.emitMessagesChange();
|
|
1059
|
+
} else console.warn(`[mcp-apps] dropped ui-resource: no target message for toolCallId "${v.toolCallId}" (toolName "${v.toolName}")`);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (this.events.onCustomEvent) {
|
|
1063
|
+
const toolCallId = chunk.value && typeof chunk.value === "object" ? chunk.value.toolCallId : void 0;
|
|
1064
|
+
this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId });
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Detect if an incoming content chunk represents a NEW text segment
|
|
1069
|
+
*/
|
|
1070
|
+
isNewTextSegment(chunk, previous) {
|
|
1071
|
+
if (chunk.content !== void 0) {
|
|
1072
|
+
if (chunk.content.length < previous.length) return true;
|
|
1073
|
+
if (!chunk.content.startsWith(previous) && !previous.startsWith(chunk.content)) return true;
|
|
1074
|
+
}
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Complete all tool calls across all active messages — safety net for stream termination.
|
|
1079
|
+
*
|
|
1080
|
+
* Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call
|
|
1081
|
+
* not yet in input-complete state. Handles cases where TOOL_CALL_END was
|
|
1082
|
+
* missed (adapter bug, network error, aborted stream).
|
|
1083
|
+
*
|
|
1084
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior
|
|
1085
|
+
*/
|
|
1086
|
+
completeAllToolCalls() {
|
|
1087
|
+
for (const messageId of this.activeMessageIds) this.completeAllToolCallsForMessage(messageId);
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Complete all tool calls for a specific message
|
|
1091
|
+
*/
|
|
1092
|
+
completeAllToolCallsForMessage(messageId) {
|
|
1093
|
+
const state = this.getMessageState(messageId);
|
|
1094
|
+
if (!state) return;
|
|
1095
|
+
state.toolCalls.forEach((toolCall, id) => {
|
|
1096
|
+
if (toolCall.state !== "input-complete") {
|
|
1097
|
+
const index = state.toolCallOrder.indexOf(id);
|
|
1098
|
+
this.completeToolCall(messageId, index, toolCall);
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Mark a tool call as complete and emit event
|
|
1104
|
+
*/
|
|
1105
|
+
completeToolCall(messageId, _index, toolCall) {
|
|
1106
|
+
toolCall.state = "input-complete";
|
|
1107
|
+
toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments);
|
|
1108
|
+
if (this.isToolCallPartErrored(toolCall.id)) return;
|
|
1109
|
+
if (this.isToolCallPartAwaitingUserAction(toolCall.id)) return;
|
|
1110
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1111
|
+
id: toolCall.id,
|
|
1112
|
+
name: toolCall.name,
|
|
1113
|
+
arguments: toolCall.arguments,
|
|
1114
|
+
state: "input-complete",
|
|
1115
|
+
...toolCall.parsedArguments !== void 0 && { input: toolCall.parsedArguments },
|
|
1116
|
+
...toolCall.metadata !== void 0 && { metadata: toolCall.metadata }
|
|
1117
|
+
});
|
|
1118
|
+
this.emitMessagesChange();
|
|
1119
|
+
this.events.onToolCallStateChange?.(messageId, toolCall.id, "input-complete", toolCall.arguments);
|
|
1120
|
+
}
|
|
1121
|
+
isToolCallPartAwaitingUserAction(toolCallId) {
|
|
1122
|
+
return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && (part.state === "approval-requested" || part.state === "approval-responded")));
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Whether the rendered tool-call part for the given id has reached the
|
|
1126
|
+
* terminal 'error' state. Used to prevent the completion safety net from
|
|
1127
|
+
* downgrading a failed call back to 'input-complete'.
|
|
1128
|
+
*/
|
|
1129
|
+
isToolCallPartErrored(toolCallId) {
|
|
1130
|
+
return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && part.state === "error"));
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Emit pending text update for a specific message.
|
|
1134
|
+
*
|
|
1135
|
+
* Calls updateTextPart() which has critical append-vs-replace logic:
|
|
1136
|
+
* - If last UIMessage part is TextPart → replaces its content (same segment).
|
|
1137
|
+
* - If last part is anything else → pushes new TextPart (new segment after tools).
|
|
1138
|
+
*
|
|
1139
|
+
* @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic
|
|
1140
|
+
*/
|
|
1141
|
+
emitTextUpdateForMessage(messageId) {
|
|
1142
|
+
const state = this.getMessageState(messageId);
|
|
1143
|
+
if (!state) return;
|
|
1144
|
+
state.lastEmittedText = state.currentSegmentText;
|
|
1145
|
+
this.messages = updateTextPart(this.messages, messageId, state.currentSegmentText);
|
|
1146
|
+
this.emitMessagesChange();
|
|
1147
|
+
this.events.onTextUpdate?.(messageId, state.currentSegmentText);
|
|
1148
|
+
}
|
|
1149
|
+
queueStructuredOutputUpdate(messageId, delta) {
|
|
1150
|
+
const existing = this.structuredOutputUpdateBatches.get(messageId);
|
|
1151
|
+
const next = {
|
|
1152
|
+
delta: `${existing?.delta ?? ""}${delta}`,
|
|
1153
|
+
chunkCount: (existing?.chunkCount ?? 0) + 1
|
|
1154
|
+
};
|
|
1155
|
+
this.structuredOutputUpdateBatches.set(messageId, next);
|
|
1156
|
+
if (next.chunkCount >= STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE) this.flushStructuredOutputUpdate(messageId);
|
|
1157
|
+
}
|
|
1158
|
+
flushStructuredOutputUpdate(messageId) {
|
|
1159
|
+
const batch = this.structuredOutputUpdateBatches.get(messageId);
|
|
1160
|
+
if (!batch || batch.chunkCount === 0) return;
|
|
1161
|
+
this.structuredOutputUpdateBatches.delete(messageId);
|
|
1162
|
+
this.emitStructuredOutputChange(messageId, "update", batch.delta);
|
|
1163
|
+
}
|
|
1164
|
+
emitStructuredOutputChange(messageId, phase, delta) {
|
|
1165
|
+
const part = this.messages.find((message) => message.id === messageId)?.parts.find((messagePart) => messagePart.type === "structured-output");
|
|
1166
|
+
if (!part) return;
|
|
1167
|
+
this.events.onStructuredOutputChange?.({
|
|
1168
|
+
phase,
|
|
1169
|
+
messageId,
|
|
1170
|
+
status: part.status,
|
|
1171
|
+
raw: part.raw,
|
|
1172
|
+
...part.partial !== void 0 ? { partial: part.partial } : {},
|
|
1173
|
+
...part.data !== void 0 ? { data: part.data } : {},
|
|
1174
|
+
...part.reasoning !== void 0 ? { reasoning: part.reasoning } : {},
|
|
1175
|
+
...part.errorMessage !== void 0 ? { errorMessage: part.errorMessage } : {},
|
|
1176
|
+
...delta !== void 0 ? { delta } : {}
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Emit messages change event
|
|
1181
|
+
*/
|
|
1182
|
+
emitMessagesChange() {
|
|
1183
|
+
this.events.onMessagesChange?.([...this.messages]);
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Finalize the stream — complete all pending operations.
|
|
1187
|
+
*
|
|
1188
|
+
* Called when the async iterable ends (stream closed). Acts as the final
|
|
1189
|
+
* safety net: completes any remaining tool calls, flushes un-emitted text,
|
|
1190
|
+
* and fires onStreamEnd.
|
|
1191
|
+
*
|
|
1192
|
+
* @see docs/chat-architecture.md#single-shot-text-response — Finalization step
|
|
1193
|
+
*/
|
|
1194
|
+
finalizeStream() {
|
|
1195
|
+
let lastAssistantMessage;
|
|
1196
|
+
for (const messageId of this.activeMessageIds) {
|
|
1197
|
+
const state = this.getMessageState(messageId);
|
|
1198
|
+
if (!state) continue;
|
|
1199
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
1200
|
+
if (state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
1201
|
+
state.isComplete = true;
|
|
1202
|
+
const msg = this.messages.find((m) => m.id === messageId);
|
|
1203
|
+
if (msg && msg.role === "assistant") lastAssistantMessage = msg;
|
|
1204
|
+
}
|
|
1205
|
+
for (const messageId of this.structuredMessageIds) {
|
|
1206
|
+
this.flushStructuredOutputUpdate(messageId);
|
|
1207
|
+
this.messages = errorStructuredOutputPart(this.messages, messageId, "Stream ended without structured-output.complete");
|
|
1208
|
+
this.emitStructuredOutputChange(messageId, "error");
|
|
1209
|
+
}
|
|
1210
|
+
this.structuredMessageIds.clear();
|
|
1211
|
+
this.structuredOutputUpdateBatches.clear();
|
|
1212
|
+
this.activeMessageIds.clear();
|
|
1213
|
+
if (lastAssistantMessage && !this.hasError) {
|
|
1214
|
+
if (this.isWhitespaceOnlyMessage(lastAssistantMessage)) {
|
|
1215
|
+
this.messages = this.messages.filter((m) => m.id !== lastAssistantMessage.id);
|
|
1216
|
+
this.emitMessagesChange();
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (lastAssistantMessage) this.events.onStreamEnd?.(lastAssistantMessage);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Get completed tool calls in API format (aggregated across all messages)
|
|
1224
|
+
*/
|
|
1225
|
+
getCompletedToolCalls() {
|
|
1226
|
+
const result = [];
|
|
1227
|
+
for (const state of this.messageStates.values()) for (const tc of state.toolCalls.values()) if (tc.state === "input-complete") result.push({
|
|
1228
|
+
id: tc.id,
|
|
1229
|
+
type: "function",
|
|
1230
|
+
function: {
|
|
1231
|
+
name: tc.name,
|
|
1232
|
+
arguments: tc.arguments
|
|
1233
|
+
},
|
|
1234
|
+
...tc.metadata !== void 0 && { metadata: tc.metadata }
|
|
1235
|
+
});
|
|
1236
|
+
return result;
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Get current result (aggregated across all messages)
|
|
1240
|
+
*/
|
|
1241
|
+
getResult() {
|
|
1242
|
+
const toolCalls = this.getCompletedToolCalls();
|
|
1243
|
+
let content = "";
|
|
1244
|
+
let thinking = "";
|
|
1245
|
+
for (const state of this.messageStates.values()) {
|
|
1246
|
+
content += state.totalTextContent;
|
|
1247
|
+
for (const stepId of state.thinkingStepOrder) thinking += state.thinkingSteps.get(stepId) ?? "";
|
|
1248
|
+
}
|
|
1249
|
+
return {
|
|
1250
|
+
content,
|
|
1251
|
+
thinking: thinking || void 0,
|
|
1252
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1253
|
+
finishReason: this.finishReason
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Get current processor state (aggregated across all messages)
|
|
1258
|
+
*/
|
|
1259
|
+
getState() {
|
|
1260
|
+
let content = "";
|
|
1261
|
+
let thinking = "";
|
|
1262
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1263
|
+
const toolCallOrder = [];
|
|
1264
|
+
for (const state of this.messageStates.values()) {
|
|
1265
|
+
content += state.totalTextContent;
|
|
1266
|
+
for (const stepId of state.thinkingStepOrder) thinking += state.thinkingSteps.get(stepId) ?? "";
|
|
1267
|
+
for (const [id, tc] of state.toolCalls) toolCalls.set(id, tc);
|
|
1268
|
+
toolCallOrder.push(...state.toolCallOrder);
|
|
1269
|
+
}
|
|
1270
|
+
return {
|
|
1271
|
+
content,
|
|
1272
|
+
thinking,
|
|
1273
|
+
toolCalls,
|
|
1274
|
+
toolCallOrder,
|
|
1275
|
+
finishReason: this.finishReason,
|
|
1276
|
+
done: this.isDone
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Start recording chunks
|
|
1281
|
+
*/
|
|
1282
|
+
startRecording() {
|
|
1283
|
+
this.recordingEnabled = true;
|
|
1284
|
+
this.recordingStartTime = Date.now();
|
|
1285
|
+
this.recording = {
|
|
1286
|
+
version: "1.0",
|
|
1287
|
+
timestamp: this.recordingStartTime,
|
|
1288
|
+
chunks: []
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Get the current recording
|
|
1293
|
+
*/
|
|
1294
|
+
getRecording() {
|
|
1295
|
+
return this.recording;
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Reset stream state (but keep messages)
|
|
1299
|
+
*/
|
|
1300
|
+
resetStreamState() {
|
|
1301
|
+
this.messageStates.clear();
|
|
1302
|
+
this.activeMessageIds.clear();
|
|
1303
|
+
this.activeRuns.clear();
|
|
1304
|
+
this.toolCallToMessage.clear();
|
|
1305
|
+
this.structuredMessageIds.clear();
|
|
1306
|
+
this.structuredOutputUpdateBatches.clear();
|
|
1307
|
+
this.pendingManualMessageId = null;
|
|
1308
|
+
this.pendingThinkingStepId = null;
|
|
1309
|
+
this.finishReason = null;
|
|
1310
|
+
this.hasError = false;
|
|
1311
|
+
this.isDone = false;
|
|
1312
|
+
this.chunkStrategy.reset?.();
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Full reset (including messages)
|
|
1316
|
+
*/
|
|
1317
|
+
reset() {
|
|
1318
|
+
this.resetStreamState();
|
|
1319
|
+
this.messages = [];
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Check if a message contains only whitespace text and no other meaningful parts
|
|
1323
|
+
* (no tool calls, tool results, thinking, etc.)
|
|
1324
|
+
*/
|
|
1325
|
+
isWhitespaceOnlyMessage(message) {
|
|
1326
|
+
if (message.parts.length === 0) return false;
|
|
1327
|
+
return message.parts.every((part) => part.type === "text" && part.content.trim() === "");
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Replay a recording through the processor
|
|
1331
|
+
*/
|
|
1332
|
+
static async replay(recording, options) {
|
|
1333
|
+
return new StreamProcessor(options).process(createReplayStream(recording));
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
/**
|
|
1337
|
+
* Create an async iterable from a recording
|
|
1338
|
+
*/
|
|
1534
1339
|
function createReplayStream(recording) {
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
for (const { chunk } of recording.chunks) {
|
|
1539
|
-
yield chunk;
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
};
|
|
1340
|
+
return { async *[Symbol.asyncIterator]() {
|
|
1341
|
+
for (const { chunk } of recording.chunks) yield chunk;
|
|
1342
|
+
} };
|
|
1543
1343
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
//# sourceMappingURL=processor.js.map
|
|
1344
|
+
//#endregion
|
|
1345
|
+
export { StreamProcessor, createReplayStream };
|
|
1346
|
+
|
|
1347
|
+
//# sourceMappingURL=processor.js.map
|