@ai-sdk/harness-opencode 1.0.37 → 1.0.39

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.
@@ -5,8 +5,8 @@
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "@modelcontextprotocol/sdk": "1.29.0",
8
- "@opencode-ai/sdk": "1.17.6",
9
- "opencode-ai": "1.17.6",
8
+ "@opencode-ai/sdk": "1.18.3",
9
+ "opencode-ai": "1.18.3",
10
10
  "ws": "8.21.0",
11
11
  "zod": "3.25.76"
12
12
  }
@@ -12,11 +12,11 @@ importers:
12
12
  specifier: 1.29.0
13
13
  version: 1.29.0(zod@3.25.76)
14
14
  '@opencode-ai/sdk':
15
- specifier: 1.17.6
16
- version: 1.17.6
15
+ specifier: 1.18.3
16
+ version: 1.18.3
17
17
  opencode-ai:
18
- specifier: 1.17.6
19
- version: 1.17.6
18
+ specifier: 1.18.3
19
+ version: 1.18.3
20
20
  ws:
21
21
  specifier: 8.21.0
22
22
  version: 8.21.0
@@ -42,8 +42,8 @@ packages:
42
42
  '@cfworker/json-schema':
43
43
  optional: true
44
44
 
45
- '@opencode-ai/sdk@1.17.6':
46
- resolution: {integrity: sha512-kCzdd9Git2sjDjXSHj4mMWuU1RXZ38d7gFEcq2rzlVz7XV6PPwNw8pelWsk99La2CtDkpfoW5lfHnpCaK6/oBQ==}
45
+ '@opencode-ai/sdk@1.18.3':
46
+ resolution: {integrity: sha512-Mevo4e6kQwbvto9E+42KSIVMhp+JBu+SwQhC5AomAvrV6Xkio3U249T+xDILDCXhl5Z/Hi/DlAuVLzpGnuh0gg==}
47
47
 
48
48
  accepts@2.0.0:
49
49
  resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
@@ -286,72 +286,72 @@ packages:
286
286
  once@1.4.0:
287
287
  resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
288
288
 
289
- opencode-ai@1.17.6:
290
- resolution: {integrity: sha512-pJpt21bapyvQ5Mye0M0IY3VGGDF48ddRdV7goyEVuNTtGo1OFmyuMCFsWX4ALbj8bHf+ebwTtVtCa2fwoI+uEQ==}
289
+ opencode-ai@1.18.3:
290
+ resolution: {integrity: sha512-HnItl/+uhSpj7JV9x6ITiE0XFq4b/PKF5OM03TIyiFoFiLw3MQoJOAXZFTEzC7IOgAIYcysRQBBmCmlXILkxww==}
291
291
  cpu: [arm64, x64]
292
292
  os: [darwin, linux, win32]
293
293
  hasBin: true
294
294
 
295
- opencode-darwin-arm64@1.17.6:
296
- resolution: {integrity: sha512-GMrdcxuhM+ohJ53nphg0lOOu0yTVBy+KYQM3f8CxwOHJBxF7+kVsiZPzOBJWXEOkFfPT5ZPY/FJPdZNcrzSaVA==}
295
+ opencode-darwin-arm64@1.18.3:
296
+ resolution: {integrity: sha512-BkfJf4E502OVwpDXgaVz8eqTDOy2B9xXPefYebT+xv8TXYvEo1wtby4eu4MoN51o4o1qpzcvCCXDqUDXu1pBGQ==}
297
297
  cpu: [arm64]
298
298
  os: [darwin]
299
299
 
300
- opencode-darwin-x64-baseline@1.17.6:
301
- resolution: {integrity: sha512-tKO9RcTD+jALhfyM+gUliNa9DsBgPr+CAlvUtAl7VtcTXOtUf1F2BgY51OFVjdmMKdzLyM2HOdf1ELxq5dYWpg==}
300
+ opencode-darwin-x64-baseline@1.18.3:
301
+ resolution: {integrity: sha512-8qQ9Ig7Zp7LEKaKGRL+OIlDb/uTLYkeAq6SGsN0fLHR2rHZRHyb61KrpkS5TgUlGwSWPtb5uCeVtTA9rGbEAKw==}
302
302
  cpu: [x64]
