@tanstack/ai 0.2.2 → 0.3.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.
Files changed (41) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/activities/chat/index.js +206 -172
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/stream/index.d.ts +1 -1
  5. package/dist/esm/activities/chat/stream/processor.d.ts +22 -43
  6. package/dist/esm/activities/chat/stream/processor.js +89 -156
  7. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  8. package/dist/esm/activities/chat/tools/tool-calls.d.ts +22 -21
  9. package/dist/esm/activities/chat/tools/tool-calls.js +40 -27
  10. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  11. package/dist/esm/activities/generateImage/index.js +41 -1
  12. package/dist/esm/activities/generateImage/index.js.map +1 -1
  13. package/dist/esm/activities/generateSpeech/index.js +33 -1
  14. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  15. package/dist/esm/activities/generateTranscription/index.js +29 -1
  16. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  17. package/dist/esm/activities/generateVideo/index.js +50 -0
  18. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  19. package/dist/esm/activities/summarize/index.js +9 -8
  20. package/dist/esm/activities/summarize/index.js.map +1 -1
  21. package/dist/esm/event-client.d.ts +378 -254
  22. package/dist/esm/event-client.js.map +1 -1
  23. package/dist/esm/index.d.ts +2 -2
  24. package/dist/esm/stream-to-response.d.ts +1 -1
  25. package/dist/esm/stream-to-response.js +5 -3
  26. package/dist/esm/stream-to-response.js.map +1 -1
  27. package/dist/esm/types.d.ts +162 -46
  28. package/package.json +1 -1
  29. package/src/activities/chat/index.ts +267 -191
  30. package/src/activities/chat/stream/index.ts +1 -5
  31. package/src/activities/chat/stream/processor.ts +128 -269
  32. package/src/activities/chat/tools/tool-calls.ts +55 -49
  33. package/src/activities/generateImage/index.ts +47 -2
  34. package/src/activities/generateSpeech/index.ts +37 -1
  35. package/src/activities/generateTranscription/index.ts +33 -1
  36. package/src/activities/generateVideo/index.ts +54 -0
  37. package/src/activities/summarize/index.ts +10 -9
  38. package/src/event-client.ts +460 -256
  39. package/src/index.ts +2 -3
  40. package/src/stream-to-response.ts +8 -6
  41. package/src/types.ts +209 -65
package/README.md CHANGED
@@ -45,6 +45,7 @@ A powerful, type-safe AI SDK for building AI-powered applications.
45
45
  - Headless chat state management with adapters (SSE, HTTP stream, custom)
46
46
  - Isomorphic type-safe tools with server/client execution
47
47
  - **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions
48
+ - **Observability events** - Structured, typed events for text, tools, image, speech, transcription, and video ([docs](./docs/guides/observability.md))
48
49
 
49
50
  ### <a href="https://tanstack.com/ai">Read the docs →</b></a>
50
51
 
