@ai-sdk/harness-opencode 1.0.41 → 1.0.43

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.
@@ -497,6 +497,7 @@ import {
497
497
  // src/bridge/opencode-events.ts
498
498
  function createTranslationState() {
499
499
  return {
500
+ streamStarted: false,
500
501
  textDeltas: /* @__PURE__ */ new Map(),
501
502
  reasoningDeltas: /* @__PURE__ */ new Map(),
502
503
  toolInputs: /* @__PURE__ */ new Map(),
@@ -512,6 +513,19 @@ function createTranslationState() {
512
513
  legacyStepFinishPartIds: /* @__PURE__ */ new Set()
513
514
  };
514
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
+ }
515
529
  function unwrapOpenCodeEvent(rawEvent) {
516
530
  if (!rawEvent || typeof rawEvent !== "object") return void 0;
517
531
  const raw = rawEvent;
@@ -817,6 +831,393 @@ function isRecord2(value) {
817
831
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
818
832
  }
819
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
+
820
1221
  // src/bridge/opencode-path.ts
821
1222
  import path from "path";
822
1223
  var fallbackPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
@@ -1342,25 +1743,6 @@ function legacyRetryStatusMessage(event) {
1342
1743
  }
1343
1744
  return details.length > 0 ? `OpenCode session retry: ${details.join("; ")}` : "OpenCode session retry";
1344
1745
  }
1345
- function nextRetryEventMessage(event) {
1346
- const props = event.properties ?? {};
1347
- const details = [];
1348
- if (typeof props.attempt === "number") {
1349
- details.push(`attempt ${props.attempt}`);
1350
- }
1351
- const error = props.error;
1352
- if (isRecord3(error)) {
1353
- const message = stringValue2(error.message) ?? (isRecord3(error.data) ? stringValue2(error.data.message) : void 0);
1354
- const statusCode = error.statusCode;
1355
- if (typeof statusCode === "number") {
1356
- details.push(`HTTP ${statusCode}`);
1357
- }
1358
- if (message) details.push(message);
1359
- } else if (error != null) {
1360
- details.push(formatError(error));
1361
- }
1362
- return details.length > 0 ? `OpenCode session retry: ${details.join("; ")}` : "OpenCode session retry";
1363
- }
1364
1746
  async function ensureSession({
1365
1747
  client,
1366
1748
  start,
@@ -1403,6 +1785,7 @@ async function runPrompt({
1403
1785
  let sawFinishStep = false;
1404
1786
  let sawBusy = false;
1405
1787
  let terminalError;
1788
+ const state = createTranslationState();
1406
1789
  const initialSessionTokens = await readSessionTokens({
1407
1790
  client,
1408
1791
  sessionId
@@ -1416,6 +1799,7 @@ async function runPrompt({
1416
1799
  permissionMode: start.permissionMode,
1417
1800
  builtinToolFiltering: start.builtinToolFiltering,
1418
1801
  turn,
1802
+ state,
1419
1803
  emit: (msg) => {
1420
1804
  if (msg.type === "text-delta" || msg.type === "reasoning-delta") {
1421
1805
  sawContent = true;
@@ -1432,6 +1816,13 @@ async function runPrompt({
1432
1816
  signal: eventsAbort.signal,
1433
1817
  onSubscribed: () => eventsReady.resolve(void 0),
1434
1818
  onEvent: (event) => {
1819
+ if (event.type === "message.updated") {
1820
+ emitOpenCodeStreamStart({
1821
+ info: event.properties?.info,
1822
+ state,
1823
+ emit
1824
+ });
1825
+ }
1435
1826
  if (event.type === "session.updated") {
1436
1827
  latestSessionTokens = extractSessionTokens(event.properties) ?? latestSessionTokens;
1437
1828
  }
@@ -1459,10 +1850,6 @@ async function runPrompt({
1459
1850
  eventsReady.resolve(void 0);
1460
1851
  turnSettled.resolve();
1461
1852
  });
1462
- emit({
1463
- type: "stream-start",
1464
- ...start.model ? { modelId: start.model } : {}
1465
- });
1466
1853
  await eventsReady.promise;
1467
1854
  const prompted = await legacySessionPrompt({
1468
1855
  client,
@@ -1482,6 +1869,7 @@ async function runPrompt({
1482
1869
  const emittedFallback = await emitContextFallback({
1483
1870
  client,
1484
1871
  sessionId,
1872
+ state,
1485
1873
  emit,
1486
1874
  emitContent: !sawContent
1487
1875
  }).catch(() => false);
@@ -1533,6 +1921,7 @@ async function runCompaction({
1533
1921
  permissionMode: start.permissionMode,
1534
1922
  builtinToolFiltering: start.builtinToolFiltering,
1535
1923
  turn,
1924
+ state: createTranslationState(),
1536
1925
  emit: (msg) => {
1537
1926
  if (msg.type === "compaction") sawCompaction = true;
1538
1927
  emit(msg);
@@ -1593,6 +1982,7 @@ async function consumeEvents({
1593
1982
  permissionMode,
1594
1983
  builtinToolFiltering,
1595
1984
  turn,
1985
+ state,
1596
1986
  emit,
1597
1987
  signal,
1598
1988
  onSubscribed,
@@ -1601,393 +1991,47 @@ async function consumeEvents({
1601
1991
  const stream = await subscribeLegacyEvents({ client, signal });
1602
1992
  onSubscribed?.();
1603
1993
  if (!stream) return;
1604
- const state = createTranslationState();
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
+ });
1605
2006
  for await (const rawEvent of stream) {
1606
2007
  if (signal.aborted || turn.abortSignal.aborted) break;
1607
2008
  const event = unwrapOpenCodeEvent(rawEvent);
1608
2009
  const eventSessionId = event ? getOpenCodeEventSessionId(event) : void 0;
1609
2010
  if (!event || eventSessionId && eventSessionId !== sessionId) continue;
1610
- await translateAndEmit({
1611
- event,
1612
- state,
1613
- sessionId,
1614
- permissionMode,
1615
- builtinToolFiltering,
1616
- client,
1617
- turn,
1618
- emit
1619
- });
1620
- if (onEvent?.(event)) break;
1621
- }
1622
- }
1623
- async function translateAndEmit({
1624
- event,
1625
- state,
1626
- sessionId,
1627
- permissionMode,
1628
- builtinToolFiltering,
1629
- client,
1630
- turn,
1631
- emit
1632
- }) {
1633
- const type = event.type;
1634
- const props = event.properties ?? {};
1635
- if (type === "message.updated") {
1636
- const info = props.info;
1637
- if (isRecord3(info)) {
1638
- const id = stringValue2(info.id);
1639
- const role = stringValue2(info.role);
1640
- if (id && role) state.messageRoles.set(id, role);
1641
- }
1642
- return;
1643
- }
1644
- if (type === "message.part.delta") {
1645
- emitLegacyPartDelta({ props, state, emit });
1646
- return;
1647
- }
1648
- if (type === "message.part.updated") {
1649
- if (emitLegacyTextPartUpdate({ part: props.part, state, emit })) return;
1650
- if (emitLegacyStepFinishPart({ part: props.part, state, emit })) return;
1651
- emitLegacyToolPart({ part: props.part, state, emit });
1652
- return;
1653
- }
1654
- if (type === "session.next.text.started") {
1655
- emit({ type: "text-start", id: String(props.textID ?? event.id) });
1656
- return;
1657
- }
1658
- if (type === "session.next.text.delta") {
1659
- const id = String(props.textID ?? event.id);
1660
- state.textDeltas.set(
1661
- id,
1662
- `${state.textDeltas.get(id) ?? ""}${String(props.delta ?? "")}`
1663
- );
1664
- emit({
1665
- type: "text-delta",
1666
- id,
1667
- delta: String(props.delta ?? "")
1668
- });
1669
- return;
1670
- }
1671
- if (type === "session.next.text.ended") {
1672
- const id = String(props.textID ?? event.id);
1673
- emitMissingFinalDelta({
1674
- id,
1675
- fullText: typeof props.text === "string" ? props.text : void 0,
1676
- emittedText: state.textDeltas.get(id) ?? "",
1677
- emit,
1678
- type: "text-delta"
1679
- });
1680
- emit({ type: "text-end", id });
1681
- return;
1682
- }
1683
- if (type === "session.next.reasoning.started") {
1684
- emit({
1685
- type: "reasoning-start",
1686
- id: String(props.reasoningID ?? event.id)
1687
- });
1688
- return;
1689
- }
1690
- if (type === "session.next.reasoning.delta") {
1691
- const id = String(props.reasoningID ?? event.id);
1692
- state.reasoningDeltas.set(
1693
- id,
1694
- `${state.reasoningDeltas.get(id) ?? ""}${String(props.delta ?? "")}`
1695
- );
1696
- emit({
1697
- type: "reasoning-delta",
1698
- id,
1699
- delta: String(props.delta ?? "")
1700
- });
1701
- return;
1702
- }
1703
- if (type === "session.next.reasoning.ended") {
1704
- const id = String(props.reasoningID ?? event.id);
1705
- emitMissingFinalDelta({
1706
- id,
1707
- fullText: typeof props.text === "string" ? props.text : void 0,
1708
- emittedText: state.reasoningDeltas.get(id) ?? "",
1709
- emit,
1710
- type: "reasoning-delta"
1711
- });
1712
- emit({ type: "reasoning-end", id });
1713
- return;
1714
- }
1715
- if (type === "session.next.shell.started") {
1716
- const callID = String(props.callID ?? event.id);
1717
- const command = String(props.command ?? "");
1718
- state.shellCommands.set(callID, command);
1719
- emit({
1720
- type: "tool-call",
1721
- toolCallId: callID,
1722
- toolName: "bash",
1723
- nativeName: "bash",
1724
- input: JSON.stringify({ command }),
1725
- providerExecuted: true
1726
- });
1727
- return;
1728
- }
1729
- if (type === "session.next.shell.ended") {
1730
- const callID = String(props.callID ?? event.id);
1731
- emit({
1732
- type: "tool-result",
1733
- toolCallId: callID,
1734
- toolName: "bash",
1735
- result: {
1736
- command: state.shellCommands.get(callID) ?? "",
1737
- output: String(props.output ?? "")
1738
- }
1739
- });
1740
- return;
1741
- }
1742
- if (type === "session.next.tool.input.delta") {
1743
- const callID = String(props.callID ?? event.id);
1744
- state.toolInputs.set(
1745
- callID,
1746
- `${state.toolInputs.get(callID) ?? ""}${String(props.delta ?? "")}`
1747
- );
1748
- return;
1749
- }
1750
- if (type === "session.next.tool.input.ended") {
1751
- state.toolInputs.set(
1752
- String(props.callID ?? event.id),
1753
- String(props.text ?? "")
1754
- );
1755
- return;
1756
- }
1757
- if (type === "session.next.tool.called") {
1758
- const callID = String(props.callID ?? event.id);
1759
- const rawToolName = String(props.tool ?? "unknown");
1760
- const toolName = toWireToolName(rawToolName);
1761
- state.toolNames.set(callID, { rawToolName, toolName });
1762
- const hostToolName = getHostToolName(toolName, props.tool);
1763
- if (hostToolName) {
1764
- authorizeHostToolCall({
1765
- callID,
1766
- toolName: hostToolName,
1767
- input: props.input ?? parseToolInput(state, props),
1768
- 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
1769
2020
  });
1770
- return;
1771
- }
1772
- emit({
1773
- type: "tool-call",
1774
- toolCallId: callID,
1775
- toolName,
1776
- ...nativeNameField({ nativeName: rawToolName, toolName }),
1777
- input: JSON.stringify(props.input ?? parseToolInput(state, props)),
1778
- providerExecuted: true,
1779
- ...props.provider?.metadata ? { providerMetadata: props.provider.metadata } : {}
1780
- });
1781
- return;
1782
- }
1783
- if (type === "session.next.tool.success" || type === "session.next.tool.failed") {
1784
- const callID = String(props.callID ?? event.id);
1785
- const cachedTool = state.toolNames.get(callID);
1786
- const rawToolName = cachedTool?.rawToolName ?? String(props.tool ?? "");
1787
- const toolName = cachedTool?.toolName ?? toWireToolName(rawToolName || "unknown");
1788
- if (getHostToolName(toolName, rawToolName)) return;
1789
- emit({
1790
- type: "tool-result",
1791
- toolCallId: callID,
1792
- toolName,
1793
- result: props.result ?? props.structured ?? ("content" in props ? props.content : null) ?? null,
1794
- ...type === "session.next.tool.failed" ? { isError: true } : {}
1795
- });
1796
- return;
1797
- }
1798
- if (type === "session.next.retried") {
1799
- const error = props.error ?? event;
1800
- if (isRecord3(error) && error.isRetryable === false) {
1801
- turn.emitError({
1802
- error,
1803
- 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
1804
2030
  });
1805
2031
  } else {
1806
- turn.emitWarning({ message: nextRetryEventMessage(event) });
2032
+ emitStreamEvent(event);
1807
2033
  }
1808
- return;
1809
- }
1810
- if (type === "session.next.step.ended") {
1811
- closeLegacyOpenParts({ state, emit });
1812
- state.turnUsage = mapUsage(props.tokens);
1813
- emit({
1814
- type: "finish-step",
1815
- finishReason: {
1816
- unified: mapOpenCodeFinishReason(String(props.finish ?? "stop")),
1817
- raw: String(props.finish ?? "stop")
1818
- },
1819
- usage: state.turnUsage,
1820
- ...typeof props.cost === "number" ? { harnessMetadata: { opencode: { cost: props.cost } } } : {}
1821
- });
1822
- return;
1823
- }
1824
- if (type === "session.next.compaction.ended") {
1825
- emit({
1826
- type: "compaction",
1827
- trigger: props.reason === "auto" ? "auto" : "manual",
1828
- summary: String(props.text ?? ""),
1829
- harnessMetadata: {
1830
- opencode: {
1831
- recent: String(props.recent ?? "")
1832
- }
1833
- }
1834
- });
1835
- return;
1836
- }
1837
- if (type === "file.edited") {
1838
- emit({
1839
- type: "file-change",
1840
- event: "modify",
1841
- path: stripWorkDir(String(props.file ?? ""))
1842
- });
1843
- return;
1844
- }
1845
- if (type === "session.error" || type === "session.next.step.failed") {
1846
- const error = props.error ?? event;
1847
- turn.emitError({
1848
- error,
1849
- message: type === "session.error" ? "OpenCode session error" : "OpenCode step failed"
1850
- });
1851
- return;
1852
- }
1853
- if (type === "permission.v2.asked") {
1854
- await handlePermissionV2({
1855
- client,
1856
- sessionId,
1857
- permissionMode,
1858
- builtinToolFiltering,
1859
- turn,
1860
- emit,
1861
- event
1862
- });
1863
- return;
1864
- }
1865
- if (type === "permission.asked") {
1866
- await handlePermission({
1867
- client,
1868
- sessionId,
1869
- permissionMode,
1870
- builtinToolFiltering,
1871
- turn,
1872
- emit,
1873
- event
1874
- });
1875
- }
1876
- }
1877
- function closeLegacyOpenParts({
1878
- state,
1879
- emit
1880
- }) {
1881
- for (const id of state.legacyReasoningPartIds) {
1882
- emit({ type: "reasoning-end", id });
1883
- state.reasoningDeltas.delete(id);
1884
- }
1885
- state.legacyReasoningPartIds.clear();
1886
- for (const id of state.legacyTextPartIds) {
1887
- emit({ type: "text-end", id });
1888
- state.textDeltas.delete(id);
1889
- }
1890
- state.legacyTextPartIds.clear();
1891
- }
1892
- function emitLegacyStepFinishPart({
1893
- part,
1894
- state,
1895
- emit
1896
- }) {
1897
- const event = legacyStepFinishPartToFinishStep(part);
1898
- if (!event) return false;
1899
- const id = isRecord3(part) ? stringValue2(part.id) : void 0;
1900
- if (id) {
1901
- if (state.legacyStepFinishPartIds.has(id)) return true;
1902
- state.legacyStepFinishPartIds.add(id);
1903
- }
1904
- closeLegacyOpenParts({ state, emit });
1905
- state.turnUsage = event.usage;
1906
- emit(event);
1907
- return true;
1908
- }
1909
- function emitLegacyToolPart({
1910
- part,
1911
- state,
1912
- emit
1913
- }) {
1914
- if (!part || typeof part !== "object") return;
1915
- const toolPart = part;
1916
- if (toolPart.type !== "tool") return;
1917
- const status = legacyToolPartStatus(toolPart);
1918
- if (status !== "running" && status !== "completed" && status !== "error") {
1919
- return;
1920
- }
1921
- if (typeof toolPart.tool !== "string" || typeof toolPart.callID !== "string") {
1922
- return;
1923
- }
1924
- const callID = toolPart.callID;
1925
- const rawToolName = toolPart.tool;
1926
- const toolName = toWireToolName(rawToolName);
1927
- state.toolNames.set(callID, { rawToolName, toolName });
1928
- const hostToolName = getHostToolName(toolName, rawToolName);
1929
- if (hostToolName) {
1930
- if (status === "running") {
1931
- authorizeHostToolCall({
1932
- callID,
1933
- toolName: hostToolName,
1934
- input: legacyToolPartInput(toolPart),
1935
- state
1936
- });
1937
- }
1938
- return;
1939
- }
1940
- if (!state.toolCallsEmitted.has(callID)) {
1941
- state.toolCallsEmitted.add(callID);
1942
- emit({
1943
- type: "tool-call",
1944
- toolCallId: callID,
1945
- toolName,
1946
- ...nativeNameField({ nativeName: rawToolName, toolName }),
1947
- input: JSON.stringify(legacyToolPartInput(toolPart)),
1948
- providerExecuted: true,
1949
- ...toolPart.provider?.metadata ? { providerMetadata: toolPart.provider.metadata } : {}
1950
- });
1951
- }
1952
- if ((status === "completed" || status === "error") && !state.toolResultsEmitted.has(callID)) {
1953
- state.toolResultsEmitted.add(callID);
1954
- emit({
1955
- type: "tool-result",
1956
- toolCallId: callID,
1957
- toolName,
1958
- result: legacyToolPartOutput(toolPart),
1959
- ...status === "error" ? { isError: true } : {}
1960
- });
1961
- }
1962
- }
1963
- function legacyToolPartStatus(part) {
1964
- return typeof part.state === "string" ? part.state : typeof part.state === "object" && part.state !== null ? String(part.state.status ?? "") : void 0;
1965
- }
1966
- function legacyToolPartInput(part) {
1967
- const state = typeof part.state === "object" && part.state !== null ? part.state : void 0;
1968
- return {
1969
- ...isRecord3(part.metadata) ? part.metadata : {},
1970
- ...isRecord3(state?.metadata) ? state.metadata : {},
1971
- ...isRecord3(state?.input) ? state.input : {}
1972
- };
1973
- }
1974
- function legacyToolPartOutput(part) {
1975
- const state = typeof part.state === "object" && part.state !== null ? part.state : void 0;
1976
- if (state?.status === "error") {
1977
- return state.error ?? part.error ?? state.result ?? "tool failed";
1978
- }
1979
- return state?.output ?? state?.result ?? state?.structured ?? state?.content ?? null;
1980
- }
1981
- function isRecord3(value) {
1982
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
1983
- }
1984
- function parseToolInput(state, props) {
1985
- const text = state.toolInputs.get(String(props.callID ?? ""));
1986
- if (!text) return {};
1987
- try {
1988
- return JSON.parse(text);
1989
- } catch {
1990
- return { input: text };
2034
+ if (onEvent?.(event)) break;
1991
2035
  }
1992
2036
  }
1993
2037
  async function handlePermissionV2({
@@ -2160,11 +2204,13 @@ function authorizeHostToolCall({
2160
2204
  async function emitContextFallback({
2161
2205
  client,
2162
2206
  sessionId,
2207
+ state,
2163
2208
  emit,
2164
2209
  emitContent
2165
2210
  }) {
2166
2211
  const assistant = await latestAssistantSnapshot({ client, sessionId });
2167
2212
  if (!assistant) return false;
2213
+ emitOpenCodeStreamStart({ info: assistant, state, emit });
2168
2214
  if (emitContent && Array.isArray(assistant.contentParts)) {
2169
2215
  for (const part of assistant.contentParts) {
2170
2216
  emitAssistantContentPart(part, emit);
@@ -2323,9 +2369,6 @@ function modelRefFromValue(value) {
2323
2369
  if (!providerID || !modelID) return void 0;
2324
2370
  return { providerID, modelID };
2325
2371
  }
2326
- function stringValue2(value) {
2327
- return typeof value === "string" && value.length > 0 ? value : void 0;
2328
- }
2329
2372
  function stripWorkDir(file) {
2330
2373
  if (!file) return file;
2331
2374
  const normalized = path2.resolve(file);