@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.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RuntypeExecutionStreamEvent, RuntypeClientInitResponse } from "./generated/runtype-openapi-contract";
|
|
1
2
|
import {
|
|
2
3
|
AgentWidgetConfig,
|
|
3
4
|
AgentWidgetMessage,
|
|
@@ -12,10 +13,8 @@ import {
|
|
|
12
13
|
AgentWidgetSSEEventParser,
|
|
13
14
|
AgentWidgetHeadersFunction,
|
|
14
15
|
AgentWidgetSSEEventResult as _AgentWidgetSSEEventResult,
|
|
15
|
-
AgentExecutionState,
|
|
16
16
|
StopReasonKind,
|
|
17
17
|
ClientSession,
|
|
18
|
-
ClientInitResponse,
|
|
19
18
|
ClientChatRequest,
|
|
20
19
|
ClientToolDefinition,
|
|
21
20
|
ClientFeedbackRequest,
|
|
@@ -138,15 +137,10 @@ export class HistoryClientError extends Error {
|
|
|
138
137
|
const DISPLAY_PROJECTION_MESSAGE_CAP = 32768;
|
|
139
138
|
const DISPLAY_PROJECTION_BATCH_CAP = 49152;
|
|
140
139
|
|
|
141
|
-
/**
|
|
140
|
+
/** Normalize current history metadata without leaking wire-only aliases. */
|
|
142
141
|
const normalizeHistorySummary = (raw: unknown): HistoryConversationSummary => {
|
|
143
142
|
const row = (raw ?? {}) as Record<string, unknown>;
|
|
144
|
-
const targetId =
|
|
145
|
-
typeof row.targetId === "string"
|
|
146
|
-
? row.targetId
|
|
147
|
-
: typeof row.flowId === "string"
|
|
148
|
-
? row.flowId
|
|
149
|
-
: null;
|
|
143
|
+
const targetId = typeof row.targetId === "string" ? row.targetId : null;
|
|
150
144
|
return {
|
|
151
145
|
id: typeof row.id === "string" ? row.id : "",
|
|
152
146
|
title: typeof row.title === "string" ? row.title : "",
|
|
@@ -798,7 +792,7 @@ export class AgentWidgetClient {
|
|
|
798
792
|
: {}),
|
|
799
793
|
};
|
|
800
794
|
|
|
801
|
-
|
|
795
|
+
const response = await fetch(this.getClientApiUrl('init'), {
|
|
802
796
|
method: 'POST',
|
|
803
797
|
headers: {
|
|
804
798
|
'Content-Type': 'application/json',
|
|
@@ -807,22 +801,6 @@ export class AgentWidgetClient {
|
|
|
807
801
|
body: JSON.stringify(requestBody),
|
|
808
802
|
});
|
|
809
803
|
|
|
810
|
-
// Rolling/self-hosted compatibility: pre-recovery servers use a strict
|
|
811
|
-
// init schema and answer 400 to the additive field. Retry once without it;
|
|
812
|
-
// the absent response capability keeps Persona on ordinary streaming.
|
|
813
|
-
if (response.status === 400 && durableRecoveryRequested) {
|
|
814
|
-
const fallbackBody = { ...requestBody };
|
|
815
|
-
delete fallbackBody.durableRecovery;
|
|
816
|
-
response = await fetch(this.getClientApiUrl('init'), {
|
|
817
|
-
method: 'POST',
|
|
818
|
-
headers: {
|
|
819
|
-
'Content-Type': 'application/json',
|
|
820
|
-
'X-Persona-Version': VERSION,
|
|
821
|
-
},
|
|
822
|
-
body: JSON.stringify(fallbackBody),
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
|
|
826
804
|
if (!response.ok) {
|
|
827
805
|
const error = await response.json().catch(() => ({ error: 'Session initialization failed' }));
|
|
828
806
|
if (historyCapable && response.status === 403 && error.error === 'visitor_history_disabled') {
|
|
@@ -861,7 +839,7 @@ export class AgentWidgetClient {
|
|
|
861
839
|
throw new Error(error.error || 'Failed to initialize session');
|
|
862
840
|
}
|
|
863
841
|
|
|
864
|
-
const data:
|
|
842
|
+
const data: RuntypeClientInitResponse = await response.json();
|
|
865
843
|
|
|
866
844
|
// First awaited op after parse: the server cannot re-issue a minted secret.
|
|
867
845
|
if (data.visitor?.token) {
|
|
@@ -871,12 +849,10 @@ export class AgentWidgetClient {
|
|
|
871
849
|
return {
|
|
872
850
|
sessionId: data.sessionId,
|
|
873
851
|
expiresAt: new Date(data.expiresAt),
|
|
874
|
-
|
|
852
|
+
// Preserve the published session facade while consuming the current wire type.
|
|
853
|
+
flow: data.flow as ClientSession["flow"],
|
|
875
854
|
...(data.conversationId ? { conversationId: data.conversationId } : {}),
|
|
876
|
-
|
|
877
|
-
...(data.targetId ?? data.flow?.id
|
|
878
|
-
? { targetId: data.targetId ?? data.flow?.id }
|
|
879
|
-
: {}),
|
|
855
|
+
...(data.targetId ? { targetId: data.targetId } : {}),
|
|
880
856
|
...(data.conversationRevision
|
|
881
857
|
? { conversationRevision: data.conversationRevision }
|
|
882
858
|
: {}),
|
|
@@ -885,7 +861,7 @@ export class AgentWidgetClient {
|
|
|
885
861
|
config: {
|
|
886
862
|
welcomeMessage: data.config.welcomeMessage,
|
|
887
863
|
placeholder: data.config.placeholder,
|
|
888
|
-
theme: data.config.theme,
|
|
864
|
+
theme: data.config.theme as ClientSession["config"]["theme"],
|
|
889
865
|
},
|
|
890
866
|
};
|
|
891
867
|
}
|
|
@@ -1675,12 +1651,8 @@ export class AgentWidgetClient {
|
|
|
1675
1651
|
...(opts?.scope ? { scope: opts.scope } : {}),
|
|
1676
1652
|
}
|
|
1677
1653
|
);
|
|
1678
|
-
const summarySource =
|
|
1679
|
-
detail && typeof detail.conversation === 'object' && detail.conversation !== null
|
|
1680
|
-
? detail.conversation
|
|
1681
|
-
: detail;
|
|
1682
1654
|
return {
|
|
1683
|
-
summary: normalizeHistorySummary(
|
|
1655
|
+
summary: normalizeHistorySummary(detail),
|
|
1684
1656
|
messages: Array.isArray(detail?.messages)
|
|
1685
1657
|
? (detail.messages as HistoryConversationDetail['messages'])
|
|
1686
1658
|
: [],
|
|
@@ -2208,7 +2180,7 @@ export class AgentWidgetClient {
|
|
|
2208
2180
|
* Returns the raw Response so the caller can pipe its SSE body through
|
|
2209
2181
|
* `connectStream()`.
|
|
2210
2182
|
*
|
|
2211
|
-
* @param executionId - The paused execution id carried on `
|
|
2183
|
+
* @param executionId - The paused execution id carried on `await`.
|
|
2212
2184
|
* @param toolOutputs - Map keyed by per-call `toolCallId` (core#3878),
|
|
2213
2185
|
* falling back to tool name for legacy servers → the tool's result value.
|
|
2214
2186
|
*/
|
|
@@ -2832,7 +2804,7 @@ export class AgentWidgetClient {
|
|
|
2832
2804
|
|
|
2833
2805
|
let assistantMessage: AgentWidgetMessage | null = null;
|
|
2834
2806
|
// Tracks the most recently touched assistant text message for the
|
|
2835
|
-
// current agent turn so `
|
|
2807
|
+
// current agent turn so `turn_complete.stopReason` can attach
|
|
2836
2808
|
// to the final visible text segment even after `assistantMessage`
|
|
2837
2809
|
// has been finalized at a tool-call boundary within the turn.
|
|
2838
2810
|
let lastAssistantInTurn: AgentWidgetMessage | null = null;
|
|
@@ -2859,46 +2831,6 @@ export class AgentWidgetClient {
|
|
|
2859
2831
|
const nestedBlockRaw = new Map<string, string>();
|
|
2860
2832
|
const reasoningMessages = new Map<string, AgentWidgetMessage>();
|
|
2861
2833
|
const toolMessages = new Map<string, AgentWidgetMessage>();
|
|
2862
|
-
const reasoningContext = {
|
|
2863
|
-
lastId: null as string | null,
|
|
2864
|
-
byStep: new Map<string, string>()
|
|
2865
|
-
};
|
|
2866
|
-
const toolContext = {
|
|
2867
|
-
lastId: null as string | null,
|
|
2868
|
-
byCall: new Map<string, string>()
|
|
2869
|
-
};
|
|
2870
|
-
|
|
2871
|
-
const normalizeKey = (value: unknown): string | null => {
|
|
2872
|
-
if (value === null || value === undefined) return null;
|
|
2873
|
-
try {
|
|
2874
|
-
return String(value);
|
|
2875
|
-
} catch (error) {
|
|
2876
|
-
return null;
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
|
|
2880
|
-
const getStepKey = (payload: Record<string, any>) =>
|
|
2881
|
-
normalizeKey(
|
|
2882
|
-
payload.stepId ??
|
|
2883
|
-
payload.step_id ??
|
|
2884
|
-
payload.step ??
|
|
2885
|
-
payload.parentId ??
|
|
2886
|
-
payload.flowStepId ??
|
|
2887
|
-
payload.flow_step_id
|
|
2888
|
-
);
|
|
2889
|
-
|
|
2890
|
-
const getToolCallKey = (payload: Record<string, any>) =>
|
|
2891
|
-
normalizeKey(
|
|
2892
|
-
payload.callId ??
|
|
2893
|
-
payload.call_id ??
|
|
2894
|
-
payload.requestId ??
|
|
2895
|
-
payload.request_id ??
|
|
2896
|
-
payload.toolCallId ??
|
|
2897
|
-
payload.tool_call_id ??
|
|
2898
|
-
payload.stepId ??
|
|
2899
|
-
payload.step_id
|
|
2900
|
-
);
|
|
2901
|
-
|
|
2902
2834
|
const baseAssistantId = assistantMessageId;
|
|
2903
2835
|
let assistantIdConsumed = false;
|
|
2904
2836
|
|
|
@@ -2930,42 +2862,6 @@ export class AgentWidgetClient {
|
|
|
2930
2862
|
return assistantMessage;
|
|
2931
2863
|
};
|
|
2932
2864
|
|
|
2933
|
-
const trackReasoningId = (stepKey: string | null, id: string) => {
|
|
2934
|
-
reasoningContext.lastId = id;
|
|
2935
|
-
if (stepKey) {
|
|
2936
|
-
reasoningContext.byStep.set(stepKey, id);
|
|
2937
|
-
}
|
|
2938
|
-
};
|
|
2939
|
-
|
|
2940
|
-
const resolveReasoningId = (
|
|
2941
|
-
payload: Record<string, any>,
|
|
2942
|
-
allowCreate: boolean
|
|
2943
|
-
): string | null => {
|
|
2944
|
-
const rawId = payload.reasoningId ?? payload.id;
|
|
2945
|
-
const stepKey = getStepKey(payload);
|
|
2946
|
-
if (rawId) {
|
|
2947
|
-
const resolved = String(rawId);
|
|
2948
|
-
trackReasoningId(stepKey, resolved);
|
|
2949
|
-
return resolved;
|
|
2950
|
-
}
|
|
2951
|
-
if (stepKey) {
|
|
2952
|
-
const existing = reasoningContext.byStep.get(stepKey);
|
|
2953
|
-
if (existing) {
|
|
2954
|
-
reasoningContext.lastId = existing;
|
|
2955
|
-
return existing;
|
|
2956
|
-
}
|
|
2957
|
-
}
|
|
2958
|
-
if (reasoningContext.lastId && !allowCreate) {
|
|
2959
|
-
return reasoningContext.lastId;
|
|
2960
|
-
}
|
|
2961
|
-
if (!allowCreate) {
|
|
2962
|
-
return null;
|
|
2963
|
-
}
|
|
2964
|
-
const generated = `reason-${nextSequence()}`;
|
|
2965
|
-
trackReasoningId(stepKey, generated);
|
|
2966
|
-
return generated;
|
|
2967
|
-
};
|
|
2968
|
-
|
|
2969
2865
|
const ensureReasoningMessage = (reasoningId: string) => {
|
|
2970
2866
|
const existing = reasoningMessages.get(reasoningId);
|
|
2971
2867
|
if (existing) {
|
|
@@ -2992,19 +2888,12 @@ export class AgentWidgetClient {
|
|
|
2992
2888
|
return message;
|
|
2993
2889
|
};
|
|
2994
2890
|
|
|
2995
|
-
const trackToolId = (callKey: string | null, id: string) => {
|
|
2996
|
-
toolContext.lastId = id;
|
|
2997
|
-
if (callKey) {
|
|
2998
|
-
toolContext.byCall.set(callKey, id);
|
|
2999
|
-
}
|
|
3000
|
-
};
|
|
3001
|
-
|
|
3002
2891
|
// Track tool call IDs for artifact emit tools so we can suppress their UI
|
|
3003
2892
|
const artifactToolCallIds = new Set<string>();
|
|
3004
2893
|
// Track artifact block messages (reference card or inline block) so we can
|
|
3005
2894
|
// update them on artifact_complete
|
|
3006
2895
|
const artifactCardMessages = new Map<string, AgentWidgetMessage>();
|
|
3007
|
-
// Track artifact IDs that already have a reference card
|
|
2896
|
+
// Track artifact IDs that already have a reference card
|
|
3008
2897
|
const artifactIdsWithCards = new Set<string>();
|
|
3009
2898
|
// Accumulate artifact markdown content (and component props) for embedding
|
|
3010
2899
|
// in block props on complete. `props` accumulates across `artifact_update`
|
|
@@ -3026,35 +2915,6 @@ export class AgentWidgetClient {
|
|
|
3026
2915
|
return normalized === "emit_artifact_markdown" || normalized === "emit_artifact_component";
|
|
3027
2916
|
};
|
|
3028
2917
|
|
|
3029
|
-
const resolveToolId = (
|
|
3030
|
-
payload: Record<string, any>,
|
|
3031
|
-
allowCreate: boolean
|
|
3032
|
-
): string | null => {
|
|
3033
|
-
const rawId = payload.toolId ?? payload.id;
|
|
3034
|
-
const callKey = getToolCallKey(payload);
|
|
3035
|
-
if (rawId) {
|
|
3036
|
-
const resolved = String(rawId);
|
|
3037
|
-
trackToolId(callKey, resolved);
|
|
3038
|
-
return resolved;
|
|
3039
|
-
}
|
|
3040
|
-
if (callKey) {
|
|
3041
|
-
const existing = toolContext.byCall.get(callKey);
|
|
3042
|
-
if (existing) {
|
|
3043
|
-
toolContext.lastId = existing;
|
|
3044
|
-
return existing;
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
if (toolContext.lastId && !allowCreate) {
|
|
3048
|
-
return toolContext.lastId;
|
|
3049
|
-
}
|
|
3050
|
-
if (!allowCreate) {
|
|
3051
|
-
return null;
|
|
3052
|
-
}
|
|
3053
|
-
const generated = `tool-${nextSequence()}`;
|
|
3054
|
-
trackToolId(callKey, generated);
|
|
3055
|
-
return generated;
|
|
3056
|
-
};
|
|
3057
|
-
|
|
3058
2918
|
const ensureToolMessage = (toolId: string) => {
|
|
3059
2919
|
const existing = toolMessages.get(toolId);
|
|
3060
2920
|
if (existing) {
|
|
@@ -3116,45 +2976,7 @@ export class AgentWidgetClient {
|
|
|
3116
2976
|
const streamParsers = new Map<string, AgentWidgetStreamParser>();
|
|
3117
2977
|
// Track accumulated raw content for structured formats (JSON, XML, etc.)
|
|
3118
2978
|
const rawContentBuffers = new Map<string, string>();
|
|
3119
|
-
|
|
3120
|
-
// content after the reorder buffer's gap-timeout flush.
|
|
3121
|
-
const orderedChunkBuffers = new Map<string, Array<{ seq: number; text: string }>>();
|
|
3122
|
-
|
|
3123
|
-
const insertOrderedChunk = (key: string, seq: number, text: string): string => {
|
|
3124
|
-
let chunks = orderedChunkBuffers.get(key);
|
|
3125
|
-
if (!chunks) {
|
|
3126
|
-
chunks = [];
|
|
3127
|
-
orderedChunkBuffers.set(key, chunks);
|
|
3128
|
-
}
|
|
3129
|
-
|
|
3130
|
-
let lo = 0;
|
|
3131
|
-
let hi = chunks.length;
|
|
3132
|
-
while (lo < hi) {
|
|
3133
|
-
const mid = (lo + hi) >>> 1;
|
|
3134
|
-
if (chunks[mid].seq < seq) {
|
|
3135
|
-
lo = mid + 1;
|
|
3136
|
-
} else {
|
|
3137
|
-
hi = mid;
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
|
|
3141
|
-
if (chunks[lo]?.seq === seq) {
|
|
3142
|
-
chunks[lo] = { seq, text };
|
|
3143
|
-
} else {
|
|
3144
|
-
chunks.splice(lo, 0, { seq, text });
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
|
-
let accumulated = "";
|
|
3148
|
-
for (let index = 0; index < chunks.length; index++) {
|
|
3149
|
-
accumulated += chunks[index].text;
|
|
3150
|
-
}
|
|
3151
|
-
return accumulated;
|
|
3152
|
-
};
|
|
3153
|
-
|
|
3154
|
-
/**
|
|
3155
|
-
* After text_end + didSplitByPartId, merge the authoritative final response into the
|
|
3156
|
-
* sealed message when streaming left content short (e.g. async parser lag).
|
|
3157
|
-
*/
|
|
2979
|
+
/** Reconcile a sealed text block with its authoritative structured response. */
|
|
3158
2980
|
const reconcileSealedAssistantWithFinalResponse = (
|
|
3159
2981
|
msg: AgentWidgetMessage,
|
|
3160
2982
|
finalContent: unknown
|
|
@@ -3427,11 +3249,11 @@ export class AgentWidgetClient {
|
|
|
3427
3249
|
};
|
|
3428
3250
|
|
|
3429
3251
|
// Ready queue of parsed wire frames awaiting a drain. The API streams the
|
|
3430
|
-
//
|
|
3252
|
+
// unified wire vocabulary; each frame is parsed in the SSE loop
|
|
3431
3253
|
// below and rendered directly by the handler (no translation bridge), then
|
|
3432
3254
|
// pushed here. The wire stream is a single, in-order SSE connection, so
|
|
3433
3255
|
// frames drain straight through with no reordering.
|
|
3434
|
-
const
|
|
3256
|
+
const eventQueue: RuntypeExecutionStreamEvent[] = [];
|
|
3435
3257
|
// Declared here so later closures can reference it; assigned after all
|
|
3436
3258
|
// handler-scoped variables are initialised (before the SSE loop).
|
|
3437
3259
|
let drainReadyQueue: () => void;
|
|
@@ -3440,7 +3262,7 @@ export class AgentWidgetClient {
|
|
|
3440
3262
|
// synthetic message at media_complete, keyed by the block id.
|
|
3441
3263
|
const mediaBuffers = new Map<
|
|
3442
3264
|
string,
|
|
3443
|
-
{ mediaType?: string;
|
|
3265
|
+
{ mediaType?: string; toolCallId?: string; parts: string[] }
|
|
3444
3266
|
>();
|
|
3445
3267
|
// Tracks the last iteration surfaced as a per-iteration message boundary, so
|
|
3446
3268
|
// `turn_start` advancing the iteration rotates the bubble in 'separate' mode.
|
|
@@ -3462,18 +3284,15 @@ export class AgentWidgetClient {
|
|
|
3462
3284
|
// from here.
|
|
3463
3285
|
let openTurnId: string | null = null;
|
|
3464
3286
|
// Agent execution state tracking
|
|
3465
|
-
let agentExecution:
|
|
3466
|
-
// Track assistant messages per agent iteration for 'separate' mode
|
|
3467
|
-
const agentIterationMessages = new Map<number, AgentWidgetMessage>();
|
|
3287
|
+
let agentExecution: { executionId: string; agentId: string; agentName: string } | null = null;
|
|
3468
3288
|
const iterationDisplay = this.config.iterationDisplay ?? 'separate';
|
|
3469
3289
|
|
|
3470
|
-
// Drains the queued
|
|
3290
|
+
// Drains the queued native events through the main event handler.
|
|
3471
3291
|
// Also invoked after the SSE loop exits so any events queued at
|
|
3472
3292
|
// end-of-stream are processed.
|
|
3473
3293
|
drainReadyQueue = () => {
|
|
3474
|
-
for (let i = 0; i <
|
|
3475
|
-
const
|
|
3476
|
-
const payload = seqReadyQueue[i].payload;
|
|
3294
|
+
for (let i = 0; i < eventQueue.length; i++) {
|
|
3295
|
+
const payload = eventQueue[i];
|
|
3477
3296
|
|
|
3478
3297
|
// Recover the execution kind on continuation streams that omit
|
|
3479
3298
|
// `execution_start` (e.g. a tool-driven `/resume`). Flow `step_*` frames
|
|
@@ -3490,7 +3309,7 @@ export class AgentWidgetClient {
|
|
|
3490
3309
|
executionKind = "flow";
|
|
3491
3310
|
}
|
|
3492
3311
|
|
|
3493
|
-
if (
|
|
3312
|
+
if (payload.type === "reasoning_start") {
|
|
3494
3313
|
// Nested flow-as-tool thinking (PR #4602): route to the parent tool's row.
|
|
3495
3314
|
const rStartBlockId = typeof payload.id === "string" ? payload.id : null;
|
|
3496
3315
|
const rStartParent =
|
|
@@ -3502,8 +3321,7 @@ export class AgentWidgetClient {
|
|
|
3502
3321
|
ensureNestedBlockMessage(rStartBlockId, rStartParent, "reasoning");
|
|
3503
3322
|
continue;
|
|
3504
3323
|
}
|
|
3505
|
-
const reasoningId =
|
|
3506
|
-
resolveReasoningId(payload, true) ?? `reason-${nextSequence()}`;
|
|
3324
|
+
const reasoningId = payload.id;
|
|
3507
3325
|
const reasoningMessage = ensureReasoningMessage(reasoningId);
|
|
3508
3326
|
reasoningMessage.reasoning = reasoningMessage.reasoning ?? {
|
|
3509
3327
|
id: reasoningId,
|
|
@@ -3512,7 +3330,7 @@ export class AgentWidgetClient {
|
|
|
3512
3330
|
};
|
|
3513
3331
|
reasoningMessage.reasoning.startedAt =
|
|
3514
3332
|
reasoningMessage.reasoning.startedAt ??
|
|
3515
|
-
|
|
3333
|
+
Date.now();
|
|
3516
3334
|
reasoningMessage.reasoning.completedAt = undefined;
|
|
3517
3335
|
reasoningMessage.reasoning.durationMs = undefined;
|
|
3518
3336
|
if (payload.scope === "loop" || payload.scope === "turn") {
|
|
@@ -3521,7 +3339,7 @@ export class AgentWidgetClient {
|
|
|
3521
3339
|
reasoningMessage.streaming = true;
|
|
3522
3340
|
reasoningMessage.reasoning.status = "streaming";
|
|
3523
3341
|
emitMessage(reasoningMessage);
|
|
3524
|
-
} else if (
|
|
3342
|
+
} else if (payload.type === "reasoning_delta") {
|
|
3525
3343
|
// Nested flow-as-tool thinking: append to the parent-tool-row message.
|
|
3526
3344
|
const rDeltaBlockId = typeof payload.id === "string" ? payload.id : null;
|
|
3527
3345
|
if (
|
|
@@ -3530,18 +3348,14 @@ export class AgentWidgetClient {
|
|
|
3530
3348
|
nestedBlockMessages.has(rDeltaBlockId)
|
|
3531
3349
|
) {
|
|
3532
3350
|
const nested = nestedBlockMessages.get(rDeltaBlockId)!;
|
|
3533
|
-
const nestedChunk =
|
|
3534
|
-
|
|
3535
|
-
if (nestedChunk && payload.hidden !== true && nested.reasoning) {
|
|
3351
|
+
const nestedChunk = payload.delta;
|
|
3352
|
+
if (nestedChunk && nested.reasoning) {
|
|
3536
3353
|
nested.reasoning.chunks.push(String(nestedChunk));
|
|
3537
3354
|
emitMessage(nested);
|
|
3538
3355
|
}
|
|
3539
3356
|
continue;
|
|
3540
3357
|
}
|
|
3541
|
-
const reasoningId =
|
|
3542
|
-
resolveReasoningId(payload, false) ??
|
|
3543
|
-
resolveReasoningId(payload, true) ??
|
|
3544
|
-
`reason-${nextSequence()}`;
|
|
3358
|
+
const reasoningId = payload.id;
|
|
3545
3359
|
const reasoningMessage = ensureReasoningMessage(reasoningId);
|
|
3546
3360
|
reasoningMessage.reasoning = reasoningMessage.reasoning ?? {
|
|
3547
3361
|
id: reasoningId,
|
|
@@ -3550,38 +3364,12 @@ export class AgentWidgetClient {
|
|
|
3550
3364
|
};
|
|
3551
3365
|
reasoningMessage.reasoning.startedAt =
|
|
3552
3366
|
reasoningMessage.reasoning.startedAt ??
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
payload.delta ??
|
|
3558
|
-
"";
|
|
3559
|
-
if (chunk && payload.hidden !== true) {
|
|
3560
|
-
const reasonSeq = typeof payload.sequenceIndex === "number" ? payload.sequenceIndex : undefined;
|
|
3561
|
-
if (reasonSeq !== undefined) {
|
|
3562
|
-
// Rebuild chunks by seq so late arrivals after a gap-timeout flush
|
|
3563
|
-
// are inserted at the correct position rather than appended.
|
|
3564
|
-
const ordered = insertOrderedChunk(reasoningId, reasonSeq, String(chunk));
|
|
3565
|
-
reasoningMessage.reasoning.chunks = [ordered];
|
|
3566
|
-
} else {
|
|
3567
|
-
reasoningMessage.reasoning.chunks.push(String(chunk));
|
|
3568
|
-
}
|
|
3569
|
-
}
|
|
3570
|
-
reasoningMessage.reasoning.status = payload.done ? "complete" : "streaming";
|
|
3571
|
-
if (payload.done) {
|
|
3572
|
-
reasoningMessage.reasoning.completedAt = resolveTimestamp(
|
|
3573
|
-
payload.completedAt ?? payload.timestamp
|
|
3574
|
-
);
|
|
3575
|
-
const start = reasoningMessage.reasoning.startedAt ?? Date.now();
|
|
3576
|
-
reasoningMessage.reasoning.durationMs = Math.max(
|
|
3577
|
-
0,
|
|
3578
|
-
(reasoningMessage.reasoning.completedAt ?? Date.now()) - start
|
|
3579
|
-
);
|
|
3580
|
-
|
|
3581
|
-
}
|
|
3582
|
-
reasoningMessage.streaming = reasoningMessage.reasoning.status !== "complete";
|
|
3367
|
+
Date.now();
|
|
3368
|
+
if (payload.delta) reasoningMessage.reasoning.chunks.push(payload.delta);
|
|
3369
|
+
reasoningMessage.reasoning.status = "streaming";
|
|
3370
|
+
reasoningMessage.streaming = true;
|
|
3583
3371
|
emitMessage(reasoningMessage);
|
|
3584
|
-
} else if (
|
|
3372
|
+
} else if (payload.type === "reasoning_complete") {
|
|
3585
3373
|
// Nested flow-as-tool thinking close: seal the parent-tool-row message.
|
|
3586
3374
|
const rCompleteBlockId = typeof payload.id === "string" ? payload.id : null;
|
|
3587
3375
|
if (
|
|
@@ -3604,10 +3392,7 @@ export class AgentWidgetClient {
|
|
|
3604
3392
|
nestedBlockMessages.delete(rCompleteBlockId);
|
|
3605
3393
|
continue;
|
|
3606
3394
|
}
|
|
3607
|
-
const reasoningId =
|
|
3608
|
-
resolveReasoningId(payload, false) ??
|
|
3609
|
-
resolveReasoningId(payload, true) ??
|
|
3610
|
-
`reason-${nextSequence()}`;
|
|
3395
|
+
const reasoningId = payload.id;
|
|
3611
3396
|
// A close carrying text (or scope:"loop") is a cross-iteration
|
|
3612
3397
|
// reflection fold (merged spec §4 E3): the API streams nothing for the
|
|
3613
3398
|
// block, then delivers the whole reflection as `text` on the close.
|
|
@@ -3627,9 +3412,7 @@ export class AgentWidgetClient {
|
|
|
3627
3412
|
reasoningMessage.reasoning.chunks.push(reflectionText);
|
|
3628
3413
|
}
|
|
3629
3414
|
reasoningMessage.reasoning.status = "complete";
|
|
3630
|
-
reasoningMessage.reasoning.completedAt =
|
|
3631
|
-
payload.completedAt ?? payload.timestamp
|
|
3632
|
-
);
|
|
3415
|
+
reasoningMessage.reasoning.completedAt = Date.now();
|
|
3633
3416
|
const start = reasoningMessage.reasoning.startedAt ?? Date.now();
|
|
3634
3417
|
reasoningMessage.reasoning.durationMs = Math.max(
|
|
3635
3418
|
0,
|
|
@@ -3639,11 +3422,7 @@ export class AgentWidgetClient {
|
|
|
3639
3422
|
|
|
3640
3423
|
emitMessage(reasoningMessage);
|
|
3641
3424
|
}
|
|
3642
|
-
|
|
3643
|
-
if (stepKey) {
|
|
3644
|
-
reasoningContext.byStep.delete(stepKey);
|
|
3645
|
-
}
|
|
3646
|
-
} else if (payloadType === "tool_start") {
|
|
3425
|
+
} else if (payload.type === "tool_start") {
|
|
3647
3426
|
// Unified tool family (agent + flow). Seal any open assistant bubble so
|
|
3648
3427
|
// text→tool→text interleaves chronologically (the API also emits a
|
|
3649
3428
|
// text_complete here, so this is usually a no-op — kept for safety).
|
|
@@ -3657,17 +3436,13 @@ export class AgentWidgetClient {
|
|
|
3657
3436
|
// own — can be stamped with the enclosing iteration even on tool-only
|
|
3658
3437
|
// turns that never emit a `turn_start`.
|
|
3659
3438
|
if (typeof payload.iteration === "number") lastIterationSeen = payload.iteration;
|
|
3660
|
-
const toolId
|
|
3661
|
-
|
|
3662
|
-
resolveToolId(payload, true) ??
|
|
3663
|
-
`tool-${nextSequence()}`;
|
|
3664
|
-
const toolName = payload.toolName ?? payload.name;
|
|
3439
|
+
const toolId = payload.toolCallId;
|
|
3440
|
+
const toolName = payload.toolName;
|
|
3665
3441
|
// Suppress tool UI for artifact emit tools: artifacts are handled via artifact_* events
|
|
3666
3442
|
if (isArtifactEmitToolName(toolName)) {
|
|
3667
3443
|
artifactToolCallIds.add(toolId);
|
|
3668
3444
|
continue;
|
|
3669
3445
|
}
|
|
3670
|
-
trackToolId(getToolCallKey(payload), toolId);
|
|
3671
3446
|
const toolMessage = ensureToolMessage(toolId);
|
|
3672
3447
|
const tool = toolMessage.toolCall ?? {
|
|
3673
3448
|
id: toolId,
|
|
@@ -3675,14 +3450,15 @@ export class AgentWidgetClient {
|
|
|
3675
3450
|
};
|
|
3676
3451
|
tool.name = toolName ?? tool.name;
|
|
3677
3452
|
tool.status = "running";
|
|
3453
|
+
tool.success = undefined;
|
|
3454
|
+
tool.error = undefined;
|
|
3455
|
+
tool.duration = undefined;
|
|
3678
3456
|
if (payload.parameters !== undefined) {
|
|
3679
3457
|
tool.args = payload.parameters;
|
|
3680
|
-
} else if (payload.args !== undefined) {
|
|
3681
|
-
tool.args = payload.args;
|
|
3682
3458
|
}
|
|
3683
3459
|
tool.startedAt =
|
|
3684
3460
|
tool.startedAt ??
|
|
3685
|
-
resolveTimestamp(payload.startedAt
|
|
3461
|
+
resolveTimestamp(payload.startedAt);
|
|
3686
3462
|
tool.completedAt = undefined;
|
|
3687
3463
|
tool.durationMs = undefined;
|
|
3688
3464
|
toolMessage.toolCall = tool;
|
|
@@ -3694,11 +3470,8 @@ export class AgentWidgetClient {
|
|
|
3694
3470
|
};
|
|
3695
3471
|
}
|
|
3696
3472
|
emitMessage(toolMessage);
|
|
3697
|
-
} else if (
|
|
3698
|
-
const toolId =
|
|
3699
|
-
resolveToolId(payload, false) ??
|
|
3700
|
-
resolveToolId(payload, true) ??
|
|
3701
|
-
`tool-${nextSequence()}`;
|
|
3473
|
+
} else if (payload.type === "tool_output_delta") {
|
|
3474
|
+
const toolId = payload.toolCallId;
|
|
3702
3475
|
if (artifactToolCallIds.has(toolId)) continue;
|
|
3703
3476
|
const toolMessage = ensureToolMessage(toolId);
|
|
3704
3477
|
const tool = toolMessage.toolCall ?? {
|
|
@@ -3707,9 +3480,8 @@ export class AgentWidgetClient {
|
|
|
3707
3480
|
};
|
|
3708
3481
|
tool.startedAt =
|
|
3709
3482
|
tool.startedAt ??
|
|
3710
|
-
|
|
3711
|
-
const chunkText =
|
|
3712
|
-
payload.text ?? payload.delta ?? payload.message ?? "";
|
|
3483
|
+
Date.now();
|
|
3484
|
+
const chunkText = payload.delta;
|
|
3713
3485
|
if (chunkText) {
|
|
3714
3486
|
tool.chunks = tool.chunks ?? [];
|
|
3715
3487
|
tool.chunks.push(String(chunkText));
|
|
@@ -3717,19 +3489,15 @@ export class AgentWidgetClient {
|
|
|
3717
3489
|
tool.status = "running";
|
|
3718
3490
|
toolMessage.toolCall = tool;
|
|
3719
3491
|
toolMessage.streaming = true;
|
|
3720
|
-
|
|
3721
|
-
if (agentCtxChunk || payload.executionId) {
|
|
3492
|
+
if (payload.executionId) {
|
|
3722
3493
|
toolMessage.agentMetadata = toolMessage.agentMetadata ?? {
|
|
3723
|
-
executionId:
|
|
3724
|
-
iteration:
|
|
3494
|
+
executionId: payload.executionId,
|
|
3495
|
+
iteration: lastIterationSeen,
|
|
3725
3496
|
};
|
|
3726
3497
|
}
|
|
3727
3498
|
emitMessage(toolMessage);
|
|
3728
|
-
} else if (
|
|
3729
|
-
const toolId =
|
|
3730
|
-
resolveToolId(payload, false) ??
|
|
3731
|
-
resolveToolId(payload, true) ??
|
|
3732
|
-
`tool-${nextSequence()}`;
|
|
3499
|
+
} else if (payload.type === "tool_complete") {
|
|
3500
|
+
const toolId = payload.toolCallId;
|
|
3733
3501
|
if (artifactToolCallIds.has(toolId)) {
|
|
3734
3502
|
artifactToolCallIds.delete(toolId);
|
|
3735
3503
|
continue;
|
|
@@ -3743,15 +3511,14 @@ export class AgentWidgetClient {
|
|
|
3743
3511
|
if (payload.result !== undefined) {
|
|
3744
3512
|
tool.result = payload.result;
|
|
3745
3513
|
}
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
tool.completedAt =
|
|
3750
|
-
|
|
3751
|
-
);
|
|
3752
|
-
const durationValue = payload.duration ?? payload.executionTime;
|
|
3514
|
+
tool.name = payload.toolName ?? tool.name;
|
|
3515
|
+
tool.success = payload.success;
|
|
3516
|
+
tool.error = payload.error;
|
|
3517
|
+
tool.completedAt = Date.now();
|
|
3518
|
+
const durationValue = payload.executionTime;
|
|
3753
3519
|
if (typeof durationValue === "number") {
|
|
3754
3520
|
tool.durationMs = durationValue;
|
|
3521
|
+
tool.duration = durationValue;
|
|
3755
3522
|
} else {
|
|
3756
3523
|
const start = tool.startedAt ?? Date.now();
|
|
3757
3524
|
tool.durationMs = Math.max(
|
|
@@ -3761,29 +3528,17 @@ export class AgentWidgetClient {
|
|
|
3761
3528
|
}
|
|
3762
3529
|
toolMessage.toolCall = tool;
|
|
3763
3530
|
toolMessage.streaming = false;
|
|
3764
|
-
|
|
3765
|
-
if (agentCtxComplete || payload.executionId) {
|
|
3531
|
+
if (payload.executionId) {
|
|
3766
3532
|
toolMessage.agentMetadata = toolMessage.agentMetadata ?? {
|
|
3767
|
-
executionId:
|
|
3768
|
-
iteration:
|
|
3533
|
+
executionId: payload.executionId,
|
|
3534
|
+
iteration: payload.iteration ?? lastIterationSeen,
|
|
3769
3535
|
};
|
|
3770
3536
|
}
|
|
3771
3537
|
emitMessage(toolMessage);
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
} else if (payloadType === "await" && payload.toolName) {
|
|
3777
|
-
// LOCAL tool pause. Two wire shapes resolve here, by dispatch target:
|
|
3778
|
-
// - FLOW dispatch → `step_await` + `awaitReason: "local_tool_required"`
|
|
3779
|
-
// (Runtype's prompt step throws LocalToolRequiredError when the model
|
|
3780
|
-
// calls a `toolType: "local"` tool).
|
|
3781
|
-
// - AGENT dispatch → `agent_await` (the agent runtime's native pause).
|
|
3782
|
-
// Either way the server emits the tool name, params, and execution id;
|
|
3783
|
-
// the execution pauses until the client POSTs /resume with toolOutputs.
|
|
3784
|
-
// `agent_await` carries a BARE tool name plus an `origin`; page tools
|
|
3785
|
-
// (origin "webmcp") are normalized to the `webmcp:`-prefixed form below
|
|
3786
|
-
// so the bridge + session.ts `/resume` keying are identical for both.
|
|
3538
|
+
} else if (payload.type === "await" && payload.toolName) {
|
|
3539
|
+
// Unified LOCAL tool pause for either dispatch kind. The execution
|
|
3540
|
+
// waits for /resume with toolOutputs. Page tools with origin "webmcp"
|
|
3541
|
+
// may carry a bare name, normalized to the internal webmcp: prefix.
|
|
3787
3542
|
//
|
|
3788
3543
|
// Upsert a fully-populated tool-variant message so the existing
|
|
3789
3544
|
// ask_user_question bubble + sheet paths fire. Mark the message with
|
|
@@ -3807,9 +3562,9 @@ export class AgentWidgetClient {
|
|
|
3807
3562
|
toolCallId ?? (payload.toolId as string) ?? `local-${nextSequence()}`;
|
|
3808
3563
|
const toolMessage = ensureToolMessage(toolId);
|
|
3809
3564
|
const rawToolName = payload.toolName as string;
|
|
3810
|
-
//
|
|
3565
|
+
// Page tools may arrive with a bare name; synthesize the
|
|
3811
3566
|
// `webmcp:` prefix so isWebMcpToolName (and the bridge's prefix-strip on
|
|
3812
|
-
// resume) treat them identically to a flow `
|
|
3567
|
+
// resume) treat them identically to a flow `await`.
|
|
3813
3568
|
const toolName =
|
|
3814
3569
|
payload.origin === "webmcp" &&
|
|
3815
3570
|
!isWebMcpToolName(rawToolName)
|
|
@@ -3820,7 +3575,7 @@ export class AgentWidgetClient {
|
|
|
3820
3575
|
tool.name = toolName;
|
|
3821
3576
|
tool.args = payload.parameters;
|
|
3822
3577
|
// WebMCP tools are executed asynchronously by the browser AFTER this
|
|
3823
|
-
// `
|
|
3578
|
+
// `await` arrives. Keep them running until session.ts resolves
|
|
3824
3579
|
// the page tool and records its actual elapsed time. Other local
|
|
3825
3580
|
// tools (for example ask_user_question) keep the existing complete
|
|
3826
3581
|
// state because they are waiting for a user interaction, not an
|
|
@@ -3829,7 +3584,7 @@ export class AgentWidgetClient {
|
|
|
3829
3584
|
tool.chunks = tool.chunks ?? [];
|
|
3830
3585
|
tool.startedAt =
|
|
3831
3586
|
tool.startedAt ??
|
|
3832
|
-
resolveTimestamp(payload.
|
|
3587
|
+
resolveTimestamp(payload.awaitedAt);
|
|
3833
3588
|
if (webMcpTool) {
|
|
3834
3589
|
tool.completedAt = undefined;
|
|
3835
3590
|
tool.duration = undefined;
|
|
@@ -3849,7 +3604,7 @@ export class AgentWidgetClient {
|
|
|
3849
3604
|
...(toolCallId ? { webMcpToolCallId: toolCallId } : {}),
|
|
3850
3605
|
};
|
|
3851
3606
|
emitMessage(toolMessage);
|
|
3852
|
-
} else if (
|
|
3607
|
+
} else if (payload.type === "text_start") {
|
|
3853
3608
|
// Nested flow-as-tool text (PR #4602): a `parentToolCallId` means this
|
|
3854
3609
|
// block belongs to a flow running as that tool — record the mapping and
|
|
3855
3610
|
// leave the top-level assistant bubble untouched (the nested deltas route
|
|
@@ -3883,7 +3638,7 @@ export class AgentWidgetClient {
|
|
|
3883
3638
|
currentTextBlockId =
|
|
3884
3639
|
typeof payload.id === "string" ? payload.id : currentTextBlockId;
|
|
3885
3640
|
pendingTextRaw = "";
|
|
3886
|
-
} else if (
|
|
3641
|
+
} else if (payload.type === "text_delta") {
|
|
3887
3642
|
// Nested flow-as-tool text: route to the parent tool's row, through the
|
|
3888
3643
|
// same structured-content parser, never the top-level assistant channel.
|
|
3889
3644
|
const deltaBlockId = typeof payload.id === "string" ? payload.id : null;
|
|
@@ -3918,7 +3673,7 @@ export class AgentWidgetClient {
|
|
|
3918
3673
|
const assistant = ensureAssistantMessage();
|
|
3919
3674
|
assistant.agentMetadata = {
|
|
3920
3675
|
executionId: payload.executionId,
|
|
3921
|
-
iteration:
|
|
3676
|
+
iteration: lastIterationSeen,
|
|
3922
3677
|
};
|
|
3923
3678
|
applyTextChunk(assistant, pendingTextRaw, delta, undefined);
|
|
3924
3679
|
lastAssistantInTurn = assistant;
|
|
@@ -3937,13 +3692,13 @@ export class AgentWidgetClient {
|
|
|
3937
3692
|
const assistant = ensureAssistantMessage();
|
|
3938
3693
|
assistant.agentMetadata = {
|
|
3939
3694
|
executionId: payload.executionId,
|
|
3940
|
-
iteration:
|
|
3695
|
+
iteration: lastIterationSeen,
|
|
3941
3696
|
turnId: openTurnId ?? undefined,
|
|
3942
3697
|
agentName: agentExecution?.agentName
|
|
3943
3698
|
};
|
|
3944
3699
|
applyTextChunk(assistant, pendingTextRaw, agentDelta, undefined);
|
|
3945
3700
|
lastAssistantInTurn = assistant;
|
|
3946
|
-
} else if (
|
|
3701
|
+
} else if (payload.type === "text_complete") {
|
|
3947
3702
|
// Nested flow-as-tool text block close: seal its parent-tool-row message.
|
|
3948
3703
|
const completeBlockId = typeof payload.id === "string" ? payload.id : null;
|
|
3949
3704
|
if (completeBlockId && nestedBlockParent.has(completeBlockId)) {
|
|
@@ -3976,11 +3731,9 @@ export class AgentWidgetClient {
|
|
|
3976
3731
|
}
|
|
3977
3732
|
currentTextBlockId = null;
|
|
3978
3733
|
pendingTextRaw = "";
|
|
3979
|
-
} else if (
|
|
3734
|
+
} else if (payload.type === "step_complete") {
|
|
3980
3735
|
// Only process completions for prompt steps, not tool/context steps
|
|
3981
|
-
|
|
3982
|
-
const executionType = (payload as any).executionType;
|
|
3983
|
-
if (stepType === "tool" || executionType === "context") {
|
|
3736
|
+
if (payload.stepType === "tool") {
|
|
3984
3737
|
// Skip tool-related completions - they're handled by tool_complete
|
|
3985
3738
|
continue;
|
|
3986
3739
|
}
|
|
@@ -3989,17 +3742,7 @@ export class AgentWidgetClient {
|
|
|
3989
3742
|
// event, which the wire encoder folds into a failed `step_complete`
|
|
3990
3743
|
// — surfaces as a terminal error and finalizes the stream.
|
|
3991
3744
|
if (payload.success === false) {
|
|
3992
|
-
const
|
|
3993
|
-
const message =
|
|
3994
|
-
typeof e === "string" && e !== ""
|
|
3995
|
-
? e
|
|
3996
|
-
: // Reflect.has, not `in`: the `in` operator inside an arrow body can
|
|
3997
|
-
// be minified into a `for(init;;)` head, which Oxc mis-parses and
|
|
3998
|
-
// Rolldown (Vite 8) silently emits as an empty chunk. Same
|
|
3999
|
-
// [[HasProperty]] semantics. Enforced by scripts/check-dist-no-in-for-init.mjs.
|
|
4000
|
-
e != null && typeof e === "object" && Reflect.has(e, "message")
|
|
4001
|
-
? String((e as { message?: unknown }).message ?? "Step failed")
|
|
4002
|
-
: "Step failed";
|
|
3745
|
+
const message = payload.error || "Step failed";
|
|
4003
3746
|
onEvent({ type: "error", error: new Error(message) });
|
|
4004
3747
|
const finalMsg = assistantMessage as AgentWidgetMessage | null;
|
|
4005
3748
|
if (finalMsg && finalMsg.streaming) {
|
|
@@ -4018,10 +3761,13 @@ export class AgentWidgetClient {
|
|
|
4018
3761
|
{
|
|
4019
3762
|
const sealed = lastSealedFlowBubble;
|
|
4020
3763
|
lastSealedFlowBubble = null;
|
|
4021
|
-
const flowStopReason =
|
|
3764
|
+
const flowStopReason = payload.stopReason as
|
|
4022
3765
|
| StopReasonKind
|
|
4023
3766
|
| undefined;
|
|
4024
|
-
const
|
|
3767
|
+
const result = payload.result && typeof payload.result === "object"
|
|
3768
|
+
? payload.result as Record<string, unknown>
|
|
3769
|
+
: null;
|
|
3770
|
+
const finalResponse = result?.response;
|
|
4025
3771
|
if (sealed) {
|
|
4026
3772
|
if (flowStopReason) sealed.stopReason = flowStopReason;
|
|
4027
3773
|
if (finalResponse !== undefined && finalResponse !== null) {
|
|
@@ -4057,21 +3803,17 @@ export class AgentWidgetClient {
|
|
|
4057
3803
|
// ================================================================
|
|
4058
3804
|
// Agent Loop Execution Events
|
|
4059
3805
|
// ================================================================
|
|
4060
|
-
} else if (
|
|
3806
|
+
} else if (payload.type === "execution_start") {
|
|
4061
3807
|
executionKind = payload.kind === "flow" ? "flow" : "agent";
|
|
4062
3808
|
executionKindResolved = true;
|
|
4063
3809
|
if (executionKind === "agent") {
|
|
4064
3810
|
agentExecution = {
|
|
4065
3811
|
executionId: payload.executionId,
|
|
4066
3812
|
agentId: payload.agentId ?? 'virtual',
|
|
4067
|
-
agentName: payload.agentName ?? ''
|
|
4068
|
-
status: 'running',
|
|
4069
|
-
currentIteration: 0,
|
|
4070
|
-
maxTurns: payload.maxTurns ?? 1,
|
|
4071
|
-
startedAt: resolveTimestamp(payload.startedAt)
|
|
3813
|
+
agentName: payload.agentName ?? ''
|
|
4072
3814
|
};
|
|
4073
3815
|
}
|
|
4074
|
-
} else if (
|
|
3816
|
+
} else if (payload.type === "turn_start") {
|
|
4075
3817
|
// Unified collapsed `agent_iteration_*` into a denormalized `iteration`
|
|
4076
3818
|
// field on the turn (merged spec §2). Reconstruct the per-iteration
|
|
4077
3819
|
// message boundary the 'separate' renderer keys off: when the iteration
|
|
@@ -4079,13 +3821,11 @@ export class AgentWidgetClient {
|
|
|
4079
3821
|
const iteration =
|
|
4080
3822
|
typeof payload.iteration === "number" ? payload.iteration : lastIterationSeen;
|
|
4081
3823
|
if (iteration !== lastIterationSeen) {
|
|
4082
|
-
if (agentExecution) agentExecution.currentIteration = iteration;
|
|
4083
3824
|
if (iterationDisplay === 'separate' && iteration > 1) {
|
|
4084
3825
|
const prevMsg = assistantMessage as AgentWidgetMessage | null;
|
|
4085
3826
|
if (prevMsg) {
|
|
4086
3827
|
prevMsg.streaming = false;
|
|
4087
3828
|
emitMessage(prevMsg);
|
|
4088
|
-
agentIterationMessages.set(iteration - 1, prevMsg);
|
|
4089
3829
|
assistantMessage = null;
|
|
4090
3830
|
}
|
|
4091
3831
|
}
|
|
@@ -4096,10 +3836,10 @@ export class AgentWidgetClient {
|
|
|
4096
3836
|
// turn_complete to attach stopReason to the final text segment of the
|
|
4097
3837
|
// turn even if that segment was sealed by an intervening tool boundary.
|
|
4098
3838
|
lastAssistantInTurn = null;
|
|
4099
|
-
} else if (
|
|
3839
|
+
} else if (payload.type === "tool_input_delta") {
|
|
4100
3840
|
// Streamed tool arguments (display-only; authoritative args ride
|
|
4101
3841
|
// tool_input_complete / tool_start).
|
|
4102
|
-
const toolId = payload.toolCallId
|
|
3842
|
+
const toolId = payload.toolCallId;
|
|
4103
3843
|
if (toolId) {
|
|
4104
3844
|
const toolMessage = toolMessages.get(toolId);
|
|
4105
3845
|
if (toolMessage?.toolCall) {
|
|
@@ -4108,16 +3848,20 @@ export class AgentWidgetClient {
|
|
|
4108
3848
|
emitMessage(toolMessage);
|
|
4109
3849
|
}
|
|
4110
3850
|
}
|
|
4111
|
-
} else if (
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
3851
|
+
} else if (payload.type === "tool_input_complete") {
|
|
3852
|
+
if (artifactToolCallIds.has(payload.toolCallId)) continue;
|
|
3853
|
+
const toolMessage = ensureToolMessage(payload.toolCallId);
|
|
3854
|
+
const tool = toolMessage.toolCall!;
|
|
3855
|
+
tool.args = payload.parameters;
|
|
3856
|
+
tool.name = payload.toolName ?? tool.name;
|
|
3857
|
+
emitMessage(toolMessage);
|
|
3858
|
+
} else if (payload.type === "turn_complete") {
|
|
4115
3859
|
// Reasoning is sealed by its own reasoning_complete on the wire
|
|
4116
3860
|
// vocabulary; this only attaches the turn-level stopReason to the
|
|
4117
3861
|
// assistant message produced by this turn. Falls back to
|
|
4118
3862
|
// lastAssistantInTurn when the bubble was sealed at a tool boundary
|
|
4119
3863
|
// mid-turn, so the notice still attaches to the final visible segment.
|
|
4120
|
-
const turnStopReason =
|
|
3864
|
+
const turnStopReason = payload.stopReason as
|
|
4121
3865
|
| StopReasonKind
|
|
4122
3866
|
| undefined;
|
|
4123
3867
|
const stopReasonTarget = assistantMessage ?? lastAssistantInTurn;
|
|
@@ -4131,19 +3875,18 @@ export class AgentWidgetClient {
|
|
|
4131
3875
|
}
|
|
4132
3876
|
}
|
|
4133
3877
|
if (openTurnId === payload.id) openTurnId = null;
|
|
4134
|
-
} else if (
|
|
3878
|
+
} else if (payload.type === "media_start") {
|
|
4135
3879
|
// Open a media block; buffer fragments until media_complete.
|
|
4136
3880
|
const id = String(payload.id);
|
|
4137
3881
|
mediaBuffers.set(id, {
|
|
4138
3882
|
mediaType: typeof payload.mediaType === "string" ? payload.mediaType : undefined,
|
|
4139
|
-
role: typeof payload.role === "string" ? payload.role : undefined,
|
|
4140
3883
|
toolCallId: payload.toolCallId,
|
|
4141
3884
|
parts: [],
|
|
4142
3885
|
});
|
|
4143
|
-
} else if (
|
|
3886
|
+
} else if (payload.type === "media_delta") {
|
|
4144
3887
|
const buf = mediaBuffers.get(String(payload.id));
|
|
4145
3888
|
if (buf && typeof payload.delta === "string") buf.parts.push(payload.delta);
|
|
4146
|
-
} else if (
|
|
3889
|
+
} else if (payload.type === "media_complete") {
|
|
4147
3890
|
// Reassemble the buffered media triad into a single AI SDK–aligned
|
|
4148
3891
|
// `MediaContentPart`, then render it as a synthetic assistant message
|
|
4149
3892
|
// inserted between the tool bubble and the next text turn:
|
|
@@ -4275,17 +4018,14 @@ export class AgentWidgetClient {
|
|
|
4275
4018
|
executionId: payload.executionId,
|
|
4276
4019
|
// Media blocks carry no iteration of their own; stamp the
|
|
4277
4020
|
// enclosing iteration tracked from turn/tool frames.
|
|
4278
|
-
iteration:
|
|
4279
|
-
typeof payload.iteration === "number"
|
|
4280
|
-
? payload.iteration
|
|
4281
|
-
: lastIterationSeen,
|
|
4021
|
+
iteration: lastIterationSeen,
|
|
4282
4022
|
},
|
|
4283
4023
|
};
|
|
4284
4024
|
emitMessage(mediaMessage);
|
|
4285
4025
|
|
|
4286
4026
|
// Seal any in-flight assistant text bubble before splitting the
|
|
4287
4027
|
// stream. Without this, an orphan bubble retains `streaming: true`
|
|
4288
|
-
// forever: `
|
|
4028
|
+
// forever: `execution_complete` only finalizes the latest
|
|
4289
4029
|
// `assistantMessage`, so the typing/caret indicator would stay on
|
|
4290
4030
|
// the prior bubble even though no more deltas will arrive.
|
|
4291
4031
|
const prevAssistant = assistantMessage as AgentWidgetMessage | null;
|
|
@@ -4296,21 +4036,14 @@ export class AgentWidgetClient {
|
|
|
4296
4036
|
assistantMessage = null;
|
|
4297
4037
|
assistantMessageRef.current = null;
|
|
4298
4038
|
}
|
|
4299
|
-
} else if (
|
|
4300
|
-
const kind = payload.kind ?? executionKind;
|
|
4301
|
-
if (kind === "agent" && agentExecution) {
|
|
4302
|
-
agentExecution.status = payload.success ? 'complete' : 'error';
|
|
4303
|
-
agentExecution.completedAt = resolveTimestamp(payload.completedAt);
|
|
4304
|
-
agentExecution.stopReason = payload.stopReason;
|
|
4305
|
-
}
|
|
4306
|
-
|
|
4039
|
+
} else if (payload.type === "execution_complete") {
|
|
4307
4040
|
// Finalize any still-open assistant message. Per-step reconciliation
|
|
4308
4041
|
// (step_complete.result.response) normally sealed the flow blocks
|
|
4309
4042
|
// already; this is the defensive close for an unterminated block, and
|
|
4310
4043
|
// for flow it runs the final structured extraction off the raw buffer.
|
|
4311
4044
|
const finalMsg = assistantMessage as AgentWidgetMessage | null;
|
|
4312
4045
|
if (finalMsg) {
|
|
4313
|
-
if (kind === "flow" && finalMsg.streaming !== false) {
|
|
4046
|
+
if (payload.kind === "flow" && finalMsg.streaming !== false) {
|
|
4314
4047
|
finalizeFlowTextBlock(finalMsg);
|
|
4315
4048
|
} else {
|
|
4316
4049
|
finalMsg.streaming = false;
|
|
@@ -4327,7 +4060,7 @@ export class AgentWidgetClient {
|
|
|
4327
4060
|
// `idle` the dispatch wrappers emit in their `finally` when a durable
|
|
4328
4061
|
// connection drops mid-stream (durable-reconnect drop detection).
|
|
4329
4062
|
onEvent({ type: "status", status: "idle", terminal: true });
|
|
4330
|
-
} else if (
|
|
4063
|
+
} else if (payload.type === "execution_error") {
|
|
4331
4064
|
// Terminal failure. The non-terminal `error` is handled
|
|
4332
4065
|
// separately (recoverable → warn).
|
|
4333
4066
|
const errorMessage = typeof payload.error === 'string'
|
|
@@ -4337,37 +4070,12 @@ export class AgentWidgetClient {
|
|
|
4337
4070
|
type: "error",
|
|
4338
4071
|
error: new Error(errorMessage)
|
|
4339
4072
|
});
|
|
4340
|
-
} else if (
|
|
4073
|
+
} else if (payload.type === "ping") {
|
|
4341
4074
|
// Keep-alive heartbeat - no action needed
|
|
4342
4075
|
// ================================================================
|
|
4343
4076
|
// Tool Approval Events
|
|
4344
4077
|
// ================================================================
|
|
4345
|
-
} else if (
|
|
4346
|
-
const approvalId = payload.approvalId ?? `approval-${nextSequence()}`;
|
|
4347
|
-
const approvalMessage: AgentWidgetMessage = {
|
|
4348
|
-
id: `approval-${approvalId}`,
|
|
4349
|
-
role: "assistant",
|
|
4350
|
-
content: "",
|
|
4351
|
-
createdAt: new Date().toISOString(),
|
|
4352
|
-
streaming: false,
|
|
4353
|
-
variant: "approval",
|
|
4354
|
-
sequence: nextSequence(),
|
|
4355
|
-
approval: {
|
|
4356
|
-
id: approvalId,
|
|
4357
|
-
status: "pending",
|
|
4358
|
-
agentId: agentExecution?.agentId ?? 'virtual',
|
|
4359
|
-
executionId: payload.executionId ?? agentExecution?.executionId ?? '',
|
|
4360
|
-
toolName: payload.toolName ?? '',
|
|
4361
|
-
toolType: payload.toolType,
|
|
4362
|
-
description: payload.description ?? `Execute ${payload.toolName ?? 'tool'}`,
|
|
4363
|
-
...(typeof payload.reason === "string" && payload.reason
|
|
4364
|
-
? { reason: payload.reason }
|
|
4365
|
-
: {}),
|
|
4366
|
-
parameters: payload.parameters,
|
|
4367
|
-
},
|
|
4368
|
-
};
|
|
4369
|
-
emitMessage(approvalMessage);
|
|
4370
|
-
} else if (payloadType === "step_await" && payload.awaitReason === "approval_required") {
|
|
4078
|
+
} else if (payload.type === "approval_start") {
|
|
4371
4079
|
const approvalId = payload.approvalId ?? `approval-${nextSequence()}`;
|
|
4372
4080
|
const approvalMessage: AgentWidgetMessage = {
|
|
4373
4081
|
id: `approval-${approvalId}`,
|
|
@@ -4392,7 +4100,7 @@ export class AgentWidgetClient {
|
|
|
4392
4100
|
},
|
|
4393
4101
|
};
|
|
4394
4102
|
emitMessage(approvalMessage);
|
|
4395
|
-
} else if (
|
|
4103
|
+
} else if (payload.type === "approval_complete") {
|
|
4396
4104
|
const approvalId = payload.approvalId;
|
|
4397
4105
|
if (approvalId) {
|
|
4398
4106
|
// Find and update the existing approval message
|
|
@@ -4410,20 +4118,20 @@ export class AgentWidgetClient {
|
|
|
4410
4118
|
status: (payload.decision as "approved" | "denied") ?? "approved",
|
|
4411
4119
|
agentId: agentExecution?.agentId ?? 'virtual',
|
|
4412
4120
|
executionId: payload.executionId ?? agentExecution?.executionId ?? '',
|
|
4413
|
-
toolName:
|
|
4414
|
-
description:
|
|
4121
|
+
toolName: '',
|
|
4122
|
+
description: '',
|
|
4415
4123
|
resolvedAt: Date.now(),
|
|
4416
4124
|
},
|
|
4417
4125
|
};
|
|
4418
4126
|
emitMessage(existingMessage);
|
|
4419
4127
|
}
|
|
4420
4128
|
} else if (
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4129
|
+
payload.type === "artifact_start" ||
|
|
4130
|
+
payload.type === "artifact_delta" ||
|
|
4131
|
+
payload.type === "artifact_update" ||
|
|
4132
|
+
payload.type === "artifact_complete"
|
|
4425
4133
|
) {
|
|
4426
|
-
if (
|
|
4134
|
+
if (payload.type === "artifact_start") {
|
|
4427
4135
|
const at = payload.artifactType as PersonaArtifactKind;
|
|
4428
4136
|
const artId = String(payload.id);
|
|
4429
4137
|
const artTitle = typeof payload.title === "string" ? payload.title : undefined;
|
|
@@ -4453,20 +4161,13 @@ export class AgentWidgetClient {
|
|
|
4453
4161
|
component: typeof payload.component === "string" ? payload.component : undefined,
|
|
4454
4162
|
...(artFile ? { file: artFile } : {})
|
|
4455
4163
|
});
|
|
4456
|
-
// Seed component props from artifact_start when the payload
|
|
4457
|
-
// carries them; artifact_update events accumulate the rest below.
|
|
4458
|
-
const startProps =
|
|
4459
|
-
payload.props && typeof payload.props === "object" && !Array.isArray(payload.props)
|
|
4460
|
-
? { ...(payload.props as Record<string, unknown>) }
|
|
4461
|
-
: undefined;
|
|
4462
4164
|
artifactContent.set(artId, {
|
|
4463
4165
|
markdown: "",
|
|
4464
4166
|
title: artTitle,
|
|
4465
4167
|
file: artFile,
|
|
4466
|
-
...(startProps ? { props: startProps } : {})
|
|
4467
4168
|
});
|
|
4468
|
-
// Insert the in-thread artifact block
|
|
4469
|
-
//
|
|
4169
|
+
// Insert the in-thread artifact block once per ID.
|
|
4170
|
+
// The resolved display mode picks the component:
|
|
4470
4171
|
// "card"/"panel" inject the reference card; "inline" injects the
|
|
4471
4172
|
// inline preview block. Both share the rawContent JSON-component
|
|
4472
4173
|
// shape so transcript persistence and hydration work unchanged.
|
|
@@ -4500,7 +4201,7 @@ export class AgentWidgetClient {
|
|
|
4500
4201
|
artifactCardMessages.set(artId, cardMsg);
|
|
4501
4202
|
emitMessage(cardMsg);
|
|
4502
4203
|
}
|
|
4503
|
-
} else if (
|
|
4204
|
+
} else if (payload.type === "artifact_delta") {
|
|
4504
4205
|
const deltaId = String(payload.id);
|
|
4505
4206
|
const deltaText = typeof payload.delta === "string" ? payload.delta : String(payload.delta ?? "");
|
|
4506
4207
|
onEvent({
|
|
@@ -4510,7 +4211,7 @@ export class AgentWidgetClient {
|
|
|
4510
4211
|
});
|
|
4511
4212
|
const acc = artifactContent.get(deltaId);
|
|
4512
4213
|
if (acc) acc.markdown += deltaText;
|
|
4513
|
-
} else if (
|
|
4214
|
+
} else if (payload.type === "artifact_update") {
|
|
4514
4215
|
const props =
|
|
4515
4216
|
payload.props && typeof payload.props === "object" && !Array.isArray(payload.props)
|
|
4516
4217
|
? (payload.props as Record<string, unknown>)
|
|
@@ -4528,7 +4229,7 @@ export class AgentWidgetClient {
|
|
|
4528
4229
|
if (updateAcc) {
|
|
4529
4230
|
updateAcc.props = { ...(updateAcc.props ?? {}), ...props };
|
|
4530
4231
|
}
|
|
4531
|
-
} else if (
|
|
4232
|
+
} else if (payload.type === "artifact_complete") {
|
|
4532
4233
|
const artCompleteId = String(payload.id);
|
|
4533
4234
|
onEvent({ type: "artifact_complete", id: artCompleteId });
|
|
4534
4235
|
// Update the inline card to show completed state
|
|
@@ -4566,46 +4267,7 @@ export class AgentWidgetClient {
|
|
|
4566
4267
|
artifactCardMessages.delete(artCompleteId);
|
|
4567
4268
|
}
|
|
4568
4269
|
}
|
|
4569
|
-
} else if (
|
|
4570
|
-
const m = payload.message as Record<string, unknown> | undefined;
|
|
4571
|
-
if (!m || typeof m !== "object") {
|
|
4572
|
-
continue;
|
|
4573
|
-
}
|
|
4574
|
-
const id = String(m.id ?? `msg-${nextSequence()}`);
|
|
4575
|
-
const roleRaw = m.role;
|
|
4576
|
-
const role =
|
|
4577
|
-
roleRaw === "user" ? "user" : roleRaw === "system" ? "system" : "assistant";
|
|
4578
|
-
const msg: AgentWidgetMessage = {
|
|
4579
|
-
id,
|
|
4580
|
-
role,
|
|
4581
|
-
content: typeof m.content === "string" ? m.content : "",
|
|
4582
|
-
rawContent: typeof m.rawContent === "string" ? m.rawContent : undefined,
|
|
4583
|
-
createdAt:
|
|
4584
|
-
typeof m.createdAt === "string" ? m.createdAt : new Date().toISOString(),
|
|
4585
|
-
streaming: m.streaming === true,
|
|
4586
|
-
// Omit variant unless the stream specifies it. Do not default to `"assistant"`:
|
|
4587
|
-
// that value is truthy and skips the component-directive branch (`!message.variant` in ui.ts).
|
|
4588
|
-
...(typeof m.variant === "string"
|
|
4589
|
-
? { variant: m.variant as AgentWidgetMessage["variant"] }
|
|
4590
|
-
: {}),
|
|
4591
|
-
sequence: nextSequence()
|
|
4592
|
-
};
|
|
4593
|
-
emitMessage(msg);
|
|
4594
|
-
// Detect artifact references in transcript_insert to prevent duplicate auto-cards
|
|
4595
|
-
if (msg.rawContent) {
|
|
4596
|
-
try {
|
|
4597
|
-
const parsed = JSON.parse(msg.rawContent);
|
|
4598
|
-
const refArtId = parsed?.props?.artifactId;
|
|
4599
|
-
if (typeof refArtId === "string") {
|
|
4600
|
-
artifactIdsWithCards.add(refArtId);
|
|
4601
|
-
}
|
|
4602
|
-
} catch { /* not JSON or no artifactId */ }
|
|
4603
|
-
}
|
|
4604
|
-
assistantMessage = null;
|
|
4605
|
-
assistantMessageRef.current = null;
|
|
4606
|
-
streamParsers.delete(id);
|
|
4607
|
-
rawContentBuffers.delete(id);
|
|
4608
|
-
} else if (payloadType === "error") {
|
|
4270
|
+
} else if (payload.type === "error") {
|
|
4609
4271
|
// Unified non-terminal error (merged spec). A bare `error` is
|
|
4610
4272
|
// recoverable by default — a transient notice such as "rate limited,
|
|
4611
4273
|
// retrying" — and the execution continues, so it must NOT surface as a
|
|
@@ -4631,41 +4293,10 @@ export class AgentWidgetClient {
|
|
|
4631
4293
|
}
|
|
4632
4294
|
onEvent({ type: "status", status: "idle" });
|
|
4633
4295
|
}
|
|
4634
|
-
} else if (
|
|
4635
|
-
payloadType === "step_error" ||
|
|
4636
|
-
payloadType === "dispatch_error" ||
|
|
4637
|
-
payloadType === "flow_error"
|
|
4638
|
-
) {
|
|
4639
|
-
let resolvedError: Error | null = null;
|
|
4640
|
-
if (payload.error instanceof Error) {
|
|
4641
|
-
resolvedError = payload.error;
|
|
4642
|
-
} else if (payloadType === "dispatch_error") {
|
|
4643
|
-
const msg = payload.message ?? payload.error;
|
|
4644
|
-
if (msg != null && msg !== "") {
|
|
4645
|
-
resolvedError = new Error(String(msg));
|
|
4646
|
-
}
|
|
4647
|
-
} else {
|
|
4648
|
-
const e = payload.error;
|
|
4649
|
-
if (typeof e === "string" && e !== "") {
|
|
4650
|
-
resolvedError = new Error(e);
|
|
4651
|
-
} else if (e != null && typeof e === "object" && Reflect.has(e, "message")) {
|
|
4652
|
-
// Reflect.has, not `in` — see the note on the equivalent guard above.
|
|
4653
|
-
resolvedError = new Error(String((e as { message?: unknown }).message ?? e));
|
|
4654
|
-
}
|
|
4655
|
-
}
|
|
4656
4296
|
|
|
4657
|
-
if (resolvedError) {
|
|
4658
|
-
onEvent({ type: "error", error: resolvedError });
|
|
4659
|
-
const finalMsg = assistantMessage as AgentWidgetMessage | null;
|
|
4660
|
-
if (finalMsg && finalMsg.streaming) {
|
|
4661
|
-
finalMsg.streaming = false;
|
|
4662
|
-
emitMessage(finalMsg);
|
|
4663
|
-
}
|
|
4664
|
-
onEvent({ type: "status", status: "idle" });
|
|
4665
|
-
}
|
|
4666
4297
|
}
|
|
4667
4298
|
}
|
|
4668
|
-
|
|
4299
|
+
eventQueue.length = 0;
|
|
4669
4300
|
};
|
|
4670
4301
|
|
|
4671
4302
|
// eslint-disable-next-line no-constant-condition
|
|
@@ -4712,7 +4343,7 @@ export class AgentWidgetClient {
|
|
|
4712
4343
|
advanceCursor();
|
|
4713
4344
|
continue;
|
|
4714
4345
|
}
|
|
4715
|
-
let payload:
|
|
4346
|
+
let payload: unknown;
|
|
4716
4347
|
try {
|
|
4717
4348
|
payload = JSON.parse(data);
|
|
4718
4349
|
} catch (error) {
|
|
@@ -4728,8 +4359,12 @@ export class AgentWidgetClient {
|
|
|
4728
4359
|
continue;
|
|
4729
4360
|
}
|
|
4730
4361
|
|
|
4731
|
-
const
|
|
4732
|
-
|
|
4362
|
+
const record = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
4363
|
+
? payload as Record<string, unknown>
|
|
4364
|
+
: null;
|
|
4365
|
+
const payloadType = eventType !== "message"
|
|
4366
|
+
? eventType
|
|
4367
|
+
: typeof record?.type === "string" ? record.type : "message";
|
|
4733
4368
|
|
|
4734
4369
|
// Tap: capture raw SSE event for event stream inspector
|
|
4735
4370
|
this.onSSEEvent?.(payloadType, payload);
|
|
@@ -4759,8 +4394,13 @@ export class AgentWidgetClient {
|
|
|
4759
4394
|
// The wire is the wire vocabulary; the handler consumes it
|
|
4760
4395
|
// natively. The stream is single-connection and in order, so each frame
|
|
4761
4396
|
// drains straight through.
|
|
4762
|
-
|
|
4763
|
-
|
|
4397
|
+
if (record) {
|
|
4398
|
+
// The raw tap and custom parser above retain the original payload.
|
|
4399
|
+
// Known native branches use the generated union; unknown event types
|
|
4400
|
+
// fall through without rejecting a custom backend's stream.
|
|
4401
|
+
eventQueue.push({ ...record, type: payloadType } as RuntypeExecutionStreamEvent);
|
|
4402
|
+
drainReadyQueue();
|
|
4403
|
+
}
|
|
4764
4404
|
advanceCursor();
|
|
4765
4405
|
}
|
|
4766
4406
|
}
|