@@ -15,7 +15,7 @@ class TextEngine {
15
15
  this.totalChunkCount = 0;
16
16
  this.currentMessageId = null;
17
17
  this.accumulatedContent = "";
18
- this.doneChunk = null;
18
+ this.finishedEvent = null;
19
19
  this.shouldEmitStreamEnd = true;
20
20
  this.earlyTermination = false;
21
21
  this.toolPhase = "continue";
@@ -66,61 +66,56 @@ class TextEngine {
66
66
  }
67
67
  beforeRun() {
68
68
  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;
69
+ const { tools, temperature, topP, maxTokens, metadata } = this.params;
79
70
  const options = {};
80
71
  if (temperature !== void 0) options.temperature = temperature;
81
72
  if (topP !== void 0) options.topP = topP;
82
73
  if (maxTokens !== void 0) options.maxTokens = maxTokens;
83
74
  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,
75
+ this.eventOptions = Object.keys(options).length > 0 ? options : void 0;
76
+ this.eventToolNames = tools?.map((t) => t.name);
77
+ aiEventClient.emit("text:request:started", {
78
+ ...this.buildTextEventContext(),
102
79
  timestamp: Date.now()
103
80
  });
81
+ const messagesToEmit = this.params.conversationId ? this.messages.slice(-1).filter((m) => m.role === "user") : this.messages;
82
+ messagesToEmit.forEach((message, index) => {
83
+ const messageIndex = this.params.conversationId ? this.messages.length - 1 : index;
84
+ const messageId = this.createId("msg");
85
+ const baseContext = this.buildTextEventContext();
86
+ const content = this.getContentString(message.content);
87
+ aiEventClient.emit("text:message:created", {
88
+ ...baseContext,
89
+ messageId,
90
+ role: message.role,
91
+ content,
92
+ toolCalls: message.toolCalls,
93
+ messageIndex,
94
+ timestamp: Date.now()
95
+ });
96
+ if (message.role === "user") {
97
+ aiEventClient.emit("text:message:user", {
98
+ ...baseContext,
99
+ messageId,
100
+ role: "user",
101
+ content,
102
+ messageIndex,
103
+ timestamp: Date.now()
104
+ });
105
+ }
106
+ });
104
107
  }
105
108
  afterRun() {
106
109
  if (!this.shouldEmitStreamEnd) {
107
110
  return;
108
111
  }
109
112
  const now = Date.now();
110
- aiEventClient.emit("text:completed", {
111
- requestId: this.requestId,
112
- streamId: this.streamId,
113
- model: this.params.model,
113
+ aiEventClient.emit("text:request:completed", {
114
+ ...this.buildTextEventContext(),
114
115
  content: this.accumulatedContent,
115
116
  messageId: this.currentMessageId || void 0,
116
117
  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,
118
+ usage: this.finishedEvent?.usage,
124
119
  duration: now - this.streamStartTime,
125
120
  timestamp: now
126
121
  });
@@ -141,7 +136,15 @@ class TextEngine {
141
136
  beginIteration() {
142
137
  this.currentMessageId = this.createId("msg");
143
138
  this.accumulatedContent = "";
144
- this.doneChunk = null;
139
+ this.finishedEvent = null;
140
+ const baseContext = this.buildTextEventContext();
141
+ aiEventClient.emit("text:message:created", {
142
+ ...baseContext,
143
+ messageId: this.currentMessageId,
144
+ role: "assistant",
145
+ content: "",
146
+ timestamp: Date.now()
147
+ });
145
148
  }
146
149
  async *streamModelResponse() {
147
150
  const { temperature, topP, maxTokens, metadata, modelOptions } = this.params;
@@ -176,102 +179,107 @@ class TextEngine {
176
179
  }
177
180
  handleStreamChunk(chunk) {
178
181
  switch (chunk.type) {
179
- case "content":
180
- this.handleContentChunk(chunk);
182
+ // AG-UI Events
183
+ case "TEXT_MESSAGE_CONTENT":
184
+ this.handleTextMessageContentEvent(chunk);
185
+ break;
186
+ case "TOOL_CALL_START":
187
+ this.handleToolCallStartEvent(chunk);
181
188
  break;
182
- case "tool_call":
183
- this.handleToolCallChunk(chunk);
189
+ case "TOOL_CALL_ARGS":
190
+ this.handleToolCallArgsEvent(chunk);
184
191
  break;
185
- case "tool_result":
186
- this.handleToolResultChunk(chunk);
192
+ case "TOOL_CALL_END":
193
+ this.handleToolCallEndEvent(chunk);
187
194
  break;
188
- case "done":
189
- this.handleDoneChunk(chunk);
195
+ case "RUN_FINISHED":
196
+ this.handleRunFinishedEvent(chunk);
190
197
  break;
191
- case "error":
192
- this.handleErrorChunk(chunk);
198
+ case "RUN_ERROR":
199
+ this.handleRunErrorEvent(chunk);
193
200
  break;
194
- case "thinking":
195
- this.handleThinkingChunk(chunk);
201
+ case "STEP_FINISHED":
202
+ this.handleStepFinishedEvent(chunk);
196
203
  break;
197
204
  }
198
205
  }
199
- handleContentChunk(chunk) {
200
- this.accumulatedContent = chunk.content;
201
- aiEventClient.emit("stream:chunk:content", {
202
- streamId: this.streamId,
206
+ // ===========================
207
+ // AG-UI Event Handlers
208
+ // ===========================
209
+ handleTextMessageContentEvent(chunk) {
210
+ if (chunk.content) {
211
+ this.accumulatedContent = chunk.content;
212
+ } else {
213
+ this.accumulatedContent += chunk.delta;
214
+ }
215
+ aiEventClient.emit("text:chunk:content", {
216
+ ...this.buildTextEventContext(),
203
217
  messageId: this.currentMessageId || void 0,
204
- content: chunk.content,
218
+ content: this.accumulatedContent,
205
219
  delta: chunk.delta,
206
220
  timestamp: Date.now()
207
221
  });
208
222
  }
209
- handleToolCallChunk(chunk) {
210
- this.toolCallManager.addToolCallChunk(chunk);
211
- aiEventClient.emit("stream:chunk:tool-call", {
212
- streamId: this.streamId,
223
+ handleToolCallStartEvent(chunk) {
224
+ this.toolCallManager.addToolCallStartEvent(chunk);
225
+ aiEventClient.emit("text:chunk:tool-call", {
226
+ ...this.buildTextEventContext(),
213
227
  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,
228
+ toolCallId: chunk.toolCallId,
229
+ toolName: chunk.toolName,
230
+ index: chunk.index ?? 0,
231
+ arguments: "",
218
232
  timestamp: Date.now()
219
233
  });
220
234
  }
221
- handleToolResultChunk(chunk) {
222
- aiEventClient.emit("stream:chunk:tool-result", {
223
- streamId: this.streamId,
235
+ handleToolCallArgsEvent(chunk) {
236
+ this.toolCallManager.addToolCallArgsEvent(chunk);
237
+ aiEventClient.emit("text:chunk:tool-call", {
238
+ ...this.buildTextEventContext(),
224
239
  messageId: this.currentMessageId || void 0,
225
240
  toolCallId: chunk.toolCallId,
226
- result: chunk.content,
241
+ toolName: "",
242
+ index: 0,
243
+ arguments: chunk.delta,
227
244
  timestamp: Date.now()
228
245
  });
229
246
  }
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,
247
+ handleToolCallEndEvent(chunk) {
248
+ this.toolCallManager.completeToolCall(chunk);
249
+ aiEventClient.emit("text:chunk:tool-result", {
250
+ ...this.buildTextEventContext(),
251
+ messageId: this.currentMessageId || void 0,
252
+ toolCallId: chunk.toolCallId,
253
+ result: chunk.result || "",
254
+ timestamp: Date.now()
255
+ });
256
+ }
257
+ handleRunFinishedEvent(chunk) {
258
+ aiEventClient.emit("text:chunk:done", {
259
+ ...this.buildTextEventContext(),
256
260
  messageId: this.currentMessageId || void 0,
257
261
  finishReason: chunk.finishReason,
258
262
  usage: chunk.usage,
259
263
  timestamp: Date.now()
260
264
  });
261
265
  if (chunk.usage) {
262
- aiEventClient.emit("usage:tokens", {
263
- requestId: this.requestId,
264
- streamId: this.streamId,
266
+ aiEventClient.emit("text:usage", {
267
+ ...this.buildTextEventContext(),
265
268
  messageId: this.currentMessageId || void 0,
266
- model: this.params.model,
267
269
  usage: chunk.usage,
268
270
  timestamp: Date.now()
269
271
  });
270
272
  }
273
+ if (this.finishedEvent?.finishReason === "tool_calls" && chunk.finishReason === "stop") {
274
+ this.lastFinishReason = chunk.finishReason;
275
+ return;
276
+ }
277
+ this.finishedEvent = chunk;
278
+ this.lastFinishReason = chunk.finishReason;
271
279
  }
272
- handleErrorChunk(chunk) {
273
- aiEventClient.emit("stream:chunk:error", {
274
- streamId: this.streamId,
280
+ handleRunErrorEvent(chunk) {
281
+ aiEventClient.emit("text:chunk:error", {
282
+ ...this.buildTextEventContext(),
275
283
  messageId: this.currentMessageId || void 0,
276
284
  error: chunk.error.message,
277
285
  timestamp: Date.now()
@@ -279,29 +287,23 @@ class TextEngine {
279
287
  this.earlyTermination = true;
280
288
  this.shouldEmitStreamEnd = false;
281
289
  }
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
- });
290
+ handleStepFinishedEvent(chunk) {
291
+ if (chunk.content || chunk.delta) {
292
+ aiEventClient.emit("text:chunk:thinking", {
293
+ ...this.buildTextEventContext(),
294
+ messageId: this.currentMessageId || void 0,
295
+ content: chunk.content || "",
296
+ delta: chunk.delta,
297
+ timestamp: Date.now()
298
+ });
299
+ }
290
300
  }
291
301
  async *checkForPendingToolCalls() {
292
302
  const pendingToolCalls = this.getPendingToolCallsFromMessages();
293
303
  if (pendingToolCalls.length === 0) {
294
304
  return "continue";
295
305
  }
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
- });
306
+ const finishEvent = this.createSyntheticFinishedEvent();
305
307
  const { approvals, clientToolResults } = this.collectClientState();
306
308
  const executionResult = await executeToolCalls(
307
309
  pendingToolCalls,
@@ -312,13 +314,13 @@ class TextEngine {
312
314
  if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
313
315
  for (const chunk of this.emitApprovalRequests(
314
316
  executionResult.needsApproval,
315
- doneChunk
317
+ finishEvent
316
318
  )) {
317
319
  yield chunk;
318
320
  }
319
321
  for (const chunk of this.emitClientToolInputs(
320
322
  executionResult.needsClientExecution,
321
- doneChunk
323
+ finishEvent
322
324
  )) {
323
325
  yield chunk;
324
326
  }
@@ -327,7 +329,7 @@ class TextEngine {
327
329
  }
328
330
  const toolResultChunks = this.emitToolResults(
329
331
  executionResult.results,
330
- doneChunk
332
+ finishEvent
331
333
  );
332
334
  for (const chunk of toolResultChunks) {
333
335
  yield chunk;
@@ -340,19 +342,11 @@ class TextEngine {
340
342
  return;
341
343
  }
342
344
  const toolCalls = this.toolCallManager.getToolCalls();
343
- const doneChunk = this.doneChunk;
344
- if (!doneChunk || toolCalls.length === 0) {
345
+ const finishEvent = this.finishedEvent;
346
+ if (!finishEvent || toolCalls.length === 0) {
345
347
  this.setToolPhase("stop");
346
348
  return;
347
349
  }
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
350
  this.addAssistantToolCallMessage(toolCalls);
357
351
  const { approvals, clientToolResults } = this.collectClientState();
358
352
  const executionResult = await executeToolCalls(
@@ -364,13 +358,13 @@ class TextEngine {
364
358
  if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
365
359
  for (const chunk of this.emitApprovalRequests(
366
360
  executionResult.needsApproval,
367
- doneChunk
361
+ finishEvent
368
362
  )) {
369
363
  yield chunk;
370
364
  }
371
365
  for (const chunk of this.emitClientToolInputs(
372
366
  executionResult.needsClientExecution,
373
- doneChunk
367
+ finishEvent
374
368
  )) {
375
369
  yield chunk;
376
370
  }
@@ -379,7 +373,7 @@ class TextEngine {
379
373
  }
380
374
  const toolResultChunks = this.emitToolResults(
381
375
  executionResult.results,
382
- doneChunk
376
+ finishEvent
383
377
  );
384
378
  for (const chunk of toolResultChunks) {
385
379
  yield chunk;
@@ -388,9 +382,10 @@ class TextEngine {
388
382
  this.setToolPhase("continue");
389
383
  }
390
384
  shouldExecuteToolPhase() {
391
- return this.doneChunk?.finishReason === "tool_calls" && this.tools.length > 0 && this.toolCallManager.hasToolCalls();
385
+ return this.finishedEvent?.finishReason === "tool_calls" && this.tools.length > 0 && this.toolCallManager.hasToolCalls();
392
386
  }
393
387
  addAssistantToolCallMessage(toolCalls) {
388
+ const messageId = this.currentMessageId ?? this.createId("msg");
394
389
  this.messages = [
395
390
  ...this.messages,
396
391
  {
@@ -399,6 +394,14 @@ class TextEngine {
399
394
  toolCalls
400
395
  }
401
396
  ];
397
+ aiEventClient.emit("text:message:created", {
398
+ ...this.buildTextEventContext(),
399
+ messageId,
400
+ role: "assistant",
401
+ content: this.accumulatedContent || "",
402
+ toolCalls,
403
+ timestamp: Date.now()
404
+ });
402
405
  }
403
406
  collectClientState() {
404
407
  const approvals = /* @__PURE__ */ new Map();
@@ -418,11 +421,11 @@ class TextEngine {
418
421
  }
419
422
  return { approvals, clientToolResults };
420
423
  }
421
- emitApprovalRequests(approvals, doneChunk) {
424
+ emitApprovalRequests(approvals, finishEvent) {
422
425
  const chunks = [];
423
426
  for (const approval of approvals) {
424
- aiEventClient.emit("stream:approval-requested", {
425
- streamId: this.streamId,
427
+ aiEventClient.emit("tools:approval:requested", {
428
+ ...this.buildTextEventContext(),
426
429
  messageId: this.currentMessageId || void 0,
427
430
  toolCallId: approval.toolCallId,
428
431
  toolName: approval.toolName,
@@ -431,26 +434,28 @@ class TextEngine {
431
434
  timestamp: Date.now()
432
435
  });
433
436
  chunks.push({
434
- type: "approval-requested",
435
- id: doneChunk.id,
436
- model: doneChunk.model,
437
+ type: "CUSTOM",
437
438
  timestamp: Date.now(),
438
- toolCallId: approval.toolCallId,
439
- toolName: approval.toolName,
440
- input: approval.input,
441
- approval: {
442
- id: approval.approvalId,
443
- needsApproval: true
439
+ model: finishEvent.model,
440
+ name: "approval-requested",
441
+ data: {
442
+ toolCallId: approval.toolCallId,
443
+ toolName: approval.toolName,
444
+ input: approval.input,
445
+ approval: {
446
+ id: approval.approvalId,
447
+ needsApproval: true
448
+ }
444
449
  }
445
450
  });
446
451
  }
447
452
  return chunks;
448
453
  }
449
- emitClientToolInputs(clientRequests, doneChunk) {
454
+ emitClientToolInputs(clientRequests, finishEvent) {
450
455
  const chunks = [];
451
456
  for (const clientTool of clientRequests) {
452
- aiEventClient.emit("stream:tool-input-available", {
453
- streamId: this.streamId,
457
+ aiEventClient.emit("tools:input:available", {
458
+ ...this.buildTextEventContext(),
454
459
  messageId: this.currentMessageId || void 0,
455
460
  toolCallId: clientTool.toolCallId,
456
461
  toolName: clientTool.toolName,
@@ -458,23 +463,24 @@ class TextEngine {
458
463
  timestamp: Date.now()
459
464
  });
460
465
  chunks.push({
461
- type: "tool-input-available",
462
- id: doneChunk.id,
463
- model: doneChunk.model,
466
+ type: "CUSTOM",
464
467
  timestamp: Date.now(),
465
- toolCallId: clientTool.toolCallId,
466
- toolName: clientTool.toolName,
467
- input: clientTool.input
468
+ model: finishEvent.model,
469
+ name: "tool-input-available",
470
+ data: {
471
+ toolCallId: clientTool.toolCallId,
472
+ toolName: clientTool.toolName,
473
+ input: clientTool.input
474
+ }
468
475
  });
469
476
  }
470
477
  return chunks;
471
478
  }
472
- emitToolResults(results, doneChunk) {
479
+ emitToolResults(results, finishEvent) {
473
480
  const chunks = [];
474
481
  for (const result of results) {
475
- aiEventClient.emit("tool:call-completed", {
476
- requestId: this.requestId,
477
- streamId: this.streamId,
482
+ aiEventClient.emit("tools:call:completed", {
483
+ ...this.buildTextEventContext(),
478
484
  messageId: this.currentMessageId || void 0,
479
485
  toolCallId: result.toolCallId,
480
486
  toolName: result.toolName,
@@ -483,15 +489,14 @@ class TextEngine {
483
489
  timestamp: Date.now()
484
490
  });
485
491
  const content = JSON.stringify(result.result);
486
- const chunk = {
487
- type: "tool_result",
488
- id: doneChunk.id,
489
- model: doneChunk.model,
492
+ chunks.push({
493
+ type: "TOOL_CALL_END",
490
494
  timestamp: Date.now(),
495
+ model: finishEvent.model,
491
496
  toolCallId: result.toolCallId,
492
- content
493
- };
494
- chunks.push(chunk);
497
+ toolName: result.toolName,
498
+ result: content
499
+ });
495
500
  this.messages = [
496
501
  ...this.messages,
497
502
  {
@@ -500,6 +505,13 @@ class TextEngine {
500
505
  toolCallId: result.toolCallId
501
506
  }
502
507
  ];
508
+ aiEventClient.emit("text:message:created", {
509
+ ...this.buildTextEventContext(),
510
+ messageId: this.createId("msg"),
511
+ role: "tool",
512
+ content,
513
+ timestamp: Date.now()
514
+ });
503
515
  }
504
516
  return chunks;
505
517
  }
@@ -520,10 +532,10 @@ class TextEngine {
520
532
  }
521
533
  return pending;
522
534
  }
523
- createSyntheticDoneChunk() {
535
+ createSyntheticFinishedEvent() {
524
536
  return {
525
- type: "done",
526
- id: this.createId("pending"),
537
+ type: "RUN_FINISHED",
538
+ runId: this.createId("pending"),
527
539
  model: this.params.model,
528
540
  timestamp: Date.now(),
529
541
  finishReason: "tool_calls"
@@ -542,6 +554,28 @@ class TextEngine {
542
554
  isAborted() {
543
555
  return !!this.effectiveSignal?.aborted;
544
556
  }
557
+ buildTextEventContext() {
558
+ return {
559
+ requestId: this.requestId,
560
+ streamId: this.streamId,
561
+ provider: this.adapter.name,
562
+ model: this.params.model,
563
+ clientId: this.params.conversationId,
564
+ source: "server",
565
+ systemPrompts: this.systemPrompts.length > 0 ? this.systemPrompts : void 0,
566
+ toolNames: this.eventToolNames,
567
+ options: this.eventOptions,
568
+ modelOptions: this.params.modelOptions,
569
+ messageCount: this.initialMessageCount,
570
+ hasTools: this.tools.length > 0,
571
+ streaming: true
572
+ };
573
+ }
574
+ getContentString(content) {
575
+ if (typeof content === "string") return content;
576
+ const text = content?.map((part) => part.type === "text" ? part.content : "").join("") || "";
577
+ return text;
578
+ }
545
579
  setToolPhase(phase) {
546
580
  this.toolPhase = phase;
547
581
  if (phase === "wait") {