@ai-sdk/harness-opencode 1.0.40 → 1.0.42
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/CHANGELOG.md +18 -0
- package/dist/bridge/index.mjs +455 -405
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +512 -0
- package/src/bridge/index.ts +54 -495
- package/src/bridge/opencode-events.ts +22 -0
package/dist/bridge/index.mjs
CHANGED
|
@@ -53,6 +53,7 @@ async function runBridge(options) {
|
|
|
53
53
|
let currentBoundPort = 0;
|
|
54
54
|
let currentTurnState = "init";
|
|
55
55
|
let activeSocket;
|
|
56
|
+
let activeSocketReadyForLiveEvents = false;
|
|
56
57
|
let isFirstTurn = true;
|
|
57
58
|
let turnAbort;
|
|
58
59
|
let currentUserMessages;
|
|
@@ -152,7 +153,7 @@ async function runBridge(options) {
|
|
|
152
153
|
diskBuffer += `${line}
|
|
153
154
|
`;
|
|
154
155
|
scheduleEventFlush();
|
|
155
|
-
if (activeSocket?.readyState === WS_OPEN) {
|
|
156
|
+
if (activeSocketReadyForLiveEvents && activeSocket?.readyState === WS_OPEN) {
|
|
156
157
|
try {
|
|
157
158
|
activeSocket.send(line);
|
|
158
159
|
} catch {
|
|
@@ -255,6 +256,8 @@ async function runBridge(options) {
|
|
|
255
256
|
const handleInbound = async (msg, ws) => {
|
|
256
257
|
switch (msg.type) {
|
|
257
258
|
case "start": {
|
|
259
|
+
if (activeSocket !== ws) return;
|
|
260
|
+
activeSocketReadyForLiveEvents = true;
|
|
258
261
|
const firstTurn = isFirstTurn;
|
|
259
262
|
isFirstTurn = false;
|
|
260
263
|
eventLog = [];
|
|
@@ -357,7 +360,9 @@ async function runBridge(options) {
|
|
|
357
360
|
}
|
|
358
361
|
return;
|
|
359
362
|
case "resume":
|
|
363
|
+
if (activeSocket !== ws) return;
|
|
360
364
|
replay(ws, msg.lastSeenEventId);
|
|
365
|
+
activeSocketReadyForLiveEvents = true;
|
|
361
366
|
return;
|
|
362
367
|
case "shutdown":
|
|
363
368
|
currentTurnState = "done";
|
|
@@ -422,6 +427,7 @@ async function runBridge(options) {
|
|
|
422
427
|
return;
|
|
423
428
|
}
|
|
424
429
|
activeSocket = ws;
|
|
430
|
+
activeSocketReadyForLiveEvents = false;
|
|
425
431
|
sendControl({
|
|
426
432
|
type: "bridge-hello",
|
|
427
433
|
state: currentTurnState,
|
|
@@ -444,6 +450,7 @@ async function runBridge(options) {
|
|
|
444
450
|
ws.on("close", () => {
|
|
445
451
|
if (activeSocket === ws) {
|
|
446
452
|
activeSocket = void 0;
|
|
453
|
+
activeSocketReadyForLiveEvents = false;
|
|
447
454
|
}
|
|
448
455
|
});
|
|
449
456
|
ws.on("error", () => {
|
|
@@ -490,6 +497,7 @@ import {
|
|
|
490
497
|
// src/bridge/opencode-events.ts
|
|
491
498
|
function createTranslationState() {
|
|
492
499
|
return {
|
|
500
|
+
streamStarted: false,
|
|
493
501
|
textDeltas: /* @__PURE__ */ new Map(),
|
|
494
502
|
reasoningDeltas: /* @__PURE__ */ new Map(),
|
|
495
503
|
toolInputs: /* @__PURE__ */ new Map(),
|
|
@@ -505,6 +513,19 @@ function createTranslationState() {
|
|
|
505
513
|
legacyStepFinishPartIds: /* @__PURE__ */ new Set()
|
|
506
514
|
};
|
|
507
515
|
}
|
|
516
|
+
function emitOpenCodeStreamStart({
|
|
517
|
+
info,
|
|
518
|
+
state,
|
|
519
|
+
emit
|
|
520
|
+
}) {
|
|
521
|
+
if (state.streamStarted || !isRecord(info)) return;
|
|
522
|
+
if (info.role !== "assistant" && info.type !== "assistant") return;
|
|
523
|
+
const providerID = stringValue(info.providerID);
|
|
524
|
+
const modelID = stringValue(info.modelID);
|
|
525
|
+
const modelId = providerID && modelID ? `${providerID}/${modelID}` : void 0;
|
|
526
|
+
state.streamStarted = true;
|
|
527
|
+
emit({ type: "stream-start", ...modelId ? { modelId } : {} });
|
|
528
|
+
}
|
|
508
529
|
function unwrapOpenCodeEvent(rawEvent) {
|
|
509
530
|
if (!rawEvent || typeof rawEvent !== "object") return void 0;
|
|
510
531
|
const raw = rawEvent;
|
|
@@ -810,6 +831,393 @@ function isRecord2(value) {
|
|
|
810
831
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
811
832
|
}
|
|
812
833
|
|
|
834
|
+
// src/bridge/create-emit-stream-event.ts
|
|
835
|
+
function createEmitStreamEvent({
|
|
836
|
+
state,
|
|
837
|
+
emit,
|
|
838
|
+
emitWarning,
|
|
839
|
+
emitError,
|
|
840
|
+
toWireToolName: toWireToolName2,
|
|
841
|
+
nativeNameField: nativeNameField2,
|
|
842
|
+
getHostToolName: getHostToolName2,
|
|
843
|
+
authorizeHostToolCall: authorizeHostToolCall2,
|
|
844
|
+
stripWorkDir: stripWorkDir2,
|
|
845
|
+
formatError: formatError2
|
|
846
|
+
}) {
|
|
847
|
+
return (event) => {
|
|
848
|
+
const type = event.type;
|
|
849
|
+
const props = event.properties ?? {};
|
|
850
|
+
if (type === "message.updated") {
|
|
851
|
+
const info = props.info;
|
|
852
|
+
if (isRecord3(info)) {
|
|
853
|
+
const id = stringValue2(info.id);
|
|
854
|
+
const role = stringValue2(info.role);
|
|
855
|
+
if (id && role) state.messageRoles.set(id, role);
|
|
856
|
+
}
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (type === "message.part.delta") {
|
|
860
|
+
emitLegacyPartDelta({ props, state, emit });
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (type === "message.part.updated") {
|
|
864
|
+
if (emitLegacyTextPartUpdate({ part: props.part, state, emit })) return;
|
|
865
|
+
if (emitLegacyStepFinishPart({ part: props.part, state, emit })) return;
|
|
866
|
+
emitLegacyToolPart({
|
|
867
|
+
part: props.part,
|
|
868
|
+
state,
|
|
869
|
+
emit,
|
|
870
|
+
toWireToolName: toWireToolName2,
|
|
871
|
+
nativeNameField: nativeNameField2,
|
|
872
|
+
getHostToolName: getHostToolName2,
|
|
873
|
+
authorizeHostToolCall: authorizeHostToolCall2
|
|
874
|
+
});
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (type === "session.next.text.started") {
|
|
878
|
+
emit({ type: "text-start", id: String(props.textID ?? event.id) });
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (type === "session.next.text.delta") {
|
|
882
|
+
const id = String(props.textID ?? event.id);
|
|
883
|
+
state.textDeltas.set(
|
|
884
|
+
id,
|
|
885
|
+
`${state.textDeltas.get(id) ?? ""}${String(props.delta ?? "")}`
|
|
886
|
+
);
|
|
887
|
+
emit({
|
|
888
|
+
type: "text-delta",
|
|
889
|
+
id,
|
|
890
|
+
delta: String(props.delta ?? "")
|
|
891
|
+
});
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (type === "session.next.text.ended") {
|
|
895
|
+
const id = String(props.textID ?? event.id);
|
|
896
|
+
emitMissingFinalDelta({
|
|
897
|
+
id,
|
|
898
|
+
fullText: typeof props.text === "string" ? props.text : void 0,
|
|
899
|
+
emittedText: state.textDeltas.get(id) ?? "",
|
|
900
|
+
emit,
|
|
901
|
+
type: "text-delta"
|
|
902
|
+
});
|
|
903
|
+
emit({ type: "text-end", id });
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (type === "session.next.reasoning.started") {
|
|
907
|
+
emit({
|
|
908
|
+
type: "reasoning-start",
|
|
909
|
+
id: String(props.reasoningID ?? event.id)
|
|
910
|
+
});
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
if (type === "session.next.reasoning.delta") {
|
|
914
|
+
const id = String(props.reasoningID ?? event.id);
|
|
915
|
+
state.reasoningDeltas.set(
|
|
916
|
+
id,
|
|
917
|
+
`${state.reasoningDeltas.get(id) ?? ""}${String(props.delta ?? "")}`
|
|
918
|
+
);
|
|
919
|
+
emit({
|
|
920
|
+
type: "reasoning-delta",
|
|
921
|
+
id,
|
|
922
|
+
delta: String(props.delta ?? "")
|
|
923
|
+
});
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (type === "session.next.reasoning.ended") {
|
|
927
|
+
const id = String(props.reasoningID ?? event.id);
|
|
928
|
+
emitMissingFinalDelta({
|
|
929
|
+
id,
|
|
930
|
+
fullText: typeof props.text === "string" ? props.text : void 0,
|
|
931
|
+
emittedText: state.reasoningDeltas.get(id) ?? "",
|
|
932
|
+
emit,
|
|
933
|
+
type: "reasoning-delta"
|
|
934
|
+
});
|
|
935
|
+
emit({ type: "reasoning-end", id });
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (type === "session.next.shell.started") {
|
|
939
|
+
const callID = String(props.callID ?? event.id);
|
|
940
|
+
const command = String(props.command ?? "");
|
|
941
|
+
state.shellCommands.set(callID, command);
|
|
942
|
+
emit({
|
|
943
|
+
type: "tool-call",
|
|
944
|
+
toolCallId: callID,
|
|
945
|
+
toolName: "bash",
|
|
946
|
+
nativeName: "bash",
|
|
947
|
+
input: JSON.stringify({ command }),
|
|
948
|
+
providerExecuted: true
|
|
949
|
+
});
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
if (type === "session.next.shell.ended") {
|
|
953
|
+
const callID = String(props.callID ?? event.id);
|
|
954
|
+
emit({
|
|
955
|
+
type: "tool-result",
|
|
956
|
+
toolCallId: callID,
|
|
957
|
+
toolName: "bash",
|
|
958
|
+
result: {
|
|
959
|
+
command: state.shellCommands.get(callID) ?? "",
|
|
960
|
+
output: String(props.output ?? "")
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (type === "session.next.tool.input.delta") {
|
|
966
|
+
const callID = String(props.callID ?? event.id);
|
|
967
|
+
state.toolInputs.set(
|
|
968
|
+
callID,
|
|
969
|
+
`${state.toolInputs.get(callID) ?? ""}${String(props.delta ?? "")}`
|
|
970
|
+
);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
if (type === "session.next.tool.input.ended") {
|
|
974
|
+
state.toolInputs.set(
|
|
975
|
+
String(props.callID ?? event.id),
|
|
976
|
+
String(props.text ?? "")
|
|
977
|
+
);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (type === "session.next.tool.called") {
|
|
981
|
+
const callID = String(props.callID ?? event.id);
|
|
982
|
+
const rawToolName = String(props.tool ?? "unknown");
|
|
983
|
+
const toolName = toWireToolName2(rawToolName);
|
|
984
|
+
state.toolNames.set(callID, { rawToolName, toolName });
|
|
985
|
+
const hostToolName = getHostToolName2(toolName, props.tool);
|
|
986
|
+
if (hostToolName) {
|
|
987
|
+
authorizeHostToolCall2({
|
|
988
|
+
callID,
|
|
989
|
+
toolName: hostToolName,
|
|
990
|
+
input: props.input ?? parseToolInput(state, props)
|
|
991
|
+
});
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
emit({
|
|
995
|
+
type: "tool-call",
|
|
996
|
+
toolCallId: callID,
|
|
997
|
+
toolName,
|
|
998
|
+
...nativeNameField2({ nativeName: rawToolName, toolName }),
|
|
999
|
+
input: JSON.stringify(props.input ?? parseToolInput(state, props)),
|
|
1000
|
+
providerExecuted: true,
|
|
1001
|
+
...props.provider?.metadata ? { providerMetadata: props.provider.metadata } : {}
|
|
1002
|
+
});
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if (type === "session.next.tool.success" || type === "session.next.tool.failed") {
|
|
1006
|
+
const callID = String(props.callID ?? event.id);
|
|
1007
|
+
const cachedTool = state.toolNames.get(callID);
|
|
1008
|
+
const rawToolName = cachedTool?.rawToolName ?? String(props.tool ?? "");
|
|
1009
|
+
const toolName = cachedTool?.toolName ?? toWireToolName2(rawToolName || "unknown");
|
|
1010
|
+
if (getHostToolName2(toolName, rawToolName)) return;
|
|
1011
|
+
emit({
|
|
1012
|
+
type: "tool-result",
|
|
1013
|
+
toolCallId: callID,
|
|
1014
|
+
toolName,
|
|
1015
|
+
result: props.result ?? props.structured ?? ("content" in props ? props.content : null) ?? null,
|
|
1016
|
+
...type === "session.next.tool.failed" ? { isError: true } : {}
|
|
1017
|
+
});
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (type === "session.next.retried") {
|
|
1021
|
+
const error = props.error ?? event;
|
|
1022
|
+
if (isRecord3(error) && error.isRetryable === false) {
|
|
1023
|
+
emitError({
|
|
1024
|
+
error,
|
|
1025
|
+
message: "OpenCode session retry failed"
|
|
1026
|
+
});
|
|
1027
|
+
} else {
|
|
1028
|
+
emitWarning({ message: nextRetryEventMessage({ event, formatError: formatError2 }) });
|
|
1029
|
+
}
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (type === "session.next.step.ended") {
|
|
1033
|
+
closeLegacyOpenParts({ state, emit });
|
|
1034
|
+
state.turnUsage = mapUsage(props.tokens);
|
|
1035
|
+
emit({
|
|
1036
|
+
type: "finish-step",
|
|
1037
|
+
finishReason: {
|
|
1038
|
+
unified: mapOpenCodeFinishReason(String(props.finish ?? "stop")),
|
|
1039
|
+
raw: String(props.finish ?? "stop")
|
|
1040
|
+
},
|
|
1041
|
+
usage: state.turnUsage,
|
|
1042
|
+
...typeof props.cost === "number" ? { harnessMetadata: { opencode: { cost: props.cost } } } : {}
|
|
1043
|
+
});
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (type === "session.next.compaction.ended") {
|
|
1047
|
+
emit({
|
|
1048
|
+
type: "compaction",
|
|
1049
|
+
trigger: props.reason === "auto" ? "auto" : "manual",
|
|
1050
|
+
summary: String(props.text ?? ""),
|
|
1051
|
+
harnessMetadata: {
|
|
1052
|
+
opencode: {
|
|
1053
|
+
recent: String(props.recent ?? "")
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
if (type === "file.edited") {
|
|
1060
|
+
emit({
|
|
1061
|
+
type: "file-change",
|
|
1062
|
+
event: "modify",
|
|
1063
|
+
path: stripWorkDir2(String(props.file ?? ""))
|
|
1064
|
+
});
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
if (type === "session.error" || type === "session.next.step.failed") {
|
|
1068
|
+
const error = props.error ?? event;
|
|
1069
|
+
emitError({
|
|
1070
|
+
error,
|
|
1071
|
+
message: type === "session.error" ? "OpenCode session error" : "OpenCode step failed"
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function closeLegacyOpenParts({
|
|
1077
|
+
state,
|
|
1078
|
+
emit
|
|
1079
|
+
}) {
|
|
1080
|
+
for (const id of state.legacyReasoningPartIds) {
|
|
1081
|
+
emit({ type: "reasoning-end", id });
|
|
1082
|
+
state.reasoningDeltas.delete(id);
|
|
1083
|
+
}
|
|
1084
|
+
state.legacyReasoningPartIds.clear();
|
|
1085
|
+
for (const id of state.legacyTextPartIds) {
|
|
1086
|
+
emit({ type: "text-end", id });
|
|
1087
|
+
state.textDeltas.delete(id);
|
|
1088
|
+
}
|
|
1089
|
+
state.legacyTextPartIds.clear();
|
|
1090
|
+
}
|
|
1091
|
+
function emitLegacyStepFinishPart({
|
|
1092
|
+
part,
|
|
1093
|
+
state,
|
|
1094
|
+
emit
|
|
1095
|
+
}) {
|
|
1096
|
+
const event = legacyStepFinishPartToFinishStep(part);
|
|
1097
|
+
if (!event) return false;
|
|
1098
|
+
const id = isRecord3(part) ? stringValue2(part.id) : void 0;
|
|
1099
|
+
if (id) {
|
|
1100
|
+
if (state.legacyStepFinishPartIds.has(id)) return true;
|
|
1101
|
+
state.legacyStepFinishPartIds.add(id);
|
|
1102
|
+
}
|
|
1103
|
+
closeLegacyOpenParts({ state, emit });
|
|
1104
|
+
state.turnUsage = event.usage;
|
|
1105
|
+
emit(event);
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
function emitLegacyToolPart({
|
|
1109
|
+
part,
|
|
1110
|
+
state,
|
|
1111
|
+
emit,
|
|
1112
|
+
toWireToolName: toWireToolName2,
|
|
1113
|
+
nativeNameField: nativeNameField2,
|
|
1114
|
+
getHostToolName: getHostToolName2,
|
|
1115
|
+
authorizeHostToolCall: authorizeHostToolCall2
|
|
1116
|
+
}) {
|
|
1117
|
+
if (!part || typeof part !== "object") return;
|
|
1118
|
+
const toolPart = part;
|
|
1119
|
+
if (toolPart.type !== "tool") return;
|
|
1120
|
+
const status = legacyToolPartStatus(toolPart);
|
|
1121
|
+
if (status !== "running" && status !== "completed" && status !== "error") {
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
if (typeof toolPart.tool !== "string" || typeof toolPart.callID !== "string") {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const callID = toolPart.callID;
|
|
1128
|
+
const rawToolName = toolPart.tool;
|
|
1129
|
+
const toolName = toWireToolName2(rawToolName);
|
|
1130
|
+
state.toolNames.set(callID, { rawToolName, toolName });
|
|
1131
|
+
const hostToolName = getHostToolName2(toolName, rawToolName);
|
|
1132
|
+
if (hostToolName) {
|
|
1133
|
+
if (status === "running") {
|
|
1134
|
+
authorizeHostToolCall2({
|
|
1135
|
+
callID,
|
|
1136
|
+
toolName: hostToolName,
|
|
1137
|
+
input: legacyToolPartInput(toolPart)
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (!state.toolCallsEmitted.has(callID)) {
|
|
1143
|
+
state.toolCallsEmitted.add(callID);
|
|
1144
|
+
emit({
|
|
1145
|
+
type: "tool-call",
|
|
1146
|
+
toolCallId: callID,
|
|
1147
|
+
toolName,
|
|
1148
|
+
...nativeNameField2({ nativeName: rawToolName, toolName }),
|
|
1149
|
+
input: JSON.stringify(legacyToolPartInput(toolPart)),
|
|
1150
|
+
providerExecuted: true,
|
|
1151
|
+
...toolPart.provider?.metadata ? { providerMetadata: toolPart.provider.metadata } : {}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
if ((status === "completed" || status === "error") && !state.toolResultsEmitted.has(callID)) {
|
|
1155
|
+
state.toolResultsEmitted.add(callID);
|
|
1156
|
+
emit({
|
|
1157
|
+
type: "tool-result",
|
|
1158
|
+
toolCallId: callID,
|
|
1159
|
+
toolName,
|
|
1160
|
+
result: legacyToolPartOutput(toolPart),
|
|
1161
|
+
...status === "error" ? { isError: true } : {}
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
function legacyToolPartStatus(part) {
|
|
1166
|
+
return typeof part.state === "string" ? part.state : typeof part.state === "object" && part.state !== null ? String(part.state.status ?? "") : void 0;
|
|
1167
|
+
}
|
|
1168
|
+
function legacyToolPartInput(part) {
|
|
1169
|
+
const state = typeof part.state === "object" && part.state !== null ? part.state : void 0;
|
|
1170
|
+
return {
|
|
1171
|
+
...isRecord3(part.metadata) ? part.metadata : {},
|
|
1172
|
+
...isRecord3(state?.metadata) ? state.metadata : {},
|
|
1173
|
+
...isRecord3(state?.input) ? state.input : {}
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function legacyToolPartOutput(part) {
|
|
1177
|
+
const state = typeof part.state === "object" && part.state !== null ? part.state : void 0;
|
|
1178
|
+
if (state?.status === "error") {
|
|
1179
|
+
return state.error ?? part.error ?? state.result ?? "tool failed";
|
|
1180
|
+
}
|
|
1181
|
+
return state?.output ?? state?.result ?? state?.structured ?? state?.content ?? null;
|
|
1182
|
+
}
|
|
1183
|
+
function parseToolInput(state, props) {
|
|
1184
|
+
const text = state.toolInputs.get(String(props.callID ?? ""));
|
|
1185
|
+
if (!text) return {};
|
|
1186
|
+
try {
|
|
1187
|
+
return JSON.parse(text);
|
|
1188
|
+
} catch {
|
|
1189
|
+
return { input: text };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
function nextRetryEventMessage({
|
|
1193
|
+
event,
|
|
1194
|
+
formatError: formatError2
|
|
1195
|
+
}) {
|
|
1196
|
+
const props = event.properties ?? {};
|
|
1197
|
+
const details = [];
|
|
1198
|
+
if (typeof props.attempt === "number") {
|
|
1199
|
+
details.push(`attempt ${props.attempt}`);
|
|
1200
|
+
}
|
|
1201
|
+
const error = props.error;
|
|
1202
|
+
if (isRecord3(error)) {
|
|
1203
|
+
const message = stringValue2(error.message) ?? (isRecord3(error.data) ? stringValue2(error.data.message) : void 0);
|
|
1204
|
+
const statusCode = error.statusCode;
|
|
1205
|
+
if (typeof statusCode === "number") {
|
|
1206
|
+
details.push(`HTTP ${statusCode}`);
|
|
1207
|
+
}
|
|
1208
|
+
if (message) details.push(message);
|
|
1209
|
+
} else if (error != null) {
|
|
1210
|
+
details.push(formatError2(error));
|
|
1211
|
+
}
|
|
1212
|
+
return details.length > 0 ? `OpenCode session retry: ${details.join("; ")}` : "OpenCode session retry";
|
|
1213
|
+
}
|
|
1214
|
+
function isRecord3(value) {
|
|
1215
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1216
|
+
}
|
|
1217
|
+
function stringValue2(value) {
|
|
1218
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
813
1221
|
// src/bridge/opencode-path.ts
|
|
814
1222
|
import path from "path";
|
|
815
1223
|
var fallbackPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
@@ -1335,25 +1743,6 @@ function legacyRetryStatusMessage(event) {
|
|
|
1335
1743
|
}
|
|
1336
1744
|
return details.length > 0 ? `OpenCode session retry: ${details.join("; ")}` : "OpenCode session retry";
|
|
1337
1745
|
}
|
|
1338
|
-
function nextRetryEventMessage(event) {
|
|
1339
|
-
const props = event.properties ?? {};
|
|
1340
|
-
const details = [];
|
|
1341
|
-
if (typeof props.attempt === "number") {
|
|
1342
|
-
details.push(`attempt ${props.attempt}`);
|
|
1343
|
-
}
|
|
1344
|
-
const error = props.error;
|
|
1345
|
-
if (isRecord3(error)) {
|
|
1346
|
-
const message = stringValue2(error.message) ?? (isRecord3(error.data) ? stringValue2(error.data.message) : void 0);
|
|
1347
|
-
const statusCode = error.statusCode;
|
|
1348
|
-
if (typeof statusCode === "number") {
|
|
1349
|
-
details.push(`HTTP ${statusCode}`);
|
|
1350
|
-
}
|
|
1351
|
-
if (message) details.push(message);
|
|
1352
|
-
} else if (error != null) {
|
|
1353
|
-
details.push(formatError(error));
|
|
1354
|
-
}
|
|
1355
|
-
return details.length > 0 ? `OpenCode session retry: ${details.join("; ")}` : "OpenCode session retry";
|
|
1356
|
-
}
|
|
1357
1746
|
async function ensureSession({
|
|
1358
1747
|
client,
|
|
1359
1748
|
start,
|
|
@@ -1396,6 +1785,7 @@ async function runPrompt({
|
|
|
1396
1785
|
let sawFinishStep = false;
|
|
1397
1786
|
let sawBusy = false;
|
|
1398
1787
|
let terminalError;
|
|
1788
|
+
const state = createTranslationState();
|
|
1399
1789
|
const initialSessionTokens = await readSessionTokens({
|
|
1400
1790
|
client,
|
|
1401
1791
|
sessionId
|
|
@@ -1409,6 +1799,7 @@ async function runPrompt({
|
|
|
1409
1799
|
permissionMode: start.permissionMode,
|
|
1410
1800
|
builtinToolFiltering: start.builtinToolFiltering,
|
|
1411
1801
|
turn,
|
|
1802
|
+
state,
|
|
1412
1803
|
emit: (msg) => {
|
|
1413
1804
|
if (msg.type === "text-delta" || msg.type === "reasoning-delta") {
|
|
1414
1805
|
sawContent = true;
|
|
@@ -1425,6 +1816,13 @@ async function runPrompt({
|
|
|
1425
1816
|
signal: eventsAbort.signal,
|
|
1426
1817
|
onSubscribed: () => eventsReady.resolve(void 0),
|
|
1427
1818
|
onEvent: (event) => {
|
|
1819
|
+
if (event.type === "message.updated") {
|
|
1820
|
+
emitOpenCodeStreamStart({
|
|
1821
|
+
info: event.properties?.info,
|
|
1822
|
+
state,
|
|
1823
|
+
emit
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1428
1826
|
if (event.type === "session.updated") {
|
|
1429
1827
|
latestSessionTokens = extractSessionTokens(event.properties) ?? latestSessionTokens;
|
|
1430
1828
|
}
|
|
@@ -1452,10 +1850,6 @@ async function runPrompt({
|
|
|
1452
1850
|
eventsReady.resolve(void 0);
|
|
1453
1851
|
turnSettled.resolve();
|
|
1454
1852
|
});
|
|
1455
|
-
emit({
|
|
1456
|
-
type: "stream-start",
|
|
1457
|
-
...start.model ? { modelId: start.model } : {}
|
|
1458
|
-
});
|
|
1459
1853
|
await eventsReady.promise;
|
|
1460
1854
|
const prompted = await legacySessionPrompt({
|
|
1461
1855
|
client,
|
|
@@ -1475,6 +1869,7 @@ async function runPrompt({
|
|
|
1475
1869
|
const emittedFallback = await emitContextFallback({
|
|
1476
1870
|
client,
|
|
1477
1871
|
sessionId,
|
|
1872
|
+
state,
|
|
1478
1873
|
emit,
|
|
1479
1874
|
emitContent: !sawContent
|
|
1480
1875
|
}).catch(() => false);
|
|
@@ -1526,6 +1921,7 @@ async function runCompaction({
|
|
|
1526
1921
|
permissionMode: start.permissionMode,
|
|
1527
1922
|
builtinToolFiltering: start.builtinToolFiltering,
|
|
1528
1923
|
turn,
|
|
1924
|
+
state: createTranslationState(),
|
|
1529
1925
|
emit: (msg) => {
|
|
1530
1926
|
if (msg.type === "compaction") sawCompaction = true;
|
|
1531
1927
|
emit(msg);
|
|
@@ -1586,6 +1982,7 @@ async function consumeEvents({
|
|
|
1586
1982
|
permissionMode,
|
|
1587
1983
|
builtinToolFiltering,
|
|
1588
1984
|
turn,
|
|
1985
|
+
state,
|
|
1589
1986
|
emit,
|
|
1590
1987
|
signal,
|
|
1591
1988
|
onSubscribed,
|
|
@@ -1594,393 +1991,47 @@ async function consumeEvents({
|
|
|
1594
1991
|
const stream = await subscribeLegacyEvents({ client, signal });
|
|
1595
1992
|
onSubscribed?.();
|
|
1596
1993
|
if (!stream) return;
|
|
1597
|
-
const
|
|
1994
|
+
const emitStreamEvent = createEmitStreamEvent({
|
|
1995
|
+
state,
|
|
1996
|
+
emit,
|
|
1997
|
+
emitWarning: turn.emitWarning,
|
|
1998
|
+
emitError: turn.emitError,
|
|
1999
|
+
toWireToolName,
|
|
2000
|
+
nativeNameField,
|
|
2001
|
+
getHostToolName,
|
|
2002
|
+
authorizeHostToolCall: (input) => authorizeHostToolCall({ ...input, state }),
|
|
2003
|
+
stripWorkDir,
|
|
2004
|
+
formatError
|
|
2005
|
+
});
|
|
1598
2006
|
for await (const rawEvent of stream) {
|
|
1599
2007
|
if (signal.aborted || turn.abortSignal.aborted) break;
|
|
1600
2008
|
const event = unwrapOpenCodeEvent(rawEvent);
|
|
1601
2009
|
const eventSessionId = event ? getOpenCodeEventSessionId(event) : void 0;
|
|
1602
2010
|
if (!event || eventSessionId && eventSessionId !== sessionId) continue;
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
});
|
|
1613
|
-
if (onEvent?.(event)) break;
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
async function translateAndEmit({
|
|
1617
|
-
event,
|
|
1618
|
-
state,
|
|
1619
|
-
sessionId,
|
|
1620
|
-
permissionMode,
|
|
1621
|
-
builtinToolFiltering,
|
|
1622
|
-
client,
|
|
1623
|
-
turn,
|
|
1624
|
-
emit
|
|
1625
|
-
}) {
|
|
1626
|
-
const type = event.type;
|
|
1627
|
-
const props = event.properties ?? {};
|
|
1628
|
-
if (type === "message.updated") {
|
|
1629
|
-
const info = props.info;
|
|
1630
|
-
if (isRecord3(info)) {
|
|
1631
|
-
const id = stringValue2(info.id);
|
|
1632
|
-
const role = stringValue2(info.role);
|
|
1633
|
-
if (id && role) state.messageRoles.set(id, role);
|
|
1634
|
-
}
|
|
1635
|
-
return;
|
|
1636
|
-
}
|
|
1637
|
-
if (type === "message.part.delta") {
|
|
1638
|
-
emitLegacyPartDelta({ props, state, emit });
|
|
1639
|
-
return;
|
|
1640
|
-
}
|
|
1641
|
-
if (type === "message.part.updated") {
|
|
1642
|
-
if (emitLegacyTextPartUpdate({ part: props.part, state, emit })) return;
|
|
1643
|
-
if (emitLegacyStepFinishPart({ part: props.part, state, emit })) return;
|
|
1644
|
-
emitLegacyToolPart({ part: props.part, state, emit });
|
|
1645
|
-
return;
|
|
1646
|
-
}
|
|
1647
|
-
if (type === "session.next.text.started") {
|
|
1648
|
-
emit({ type: "text-start", id: String(props.textID ?? event.id) });
|
|
1649
|
-
return;
|
|
1650
|
-
}
|
|
1651
|
-
if (type === "session.next.text.delta") {
|
|
1652
|
-
const id = String(props.textID ?? event.id);
|
|
1653
|
-
state.textDeltas.set(
|
|
1654
|
-
id,
|
|
1655
|
-
`${state.textDeltas.get(id) ?? ""}${String(props.delta ?? "")}`
|
|
1656
|
-
);
|
|
1657
|
-
emit({
|
|
1658
|
-
type: "text-delta",
|
|
1659
|
-
id,
|
|
1660
|
-
delta: String(props.delta ?? "")
|
|
1661
|
-
});
|
|
1662
|
-
return;
|
|
1663
|
-
}
|
|
1664
|
-
if (type === "session.next.text.ended") {
|
|
1665
|
-
const id = String(props.textID ?? event.id);
|
|
1666
|
-
emitMissingFinalDelta({
|
|
1667
|
-
id,
|
|
1668
|
-
fullText: typeof props.text === "string" ? props.text : void 0,
|
|
1669
|
-
emittedText: state.textDeltas.get(id) ?? "",
|
|
1670
|
-
emit,
|
|
1671
|
-
type: "text-delta"
|
|
1672
|
-
});
|
|
1673
|
-
emit({ type: "text-end", id });
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
if (type === "session.next.reasoning.started") {
|
|
1677
|
-
emit({
|
|
1678
|
-
type: "reasoning-start",
|
|
1679
|
-
id: String(props.reasoningID ?? event.id)
|
|
1680
|
-
});
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
if (type === "session.next.reasoning.delta") {
|
|
1684
|
-
const id = String(props.reasoningID ?? event.id);
|
|
1685
|
-
state.reasoningDeltas.set(
|
|
1686
|
-
id,
|
|
1687
|
-
`${state.reasoningDeltas.get(id) ?? ""}${String(props.delta ?? "")}`
|
|
1688
|
-
);
|
|
1689
|
-
emit({
|
|
1690
|
-
type: "reasoning-delta",
|
|
1691
|
-
id,
|
|
1692
|
-
delta: String(props.delta ?? "")
|
|
1693
|
-
});
|
|
1694
|
-
return;
|
|
1695
|
-
}
|
|
1696
|
-
if (type === "session.next.reasoning.ended") {
|
|
1697
|
-
const id = String(props.reasoningID ?? event.id);
|
|
1698
|
-
emitMissingFinalDelta({
|
|
1699
|
-
id,
|
|
1700
|
-
fullText: typeof props.text === "string" ? props.text : void 0,
|
|
1701
|
-
emittedText: state.reasoningDeltas.get(id) ?? "",
|
|
1702
|
-
emit,
|
|
1703
|
-
type: "reasoning-delta"
|
|
1704
|
-
});
|
|
1705
|
-
emit({ type: "reasoning-end", id });
|
|
1706
|
-
return;
|
|
1707
|
-
}
|
|
1708
|
-
if (type === "session.next.shell.started") {
|
|
1709
|
-
const callID = String(props.callID ?? event.id);
|
|
1710
|
-
const command = String(props.command ?? "");
|
|
1711
|
-
state.shellCommands.set(callID, command);
|
|
1712
|
-
emit({
|
|
1713
|
-
type: "tool-call",
|
|
1714
|
-
toolCallId: callID,
|
|
1715
|
-
toolName: "bash",
|
|
1716
|
-
nativeName: "bash",
|
|
1717
|
-
input: JSON.stringify({ command }),
|
|
1718
|
-
providerExecuted: true
|
|
1719
|
-
});
|
|
1720
|
-
return;
|
|
1721
|
-
}
|
|
1722
|
-
if (type === "session.next.shell.ended") {
|
|
1723
|
-
const callID = String(props.callID ?? event.id);
|
|
1724
|
-
emit({
|
|
1725
|
-
type: "tool-result",
|
|
1726
|
-
toolCallId: callID,
|
|
1727
|
-
toolName: "bash",
|
|
1728
|
-
result: {
|
|
1729
|
-
command: state.shellCommands.get(callID) ?? "",
|
|
1730
|
-
output: String(props.output ?? "")
|
|
1731
|
-
}
|
|
1732
|
-
});
|
|
1733
|
-
return;
|
|
1734
|
-
}
|
|
1735
|
-
if (type === "session.next.tool.input.delta") {
|
|
1736
|
-
const callID = String(props.callID ?? event.id);
|
|
1737
|
-
state.toolInputs.set(
|
|
1738
|
-
callID,
|
|
1739
|
-
`${state.toolInputs.get(callID) ?? ""}${String(props.delta ?? "")}`
|
|
1740
|
-
);
|
|
1741
|
-
return;
|
|
1742
|
-
}
|
|
1743
|
-
if (type === "session.next.tool.input.ended") {
|
|
1744
|
-
state.toolInputs.set(
|
|
1745
|
-
String(props.callID ?? event.id),
|
|
1746
|
-
String(props.text ?? "")
|
|
1747
|
-
);
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
if (type === "session.next.tool.called") {
|
|
1751
|
-
const callID = String(props.callID ?? event.id);
|
|
1752
|
-
const rawToolName = String(props.tool ?? "unknown");
|
|
1753
|
-
const toolName = toWireToolName(rawToolName);
|
|
1754
|
-
state.toolNames.set(callID, { rawToolName, toolName });
|
|
1755
|
-
const hostToolName = getHostToolName(toolName, props.tool);
|
|
1756
|
-
if (hostToolName) {
|
|
1757
|
-
authorizeHostToolCall({
|
|
1758
|
-
callID,
|
|
1759
|
-
toolName: hostToolName,
|
|
1760
|
-
input: props.input ?? parseToolInput(state, props),
|
|
1761
|
-
state
|
|
2011
|
+
if (event.type === "permission.v2.asked") {
|
|
2012
|
+
await handlePermissionV2({
|
|
2013
|
+
client,
|
|
2014
|
+
sessionId,
|
|
2015
|
+
permissionMode,
|
|
2016
|
+
builtinToolFiltering,
|
|
2017
|
+
turn,
|
|
2018
|
+
emit,
|
|
2019
|
+
event
|
|
1762
2020
|
});
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
...props.provider?.metadata ? { providerMetadata: props.provider.metadata } : {}
|
|
1773
|
-
});
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
if (type === "session.next.tool.success" || type === "session.next.tool.failed") {
|
|
1777
|
-
const callID = String(props.callID ?? event.id);
|
|
1778
|
-
const cachedTool = state.toolNames.get(callID);
|
|
1779
|
-
const rawToolName = cachedTool?.rawToolName ?? String(props.tool ?? "");
|
|
1780
|
-
const toolName = cachedTool?.toolName ?? toWireToolName(rawToolName || "unknown");
|
|
1781
|
-
if (getHostToolName(toolName, rawToolName)) return;
|
|
1782
|
-
emit({
|
|
1783
|
-
type: "tool-result",
|
|
1784
|
-
toolCallId: callID,
|
|
1785
|
-
toolName,
|
|
1786
|
-
result: props.result ?? props.structured ?? ("content" in props ? props.content : null) ?? null,
|
|
1787
|
-
...type === "session.next.tool.failed" ? { isError: true } : {}
|
|
1788
|
-
});
|
|
1789
|
-
return;
|
|
1790
|
-
}
|
|
1791
|
-
if (type === "session.next.retried") {
|
|
1792
|
-
const error = props.error ?? event;
|
|
1793
|
-
if (isRecord3(error) && error.isRetryable === false) {
|
|
1794
|
-
turn.emitError({
|
|
1795
|
-
error,
|
|
1796
|
-
message: "OpenCode session retry failed"
|
|
2021
|
+
} else if (event.type === "permission.asked") {
|
|
2022
|
+
await handlePermission({
|
|
2023
|
+
client,
|
|
2024
|
+
sessionId,
|
|
2025
|
+
permissionMode,
|
|
2026
|
+
builtinToolFiltering,
|
|
2027
|
+
turn,
|
|
2028
|
+
emit,
|
|
2029
|
+
event
|
|
1797
2030
|
});
|
|
1798
2031
|
} else {
|
|
1799
|
-
|
|
1800
|
-
}
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
1803
|
-
if (type === "session.next.step.ended") {
|
|
1804
|
-
closeLegacyOpenParts({ state, emit });
|
|
1805
|
-
state.turnUsage = mapUsage(props.tokens);
|
|
1806
|
-
emit({
|
|
1807
|
-
type: "finish-step",
|
|
1808
|
-
finishReason: {
|
|
1809
|
-
unified: mapOpenCodeFinishReason(String(props.finish ?? "stop")),
|
|
1810
|
-
raw: String(props.finish ?? "stop")
|
|
1811
|
-
},
|
|
1812
|
-
usage: state.turnUsage,
|
|
1813
|
-
...typeof props.cost === "number" ? { harnessMetadata: { opencode: { cost: props.cost } } } : {}
|
|
1814
|
-
});
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1817
|
-
if (type === "session.next.compaction.ended") {
|
|
1818
|
-
emit({
|
|
1819
|
-
type: "compaction",
|
|
1820
|
-
trigger: props.reason === "auto" ? "auto" : "manual",
|
|
1821
|
-
summary: String(props.text ?? ""),
|
|
1822
|
-
harnessMetadata: {
|
|
1823
|
-
opencode: {
|
|
1824
|
-
recent: String(props.recent ?? "")
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
});
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
if (type === "file.edited") {
|
|
1831
|
-
emit({
|
|
1832
|
-
type: "file-change",
|
|
1833
|
-
event: "modify",
|
|
1834
|
-
path: stripWorkDir(String(props.file ?? ""))
|
|
1835
|
-
});
|
|
1836
|
-
return;
|
|
1837
|
-
}
|
|
1838
|
-
if (type === "session.error" || type === "session.next.step.failed") {
|
|
1839
|
-
const error = props.error ?? event;
|
|
1840
|
-
turn.emitError({
|
|
1841
|
-
error,
|
|
1842
|
-
message: type === "session.error" ? "OpenCode session error" : "OpenCode step failed"
|
|
1843
|
-
});
|
|
1844
|
-
return;
|
|
1845
|
-
}
|
|
1846
|
-
if (type === "permission.v2.asked") {
|
|
1847
|
-
await handlePermissionV2({
|
|
1848
|
-
client,
|
|
1849
|
-
sessionId,
|
|
1850
|
-
permissionMode,
|
|
1851
|
-
builtinToolFiltering,
|
|
1852
|
-
turn,
|
|
1853
|
-
emit,
|
|
1854
|
-
event
|
|
1855
|
-
});
|
|
1856
|
-
return;
|
|
1857
|
-
}
|
|
1858
|
-
if (type === "permission.asked") {
|
|
1859
|
-
await handlePermission({
|
|
1860
|
-
client,
|
|
1861
|
-
sessionId,
|
|
1862
|
-
permissionMode,
|
|
1863
|
-
builtinToolFiltering,
|
|
1864
|
-
turn,
|
|
1865
|
-
emit,
|
|
1866
|
-
event
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
function closeLegacyOpenParts({
|
|
1871
|
-
state,
|
|
1872
|
-
emit
|
|
1873
|
-
}) {
|
|
1874
|
-
for (const id of state.legacyReasoningPartIds) {
|
|
1875
|
-
emit({ type: "reasoning-end", id });
|
|
1876
|
-
state.reasoningDeltas.delete(id);
|
|
1877
|
-
}
|
|
1878
|
-
state.legacyReasoningPartIds.clear();
|
|
1879
|
-
for (const id of state.legacyTextPartIds) {
|
|
1880
|
-
emit({ type: "text-end", id });
|
|
1881
|
-
state.textDeltas.delete(id);
|
|
1882
|
-
}
|
|
1883
|
-
state.legacyTextPartIds.clear();
|
|
1884
|
-
}
|
|
1885
|
-
function emitLegacyStepFinishPart({
|
|
1886
|
-
part,
|
|
1887
|
-
state,
|
|
1888
|
-
emit
|
|
1889
|
-
}) {
|
|
1890
|
-
const event = legacyStepFinishPartToFinishStep(part);
|
|
1891
|
-
if (!event) return false;
|
|
1892
|
-
const id = isRecord3(part) ? stringValue2(part.id) : void 0;
|
|
1893
|
-
if (id) {
|
|
1894
|
-
if (state.legacyStepFinishPartIds.has(id)) return true;
|
|
1895
|
-
state.legacyStepFinishPartIds.add(id);
|
|
1896
|
-
}
|
|
1897
|
-
closeLegacyOpenParts({ state, emit });
|
|
1898
|
-
state.turnUsage = event.usage;
|
|
1899
|
-
emit(event);
|
|
1900
|
-
return true;
|
|
1901
|
-
}
|
|
1902
|
-
function emitLegacyToolPart({
|
|
1903
|
-
part,
|
|
1904
|
-
state,
|
|
1905
|
-
emit
|
|
1906
|
-
}) {
|
|
1907
|
-
if (!part || typeof part !== "object") return;
|
|
1908
|
-
const toolPart = part;
|
|
1909
|
-
if (toolPart.type !== "tool") return;
|
|
1910
|
-
const status = legacyToolPartStatus(toolPart);
|
|
1911
|
-
if (status !== "running" && status !== "completed" && status !== "error") {
|
|
1912
|
-
return;
|
|
1913
|
-
}
|
|
1914
|
-
if (typeof toolPart.tool !== "string" || typeof toolPart.callID !== "string") {
|
|
1915
|
-
return;
|
|
1916
|
-
}
|
|
1917
|
-
const callID = toolPart.callID;
|
|
1918
|
-
const rawToolName = toolPart.tool;
|
|
1919
|
-
const toolName = toWireToolName(rawToolName);
|
|
1920
|
-
state.toolNames.set(callID, { rawToolName, toolName });
|
|
1921
|
-
const hostToolName = getHostToolName(toolName, rawToolName);
|
|
1922
|
-
if (hostToolName) {
|
|
1923
|
-
if (status === "running") {
|
|
1924
|
-
authorizeHostToolCall({
|
|
1925
|
-
callID,
|
|
1926
|
-
toolName: hostToolName,
|
|
1927
|
-
input: legacyToolPartInput(toolPart),
|
|
1928
|
-
state
|
|
1929
|
-
});
|
|
2032
|
+
emitStreamEvent(event);
|
|
1930
2033
|
}
|
|
1931
|
-
|
|
1932
|
-
}
|
|
1933
|
-
if (!state.toolCallsEmitted.has(callID)) {
|
|
1934
|
-
state.toolCallsEmitted.add(callID);
|
|
1935
|
-
emit({
|
|
1936
|
-
type: "tool-call",
|
|
1937
|
-
toolCallId: callID,
|
|
1938
|
-
toolName,
|
|
1939
|
-
...nativeNameField({ nativeName: rawToolName, toolName }),
|
|
1940
|
-
input: JSON.stringify(legacyToolPartInput(toolPart)),
|
|
1941
|
-
providerExecuted: true,
|
|
1942
|
-
...toolPart.provider?.metadata ? { providerMetadata: toolPart.provider.metadata } : {}
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
|
-
if ((status === "completed" || status === "error") && !state.toolResultsEmitted.has(callID)) {
|
|
1946
|
-
state.toolResultsEmitted.add(callID);
|
|
1947
|
-
emit({
|
|
1948
|
-
type: "tool-result",
|
|
1949
|
-
toolCallId: callID,
|
|
1950
|
-
toolName,
|
|
1951
|
-
result: legacyToolPartOutput(toolPart),
|
|
1952
|
-
...status === "error" ? { isError: true } : {}
|
|
1953
|
-
});
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
function legacyToolPartStatus(part) {
|
|
1957
|
-
return typeof part.state === "string" ? part.state : typeof part.state === "object" && part.state !== null ? String(part.state.status ?? "") : void 0;
|
|
1958
|
-
}
|
|
1959
|
-
function legacyToolPartInput(part) {
|
|
1960
|
-
const state = typeof part.state === "object" && part.state !== null ? part.state : void 0;
|
|
1961
|
-
return {
|
|
1962
|
-
...isRecord3(part.metadata) ? part.metadata : {},
|
|
1963
|
-
...isRecord3(state?.metadata) ? state.metadata : {},
|
|
1964
|
-
...isRecord3(state?.input) ? state.input : {}
|
|
1965
|
-
};
|
|
1966
|
-
}
|
|
1967
|
-
function legacyToolPartOutput(part) {
|
|
1968
|
-
const state = typeof part.state === "object" && part.state !== null ? part.state : void 0;
|
|
1969
|
-
if (state?.status === "error") {
|
|
1970
|
-
return state.error ?? part.error ?? state.result ?? "tool failed";
|
|
1971
|
-
}
|
|
1972
|
-
return state?.output ?? state?.result ?? state?.structured ?? state?.content ?? null;
|
|
1973
|
-
}
|
|
1974
|
-
function isRecord3(value) {
|
|
1975
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1976
|
-
}
|
|
1977
|
-
function parseToolInput(state, props) {
|
|
1978
|
-
const text = state.toolInputs.get(String(props.callID ?? ""));
|
|
1979
|
-
if (!text) return {};
|
|
1980
|
-
try {
|
|
1981
|
-
return JSON.parse(text);
|
|
1982
|
-
} catch {
|
|
1983
|
-
return { input: text };
|
|
2034
|
+
if (onEvent?.(event)) break;
|
|
1984
2035
|
}
|
|
1985
2036
|
}
|
|
1986
2037
|
async function handlePermissionV2({
|
|
@@ -2153,11 +2204,13 @@ function authorizeHostToolCall({
|
|
|
2153
2204
|
async function emitContextFallback({
|
|
2154
2205
|
client,
|
|
2155
2206
|
sessionId,
|
|
2207
|
+
state,
|
|
2156
2208
|
emit,
|
|
2157
2209
|
emitContent
|
|
2158
2210
|
}) {
|
|
2159
2211
|
const assistant = await latestAssistantSnapshot({ client, sessionId });
|
|
2160
2212
|
if (!assistant) return false;
|
|
2213
|
+
emitOpenCodeStreamStart({ info: assistant, state, emit });
|
|
2161
2214
|
if (emitContent && Array.isArray(assistant.contentParts)) {
|
|
2162
2215
|
for (const part of assistant.contentParts) {
|
|
2163
2216
|
emitAssistantContentPart(part, emit);
|
|
@@ -2316,9 +2369,6 @@ function modelRefFromValue(value) {
|
|
|
2316
2369
|
if (!providerID || !modelID) return void 0;
|
|
2317
2370
|
return { providerID, modelID };
|
|
2318
2371
|
}
|
|
2319
|
-
function stringValue2(value) {
|
|
2320
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2321
|
-
}
|
|
2322
2372
|
function stripWorkDir(file) {
|
|
2323
2373
|
if (!file) return file;
|
|
2324
2374
|
const normalized = path2.resolve(file);
|