303
303
  os: [darwin]
304
304
 
305
- opencode-darwin-x64@1.17.6:
306
- resolution: {integrity: sha512-Y2Hi5n4Q1BtqTVyIjxciaTEC8CxMD6+bIIZoNSi6we/jhiC5NPOhBbCQFlu6bcFnP2En9T0GvAiz73hvMDCPnA==}
305
+ opencode-darwin-x64@1.18.3:
306
+ resolution: {integrity: sha512-ujy61O3WUEHjPqk+qXMBl97zR9q3crOzHbm2VLmXLHUWbqB5ZtN1HToODbuI1SBMKVR8GyZwPygRQVMfON4DHg==}
307
307
  cpu: [x64]
308
308
  os: [darwin]
309
309
 
310
- opencode-linux-arm64-musl@1.17.6:
311
- resolution: {integrity: sha512-rMoV+DMHMkzx3ZMbhlbRRsHwF7IcGjKQokjmN7VOkX/K8hghZvF7d3wD2YPeOldZHHzucdGOHYR5dqWqfP0moQ==}
310
+ opencode-linux-arm64-musl@1.18.3:
311
+ resolution: {integrity: sha512-s1vNP8dpIH3vFs8aoMzgHViBNVz6xtR7n24gGg0WOw4uSZVM+sRUmZKc3Hj56XjHQSsVBg5AkwR2Xm0nFSfIUQ==}
312
312
  cpu: [arm64]
313
313
  os: [linux]
314
314
  libc: [musl]
315
315
 
316
- opencode-linux-arm64@1.17.6:
317
- resolution: {integrity: sha512-I29+BqsG878YtfRfuwLRvt8sCFdlkIyXaPk/iZ9ToaiY/NXWld9AmKlePLKSdrkNbGYo+haXrBhH0D4QxVh1NQ==}
316
+ opencode-linux-arm64@1.18.3:
317
+ resolution: {integrity: sha512-K+1SPkfrYFUufPUYpbSurlB2ogpjv5in2usA1FwOM7HCOpNxR3ap7K+X4uQXnOV8AtsON+Vr9W5uYXQJA1yKlA==}
318
318
  cpu: [arm64]
319
319
  os: [linux]
320
320
 
321
- opencode-linux-x64-baseline-musl@1.17.6:
322
- resolution: {integrity: sha512-AGAASJviA2BRdXFFYtHMpgjKiTQRS/8URv6okVqC4g85MBhi9FOXghzs0i3puqAhkj0onqe9cZk2shDO2JfnSg==}
321
+ opencode-linux-x64-baseline-musl@1.18.3:
322
+ resolution: {integrity: sha512-F2nNPqYPdTCydLvNH1rvSXdlX3pfmOmOOfsFl+pqi3W0iE3Alyxmv28AjrIGUw4/6EfWhMKYcyxKjNIKLFyaZQ==}
323
323
  cpu: [x64]
324
324
  os: [linux]
325
325
  libc: [musl]
326
326
 
327
- opencode-linux-x64-baseline@1.17.6:
328
- resolution: {integrity: sha512-jkn0j/v1cZ9MaIX10uC/PVI3UFc1zGK4s0ZQQR/4bLCrCZ1EeXwSRQJxeCAwLgEeepZbLuZhnPEyBf9Uy0cw+Q==}
327
+ opencode-linux-x64-baseline@1.18.3:
328
+ resolution: {integrity: sha512-DDfspGSQ123yhsFhAlbpF+tsvje5kHOS2/PYUpWJroTlvgtqRLGmET51Anrxd5BrAFrqB6PXYA2kGh5QStPU/g==}
329
329
  cpu: [x64]
