@runtypelabs/persona 3.21.1 → 3.21.2
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/index.cjs +31 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +43 -43
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +32 -32
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +13 -4
- package/dist/theme-editor.js +13 -4
- package/package.json +1 -1
- package/src/client.test.ts +234 -0
- package/src/client.ts +29 -5
package/dist/theme-editor.cjs
CHANGED
|
@@ -4644,6 +4644,7 @@ var AgentWidgetClient = class {
|
|
|
4644
4644
|
});
|
|
4645
4645
|
};
|
|
4646
4646
|
let assistantMessage = null;
|
|
4647
|
+
let lastAssistantInTurn = null;
|
|
4647
4648
|
const assistantMessageRef = { current: null };
|
|
4648
4649
|
const partIdState = { current: null };
|
|
4649
4650
|
let didSplitByPartId = false;
|
|
@@ -5640,6 +5641,7 @@ var AgentWidgetClient = class {
|
|
|
5640
5641
|
}
|
|
5641
5642
|
}
|
|
5642
5643
|
} else if (payloadType === "agent_turn_start") {
|
|
5644
|
+
lastAssistantInTurn = null;
|
|
5643
5645
|
} else if (payloadType === "agent_turn_delta") {
|
|
5644
5646
|
if (payload.contentType === "text") {
|
|
5645
5647
|
const assistant = ensureAssistantMessage();
|
|
@@ -5650,6 +5652,7 @@ var AgentWidgetClient = class {
|
|
|
5650
5652
|
turnId: payload.turnId,
|
|
5651
5653
|
agentName: agentExecution == null ? void 0 : agentExecution.agentName
|
|
5652
5654
|
};
|
|
5655
|
+
lastAssistantInTurn = assistant;
|
|
5653
5656
|
emitMessage(assistant);
|
|
5654
5657
|
} else if (payload.contentType === "thinking") {
|
|
5655
5658
|
const reasoningId = (_Ha = payload.turnId) != null ? _Ha : `agent-think-${payload.iteration}`;
|
|
@@ -5694,15 +5697,21 @@ var AgentWidgetClient = class {
|
|
|
5694
5697
|
}
|
|
5695
5698
|
}
|
|
5696
5699
|
const turnStopReason = payload.stopReason;
|
|
5697
|
-
|
|
5700
|
+
const stopReasonTarget = assistantMessage != null ? assistantMessage : lastAssistantInTurn;
|
|
5701
|
+
if (turnStopReason && stopReasonTarget !== null) {
|
|
5698
5702
|
const turnId = payload.turnId;
|
|
5699
|
-
const matchesTurn = !turnId || ((_Pa =
|
|
5703
|
+
const matchesTurn = !turnId || ((_Pa = stopReasonTarget.agentMetadata) == null ? void 0 : _Pa.turnId) === turnId;
|
|
5700
5704
|
if (matchesTurn) {
|
|
5701
|
-
|
|
5702
|
-
emitMessage(
|
|
5705
|
+
stopReasonTarget.stopReason = turnStopReason;
|
|
5706
|
+
emitMessage(stopReasonTarget);
|
|
5703
5707
|
}
|
|
5704
5708
|
}
|
|
5705
5709
|
} else if (payloadType === "agent_tool_start") {
|
|
5710
|
+
if (assistantMessage) {
|
|
5711
|
+
assistantMessage.streaming = false;
|
|
5712
|
+
emitMessage(assistantMessage);
|
|
5713
|
+
assistantMessage = null;
|
|
5714
|
+
}
|
|
5706
5715
|
const toolId = (_Qa = payload.toolCallId) != null ? _Qa : `agent-tool-${nextSequence()}`;
|
|
5707
5716
|
trackToolId(getToolCallKey(payload), toolId);
|
|
5708
5717
|
const toolMessage = ensureToolMessage(toolId);
|
package/dist/theme-editor.js
CHANGED
|
@@ -4533,6 +4533,7 @@ var AgentWidgetClient = class {
|
|
|
4533
4533
|
});
|
|
4534
4534
|
};
|
|
4535
4535
|
let assistantMessage = null;
|
|
4536
|
+
let lastAssistantInTurn = null;
|
|
4536
4537
|
const assistantMessageRef = { current: null };
|
|
4537
4538
|
const partIdState = { current: null };
|
|
4538
4539
|
let didSplitByPartId = false;
|
|
@@ -5529,6 +5530,7 @@ var AgentWidgetClient = class {
|
|
|
5529
5530
|
}
|
|
5530
5531
|
}
|
|
5531
5532
|
} else if (payloadType === "agent_turn_start") {
|
|
5533
|
+
lastAssistantInTurn = null;
|
|
5532
5534
|
} else if (payloadType === "agent_turn_delta") {
|
|
5533
5535
|
if (payload.contentType === "text") {
|
|
5534
5536
|
const assistant = ensureAssistantMessage();
|
|
@@ -5539,6 +5541,7 @@ var AgentWidgetClient = class {
|
|
|
5539
5541
|
turnId: payload.turnId,
|
|
5540
5542
|
agentName: agentExecution == null ? void 0 : agentExecution.agentName
|
|
5541
5543
|
};
|
|
5544
|
+
lastAssistantInTurn = assistant;
|
|
5542
5545
|
emitMessage(assistant);
|
|
5543
5546
|
} else if (payload.contentType === "thinking") {
|
|
5544
5547
|
const reasoningId = (_Ha = payload.turnId) != null ? _Ha : `agent-think-${payload.iteration}`;
|
|
@@ -5583,15 +5586,21 @@ var AgentWidgetClient = class {
|
|
|
5583
5586
|
}
|
|
5584
5587
|
}
|
|
5585
5588
|
const turnStopReason = payload.stopReason;
|
|
5586
|
-
|
|
5589
|
+
const stopReasonTarget = assistantMessage != null ? assistantMessage : lastAssistantInTurn;
|
|
5590
|
+
if (turnStopReason && stopReasonTarget !== null) {
|
|
5587
5591
|
const turnId = payload.turnId;
|
|
5588
|
-
const matchesTurn = !turnId || ((_Pa =
|
|
5592
|
+
const matchesTurn = !turnId || ((_Pa = stopReasonTarget.agentMetadata) == null ? void 0 : _Pa.turnId) === turnId;
|
|
5589
5593
|
if (matchesTurn) {
|
|
5590
|
-
|
|
5591
|
-
emitMessage(
|
|
5594
|
+
stopReasonTarget.stopReason = turnStopReason;
|
|
5595
|
+
emitMessage(stopReasonTarget);
|
|
5592
5596
|
}
|
|
5593
5597
|
}
|
|
5594
5598
|
} else if (payloadType === "agent_tool_start") {
|
|
5599
|
+
if (assistantMessage) {
|
|
5600
|
+
assistantMessage.streaming = false;
|
|
5601
|
+
emitMessage(assistantMessage);
|
|
5602
|
+
assistantMessage = null;
|
|
5603
|
+
}
|
|
5595
5604
|
const toolId = (_Qa = payload.toolCallId) != null ? _Qa : `agent-tool-${nextSequence()}`;
|
|
5596
5605
|
trackToolId(getToolCallKey(payload), toolId);
|
|
5597
5606
|
const toolMessage = ensureToolMessage(toolId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.21.
|
|
3
|
+
"version": "3.21.2",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
package/src/client.test.ts
CHANGED
|
@@ -2717,6 +2717,240 @@ describe('AgentWidgetClient - stopReason propagation', () => {
|
|
|
2717
2717
|
});
|
|
2718
2718
|
});
|
|
2719
2719
|
|
|
2720
|
+
// ============================================================================
|
|
2721
|
+
// Within-turn text/tool interleaving — assistant text bubbles must seal at
|
|
2722
|
+
// each agent_tool_start so the chronological text→tool→text→tool sequence
|
|
2723
|
+
// renders as distinct timeline entries instead of one merged bubble that
|
|
2724
|
+
// appears below all the tool cards.
|
|
2725
|
+
// ============================================================================
|
|
2726
|
+
|
|
2727
|
+
describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
|
|
2728
|
+
const collectMessages = (events: AgentWidgetEvent[]): AgentWidgetMessage[] => {
|
|
2729
|
+
const byId = new Map<string, AgentWidgetMessage>();
|
|
2730
|
+
const order: string[] = [];
|
|
2731
|
+
for (const e of events) {
|
|
2732
|
+
if (e.type !== 'message') continue;
|
|
2733
|
+
if (!byId.has(e.message.id)) order.push(e.message.id);
|
|
2734
|
+
byId.set(e.message.id, e.message);
|
|
2735
|
+
}
|
|
2736
|
+
return order.map((id) => byId.get(id)!);
|
|
2737
|
+
};
|
|
2738
|
+
|
|
2739
|
+
it('seals the assistant text bubble at each agent_tool_start so subsequent text creates a new bubble', async () => {
|
|
2740
|
+
const execId = 'exec_interleave';
|
|
2741
|
+
global.fetch = createAgentStreamFetch([
|
|
2742
|
+
sseEvent('agent_start', {
|
|
2743
|
+
executionId: execId, agentId: 'virtual', agentName: 'Test',
|
|
2744
|
+
maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
|
|
2745
|
+
}),
|
|
2746
|
+
sseEvent('agent_iteration_start', {
|
|
2747
|
+
executionId: execId, iteration: 1, maxTurns: 1,
|
|
2748
|
+
startedAt: new Date().toISOString(), seq: 2,
|
|
2749
|
+
}),
|
|
2750
|
+
sseEvent('agent_turn_start', {
|
|
2751
|
+
executionId: execId, iteration: 1, turnIndex: 0,
|
|
2752
|
+
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
2753
|
+
}),
|
|
2754
|
+
sseEvent('agent_turn_delta', {
|
|
2755
|
+
executionId: execId, iteration: 1, delta: 'before tool 1',
|
|
2756
|
+
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2757
|
+
}),
|
|
2758
|
+
sseEvent('agent_tool_start', {
|
|
2759
|
+
executionId: execId, iteration: 1,
|
|
2760
|
+
toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
|
|
2761
|
+
}),
|
|
2762
|
+
sseEvent('agent_tool_complete', {
|
|
2763
|
+
executionId: execId, iteration: 1,
|
|
2764
|
+
toolCallId: 'call_1', toolName: 'search', success: true,
|
|
2765
|
+
executionTime: 10, seq: 6,
|
|
2766
|
+
}),
|
|
2767
|
+
sseEvent('agent_turn_delta', {
|
|
2768
|
+
executionId: execId, iteration: 1, delta: 'between tools',
|
|
2769
|
+
contentType: 'text', turnId: 'turn_1', seq: 7,
|
|
2770
|
+
}),
|
|
2771
|
+
sseEvent('agent_tool_start', {
|
|
2772
|
+
executionId: execId, iteration: 1,
|
|
2773
|
+
toolCallId: 'call_2', toolName: 'fetch', toolType: 'builtin', seq: 8,
|
|
2774
|
+
}),
|
|
2775
|
+
sseEvent('agent_tool_complete', {
|
|
2776
|
+
executionId: execId, iteration: 1,
|
|
2777
|
+
toolCallId: 'call_2', toolName: 'fetch', success: true,
|
|
2778
|
+
executionTime: 10, seq: 9,
|
|
2779
|
+
}),
|
|
2780
|
+
sseEvent('agent_turn_delta', {
|
|
2781
|
+
executionId: execId, iteration: 1, delta: 'after tool 2',
|
|
2782
|
+
contentType: 'text', turnId: 'turn_1', seq: 10,
|
|
2783
|
+
}),
|
|
2784
|
+
sseEvent('agent_turn_complete', {
|
|
2785
|
+
executionId: execId, iteration: 1, role: 'assistant',
|
|
2786
|
+
turnId: 'turn_1', completedAt: new Date().toISOString(),
|
|
2787
|
+
stopReason: 'end_turn', seq: 11,
|
|
2788
|
+
}),
|
|
2789
|
+
sseEvent('agent_iteration_complete', {
|
|
2790
|
+
executionId: execId, iteration: 1, toolCallsMade: 2,
|
|
2791
|
+
stopConditionMet: true, completedAt: new Date().toISOString(), seq: 12,
|
|
2792
|
+
}),
|
|
2793
|
+
sseEvent('agent_complete', {
|
|
2794
|
+
executionId: execId, agentId: 'virtual', success: true,
|
|
2795
|
+
iterations: 1, stopReason: 'complete',
|
|
2796
|
+
completedAt: new Date().toISOString(), seq: 13,
|
|
2797
|
+
}),
|
|
2798
|
+
]);
|
|
2799
|
+
|
|
2800
|
+
const client = new AgentWidgetClient({
|
|
2801
|
+
apiUrl: 'http://localhost:8000',
|
|
2802
|
+
agent: { name: 'Test', model: 'openai:gpt-4o-mini', systemPrompt: 'test' },
|
|
2803
|
+
});
|
|
2804
|
+
const events: AgentWidgetEvent[] = [];
|
|
2805
|
+
await client.dispatch(
|
|
2806
|
+
{ messages: [{ id: 'usr_1', role: 'user', content: 'Hi', createdAt: new Date().toISOString() }] },
|
|
2807
|
+
(e) => events.push(e)
|
|
2808
|
+
);
|
|
2809
|
+
|
|
2810
|
+
const messages = collectMessages(events);
|
|
2811
|
+
const assistants = messages.filter((m) => m.role === 'assistant' && m.variant !== 'tool');
|
|
2812
|
+
const tools = messages.filter((m) => m.variant === 'tool');
|
|
2813
|
+
|
|
2814
|
+
expect(assistants.map((m) => m.content)).toEqual([
|
|
2815
|
+
'before tool 1',
|
|
2816
|
+
'between tools',
|
|
2817
|
+
'after tool 2',
|
|
2818
|
+
]);
|
|
2819
|
+
expect(tools.map((m) => m.toolCall?.name)).toEqual(['search', 'fetch']);
|
|
2820
|
+
expect(new Set(assistants.map((m) => m.id)).size).toBe(3);
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
it('attaches agent_turn_complete.stopReason to the final assistant text segment when the turn ends with text', async () => {
|
|
2824
|
+
const execId = 'exec_stopreason_tail_text';
|
|
2825
|
+
global.fetch = createAgentStreamFetch([
|
|
2826
|
+
sseEvent('agent_start', {
|
|
2827
|
+
executionId: execId, agentId: 'virtual', agentName: 'Test',
|
|
2828
|
+
maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
|
|
2829
|
+
}),
|
|
2830
|
+
sseEvent('agent_iteration_start', {
|
|
2831
|
+
executionId: execId, iteration: 1, maxTurns: 1,
|
|
2832
|
+
startedAt: new Date().toISOString(), seq: 2,
|
|
2833
|
+
}),
|
|
2834
|
+
sseEvent('agent_turn_start', {
|
|
2835
|
+
executionId: execId, iteration: 1, turnIndex: 0,
|
|
2836
|
+
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
2837
|
+
}),
|
|
2838
|
+
sseEvent('agent_turn_delta', {
|
|
2839
|
+
executionId: execId, iteration: 1, delta: 'first segment',
|
|
2840
|
+
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2841
|
+
}),
|
|
2842
|
+
sseEvent('agent_tool_start', {
|
|
2843
|
+
executionId: execId, iteration: 1,
|
|
2844
|
+
toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
|
|
2845
|
+
}),
|
|
2846
|
+
sseEvent('agent_tool_complete', {
|
|
2847
|
+
executionId: execId, iteration: 1,
|
|
2848
|
+
toolCallId: 'call_1', toolName: 'search', success: true,
|
|
2849
|
+
executionTime: 10, seq: 6,
|
|
2850
|
+
}),
|
|
2851
|
+
sseEvent('agent_turn_delta', {
|
|
2852
|
+
executionId: execId, iteration: 1, delta: 'final segment',
|
|
2853
|
+
contentType: 'text', turnId: 'turn_1', seq: 7,
|
|
2854
|
+
}),
|
|
2855
|
+
sseEvent('agent_turn_complete', {
|
|
2856
|
+
executionId: execId, iteration: 1, role: 'assistant',
|
|
2857
|
+
turnId: 'turn_1', completedAt: new Date().toISOString(),
|
|
2858
|
+
stopReason: 'length', seq: 8,
|
|
2859
|
+
}),
|
|
2860
|
+
sseEvent('agent_iteration_complete', {
|
|
2861
|
+
executionId: execId, iteration: 1, toolCallsMade: 1,
|
|
2862
|
+
stopConditionMet: true, completedAt: new Date().toISOString(), seq: 9,
|
|
2863
|
+
}),
|
|
2864
|
+
sseEvent('agent_complete', {
|
|
2865
|
+
executionId: execId, agentId: 'virtual', success: true,
|
|
2866
|
+
iterations: 1, stopReason: 'complete',
|
|
2867
|
+
completedAt: new Date().toISOString(), seq: 10,
|
|
2868
|
+
}),
|
|
2869
|
+
]);
|
|
2870
|
+
|
|
2871
|
+
const client = new AgentWidgetClient({
|
|
2872
|
+
apiUrl: 'http://localhost:8000',
|
|
2873
|
+
agent: { name: 'Test', model: 'openai:gpt-4o-mini', systemPrompt: 'test' },
|
|
2874
|
+
});
|
|
2875
|
+
const events: AgentWidgetEvent[] = [];
|
|
2876
|
+
await client.dispatch(
|
|
2877
|
+
{ messages: [{ id: 'usr_1', role: 'user', content: 'Hi', createdAt: new Date().toISOString() }] },
|
|
2878
|
+
(e) => events.push(e)
|
|
2879
|
+
);
|
|
2880
|
+
|
|
2881
|
+
const assistants = collectMessages(events).filter(
|
|
2882
|
+
(m) => m.role === 'assistant' && m.variant !== 'tool'
|
|
2883
|
+
);
|
|
2884
|
+
expect(assistants).toHaveLength(2);
|
|
2885
|
+
expect(assistants[0].content).toBe('first segment');
|
|
2886
|
+
expect(assistants[0].stopReason).toBeUndefined();
|
|
2887
|
+
expect(assistants[1].content).toBe('final segment');
|
|
2888
|
+
expect(assistants[1].stopReason).toBe('length');
|
|
2889
|
+
});
|
|
2890
|
+
|
|
2891
|
+
it('attaches agent_turn_complete.stopReason to the preceding text segment when the turn ends with a tool call', async () => {
|
|
2892
|
+
const execId = 'exec_stopreason_tail_tool';
|
|
2893
|
+
global.fetch = createAgentStreamFetch([
|
|
2894
|
+
sseEvent('agent_start', {
|
|
2895
|
+
executionId: execId, agentId: 'virtual', agentName: 'Test',
|
|
2896
|
+
maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
|
|
2897
|
+
}),
|
|
2898
|
+
sseEvent('agent_iteration_start', {
|
|
2899
|
+
executionId: execId, iteration: 1, maxTurns: 1,
|
|
2900
|
+
startedAt: new Date().toISOString(), seq: 2,
|
|
2901
|
+
}),
|
|
2902
|
+
sseEvent('agent_turn_start', {
|
|
2903
|
+
executionId: execId, iteration: 1, turnIndex: 0,
|
|
2904
|
+
role: 'assistant', turnId: 'turn_1', seq: 3,
|
|
2905
|
+
}),
|
|
2906
|
+
sseEvent('agent_turn_delta', {
|
|
2907
|
+
executionId: execId, iteration: 1, delta: 'about to call tool',
|
|
2908
|
+
contentType: 'text', turnId: 'turn_1', seq: 4,
|
|
2909
|
+
}),
|
|
2910
|
+
sseEvent('agent_tool_start', {
|
|
2911
|
+
executionId: execId, iteration: 1,
|
|
2912
|
+
toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
|
|
2913
|
+
}),
|
|
2914
|
+
sseEvent('agent_tool_complete', {
|
|
2915
|
+
executionId: execId, iteration: 1,
|
|
2916
|
+
toolCallId: 'call_1', toolName: 'search', success: true,
|
|
2917
|
+
executionTime: 10, seq: 6,
|
|
2918
|
+
}),
|
|
2919
|
+
sseEvent('agent_turn_complete', {
|
|
2920
|
+
executionId: execId, iteration: 1, role: 'assistant',
|
|
2921
|
+
turnId: 'turn_1', completedAt: new Date().toISOString(),
|
|
2922
|
+
stopReason: 'max_tool_calls', seq: 7,
|
|
2923
|
+
}),
|
|
2924
|
+
sseEvent('agent_iteration_complete', {
|
|
2925
|
+
executionId: execId, iteration: 1, toolCallsMade: 1,
|
|
2926
|
+
stopConditionMet: true, completedAt: new Date().toISOString(), seq: 8,
|
|
2927
|
+
}),
|
|
2928
|
+
sseEvent('agent_complete', {
|
|
2929
|
+
executionId: execId, agentId: 'virtual', success: true,
|
|
2930
|
+
iterations: 1, stopReason: 'max_tool_calls',
|
|
2931
|
+
completedAt: new Date().toISOString(), seq: 9,
|
|
2932
|
+
}),
|
|
2933
|
+
]);
|
|
2934
|
+
|
|
2935
|
+
const client = new AgentWidgetClient({
|
|
2936
|
+
apiUrl: 'http://localhost:8000',
|
|
2937
|
+
agent: { name: 'Test', model: 'openai:gpt-4o-mini', systemPrompt: 'test' },
|
|
2938
|
+
});
|
|
2939
|
+
const events: AgentWidgetEvent[] = [];
|
|
2940
|
+
await client.dispatch(
|
|
2941
|
+
{ messages: [{ id: 'usr_1', role: 'user', content: 'Hi', createdAt: new Date().toISOString() }] },
|
|
2942
|
+
(e) => events.push(e)
|
|
2943
|
+
);
|
|
2944
|
+
|
|
2945
|
+
const assistants = collectMessages(events).filter(
|
|
2946
|
+
(m) => m.role === 'assistant' && m.variant !== 'tool'
|
|
2947
|
+
);
|
|
2948
|
+
expect(assistants).toHaveLength(1);
|
|
2949
|
+
expect(assistants[0].content).toBe('about to call tool');
|
|
2950
|
+
expect(assistants[0].stopReason).toBe('max_tool_calls');
|
|
2951
|
+
});
|
|
2952
|
+
});
|
|
2953
|
+
|
|
2720
2954
|
// ============================================================================
|
|
2721
2955
|
// step_await (LOCAL tool pause) + resumeFlow
|
|
2722
2956
|
// ============================================================================
|
package/src/client.ts
CHANGED
|
@@ -1125,6 +1125,11 @@ export class AgentWidgetClient {
|
|
|
1125
1125
|
};
|
|
1126
1126
|
|
|
1127
1127
|
let assistantMessage: AgentWidgetMessage | null = null;
|
|
1128
|
+
// Tracks the most recently touched assistant text message for the
|
|
1129
|
+
// current agent turn so `agent_turn_complete.stopReason` can attach
|
|
1130
|
+
// to the final visible text segment even after `assistantMessage`
|
|
1131
|
+
// has been finalized at a tool-call boundary within the turn.
|
|
1132
|
+
let lastAssistantInTurn: AgentWidgetMessage | null = null;
|
|
1128
1133
|
// Reference to track assistant message for custom event handler
|
|
1129
1134
|
const assistantMessageRef = { current: null as AgentWidgetMessage | null };
|
|
1130
1135
|
// Track current partId for message segmentation at tool boundaries
|
|
@@ -2464,7 +2469,11 @@ export class AgentWidgetClient {
|
|
|
2464
2469
|
}
|
|
2465
2470
|
}
|
|
2466
2471
|
} else if (payloadType === "agent_turn_start") {
|
|
2467
|
-
//
|
|
2472
|
+
// Reset the per-turn assistant tracker. lastAssistantInTurn is
|
|
2473
|
+
// used by agent_turn_complete to attach stopReason to the final
|
|
2474
|
+
// text segment of the turn even if that segment was sealed by an
|
|
2475
|
+
// intervening tool-call boundary.
|
|
2476
|
+
lastAssistantInTurn = null;
|
|
2468
2477
|
} else if (payloadType === "agent_turn_delta") {
|
|
2469
2478
|
if (payload.contentType === 'text') {
|
|
2470
2479
|
// Stream text to assistant message
|
|
@@ -2476,6 +2485,7 @@ export class AgentWidgetClient {
|
|
|
2476
2485
|
turnId: payload.turnId,
|
|
2477
2486
|
agentName: agentExecution?.agentName
|
|
2478
2487
|
};
|
|
2488
|
+
lastAssistantInTurn = assistant;
|
|
2479
2489
|
emitMessage(assistant);
|
|
2480
2490
|
} else if (payload.contentType === 'thinking') {
|
|
2481
2491
|
// Stream thinking content to a reasoning message
|
|
@@ -2526,19 +2536,33 @@ export class AgentWidgetClient {
|
|
|
2526
2536
|
// Attach the turn-level stopReason to the assistant message
|
|
2527
2537
|
// produced by this turn. Only overwrite the current message —
|
|
2528
2538
|
// prior turns already sealed their own stopReason via step_complete.
|
|
2539
|
+
// Falls back to lastAssistantInTurn when the current bubble was
|
|
2540
|
+
// sealed at a tool-call boundary mid-turn, so the notice still
|
|
2541
|
+
// attaches to the final visible text segment.
|
|
2529
2542
|
const turnStopReason = (payload as any).stopReason as
|
|
2530
2543
|
| StopReasonKind
|
|
2531
2544
|
| undefined;
|
|
2532
|
-
|
|
2545
|
+
const stopReasonTarget = assistantMessage ?? lastAssistantInTurn;
|
|
2546
|
+
if (turnStopReason && stopReasonTarget !== null) {
|
|
2533
2547
|
const turnId = payload.turnId;
|
|
2534
2548
|
const matchesTurn =
|
|
2535
|
-
!turnId ||
|
|
2549
|
+
!turnId || stopReasonTarget.agentMetadata?.turnId === turnId;
|
|
2536
2550
|
if (matchesTurn) {
|
|
2537
|
-
|
|
2538
|
-
emitMessage(
|
|
2551
|
+
stopReasonTarget.stopReason = turnStopReason;
|
|
2552
|
+
emitMessage(stopReasonTarget);
|
|
2539
2553
|
}
|
|
2540
2554
|
}
|
|
2541
2555
|
} else if (payloadType === "agent_tool_start") {
|
|
2556
|
+
// Finalize any in-flight assistant text bubble so subsequent text
|
|
2557
|
+
// deltas in this turn create a NEW bubble. Without this, text
|
|
2558
|
+
// emitted before AND after a tool call accumulates into one
|
|
2559
|
+
// message that renders below all the tool bubbles, losing the
|
|
2560
|
+
// chronological text→tool→text→tool interleaving.
|
|
2561
|
+
if (assistantMessage) {
|
|
2562
|
+
assistantMessage.streaming = false;
|
|
2563
|
+
emitMessage(assistantMessage);
|
|
2564
|
+
assistantMessage = null;
|
|
2565
|
+
}
|
|
2542
2566
|
const toolId = payload.toolCallId ?? `agent-tool-${nextSequence()}`;
|
|
2543
2567
|
trackToolId(getToolCallKey(payload), toolId);
|
|
2544
2568
|
const toolMessage = ensureToolMessage(toolId);
|