@ai-sdk/harness-opencode 1.0.21 → 1.0.23
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 +194 -59
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +42 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/index.ts +93 -35
- package/src/bridge/opencode-finish-step.ts +39 -0
- package/src/bridge/opencode-usage.ts +5 -1
- package/src/opencode-harness.ts +42 -41
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @ai-sdk/harness-opencode
|
|
2
2
|
|
|
3
|
+
## 1.0.23
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 39c8276: fix(harness): improve opaque sandbox bridge error handling
|
|
8
|
+
- 91fe6d8: fix(harness): emit `finish-step` stream parts correctly per the underlying model steps
|
|
9
|
+
- Updated dependencies [39c8276]
|
|
10
|
+
- Updated dependencies [91fe6d8]
|
|
11
|
+
- Updated dependencies [0be5014]
|
|
12
|
+
- @ai-sdk/harness@1.0.23
|
|
13
|
+
|
|
14
|
+
## 1.0.22
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- @ai-sdk/harness@1.0.22
|
|
19
|
+
- @ai-sdk/provider-utils@5.0.7
|
|
20
|
+
|
|
3
21
|
## 1.0.21
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -20,6 +20,15 @@ function formatBridgeError(err) {
|
|
|
20
20
|
if (err instanceof Error) {
|
|
21
21
|
return { name: err.name, message: err.message, stack: err.stack };
|
|
22
22
|
}
|
|
23
|
+
if (typeof err === "string") {
|
|
24
|
+
return { message: err };
|
|
25
|
+
}
|
|
26
|
+
if (err !== null && typeof err === "object") {
|
|
27
|
+
try {
|
|
28
|
+
return { message: JSON.stringify(err) };
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
23
32
|
return { message: String(err) };
|
|
24
33
|
}
|
|
25
34
|
function parseEnvList(value) {
|
|
@@ -47,6 +56,7 @@ async function runBridge(options) {
|
|
|
47
56
|
let isFirstTurn = true;
|
|
48
57
|
let turnAbort;
|
|
49
58
|
let currentUserMessages;
|
|
59
|
+
let currentInterruptHandler;
|
|
50
60
|
let debugConfig;
|
|
51
61
|
let consoleCaptureInstalled = false;
|
|
52
62
|
const envDebugEnabled = ENV_TRUTHY.has(
|
|
@@ -164,6 +174,38 @@ async function runBridge(options) {
|
|
|
164
174
|
};
|
|
165
175
|
const rawStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
166
176
|
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
177
|
+
const writeErrorToStderr = (input) => {
|
|
178
|
+
try {
|
|
179
|
+
const formatted = formatBridgeError(input.error);
|
|
180
|
+
rawStderrWrite(
|
|
181
|
+
`[harness:${bridgeType}:error] ${input.message}: ${formatted.message}
|
|
182
|
+
`
|
|
183
|
+
);
|
|
184
|
+
if (formatted.stack) {
|
|
185
|
+
rawStderrWrite(`${formatted.stack}
|
|
186
|
+
`);
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const emitWarning = (input) => {
|
|
192
|
+
try {
|
|
193
|
+
for (const line of input.message.split("\n")) {
|
|
194
|
+
if (line.trim().length > 0) {
|
|
195
|
+
rawStderrWrite(`[harness:${bridgeType}:warn] ${line}
|
|
196
|
+
`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const emitError = (input) => {
|
|
203
|
+
writeErrorToStderr({
|
|
204
|
+
message: input.message ?? "bridge error",
|
|
205
|
+
error: input.error
|
|
206
|
+
});
|
|
207
|
+
emit({ type: "error", error: serialiseError(input.error) });
|
|
208
|
+
};
|
|
167
209
|
const installConsoleCapture = () => {
|
|
168
210
|
if (consoleCaptureInstalled) return;
|
|
169
211
|
consoleCaptureInstalled = true;
|
|
@@ -221,6 +263,7 @@ async function runBridge(options) {
|
|
|
221
263
|
});
|
|
222
264
|
turnAbort = new AbortController();
|
|
223
265
|
currentTurnState = "running";
|
|
266
|
+
currentInterruptHandler = void 0;
|
|
224
267
|
void writeStartConfig(msg);
|
|
225
268
|
void writeBridgeMeta("running");
|
|
226
269
|
const startDebug = msg.debug;
|
|
@@ -242,6 +285,9 @@ async function runBridge(options) {
|
|
|
242
285
|
}),
|
|
243
286
|
pendingUserMessages: [],
|
|
244
287
|
abortSignal: turnAbort.signal,
|
|
288
|
+
onInterrupt: (handler) => {
|
|
289
|
+
currentInterruptHandler = handler;
|
|
290
|
+
},
|
|
245
291
|
firstTurn,
|
|
246
292
|
bridgeLog: (input) => {
|
|
247
293
|
const level = input.level ?? "debug";
|
|
@@ -254,14 +300,17 @@ async function runBridge(options) {
|
|
|
254
300
|
...input.attrs ? { attrs: input.attrs } : {},
|
|
255
301
|
...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
|
|
256
302
|
});
|
|
257
|
-
}
|
|
303
|
+
},
|
|
304
|
+
emitWarning,
|
|
305
|
+
emitError
|
|
258
306
|
};
|
|
259
307
|
currentUserMessages = turn.pendingUserMessages;
|
|
260
308
|
try {
|
|
261
309
|
await onStart(msg, turn);
|
|
262
310
|
} catch (err) {
|
|
263
|
-
|
|
311
|
+
emitError({ error: err, message: "bridge turn failed" });
|
|
264
312
|
} finally {
|
|
313
|
+
currentInterruptHandler = void 0;
|
|
265
314
|
currentTurnState = "waiting";
|
|
266
315
|
void writeBridgeMeta("waiting");
|
|
267
316
|
}
|
|
@@ -289,6 +338,22 @@ async function runBridge(options) {
|
|
|
289
338
|
case "abort":
|
|
290
339
|
turnAbort?.abort();
|
|
291
340
|
return;
|
|
341
|
+
case "interrupt":
|
|
342
|
+
try {
|
|
343
|
+
if (currentInterruptHandler) {
|
|
344
|
+
await currentInterruptHandler();
|
|
345
|
+
} else {
|
|
346
|
+
turnAbort?.abort();
|
|
347
|
+
}
|
|
348
|
+
sendControl({ type: "bridge-interrupted", ok: true });
|
|
349
|
+
} catch (err) {
|
|
350
|
+
sendControl({
|
|
351
|
+
type: "bridge-interrupted",
|
|
352
|
+
ok: false,
|
|
353
|
+
error: serialiseError(err)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
292
357
|
case "resume":
|
|
293
358
|
replay(ws, msg.lastSeenEventId);
|
|
294
359
|
return;
|
|
@@ -383,10 +448,10 @@ async function runBridge(options) {
|
|
|
383
448
|
});
|
|
384
449
|
});
|
|
385
450
|
process.on("uncaughtException", (err) => {
|
|
386
|
-
|
|
451
|
+
emitError({ error: err, message: "uncaught exception" });
|
|
387
452
|
});
|
|
388
453
|
process.on("unhandledRejection", (err) => {
|
|
389
|
-
|
|
454
|
+
emitError({ error: err, message: "unhandled rejection" });
|
|
390
455
|
});
|
|
391
456
|
await new Promise((resolve, reject) => {
|
|
392
457
|
if (wss.address() != null) {
|
|
@@ -477,19 +542,6 @@ function asRecord(value) {
|
|
|
477
542
|
return value;
|
|
478
543
|
}
|
|
479
544
|
|
|
480
|
-
// src/bridge/opencode-path.ts
|
|
481
|
-
import path from "path";
|
|
482
|
-
var fallbackPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
483
|
-
function prependOpenCodeBinToPath({
|
|
484
|
-
bootstrapDir: bootstrapDir2,
|
|
485
|
-
env
|
|
486
|
-
}) {
|
|
487
|
-
env.PATH = [
|
|
488
|
-
path.join(bootstrapDir2, "node_modules", ".bin"),
|
|
489
|
-
env.PATH || fallbackPath
|
|
490
|
-
].join(path.delimiter);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
545
|
// src/bridge/opencode-usage.ts
|
|
494
546
|
function mapUsage(tokens) {
|
|
495
547
|
const value = extractOpenCodeTokens(tokens) ?? zeroOpenCodeTokens();
|
|
@@ -608,6 +660,47 @@ function add({
|
|
|
608
660
|
return leftNumber == null && rightNumber == null ? void 0 : (leftNumber ?? 0) + (rightNumber ?? 0);
|
|
609
661
|
}
|
|
610
662
|
|
|
663
|
+
// src/bridge/opencode-finish-step.ts
|
|
664
|
+
function mapOpenCodeFinishReason(reason) {
|
|
665
|
+
const normalized = reason.toLowerCase();
|
|
666
|
+
if (normalized.includes("length")) return "length";
|
|
667
|
+
if (normalized.includes("filter")) return "content-filter";
|
|
668
|
+
if (normalized.includes("tool")) return "tool-calls";
|
|
669
|
+
if (normalized.includes("error") || normalized.includes("fail"))
|
|
670
|
+
return "error";
|
|
671
|
+
if (normalized === "stop" || normalized === "end") return "stop";
|
|
672
|
+
return "other";
|
|
673
|
+
}
|
|
674
|
+
function legacyStepFinishPartToFinishStep(part) {
|
|
675
|
+
if (!isRecord(part) || part.type !== "step-finish") return void 0;
|
|
676
|
+
const rawFinish = typeof part.reason === "string" ? part.reason : "stop";
|
|
677
|
+
return {
|
|
678
|
+
type: "finish-step",
|
|
679
|
+
finishReason: {
|
|
680
|
+
unified: mapOpenCodeFinishReason(rawFinish),
|
|
681
|
+
raw: rawFinish
|
|
682
|
+
},
|
|
683
|
+
usage: mapUsage(part.tokens),
|
|
684
|
+
...typeof part.cost === "number" ? { harnessMetadata: { opencode: { cost: part.cost } } } : {}
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function isRecord(value) {
|
|
688
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/bridge/opencode-path.ts
|
|
692
|
+
import path from "path";
|
|
693
|
+
var fallbackPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
694
|
+
function prependOpenCodeBinToPath({
|
|
695
|
+
bootstrapDir: bootstrapDir2,
|
|
696
|
+
env
|
|
697
|
+
}) {
|
|
698
|
+
env.PATH = [
|
|
699
|
+
path.join(bootstrapDir2, "node_modules", ".bin"),
|
|
700
|
+
env.PATH || fallbackPath
|
|
701
|
+
].join(path.delimiter);
|
|
702
|
+
}
|
|
703
|
+
|
|
611
704
|
// src/bridge/tool-relay-auth.ts
|
|
612
705
|
import { readdir, readFile, readlink } from "fs/promises";
|
|
613
706
|
var ToolRelayAuthorizer = class {
|
|
@@ -790,7 +883,7 @@ async function runTurn(start, turn) {
|
|
|
790
883
|
totalUsage = await runPrompt({ client, sessionId, start, turn, emit });
|
|
791
884
|
}
|
|
792
885
|
} catch (err) {
|
|
793
|
-
|
|
886
|
+
turn.emitError({ error: err, message: "OpenCode turn failed" });
|
|
794
887
|
} finally {
|
|
795
888
|
emit({
|
|
796
889
|
type: "finish",
|
|
@@ -1032,11 +1125,38 @@ function legacyStatusType(event) {
|
|
|
1032
1125
|
const status = event.properties?.status;
|
|
1033
1126
|
return status && typeof status === "object" ? String(status.type ?? "") : void 0;
|
|
1034
1127
|
}
|
|
1035
|
-
function
|
|
1128
|
+
function legacyRetryStatusMessage(event) {
|
|
1036
1129
|
const status = event.properties?.status;
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1130
|
+
const details = [];
|
|
1131
|
+
if (status && typeof status === "object") {
|
|
1132
|
+
const retryStatus = status;
|
|
1133
|
+
if (typeof retryStatus.attempt === "number") {
|
|
1134
|
+
details.push(`attempt ${retryStatus.attempt}`);
|
|
1135
|
+
}
|
|
1136
|
+
if (typeof retryStatus.message === "string" && retryStatus.message.trim()) {
|
|
1137
|
+
details.push(retryStatus.message.trim());
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return details.length > 0 ? `OpenCode session retry: ${details.join("; ")}` : "OpenCode session retry";
|
|
1141
|
+
}
|
|
1142
|
+
function nextRetryEventMessage(event) {
|
|
1143
|
+
const props = event.properties ?? {};
|
|
1144
|
+
const details = [];
|
|
1145
|
+
if (typeof props.attempt === "number") {
|
|
1146
|
+
details.push(`attempt ${props.attempt}`);
|
|
1147
|
+
}
|
|
1148
|
+
const error = props.error;
|
|
1149
|
+
if (isRecord2(error)) {
|
|
1150
|
+
const message = stringValue(error.message) ?? (isRecord2(error.data) ? stringValue(error.data.message) : void 0);
|
|
1151
|
+
const statusCode = error.statusCode;
|
|
1152
|
+
if (typeof statusCode === "number") {
|
|
1153
|
+
details.push(`HTTP ${statusCode}`);
|
|
1154
|
+
}
|
|
1155
|
+
if (message) details.push(message);
|
|
1156
|
+
} else if (error != null) {
|
|
1157
|
+
details.push(formatError(error));
|
|
1158
|
+
}
|
|
1159
|
+
return details.length > 0 ? `OpenCode session retry: ${details.join("; ")}` : "OpenCode session retry";
|
|
1040
1160
|
}
|
|
1041
1161
|
async function ensureSession({
|
|
1042
1162
|
client,
|
|
@@ -1119,9 +1239,7 @@ async function runPrompt({
|
|
|
1119
1239
|
sawBusy = true;
|
|
1120
1240
|
} else if (status === "retry") {
|
|
1121
1241
|
sawBusy = true;
|
|
1122
|
-
|
|
1123
|
-
turnSettled.resolve();
|
|
1124
|
-
return true;
|
|
1242
|
+
turn.emitWarning({ message: legacyRetryStatusMessage(event) });
|
|
1125
1243
|
} else if (sawBusy && status === "idle") {
|
|
1126
1244
|
turnSettled.resolve();
|
|
1127
1245
|
return true;
|
|
@@ -1221,9 +1339,7 @@ async function runCompaction({
|
|
|
1221
1339
|
sawBusy = true;
|
|
1222
1340
|
} else if (status === "retry") {
|
|
1223
1341
|
sawBusy = true;
|
|
1224
|
-
|
|
1225
|
-
compactionSettled.resolve();
|
|
1226
|
-
return true;
|
|
1342
|
+
turn.emitWarning({ message: legacyRetryStatusMessage(event) });
|
|
1227
1343
|
} else if (sawBusy && status === "idle") {
|
|
1228
1344
|
compactionSettled.resolve();
|
|
1229
1345
|
return true;
|
|
@@ -1306,7 +1422,8 @@ function createTranslationState() {
|
|
|
1306
1422
|
messageRoles: /* @__PURE__ */ new Map(),
|
|
1307
1423
|
turnUsage: void 0,
|
|
1308
1424
|
legacyTextPartIds: /* @__PURE__ */ new Set(),
|
|
1309
|
-
legacyReasoningPartIds: /* @__PURE__ */ new Set()
|
|
1425
|
+
legacyReasoningPartIds: /* @__PURE__ */ new Set(),
|
|
1426
|
+
legacyStepFinishPartIds: /* @__PURE__ */ new Set()
|
|
1310
1427
|
};
|
|
1311
1428
|
}
|
|
1312
1429
|
async function translateAndEmit({
|
|
@@ -1323,7 +1440,7 @@ async function translateAndEmit({
|
|
|
1323
1440
|
const props = event.properties ?? {};
|
|
1324
1441
|
if (type === "message.updated") {
|
|
1325
1442
|
const info = props.info;
|
|
1326
|
-
if (
|
|
1443
|
+
if (isRecord2(info)) {
|
|
1327
1444
|
const id = stringValue(info.id);
|
|
1328
1445
|
const role = stringValue(info.role);
|
|
1329
1446
|
if (id && role) state.messageRoles.set(id, role);
|
|
@@ -1361,6 +1478,7 @@ async function translateAndEmit({
|
|
|
1361
1478
|
}
|
|
1362
1479
|
if (type === "message.part.updated") {
|
|
1363
1480
|
if (emitLegacyTextPartUpdate({ part: props.part, state, emit })) return;
|
|
1481
|
+
if (emitLegacyStepFinishPart({ part: props.part, state, emit })) return;
|
|
1364
1482
|
emitLegacyToolPart({ part: props.part, state, emit });
|
|
1365
1483
|
return;
|
|
1366
1484
|
}
|
|
@@ -1508,13 +1626,25 @@ async function translateAndEmit({
|
|
|
1508
1626
|
});
|
|
1509
1627
|
return;
|
|
1510
1628
|
}
|
|
1629
|
+
if (type === "session.next.retried") {
|
|
1630
|
+
const error = props.error ?? event;
|
|
1631
|
+
if (isRecord2(error) && error.isRetryable === false) {
|
|
1632
|
+
turn.emitError({
|
|
1633
|
+
error,
|
|
1634
|
+
message: "OpenCode session retry failed"
|
|
1635
|
+
});
|
|
1636
|
+
} else {
|
|
1637
|
+
turn.emitWarning({ message: nextRetryEventMessage(event) });
|
|
1638
|
+
}
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1511
1641
|
if (type === "session.next.step.ended") {
|
|
1512
1642
|
closeLegacyOpenParts({ state, emit });
|
|
1513
1643
|
state.turnUsage = mapUsage(props.tokens);
|
|
1514
1644
|
emit({
|
|
1515
1645
|
type: "finish-step",
|
|
1516
1646
|
finishReason: {
|
|
1517
|
-
unified:
|
|
1647
|
+
unified: mapOpenCodeFinishReason(String(props.finish ?? "stop")),
|
|
1518
1648
|
raw: String(props.finish ?? "stop")
|
|
1519
1649
|
},
|
|
1520
1650
|
usage: state.turnUsage,
|
|
@@ -1544,7 +1674,11 @@ async function translateAndEmit({
|
|
|
1544
1674
|
return;
|
|
1545
1675
|
}
|
|
1546
1676
|
if (type === "session.error" || type === "session.next.step.failed") {
|
|
1547
|
-
|
|
1677
|
+
const error = props.error ?? event;
|
|
1678
|
+
turn.emitError({
|
|
1679
|
+
error,
|
|
1680
|
+
message: type === "session.error" ? "OpenCode session error" : "OpenCode step failed"
|
|
1681
|
+
});
|
|
1548
1682
|
return;
|
|
1549
1683
|
}
|
|
1550
1684
|
if (type === "permission.v2.asked") {
|
|
@@ -1592,7 +1726,7 @@ function emitLegacyTextPartUpdate({
|
|
|
1592
1726
|
state,
|
|
1593
1727
|
emit
|
|
1594
1728
|
}) {
|
|
1595
|
-
if (!
|
|
1729
|
+
if (!isRecord2(part)) return false;
|
|
1596
1730
|
if (part.type !== "text" && part.type !== "reasoning") return false;
|
|
1597
1731
|
const id = stringValue(part.id);
|
|
1598
1732
|
if (!id) return true;
|
|
@@ -1627,7 +1761,7 @@ function emitLegacyTextPartUpdate({
|
|
|
1627
1761
|
return true;
|
|
1628
1762
|
}
|
|
1629
1763
|
function legacyPartEnded(part) {
|
|
1630
|
-
return
|
|
1764
|
+
return isRecord2(part.time) && part.time.end != null;
|
|
1631
1765
|
}
|
|
1632
1766
|
function closeLegacyOpenParts({
|
|
1633
1767
|
state,
|
|
@@ -1644,6 +1778,23 @@ function closeLegacyOpenParts({
|
|
|
1644
1778
|
}
|
|
1645
1779
|
state.legacyTextPartIds.clear();
|
|
1646
1780
|
}
|
|
1781
|
+
function emitLegacyStepFinishPart({
|
|
1782
|
+
part,
|
|
1783
|
+
state,
|
|
1784
|
+
emit
|
|
1785
|
+
}) {
|
|
1786
|
+
const event = legacyStepFinishPartToFinishStep(part);
|
|
1787
|
+
if (!event) return false;
|
|
1788
|
+
const id = isRecord2(part) ? stringValue(part.id) : void 0;
|
|
1789
|
+
if (id) {
|
|
1790
|
+
if (state.legacyStepFinishPartIds.has(id)) return true;
|
|
1791
|
+
state.legacyStepFinishPartIds.add(id);
|
|
1792
|
+
}
|
|
1793
|
+
closeLegacyOpenParts({ state, emit });
|
|
1794
|
+
state.turnUsage = event.usage;
|
|
1795
|
+
emit(event);
|
|
1796
|
+
return true;
|
|
1797
|
+
}
|
|
1647
1798
|
function emitLegacyToolPart({
|
|
1648
1799
|
part,
|
|
1649
1800
|
state,
|
|
@@ -1704,9 +1855,9 @@ function legacyToolPartStatus(part) {
|
|
|
1704
1855
|
function legacyToolPartInput(part) {
|
|
1705
1856
|
const state = typeof part.state === "object" && part.state !== null ? part.state : void 0;
|
|
1706
1857
|
return {
|
|
1707
|
-
...
|
|
1708
|
-
...
|
|
1709
|
-
...
|
|
1858
|
+
...isRecord2(part.metadata) ? part.metadata : {},
|
|
1859
|
+
...isRecord2(state?.metadata) ? state.metadata : {},
|
|
1860
|
+
...isRecord2(state?.input) ? state.input : {}
|
|
1710
1861
|
};
|
|
1711
1862
|
}
|
|
1712
1863
|
function legacyToolPartOutput(part) {
|
|
@@ -1716,7 +1867,7 @@ function legacyToolPartOutput(part) {
|
|
|
1716
1867
|
}
|
|
1717
1868
|
return state?.output ?? state?.result ?? state?.structured ?? state?.content ?? null;
|
|
1718
1869
|
}
|
|
1719
|
-
function
|
|
1870
|
+
function isRecord2(value) {
|
|
1720
1871
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1721
1872
|
}
|
|
1722
1873
|
function parseToolInput(state, props) {
|
|
@@ -1912,7 +2063,7 @@ async function emitContextFallback({
|
|
|
1912
2063
|
emit({
|
|
1913
2064
|
type: "finish-step",
|
|
1914
2065
|
finishReason: {
|
|
1915
|
-
unified:
|
|
2066
|
+
unified: mapOpenCodeFinishReason(rawFinish),
|
|
1916
2067
|
raw: rawFinish
|
|
1917
2068
|
},
|
|
1918
2069
|
usage: mapUsage(assistant.tokens),
|
|
@@ -1989,16 +2140,6 @@ function emitAssistantContentPart(part, emit) {
|
|
|
1989
2140
|
if (text) emit({ type: "reasoning-delta", id, delta: text });
|
|
1990
2141
|
emit({ type: "reasoning-end", id });
|
|
1991
2142
|
}
|
|
1992
|
-
function mapFinishReason(reason) {
|
|
1993
|
-
const normalized = reason.toLowerCase();
|
|
1994
|
-
if (normalized.includes("length")) return "length";
|
|
1995
|
-
if (normalized.includes("filter")) return "content-filter";
|
|
1996
|
-
if (normalized.includes("tool")) return "tool-calls";
|
|
1997
|
-
if (normalized.includes("error") || normalized.includes("fail"))
|
|
1998
|
-
return "error";
|
|
1999
|
-
if (normalized === "stop" || normalized === "end") return "stop";
|
|
2000
|
-
return "other";
|
|
2001
|
-
}
|
|
2002
2143
|
async function startToolRelay({
|
|
2003
2144
|
allowedScriptPaths,
|
|
2004
2145
|
tools,
|
|
@@ -2123,13 +2264,13 @@ function modelRefFromAssistantSnapshot(assistant) {
|
|
|
2123
2264
|
if (model) return model;
|
|
2124
2265
|
const direct = modelRefFromValue(assistant);
|
|
2125
2266
|
if (direct) return direct;
|
|
2126
|
-
if (
|
|
2267
|
+
if (isRecord2(assistant.metadata)) {
|
|
2127
2268
|
return modelRefFromValue(assistant.metadata.assistant);
|
|
2128
2269
|
}
|
|
2129
2270
|
return void 0;
|
|
2130
2271
|
}
|
|
2131
2272
|
function modelRefFromSessionInfo(data) {
|
|
2132
|
-
if (!
|
|
2273
|
+
if (!isRecord2(data)) return void 0;
|
|
2133
2274
|
return modelRefFromValue(data.model) ?? modelRefFromValue(data);
|
|
2134
2275
|
}
|
|
2135
2276
|
function modelRefFromStart(start) {
|
|
@@ -2141,7 +2282,7 @@ function modelRefFromStart(start) {
|
|
|
2141
2282
|
};
|
|
2142
2283
|
}
|
|
2143
2284
|
function modelRefFromValue(value) {
|
|
2144
|
-
if (!
|
|
2285
|
+
if (!isRecord2(value)) return void 0;
|
|
2145
2286
|
const providerID = stringValue(value.providerID);
|
|
2146
2287
|
const modelID = stringValue(value.modelID ?? value.id);
|
|
2147
2288
|
if (!providerID || !modelID) return void 0;
|
|
@@ -2180,14 +2321,8 @@ function formatError(error) {
|
|
|
2180
2321
|
return String(error);
|
|
2181
2322
|
}
|
|
2182
2323
|
}
|
|
2183
|
-
function serialiseError2(err) {
|
|
2184
|
-
if (err instanceof Error) {
|
|
2185
|
-
return { name: err.name, message: err.message, stack: err.stack };
|
|
2186
|
-
}
|
|
2187
|
-
return err;
|
|
2188
|
-
}
|
|
2189
2324
|
function emitFatal(message) {
|
|
2190
|
-
process.stderr.write(`[
|
|
2325
|
+
process.stderr.write(`[OpenCode bridge] ${message}
|
|
2191
2326
|
`);
|
|
2192
2327
|
process.exit(1);
|
|
2193
2328
|
}
|