330
330
  os: [linux]
331
331
 
332
- opencode-linux-x64-musl@1.17.6:
333
- resolution: {integrity: sha512-jgrjw6X4mlsKxIlX9h1b9YI/c4f0XT+wgn1VfaXSZnssqr6Kvzf3njsIoreVhHnyIKMqngKGKsxlp3sTxn9i/g==}
332
+ opencode-linux-x64-musl@1.18.3:
333
+ resolution: {integrity: sha512-SSp2zSpAM4dntcPZw8ISl3CmXYrWPftLc8u0HuAIeNWJOo6G4xAHPoXhNnXI3tQYqqGcppArf/3EMPnF9WnyFw==}
334
334
  cpu: [x64]
335
335
  os: [linux]
336
336
  libc: [musl]
337
337
 
338
- opencode-linux-x64@1.17.6:
339
- resolution: {integrity: sha512-J+3Vn/+4zg3BnpMSanGiIJ6SPmKnVnRsyFpfZzAoVOli9aShSYI7zWpy7KCy9zn6JYkMWa1v+hdW+0Ou1Wr0Vw==}
338
+ opencode-linux-x64@1.18.3:
339
+ resolution: {integrity: sha512-p+19dSTf3pWL3+Rh/Ym94DvS9mNxNG94HJnR5oMCDhQvgimdT5tSdoK4ETtk4XOoCKz3OcTQDPHTdYKcJg32KQ==}
340
340
  cpu: [x64]
341
341
  os: [linux]
342
342
 
343
- opencode-windows-arm64@1.17.6:
344
- resolution: {integrity: sha512-WGYSRE2hNEsuoKVKHNBIML+OHrwF/UiFQMwrY4zjnz+CC95+Kjr1qM66iPvQyJCbLlmYGHXcx1Gt7qCG55lYpg==}
343
+ opencode-windows-arm64@1.18.3:
344
+ resolution: {integrity: sha512-qkz9/r6kUhhgLycQv71MoSQ2m5UI2QnZZx8SeZgyYD7oB8pDH6DIGbDWcs6BJ02ND1Y5hmgYWlP+rOrMkGlMJw==}
345
345
  cpu: [arm64]
346
346
  os: [win32]
347
347
 
348
- opencode-windows-x64-baseline@1.17.6:
349
- resolution: {integrity: sha512-oR2rl+UzP76KfxCOnV9paaRHRqFMSXrYIkH4ewF0dCrq9djh9cfVsSd9SjVLOcvShIMpIg4SbCsz3GpeN+XAdQ==}
348
+ opencode-windows-x64-baseline@1.18.3:
349
+ resolution: {integrity: sha512-+tyoxwSfY7msfZNY1IoA0ZWRL9XfdqY+ash6U3ldfE+GfF2WNAdhewmmYiUsC4lHjkdSmLt2brdWgoebQK9HAg==}
350
350
  cpu: [x64]
351
351
  os: [win32]
352
352
 
353
- opencode-windows-x64@1.17.6:
354
- resolution: {integrity: sha512-W0MZV0psmk+9BqdGXa72r6Jq6hwKPdzhgCA66UzdBXJ+A6xqXUrgh5o/P3O8LNS4cUiRHaSDdnIca96ZfLo5SA==}
353
+ opencode-windows-x64@1.18.3:
354
+ resolution: {integrity: sha512-V/Q4VkyZ7TkotLbpTCX/s5wP8MoFj5UnRqBHFJ8WGptb/+oYw36kamUUt1P1wN8iRjmw+8vwYhuYdAe+3xbgpQ==}
355
355
  cpu: [x64]
356
356
  os: [win32]
357
357
 
@@ -508,7 +508,7 @@ snapshots:
508
508
  transitivePeerDependencies:
509
509
  - supports-color
510
510
 
511
- '@opencode-ai/sdk@1.17.6':
511
+ '@opencode-ai/sdk@1.18.3':
512
512
  dependencies:
