@tanstack/ai 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/esm/activities/chat/index.js +264 -186
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +22 -0
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/index.d.ts +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +22 -43
- package/dist/esm/activities/chat/stream/processor.js +93 -157
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +22 -21
- package/dist/esm/activities/chat/tools/tool-calls.js +40 -27
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +41 -1
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.js +33 -1
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.js +29 -1
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +50 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/summarize/index.js +9 -8
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/event-client.d.ts +378 -254
- package/dist/esm/event-client.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/stream-to-response.d.ts +1 -1
- package/dist/esm/stream-to-response.js +5 -3
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/types.d.ts +162 -46
- package/package.json +1 -1
- package/src/activities/chat/index.ts +362 -216
- package/src/activities/chat/messages.ts +40 -1
- package/src/activities/chat/stream/index.ts +1 -5
- package/src/activities/chat/stream/processor.ts +142 -270
- package/src/activities/chat/tools/tool-calls.ts +55 -49
- package/src/activities/generateImage/index.ts +47 -2
- package/src/activities/generateSpeech/index.ts +37 -1
- package/src/activities/generateTranscription/index.ts +33 -1
- package/src/activities/generateVideo/index.ts +54 -0
- package/src/activities/summarize/index.ts +10 -9
- package/src/event-client.ts +460 -256
- package/src/index.ts +2 -3
- package/src/stream-to-response.ts +8 -6
- package/src/types.ts +209 -65
|
@@ -3,6 +3,7 @@ import { streamToText } from "../../stream-to-response.js";
|
|
|
3
3
|
import { ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
|
|
4
4
|
import { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "./tools/schema-converter.js";
|
|
5
5
|
import { maxIterations } from "./agent-loop-strategies.js";
|
|
6
|
+
import { convertMessagesToModelMessages } from "./messages.js";
|
|
6
7
|
const kind = "text";
|
|
7
8
|
function createChatOptions(options) {
|
|
8
9
|
return options;
|
|
@@ -15,7 +16,7 @@ class TextEngine {
|
|
|
15
16
|
this.totalChunkCount = 0;
|
|
16
17
|
this.currentMessageId = null;
|
|
17
18
|
this.accumulatedContent = "";
|
|
18
|
-
this.
|
|
19
|
+
this.finishedEvent = null;
|
|
19
20
|
this.shouldEmitStreamEnd = true;
|
|
20
21
|
this.earlyTermination = false;
|
|
21
22
|
this.toolPhase = "continue";
|
|
@@ -27,7 +28,14 @@ class TextEngine {
|
|
|
27
28
|
this.loopStrategy = config.params.agentLoopStrategy || maxIterations(5);
|
|
28
29
|
this.toolCallManager = new ToolCallManager(this.tools);
|
|
29
30
|
this.initialMessageCount = config.params.messages.length;
|
|
30
|
-
|
|
31
|
+
const { approvals, clientToolResults } = this.extractClientStateFromOriginalMessages(
|
|
32
|
+
config.params.messages
|
|
33
|
+
);
|
|
34
|
+
this.initialApprovals = approvals;
|
|
35
|
+
this.initialClientToolResults = clientToolResults;
|
|
36
|
+
this.messages = convertMessagesToModelMessages(
|
|
37
|
+
config.params.messages
|
|
38
|
+
);
|
|
31
39
|
this.requestId = this.createId("chat");
|
|
32
40
|
this.streamId = this.createId("stream");
|
|
33
41
|
this.effectiveRequest = config.params.abortController ? { signal: config.params.abortController.signal } : void 0;
|
|
@@ -66,61 +74,56 @@ class TextEngine {
|
|
|
66
74
|
}
|
|
67
75
|
beforeRun() {
|
|
68
76
|
this.streamStartTime = Date.now();
|
|
69
|
-
const {
|
|
70
|
-
model,
|
|
71
|
-
tools,
|
|
72
|
-
temperature,
|
|
73
|
-
topP,
|
|
74
|
-
maxTokens,
|
|
75
|
-
metadata,
|
|
76
|
-
modelOptions,
|
|
77
|
-
conversationId
|
|
78
|
-
} = this.params;
|
|
77
|
+
const { tools, temperature, topP, maxTokens, metadata } = this.params;
|
|
79
78
|
const options = {};
|
|
80
79
|
if (temperature !== void 0) options.temperature = temperature;
|
|
81
80
|
if (topP !== void 0) options.topP = topP;
|
|
82
81
|
if (maxTokens !== void 0) options.maxTokens = maxTokens;
|
|
83
82
|
if (metadata !== void 0) options.metadata = metadata;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
provider: this.adapter.name,
|
|
89
|
-
messageCount: this.initialMessageCount,
|
|
90
|
-
hasTools: !!tools && tools.length > 0,
|
|
91
|
-
streaming: true,
|
|
92
|
-
timestamp: Date.now(),
|
|
93
|
-
clientId: conversationId,
|
|
94
|
-
toolNames: tools?.map((t) => t.name),
|
|
95
|
-
options: Object.keys(options).length > 0 ? options : void 0,
|
|
96
|
-
modelOptions
|
|
97
|
-
});
|
|
98
|
-
aiEventClient.emit("stream:started", {
|
|
99
|
-
streamId: this.streamId,
|
|
100
|
-
model,
|
|
101
|
-
provider: this.adapter.name,
|
|
83
|
+
this.eventOptions = Object.keys(options).length > 0 ? options : void 0;
|
|
84
|
+
this.eventToolNames = tools?.map((t) => t.name);
|
|
85
|
+
aiEventClient.emit("text:request:started", {
|
|
86
|
+
...this.buildTextEventContext(),
|
|
102
87
|
timestamp: Date.now()
|
|
103
88
|
});
|
|
89
|
+
const messagesToEmit = this.params.conversationId ? this.messages.slice(-1).filter((m) => m.role === "user") : this.messages;
|
|
90
|
+
messagesToEmit.forEach((message, index) => {
|
|
91
|
+
const messageIndex = this.params.conversationId ? this.messages.length - 1 : index;
|
|
92
|
+
const messageId = this.createId("msg");
|
|
93
|
+
const baseContext = this.buildTextEventContext();
|
|
94
|
+
const content = this.getContentString(message.content);
|
|
95
|
+
aiEventClient.emit("text:message:created", {
|
|
96
|
+
...baseContext,
|
|
97
|
+
messageId,
|
|
98
|
+
role: message.role,
|
|
99
|
+
content,
|
|
100
|
+
toolCalls: message.toolCalls,
|
|
101
|
+
messageIndex,
|
|
102
|
+
timestamp: Date.now()
|
|
103
|
+
});
|
|
104
|
+
if (message.role === "user") {
|
|
105
|
+
aiEventClient.emit("text:message:user", {
|
|
106
|
+
...baseContext,
|
|
107
|
+
messageId,
|
|
108
|
+
role: "user",
|
|
109
|
+
content,
|
|
110
|
+
messageIndex,
|
|
111
|
+
timestamp: Date.now()
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
104
115
|
}
|
|
105
116
|
afterRun() {
|
|
106
117
|
if (!this.shouldEmitStreamEnd) {
|
|
107
118
|
return;
|
|
108
119
|
}
|
|
109
120
|
const now = Date.now();
|
|
110
|
-
aiEventClient.emit("text:completed", {
|
|
111
|
-
|
|
112
|
-
streamId: this.streamId,
|
|
113
|
-
model: this.params.model,
|
|
121
|
+
aiEventClient.emit("text:request:completed", {
|
|
122
|
+
...this.buildTextEventContext(),
|
|
114
123
|
content: this.accumulatedContent,
|
|
115
124
|
messageId: this.currentMessageId || void 0,
|
|
116
125
|
finishReason: this.lastFinishReason || void 0,
|
|
117
|
-
usage: this.
|
|
118
|
-
timestamp: now
|
|
119
|
-
});
|
|
120
|
-
aiEventClient.emit("stream:ended", {
|
|
121
|
-
requestId: this.requestId,
|
|
122
|
-
streamId: this.streamId,
|
|
123
|
-
totalChunks: this.totalChunkCount,
|
|
126
|
+
usage: this.finishedEvent?.usage,
|
|
124
127
|
duration: now - this.streamStartTime,
|
|
125
128
|
timestamp: now
|
|
126
129
|
});
|
|
@@ -141,7 +144,15 @@ class TextEngine {
|
|
|
141
144
|
beginIteration() {
|
|
142
145
|
this.currentMessageId = this.createId("msg");
|
|
143
146
|
this.accumulatedContent = "";
|
|
144
|
-
this.
|
|
147
|
+
this.finishedEvent = null;
|
|
148
|
+
const baseContext = this.buildTextEventContext();
|
|
149
|
+
aiEventClient.emit("text:message:created", {
|
|
150
|
+
...baseContext,
|
|
151
|
+
messageId: this.currentMessageId,
|
|
152
|
+
role: "assistant",
|
|
153
|
+
content: "",
|
|
154
|
+
timestamp: Date.now()
|
|
155
|
+
});
|
|
145
156
|
}
|
|
146
157
|
async *streamModelResponse() {
|
|
147
158
|
const { temperature, topP, maxTokens, metadata, modelOptions } = this.params;
|
|
@@ -176,102 +187,107 @@ class TextEngine {
|
|
|
176
187
|
}
|
|
177
188
|
handleStreamChunk(chunk) {
|
|
178
189
|
switch (chunk.type) {
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
// AG-UI Events
|
|
191
|
+
case "TEXT_MESSAGE_CONTENT":
|
|
192
|
+
this.handleTextMessageContentEvent(chunk);
|
|
193
|
+
break;
|
|
194
|
+
case "TOOL_CALL_START":
|
|
195
|
+
this.handleToolCallStartEvent(chunk);
|
|
181
196
|
break;
|
|
182
|
-
case "
|
|
183
|
-
this.
|
|
197
|
+
case "TOOL_CALL_ARGS":
|
|
198
|
+
this.handleToolCallArgsEvent(chunk);
|
|
184
199
|
break;
|
|
185
|
-
case "
|
|
186
|
-
this.
|
|
200
|
+
case "TOOL_CALL_END":
|
|
201
|
+
this.handleToolCallEndEvent(chunk);
|
|
187
202
|
break;
|
|
188
|
-
case "
|
|
189
|
-
this.
|
|
203
|
+
case "RUN_FINISHED":
|
|
204
|
+
this.handleRunFinishedEvent(chunk);
|
|
190
205
|
break;
|
|
191
|
-
case "
|
|
192
|
-
this.
|
|
206
|
+
case "RUN_ERROR":
|
|
207
|
+
this.handleRunErrorEvent(chunk);
|
|
193
208
|
break;
|
|
194
|
-
case "
|
|
195
|
-
this.
|
|
209
|
+
case "STEP_FINISHED":
|
|
210
|
+
this.handleStepFinishedEvent(chunk);
|
|
196
211
|
break;
|
|
197
212
|
}
|
|
198
213
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
214
|
+
// ===========================
|
|
215
|
+
// AG-UI Event Handlers
|
|
216
|
+
// ===========================
|
|
217
|
+
handleTextMessageContentEvent(chunk) {
|
|
218
|
+
if (chunk.content) {
|
|
219
|
+
this.accumulatedContent = chunk.content;
|
|
220
|
+
} else {
|
|
221
|
+
this.accumulatedContent += chunk.delta;
|
|
222
|
+
}
|
|
223
|
+
aiEventClient.emit("text:chunk:content", {
|
|
224
|
+
...this.buildTextEventContext(),
|
|
203
225
|
messageId: this.currentMessageId || void 0,
|
|
204
|
-
content:
|
|
226
|
+
content: this.accumulatedContent,
|
|
205
227
|
delta: chunk.delta,
|
|
206
228
|
timestamp: Date.now()
|
|
207
229
|
});
|
|
208
230
|
}
|
|
209
|
-
|
|
210
|
-
this.toolCallManager.
|
|
211
|
-
aiEventClient.emit("
|
|
212
|
-
|
|
231
|
+
handleToolCallStartEvent(chunk) {
|
|
232
|
+
this.toolCallManager.addToolCallStartEvent(chunk);
|
|
233
|
+
aiEventClient.emit("text:chunk:tool-call", {
|
|
234
|
+
...this.buildTextEventContext(),
|
|
213
235
|
messageId: this.currentMessageId || void 0,
|
|
214
|
-
toolCallId: chunk.
|
|
215
|
-
toolName: chunk.
|
|
216
|
-
index: chunk.index,
|
|
217
|
-
arguments:
|
|
236
|
+
toolCallId: chunk.toolCallId,
|
|
237
|
+
toolName: chunk.toolName,
|
|
238
|
+
index: chunk.index ?? 0,
|
|
239
|
+
arguments: "",
|
|
218
240
|
timestamp: Date.now()
|
|
219
241
|
});
|
|
220
242
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
243
|
+
handleToolCallArgsEvent(chunk) {
|
|
244
|
+
this.toolCallManager.addToolCallArgsEvent(chunk);
|
|
245
|
+
aiEventClient.emit("text:chunk:tool-call", {
|
|
246
|
+
...this.buildTextEventContext(),
|
|
224
247
|
messageId: this.currentMessageId || void 0,
|
|
225
248
|
toolCallId: chunk.toolCallId,
|
|
226
|
-
|
|
249
|
+
toolName: "",
|
|
250
|
+
index: 0,
|
|
251
|
+
arguments: chunk.delta,
|
|
227
252
|
timestamp: Date.now()
|
|
228
253
|
});
|
|
229
254
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
streamId: this.streamId,
|
|
244
|
-
messageId: this.currentMessageId || void 0,
|
|
245
|
-
model: this.params.model,
|
|
246
|
-
usage: chunk.usage,
|
|
247
|
-
timestamp: Date.now()
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
this.doneChunk = chunk;
|
|
253
|
-
this.lastFinishReason = chunk.finishReason;
|
|
254
|
-
aiEventClient.emit("stream:chunk:done", {
|
|
255
|
-
streamId: this.streamId,
|
|
255
|
+
handleToolCallEndEvent(chunk) {
|
|
256
|
+
this.toolCallManager.completeToolCall(chunk);
|
|
257
|
+
aiEventClient.emit("text:chunk:tool-result", {
|
|
258
|
+
...this.buildTextEventContext(),
|
|
259
|
+
messageId: this.currentMessageId || void 0,
|
|
260
|
+
toolCallId: chunk.toolCallId,
|
|
261
|
+
result: chunk.result || "",
|
|
262
|
+
timestamp: Date.now()
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
handleRunFinishedEvent(chunk) {
|
|
266
|
+
aiEventClient.emit("text:chunk:done", {
|
|
267
|
+
...this.buildTextEventContext(),
|
|
256
268
|
messageId: this.currentMessageId || void 0,
|
|
257
269
|
finishReason: chunk.finishReason,
|
|
258
270
|
usage: chunk.usage,
|
|
259
271
|
timestamp: Date.now()
|
|
260
272
|
});
|
|
261
273
|
if (chunk.usage) {
|
|
262
|
-
aiEventClient.emit("usage
|
|
263
|
-
|
|
264
|
-
streamId: this.streamId,
|
|
274
|
+
aiEventClient.emit("text:usage", {
|
|
275
|
+
...this.buildTextEventContext(),
|
|
265
276
|
messageId: this.currentMessageId || void 0,
|
|
266
|
-
model: this.params.model,
|
|
267
277
|
usage: chunk.usage,
|
|
268
278
|
timestamp: Date.now()
|
|
269
279
|
});
|
|
270
280
|
}
|
|
281
|
+
if (this.finishedEvent?.finishReason === "tool_calls" && chunk.finishReason === "stop") {
|
|
282
|
+
this.lastFinishReason = chunk.finishReason;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
this.finishedEvent = chunk;
|
|
286
|
+
this.lastFinishReason = chunk.finishReason;
|
|
271
287
|
}
|
|
272
|
-
|
|
273
|
-
aiEventClient.emit("
|
|
274
|
-
|
|
288
|
+
handleRunErrorEvent(chunk) {
|
|
289
|
+
aiEventClient.emit("text:chunk:error", {
|
|
290
|
+
...this.buildTextEventContext(),
|
|
275
291
|
messageId: this.currentMessageId || void 0,
|
|
276
292
|
error: chunk.error.message,
|
|
277
293
|
timestamp: Date.now()
|
|
@@ -279,29 +295,23 @@ class TextEngine {
|
|
|
279
295
|
this.earlyTermination = true;
|
|
280
296
|
this.shouldEmitStreamEnd = false;
|
|
281
297
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
handleStepFinishedEvent(chunk) {
|
|
299
|
+
if (chunk.content || chunk.delta) {
|
|
300
|
+
aiEventClient.emit("text:chunk:thinking", {
|
|
301
|
+
...this.buildTextEventContext(),
|
|
302
|
+
messageId: this.currentMessageId || void 0,
|
|
303
|
+
content: chunk.content || "",
|
|
304
|
+
delta: chunk.delta,
|
|
305
|
+
timestamp: Date.now()
|
|
306
|
+
});
|
|
307
|
+
}
|
|
290
308
|
}
|
|
291
309
|
async *checkForPendingToolCalls() {
|
|
292
310
|
const pendingToolCalls = this.getPendingToolCallsFromMessages();
|
|
293
311
|
if (pendingToolCalls.length === 0) {
|
|
294
312
|
return "continue";
|
|
295
313
|
}
|
|
296
|
-
const
|
|
297
|
-
aiEventClient.emit("text:iteration", {
|
|
298
|
-
requestId: this.requestId,
|
|
299
|
-
streamId: this.streamId,
|
|
300
|
-
iterationNumber: this.iterationCount + 1,
|
|
301
|
-
messageCount: this.messages.length,
|
|
302
|
-
toolCallCount: pendingToolCalls.length,
|
|
303
|
-
timestamp: Date.now()
|
|
304
|
-
});
|
|
314
|
+
const finishEvent = this.createSyntheticFinishedEvent();
|
|
305
315
|
const { approvals, clientToolResults } = this.collectClientState();
|
|
306
316
|
const executionResult = await executeToolCalls(
|
|
307
317
|
pendingToolCalls,
|
|
@@ -312,13 +322,13 @@ class TextEngine {
|
|
|
312
322
|
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
|
|
313
323
|
for (const chunk of this.emitApprovalRequests(
|
|
314
324
|
executionResult.needsApproval,
|
|
315
|
-
|
|
325
|
+
finishEvent
|
|
316
326
|
)) {
|
|
317
327
|
yield chunk;
|
|
318
328
|
}
|
|
319
329
|
for (const chunk of this.emitClientToolInputs(
|
|
320
330
|
executionResult.needsClientExecution,
|
|
321
|
-
|
|
331
|
+
finishEvent
|
|
322
332
|
)) {
|
|
323
333
|
yield chunk;
|
|
324
334
|
}
|
|
@@ -327,7 +337,7 @@ class TextEngine {
|
|
|
327
337
|
}
|
|
328
338
|
const toolResultChunks = this.emitToolResults(
|
|
329
339
|
executionResult.results,
|
|
330
|
-
|
|
340
|
+
finishEvent
|
|
331
341
|
);
|
|
332
342
|
for (const chunk of toolResultChunks) {
|
|
333
343
|
yield chunk;
|
|
@@ -340,19 +350,11 @@ class TextEngine {
|
|
|
340
350
|
return;
|
|
341
351
|
}
|
|
342
352
|
const toolCalls = this.toolCallManager.getToolCalls();
|
|
343
|
-
const
|
|
344
|
-
if (!
|
|
353
|
+
const finishEvent = this.finishedEvent;
|
|
354
|
+
if (!finishEvent || toolCalls.length === 0) {
|
|
345
355
|
this.setToolPhase("stop");
|
|
346
356
|
return;
|
|
347
357
|
}
|
|
348
|
-
aiEventClient.emit("text:iteration", {
|
|
349
|
-
requestId: this.requestId,
|
|
350
|
-
streamId: this.streamId,
|
|
351
|
-
iterationNumber: this.iterationCount + 1,
|
|
352
|
-
messageCount: this.messages.length,
|
|
353
|
-
toolCallCount: toolCalls.length,
|
|
354
|
-
timestamp: Date.now()
|
|
355
|
-
});
|
|
356
358
|
this.addAssistantToolCallMessage(toolCalls);
|
|
357
359
|
const { approvals, clientToolResults } = this.collectClientState();
|
|
358
360
|
const executionResult = await executeToolCalls(
|
|
@@ -364,13 +366,13 @@ class TextEngine {
|
|
|
364
366
|
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
|
|
365
367
|
for (const chunk of this.emitApprovalRequests(
|
|
366
368
|
executionResult.needsApproval,
|
|
367
|
-
|
|
369
|
+
finishEvent
|
|
368
370
|
)) {
|
|
369
371
|
yield chunk;
|
|
370
372
|
}
|
|
371
373
|
for (const chunk of this.emitClientToolInputs(
|
|
372
374
|
executionResult.needsClientExecution,
|
|
373
|
-
|
|
375
|
+
finishEvent
|
|
374
376
|
)) {
|
|
375
377
|
yield chunk;
|
|
376
378
|
}
|
|
@@ -379,7 +381,7 @@ class TextEngine {
|
|
|
379
381
|
}
|
|
380
382
|
const toolResultChunks = this.emitToolResults(
|
|
381
383
|
executionResult.results,
|
|
382
|
-
|
|
384
|
+
finishEvent
|
|
383
385
|
);
|
|
384
386
|
for (const chunk of toolResultChunks) {
|
|
385
387
|
yield chunk;
|
|
@@ -388,9 +390,10 @@ class TextEngine {
|
|
|
388
390
|
this.setToolPhase("continue");
|
|
389
391
|
}
|
|
390
392
|
shouldExecuteToolPhase() {
|
|
391
|
-
return this.
|
|
393
|
+
return this.finishedEvent?.finishReason === "tool_calls" && this.tools.length > 0 && this.toolCallManager.hasToolCalls();
|
|
392
394
|
}
|
|
393
395
|
addAssistantToolCallMessage(toolCalls) {
|
|
396
|
+
const messageId = this.currentMessageId ?? this.createId("msg");
|
|
394
397
|
this.messages = [
|
|
395
398
|
...this.messages,
|
|
396
399
|
{
|
|
@@ -399,30 +402,60 @@ class TextEngine {
|
|
|
399
402
|
toolCalls
|
|
400
403
|
}
|
|
401
404
|
];
|
|
405
|
+
aiEventClient.emit("text:message:created", {
|
|
406
|
+
...this.buildTextEventContext(),
|
|
407
|
+
messageId,
|
|
408
|
+
role: "assistant",
|
|
409
|
+
content: this.accumulatedContent || "",
|
|
410
|
+
toolCalls,
|
|
411
|
+
timestamp: Date.now()
|
|
412
|
+
});
|
|
402
413
|
}
|
|
403
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Extract client state (approvals and client tool results) from original messages.
|
|
416
|
+
* This is called in the constructor BEFORE converting to ModelMessage format,
|
|
417
|
+
* because the parts array (which contains approval state) is lost during conversion.
|
|
418
|
+
*/
|
|
419
|
+
extractClientStateFromOriginalMessages(originalMessages) {
|
|
404
420
|
const approvals = /* @__PURE__ */ new Map();
|
|
405
421
|
const clientToolResults = /* @__PURE__ */ new Map();
|
|
406
|
-
for (const message of
|
|
422
|
+
for (const message of originalMessages) {
|
|
407
423
|
if (message.role === "assistant" && message.parts) {
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
424
|
+
for (const part of message.parts) {
|
|
425
|
+
if (part.type === "tool-call") {
|
|
426
|
+
if (part.output !== void 0 && !part.approval) {
|
|
427
|
+
clientToolResults.set(part.id, part.output);
|
|
428
|
+
}
|
|
429
|
+
if (part.approval?.id && part.approval?.approved !== void 0 && part.state === "approval-responded") {
|
|
430
|
+
approvals.set(part.approval.id, part.approval.approved);
|
|
431
|
+
}
|
|
415
432
|
}
|
|
416
433
|
}
|
|
417
434
|
}
|
|
418
435
|
}
|
|
419
436
|
return { approvals, clientToolResults };
|
|
420
437
|
}
|
|
421
|
-
|
|
438
|
+
collectClientState() {
|
|
439
|
+
const approvals = new Map(this.initialApprovals);
|
|
440
|
+
const clientToolResults = new Map(this.initialClientToolResults);
|
|
441
|
+
for (const message of this.messages) {
|
|
442
|
+
if (message.role === "tool" && message.toolCallId) {
|
|
443
|
+
let output;
|
|
444
|
+
try {
|
|
445
|
+
output = JSON.parse(message.content);
|
|
446
|
+
} catch {
|
|
447
|
+
output = message.content;
|
|
448
|
+
}
|
|
449
|
+
clientToolResults.set(message.toolCallId, output);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return { approvals, clientToolResults };
|
|
453
|
+
}
|
|
454
|
+
emitApprovalRequests(approvals, finishEvent) {
|
|
422
455
|
const chunks = [];
|
|
423
456
|
for (const approval of approvals) {
|
|
424
|
-
aiEventClient.emit("
|
|
425
|
-
|
|
457
|
+
aiEventClient.emit("tools:approval:requested", {
|
|
458
|
+
...this.buildTextEventContext(),
|
|
426
459
|
messageId: this.currentMessageId || void 0,
|
|
427
460
|
toolCallId: approval.toolCallId,
|
|
428
461
|
toolName: approval.toolName,
|
|
@@ -431,26 +464,28 @@ class TextEngine {
|
|
|
431
464
|
timestamp: Date.now()
|
|
432
465
|
});
|
|
433
466
|
chunks.push({
|
|
434
|
-
type: "
|
|
435
|
-
id: doneChunk.id,
|
|
436
|
-
model: doneChunk.model,
|
|
467
|
+
type: "CUSTOM",
|
|
437
468
|
timestamp: Date.now(),
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
469
|
+
model: finishEvent.model,
|
|
470
|
+
name: "approval-requested",
|
|
471
|
+
data: {
|
|
472
|
+
toolCallId: approval.toolCallId,
|
|
473
|
+
toolName: approval.toolName,
|
|
474
|
+
input: approval.input,
|
|
475
|
+
approval: {
|
|
476
|
+
id: approval.approvalId,
|
|
477
|
+
needsApproval: true
|
|
478
|
+
}
|
|
444
479
|
}
|
|
445
480
|
});
|
|
446
481
|
}
|
|
447
482
|
return chunks;
|
|
448
483
|
}
|
|
449
|
-
emitClientToolInputs(clientRequests,
|
|
484
|
+
emitClientToolInputs(clientRequests, finishEvent) {
|
|
450
485
|
const chunks = [];
|
|
451
486
|
for (const clientTool of clientRequests) {
|
|
452
|
-
aiEventClient.emit("
|
|
453
|
-
|
|
487
|
+
aiEventClient.emit("tools:input:available", {
|
|
488
|
+
...this.buildTextEventContext(),
|
|
454
489
|
messageId: this.currentMessageId || void 0,
|
|
455
490
|
toolCallId: clientTool.toolCallId,
|
|
456
491
|
toolName: clientTool.toolName,
|
|
@@ -458,23 +493,24 @@ class TextEngine {
|
|
|
458
493
|
timestamp: Date.now()
|
|
459
494
|
});
|
|
460
495
|
chunks.push({
|
|
461
|
-
type: "
|
|
462
|
-
id: doneChunk.id,
|
|
463
|
-
model: doneChunk.model,
|
|
496
|
+
type: "CUSTOM",
|
|
464
497
|
timestamp: Date.now(),
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
498
|
+
model: finishEvent.model,
|
|
499
|
+
name: "tool-input-available",
|
|
500
|
+
data: {
|
|
501
|
+
toolCallId: clientTool.toolCallId,
|
|
502
|
+
toolName: clientTool.toolName,
|
|
503
|
+
input: clientTool.input
|
|
504
|
+
}
|
|
468
505
|
});
|
|
469
506
|
}
|
|
470
507
|
return chunks;
|
|
471
508
|
}
|
|
472
|
-
emitToolResults(results,
|
|
509
|
+
emitToolResults(results, finishEvent) {
|
|
473
510
|
const chunks = [];
|
|
474
511
|
for (const result of results) {
|
|
475
|
-
aiEventClient.emit("
|
|
476
|
-
|
|
477
|
-
streamId: this.streamId,
|
|
512
|
+
aiEventClient.emit("tools:call:completed", {
|
|
513
|
+
...this.buildTextEventContext(),
|
|
478
514
|
messageId: this.currentMessageId || void 0,
|
|
479
515
|
toolCallId: result.toolCallId,
|
|
480
516
|
toolName: result.toolName,
|
|
@@ -483,15 +519,14 @@ class TextEngine {
|
|
|
483
519
|
timestamp: Date.now()
|
|
484
520
|
});
|
|
485
521
|
const content = JSON.stringify(result.result);
|
|
486
|
-
|
|
487
|
-
type: "
|
|
488
|
-
id: doneChunk.id,
|
|
489
|
-
model: doneChunk.model,
|
|
522
|
+
chunks.push({
|
|
523
|
+
type: "TOOL_CALL_END",
|
|
490
524
|
timestamp: Date.now(),
|
|
525
|
+
model: finishEvent.model,
|
|
491
526
|
toolCallId: result.toolCallId,
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
527
|
+
toolName: result.toolName,
|
|
528
|
+
result: content
|
|
529
|
+
});
|
|
495
530
|
this.messages = [
|
|
496
531
|
...this.messages,
|
|
497
532
|
{
|
|
@@ -500,14 +535,35 @@ class TextEngine {
|
|
|
500
535
|
toolCallId: result.toolCallId
|
|
501
536
|
}
|
|
502
537
|
];
|
|
538
|
+
aiEventClient.emit("text:message:created", {
|
|
539
|
+
...this.buildTextEventContext(),
|
|
540
|
+
messageId: this.createId("msg"),
|
|
541
|
+
role: "tool",
|
|
542
|
+
content,
|
|
543
|
+
timestamp: Date.now()
|
|
544
|
+
});
|
|
503
545
|
}
|
|
504
546
|
return chunks;
|
|
505
547
|
}
|
|
506
548
|
getPendingToolCallsFromMessages() {
|
|
507
|
-
const completedToolIds = new Set(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
549
|
+
const completedToolIds = /* @__PURE__ */ new Set();
|
|
550
|
+
for (const message of this.messages) {
|
|
551
|
+
if (message.role === "tool" && message.toolCallId) {
|
|
552
|
+
let hasPendingExecution = false;
|
|
553
|
+
if (typeof message.content === "string") {
|
|
554
|
+
try {
|
|
555
|
+
const parsed = JSON.parse(message.content);
|
|
556
|
+
if (parsed.pendingExecution === true) {
|
|
557
|
+
hasPendingExecution = true;
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (!hasPendingExecution) {
|
|
563
|
+
completedToolIds.add(message.toolCallId);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
511
567
|
const pending = [];
|
|
512
568
|
for (const message of this.messages) {
|
|
513
569
|
if (message.role === "assistant" && message.toolCalls) {
|
|
@@ -520,10 +576,10 @@ class TextEngine {
|
|
|
520
576
|
}
|
|
521
577
|
return pending;
|
|
522
578
|
}
|
|
523
|
-
|
|
579
|
+
createSyntheticFinishedEvent() {
|
|
524
580
|
return {
|
|
525
|
-
type: "
|
|
526
|
-
|
|
581
|
+
type: "RUN_FINISHED",
|
|
582
|
+
runId: this.createId("pending"),
|
|
527
583
|
model: this.params.model,
|
|
528
584
|
timestamp: Date.now(),
|
|
529
585
|
finishReason: "tool_calls"
|
|
@@ -542,6 +598,28 @@ class TextEngine {
|
|
|
542
598
|
isAborted() {
|
|
543
599
|
return !!this.effectiveSignal?.aborted;
|
|
544
600
|
}
|
|
601
|
+
buildTextEventContext() {
|
|
602
|
+
return {
|
|
603
|
+
requestId: this.requestId,
|
|
604
|
+
streamId: this.streamId,
|
|
605
|
+
provider: this.adapter.name,
|
|
606
|
+
model: this.params.model,
|
|
607
|
+
clientId: this.params.conversationId,
|
|
608
|
+
source: "server",
|
|
609
|
+
systemPrompts: this.systemPrompts.length > 0 ? this.systemPrompts : void 0,
|
|
610
|
+
toolNames: this.eventToolNames,
|
|
611
|
+
options: this.eventOptions,
|
|
612
|
+
modelOptions: this.params.modelOptions,
|
|
613
|
+
messageCount: this.initialMessageCount,
|
|
614
|
+
hasTools: this.tools.length > 0,
|
|
615
|
+
streaming: true
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
getContentString(content) {
|
|
619
|
+
if (typeof content === "string") return content;
|
|
620
|
+
const text = content?.map((part) => part.type === "text" ? part.content : "").join("") || "";
|
|
621
|
+
return text;
|
|
622
|
+
}
|
|
545
623
|
setToolPhase(phase) {
|
|
546
624
|
this.toolPhase = phase;
|
|
547
625
|
if (phase === "wait") {
|