@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.
Files changed (44) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/activities/chat/index.js +264 -186
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/messages.js +22 -0
  5. package/dist/esm/activities/chat/messages.js.map +1 -1
  6. package/dist/esm/activities/chat/stream/index.d.ts +1 -1
  7. package/dist/esm/activities/chat/stream/processor.d.ts +22 -43
  8. package/dist/esm/activities/chat/stream/processor.js +93 -157
  9. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  10. package/dist/esm/activities/chat/tools/tool-calls.d.ts +22 -21
  11. package/dist/esm/activities/chat/tools/tool-calls.js +40 -27
  12. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  13. package/dist/esm/activities/generateImage/index.js +41 -1
  14. package/dist/esm/activities/generateImage/index.js.map +1 -1
  15. package/dist/esm/activities/generateSpeech/index.js +33 -1
  16. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  17. package/dist/esm/activities/generateTranscription/index.js +29 -1
  18. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  19. package/dist/esm/activities/generateVideo/index.js +50 -0
  20. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  21. package/dist/esm/activities/summarize/index.js +9 -8
  22. package/dist/esm/activities/summarize/index.js.map +1 -1
  23. package/dist/esm/event-client.d.ts +378 -254
  24. package/dist/esm/event-client.js.map +1 -1
  25. package/dist/esm/index.d.ts +2 -2
  26. package/dist/esm/stream-to-response.d.ts +1 -1
  27. package/dist/esm/stream-to-response.js +5 -3
  28. package/dist/esm/stream-to-response.js.map +1 -1
  29. package/dist/esm/types.d.ts +162 -46
  30. package/package.json +1 -1
  31. package/src/activities/chat/index.ts +362 -216
  32. package/src/activities/chat/messages.ts +40 -1
  33. package/src/activities/chat/stream/index.ts +1 -5
  34. package/src/activities/chat/stream/processor.ts +142 -270
  35. package/src/activities/chat/tools/tool-calls.ts +55 -49
  36. package/src/activities/generateImage/index.ts +47 -2
  37. package/src/activities/generateSpeech/index.ts +37 -1
  38. package/src/activities/generateTranscription/index.ts +33 -1
  39. package/src/activities/generateVideo/index.ts +54 -0
  40. package/src/activities/summarize/index.ts +10 -9
  41. package/src/event-client.ts +460 -256
  42. package/src/index.ts +2 -3
  43. package/src/stream-to-response.ts +8 -6
  44. 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.doneChunk = null;
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
- this.messages = config.params.messages;
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
- aiEventClient.emit("text:started", {
85
- requestId: this.requestId,
86
- streamId: this.streamId,
87
- model,
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
- requestId: this.requestId,
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.doneChunk?.usage,
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.doneChunk = null;
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
- case "content":
180
- this.handleContentChunk(chunk);
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 "tool_call":
183
- this.handleToolCallChunk(chunk);
197
+ case "TOOL_CALL_ARGS":
198
+ this.handleToolCallArgsEvent(chunk);
184
199
  break;
185
- case "tool_result":
186
- this.handleToolResultChunk(chunk);
200
+ case "TOOL_CALL_END":
201
+ this.handleToolCallEndEvent(chunk);
187
202
  break;
188
- case "done":
189
- this.handleDoneChunk(chunk);
203
+ case "RUN_FINISHED":
204
+ this.handleRunFinishedEvent(chunk);
190
205
  break;
191
- case "error":
192
- this.handleErrorChunk(chunk);
206
+ case "RUN_ERROR":
207
+ this.handleRunErrorEvent(chunk);
193
208
  break;
194
- case "thinking":
195
- this.handleThinkingChunk(chunk);
209
+ case "STEP_FINISHED":
210
+ this.handleStepFinishedEvent(chunk);
196
211
  break;
197
212
  }
198
213
  }
199
- handleContentChunk(chunk) {
200
- this.accumulatedContent = chunk.content;
201
- aiEventClient.emit("stream:chunk:content", {
202
- streamId: this.streamId,
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: chunk.content,
226
+ content: this.accumulatedContent,
205
227
  delta: chunk.delta,
206
228
  timestamp: Date.now()
207
229
  });
208
230
  }