513
513
  cross-spawn: 7.0.6
514
514
 
@@ -752,55 +752,55 @@ snapshots:
752
752
  dependencies:
753
753
  wrappy: 1.0.2
754
754
 
755
- opencode-ai@1.17.6:
755
+ opencode-ai@1.18.3:
756
756
  optionalDependencies:
757
- opencode-darwin-arm64: 1.17.6
758
- opencode-darwin-x64: 1.17.6
759
- opencode-darwin-x64-baseline: 1.17.6
760
- opencode-linux-arm64: 1.17.6
761
- opencode-linux-arm64-musl: 1.17.6
762
- opencode-linux-x64: 1.17.6
763
- opencode-linux-x64-baseline: 1.17.6
764
- opencode-linux-x64-baseline-musl: 1.17.6
765
- opencode-linux-x64-musl: 1.17.6
766
- opencode-windows-arm64: 1.17.6
767
- opencode-windows-x64: 1.17.6
768
- opencode-windows-x64-baseline: 1.17.6
769
-
770
- opencode-darwin-arm64@1.17.6:
757
+ opencode-darwin-arm64: 1.18.3
758
+ opencode-darwin-x64: 1.18.3
759
+ opencode-darwin-x64-baseline: 1.18.3
760
+ opencode-linux-arm64: 1.18.3
761
+ opencode-linux-arm64-musl: 1.18.3
762
+ opencode-linux-x64: 1.18.3
763
+ opencode-linux-x64-baseline: 1.18.3
764
+ opencode-linux-x64-baseline-musl: 1.18.3
765
+ opencode-linux-x64-musl: 1.18.3
766
+ opencode-windows-arm64: 1.18.3
767
+ opencode-windows-x64: 1.18.3
768
+ opencode-windows-x64-baseline: 1.18.3
769
+
770
+ opencode-darwin-arm64@1.18.3:
771
771
  optional: true
772
772
 
773
- opencode-darwin-x64-baseline@1.17.6:
773
+ opencode-darwin-x64-baseline@1.18.3:
774
774
  optional: true
775
775
 
776
- opencode-darwin-x64@1.17.6:
776
+ opencode-darwin-x64@1.18.3:
777
777
  optional: true
778
778
 
779
- opencode-linux-arm64-musl@1.17.6:
779
+ opencode-linux-arm64-musl@1.18.3:
780
780
  optional: true
781
781
 
782
- opencode-linux-arm64@1.17.6:
782
+ opencode-linux-arm64@1.18.3:
783
783
  optional: true
784
784
 
785
- opencode-linux-x64-baseline-musl@1.17.6:
785
+ opencode-linux-x64-baseline-musl@1.18.3:
786
786
  optional: true
787
787
 
788
- opencode-linux-x64-baseline@1.17.6:
788
+ opencode-linux-x64-baseline@1.18.3:
789
789
  optional: true
790
790
 
791
- opencode-linux-x64-musl@1.17.6:
791
+ opencode-linux-x64-musl@1.18.3:
792
792
  optional: true
793
793
 
794
- opencode-linux-x64@1.17.6:
794
+ opencode-linux-x64@1.18.3:
795
795
  optional: true
796
796
 
797
- opencode-windows-arm64@1.17.6:
797
+ opencode-windows-arm64@1.18.3:
798
798
  optional: true
799
799
 
800
- opencode-windows-x64-baseline@1.17.6:
800
+ opencode-windows-x64-baseline@1.18.3:
801
801
  optional: true
802
802
 
803
- opencode-windows-x64@1.17.6:
803
+ opencode-windows-x64@1.18.3:
804
804
  optional: true
805
805
 
806
806
  parseurl@1.3.3: {}
package/dist/index.js CHANGED
@@ -168,7 +168,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
168
168
  ]);
169
169
 
170
170
  // src/version.ts
