@runtypelabs/persona 4.21.1 → 4.22.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/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-B_JdVK9o.d.cts → types-9Vxm-PPz.d.cts} +136 -6
- package/dist/animations/{types-B_JdVK9o.d.ts → types-9Vxm-PPz.d.ts} +136 -6
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +136 -6
- package/dist/animations-extra.d.ts +136 -6
- package/dist/{approval-ui-RNNBR5R3.js → approval-ui-K7R5Q5OC.js} +1 -1
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +221 -16
- package/dist/approval-ui.d.ts +221 -16
- package/dist/approval-ui.js +2 -2
- package/dist/artifacts-ui-F4N6VMKF.js +7 -0
- package/dist/artifacts-ui.cjs +1 -1
- package/dist/artifacts-ui.d.cts +221 -16
- package/dist/artifacts-ui.d.ts +221 -16
- package/dist/artifacts-ui.js +1 -1
- package/dist/chunk-NQLDCJAP.js +4 -0
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +221 -16
- package/dist/context-mentions.d.ts +221 -16
- package/dist/event-stream-view-GL3ADRY2.js +1 -0
- package/dist/event-stream-view.cjs +1 -1
- package/dist/event-stream-view.d.cts +221 -16
- package/dist/event-stream-view.d.ts +221 -16
- package/dist/event-stream-view.js +1 -1
- package/dist/forms-ui.d.cts +234 -25
- package/dist/forms-ui.d.ts +234 -25
- package/dist/history-view.d.cts +136 -6
- package/dist/history-view.d.ts +136 -6
- package/dist/index.cjs +70 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +302 -32
- package/dist/index.d.ts +302 -32
- package/dist/index.global.js +54 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +69 -69
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +221 -16
- package/dist/session-reconnect.d.ts +221 -16
- package/dist/smart-dom-reader.d.cts +221 -16
- package/dist/smart-dom-reader.d.ts +221 -16
- package/dist/testing.cjs +3 -3
- package/dist/testing.d.cts +3 -4
- package/dist/testing.d.ts +3 -4
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +73 -73
- package/dist/theme-editor-preview.d.cts +221 -16
- package/dist/theme-editor-preview.d.ts +221 -16
- package/dist/theme-editor-preview.js +51 -51
- package/dist/theme-editor.cjs +4 -4
- package/dist/theme-editor.d.cts +221 -16
- package/dist/theme-editor.d.ts +221 -16
- package/dist/theme-editor.js +2 -2
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +65 -1
- package/dist/theme-reference.d.ts +65 -1
- package/dist/theme-reference.js +1 -1
- package/dist/voice-runtime.d.cts +6 -2
- package/dist/voice-runtime.d.ts +6 -2
- package/dist/webmcp-polyfill.js +1 -4
- package/dist/webmcp-runtime-JI7T4QYF.js +6 -0
- package/dist/webmcp-runtime.cjs +2 -2
- package/dist/webmcp-runtime.d.cts +34 -2
- package/dist/webmcp-runtime.d.ts +34 -2
- package/dist/webmcp-runtime.js +2 -2
- package/package.json +2 -2
- package/src/ask-user-question-tool.ts +1 -1
- package/src/client-history-rest.test.ts +2 -1
- package/src/client-visitor-history.test.ts +20 -4
- package/src/client.composer-options.test.ts +1 -1
- package/src/client.interrupt.test.ts +2 -2
- package/src/client.test.ts +477 -915
- package/src/client.ts +134 -494
- package/src/client.unified-tools.test.ts +76 -0
- package/src/components/artifact-preview.ts +5 -0
- package/src/components/event-stream-badge-theming.test.ts +162 -0
- package/src/components/event-stream-view.test.ts +76 -76
- package/src/components/event-stream-view.ts +55 -20
- package/src/components/tool-bubble.test.ts +12 -0
- package/src/components/tool-bubble.ts +13 -2
- package/src/event-stream-view-bundle.test.ts +2 -2
- package/src/generated/runtype-openapi-contract.ts +137 -1
- package/src/index-core.ts +2 -0
- package/src/internal/runtype-history-provider.test.ts +2 -2
- package/src/runtime/init.registry.test.ts +56 -0
- package/src/runtime/init.ts +17 -0
- package/src/session-history.test.ts +1 -1
- package/src/session.quote.test.ts +1 -1
- package/src/session.resubmit-from.test.ts +1 -1
- package/src/session.test.ts +5 -5
- package/src/session.ts +95 -36
- package/src/suggest-replies-tool.ts +1 -1
- package/src/testing/mock-stream.test.ts +37 -16
- package/src/testing/mock-stream.ts +14 -9
- package/src/types/theme.ts +41 -1
- package/src/types.ts +57 -15
- package/src/ui.ask-user-question-plugin.test.ts +7 -7
- package/src/ui.composer-defer-one.test.ts +6 -34
- package/src/ui.composer-draft-persistence.test.ts +1 -1
- package/src/ui.composer-quote.test.ts +1 -1
- package/src/ui.message-history-actions.test.ts +1 -1
- package/src/ui.reasoning-display.test.ts +136 -2
- package/src/ui.streaming-coalescing.test.ts +10 -13
- package/src/ui.ts +101 -35
- package/src/ui.typing-indicator.test.ts +216 -0
- package/src/ui.voice-custom-provider.test.ts +211 -0
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1740 -0
- package/src/utils/event-stream-buffer.test.ts +13 -13
- package/src/utils/event-stream-capture.test.ts +20 -20
- package/src/utils/formatting.ts +22 -0
- package/src/utils/theme.ts +16 -0
- package/src/utils/throughput-tracker.test.ts +100 -142
- package/src/utils/throughput-tracker.ts +18 -132
- package/src/utils/tokens.ts +59 -0
- package/src/utils/voice-support.ts +18 -1
- package/src/voice/voice-turn-tracker.ts +18 -0
- package/src/webmcp-bridge.test.ts +132 -23
- package/src/webmcp-bridge.ts +27 -3
- package/src/webmcp-runtime-entry.ts +71 -11
- package/dist/artifacts-ui-J6AKUGTS.js +0 -7
- package/dist/chunk-HBE7LFNT.js +0 -4
- package/dist/event-stream-view-73NWKWVQ.js +0 -1
- package/dist/webmcp-runtime-P3CHZRHF.js +0 -6
- package/src/utils/__fixtures__/unified-translator.oracle.ts +0 -717
package/src/client.test.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { AgentWidgetClient, preferFinalStructuredContent } from './client';
|
|
|
3
3
|
import { AgentWidgetEvent, AgentWidgetMessage, AgentWidgetArtifactsFeature } from './types';
|
|
4
4
|
import { createJsonStreamParser } from './utils/formatting';
|
|
5
5
|
import { VERSION } from './version';
|
|
6
|
-
import { createUnifiedEventWrite } from './utils/__fixtures__/unified-translator.oracle';
|
|
7
6
|
|
|
8
7
|
describe('AgentWidgetClient - dispatch abort signals', () => {
|
|
9
8
|
const message = {
|
|
@@ -106,7 +105,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
|
|
|
106
105
|
const encoder = new TextEncoder();
|
|
107
106
|
const stream = new ReadableStream({
|
|
108
107
|
start(controller) {
|
|
109
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
108
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
110
109
|
controller.close();
|
|
111
110
|
}
|
|
112
111
|
});
|
|
@@ -159,7 +158,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
|
|
|
159
158
|
const encoder = new TextEncoder();
|
|
160
159
|
const stream = new ReadableStream({
|
|
161
160
|
start(controller) {
|
|
162
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
161
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
163
162
|
controller.close();
|
|
164
163
|
}
|
|
165
164
|
});
|
|
@@ -209,7 +208,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
|
|
|
209
208
|
const encoder = new TextEncoder();
|
|
210
209
|
const stream = new ReadableStream({
|
|
211
210
|
start(controller) {
|
|
212
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
211
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
213
212
|
controller.close();
|
|
214
213
|
}
|
|
215
214
|
});
|
|
@@ -248,7 +247,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
|
|
|
248
247
|
const encoder = new TextEncoder();
|
|
249
248
|
const stream = new ReadableStream({
|
|
250
249
|
start(controller) {
|
|
251
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
250
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
252
251
|
controller.close();
|
|
253
252
|
}
|
|
254
253
|
});
|
|
@@ -292,7 +291,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
|
|
|
292
291
|
const encoder = new TextEncoder();
|
|
293
292
|
const stream = new ReadableStream({
|
|
294
293
|
start(controller) {
|
|
295
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
294
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
296
295
|
controller.close();
|
|
297
296
|
}
|
|
298
297
|
});
|
|
@@ -323,7 +322,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
|
|
|
323
322
|
const encoder = new TextEncoder();
|
|
324
323
|
const stream = new ReadableStream({
|
|
325
324
|
start(controller) {
|
|
326
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
325
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
327
326
|
controller.close();
|
|
328
327
|
}
|
|
329
328
|
});
|
|
@@ -352,7 +351,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
|
|
|
352
351
|
const encoder = new TextEncoder();
|
|
353
352
|
const stream = new ReadableStream({
|
|
354
353
|
start(controller) {
|
|
355
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
354
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
356
355
|
controller.close();
|
|
357
356
|
}
|
|
358
357
|
});
|
|
@@ -384,7 +383,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
|
|
|
384
383
|
const encoder = new TextEncoder();
|
|
385
384
|
const stream = new ReadableStream({
|
|
386
385
|
start(controller) {
|
|
387
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
386
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
388
387
|
controller.close();
|
|
389
388
|
}
|
|
390
389
|
});
|
|
@@ -413,7 +412,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
|
|
|
413
412
|
const encoder = new TextEncoder();
|
|
414
413
|
const stream = new ReadableStream({
|
|
415
414
|
start(controller) {
|
|
416
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
415
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
417
416
|
controller.close();
|
|
418
417
|
}
|
|
419
418
|
});
|
|
@@ -474,91 +473,48 @@ describe('AgentWidgetClient - JSON Streaming', () => {
|
|
|
474
473
|
});
|
|
475
474
|
|
|
476
475
|
it('should stream text incrementally and not show raw JSON at the end', async () => {
|
|
477
|
-
//
|
|
478
|
-
const sseEvents = [
|
|
479
|
-
'data: {"type":"flow_start","flowId":"flow_01k9pfnztzfag9tfz4t65c9c5q","flowName":"Shopping Assistant","totalSteps":1,"startedAt":"2025-11-12T23:47:39.565Z","executionId":"exec_standalone_1762991259266_7wz736k7n","executionContext":{"source":"standalone","record":{"id":"-1","name":"Streaming Chat Widget","created":false},"flow":{"id":"flow_01k9pfnztzfag9tfz4t65c9c5q","name":"Shopping Assistant","created":false}}}',
|
|
480
|
-
'',
|
|
481
|
-
'data: {"type":"step_start","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","stepType":"prompt","index":1,"totalSteps":1,"startedAt":"2025-11-12T23:47:39.565Z"}',
|
|
482
|
-
'',
|
|
483
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"{\\n"}',
|
|
484
|
-
'',
|
|
485
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" "}',
|
|
486
|
-
'',
|
|
487
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
|
|
488
|
-
'',
|
|
489
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"action"}',
|
|
490
|
-
'',
|
|
491
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"\\":"}',
|
|
492
|
-
'',
|
|
493
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
|
|
494
|
-
'',
|
|
495
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"message"}',
|
|
496
|
-
'',
|
|
497
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"\\",\\n"}',
|
|
498
|
-
'',
|
|
499
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" "}',
|
|
500
|
-
'',
|
|
501
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
|
|
502
|
-
'',
|
|
503
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"text"}',
|
|
504
|
-
'',
|
|
505
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"\\":"}',
|
|
506
|
-
'',
|
|
507
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
|
|
508
|
-
'',
|
|
509
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"Great"}',
|
|
510
|
-
'',
|
|
511
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"!"}',
|
|
512
|
-
'',
|
|
513
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" If"}',
|
|
514
|
-
'',
|
|
515
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" you"}',
|
|
516
|
-
'',
|
|
517
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" have"}',
|
|
518
|
-
'',
|
|
519
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" any"}',
|
|
520
|
-
'',
|
|
521
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" questions"}',
|
|
522
|
-
'',
|
|
523
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" or"}',
|
|
524
|
-
'',
|
|
525
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" need"}',
|
|
526
|
-
'',
|
|
527
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" help"}',
|
|
528
|
-
'',
|
|
529
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" finding"}',
|
|
530
|
-
'',
|
|
531
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" something"}',
|
|
532
|
-
'',
|
|
533
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":","}',
|
|
534
|
-
'',
|
|
535
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" just"}',
|
|
536
|
-
'',
|
|
537
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" let"}',
|
|
538
|
-
'',
|
|
539
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" me"}',
|
|
540
|
-
'',
|
|
541
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" know"}',
|
|
542
|
-
'',
|
|
543
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"!\\"\\n"}',
|
|
544
|
-
'',
|
|
545
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"}"}',
|
|
546
|
-
'',
|
|
547
|
-
'data: {"type":"step_complete","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":1,"success":true,"result":{"promptId":"step_01k9x5db72fzwvmdenryn0qm48","promptName":"Prompt 1","processedPrompt":"ok","response":"{\\"\\n \\"action\\": \\"message\\",\\n \\"text\\": \\"Great! If you have any questions or need help finding something, just let me know!\\"\\n}","tokens":{"input":1833,"output":34,"total":1867},"cost":0.000700125,"executionTime":2222,"order":2},"executionTime":2222}',
|
|
548
|
-
'',
|
|
549
|
-
'data: {"type":"flow_complete","flowId":"flow_01k9pfnztzfag9tfz4t65c9c5q","success":true,"duration":2968,"completedAt":"2025-11-12T23:47:42.234Z","totalTokensUsed":0}'
|
|
550
|
-
];
|
|
551
|
-
|
|
552
|
-
// Route the legacy step_chunk fixtures through the oracle as the 4.0 wire
|
|
553
|
-
// (step_chunk → step_delta → text_delta), exercising the structured
|
|
554
|
-
// JSON parser on the wire flow path: incremental text extraction, never
|
|
555
|
-
// showing raw JSON, with the assembled response reconciled at step_complete.
|
|
476
|
+
// Unified text blocks exercise incremental structured JSON parsing.
|
|
556
477
|
global.fetch = createRawStreamFetch(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
)
|
|
478
|
+
[
|
|
479
|
+
sseEvent("execution_start", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 0, kind: "flow", startedAt: "2025-11-12T23:47:39.565Z", flowId: "flow_01k9pfnztzfag9tfz4t65c9c5q", flowName: "Shopping Assistant", totalSteps: 1 }),
|
|
480
|
+
sseEvent("step_start", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 1, id: "step_01k9x5db72fzwvmdenryn0qm48", name: "Prompt 1", stepType: "prompt", index: 1, totalSteps: 1, startedAt: "2025-11-12T23:47:39.565Z" }),
|
|
481
|
+
sseEvent("text_start", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 2, id: "text_1" }),
|
|
482
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 3, id: "text_1", delta: "{\n" }),
|
|
483
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 4, id: "text_1", delta: " " }),
|
|
484
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 5, id: "text_1", delta: " \"" }),
|
|
485
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 6, id: "text_1", delta: "action" }),
|
|
486
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 7, id: "text_1", delta: "\":" }),
|
|
487
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 8, id: "text_1", delta: " \"" }),
|
|
488
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 9, id: "text_1", delta: "message" }),
|
|
489
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 10, id: "text_1", delta: "\",\n" }),
|
|
490
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 11, id: "text_1", delta: " " }),
|
|
491
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 12, id: "text_1", delta: " \"" }),
|
|
492
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 13, id: "text_1", delta: "text" }),
|
|
493
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 14, id: "text_1", delta: "\":" }),
|
|
494
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 15, id: "text_1", delta: " \"" }),
|
|
495
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 16, id: "text_1", delta: "Great" }),
|
|
496
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 17, id: "text_1", delta: "!" }),
|
|
497
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 18, id: "text_1", delta: " If" }),
|
|
498
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 19, id: "text_1", delta: " you" }),
|
|
499
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 20, id: "text_1", delta: " have" }),
|
|
500
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 21, id: "text_1", delta: " any" }),
|
|
501
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 22, id: "text_1", delta: " questions" }),
|
|
502
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 23, id: "text_1", delta: " or" }),
|
|
503
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 24, id: "text_1", delta: " need" }),
|
|
504
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 25, id: "text_1", delta: " help" }),
|
|
505
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 26, id: "text_1", delta: " finding" }),
|
|
506
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 27, id: "text_1", delta: " something" }),
|
|
507
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 28, id: "text_1", delta: "," }),
|
|
508
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 29, id: "text_1", delta: " just" }),
|
|
509
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 30, id: "text_1", delta: " let" }),
|
|
510
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 31, id: "text_1", delta: " me" }),
|
|
511
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 32, id: "text_1", delta: " know" }),
|
|
512
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 33, id: "text_1", delta: "!\"\n" }),
|
|
513
|
+
sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 34, id: "text_1", delta: "}" }),
|
|
514
|
+
sseEvent("text_complete", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 35, id: "text_1", text: "{\n \"action\": \"message\",\n \"text\": \"Great! If you have any questions or need help finding something, just let me know!\"\n}" }),
|
|
515
|
+
sseEvent("step_complete", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 36, id: "step_01k9x5db72fzwvmdenryn0qm48", name: "Prompt 1", success: true, durationMs: 2222, result: { promptId: "step_01k9x5db72fzwvmdenryn0qm48", promptName: "Prompt 1", processedPrompt: "ok", response: "{\"\n \"action\": \"message\",\n \"text\": \"Great! If you have any questions or need help finding something, just let me know!\"\n}", tokens: { input: 1833, output: 34, total: 1867 }, cost: 0.000700125, executionTime: 2222, order: 2 } }),
|
|
516
|
+
sseEvent("execution_complete", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 37, kind: "flow", success: true, completedAt: "2025-11-12T23:47:42.234Z", durationMs: 2968 })
|
|
517
|
+
]
|
|
562
518
|
);
|
|
563
519
|
|
|
564
520
|
// Dispatch and collect events
|
|
@@ -659,21 +615,6 @@ function sseEvent(eventType: string, data: Record<string, unknown>): string {
|
|
|
659
615
|
return `event: ${eventType}\ndata: ${JSON.stringify({ type: eventType, ...data })}\n\n`;
|
|
660
616
|
}
|
|
661
617
|
|
|
662
|
-
/**
|
|
663
|
-
* Re-encode legacy `agent_*` / `flow_*` / `step_*` / `tool_*` SSE frames into the
|
|
664
|
-
* Persona wire the 4.0 API now emits, using the same encoder the API uses
|
|
665
|
-
* (the vendored `createUnifiedEventWrite` oracle). The 4.0 widget only consumes the
|
|
666
|
-
* wire vocabulary, so these handler tests author the rendering intent in the
|
|
667
|
-
* (more readable) legacy frames and inject exactly what the client sees off the
|
|
668
|
-
* wire — the bridge translates it straight back before the dispatch chain renders.
|
|
669
|
-
*/
|
|
670
|
-
function legacyToWireFrames(legacyFrames: string[]): string[] {
|
|
671
|
-
const out: string[] = [];
|
|
672
|
-
const write = createUnifiedEventWrite((chunk) => out.push(chunk));
|
|
673
|
-
for (const frame of legacyFrames) write(frame);
|
|
674
|
-
return out;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
618
|
/** Stream pre-built SSE frames verbatim (no re-encode) — for fixtures already in
|
|
678
619
|
* the wire vocabulary. */
|
|
679
620
|
function createRawStreamFetch(frames: string[]) {
|
|
@@ -689,13 +630,6 @@ function createRawStreamFetch(frames: string[]) {
|
|
|
689
630
|
});
|
|
690
631
|
}
|
|
691
632
|
|
|
692
|
-
/**
|
|
693
|
-
* Mock fetch that streams the given legacy events as the 4.0 wire.
|
|
694
|
-
*/
|
|
695
|
-
function createAgentStreamFetch(events: string[]) {
|
|
696
|
-
return createRawStreamFetch(legacyToWireFrames(events));
|
|
697
|
-
}
|
|
698
|
-
|
|
699
633
|
describe('AgentWidgetClient - Agent Mode Detection', () => {
|
|
700
634
|
it('should detect agent mode when agent config is provided', () => {
|
|
701
635
|
const client = new AgentWidgetClient({
|
|
@@ -732,7 +666,7 @@ describe('AgentWidgetClient - target routing', () => {
|
|
|
732
666
|
ok: true,
|
|
733
667
|
body: new ReadableStream({
|
|
734
668
|
start(controller) {
|
|
735
|
-
controller.enqueue(encoder.encode(sseEvent('
|
|
669
|
+
controller.enqueue(encoder.encode(sseEvent('execution_complete', {
|
|
736
670
|
executionId: 'exec_1', agentId: 'agent_123', success: true, iterations: 1,
|
|
737
671
|
completedAt: new Date().toISOString(), seq: 1,
|
|
738
672
|
})));
|
|
@@ -758,7 +692,7 @@ describe('AgentWidgetClient - target routing', () => {
|
|
|
758
692
|
ok: true,
|
|
759
693
|
body: new ReadableStream({
|
|
760
694
|
start(controller) {
|
|
761
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
695
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
762
696
|
controller.close();
|
|
763
697
|
},
|
|
764
698
|
}),
|
|
@@ -782,7 +716,7 @@ describe('AgentWidgetClient - target routing', () => {
|
|
|
782
716
|
ok: true,
|
|
783
717
|
body: new ReadableStream({
|
|
784
718
|
start(controller) {
|
|
785
|
-
controller.enqueue(encoder.encode('data: {"type":"
|
|
719
|
+
controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
786
720
|
controller.close();
|
|
787
721
|
},
|
|
788
722
|
}),
|
|
@@ -816,7 +750,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
|
|
|
816
750
|
const encoder = new TextEncoder();
|
|
817
751
|
const stream = new ReadableStream({
|
|
818
752
|
start(controller) {
|
|
819
|
-
controller.enqueue(encoder.encode(sseEvent('
|
|
753
|
+
controller.enqueue(encoder.encode(sseEvent('execution_complete', {
|
|
820
754
|
executionId: 'exec_1',
|
|
821
755
|
agentId: 'agent_123',
|
|
822
756
|
success: true,
|
|
@@ -872,7 +806,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
|
|
|
872
806
|
ok: true,
|
|
873
807
|
body: new ReadableStream({
|
|
874
808
|
start(controller) {
|
|
875
|
-
controller.enqueue(encoder.encode(sseEvent('
|
|
809
|
+
controller.enqueue(encoder.encode(sseEvent('execution_complete', {
|
|
876
810
|
executionId: 'exec_1',
|
|
877
811
|
agentId: 'agent_123',
|
|
878
812
|
success: true,
|
|
@@ -920,7 +854,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
|
|
|
920
854
|
ok: true,
|
|
921
855
|
body: new ReadableStream({
|
|
922
856
|
start(controller) {
|
|
923
|
-
controller.enqueue(encoder.encode(sseEvent('
|
|
857
|
+
controller.enqueue(encoder.encode(sseEvent('execution_complete', {
|
|
924
858
|
executionId: 'exec_1',
|
|
925
859
|
agentId: 'agent_123',
|
|
926
860
|
success: true,
|
|
@@ -972,7 +906,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
|
|
|
972
906
|
const encoder = new TextEncoder();
|
|
973
907
|
const stream = new ReadableStream({
|
|
974
908
|
start(controller) {
|
|
975
|
-
controller.enqueue(encoder.encode(sseEvent('
|
|
909
|
+
controller.enqueue(encoder.encode(sseEvent('execution_complete', {
|
|
976
910
|
executionId: 'exec_1',
|
|
977
911
|
agentId: 'virtual',
|
|
978
912
|
success: true,
|
|
@@ -1034,7 +968,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
|
|
|
1034
968
|
const encoder = new TextEncoder();
|
|
1035
969
|
const stream = new ReadableStream({
|
|
1036
970
|
start(controller) {
|
|
1037
|
-
controller.enqueue(encoder.encode(sseEvent('
|
|
971
|
+
controller.enqueue(encoder.encode(sseEvent('execution_complete', {
|
|
1038
972
|
executionId: 'exec_1', agentId: 'virtual', success: true,
|
|
1039
973
|
iterations: 1, stopReason: 'max_iterations',
|
|
1040
974
|
completedAt: new Date().toISOString(), seq: 1,
|
|
@@ -1073,40 +1007,15 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1073
1007
|
const events: AgentWidgetEvent[] = [];
|
|
1074
1008
|
const execId = 'exec_test_1';
|
|
1075
1009
|
|
|
1076
|
-
global.fetch =
|
|
1077
|
-
sseEvent(
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
}),
|
|
1081
|
-
sseEvent(
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
})
|
|
1085
|
-
sseEvent('agent_turn_start', {
|
|
1086
|
-
executionId: execId, iteration: 1, turnIndex: 0,
|
|
1087
|
-
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
1088
|
-
}),
|
|
1089
|
-
sseEvent('agent_turn_delta', {
|
|
1090
|
-
executionId: execId, iteration: 1, delta: 'Hello',
|
|
1091
|
-
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
1092
|
-
}),
|
|
1093
|
-
sseEvent('agent_turn_delta', {
|
|
1094
|
-
executionId: execId, iteration: 1, delta: ' World',
|
|
1095
|
-
contentType: 'text', turnId: 'turn_1', seq: 5,
|
|
1096
|
-
}),
|
|
1097
|
-
sseEvent('agent_turn_complete', {
|
|
1098
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
1099
|
-
turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 6,
|
|
1100
|
-
}),
|
|
1101
|
-
sseEvent('agent_iteration_complete', {
|
|
1102
|
-
executionId: execId, iteration: 1, toolCallsMade: 0,
|
|
1103
|
-
stopConditionMet: false, completedAt: new Date().toISOString(), seq: 7,
|
|
1104
|
-
}),
|
|
1105
|
-
sseEvent('agent_complete', {
|
|
1106
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
1107
|
-
iterations: 1, stopReason: 'max_iterations',
|
|
1108
|
-
completedAt: new Date().toISOString(), seq: 8,
|
|
1109
|
-
}),
|
|
1010
|
+
global.fetch = createRawStreamFetch([
|
|
1011
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
1012
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
1013
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
1014
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Hello" }),
|
|
1015
|
+
sseEvent("text_delta", { executionId: execId, seq: 4, id: "text_1", delta: " World" }),
|
|
1016
|
+
sseEvent("text_complete", { executionId: execId, seq: 5, id: "text_1", text: "Hello World" }),
|
|
1017
|
+
sseEvent("turn_complete", { executionId: execId, seq: 6, id: "turn_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
|
|
1018
|
+
sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
1110
1019
|
]);
|
|
1111
1020
|
|
|
1112
1021
|
const client = new AgentWidgetClient({
|
|
@@ -1139,58 +1048,19 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1139
1048
|
const events: AgentWidgetEvent[] = [];
|
|
1140
1049
|
const execId = 'exec_test_2';
|
|
1141
1050
|
|
|
1142
|
-
global.fetch =
|
|
1143
|
-
sseEvent(
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}),
|
|
1147
|
-
|
|
1148
|
-
sseEvent(
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
}),
|
|
1152
|
-
sseEvent(
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}),
|
|
1156
|
-
sseEvent('agent_turn_delta', {
|
|
1157
|
-
executionId: execId, iteration: 1, delta: 'First iteration',
|
|
1158
|
-
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
1159
|
-
}),
|
|
1160
|
-
sseEvent('agent_turn_complete', {
|
|
1161
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
1162
|
-
turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 5,
|
|
1163
|
-
}),
|
|
1164
|
-
sseEvent('agent_iteration_complete', {
|
|
1165
|
-
executionId: execId, iteration: 1, toolCallsMade: 0,
|
|
1166
|
-
stopConditionMet: false, completedAt: new Date().toISOString(), seq: 6,
|
|
1167
|
-
}),
|
|
1168
|
-
// Iteration 2
|
|
1169
|
-
sseEvent('agent_iteration_start', {
|
|
1170
|
-
executionId: execId, iteration: 2, maxTurns: 2,
|
|
1171
|
-
startedAt: new Date().toISOString(), seq: 7,
|
|
1172
|
-
}),
|
|
1173
|
-
sseEvent('agent_turn_start', {
|
|
1174
|
-
executionId: execId, iteration: 2, turnIndex: 0,
|
|
1175
|
-
role: 'assistant', turnId: 'turn_2', seq: 8,
|
|
1176
|
-
}),
|
|
1177
|
-
sseEvent('agent_turn_delta', {
|
|
1178
|
-
executionId: execId, iteration: 2, delta: 'Second iteration',
|
|
1179
|
-
contentType: 'text', turnId: 'turn_2', seq: 9,
|
|
1180
|
-
}),
|
|
1181
|
-
sseEvent('agent_turn_complete', {
|
|
1182
|
-
executionId: execId, iteration: 2, role: 'assistant',
|
|
1183
|
-
turnId: 'turn_2', completedAt: new Date().toISOString(), seq: 10,
|
|
1184
|
-
}),
|
|
1185
|
-
sseEvent('agent_iteration_complete', {
|
|
1186
|
-
executionId: execId, iteration: 2, toolCallsMade: 0,
|
|
1187
|
-
stopConditionMet: false, completedAt: new Date().toISOString(), seq: 11,
|
|
1188
|
-
}),
|
|
1189
|
-
sseEvent('agent_complete', {
|
|
1190
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
1191
|
-
iterations: 2, stopReason: 'max_iterations',
|
|
1192
|
-
completedAt: new Date().toISOString(), seq: 12,
|
|
1193
|
-
}),
|
|
1051
|
+
global.fetch = createRawStreamFetch([
|
|
1052
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 2 }),
|
|
1053
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
1054
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
1055
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "First iteration" }),
|
|
1056
|
+
sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "First iteration" }),
|
|
1057
|
+
sseEvent("turn_complete", { executionId: execId, seq: 5, id: "turn_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
|
|
1058
|
+
sseEvent("turn_start", { executionId: execId, seq: 6, id: "turn_2", iteration: 2, turnIndex: 0, role: "assistant" }),
|
|
1059
|
+
sseEvent("text_start", { executionId: execId, seq: 7, id: "text_2", role: "assistant" }),
|
|
1060
|
+
sseEvent("text_delta", { executionId: execId, seq: 8, id: "text_2", delta: "Second iteration" }),
|
|
1061
|
+
sseEvent("text_complete", { executionId: execId, seq: 9, id: "text_2", text: "Second iteration" }),
|
|
1062
|
+
sseEvent("turn_complete", { executionId: execId, seq: 10, id: "turn_2", iteration: 2, role: "assistant", completedAt: new Date().toISOString() }),
|
|
1063
|
+
sseEvent("execution_complete", { executionId: execId, seq: 11, kind: "agent", success: true, iterations: 2, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
1194
1064
|
]);
|
|
1195
1065
|
|
|
1196
1066
|
const client = new AgentWidgetClient({
|
|
@@ -1229,36 +1099,13 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1229
1099
|
const events: AgentWidgetEvent[] = [];
|
|
1230
1100
|
const execId = 'exec_test_3';
|
|
1231
1101
|
|
|
1232
|
-
global.fetch =
|
|
1233
|
-
sseEvent(
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}),
|
|
1237
|
-
sseEvent(
|
|
1238
|
-
|
|
1239
|
-
startedAt: new Date().toISOString(), seq: 2,
|
|
1240
|
-
}),
|
|
1241
|
-
sseEvent('agent_turn_delta', {
|
|
1242
|
-
executionId: execId, iteration: 1, delta: 'First',
|
|
1243
|
-
contentType: 'text', turnId: 'turn_1', seq: 3,
|
|
1244
|
-
}),
|
|
1245
|
-
sseEvent('agent_iteration_complete', {
|
|
1246
|
-
executionId: execId, iteration: 1, toolCallsMade: 0,
|
|
1247
|
-
stopConditionMet: false, completedAt: new Date().toISOString(), seq: 4,
|
|
1248
|
-
}),
|
|
1249
|
-
sseEvent('agent_iteration_start', {
|
|
1250
|
-
executionId: execId, iteration: 2, maxTurns: 2,
|
|
1251
|
-
startedAt: new Date().toISOString(), seq: 5,
|
|
1252
|
-
}),
|
|
1253
|
-
sseEvent('agent_turn_delta', {
|
|
1254
|
-
executionId: execId, iteration: 2, delta: ' Second',
|
|
1255
|
-
contentType: 'text', turnId: 'turn_2', seq: 6,
|
|
1256
|
-
}),
|
|
1257
|
-
sseEvent('agent_complete', {
|
|
1258
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
1259
|
-
iterations: 2, stopReason: 'max_iterations',
|
|
1260
|
-
completedAt: new Date().toISOString(), seq: 7,
|
|
1261
|
-
}),
|
|
1102
|
+
global.fetch = createRawStreamFetch([
|
|
1103
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 2 }),
|
|
1104
|
+
sseEvent("text_start", { executionId: execId, seq: 1, id: "text_1", role: "assistant" }),
|
|
1105
|
+
sseEvent("text_delta", { executionId: execId, seq: 2, id: "text_1", delta: "First" }),
|
|
1106
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: " Second" }),
|
|
1107
|
+
sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "First Second" }),
|
|
1108
|
+
sseEvent("execution_complete", { executionId: execId, seq: 5, kind: "agent", success: true, iterations: 2, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
1262
1109
|
]);
|
|
1263
1110
|
|
|
1264
1111
|
const client = new AgentWidgetClient({
|
|
@@ -1292,38 +1139,15 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1292
1139
|
const events: AgentWidgetEvent[] = [];
|
|
1293
1140
|
const execId = 'exec_test_4';
|
|
1294
1141
|
|
|
1295
|
-
global.fetch =
|
|
1296
|
-
sseEvent(
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
}),
|
|
1300
|
-
sseEvent(
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
})
|
|
1304
|
-
sseEvent('agent_tool_start', {
|
|
1305
|
-
executionId: execId, iteration: 1, toolCallId: 'tc_1',
|
|
1306
|
-
toolName: 'search', toolType: 'function',
|
|
1307
|
-
parameters: { query: 'weather' }, seq: 3,
|
|
1308
|
-
}),
|
|
1309
|
-
sseEvent('agent_tool_delta', {
|
|
1310
|
-
executionId: execId, iteration: 1, toolCallId: 'tc_1',
|
|
1311
|
-
delta: 'Searching...', seq: 4,
|
|
1312
|
-
}),
|
|
1313
|
-
sseEvent('agent_tool_complete', {
|
|
1314
|
-
executionId: execId, iteration: 1, toolCallId: 'tc_1',
|
|
1315
|
-
toolName: 'search', success: true,
|
|
1316
|
-
result: { temperature: 72 }, executionTime: 150, seq: 5,
|
|
1317
|
-
}),
|
|
1318
|
-
sseEvent('agent_turn_delta', {
|
|
1319
|
-
executionId: execId, iteration: 1, delta: 'The weather is 72F.',
|
|
1320
|
-
contentType: 'text', turnId: 'turn_1', seq: 6,
|
|
1321
|
-
}),
|
|
1322
|
-
sseEvent('agent_complete', {
|
|
1323
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
1324
|
-
iterations: 1, stopReason: 'max_iterations',
|
|
1325
|
-
completedAt: new Date().toISOString(), seq: 7,
|
|
1326
|
-
}),
|
|
1142
|
+
global.fetch = createRawStreamFetch([
|
|
1143
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
1144
|
+
sseEvent("tool_start", { executionId: execId, seq: 1, toolCallId: "tc_1", toolName: "search", toolType: "function", iteration: 1, parameters: { query: "weather" } }),
|
|
1145
|
+
sseEvent("tool_output_delta", { executionId: execId, seq: 2, toolCallId: "tc_1", delta: "Searching..." }),
|
|
1146
|
+
sseEvent("tool_complete", { executionId: execId, seq: 3, toolCallId: "tc_1", toolName: "search", success: true, result: { temperature: 72 }, executionTime: 150, iteration: 1 }),
|
|
1147
|
+
sseEvent("text_start", { executionId: execId, seq: 4, id: "text_1", role: "assistant" }),
|
|
1148
|
+
sseEvent("text_delta", { executionId: execId, seq: 5, id: "text_1", delta: "The weather is 72F." }),
|
|
1149
|
+
sseEvent("text_complete", { executionId: execId, seq: 6, id: "text_1", text: "The weather is 72F." }),
|
|
1150
|
+
sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
1327
1151
|
]);
|
|
1328
1152
|
|
|
1329
1153
|
const client = new AgentWidgetClient({
|
|
@@ -1364,32 +1188,16 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1364
1188
|
const events: AgentWidgetEvent[] = [];
|
|
1365
1189
|
const execId = 'exec_test_5';
|
|
1366
1190
|
|
|
1367
|
-
global.fetch =
|
|
1368
|
-
sseEvent(
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
}),
|
|
1372
|
-
sseEvent(
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
}),
|
|
1376
|
-
sseEvent(
|
|
1377
|
-
executionId: execId, iteration: 1, delta: 'Let me think...',
|
|
1378
|
-
contentType: 'thinking', turnId: 'think_1', seq: 3,
|
|
1379
|
-
}),
|
|
1380
|
-
sseEvent('agent_turn_complete', {
|
|
1381
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
1382
|
-
turnId: 'think_1', completedAt: new Date().toISOString(), seq: 4,
|
|
1383
|
-
}),
|
|
1384
|
-
sseEvent('agent_turn_delta', {
|
|
1385
|
-
executionId: execId, iteration: 1, delta: 'The answer is 42.',
|
|
1386
|
-
contentType: 'text', turnId: 'turn_1', seq: 5,
|
|
1387
|
-
}),
|
|
1388
|
-
sseEvent('agent_complete', {
|
|
1389
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
1390
|
-
iterations: 1, stopReason: 'max_iterations',
|
|
1391
|
-
completedAt: new Date().toISOString(), seq: 6,
|
|
1392
|
-
}),
|
|
1191
|
+
global.fetch = createRawStreamFetch([
|
|
1192
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
1193
|
+
sseEvent("reasoning_start", { executionId: execId, seq: 1, id: "reason_1" }),
|
|
1194
|
+
sseEvent("reasoning_delta", { executionId: execId, seq: 2, id: "reason_1", delta: "Let me think..." }),
|
|
1195
|
+
sseEvent("reasoning_complete", { executionId: execId, seq: 3, id: "reason_1", text: "Let me think..." }),
|
|
1196
|
+
sseEvent("turn_complete", { executionId: execId, seq: 4, id: "think_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
|
|
1197
|
+
sseEvent("text_start", { executionId: execId, seq: 5, id: "text_2", role: "assistant" }),
|
|
1198
|
+
sseEvent("text_delta", { executionId: execId, seq: 6, id: "text_2", delta: "The answer is 42." }),
|
|
1199
|
+
sseEvent("text_complete", { executionId: execId, seq: 7, id: "text_2", text: "The answer is 42." }),
|
|
1200
|
+
sseEvent("execution_complete", { executionId: execId, seq: 8, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
1393
1201
|
]);
|
|
1394
1202
|
|
|
1395
1203
|
const client = new AgentWidgetClient({
|
|
@@ -1428,21 +1236,10 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1428
1236
|
const events: AgentWidgetEvent[] = [];
|
|
1429
1237
|
const execId = 'exec_test_6';
|
|
1430
1238
|
|
|
1431
|
-
global.fetch =
|
|
1432
|
-
sseEvent(
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
}),
|
|
1436
|
-
sseEvent('agent_error', {
|
|
1437
|
-
executionId: execId, iteration: 1,
|
|
1438
|
-
error: 'Rate limit hit, retrying...',
|
|
1439
|
-
recoverable: true, seq: 2,
|
|
1440
|
-
}),
|
|
1441
|
-
sseEvent('agent_error', {
|
|
1442
|
-
executionId: execId, iteration: 1,
|
|
1443
|
-
error: 'Fatal: model unavailable',
|
|
1444
|
-
recoverable: false, seq: 3,
|
|
1445
|
-
}),
|
|
1239
|
+
global.fetch = createRawStreamFetch([
|
|
1240
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
1241
|
+
sseEvent("error", { executionId: execId, seq: 1, error: "Rate limit hit, retrying...", recoverable: true }),
|
|
1242
|
+
sseEvent("execution_error", { executionId: execId, seq: 2, kind: "agent", error: "Fatal: model unavailable" })
|
|
1446
1243
|
]);
|
|
1447
1244
|
|
|
1448
1245
|
const client = new AgentWidgetClient({
|
|
@@ -1463,13 +1260,15 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1463
1260
|
}
|
|
1464
1261
|
});
|
|
1465
1262
|
|
|
1466
|
-
it('should emit error and finalize streaming on
|
|
1263
|
+
it('should emit error and finalize streaming on a failed step_complete', async () => {
|
|
1467
1264
|
const events: AgentWidgetEvent[] = [];
|
|
1468
1265
|
|
|
1469
|
-
global.fetch =
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
sseEvent(
|
|
1266
|
+
global.fetch = createRawStreamFetch([
|
|
1267
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1268
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1269
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "partial" }),
|
|
1270
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "partial" }),
|
|
1271
|
+
sseEvent("step_complete", { executionId: "exec-test", seq: 4, success: false, error: "step blew up" })
|
|
1473
1272
|
]);
|
|
1474
1273
|
|
|
1475
1274
|
const client = new AgentWidgetClient({
|
|
@@ -1498,13 +1297,18 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1498
1297
|
expect(lastAssistant?.type === 'message' && lastAssistant.message.streaming).toBe(false);
|
|
1499
1298
|
});
|
|
1500
1299
|
|
|
1501
|
-
it('should emit error and finalize streaming on
|
|
1300
|
+
it('should emit error and finalize streaming on execution_error', async () => {
|
|
1502
1301
|
const events: AgentWidgetEvent[] = [];
|
|
1503
1302
|
|
|
1504
|
-
global.fetch =
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
sseEvent(
|
|
1303
|
+
global.fetch = createRawStreamFetch([
|
|
1304
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1305
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1306
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "x" }),
|
|
1307
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "x" }),
|
|
1308
|
+
sseEvent("execution_error", {
|
|
1309
|
+
executionId: "exec-test", seq: 4,
|
|
1310
|
+
kind: "flow", error: { code: "error", message: "bad config" },
|
|
1311
|
+
})
|
|
1508
1312
|
]);
|
|
1509
1313
|
|
|
1510
1314
|
const client = new AgentWidgetClient({
|
|
@@ -1531,20 +1335,11 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1531
1335
|
const events: AgentWidgetEvent[] = [];
|
|
1532
1336
|
const execId = 'exec_test_7';
|
|
1533
1337
|
|
|
1534
|
-
global.fetch =
|
|
1535
|
-
sseEvent(
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
})
|
|
1539
|
-
sseEvent('agent_reflection', {
|
|
1540
|
-
executionId: execId, iteration: 1,
|
|
1541
|
-
reflection: 'I should try a different approach.', seq: 2,
|
|
1542
|
-
}),
|
|
1543
|
-
sseEvent('agent_complete', {
|
|
1544
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
1545
|
-
iterations: 2, stopReason: 'max_iterations',
|
|
1546
|
-
completedAt: new Date().toISOString(), seq: 3,
|
|
1547
|
-
}),
|
|
1338
|
+
global.fetch = createRawStreamFetch([
|
|
1339
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 2 }),
|
|
1340
|
+
sseEvent("reasoning_start", { executionId: execId, seq: 1, id: "reason_1", scope: "loop" }),
|
|
1341
|
+
sseEvent("reasoning_complete", { executionId: execId, seq: 2, id: "reason_1", text: "I should try a different approach.", scope: "loop" }),
|
|
1342
|
+
sseEvent("execution_complete", { executionId: execId, seq: 3, kind: "agent", success: true, iterations: 2, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
1548
1343
|
]);
|
|
1549
1344
|
|
|
1550
1345
|
const client = new AgentWidgetClient({
|
|
@@ -1573,27 +1368,17 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1573
1368
|
expect(reflectionMessages[0].reasoning?.chunks.join('')).toBe('I should try a different approach.');
|
|
1574
1369
|
});
|
|
1575
1370
|
|
|
1576
|
-
it('should handle
|
|
1371
|
+
it('should handle ping events gracefully', async () => {
|
|
1577
1372
|
const events: AgentWidgetEvent[] = [];
|
|
1578
1373
|
const execId = 'exec_test_8';
|
|
1579
1374
|
|
|
1580
|
-
global.fetch =
|
|
1581
|
-
sseEvent(
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
}),
|
|
1585
|
-
sseEvent(
|
|
1586
|
-
|
|
1587
|
-
}),
|
|
1588
|
-
sseEvent('agent_turn_delta', {
|
|
1589
|
-
executionId: execId, iteration: 1, delta: 'Hi',
|
|
1590
|
-
contentType: 'text', turnId: 'turn_1', seq: 3,
|
|
1591
|
-
}),
|
|
1592
|
-
sseEvent('agent_complete', {
|
|
1593
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
1594
|
-
iterations: 1, stopReason: 'max_iterations',
|
|
1595
|
-
completedAt: new Date().toISOString(), seq: 4,
|
|
1596
|
-
}),
|
|
1375
|
+
global.fetch = createRawStreamFetch([
|
|
1376
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
1377
|
+
sseEvent("ping", { executionId: execId, seq: 1, timestamp: new Date().toISOString() }),
|
|
1378
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
1379
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Hi" }),
|
|
1380
|
+
sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "Hi" }),
|
|
1381
|
+
sseEvent("execution_complete", { executionId: execId, seq: 5, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
1597
1382
|
]);
|
|
1598
1383
|
|
|
1599
1384
|
const client = new AgentWidgetClient({
|
|
@@ -1622,7 +1407,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
|
|
|
1622
1407
|
});
|
|
1623
1408
|
|
|
1624
1409
|
// ============================================================================
|
|
1625
|
-
//
|
|
1410
|
+
// Unified text and tool event handling
|
|
1626
1411
|
// ============================================================================
|
|
1627
1412
|
|
|
1628
1413
|
// ============================================================================
|
|
@@ -1633,20 +1418,20 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1633
1418
|
it('should split flow text segments at a tool boundary', async () => {
|
|
1634
1419
|
const events: AgentWidgetEvent[] = [];
|
|
1635
1420
|
|
|
1636
|
-
global.fetch =
|
|
1637
|
-
sseEvent(
|
|
1638
|
-
sseEvent(
|
|
1639
|
-
sseEvent(
|
|
1640
|
-
sseEvent(
|
|
1641
|
-
sseEvent(
|
|
1642
|
-
sseEvent(
|
|
1643
|
-
sseEvent(
|
|
1644
|
-
sseEvent(
|
|
1645
|
-
sseEvent(
|
|
1646
|
-
sseEvent(
|
|
1647
|
-
sseEvent(
|
|
1648
|
-
sseEvent(
|
|
1649
|
-
sseEvent(
|
|
1421
|
+
global.fetch = createRawStreamFetch([
|
|
1422
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1423
|
+
sseEvent("step_start", { executionId: "exec-test", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
|
|
1424
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 2, id: "text_1" }),
|
|
1425
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 3, id: "text_1", delta: "Let me search" }),
|
|
1426
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: " for that!" }),
|
|
1427
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "Let me search for that!" }),
|
|
1428
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 6, toolCallId: "tc_1", toolName: "search", toolType: "mcp", startedAt: new Date().toISOString() }),
|
|
1429
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 7, toolCallId: "tc_1", toolName: "search", success: true, result: { found: true }, executionTime: 200 }),
|
|
1430
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 8, id: "text_2" }),
|
|
1431
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 9, id: "text_2", delta: "Found it! Here" }),
|
|
1432
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 10, id: "text_2", delta: " are the results." }),
|
|
1433
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 11, id: "text_2", text: "Found it! Here are the results." }),
|
|
1434
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 12, kind: "flow", success: true })
|
|
1650
1435
|
]);
|
|
1651
1436
|
|
|
1652
1437
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -1690,17 +1475,17 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1690
1475
|
it('should split assistant messages using text_start/text_end lifecycle events', async () => {
|
|
1691
1476
|
const events: AgentWidgetEvent[] = [];
|
|
1692
1477
|
|
|
1693
|
-
global.fetch =
|
|
1694
|
-
sseEvent(
|
|
1695
|
-
sseEvent(
|
|
1696
|
-
sseEvent(
|
|
1697
|
-
sseEvent(
|
|
1698
|
-
sseEvent(
|
|
1699
|
-
sseEvent(
|
|
1700
|
-
sseEvent(
|
|
1701
|
-
sseEvent(
|
|
1702
|
-
sseEvent(
|
|
1703
|
-
sseEvent(
|
|
1478
|
+
global.fetch = createRawStreamFetch([
|
|
1479
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1480
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1481
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Preamble text." }),
|
|
1482
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Preamble text." }),
|
|
1483
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tc_1", toolName: "get_weather", toolType: "builtin", startedAt: new Date().toISOString() }),
|
|
1484
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tc_1", toolName: "get_weather", success: true, result: { temp: 72 }, executionTime: 100 }),
|
|
1485
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 6, id: "text_2" }),
|
|
1486
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 7, id: "text_2", delta: "The weather is 72F." }),
|
|
1487
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 8, id: "text_2", text: "The weather is 72F." }),
|
|
1488
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 9, kind: "flow", success: true })
|
|
1704
1489
|
]);
|
|
1705
1490
|
|
|
1706
1491
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -1741,15 +1526,15 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1741
1526
|
// Tool UI is the first meaningful output. Some providers still emit
|
|
1742
1527
|
// newline-only text lifecycle events around the tool boundary; those must
|
|
1743
1528
|
// not become an empty assistant message bubble.
|
|
1744
|
-
global.fetch =
|
|
1745
|
-
sseEvent(
|
|
1746
|
-
sseEvent(
|
|
1747
|
-
sseEvent(
|
|
1748
|
-
sseEvent(
|
|
1749
|
-
sseEvent(
|
|
1750
|
-
sseEvent(
|
|
1751
|
-
sseEvent(
|
|
1752
|
-
sseEvent(
|
|
1529
|
+
global.fetch = createRawStreamFetch([
|
|
1530
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1531
|
+
sseEvent("step_start", { executionId: "exec-test", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 1, totalSteps: 1 }),
|
|
1532
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 2, toolCallId: "tc_1", toolName: "add_to_cart", toolType: "local" }),
|
|
1533
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 3, id: "text_1" }),
|
|
1534
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: "\n" }),
|
|
1535
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "\n" }),
|
|
1536
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 6, toolCallId: "tc_1", toolName: "add_to_cart", success: true, executionTime: 20 }),
|
|
1537
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 7, kind: "flow", success: true })
|
|
1753
1538
|
]);
|
|
1754
1539
|
|
|
1755
1540
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -1778,11 +1563,13 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1778
1563
|
const events: AgentWidgetEvent[] = [];
|
|
1779
1564
|
|
|
1780
1565
|
// No text_end / tool boundary between the two deltas → one text block → one bubble.
|
|
1781
|
-
global.fetch =
|
|
1782
|
-
sseEvent(
|
|
1783
|
-
sseEvent(
|
|
1784
|
-
sseEvent(
|
|
1785
|
-
sseEvent(
|
|
1566
|
+
global.fetch = createRawStreamFetch([
|
|
1567
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1568
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1569
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Hello " }),
|
|
1570
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 3, id: "text_1", delta: "world" }),
|
|
1571
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 4, id: "text_1", text: "Hello world" }),
|
|
1572
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 5, kind: "flow", success: true })
|
|
1786
1573
|
]);
|
|
1787
1574
|
|
|
1788
1575
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -1809,27 +1596,22 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1809
1596
|
it('should handle multiple tool calls with proper text interleaving', async () => {
|
|
1810
1597
|
const events: AgentWidgetEvent[] = [];
|
|
1811
1598
|
|
|
1812
|
-
global.fetch =
|
|
1813
|
-
sseEvent(
|
|
1814
|
-
|
|
1815
|
-
sseEvent(
|
|
1816
|
-
sseEvent(
|
|
1817
|
-
sseEvent(
|
|
1818
|
-
|
|
1819
|
-
sseEvent(
|
|
1820
|
-
sseEvent(
|
|
1821
|
-
|
|
1822
|
-
sseEvent(
|
|
1823
|
-
sseEvent(
|
|
1824
|
-
sseEvent(
|
|
1825
|
-
|
|
1826
|
-
sseEvent(
|
|
1827
|
-
sseEvent(
|
|
1828
|
-
// final segment
|
|
1829
|
-
sseEvent('text_start', { messageId: 'msg_s1' }),
|
|
1830
|
-
sseEvent('step_delta', { id: 's1', text: 'Done! Item added.' }),
|
|
1831
|
-
sseEvent('text_end', { messageId: 'msg_s1' }),
|
|
1832
|
-
sseEvent('flow_complete', { success: true }),
|
|
1599
|
+
global.fetch = createRawStreamFetch([
|
|
1600
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1601
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1602
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Searching..." }),
|
|
1603
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Searching..." }),
|
|
1604
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tc_1", toolName: "search", toolType: "mcp" }),
|
|
1605
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tc_1", toolName: "search", success: true, result: { id: 27 }, executionTime: 100 }),
|
|
1606
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 6, id: "text_2" }),
|
|
1607
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 7, id: "text_2", delta: "Adding to cart..." }),
|
|
1608
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 8, id: "text_2", text: "Adding to cart..." }),
|
|
1609
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 9, toolCallId: "tc_2", toolName: "add_to_cart", toolType: "mcp" }),
|
|
1610
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 10, toolCallId: "tc_2", toolName: "add_to_cart", success: true, result: { success: true }, executionTime: 50 }),
|
|
1611
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 11, id: "text_3" }),
|
|
1612
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 12, id: "text_3", delta: "Done! Item added." }),
|
|
1613
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 13, id: "text_3", text: "Done! Item added." }),
|
|
1614
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 14, kind: "flow", success: true })
|
|
1833
1615
|
]);
|
|
1834
1616
|
|
|
1835
1617
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -1876,17 +1658,17 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1876
1658
|
it('should give split messages unique IDs even when assistantMessageId is provided', async () => {
|
|
1877
1659
|
const events: AgentWidgetEvent[] = [];
|
|
1878
1660
|
|
|
1879
|
-
global.fetch =
|
|
1880
|
-
sseEvent(
|
|
1881
|
-
sseEvent(
|
|
1882
|
-
sseEvent(
|
|
1883
|
-
sseEvent(
|
|
1884
|
-
sseEvent(
|
|
1885
|
-
sseEvent(
|
|
1886
|
-
sseEvent(
|
|
1887
|
-
sseEvent(
|
|
1888
|
-
sseEvent(
|
|
1889
|
-
sseEvent(
|
|
1661
|
+
global.fetch = createRawStreamFetch([
|
|
1662
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1663
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1664
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Before tool." }),
|
|
1665
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Before tool." }),
|
|
1666
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tc_1", toolName: "lookup", toolType: "mcp" }),
|
|
1667
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tc_1", toolName: "lookup", success: true, result: { }, executionTime: 50 }),
|
|
1668
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 6, id: "text_2" }),
|
|
1669
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 7, id: "text_2", delta: "After tool." }),
|
|
1670
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 8, id: "text_2", text: "After tool." }),
|
|
1671
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 9, kind: "flow", success: true })
|
|
1890
1672
|
]);
|
|
1891
1673
|
|
|
1892
1674
|
// Use agent mode so assistantMessageId is forwarded to streamResponse
|
|
@@ -1930,19 +1712,19 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1930
1712
|
// (sealed by text_end → text_complete). The step's full structured response
|
|
1931
1713
|
// (`step_complete.result.response`) reconciles rawContent without clobbering
|
|
1932
1714
|
// either sealed bubble's displayed content.
|
|
1933
|
-
global.fetch =
|
|
1934
|
-
sseEvent(
|
|
1935
|
-
sseEvent(
|
|
1936
|
-
sseEvent(
|
|
1937
|
-
sseEvent(
|
|
1938
|
-
sseEvent(
|
|
1939
|
-
sseEvent(
|
|
1940
|
-
sseEvent(
|
|
1941
|
-
sseEvent(
|
|
1942
|
-
sseEvent(
|
|
1943
|
-
sseEvent(
|
|
1944
|
-
sseEvent(
|
|
1945
|
-
sseEvent(
|
|
1715
|
+
global.fetch = createRawStreamFetch([
|
|
1716
|
+
sseEvent("execution_start", { executionId: "exec_f1", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1717
|
+
sseEvent("step_start", { executionId: "exec_f1", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
|
|
1718
|
+
sseEvent("text_start", { executionId: "exec_f1", seq: 2, id: "text_1" }),
|
|
1719
|
+
sseEvent("text_delta", { executionId: "exec_f1", seq: 3, id: "text_1", delta: "First part." }),
|
|
1720
|
+
sseEvent("text_complete", { executionId: "exec_f1", seq: 4, id: "text_1", text: "First part." }),
|
|
1721
|
+
sseEvent("tool_start", { executionId: "exec_f1", seq: 5, toolCallId: "tc_1", toolName: "action", toolType: "mcp", startedAt: new Date().toISOString() }),
|
|
1722
|
+
sseEvent("tool_complete", { executionId: "exec_f1", seq: 6, toolCallId: "tc_1", toolName: "action", success: true, result: { }, executionTime: 10 }),
|
|
1723
|
+
sseEvent("text_start", { executionId: "exec_f1", seq: 7, id: "text_2" }),
|
|
1724
|
+
sseEvent("text_delta", { executionId: "exec_f1", seq: 8, id: "text_2", delta: "Second part." }),
|
|
1725
|
+
sseEvent("text_complete", { executionId: "exec_f1", seq: 9, id: "text_2", text: "Second part." }),
|
|
1726
|
+
sseEvent("step_complete", { executionId: "exec_f1", seq: 10, id: "s1", name: "Prompt", stepType: "prompt", success: true, durationMs: 500, result: { response: "First part.Second part." } }),
|
|
1727
|
+
sseEvent("execution_complete", { executionId: "exec_f1", seq: 11, kind: "flow", success: true })
|
|
1946
1728
|
]);
|
|
1947
1729
|
|
|
1948
1730
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -1973,18 +1755,15 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1973
1755
|
it('should not duplicate text when step_complete follows text_end', async () => {
|
|
1974
1756
|
const events: AgentWidgetEvent[] = [];
|
|
1975
1757
|
|
|
1976
|
-
global.fetch =
|
|
1977
|
-
sseEvent(
|
|
1978
|
-
|
|
1979
|
-
sseEvent(
|
|
1980
|
-
sseEvent(
|
|
1981
|
-
|
|
1982
|
-
sseEvent(
|
|
1983
|
-
sseEvent(
|
|
1984
|
-
sseEvent(
|
|
1985
|
-
// step_complete with full response (should NOT create a duplicate)
|
|
1986
|
-
sseEvent('step_complete', { id: 's1', name: 'Response', stepType: 'prompt', success: true, result: { response: 'Tool returned a result.' }, executionTime: 500 }),
|
|
1987
|
-
sseEvent('flow_complete', { success: true }),
|
|
1758
|
+
global.fetch = createRawStreamFetch([
|
|
1759
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1760
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 1, toolCallId: "tc_1", toolName: "test_tool", toolType: "custom", startedAt: new Date().toISOString() }),
|
|
1761
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 2, toolCallId: "tc_1", toolName: "test_tool", success: true, executionTime: 0 }),
|
|
1762
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 3, id: "text_1" }),
|
|
1763
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: "Tool returned a result." }),
|
|
1764
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "Tool returned a result." }),
|
|
1765
|
+
sseEvent("step_complete", { executionId: "exec-test", seq: 6, id: "s1", name: "Response", stepType: "prompt", success: true, durationMs: 500, result: { response: "Tool returned a result." } }),
|
|
1766
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 7, kind: "flow", success: true })
|
|
1988
1767
|
]);
|
|
1989
1768
|
|
|
1990
1769
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -2041,26 +1820,19 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
2041
1820
|
};
|
|
2042
1821
|
};
|
|
2043
1822
|
|
|
2044
|
-
//
|
|
1823
|
+
// Unified wire: a single flow text block carrying partial
|
|
2045
1824
|
// structured JSON, sealed by text_end, then the authoritative final structured
|
|
2046
1825
|
// response on step_complete. Exercises the async structured-content parser +
|
|
2047
1826
|
// sealed-segment reconciliation on the wire flow path.
|
|
2048
|
-
global.fetch =
|
|
2049
|
-
sseEvent(
|
|
2050
|
-
sseEvent(
|
|
2051
|
-
sseEvent(
|
|
2052
|
-
sseEvent(
|
|
2053
|
-
sseEvent(
|
|
2054
|
-
sseEvent(
|
|
2055
|
-
sseEvent(
|
|
2056
|
-
|
|
2057
|
-
name: 'Response',
|
|
2058
|
-
stepType: 'prompt',
|
|
2059
|
-
success: true,
|
|
2060
|
-
result: { response: opts.stepCompleteResponse },
|
|
2061
|
-
executionTime: 500
|
|
2062
|
-
}),
|
|
2063
|
-
sseEvent('flow_complete', { success: true }),
|
|
1827
|
+
global.fetch = createRawStreamFetch([
|
|
1828
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1829
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 1, toolCallId: "tc_1", toolName: "test_tool", toolType: "custom", startedAt: new Date().toISOString() }),
|
|
1830
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 2, toolCallId: "tc_1", toolName: "test_tool", success: true, executionTime: 0 }),
|
|
1831
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 3, id: "text_1" }),
|
|
1832
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: "{\"text\":\"Tool returned a re" }),
|
|
1833
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "{\"text\":\"Tool returned a re" }),
|
|
1834
|
+
sseEvent("step_complete", { executionId: "exec-test", seq: 6, id: "s1", name: "Response", stepType: "prompt", success: true, durationMs: 500, result: { response: opts.stepCompleteResponse } }),
|
|
1835
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 7, kind: "flow", success: true })
|
|
2064
1836
|
]);
|
|
2065
1837
|
|
|
2066
1838
|
const client = new AgentWidgetClient({
|
|
@@ -2112,7 +1884,7 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
2112
1884
|
|
|
2113
1885
|
describe('AgentWidgetClient - flow continuation stream without execution_start', () => {
|
|
2114
1886
|
// A tool-driven `/resume` continues a flow on a brand-new stream that does NOT
|
|
2115
|
-
// re-emit `execution_start
|
|
1887
|
+
// re-emit `execution_start`. Each stream resolves its execution
|
|
2116
1888
|
// kind independently and defaults to `"agent"`, so the continuation used to
|
|
2117
1889
|
// mis-route the final prompt-step finalization: the streamed text block was
|
|
2118
1890
|
// sealed via the agent path (which never records it as the sealed flow bubble),
|
|
@@ -2132,16 +1904,15 @@ describe('AgentWidgetClient - flow continuation stream without execution_start',
|
|
|
2132
1904
|
it('does not duplicate the final message when a flow resumes without execution_start', async () => {
|
|
2133
1905
|
const events: AgentWidgetEvent[] = [];
|
|
2134
1906
|
|
|
2135
|
-
// No `
|
|
1907
|
+
// No `execution_start`: this is exactly the wire a tool-driven
|
|
2136
1908
|
// `/resume` continuation delivers.
|
|
2137
|
-
global.fetch =
|
|
2138
|
-
sseEvent(
|
|
2139
|
-
sseEvent(
|
|
2140
|
-
sseEvent(
|
|
2141
|
-
sseEvent(
|
|
2142
|
-
|
|
2143
|
-
sseEvent(
|
|
2144
|
-
sseEvent('flow_complete', { success: true }),
|
|
1909
|
+
global.fetch = createRawStreamFetch([
|
|
1910
|
+
sseEvent("step_start", { executionId: "exec-test", seq: 0, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
|
|
1911
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1912
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Done! Added to your cart." }),
|
|
1913
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Done! Added to your cart." }),
|
|
1914
|
+
sseEvent("step_complete", { executionId: "exec-test", seq: 4, id: "s1", name: "Prompt", stepType: "prompt", success: true, durationMs: 500, result: { response: "Done! Added to your cart." } }),
|
|
1915
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 5, kind: "flow", success: true })
|
|
2145
1916
|
]);
|
|
2146
1917
|
|
|
2147
1918
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -2161,15 +1932,15 @@ describe('AgentWidgetClient - flow continuation stream without execution_start',
|
|
|
2161
1932
|
it('still reconciles in place when execution_start IS present (initial flow stream)', async () => {
|
|
2162
1933
|
const events: AgentWidgetEvent[] = [];
|
|
2163
1934
|
|
|
2164
|
-
// Same shape but WITH
|
|
2165
|
-
global.fetch =
|
|
2166
|
-
sseEvent(
|
|
2167
|
-
sseEvent(
|
|
2168
|
-
sseEvent(
|
|
2169
|
-
sseEvent(
|
|
2170
|
-
sseEvent(
|
|
2171
|
-
sseEvent(
|
|
2172
|
-
sseEvent(
|
|
1935
|
+
// Same shape but WITH execution_start — the pre-existing, already-correct path.
|
|
1936
|
+
global.fetch = createRawStreamFetch([
|
|
1937
|
+
sseEvent("execution_start", { executionId: "exec_f1", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1938
|
+
sseEvent("step_start", { executionId: "exec_f1", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
|
|
1939
|
+
sseEvent("text_start", { executionId: "exec_f1", seq: 2, id: "text_1" }),
|
|
1940
|
+
sseEvent("text_delta", { executionId: "exec_f1", seq: 3, id: "text_1", delta: "Done! Added to your cart." }),
|
|
1941
|
+
sseEvent("text_complete", { executionId: "exec_f1", seq: 4, id: "text_1", text: "Done! Added to your cart." }),
|
|
1942
|
+
sseEvent("step_complete", { executionId: "exec_f1", seq: 5, id: "s1", name: "Prompt", stepType: "prompt", success: true, durationMs: 500, result: { response: "Done! Added to your cart." } }),
|
|
1943
|
+
sseEvent("execution_complete", { executionId: "exec_f1", seq: 6, kind: "flow", success: true })
|
|
2173
1944
|
]);
|
|
2174
1945
|
|
|
2175
1946
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -2188,39 +1959,17 @@ describe('AgentWidgetClient - flow continuation stream without execution_start',
|
|
|
2188
1959
|
const events: AgentWidgetEvent[] = [];
|
|
2189
1960
|
const execId = 'exec_a1';
|
|
2190
1961
|
|
|
2191
|
-
// Agent loops use `
|
|
1962
|
+
// Agent loops use `turn_*` frames and never carry a `stepType`, so the
|
|
2192
1963
|
// flow recovery must not engage. A single streamed turn yields exactly one
|
|
2193
1964
|
// bubble (regression guard so the recovery can't over-fire on agents).
|
|
2194
|
-
global.fetch =
|
|
2195
|
-
sseEvent(
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
}),
|
|
2199
|
-
sseEvent(
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
}),
|
|
2203
|
-
sseEvent('agent_turn_start', {
|
|
2204
|
-
executionId: execId, iteration: 1, turnIndex: 0,
|
|
2205
|
-
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
2206
|
-
}),
|
|
2207
|
-
sseEvent('agent_turn_delta', {
|
|
2208
|
-
executionId: execId, iteration: 1, delta: 'Hi there!',
|
|
2209
|
-
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2210
|
-
}),
|
|
2211
|
-
sseEvent('agent_turn_complete', {
|
|
2212
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
2213
|
-
turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 5,
|
|
2214
|
-
}),
|
|
2215
|
-
sseEvent('agent_iteration_complete', {
|
|
2216
|
-
executionId: execId, iteration: 1, toolCallsMade: 0,
|
|
2217
|
-
stopConditionMet: false, completedAt: new Date().toISOString(), seq: 6,
|
|
2218
|
-
}),
|
|
2219
|
-
sseEvent('agent_complete', {
|
|
2220
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
2221
|
-
iterations: 1, stopReason: 'max_iterations',
|
|
2222
|
-
completedAt: new Date().toISOString(), seq: 7,
|
|
2223
|
-
}),
|
|
1965
|
+
global.fetch = createRawStreamFetch([
|
|
1966
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
1967
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
1968
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
1969
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Hi there!" }),
|
|
1970
|
+
sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "Hi there!" }),
|
|
1971
|
+
sseEvent("turn_complete", { executionId: execId, seq: 5, id: "turn_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
|
|
1972
|
+
sseEvent("execution_complete", { executionId: execId, seq: 6, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
2224
1973
|
]);
|
|
2225
1974
|
|
|
2226
1975
|
const client = new AgentWidgetClient({
|
|
@@ -2244,20 +1993,17 @@ describe('AgentWidgetClient - nested flow-as-tool (parentToolCallId)', () => {
|
|
|
2244
1993
|
// .parentToolCallId, and the widget routes that block into the parent tool's row.
|
|
2245
1994
|
it('routes nested flow text into the parent tool row, not the top-level assistant', async () => {
|
|
2246
1995
|
const events: AgentWidgetEvent[] = [];
|
|
2247
|
-
global.fetch =
|
|
2248
|
-
sseEvent(
|
|
2249
|
-
|
|
2250
|
-
sseEvent(
|
|
2251
|
-
sseEvent(
|
|
2252
|
-
sseEvent(
|
|
2253
|
-
|
|
2254
|
-
sseEvent(
|
|
2255
|
-
|
|
2256
|
-
sseEvent(
|
|
2257
|
-
sseEvent(
|
|
2258
|
-
sseEvent('text_end', { messageId: 'msg_nested', toolContext: { toolId: 'tool_nested_1' } }),
|
|
2259
|
-
sseEvent('tool_complete', { toolId: 'tool_nested_1', name: 'run_subflow', success: true, executionTime: 100 }),
|
|
2260
|
-
sseEvent('flow_complete', { success: true }),
|
|
1996
|
+
global.fetch = createRawStreamFetch([
|
|
1997
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
1998
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
1999
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Parent says hi." }),
|
|
2000
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Parent says hi." }),
|
|
2001
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tool_nested_1", toolName: "run_subflow", toolType: "flow", startedAt: new Date().toISOString() }),
|
|
2002
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 5, id: "text_2", parentToolCallId: "tool_nested_1" }),
|
|
2003
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 6, id: "text_2", delta: "Nested result." }),
|
|
2004
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 7, id: "text_2", text: "Nested result." }),
|
|
2005
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 8, toolCallId: "tool_nested_1", toolName: "run_subflow", success: true, executionTime: 100 }),
|
|
2006
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 9, kind: "flow", success: true })
|
|
2261
2007
|
]);
|
|
2262
2008
|
|
|
2263
2009
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
@@ -2284,12 +2030,12 @@ describe('AgentWidgetClient - nested flow-as-tool (parentToolCallId)', () => {
|
|
|
2284
2030
|
|
|
2285
2031
|
it('does not tag top-level flow text with a parentToolId', async () => {
|
|
2286
2032
|
const events: AgentWidgetEvent[] = [];
|
|
2287
|
-
global.fetch =
|
|
2288
|
-
sseEvent(
|
|
2289
|
-
sseEvent(
|
|
2290
|
-
sseEvent(
|
|
2291
|
-
sseEvent(
|
|
2292
|
-
sseEvent(
|
|
2033
|
+
global.fetch = createRawStreamFetch([
|
|
2034
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
2035
|
+
sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
|
|
2036
|
+
sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Just top-level." }),
|
|
2037
|
+
sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Just top-level." }),
|
|
2038
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 4, kind: "flow", success: true })
|
|
2293
2039
|
]);
|
|
2294
2040
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
2295
2041
|
await client.dispatch(
|
|
@@ -2306,14 +2052,14 @@ describe('AgentWidgetClient - nested flow-as-tool (parentToolCallId)', () => {
|
|
|
2306
2052
|
|
|
2307
2053
|
it('routes nested flow reasoning into the parent tool row', async () => {
|
|
2308
2054
|
const events: AgentWidgetEvent[] = [];
|
|
2309
|
-
global.fetch =
|
|
2310
|
-
sseEvent(
|
|
2311
|
-
sseEvent(
|
|
2312
|
-
sseEvent(
|
|
2313
|
-
sseEvent(
|
|
2314
|
-
sseEvent(
|
|
2315
|
-
sseEvent(
|
|
2316
|
-
sseEvent(
|
|
2055
|
+
global.fetch = createRawStreamFetch([
|
|
2056
|
+
sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
|
|
2057
|
+
sseEvent("tool_start", { executionId: "exec-test", seq: 1, toolCallId: "tool_nested_2", toolName: "run_subflow", toolType: "flow", startedAt: new Date().toISOString() }),
|
|
2058
|
+
sseEvent("reasoning_start", { executionId: "exec-test", seq: 2, id: "reason_1", parentToolCallId: "tool_nested_2" }),
|
|
2059
|
+
sseEvent("reasoning_delta", { executionId: "exec-test", seq: 3, id: "reason_1", delta: "thinking nested" }),
|
|
2060
|
+
sseEvent("reasoning_complete", { executionId: "exec-test", seq: 4, id: "reason_1", text: "thinking nested" }),
|
|
2061
|
+
sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tool_nested_2", toolName: "run_subflow", success: true, executionTime: 50 }),
|
|
2062
|
+
sseEvent("execution_complete", { executionId: "exec-test", seq: 6, kind: "flow", success: true })
|
|
2317
2063
|
]);
|
|
2318
2064
|
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
2319
2065
|
await client.dispatch(
|
|
@@ -2409,7 +2155,7 @@ describe('preferFinalStructuredContent', () => {
|
|
|
2409
2155
|
});
|
|
2410
2156
|
|
|
2411
2157
|
// ============================================================================
|
|
2412
|
-
// stopReason wiring (
|
|
2158
|
+
// stopReason wiring (turn_complete / step_complete)
|
|
2413
2159
|
// ============================================================================
|
|
2414
2160
|
|
|
2415
2161
|
describe('AgentWidgetClient - stopReason propagation', () => {
|
|
@@ -2423,7 +2169,7 @@ describe('AgentWidgetClient - stopReason propagation', () => {
|
|
|
2423
2169
|
if (stopReason) data.stopReason = stopReason;
|
|
2424
2170
|
return [
|
|
2425
2171
|
`data: ${JSON.stringify(data)}\n\n`,
|
|
2426
|
-
`data: ${JSON.stringify({ type: '
|
|
2172
|
+
`data: ${JSON.stringify({ type: 'execution_complete', success: true })}\n\n`,
|
|
2427
2173
|
];
|
|
2428
2174
|
};
|
|
2429
2175
|
|
|
@@ -2487,7 +2233,7 @@ describe('AgentWidgetClient - stopReason propagation', () => {
|
|
|
2487
2233
|
result: { response: '' },
|
|
2488
2234
|
stopReason: 'max_tool_calls',
|
|
2489
2235
|
})}\n\n`,
|
|
2490
|
-
`data: ${JSON.stringify({ type: '
|
|
2236
|
+
`data: ${JSON.stringify({ type: 'execution_complete', success: true })}\n\n`,
|
|
2491
2237
|
]);
|
|
2492
2238
|
const final = collectFinalAssistant(events);
|
|
2493
2239
|
expect(final).not.toBeNull();
|
|
@@ -2495,41 +2241,18 @@ describe('AgentWidgetClient - stopReason propagation', () => {
|
|
|
2495
2241
|
expect(final!.stopReason).toBe('max_tool_calls');
|
|
2496
2242
|
});
|
|
2497
2243
|
|
|
2498
|
-
it('
|
|
2499
|
-
// Build an agent-mode stream that emits both events.
|
|
2244
|
+
it('turn_complete.stopReason overrides any earlier step_complete value (agent-loop path)', async () => {
|
|
2245
|
+
// Build an agent-mode stream that emits both events. turn_complete
|
|
2500
2246
|
// arrives last; its stopReason should win.
|
|
2501
2247
|
const execId = 'exec_stopreason';
|
|
2502
|
-
global.fetch =
|
|
2503
|
-
sseEvent(
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
}),
|
|
2507
|
-
sseEvent(
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
}),
|
|
2511
|
-
sseEvent('agent_turn_start', {
|
|
2512
|
-
executionId: execId, iteration: 1, turnIndex: 0,
|
|
2513
|
-
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
2514
|
-
}),
|
|
2515
|
-
sseEvent('agent_turn_delta', {
|
|
2516
|
-
executionId: execId, iteration: 1, delta: 'partial answer',
|
|
2517
|
-
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2518
|
-
}),
|
|
2519
|
-
sseEvent('agent_turn_complete', {
|
|
2520
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
2521
|
-
turnId: 'turn_1', completedAt: new Date().toISOString(),
|
|
2522
|
-
stopReason: 'max_tool_calls', seq: 5,
|
|
2523
|
-
}),
|
|
2524
|
-
sseEvent('agent_iteration_complete', {
|
|
2525
|
-
executionId: execId, iteration: 1, toolCallsMade: 0,
|
|
2526
|
-
stopConditionMet: true, completedAt: new Date().toISOString(), seq: 6,
|
|
2527
|
-
}),
|
|
2528
|
-
sseEvent('agent_complete', {
|
|
2529
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
2530
|
-
iterations: 1, stopReason: 'max_iterations',
|
|
2531
|
-
completedAt: new Date().toISOString(), seq: 7,
|
|
2532
|
-
}),
|
|
2248
|
+
global.fetch = createRawStreamFetch([
|
|
2249
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2250
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
2251
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
2252
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "partial answer" }),
|
|
2253
|
+
sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "partial answer" }),
|
|
2254
|
+
sseEvent("turn_complete", { executionId: execId, seq: 5, id: "turn_1", iteration: 1, role: "assistant", stopReason: "max_tool_calls", completedAt: new Date().toISOString() }),
|
|
2255
|
+
sseEvent("execution_complete", { executionId: execId, seq: 6, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
2533
2256
|
]);
|
|
2534
2257
|
|
|
2535
2258
|
const client = new AgentWidgetClient({
|
|
@@ -2551,7 +2274,7 @@ describe('AgentWidgetClient - stopReason propagation', () => {
|
|
|
2551
2274
|
|
|
2552
2275
|
// ============================================================================
|
|
2553
2276
|
// Within-turn text/tool interleaving: assistant text bubbles must seal at
|
|
2554
|
-
// each
|
|
2277
|
+
// each tool_start so the chronological text→tool→text→tool sequence
|
|
2555
2278
|
// renders as distinct timeline entries instead of one merged bubble that
|
|
2556
2279
|
// appears below all the tool cards.
|
|
2557
2280
|
// ============================================================================
|
|
@@ -2568,65 +2291,22 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
|
|
|
2568
2291
|
return order.map((id) => byId.get(id)!);
|
|
2569
2292
|
};
|
|
2570
2293
|
|
|
2571
|
-
it('seals the assistant text bubble at each
|
|
2294
|
+
it('seals the assistant text bubble at each tool_start so subsequent text creates a new bubble', async () => {
|
|
2572
2295
|
const execId = 'exec_interleave';
|
|
2573
|
-
global.fetch =
|
|
2574
|
-
sseEvent(
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
}),
|
|
2578
|
-
sseEvent(
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
}),
|
|
2582
|
-
sseEvent(
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
}),
|
|
2586
|
-
sseEvent(
|
|
2587
|
-
executionId: execId, iteration: 1, delta: 'before tool 1',
|
|
2588
|
-
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2589
|
-
}),
|
|
2590
|
-
sseEvent('agent_tool_start', {
|
|
2591
|
-
executionId: execId, iteration: 1,
|
|
2592
|
-
toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
|
|
2593
|
-
}),
|
|
2594
|
-
sseEvent('agent_tool_complete', {
|
|
2595
|
-
executionId: execId, iteration: 1,
|
|
2596
|
-
toolCallId: 'call_1', toolName: 'search', success: true,
|
|
2597
|
-
executionTime: 10, seq: 6,
|
|
2598
|
-
}),
|
|
2599
|
-
sseEvent('agent_turn_delta', {
|
|
2600
|
-
executionId: execId, iteration: 1, delta: 'between tools',
|
|
2601
|
-
contentType: 'text', turnId: 'turn_1', seq: 7,
|
|
2602
|
-
}),
|
|
2603
|
-
sseEvent('agent_tool_start', {
|
|
2604
|
-
executionId: execId, iteration: 1,
|
|
2605
|
-
toolCallId: 'call_2', toolName: 'fetch', toolType: 'builtin', seq: 8,
|
|
2606
|
-
}),
|
|
2607
|
-
sseEvent('agent_tool_complete', {
|
|
2608
|
-
executionId: execId, iteration: 1,
|
|
2609
|
-
toolCallId: 'call_2', toolName: 'fetch', success: true,
|
|
2610
|
-
executionTime: 10, seq: 9,
|
|
2611
|
-
}),
|
|
2612
|
-
sseEvent('agent_turn_delta', {
|
|
2613
|
-
executionId: execId, iteration: 1, delta: 'after tool 2',
|
|
2614
|
-
contentType: 'text', turnId: 'turn_1', seq: 10,
|
|
2615
|
-
}),
|
|
2616
|
-
sseEvent('agent_turn_complete', {
|
|
2617
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
2618
|
-
turnId: 'turn_1', completedAt: new Date().toISOString(),
|
|
2619
|
-
stopReason: 'end_turn', seq: 11,
|
|
2620
|
-
}),
|
|
2621
|
-
sseEvent('agent_iteration_complete', {
|
|
2622
|
-
executionId: execId, iteration: 1, toolCallsMade: 2,
|
|
2623
|
-
stopConditionMet: true, completedAt: new Date().toISOString(), seq: 12,
|
|
2624
|
-
}),
|
|
2625
|
-
sseEvent('agent_complete', {
|
|
2626
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
2627
|
-
iterations: 1, stopReason: 'complete',
|
|
2628
|
-
completedAt: new Date().toISOString(), seq: 13,
|
|
2629
|
-
}),
|
|
2296
|
+
global.fetch = createRawStreamFetch([
|
|
2297
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2298
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
2299
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
2300
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "before tool 1" }),
|
|
2301
|
+
sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "call_1", toolName: "search", toolType: "builtin", iteration: 1 }),
|
|
2302
|
+
sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "call_1", toolName: "search", success: true, executionTime: 10, iteration: 1 }),
|
|
2303
|
+
sseEvent("text_delta", { executionId: execId, seq: 6, id: "text_1", delta: "between tools" }),
|
|
2304
|
+
sseEvent("tool_start", { executionId: execId, seq: 7, toolCallId: "call_2", toolName: "fetch", toolType: "builtin", iteration: 1 }),
|
|
2305
|
+
sseEvent("tool_complete", { executionId: execId, seq: 8, toolCallId: "call_2", toolName: "fetch", success: true, executionTime: 10, iteration: 1 }),
|
|
2306
|
+
sseEvent("text_delta", { executionId: execId, seq: 9, id: "text_1", delta: "after tool 2" }),
|
|
2307
|
+
sseEvent("text_complete", { executionId: execId, seq: 10, id: "text_1", text: "before tool 1between toolsafter tool 2" }),
|
|
2308
|
+
sseEvent("turn_complete", { executionId: execId, seq: 11, id: "turn_1", iteration: 1, role: "assistant", stopReason: "end_turn", completedAt: new Date().toISOString() }),
|
|
2309
|
+
sseEvent("execution_complete", { executionId: execId, seq: 12, kind: "agent", success: true, iterations: 1, stopReason: "complete", completedAt: new Date().toISOString() })
|
|
2630
2310
|
]);
|
|
2631
2311
|
|
|
2632
2312
|
const client = new AgentWidgetClient({
|
|
@@ -2652,52 +2332,19 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
|
|
|
2652
2332
|
expect(new Set(assistants.map((m) => m.id)).size).toBe(3);
|
|
2653
2333
|
});
|
|
2654
2334
|
|
|
2655
|
-
it('attaches
|
|
2335
|
+
it('attaches turn_complete.stopReason to the final assistant text segment when the turn ends with text', async () => {
|
|
2656
2336
|
const execId = 'exec_stopreason_tail_text';
|
|
2657
|
-
global.fetch =
|
|
2658
|
-
sseEvent(
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
}),
|
|
2662
|
-
sseEvent(
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
}),
|
|
2666
|
-
sseEvent(
|
|
2667
|
-
|
|
2668
|
-
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
2669
|
-
}),
|
|
2670
|
-
sseEvent('agent_turn_delta', {
|
|
2671
|
-
executionId: execId, iteration: 1, delta: 'first segment',
|
|
2672
|
-
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2673
|
-
}),
|
|
2674
|
-
sseEvent('agent_tool_start', {
|
|
2675
|
-
executionId: execId, iteration: 1,
|
|
2676
|
-
toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
|
|
2677
|
-
}),
|
|
2678
|
-
sseEvent('agent_tool_complete', {
|
|
2679
|
-
executionId: execId, iteration: 1,
|
|
2680
|
-
toolCallId: 'call_1', toolName: 'search', success: true,
|
|
2681
|
-
executionTime: 10, seq: 6,
|
|
2682
|
-
}),
|
|
2683
|
-
sseEvent('agent_turn_delta', {
|
|
2684
|
-
executionId: execId, iteration: 1, delta: 'final segment',
|
|
2685
|
-
contentType: 'text', turnId: 'turn_1', seq: 7,
|
|
2686
|
-
}),
|
|
2687
|
-
sseEvent('agent_turn_complete', {
|
|
2688
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
2689
|
-
turnId: 'turn_1', completedAt: new Date().toISOString(),
|
|
2690
|
-
stopReason: 'length', seq: 8,
|
|
2691
|
-
}),
|
|
2692
|
-
sseEvent('agent_iteration_complete', {
|
|
2693
|
-
executionId: execId, iteration: 1, toolCallsMade: 1,
|
|
2694
|
-
stopConditionMet: true, completedAt: new Date().toISOString(), seq: 9,
|
|
2695
|
-
}),
|
|
2696
|
-
sseEvent('agent_complete', {
|
|
2697
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
2698
|
-
iterations: 1, stopReason: 'complete',
|
|
2699
|
-
completedAt: new Date().toISOString(), seq: 10,
|
|
2700
|
-
}),
|
|
2337
|
+
global.fetch = createRawStreamFetch([
|
|
2338
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2339
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
2340
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
2341
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "first segment" }),
|
|
2342
|
+
sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "call_1", toolName: "search", toolType: "builtin", iteration: 1 }),
|
|
2343
|
+
sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "call_1", toolName: "search", success: true, executionTime: 10, iteration: 1 }),
|
|
2344
|
+
sseEvent("text_delta", { executionId: execId, seq: 6, id: "text_1", delta: "final segment" }),
|
|
2345
|
+
sseEvent("text_complete", { executionId: execId, seq: 7, id: "text_1", text: "first segmentfinal segment" }),
|
|
2346
|
+
sseEvent("turn_complete", { executionId: execId, seq: 8, id: "turn_1", iteration: 1, role: "assistant", stopReason: "length", completedAt: new Date().toISOString() }),
|
|
2347
|
+
sseEvent("execution_complete", { executionId: execId, seq: 9, kind: "agent", success: true, iterations: 1, stopReason: "complete", completedAt: new Date().toISOString() })
|
|
2701
2348
|
]);
|
|
2702
2349
|
|
|
2703
2350
|
const client = new AgentWidgetClient({
|
|
@@ -2720,48 +2367,18 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
|
|
|
2720
2367
|
expect(assistants[1].stopReason).toBe('length');
|
|
2721
2368
|
});
|
|
2722
2369
|
|
|
2723
|
-
it('attaches
|
|
2370
|
+
it('attaches turn_complete.stopReason to the preceding text segment when the turn ends with a tool call', async () => {
|
|
2724
2371
|
const execId = 'exec_stopreason_tail_tool';
|
|
2725
|
-
global.fetch =
|
|
2726
|
-
sseEvent(
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
}),
|
|
2730
|
-
sseEvent(
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
}),
|
|
2734
|
-
sseEvent(
|
|
2735
|
-
executionId: execId, iteration: 1, turnIndex: 0,
|
|
2736
|
-
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
2737
|
-
}),
|
|
2738
|
-
sseEvent('agent_turn_delta', {
|
|
2739
|
-
executionId: execId, iteration: 1, delta: 'about to call tool',
|
|
2740
|
-
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2741
|
-
}),
|
|
2742
|
-
sseEvent('agent_tool_start', {
|
|
2743
|
-
executionId: execId, iteration: 1,
|
|
2744
|
-
toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
|
|
2745
|
-
}),
|
|
2746
|
-
sseEvent('agent_tool_complete', {
|
|
2747
|
-
executionId: execId, iteration: 1,
|
|
2748
|
-
toolCallId: 'call_1', toolName: 'search', success: true,
|
|
2749
|
-
executionTime: 10, seq: 6,
|
|
2750
|
-
}),
|
|
2751
|
-
sseEvent('agent_turn_complete', {
|
|
2752
|
-
executionId: execId, iteration: 1, role: 'assistant',
|
|
2753
|
-
turnId: 'turn_1', completedAt: new Date().toISOString(),
|
|
2754
|
-
stopReason: 'max_tool_calls', seq: 7,
|
|
2755
|
-
}),
|
|
2756
|
-
sseEvent('agent_iteration_complete', {
|
|
2757
|
-
executionId: execId, iteration: 1, toolCallsMade: 1,
|
|
2758
|
-
stopConditionMet: true, completedAt: new Date().toISOString(), seq: 8,
|
|
2759
|
-
}),
|
|
2760
|
-
sseEvent('agent_complete', {
|
|
2761
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
2762
|
-
iterations: 1, stopReason: 'max_tool_calls',
|
|
2763
|
-
completedAt: new Date().toISOString(), seq: 9,
|
|
2764
|
-
}),
|
|
2372
|
+
global.fetch = createRawStreamFetch([
|
|
2373
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2374
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
2375
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
2376
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "about to call tool" }),
|
|
2377
|
+
sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "call_1", toolName: "search", toolType: "builtin", iteration: 1 }),
|
|
2378
|
+
sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "call_1", toolName: "search", success: true, executionTime: 10, iteration: 1 }),
|
|
2379
|
+
sseEvent("text_complete", { executionId: execId, seq: 6, id: "text_1", text: "about to call tool" }),
|
|
2380
|
+
sseEvent("turn_complete", { executionId: execId, seq: 7, id: "turn_1", iteration: 1, role: "assistant", stopReason: "max_tool_calls", completedAt: new Date().toISOString() }),
|
|
2381
|
+
sseEvent("execution_complete", { executionId: execId, seq: 8, kind: "agent", success: true, iterations: 1, stopReason: "max_tool_calls", completedAt: new Date().toISOString() })
|
|
2765
2382
|
]);
|
|
2766
2383
|
|
|
2767
2384
|
const client = new AgentWidgetClient({
|
|
@@ -2787,9 +2404,8 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
|
|
|
2787
2404
|
// step_await (LOCAL tool pause) + resumeFlow
|
|
2788
2405
|
// ============================================================================
|
|
2789
2406
|
|
|
2790
|
-
describe('AgentWidgetClient:
|
|
2791
|
-
//
|
|
2792
|
-
// into the neutral `await` event the native handler consumes.
|
|
2407
|
+
describe('AgentWidgetClient: unified local-tool await parsing', () => {
|
|
2408
|
+
// Both dispatch kinds use the unified await event for local tools.
|
|
2793
2409
|
const buildAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
|
|
2794
2410
|
const encoder = new TextEncoder();
|
|
2795
2411
|
const body = `event: await\ndata: ${JSON.stringify({ type: 'await', ...payload })}\n\n`;
|
|
@@ -2800,8 +2416,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
|
|
|
2800
2416
|
},
|
|
2801
2417
|
});
|
|
2802
2418
|
};
|
|
2803
|
-
//
|
|
2804
|
-
// below until Phase C removes the legacy approval branch from the handler.
|
|
2419
|
+
// Unsupported legacy events must not create approval or local-tool bubbles.
|
|
2805
2420
|
const buildStepAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
|
|
2806
2421
|
const encoder = new TextEncoder();
|
|
2807
2422
|
const body = `event: step_await\ndata: ${JSON.stringify({ type: 'step_await', ...payload })}\n\n`;
|
|
@@ -2865,7 +2480,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
|
|
|
2865
2480
|
toolCallId: 'tc_webmcp_1',
|
|
2866
2481
|
toolName: 'webmcp:get_product_by_url',
|
|
2867
2482
|
executionId: 'exec_abc',
|
|
2868
|
-
|
|
2483
|
+
awaitedAt: new Date(1234).toISOString(),
|
|
2869
2484
|
parameters: { path: '/jade/' },
|
|
2870
2485
|
}),
|
|
2871
2486
|
});
|
|
@@ -2893,7 +2508,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
|
|
|
2893
2508
|
expect(toolMsg!.agentMetadata?.awaitingLocalTool).toBe(true);
|
|
2894
2509
|
});
|
|
2895
2510
|
|
|
2896
|
-
it('ignores
|
|
2511
|
+
it('ignores the obsolete step_await approval event', async () => {
|
|
2897
2512
|
global.fetch = vi.fn().mockResolvedValue({
|
|
2898
2513
|
ok: true,
|
|
2899
2514
|
body: buildStepAwaitStream({
|
|
@@ -2917,17 +2532,18 @@ describe('AgentWidgetClient: step_await parsing', () => {
|
|
|
2917
2532
|
.map((e) => (e as { message: AgentWidgetMessage }).message)
|
|
2918
2533
|
.find((m) => m.agentMetadata?.awaitingLocalTool);
|
|
2919
2534
|
expect(toolMsg).toBeUndefined();
|
|
2535
|
+
expect(events.some((event) => event.type === "message" && event.message.variant === "approval")).toBe(false);
|
|
2920
2536
|
});
|
|
2921
2537
|
});
|
|
2922
2538
|
|
|
2923
2539
|
// ============================================================================
|
|
2924
|
-
//
|
|
2925
|
-
//
|
|
2540
|
+
// Agent-dispatch LOCAL tool pauses use await, with a bare name + origin
|
|
2541
|
+
// instead of a webmcp:
|
|
2926
2542
|
// prefix + awaitReason.
|
|
2927
2543
|
// ============================================================================
|
|
2928
2544
|
|
|
2929
|
-
describe('AgentWidgetClient:
|
|
2930
|
-
//
|
|
2545
|
+
describe('AgentWidgetClient: agent await parsing', () => {
|
|
2546
|
+
// Agent and flow dispatch share the same `await`
|
|
2931
2547
|
// event; the dispatch origin survives as the `origin` field on the payload.
|
|
2932
2548
|
const buildAgentAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
|
|
2933
2549
|
const encoder = new TextEncoder();
|
|
@@ -2940,7 +2556,7 @@ describe('AgentWidgetClient: agent_await parsing', () => {
|
|
|
2940
2556
|
});
|
|
2941
2557
|
};
|
|
2942
2558
|
|
|
2943
|
-
it('normalizes a WebMCP
|
|
2559
|
+
it('normalizes a WebMCP await (origin "webmcp") to a running webmcp: tool message', async () => {
|
|
2944
2560
|
global.fetch = vi.fn().mockResolvedValue({
|
|
2945
2561
|
ok: true,
|
|
2946
2562
|
body: buildAgentAwaitStream({
|
|
@@ -2979,7 +2595,7 @@ describe('AgentWidgetClient: agent_await parsing', () => {
|
|
|
2979
2595
|
expect(toolMsg!.agentMetadata?.webMcpToolCallId).toBe('tc_webmcp_1');
|
|
2980
2596
|
});
|
|
2981
2597
|
|
|
2982
|
-
it('emits a complete tool message for a non-WebMCP
|
|
2598
|
+
it('emits a complete tool message for a non-WebMCP await (origin "sdk")', async () => {
|
|
2983
2599
|
global.fetch = vi.fn().mockResolvedValue({
|
|
2984
2600
|
ok: true,
|
|
2985
2601
|
body: buildAgentAwaitStream({
|
|
@@ -3095,7 +2711,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3095
2711
|
(client as unknown as { readVisitorToken: () => Promise<string> }).readVisitorToken =
|
|
3096
2712
|
async () => 'cvt_resume';
|
|
3097
2713
|
|
|
3098
|
-
await client.resumeFlow('exec_xyz', { toolu_A: { ok: true } }, { after: '18' });
|
|
2714
|
+
await client.resumeFlow('exec_xyz', { toolu_A: { ok: true } }, { after: '18.2' });
|
|
3099
2715
|
|
|
3100
2716
|
// Session-authed sibling of /v1/client/chat: no Bearer key, sessionId in body.
|
|
3101
2717
|
expect(capturedUrl).toBe('https://api.runtype.com/v1/client/resume');
|
|
@@ -3104,7 +2720,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3104
2720
|
toolOutputs: { toolu_A: { ok: true } },
|
|
3105
2721
|
streamResponse: true,
|
|
3106
2722
|
sessionId: 'cs_123',
|
|
3107
|
-
after: '18',
|
|
2723
|
+
after: '18.2',
|
|
3108
2724
|
});
|
|
3109
2725
|
expect(capturedHeaders!['Authorization']).toBeUndefined();
|
|
3110
2726
|
expect(capturedHeaders!['X-Visitor-Token']).toBe('cvt_resume');
|
|
@@ -3164,10 +2780,10 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3164
2780
|
});
|
|
3165
2781
|
|
|
3166
2782
|
// ============================================================================
|
|
3167
|
-
//
|
|
2783
|
+
// media Event Handling
|
|
3168
2784
|
// ============================================================================
|
|
3169
2785
|
|
|
3170
|
-
describe('AgentWidgetClient -
|
|
2786
|
+
describe('AgentWidgetClient - media events', () => {
|
|
3171
2787
|
const collectMediaMessages = (events: AgentWidgetEvent[]): AgentWidgetMessage[] => {
|
|
3172
2788
|
const byId = new Map<string, AgentWidgetMessage>();
|
|
3173
2789
|
for (const event of events) {
|
|
@@ -3180,36 +2796,14 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3180
2796
|
|
|
3181
2797
|
it('renders a base64 image (AI SDK v6 type:"media") as a synthetic message', async () => {
|
|
3182
2798
|
const execId = 'exec_media_image';
|
|
3183
|
-
global.fetch =
|
|
3184
|
-
sseEvent(
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
}),
|
|
3188
|
-
sseEvent(
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
}),
|
|
3192
|
-
sseEvent('agent_tool_start', {
|
|
3193
|
-
executionId: execId, iteration: 1, toolCallId: 'tc_shot',
|
|
3194
|
-
toolName: 'browser:screenshot', startedAt: new Date().toISOString(), seq: 3,
|
|
3195
|
-
}),
|
|
3196
|
-
sseEvent('agent_tool_complete', {
|
|
3197
|
-
executionId: execId, iteration: 1, toolCallId: 'tc_shot',
|
|
3198
|
-
toolName: 'browser:screenshot', completedAt: new Date().toISOString(), seq: 4,
|
|
3199
|
-
}),
|
|
3200
|
-
sseEvent('agent_media', {
|
|
3201
|
-
executionId: execId, iteration: 1, toolCallId: 'tc_shot',
|
|
3202
|
-
toolName: 'browser:screenshot',
|
|
3203
|
-
media: [
|
|
3204
|
-
{ type: 'media', data: 'iVBORw==', mediaType: 'image/png' },
|
|
3205
|
-
],
|
|
3206
|
-
seq: 5,
|
|
3207
|
-
}),
|
|
3208
|
-
sseEvent('agent_complete', {
|
|
3209
|
-
executionId: execId, agentId: 'virtual', success: true,
|
|
3210
|
-
iterations: 1, stopReason: 'max_iterations',
|
|
3211
|
-
completedAt: new Date().toISOString(), seq: 6,
|
|
3212
|
-
}),
|
|
2799
|
+
global.fetch = createRawStreamFetch([
|
|
2800
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2801
|
+
sseEvent("tool_start", { executionId: execId, seq: 1, toolCallId: "tc_shot", toolName: "browser:screenshot", toolType: "builtin", iteration: 1 }),
|
|
2802
|
+
sseEvent("tool_complete", { executionId: execId, seq: 2, toolCallId: "tc_shot", toolName: "browser:screenshot", success: true, iteration: 1 }),
|
|
2803
|
+
sseEvent("media_start", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_shot" }),
|
|
2804
|
+
sseEvent("media_delta", { executionId: execId, seq: 4, id: "media_1", delta: "iVBORw==" }),
|
|
2805
|
+
sseEvent("media_complete", { executionId: execId, seq: 5, id: "media_1", mediaType: "image/png", data: "iVBORw==", toolCallId: "tc_shot" }),
|
|
2806
|
+
sseEvent("execution_complete", { executionId: execId, seq: 6, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3213
2807
|
]);
|
|
3214
2808
|
|
|
3215
2809
|
const client = new AgentWidgetClient({
|
|
@@ -3242,15 +2836,12 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3242
2836
|
|
|
3243
2837
|
it('renders a hosted image (AI SDK v3/v4 type:"image-url") as a synthetic message', async () => {
|
|
3244
2838
|
const execId = 'exec_media_url';
|
|
3245
|
-
global.fetch =
|
|
3246
|
-
sseEvent(
|
|
3247
|
-
sseEvent(
|
|
3248
|
-
sseEvent(
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
seq: 3,
|
|
3252
|
-
}),
|
|
3253
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
2839
|
+
global.fetch = createRawStreamFetch([
|
|
2840
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2841
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image", role: "assistant", toolCallId: "tc_dalle" }),
|
|
2842
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "https://r2.example.com/img.png" }),
|
|
2843
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image", url: "https://r2.example.com/img.png", toolCallId: "tc_dalle" }),
|
|
2844
|
+
sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3254
2845
|
]);
|
|
3255
2846
|
|
|
3256
2847
|
const client = new AgentWidgetClient({
|
|
@@ -3275,15 +2866,12 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3275
2866
|
|
|
3276
2867
|
it('preserves mediaType on image-url parts when provided', async () => {
|
|
3277
2868
|
const execId = 'exec_media_url_typed';
|
|
3278
|
-
global.fetch =
|
|
3279
|
-
sseEvent(
|
|
3280
|
-
sseEvent(
|
|
3281
|
-
sseEvent(
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
seq: 3,
|
|
3285
|
-
}),
|
|
3286
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
2869
|
+
global.fetch = createRawStreamFetch([
|
|
2870
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2871
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_dalle" }),
|
|
2872
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "https://r2.example.com/img.png" }),
|
|
2873
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", url: "https://r2.example.com/img.png", toolCallId: "tc_dalle" }),
|
|
2874
|
+
sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3287
2875
|
]);
|
|
3288
2876
|
|
|
3289
2877
|
const client = new AgentWidgetClient({
|
|
@@ -3306,15 +2894,12 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3306
2894
|
|
|
3307
2895
|
it('renders a base64 audio part with mediaType', async () => {
|
|
3308
2896
|
const execId = 'exec_media_audio';
|
|
3309
|
-
global.fetch =
|
|
3310
|
-
sseEvent(
|
|
3311
|
-
sseEvent(
|
|
3312
|
-
sseEvent(
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
seq: 3,
|
|
3316
|
-
}),
|
|
3317
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
2897
|
+
global.fetch = createRawStreamFetch([
|
|
2898
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2899
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "audio/mpeg", role: "assistant", toolCallId: "tc_tts" }),
|
|
2900
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "AAAA" }),
|
|
2901
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "audio/mpeg", data: "AAAA", toolCallId: "tc_tts" }),
|
|
2902
|
+
sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3318
2903
|
]);
|
|
3319
2904
|
|
|
3320
2905
|
const client = new AgentWidgetClient({
|
|
@@ -3339,19 +2924,18 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3339
2924
|
|
|
3340
2925
|
it('routes file-url parts by mediaType (audio/video/file)', async () => {
|
|
3341
2926
|
const execId = 'exec_media_file_url';
|
|
3342
|
-
global.fetch =
|
|
3343
|
-
sseEvent(
|
|
3344
|
-
sseEvent(
|
|
3345
|
-
sseEvent(
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
})
|
|
3354
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
2927
|
+
global.fetch = createRawStreamFetch([
|
|
2928
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2929
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "audio/mpeg", role: "assistant", toolCallId: "tc_files" }),
|
|
2930
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "https://example.com/a.mp3" }),
|
|
2931
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "audio/mpeg", url: "https://example.com/a.mp3", toolCallId: "tc_files" }),
|
|
2932
|
+
sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "video/mp4", role: "assistant", toolCallId: "tc_files" }),
|
|
2933
|
+
sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "https://example.com/v.mp4" }),
|
|
2934
|
+
sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "video/mp4", url: "https://example.com/v.mp4", toolCallId: "tc_files" }),
|
|
2935
|
+
sseEvent("media_start", { executionId: execId, seq: 7, id: "media_3", mediaType: "application/pdf", role: "assistant", toolCallId: "tc_files" }),
|
|
2936
|
+
sseEvent("media_delta", { executionId: execId, seq: 8, id: "media_3", delta: "https://example.com/r.pdf" }),
|
|
2937
|
+
sseEvent("media_complete", { executionId: execId, seq: 9, id: "media_3", mediaType: "application/pdf", url: "https://example.com/r.pdf", toolCallId: "tc_files" }),
|
|
2938
|
+
sseEvent("execution_complete", { executionId: execId, seq: 10, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3355
2939
|
]);
|
|
3356
2940
|
|
|
3357
2941
|
const client = new AgentWidgetClient({
|
|
@@ -3383,19 +2967,18 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3383
2967
|
|
|
3384
2968
|
it('renders each mixed media part as its own synthetic message', async () => {
|
|
3385
2969
|
const execId = 'exec_media_mixed';
|
|
3386
|
-
global.fetch =
|
|
3387
|
-
sseEvent(
|
|
3388
|
-
sseEvent(
|
|
3389
|
-
sseEvent(
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
})
|
|
3398
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
2970
|
+
global.fetch = createRawStreamFetch([
|
|
2971
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
2972
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_mix" }),
|
|
2973
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "IMG" }),
|
|
2974
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", data: "IMG", toolCallId: "tc_mix" }),
|
|
2975
|
+
sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "image", role: "assistant", toolCallId: "tc_mix" }),
|
|
2976
|
+
sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "https://example.com/dalle.png" }),
|
|
2977
|
+
sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "image", url: "https://example.com/dalle.png", toolCallId: "tc_mix" }),
|
|
2978
|
+
sseEvent("media_start", { executionId: execId, seq: 7, id: "media_3", mediaType: "application/pdf", role: "assistant", toolCallId: "tc_mix" }),
|
|
2979
|
+
sseEvent("media_delta", { executionId: execId, seq: 8, id: "media_3", delta: "FILE" }),
|
|
2980
|
+
sseEvent("media_complete", { executionId: execId, seq: 9, id: "media_3", mediaType: "application/pdf", data: "FILE", toolCallId: "tc_mix" }),
|
|
2981
|
+
sseEvent("execution_complete", { executionId: execId, seq: 10, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3399
2982
|
]);
|
|
3400
2983
|
|
|
3401
2984
|
const client = new AgentWidgetClient({
|
|
@@ -3422,21 +3005,22 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3422
3005
|
|
|
3423
3006
|
it('inserts media between tool bubble and the next text turn', async () => {
|
|
3424
3007
|
const execId = 'exec_media_order';
|
|
3425
|
-
global.fetch =
|
|
3426
|
-
sseEvent(
|
|
3427
|
-
sseEvent(
|
|
3428
|
-
sseEvent(
|
|
3429
|
-
sseEvent(
|
|
3430
|
-
sseEvent(
|
|
3431
|
-
sseEvent(
|
|
3432
|
-
sseEvent(
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
}),
|
|
3437
|
-
sseEvent(
|
|
3438
|
-
sseEvent(
|
|
3439
|
-
sseEvent(
|
|
3008
|
+
global.fetch = createRawStreamFetch([
|
|
3009
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
3010
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
3011
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
3012
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Calling tool..." }),
|
|
3013
|
+
sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "tc_1", toolName: "browser:screenshot", toolType: "builtin", iteration: 1 }),
|
|
3014
|
+
sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "tc_1", toolName: "browser:screenshot", success: true, iteration: 1 }),
|
|
3015
|
+
sseEvent("media_start", { executionId: execId, seq: 6, id: "media_2", mediaType: "image/png", role: "assistant", toolCallId: "tc_1" }),
|
|
3016
|
+
sseEvent("media_delta", { executionId: execId, seq: 7, id: "media_2", delta: "PNG" }),
|
|
3017
|
+
sseEvent("media_complete", { executionId: execId, seq: 8, id: "media_2", mediaType: "image/png", data: "PNG", toolCallId: "tc_1" }),
|
|
3018
|
+
sseEvent("text_complete", { executionId: execId, seq: 9, id: "text_1", text: "Calling tool..." }),
|
|
3019
|
+
sseEvent("turn_start", { executionId: execId, seq: 10, id: "turn_2", iteration: 1, turnIndex: 1, role: "assistant" }),
|
|
3020
|
+
sseEvent("text_start", { executionId: execId, seq: 11, id: "text_3", role: "assistant" }),
|
|
3021
|
+
sseEvent("text_delta", { executionId: execId, seq: 12, id: "text_3", delta: "Done!" }),
|
|
3022
|
+
sseEvent("text_complete", { executionId: execId, seq: 13, id: "text_3", text: "Done!" }),
|
|
3023
|
+
sseEvent("execution_complete", { executionId: execId, seq: 14, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3440
3024
|
]);
|
|
3441
3025
|
|
|
3442
3026
|
const client = new AgentWidgetClient({
|
|
@@ -3474,19 +3058,15 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3474
3058
|
|
|
3475
3059
|
it('skips malformed media parts that have neither data nor url', async () => {
|
|
3476
3060
|
const execId = 'exec_media_empty';
|
|
3477
|
-
global.fetch =
|
|
3478
|
-
sseEvent(
|
|
3479
|
-
sseEvent(
|
|
3480
|
-
sseEvent(
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
],
|
|
3487
|
-
seq: 3,
|
|
3488
|
-
}),
|
|
3489
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
3061
|
+
global.fetch = createRawStreamFetch([
|
|
3062
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
3063
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_x" }),
|
|
3064
|
+
sseEvent("media_complete", { executionId: execId, seq: 2, id: "media_1", mediaType: "image/png", toolCallId: "tc_x" }),
|
|
3065
|
+
sseEvent("media_start", { executionId: execId, seq: 3, id: "media_2", mediaType: "image", role: "assistant", toolCallId: "tc_x" }),
|
|
3066
|
+
sseEvent("media_complete", { executionId: execId, seq: 4, id: "media_2", mediaType: "image", toolCallId: "tc_x" }),
|
|
3067
|
+
sseEvent("media_start", { executionId: execId, seq: 5, id: "media_3", mediaType: "application/octet-stream", role: "assistant", toolCallId: "tc_x" }),
|
|
3068
|
+
sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_3", mediaType: "application/octet-stream", toolCallId: "tc_x" }),
|
|
3069
|
+
sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3490
3070
|
]);
|
|
3491
3071
|
|
|
3492
3072
|
const client = new AgentWidgetClient({
|
|
@@ -3502,22 +3082,17 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3502
3082
|
expect(collectMediaMessages(events)).toHaveLength(0);
|
|
3503
3083
|
});
|
|
3504
3084
|
|
|
3505
|
-
it('produces unique ids for repeated
|
|
3085
|
+
it('produces unique ids for repeated media events on the same toolCallId', async () => {
|
|
3506
3086
|
const execId = 'exec_media_repeat';
|
|
3507
|
-
global.fetch =
|
|
3508
|
-
sseEvent(
|
|
3509
|
-
sseEvent(
|
|
3510
|
-
sseEvent(
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
}),
|
|
3515
|
-
sseEvent(
|
|
3516
|
-
executionId: execId, iteration: 1, toolCallId: 'tc_repeat', toolName: 'multi',
|
|
3517
|
-
media: [{ type: 'media', data: 'B', mediaType: 'image/png' }],
|
|
3518
|
-
seq: 4,
|
|
3519
|
-
}),
|
|
3520
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 5 }),
|
|
3087
|
+
global.fetch = createRawStreamFetch([
|
|
3088
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
3089
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_repeat" }),
|
|
3090
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "A" }),
|
|
3091
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", data: "A", toolCallId: "tc_repeat" }),
|
|
3092
|
+
sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "image/png", role: "assistant", toolCallId: "tc_repeat" }),
|
|
3093
|
+
sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "B" }),
|
|
3094
|
+
sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "image/png", data: "B", toolCallId: "tc_repeat" }),
|
|
3095
|
+
sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3521
3096
|
]);
|
|
3522
3097
|
|
|
3523
3098
|
const client = new AgentWidgetClient({
|
|
@@ -3545,20 +3120,18 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3545
3120
|
expect(first).toBeDefined();
|
|
3546
3121
|
});
|
|
3547
3122
|
|
|
3548
|
-
it('seals an in-flight assistant text bubble before splitting on
|
|
3123
|
+
it('seals an in-flight assistant text bubble before splitting on media', async () => {
|
|
3549
3124
|
const execId = 'exec_media_seal';
|
|
3550
|
-
global.fetch =
|
|
3551
|
-
sseEvent(
|
|
3552
|
-
sseEvent(
|
|
3553
|
-
sseEvent(
|
|
3554
|
-
sseEvent(
|
|
3555
|
-
|
|
3556
|
-
sseEvent(
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
}),
|
|
3561
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 6 }),
|
|
3125
|
+
global.fetch = createRawStreamFetch([
|
|
3126
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
3127
|
+
sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
|
|
3128
|
+
sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
|
|
3129
|
+
sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Streaming..." }),
|
|
3130
|
+
sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "image/png", role: "assistant", toolCallId: "tc_seal" }),
|
|
3131
|
+
sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "PNG" }),
|
|
3132
|
+
sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "image/png", data: "PNG", toolCallId: "tc_seal" }),
|
|
3133
|
+
sseEvent("text_complete", { executionId: execId, seq: 7, id: "text_1", text: "Streaming..." }),
|
|
3134
|
+
sseEvent("execution_complete", { executionId: execId, seq: 8, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3562
3135
|
]);
|
|
3563
3136
|
|
|
3564
3137
|
const client = new AgentWidgetClient({
|
|
@@ -3590,16 +3163,12 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3590
3163
|
|
|
3591
3164
|
it('routes audio parts case-insensitively (RFC 7231)', async () => {
|
|
3592
3165
|
const execId = 'exec_media_case';
|
|
3593
|
-
global.fetch =
|
|
3594
|
-
sseEvent(
|
|
3595
|
-
sseEvent(
|
|
3596
|
-
sseEvent(
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
media: [{ type: 'media', data: 'AAAA', mediaType: 'Audio/MPEG' }],
|
|
3600
|
-
seq: 3,
|
|
3601
|
-
}),
|
|
3602
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
3166
|
+
global.fetch = createRawStreamFetch([
|
|
3167
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
3168
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "Audio/MPEG", role: "assistant", toolCallId: "tc_case" }),
|
|
3169
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "AAAA" }),
|
|
3170
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "Audio/MPEG", data: "AAAA", toolCallId: "tc_case" }),
|
|
3171
|
+
sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3603
3172
|
]);
|
|
3604
3173
|
|
|
3605
3174
|
const client = new AgentWidgetClient({
|
|
@@ -3623,15 +3192,12 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3623
3192
|
|
|
3624
3193
|
it('renders a base64 text/csv attachment as a file part (not silently dropped)', async () => {
|
|
3625
3194
|
const execId = 'exec_media_csv';
|
|
3626
|
-
global.fetch =
|
|
3627
|
-
sseEvent(
|
|
3628
|
-
sseEvent(
|
|
3629
|
-
sseEvent(
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
seq: 3,
|
|
3633
|
-
}),
|
|
3634
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
3195
|
+
global.fetch = createRawStreamFetch([
|
|
3196
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
3197
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "text/csv", role: "assistant", toolCallId: "tc_csv" }),
|
|
3198
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "YSxiCjEsMg==" }),
|
|
3199
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "text/csv", data: "YSxiCjEsMg==", toolCallId: "tc_csv" }),
|
|
3200
|
+
sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3635
3201
|
]);
|
|
3636
3202
|
|
|
3637
3203
|
const client = new AgentWidgetClient({
|
|
@@ -3655,16 +3221,12 @@ describe('AgentWidgetClient - agent_media events', () => {
|
|
|
3655
3221
|
|
|
3656
3222
|
it('defaults missing mediaType on a type:"media" part to application/octet-stream', async () => {
|
|
3657
3223
|
const execId = 'exec_media_no_type';
|
|
3658
|
-
global.fetch =
|
|
3659
|
-
sseEvent(
|
|
3660
|
-
sseEvent(
|
|
3661
|
-
sseEvent(
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
media: [{ type: 'media', data: 'AAAA', mediaType: '' }],
|
|
3665
|
-
seq: 3,
|
|
3666
|
-
}),
|
|
3667
|
-
sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
|
|
3224
|
+
global.fetch = createRawStreamFetch([
|
|
3225
|
+
sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
|
|
3226
|
+
sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "", role: "assistant", toolCallId: "tc_blob" }),
|
|
3227
|
+
sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "AAAA" }),
|
|
3228
|
+
sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "", data: "AAAA", toolCallId: "tc_blob" }),
|
|
3229
|
+
sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
|
|
3668
3230
|
]);
|
|
3669
3231
|
|
|
3670
3232
|
const client = new AgentWidgetClient({
|
|
@@ -4324,7 +3886,7 @@ describe('AgentWidgetClient - version header', () => {
|
|
|
4324
3886
|
const encoder = new TextEncoder();
|
|
4325
3887
|
const stream = new ReadableStream({
|
|
4326
3888
|
start(c) {
|
|
4327
|
-
c.enqueue(encoder.encode('data: {"type":"
|
|
3889
|
+
c.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
4328
3890
|
c.close();
|
|
4329
3891
|
},
|
|
4330
3892
|
});
|
|
@@ -4610,7 +4172,7 @@ describe('AgentWidgetClient - Artifact display modes', () => {
|
|
|
4610
4172
|
|
|
4611
4173
|
describe('AgentWidgetClient - mention context', () => {
|
|
4612
4174
|
const agentDone = (controller: ReadableStreamDefaultController, encoder: TextEncoder) => {
|
|
4613
|
-
controller.enqueue(encoder.encode(sseEvent('
|
|
4175
|
+
controller.enqueue(encoder.encode(sseEvent('execution_complete', {
|
|
4614
4176
|
executionId: 'exec_1',
|
|
4615
4177
|
agentId: 'agent_123',
|
|
4616
4178
|
success: true,
|