209
- handleToolCallChunk(chunk) {
210
- this.toolCallManager.addToolCallChunk(chunk);
211
- aiEventClient.emit("stream:chunk:tool-call", {
212
- streamId: this.streamId,
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.toolCall.id,
215
- toolName: chunk.toolCall.function.name,
216
- index: chunk.index,
217
- arguments: chunk.toolCall.function.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
- handleToolResultChunk(chunk) {
222
- aiEventClient.emit("stream:chunk:tool-result", {
223
- streamId: this.streamId,
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
- result: chunk.content,
249
+ toolName: "",
250
+ index: 0,
251
+ arguments: chunk.delta,
227
252
  timestamp: Date.now()
228
253
  });
229
254
  }
230
- handleDoneChunk(chunk) {
231
- if (this.doneChunk?.finishReason === "tool_calls" && chunk.finishReason === "stop") {
232
- this.lastFinishReason = chunk.finishReason;
233
- aiEventClient.emit("stream:chunk:done", {
234
- streamId: this.streamId,
235
- messageId: this.currentMessageId || void 0,
236
- finishReason: chunk.finishReason,
237
- usage: chunk.usage,
238
- timestamp: Date.now()
239
- });
240
- if (chunk.usage) {
241
- aiEventClient.emit("usage:tokens", {
242
- requestId: this.requestId,
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:tokens", {
263
- requestId: this.requestId,
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
- handleErrorChunk(chunk) {
273
- aiEventClient.emit("stream:chunk:error", {
274
- streamId: this.streamId,
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
- handleThinkingChunk(chunk) {
283
- aiEventClient.emit("stream:chunk:thinking", {
284
- streamId: this.streamId,
285
- messageId: this.currentMessageId || void 0,
286
- content: chunk.content,
287
- delta: chunk.delta,
288
- timestamp: Date.now()
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 doneChunk = this.createSyntheticDoneChunk();
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
- doneChunk
325
+ finishEvent
316
326
  )) {
317
327
  yield chunk;
318
328
  }
319
329
  for (const chunk of this.emitClientToolInputs(
320
330
  executionResult.needsClientExecution,
321
- doneChunk
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
- doneChunk
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 doneChunk = this.doneChunk;
344
- if (!doneChunk || toolCalls.length === 0) {
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
- doneChunk
369
+ finishEvent
368
370
  )) {
369
371
  yield chunk;
370
372
  }
371
373
  for (const chunk of this.emitClientToolInputs(
372
374
  executionResult.needsClientExecution,
373
- doneChunk
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
- doneChunk
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.doneChunk?.finishReason === "tool_calls" && this.tools.length > 0 && this.toolCallManager.hasToolCalls();
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
- collectClientState() {
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 this.messages) {
422
+ for (const message of originalMessages) {
407
423
  if (message.role === "assistant" && message.parts) {
408
- const parts = message.parts;
409
- for (const part of parts) {
410
- if (part.type === "tool-call" && part.state === "approval-responded" && part.approval) {
411
- approvals.set(part.approval.id, part.approval.approved);
412
- }
413
- if (part.type === "tool-call" && part.output !== void 0 && !part.approval) {
414
- clientToolResults.set(part.id, part.output);
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
- emitApprovalRequests(approvals, doneChunk) {
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("stream:approval-requested", {
425
- streamId: this.streamId,
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: "approval-requested",
435
- id: doneChunk.id,
436
- model: doneChunk.model,
467
+ type: "CUSTOM",
437
468
  timestamp: Date.now(),
438
- toolCallId: approval.toolCallId,
439
- toolName: approval.toolName,
440
- input: approval.input,
441
- approval: {
442
- id: approval.approvalId,
443
- needsApproval: true
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, doneChunk) {
484
+ emitClientToolInputs(clientRequests, finishEvent) {
450
485
  const chunks = [];
451
486
  for (const clientTool of clientRequests) {
452
- aiEventClient.emit("stream:tool-input-available", {
453
- streamId: this.streamId,
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: "tool-input-available",
462
- id: doneChunk.id,
463
- model: doneChunk.model,
496
+ type: "CUSTOM",
464
497
  timestamp: Date.now(),
465
- toolCallId: clientTool.toolCallId,
466
- toolName: clientTool.toolName,
467
- input: clientTool.input
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, doneChunk) {
509
+ emitToolResults(results, finishEvent) {
473
510
  const chunks = [];
474
511
  for (const result of results) {
475
- aiEventClient.emit("tool:call-completed", {
476
- requestId: this.requestId,
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
- const chunk = {
487
- type: "tool_result",
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
- content
493
- };
494
- chunks.push(chunk);
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
- this.messages.filter((message) => message.role === "tool" && message.toolCallId).map((message) => message.toolCallId)
509
- // toolCallId exists due to filter
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
- createSyntheticDoneChunk() {
579
+ createSyntheticFinishedEvent() {
524
580
  return {
525
- type: "done",
526
- id: this.createId("pending"),
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") {