171
- var VERSION = true ? "1.0.37" : "0.0.0-test";
171
+ var VERSION = true ? "1.0.39" : "0.0.0-test";
172
172
 
173
173
  // src/opencode-harness.ts
174
174
  var OPENCODE_CLIENT_APP = `ai-sdk/harness-opencode/${VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-opencode",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -27,15 +27,15 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "ws": "8.21.0",
30
- "@ai-sdk/harness": "1.0.36",
31
- "@ai-sdk/provider-utils": "5.0.11"
30
+ "@ai-sdk/provider-utils": "5.0.12",
31
+ "@ai-sdk/harness": "1.0.38"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "zod": "^3.25.76 || ^4.1.8"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@modelcontextprotocol/sdk": "1.29.0",
38
- "@opencode-ai/sdk": "1.17.6",
38
+ "@opencode-ai/sdk": "1.18.3",
39
39
  "@types/node": "22.19.19",
40
40
  "@types/ws": "^8.5.13",
41
41
  "tsup": "^8.5.1",
@@ -14,10 +14,14 @@ import {
14
14
  createOpencodeServer,
15
15
  } from '@opencode-ai/sdk/v2';
16
16
  import {
17
+ createTranslationState,
18
+ emitLegacyPartDelta,
19
+ emitLegacyTextPartUpdate,
17
20
  emitMissingFinalDelta,
18
21
  getOpenCodeEventSessionId,
19
22
  isStepSettlementEvent,
20
23
  type OpenCodeEvent,
24
+ type TranslationState,
21
25
  unwrapOpenCodeEvent,
22
26
  } from './opencode-events';
23
27
  import {
@@ -798,40 +802,6 @@ async function consumeEvents({
798
802
  }
799
803
  }
800
804
 
801
- type TranslationState = {
802
- textDeltas: Map<string, string>;
803
- reasoningDeltas: Map<string, string>;
804
- toolInputs: Map<string, string>;
805
- toolNames: Map<string, { rawToolName: string; toolName: string }>;
806
- toolCallsEmitted: Set<string>;
807
- toolResultsEmitted: Set<string>;
808
- hostToolCallsAuthorized: Set<string>;
809
- shellCommands: Map<string, string>;
810
- messageRoles: Map<string, string>;
811
- turnUsage: Record<string, unknown> | undefined;
812
- legacyTextPartIds: Set<string>;
813
- legacyReasoningPartIds: Set<string>;
814
- legacyStepFinishPartIds: Set<string>;
815
- };
816
-
817
- function createTranslationState(): TranslationState {
818
- return {
819
- textDeltas: new Map(),
820
- reasoningDeltas: new Map(),
821
- toolInputs: new Map(),
822
- toolNames: new Map(),
823
- toolCallsEmitted: new Set(),
824
- toolResultsEmitted: new Set(),
825
- hostToolCallsAuthorized: new Set(),
826
- shellCommands: new Map(),
827
- messageRoles: new Map(),
828
- turnUsage: undefined,
829
- legacyTextPartIds: new Set(),
830
- legacyReasoningPartIds: new Set(),
831
- legacyStepFinishPartIds: new Set(),
832
- };
833
- }
834
-
835
805
  async function translateAndEmit({
836
806
  event,
837
807
  state,
@@ -865,32 +835,7 @@ async function translateAndEmit({
865
835
  }
866
836
 
867
837
  if (type === 'message.part.delta') {
868
- const field = String(props.field ?? '');
869
- const delta = String(props.delta ?? '');
870
- if (!delta) return;
871
- const messageID = stringValue(props.messageID);
872
- if (messageID && state.messageRoles.get(messageID) === 'user') return;
873
- if (field === 'text') {
874
- const id = legacyPartId({ value: props, fallback: 'legacy-text' });
875
- startLegacyPart({ ids: state.legacyTextPartIds, id, emit, type: 'text' });
876
- state.textDeltas.set(id, `${state.textDeltas.get(id) ?? ''}${delta}`);
877
- emit({ type: 'text-delta', id, delta });
878
- return;
879
- }
880
- if (field === 'reasoning') {
881
- const id = legacyPartId({ value: props, fallback: 'legacy-reasoning' });
882
- startLegacyPart({
883
- ids: state.legacyReasoningPartIds,
884
- id,
885
- emit,
886
- type: 'reasoning',
887
- });
888
- state.reasoningDeltas.set(
889
- id,
890
- `${state.reasoningDeltas.get(id) ?? ''}${delta}`,
891
- );
892
- emit({ type: 'reasoning-delta', id, delta });
893
- }
838
+ emitLegacyPartDelta({ props, state, emit });
894
839
  return;
895
840
  }
896
841
 
@@ -1142,88 +1087,6 @@ async function translateAndEmit({
1142
1087
  }
1143
1088
  }
1144
1089
 
1145
- function legacyPartId({
1146
- value,
1147
- fallback,
1148
- }: {
1149
- value: Record<string, unknown>;
1150
- fallback: string;
1151
- }): string {
1152
- return stringValue(value.partID) ?? stringValue(value.id) ?? fallback;
1153
- }
1154
-
1155
- function startLegacyPart({
1156
- ids,
1157
- id,
1158
- emit,
1159
- type,
1160
- }: {
1161
- ids: Set<string>;
1162
- id: string;
1163
- emit: Emit;
1164
- type: 'text' | 'reasoning';
1165
- }): void {
1166
- if (ids.has(id)) return;
1167
- ids.add(id);
1168
- emit({ type: `${type}-start`, id });
1169
- }
1170
-
1171
- function emitLegacyTextPartUpdate({
1172
- part,
1173
- state,
1174
- emit,
1175
- }: {
1176
- part: unknown;
1177
- state: TranslationState;
1178
- emit: Emit;
1179
- }): boolean {
1180
- if (!isRecord(part)) return false;
1181
- if (part.type !== 'text' && part.type !== 'reasoning') return false;
1182
- const id = stringValue(part.id);
1183
- if (!id) return true;
1184
-
1185
- const messageID = stringValue(part.messageID);
1186
- if (messageID && state.messageRoles.get(messageID) === 'user') return true;
1187
-
1188
- const isReasoning = part.type === 'reasoning';
1189
- const ids = isReasoning
1190
- ? state.legacyReasoningPartIds
1191
- : state.legacyTextPartIds;
1192
- const deltaMap = isReasoning ? state.reasoningDeltas : state.textDeltas;
1193
- const deltaType = isReasoning ? 'reasoning-delta' : 'text-delta';
1194
- const text = typeof part.text === 'string' ? part.text : undefined;
1195
-
1196
- startLegacyPart({
1197
- ids,
1198
- id,
1199
- emit,
1200
- type: isReasoning ? 'reasoning' : 'text',
1201
- });
1202
-
1203
- if (text !== undefined) {
1204
- emitMissingFinalDelta({
1205
- id,
1206
- fullText: text,
1207
- emittedText: deltaMap.get(id) ?? '',
1208
- emit,
1209
- type: deltaType,
1210
- });
1211
- deltaMap.set(id, text);
1212
- }
1213
-
1214
- if (legacyPartEnded(part)) {
1215
- ids.delete(id);
1216
- deltaMap.delete(id);
1217
- emit({ type: isReasoning ? 'reasoning-end' : 'text-end', id });
1218
- }
1219
-
1220
- return true;
1221
- }
1222
-
1223
- function legacyPartEnded(part: Record<string, unknown>): boolean {
1224
- return isRecord(part.time) && part.time.end != null;
1225
- }
1226
-
1227
1090
  function closeLegacyOpenParts({
1228
1091
  state,
1229
1092
  emit,
@@ -4,6 +4,42 @@ export type OpenCodeEvent = {
4
4
  properties?: Record<string, any>;
5
5
  };
6
6
 
7
+ type Emit = (msg: Record<string, unknown>) => void;
8
+
9
+ export type TranslationState = {
10
+ textDeltas: Map<string, string>;
11
+ reasoningDeltas: Map<string, string>;
12
+ toolInputs: Map<string, string>;
13
+ toolNames: Map<string, { rawToolName: string; toolName: string }>;
14
+ toolCallsEmitted: Set<string>;
15
+ toolResultsEmitted: Set<string>;
16
+ hostToolCallsAuthorized: Set<string>;
17
+ shellCommands: Map<string, string>;
18
+ messageRoles: Map<string, string>;
19
+ turnUsage: Record<string, unknown> | undefined;
20
+ legacyTextPartIds: Set<string>;
21
+ legacyReasoningPartIds: Set<string>;
22
+ legacyStepFinishPartIds: Set<string>;
23
+ };
24
+
25
+ export function createTranslationState(): TranslationState {
26
+ return {
27
+ textDeltas: new Map(),
28
+ reasoningDeltas: new Map(),
29
+ toolInputs: new Map(),
30
+ toolNames: new Map(),
31
+ toolCallsEmitted: new Set(),
32
+ toolResultsEmitted: new Set(),
33
+ hostToolCallsAuthorized: new Set(),
34
+ shellCommands: new Map(),
35
+ messageRoles: new Map(),
36
+ turnUsage: undefined,
37
+ legacyTextPartIds: new Set(),
38
+ legacyReasoningPartIds: new Set(),
39
+ legacyStepFinishPartIds: new Set(),
40
+ };
41
+ }
42
+
7
43
  export function unwrapOpenCodeEvent(
8
44
  rawEvent: unknown,
9
45
  ): OpenCodeEvent | undefined {
@@ -77,6 +113,114 @@ export function emitMissingFinalDelta({
77
113
  emit({ type, id, delta: fullText.slice(emittedText.length) });
78
114
  }
79
115
 
116
+ /**
117
+ * Translates an OpenCode `message.part.delta` event (a streaming text or
118
+ * reasoning delta) into legacy stream parts.
119
+ */
120
+ export function emitLegacyPartDelta({
121
+ props,
122
+ state,
123
+ emit,
124
+ }: {
125
+ props: Record<string, unknown>;
126
+ state: TranslationState;
127
+ emit: Emit;
128
+ }): void {
129
+ const field = String(props.field ?? '');
130
+ const delta = String(props.delta ?? '');
131
+ if (!delta) return;
132
+ const messageID = stringValue(props.messageID);
133
+ if (messageID && state.messageRoles.get(messageID) === 'user') return;
134
+ if (field === 'text') {
135
+ const id = legacyPartId({ value: props, fallback: 'legacy-text' });
136
+ // OpenCode publishes reasoning deltas with field:"text". If this id was
137
+ // already announced as a reasoning part, route it to reasoning so it is not
138
+ // duplicated as a text part.
139
+ if (state.legacyReasoningPartIds.has(id)) {
140
+ state.reasoningDeltas.set(
141
+ id,
142
+ `${state.reasoningDeltas.get(id) ?? ''}${delta}`,
143
+ );
144
+ emit({ type: 'reasoning-delta', id, delta });
145
+ return;
146
+ }
147
+ startLegacyPart({ ids: state.legacyTextPartIds, id, emit, type: 'text' });
148
+ state.textDeltas.set(id, `${state.textDeltas.get(id) ?? ''}${delta}`);
149
+ emit({ type: 'text-delta', id, delta });
150
+ return;
151
+ }
152
+ if (field === 'reasoning') {
153
+ const id = legacyPartId({ value: props, fallback: 'legacy-reasoning' });
154
+ startLegacyPart({
155
+ ids: state.legacyReasoningPartIds,
156
+ id,
157
+ emit,
158
+ type: 'reasoning',
159
+ });
160
+ state.reasoningDeltas.set(
161
+ id,
162
+ `${state.reasoningDeltas.get(id) ?? ''}${delta}`,
163
+ );
164
+ emit({ type: 'reasoning-delta', id, delta });
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Translates an OpenCode `message.part.updated` event for a text or reasoning
170
+ * part. Returns `true` when it handled the part.
171
+ */
172
+ export function emitLegacyTextPartUpdate({
173
+ part,
174
+ state,
175
+ emit,
176
+ }: {
177
+ part: unknown;
178
+ state: TranslationState;
179
+ emit: Emit;
180
+ }): boolean {
181
+ if (!isRecord(part)) return false;
182
+ if (part.type !== 'text' && part.type !== 'reasoning') return false;
183
+ const id = stringValue(part.id);
184
+ if (!id) return true;
185
+
186
+ const messageID = stringValue(part.messageID);
187
+ if (messageID && state.messageRoles.get(messageID) === 'user') return true;
188
+
189
+ const isReasoning = part.type === 'reasoning';
190
+ const ids = isReasoning
191
+ ? state.legacyReasoningPartIds
192
+ : state.legacyTextPartIds;
193
+ const deltaMap = isReasoning ? state.reasoningDeltas : state.textDeltas;
194
+ const deltaType = isReasoning ? 'reasoning-delta' : 'text-delta';
195
+ const text = typeof part.text === 'string' ? part.text : undefined;
196
+
197
+ startLegacyPart({
198
+ ids,
199
+ id,
200
+ emit,
201
+ type: isReasoning ? 'reasoning' : 'text',
202
+ });
203
+
204
+ if (text !== undefined) {
205
+ emitMissingFinalDelta({
206
+ id,
207
+ fullText: text,
208
+ emittedText: deltaMap.get(id) ?? '',
209
+ emit,
210
+ type: deltaType,
211
+ });
212
+ deltaMap.set(id, text);
213
+ }
214
+
215
+ if (legacyPartEnded(part)) {
216
+ ids.delete(id);
217
+ deltaMap.delete(id);
218
+ emit({ type: isReasoning ? 'reasoning-end' : 'text-end', id });
219
+ }
220
+
221
+ return true;
222
+ }
223
+
80
224
  function stripSyncVersion(type: string): string {
81
225
  return type.replace(/\.\d+$/, '');
82
226
  }
@@ -86,3 +230,41 @@ function asRecord(value: unknown): Record<string, any> | undefined {
86
230
  return undefined;
87
231
  return value as Record<string, any>;
88
232
  }
233
+
234
+ function isRecord(value: unknown): value is Record<string, unknown> {
235
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
236
+ }
237
+
238
+ function stringValue(value: unknown): string | undefined {
239
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
240
+ }
241
+
242
+ function legacyPartId({
243
+ value,
244
+ fallback,
245
+ }: {
246
+ value: Record<string, unknown>;
247
+ fallback: string;
248
+ }): string {
249
+ return stringValue(value.partID) ?? stringValue(value.id) ?? fallback;
250
+ }
251
+
252
+ function startLegacyPart({
253
+ ids,
254
+ id,
255
+ emit,
256
+ type,
257
+ }: {
258
+ ids: Set<string>;
259
+ id: string;
260
+ emit: Emit;
261
+ type: 'text' | 'reasoning';
262
+ }): void {
263
+ if (ids.has(id)) return;
264
+ ids.add(id);
265
+ emit({ type: `${type}-start`, id });
266
+ }
267
+
268
+ function legacyPartEnded(part: Record<string, unknown>): boolean {
269
+ return isRecord(part.time) && part.time.end != null;
270
+ }
@@ -5,8 +5,8 @@
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "@modelcontextprotocol/sdk": "1.29.0",
8
- "@opencode-ai/sdk": "1.17.6",
9
- "opencode-ai": "1.17.6",
8
+ "@opencode-ai/sdk": "1.18.3",
9
+ "opencode-ai": "1.18.3",
10
10
  "ws": "8.21.0",
11
11
  "zod": "3.25.76"
12
12
  }