@tanstack/ai 0.2.1 → 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.
- package/README.md +1 -0
- package/dist/esm/activities/chat/index.js +206 -172
- package/dist/esm/activities/chat/index.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 +90 -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 +267 -191
- package/src/activities/chat/stream/index.ts +1 -5
- package/src/activities/chat/stream/processor.ts +129 -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
|
@@ -23,14 +23,18 @@ import type { AnyTextAdapter } from './adapter'
|
|
|
23
23
|
import type {
|
|
24
24
|
AgentLoopStrategy,
|
|
25
25
|
ConstrainedModelMessage,
|
|
26
|
-
DoneStreamChunk,
|
|
27
26
|
InferSchemaType,
|
|
28
27
|
ModelMessage,
|
|
28
|
+
RunFinishedEvent,
|
|
29
29
|
SchemaInput,
|
|
30
30
|
StreamChunk,
|
|
31
|
+
TextMessageContentEvent,
|
|
31
32
|
TextOptions,
|
|
32
33
|
Tool,
|
|
33
34
|
ToolCall,
|
|
35
|
+
ToolCallArgsEvent,
|
|
36
|
+
ToolCallEndEvent,
|
|
37
|
+
ToolCallStartEvent,
|
|
34
38
|
} from '../../types'
|
|
35
39
|
|
|
36
40
|
// ===========================
|
|
@@ -213,7 +217,9 @@ class TextEngine<
|
|
|
213
217
|
private totalChunkCount = 0
|
|
214
218
|
private currentMessageId: string | null = null
|
|
215
219
|
private accumulatedContent = ''
|
|
216
|
-
private
|
|
220
|
+
private eventOptions?: Record<string, unknown>
|
|
221
|
+
private eventToolNames?: Array<string>
|
|
222
|
+
private finishedEvent: RunFinishedEvent | null = null
|
|
217
223
|
private shouldEmitStreamEnd = true
|
|
218
224
|
private earlyTermination = false
|
|
219
225
|
private toolPhase: ToolPhaseResult = 'continue'
|
|
@@ -278,16 +284,7 @@ class TextEngine<
|
|
|
278
284
|
|
|
279
285
|
private beforeRun(): void {
|
|
280
286
|
this.streamStartTime = Date.now()
|
|
281
|
-
const {
|
|
282
|
-
model,
|
|
283
|
-
tools,
|
|
284
|
-
temperature,
|
|
285
|
-
topP,
|
|
286
|
-
maxTokens,
|
|
287
|
-
metadata,
|
|
288
|
-
modelOptions,
|
|
289
|
-
conversationId,
|
|
290
|
-
} = this.params
|
|
287
|
+
const { tools, temperature, topP, maxTokens, metadata } = this.params
|
|
291
288
|
|
|
292
289
|
// Gather flattened options into an object for event emission
|
|
293
290
|
const options: Record<string, unknown> = {}
|
|
@@ -296,26 +293,49 @@ class TextEngine<
|
|
|
296
293
|
if (maxTokens !== undefined) options.maxTokens = maxTokens
|
|
297
294
|
if (metadata !== undefined) options.metadata = metadata
|
|
298
295
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
messageCount: this.initialMessageCount,
|
|
305
|
-
hasTools: !!tools && tools.length > 0,
|
|
306
|
-
streaming: true,
|
|
296
|
+
this.eventOptions = Object.keys(options).length > 0 ? options : undefined
|
|
297
|
+
this.eventToolNames = tools?.map((t) => t.name)
|
|
298
|
+
|
|
299
|
+
aiEventClient.emit('text:request:started', {
|
|
300
|
+
...this.buildTextEventContext(),
|
|
307
301
|
timestamp: Date.now(),
|
|
308
|
-
clientId: conversationId,
|
|
309
|
-
toolNames: tools?.map((t) => t.name),
|
|
310
|
-
options: Object.keys(options).length > 0 ? options : undefined,
|
|
311
|
-
modelOptions: modelOptions as Record<string, unknown> | undefined,
|
|
312
302
|
})
|
|
313
303
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
304
|
+
// Always emit messages for tracking:
|
|
305
|
+
// - For existing conversations (with conversationId): only emit the latest user message
|
|
306
|
+
// - For new conversations (without conversationId): emit all messages for reconstruction
|
|
307
|
+
const messagesToEmit = this.params.conversationId
|
|
308
|
+
? this.messages.slice(-1).filter((m) => m.role === 'user')
|
|
309
|
+
: this.messages
|
|
310
|
+
|
|
311
|
+
messagesToEmit.forEach((message, index) => {
|
|
312
|
+
const messageIndex = this.params.conversationId
|
|
313
|
+
? this.messages.length - 1
|
|
314
|
+
: index
|
|
315
|
+
const messageId = this.createId('msg')
|
|
316
|
+
const baseContext = this.buildTextEventContext()
|
|
317
|
+
const content = this.getContentString(message.content)
|
|
318
|
+
|
|
319
|
+
aiEventClient.emit('text:message:created', {
|
|
320
|
+
...baseContext,
|
|
321
|
+
messageId,
|
|
322
|
+
role: message.role,
|
|
323
|
+
content,
|
|
324
|
+
toolCalls: message.toolCalls,
|
|
325
|
+
messageIndex,
|
|
326
|
+
timestamp: Date.now(),
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
if (message.role === 'user') {
|
|
330
|
+
aiEventClient.emit('text:message:user', {
|
|
331
|
+
...baseContext,
|
|
332
|
+
messageId,
|
|
333
|
+
role: 'user',
|
|
334
|
+
content,
|
|
335
|
+
messageIndex,
|
|
336
|
+
timestamp: Date.now(),
|
|
337
|
+
})
|
|
338
|
+
}
|
|
319
339
|
})
|
|
320
340
|
}
|
|
321
341
|
|
|
@@ -325,23 +345,13 @@ class TextEngine<
|
|
|
325
345
|
}
|
|
326
346
|
|
|
327
347
|
const now = Date.now()
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
requestId: this.requestId,
|
|
332
|
-
streamId: this.streamId,
|
|
333
|
-
model: this.params.model,
|
|
348
|
+
// Emit text:request:completed with final state
|
|
349
|
+
aiEventClient.emit('text:request:completed', {
|
|
350
|
+
...this.buildTextEventContext(),
|
|
334
351
|
content: this.accumulatedContent,
|
|
335
352
|
messageId: this.currentMessageId || undefined,
|
|
336
353
|
finishReason: this.lastFinishReason || undefined,
|
|
337
|
-
usage: this.
|
|
338
|
-
timestamp: now,
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
aiEventClient.emit('stream:ended', {
|
|
342
|
-
requestId: this.requestId,
|
|
343
|
-
streamId: this.streamId,
|
|
344
|
-
totalChunks: this.totalChunkCount,
|
|
354
|
+
usage: this.finishedEvent?.usage,
|
|
345
355
|
duration: now - this.streamStartTime,
|
|
346
356
|
timestamp: now,
|
|
347
357
|
})
|
|
@@ -366,7 +376,16 @@ class TextEngine<
|
|
|
366
376
|
private beginIteration(): void {
|
|
367
377
|
this.currentMessageId = this.createId('msg')
|
|
368
378
|
this.accumulatedContent = ''
|
|
369
|
-
this.
|
|
379
|
+
this.finishedEvent = null
|
|
380
|
+
|
|
381
|
+
const baseContext = this.buildTextEventContext()
|
|
382
|
+
aiEventClient.emit('text:message:created', {
|
|
383
|
+
...baseContext,
|
|
384
|
+
messageId: this.currentMessageId,
|
|
385
|
+
role: 'assistant',
|
|
386
|
+
content: '',
|
|
387
|
+
timestamp: Date.now(),
|
|
388
|
+
})
|
|
370
389
|
}
|
|
371
390
|
|
|
372
391
|
private async *streamModelResponse(): AsyncGenerator<StreamChunk> {
|
|
@@ -413,101 +432,96 @@ class TextEngine<
|
|
|
413
432
|
|
|
414
433
|
private handleStreamChunk(chunk: StreamChunk): void {
|
|
415
434
|
switch (chunk.type) {
|
|
416
|
-
|
|
417
|
-
|
|
435
|
+
// AG-UI Events
|
|
436
|
+
case 'TEXT_MESSAGE_CONTENT':
|
|
437
|
+
this.handleTextMessageContentEvent(chunk)
|
|
438
|
+
break
|
|
439
|
+
case 'TOOL_CALL_START':
|
|
440
|
+
this.handleToolCallStartEvent(chunk)
|
|
418
441
|
break
|
|
419
|
-
case '
|
|
420
|
-
this.
|
|
442
|
+
case 'TOOL_CALL_ARGS':
|
|
443
|
+
this.handleToolCallArgsEvent(chunk)
|
|
421
444
|
break
|
|
422
|
-
case '
|
|
423
|
-
this.
|
|
445
|
+
case 'TOOL_CALL_END':
|
|
446
|
+
this.handleToolCallEndEvent(chunk)
|
|
424
447
|
break
|
|
425
|
-
case '
|
|
426
|
-
this.
|
|
448
|
+
case 'RUN_FINISHED':
|
|
449
|
+
this.handleRunFinishedEvent(chunk)
|
|
427
450
|
break
|
|
428
|
-
case '
|
|
429
|
-
this.
|
|
451
|
+
case 'RUN_ERROR':
|
|
452
|
+
this.handleRunErrorEvent(chunk)
|
|
430
453
|
break
|
|
431
|
-
case '
|
|
432
|
-
this.
|
|
454
|
+
case 'STEP_FINISHED':
|
|
455
|
+
this.handleStepFinishedEvent(chunk)
|
|
433
456
|
break
|
|
457
|
+
|
|
434
458
|
default:
|
|
459
|
+
// RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_END, STEP_STARTED,
|
|
460
|
+
// STATE_SNAPSHOT, STATE_DELTA, CUSTOM
|
|
461
|
+
// - no special handling needed in chat activity
|
|
435
462
|
break
|
|
436
463
|
}
|
|
437
464
|
}
|
|
438
465
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
466
|
+
// ===========================
|
|
467
|
+
// AG-UI Event Handlers
|
|
468
|
+
// ===========================
|
|
469
|
+
|
|
470
|
+
private handleTextMessageContentEvent(chunk: TextMessageContentEvent): void {
|
|
471
|
+
if (chunk.content) {
|
|
472
|
+
this.accumulatedContent = chunk.content
|
|
473
|
+
} else {
|
|
474
|
+
this.accumulatedContent += chunk.delta
|
|
475
|
+
}
|
|
476
|
+
aiEventClient.emit('text:chunk:content', {
|
|
477
|
+
...this.buildTextEventContext(),
|
|
443
478
|
messageId: this.currentMessageId || undefined,
|
|
444
|
-
content:
|
|
479
|
+
content: this.accumulatedContent,
|
|
445
480
|
delta: chunk.delta,
|
|
446
481
|
timestamp: Date.now(),
|
|
447
482
|
})
|
|
448
483
|
}
|
|
449
484
|
|
|
450
|
-
private
|
|
451
|
-
chunk
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
aiEventClient.emit('stream:chunk:tool-call', {
|
|
455
|
-
streamId: this.streamId,
|
|
485
|
+
private handleToolCallStartEvent(chunk: ToolCallStartEvent): void {
|
|
486
|
+
this.toolCallManager.addToolCallStartEvent(chunk)
|
|
487
|
+
aiEventClient.emit('text:chunk:tool-call', {
|
|
488
|
+
...this.buildTextEventContext(),
|
|
456
489
|
messageId: this.currentMessageId || undefined,
|
|
457
|
-
toolCallId: chunk.
|
|
458
|
-
toolName: chunk.
|
|
459
|
-
index: chunk.index,
|
|
460
|
-
arguments:
|
|
490
|
+
toolCallId: chunk.toolCallId,
|
|
491
|
+
toolName: chunk.toolName,
|
|
492
|
+
index: chunk.index ?? 0,
|
|
493
|
+
arguments: '',
|
|
461
494
|
timestamp: Date.now(),
|
|
462
495
|
})
|
|
463
496
|
}
|
|
464
497
|
|
|
465
|
-
private
|
|
466
|
-
chunk
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
streamId: this.streamId,
|
|
498
|
+
private handleToolCallArgsEvent(chunk: ToolCallArgsEvent): void {
|
|
499
|
+
this.toolCallManager.addToolCallArgsEvent(chunk)
|
|
500
|
+
aiEventClient.emit('text:chunk:tool-call', {
|
|
501
|
+
...this.buildTextEventContext(),
|
|
470
502
|
messageId: this.currentMessageId || undefined,
|
|
471
503
|
toolCallId: chunk.toolCallId,
|
|
472
|
-
|
|
504
|
+
toolName: '',
|
|
505
|
+
index: 0,
|
|
506
|
+
arguments: chunk.delta,
|
|
473
507
|
timestamp: Date.now(),
|
|
474
508
|
})
|
|
475
509
|
}
|
|
476
510
|
|
|
477
|
-
private
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
this.
|
|
482
|
-
chunk.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
streamId: this.streamId,
|
|
488
|
-
messageId: this.currentMessageId || undefined,
|
|
489
|
-
finishReason: chunk.finishReason,
|
|
490
|
-
usage: chunk.usage,
|
|
491
|
-
timestamp: Date.now(),
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
if (chunk.usage) {
|
|
495
|
-
aiEventClient.emit('usage:tokens', {
|
|
496
|
-
requestId: this.requestId,
|
|
497
|
-
streamId: this.streamId,
|
|
498
|
-
messageId: this.currentMessageId || undefined,
|
|
499
|
-
model: this.params.model,
|
|
500
|
-
usage: chunk.usage,
|
|
501
|
-
timestamp: Date.now(),
|
|
502
|
-
})
|
|
503
|
-
}
|
|
504
|
-
return
|
|
505
|
-
}
|
|
511
|
+
private handleToolCallEndEvent(chunk: ToolCallEndEvent): void {
|
|
512
|
+
this.toolCallManager.completeToolCall(chunk)
|
|
513
|
+
aiEventClient.emit('text:chunk:tool-result', {
|
|
514
|
+
...this.buildTextEventContext(),
|
|
515
|
+
messageId: this.currentMessageId || undefined,
|
|
516
|
+
toolCallId: chunk.toolCallId,
|
|
517
|
+
result: chunk.result || '',
|
|
518
|
+
timestamp: Date.now(),
|
|
519
|
+
})
|
|
520
|
+
}
|
|
506
521
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
streamId: this.streamId,
|
|
522
|
+
private handleRunFinishedEvent(chunk: RunFinishedEvent): void {
|
|
523
|
+
aiEventClient.emit('text:chunk:done', {
|
|
524
|
+
...this.buildTextEventContext(),
|
|
511
525
|
messageId: this.currentMessageId || undefined,
|
|
512
526
|
finishReason: chunk.finishReason,
|
|
513
527
|
usage: chunk.usage,
|
|
@@ -515,22 +529,32 @@ class TextEngine<
|
|
|
515
529
|
})
|
|
516
530
|
|
|
517
531
|
if (chunk.usage) {
|
|
518
|
-
aiEventClient.emit('usage
|
|
519
|
-
|
|
520
|
-
streamId: this.streamId,
|
|
532
|
+
aiEventClient.emit('text:usage', {
|
|
533
|
+
...this.buildTextEventContext(),
|
|
521
534
|
messageId: this.currentMessageId || undefined,
|
|
522
|
-
model: this.params.model,
|
|
523
535
|
usage: chunk.usage,
|
|
524
536
|
timestamp: Date.now(),
|
|
525
537
|
})
|
|
526
538
|
}
|
|
539
|
+
|
|
540
|
+
// Don't overwrite a tool_calls finishReason with a stop finishReason
|
|
541
|
+
if (
|
|
542
|
+
this.finishedEvent?.finishReason === 'tool_calls' &&
|
|
543
|
+
chunk.finishReason === 'stop'
|
|
544
|
+
) {
|
|
545
|
+
this.lastFinishReason = chunk.finishReason
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
this.finishedEvent = chunk
|
|
550
|
+
this.lastFinishReason = chunk.finishReason
|
|
527
551
|
}
|
|
528
552
|
|
|
529
|
-
private
|
|
530
|
-
chunk: Extract<StreamChunk, { type: '
|
|
553
|
+
private handleRunErrorEvent(
|
|
554
|
+
chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,
|
|
531
555
|
): void {
|
|
532
|
-
aiEventClient.emit('
|
|
533
|
-
|
|
556
|
+
aiEventClient.emit('text:chunk:error', {
|
|
557
|
+
...this.buildTextEventContext(),
|
|
534
558
|
messageId: this.currentMessageId || undefined,
|
|
535
559
|
error: chunk.error.message,
|
|
536
560
|
timestamp: Date.now(),
|
|
@@ -539,16 +563,19 @@ class TextEngine<
|
|
|
539
563
|
this.shouldEmitStreamEnd = false
|
|
540
564
|
}
|
|
541
565
|
|
|
542
|
-
private
|
|
543
|
-
chunk: Extract<StreamChunk, { type: '
|
|
566
|
+
private handleStepFinishedEvent(
|
|
567
|
+
chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,
|
|
544
568
|
): void {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
569
|
+
// Handle thinking/reasoning content from STEP_FINISHED events
|
|
570
|
+
if (chunk.content || chunk.delta) {
|
|
571
|
+
aiEventClient.emit('text:chunk:thinking', {
|
|
572
|
+
...this.buildTextEventContext(),
|
|
573
|
+
messageId: this.currentMessageId || undefined,
|
|
574
|
+
content: chunk.content || '',
|
|
575
|
+
delta: chunk.delta,
|
|
576
|
+
timestamp: Date.now(),
|
|
577
|
+
})
|
|
578
|
+
}
|
|
552
579
|
}
|
|
553
580
|
|
|
554
581
|
private async *checkForPendingToolCalls(): AsyncGenerator<
|
|
@@ -561,16 +588,7 @@ class TextEngine<
|
|
|
561
588
|
return 'continue'
|
|
562
589
|
}
|
|
563
590
|
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
aiEventClient.emit('text:iteration', {
|
|
567
|
-
requestId: this.requestId,
|
|
568
|
-
streamId: this.streamId,
|
|
569
|
-
iterationNumber: this.iterationCount + 1,
|
|
570
|
-
messageCount: this.messages.length,
|
|
571
|
-
toolCallCount: pendingToolCalls.length,
|
|
572
|
-
timestamp: Date.now(),
|
|
573
|
-
})
|
|
591
|
+
const finishEvent = this.createSyntheticFinishedEvent()
|
|
574
592
|
|
|
575
593
|
const { approvals, clientToolResults } = this.collectClientState()
|
|
576
594
|
|
|
@@ -587,14 +605,14 @@ class TextEngine<
|
|
|
587
605
|
) {
|
|
588
606
|
for (const chunk of this.emitApprovalRequests(
|
|
589
607
|
executionResult.needsApproval,
|
|
590
|
-
|
|
608
|
+
finishEvent,
|
|
591
609
|
)) {
|
|
592
610
|
yield chunk
|
|
593
611
|
}
|
|
594
612
|
|
|
595
613
|
for (const chunk of this.emitClientToolInputs(
|
|
596
614
|
executionResult.needsClientExecution,
|
|
597
|
-
|
|
615
|
+
finishEvent,
|
|
598
616
|
)) {
|
|
599
617
|
yield chunk
|
|
600
618
|
}
|
|
@@ -605,7 +623,7 @@ class TextEngine<
|
|
|
605
623
|
|
|
606
624
|
const toolResultChunks = this.emitToolResults(
|
|
607
625
|
executionResult.results,
|
|
608
|
-
|
|
626
|
+
finishEvent,
|
|
609
627
|
)
|
|
610
628
|
|
|
611
629
|
for (const chunk of toolResultChunks) {
|
|
@@ -622,22 +640,13 @@ class TextEngine<
|
|
|
622
640
|
}
|
|
623
641
|
|
|
624
642
|
const toolCalls = this.toolCallManager.getToolCalls()
|
|
625
|
-
const
|
|
643
|
+
const finishEvent = this.finishedEvent
|
|
626
644
|
|
|
627
|
-
if (!
|
|
645
|
+
if (!finishEvent || toolCalls.length === 0) {
|
|
628
646
|
this.setToolPhase('stop')
|
|
629
647
|
return
|
|
630
648
|
}
|
|
631
649
|
|
|
632
|
-
aiEventClient.emit('text:iteration', {
|
|
633
|
-
requestId: this.requestId,
|
|
634
|
-
streamId: this.streamId,
|
|
635
|
-
iterationNumber: this.iterationCount + 1,
|
|
636
|
-
messageCount: this.messages.length,
|
|
637
|
-
toolCallCount: toolCalls.length,
|
|
638
|
-
timestamp: Date.now(),
|
|
639
|
-
})
|
|
640
|
-
|
|
641
650
|
this.addAssistantToolCallMessage(toolCalls)
|
|
642
651
|
|
|
643
652
|
const { approvals, clientToolResults } = this.collectClientState()
|
|
@@ -655,14 +664,14 @@ class TextEngine<
|
|
|
655
664
|
) {
|
|
656
665
|
for (const chunk of this.emitApprovalRequests(
|
|
657
666
|
executionResult.needsApproval,
|
|
658
|
-
|
|
667
|
+
finishEvent,
|
|
659
668
|
)) {
|
|
660
669
|
yield chunk
|
|
661
670
|
}
|
|
662
671
|
|
|
663
672
|
for (const chunk of this.emitClientToolInputs(
|
|
664
673
|
executionResult.needsClientExecution,
|
|
665
|
-
|
|
674
|
+
finishEvent,
|
|
666
675
|
)) {
|
|
667
676
|
yield chunk
|
|
668
677
|
}
|
|
@@ -673,7 +682,7 @@ class TextEngine<
|
|
|
673
682
|
|
|
674
683
|
const toolResultChunks = this.emitToolResults(
|
|
675
684
|
executionResult.results,
|
|
676
|
-
|
|
685
|
+
finishEvent,
|
|
677
686
|
)
|
|
678
687
|
|
|
679
688
|
for (const chunk of toolResultChunks) {
|
|
@@ -687,13 +696,14 @@ class TextEngine<
|
|
|
687
696
|
|
|
688
697
|
private shouldExecuteToolPhase(): boolean {
|
|
689
698
|
return (
|
|
690
|
-
this.
|
|
699
|
+
this.finishedEvent?.finishReason === 'tool_calls' &&
|
|
691
700
|
this.tools.length > 0 &&
|
|
692
701
|
this.toolCallManager.hasToolCalls()
|
|
693
702
|
)
|
|
694
703
|
}
|
|
695
704
|
|
|
696
705
|
private addAssistantToolCallMessage(toolCalls: Array<ToolCall>): void {
|
|
706
|
+
const messageId = this.currentMessageId ?? this.createId('msg')
|
|
697
707
|
this.messages = [
|
|
698
708
|
...this.messages,
|
|
699
709
|
{
|
|
@@ -702,6 +712,15 @@ class TextEngine<
|
|
|
702
712
|
toolCalls,
|
|
703
713
|
},
|
|
704
714
|
]
|
|
715
|
+
|
|
716
|
+
aiEventClient.emit('text:message:created', {
|
|
717
|
+
...this.buildTextEventContext(),
|
|
718
|
+
messageId,
|
|
719
|
+
role: 'assistant',
|
|
720
|
+
content: this.accumulatedContent || '',
|
|
721
|
+
toolCalls,
|
|
722
|
+
timestamp: Date.now(),
|
|
723
|
+
})
|
|
705
724
|
}
|
|
706
725
|
|
|
707
726
|
private collectClientState(): {
|
|
@@ -740,13 +759,13 @@ class TextEngine<
|
|
|
740
759
|
|
|
741
760
|
private emitApprovalRequests(
|
|
742
761
|
approvals: Array<ApprovalRequest>,
|
|
743
|
-
|
|
762
|
+
finishEvent: RunFinishedEvent,
|
|
744
763
|
): Array<StreamChunk> {
|
|
745
764
|
const chunks: Array<StreamChunk> = []
|
|
746
765
|
|
|
747
766
|
for (const approval of approvals) {
|
|
748
|
-
aiEventClient.emit('
|
|
749
|
-
|
|
767
|
+
aiEventClient.emit('tools:approval:requested', {
|
|
768
|
+
...this.buildTextEventContext(),
|
|
750
769
|
messageId: this.currentMessageId || undefined,
|
|
751
770
|
toolCallId: approval.toolCallId,
|
|
752
771
|
toolName: approval.toolName,
|
|
@@ -755,17 +774,20 @@ class TextEngine<
|
|
|
755
774
|
timestamp: Date.now(),
|
|
756
775
|
})
|
|
757
776
|
|
|
777
|
+
// Emit a CUSTOM event for approval requests
|
|
758
778
|
chunks.push({
|
|
759
|
-
type: '
|
|
760
|
-
id: doneChunk.id,
|
|
761
|
-
model: doneChunk.model,
|
|
779
|
+
type: 'CUSTOM',
|
|
762
780
|
timestamp: Date.now(),
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
781
|
+
model: finishEvent.model,
|
|
782
|
+
name: 'approval-requested',
|
|
783
|
+
data: {
|
|
784
|
+
toolCallId: approval.toolCallId,
|
|
785
|
+
toolName: approval.toolName,
|
|
786
|
+
input: approval.input,
|
|
787
|
+
approval: {
|
|
788
|
+
id: approval.approvalId,
|
|
789
|
+
needsApproval: true,
|
|
790
|
+
},
|
|
769
791
|
},
|
|
770
792
|
})
|
|
771
793
|
}
|
|
@@ -775,13 +797,13 @@ class TextEngine<
|
|
|
775
797
|
|
|
776
798
|
private emitClientToolInputs(
|
|
777
799
|
clientRequests: Array<ClientToolRequest>,
|
|
778
|
-
|
|
800
|
+
finishEvent: RunFinishedEvent,
|
|
779
801
|
): Array<StreamChunk> {
|
|
780
802
|
const chunks: Array<StreamChunk> = []
|
|
781
803
|
|
|
782
804
|
for (const clientTool of clientRequests) {
|
|
783
|
-
aiEventClient.emit('
|
|
784
|
-
|
|
805
|
+
aiEventClient.emit('tools:input:available', {
|
|
806
|
+
...this.buildTextEventContext(),
|
|
785
807
|
messageId: this.currentMessageId || undefined,
|
|
786
808
|
toolCallId: clientTool.toolCallId,
|
|
787
809
|
toolName: clientTool.toolName,
|
|
@@ -789,14 +811,17 @@ class TextEngine<
|
|
|
789
811
|
timestamp: Date.now(),
|
|
790
812
|
})
|
|
791
813
|
|
|
814
|
+
// Emit a CUSTOM event for client tool inputs
|
|
792
815
|
chunks.push({
|
|
793
|
-
type: '
|
|
794
|
-
id: doneChunk.id,
|
|
795
|
-
model: doneChunk.model,
|
|
816
|
+
type: 'CUSTOM',
|
|
796
817
|
timestamp: Date.now(),
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
818
|
+
model: finishEvent.model,
|
|
819
|
+
name: 'tool-input-available',
|
|
820
|
+
data: {
|
|
821
|
+
toolCallId: clientTool.toolCallId,
|
|
822
|
+
toolName: clientTool.toolName,
|
|
823
|
+
input: clientTool.input,
|
|
824
|
+
},
|
|
800
825
|
})
|
|
801
826
|
}
|
|
802
827
|
|
|
@@ -805,14 +830,13 @@ class TextEngine<
|
|
|
805
830
|
|
|
806
831
|
private emitToolResults(
|
|
807
832
|
results: Array<ToolResult>,
|
|
808
|
-
|
|
833
|
+
finishEvent: RunFinishedEvent,
|
|
809
834
|
): Array<StreamChunk> {
|
|
810
835
|
const chunks: Array<StreamChunk> = []
|
|
811
836
|
|
|
812
837
|
for (const result of results) {
|
|
813
|
-
aiEventClient.emit('
|
|
814
|
-
|
|
815
|
-
streamId: this.streamId,
|
|
838
|
+
aiEventClient.emit('tools:call:completed', {
|
|
839
|
+
...this.buildTextEventContext(),
|
|
816
840
|
messageId: this.currentMessageId || undefined,
|
|
817
841
|
toolCallId: result.toolCallId,
|
|
818
842
|
toolName: result.toolName,
|
|
@@ -822,16 +846,16 @@ class TextEngine<
|
|
|
822
846
|
})
|
|
823
847
|
|
|
824
848
|
const content = JSON.stringify(result.result)
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
849
|
+
|
|
850
|
+
// Emit TOOL_CALL_END event
|
|
851
|
+
chunks.push({
|
|
852
|
+
type: 'TOOL_CALL_END',
|
|
829
853
|
timestamp: Date.now(),
|
|
854
|
+
model: finishEvent.model,
|
|
830
855
|
toolCallId: result.toolCallId,
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
chunks.push(chunk)
|
|
856
|
+
toolName: result.toolName,
|
|
857
|
+
result: content,
|
|
858
|
+
})
|
|
835
859
|
|
|
836
860
|
this.messages = [
|
|
837
861
|
...this.messages,
|
|
@@ -841,6 +865,14 @@ class TextEngine<
|
|
|
841
865
|
toolCallId: result.toolCallId,
|
|
842
866
|
},
|
|
843
867
|
]
|
|
868
|
+
|
|
869
|
+
aiEventClient.emit('text:message:created', {
|
|
870
|
+
...this.buildTextEventContext(),
|
|
871
|
+
messageId: this.createId('msg'),
|
|
872
|
+
role: 'tool',
|
|
873
|
+
content,
|
|
874
|
+
timestamp: Date.now(),
|
|
875
|
+
})
|
|
844
876
|
}
|
|
845
877
|
|
|
846
878
|
return chunks
|
|
@@ -868,10 +900,10 @@ class TextEngine<
|
|
|
868
900
|
return pending
|
|
869
901
|
}
|
|
870
902
|
|
|
871
|
-
private
|
|
903
|
+
private createSyntheticFinishedEvent(): RunFinishedEvent {
|
|
872
904
|
return {
|
|
873
|
-
type: '
|
|
874
|
-
|
|
905
|
+
type: 'RUN_FINISHED',
|
|
906
|
+
runId: this.createId('pending'),
|
|
875
907
|
model: this.params.model,
|
|
876
908
|
timestamp: Date.now(),
|
|
877
909
|
finishReason: 'tool_calls',
|
|
@@ -896,6 +928,50 @@ class TextEngine<
|
|
|
896
928
|
return !!this.effectiveSignal?.aborted
|
|
897
929
|
}
|
|
898
930
|
|
|
931
|
+
private buildTextEventContext(): {
|
|
932
|
+
requestId: string
|
|
933
|
+
streamId: string
|
|
934
|
+
provider: string
|
|
935
|
+
model: string
|
|
936
|
+
clientId?: string
|
|
937
|
+
source?: 'client' | 'server'
|
|
938
|
+
systemPrompts?: Array<string>
|
|
939
|
+
toolNames?: Array<string>
|
|
940
|
+
options?: Record<string, unknown>
|
|
941
|
+
modelOptions?: Record<string, unknown>
|
|
942
|
+
messageCount: number
|
|
943
|
+
hasTools: boolean
|
|
944
|
+
streaming: boolean
|
|
945
|
+
} {
|
|
946
|
+
return {
|
|
947
|
+
requestId: this.requestId,
|
|
948
|
+
streamId: this.streamId,
|
|
949
|
+
provider: this.adapter.name,
|
|
950
|
+
model: this.params.model,
|
|
951
|
+
clientId: this.params.conversationId,
|
|
952
|
+
source: 'server',
|
|
953
|
+
systemPrompts:
|
|
954
|
+
this.systemPrompts.length > 0 ? this.systemPrompts : undefined,
|
|
955
|
+
toolNames: this.eventToolNames,
|
|
956
|
+
options: this.eventOptions,
|
|
957
|
+
modelOptions: this.params.modelOptions as
|
|
958
|
+
| Record<string, unknown>
|
|
959
|
+
| undefined,
|
|
960
|
+
messageCount: this.initialMessageCount,
|
|
961
|
+
hasTools: this.tools.length > 0,
|
|
962
|
+
streaming: true,
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
private getContentString(content: ModelMessage['content']): string {
|
|
967
|
+
if (typeof content === 'string') return content
|
|
968
|
+
const text =
|
|
969
|
+
content
|
|
970
|
+
?.map((part) => (part.type === 'text' ? part.content : ''))
|
|
971
|
+
.join('') || ''
|
|
972
|
+
return text
|
|
973
|
+
}
|
|
974
|
+
|
|
899
975
|
private setToolPhase(phase: ToolPhaseResult): void {
|
|
900
976
|
this.toolPhase = phase
|
|
901
977
|
if (phase === 'wait') {
|