@ai-sdk/harness-opencode 1.0.72 → 1.0.74
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 +22 -0
- package/dist/bridge/index.mjs +68 -28
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +199 -69
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +3 -0
- package/src/bridge/index.ts +47 -7
- package/src/bridge/opencode-server-auth.ts +15 -0
- package/src/opencode-auth.ts +101 -4
- package/src/opencode-bootstrap.ts +58 -0
- package/src/opencode-harness.ts +101 -76
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @ai-sdk/harness-opencode
|
|
2
2
|
|
|
3
|
+
## 1.0.74
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 62a9c2a: feat(harness): add support for structured output to `HarnessAgent` via `output` property
|
|
8
|
+
- Updated dependencies [62a9c2a]
|
|
9
|
+
- Updated dependencies [d25cae2]
|
|
10
|
+
- @ai-sdk/harness@1.0.73
|
|
11
|
+
|
|
12
|
+
## 1.0.73
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 69bb613: feat(harness): support request transformations in network sandbox abstraction and use it to apply credential brokering when available
|
|
17
|
+
- 52bc889: feat(harness): add `getPortEndpoint()` as more comprehensive replacement to `getPortUrl()` (now deprecated) in `HarnessV1NetworkSandboxSession`
|
|
18
|
+
- 4cd4989: chore(harness): decouple bridge based harness bootstrap recipe logic from dynamic params and enforce unique harnesses in `prepareSandboxForHarness()` helper
|
|
19
|
+
- 02af883: fix(harness-opencode): enhance local OpenCode server behavior
|
|
20
|
+
- Updated dependencies [69bb613]
|
|
21
|
+
- Updated dependencies [52bc889]
|
|
22
|
+
- Updated dependencies [4cd4989]
|
|
23
|
+
- @ai-sdk/harness@1.0.72
|
|
24
|
+
|
|
3
25
|
## 1.0.72
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -53,7 +53,6 @@ async function runBridge(options) {
|
|
|
53
53
|
let currentBoundPort = 0;
|
|
54
54
|
let currentTurnState = "init";
|
|
55
55
|
let activeSocket;
|
|
56
|
-
let activeSocketReadyForLiveEvents = false;
|
|
57
56
|
let isFirstTurn = true;
|
|
58
57
|
let turnAbort;
|
|
59
58
|
let currentUserMessages;
|
|
@@ -137,14 +136,6 @@ async function runBridge(options) {
|
|
|
137
136
|
} catch {
|
|
138
137
|
}
|
|
139
138
|
};
|
|
140
|
-
const sendControl = (msg) => {
|
|
141
|
-
if (activeSocket?.readyState === WS_OPEN) {
|
|
142
|
-
try {
|
|
143
|
-
activeSocket.send(JSON.stringify(msg));
|
|
144
|
-
} catch {
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
139
|
const emit = (event) => {
|
|
149
140
|
const seq = ++seqCounter;
|
|
150
141
|
const line = JSON.stringify({ ...event, seq });
|
|
@@ -152,7 +143,7 @@ async function runBridge(options) {
|
|
|
152
143
|
diskBuffer += `${line}
|
|
153
144
|
`;
|
|
154
145
|
scheduleEventFlush();
|
|
155
|
-
if (
|
|
146
|
+
if (activeSocket?.readyState === WS_OPEN) {
|
|
156
147
|
try {
|
|
157
148
|
activeSocket.send(line);
|
|
158
149
|
} catch {
|
|
@@ -255,8 +246,7 @@ async function runBridge(options) {
|
|
|
255
246
|
const handleInbound = async (msg, ws) => {
|
|
256
247
|
switch (msg.type) {
|
|
257
248
|
case "start": {
|
|
258
|
-
|
|
259
|
-
activeSocketReadyForLiveEvents = true;
|
|
249
|
+
activeSocket = ws;
|
|
260
250
|
const firstTurn = isFirstTurn;
|
|
261
251
|
isFirstTurn = false;
|
|
262
252
|
eventLog = [];
|
|
@@ -336,9 +326,8 @@ async function runBridge(options) {
|
|
|
336
326
|
turnAbort?.abort();
|
|
337
327
|
return;
|
|
338
328
|
case "resume":
|
|
339
|
-
|
|
329
|
+
activeSocket = ws;
|
|
340
330
|
replay(ws, msg.lastSeenEventId);
|
|
341
|
-
activeSocketReadyForLiveEvents = true;
|
|
342
331
|
return;
|
|
343
332
|
case "destroy":
|
|
344
333
|
currentTurnState = "done";
|
|
@@ -350,7 +339,7 @@ async function runBridge(options) {
|
|
|
350
339
|
currentTurnState = "done";
|
|
351
340
|
void writeBridgeMeta("done");
|
|
352
341
|
const data = await onStop?.() ?? {};
|
|
353
|
-
sendControl({ type: "bridge-stop", data });
|
|
342
|
+
sendControl(ws, { type: "bridge-stop", data });
|
|
354
343
|
drainThenExit(ws, 1e3, "stop");
|
|
355
344
|
return;
|
|
356
345
|
}
|
|
@@ -403,9 +392,7 @@ async function runBridge(options) {
|
|
|
403
392
|
ws.close(1008, "unauthorized");
|
|
404
393
|
return;
|
|
405
394
|
}
|
|
406
|
-
|
|
407
|
-
activeSocketReadyForLiveEvents = false;
|
|
408
|
-
sendControl({
|
|
395
|
+
sendControl(ws, {
|
|
409
396
|
type: "bridge-hello",
|
|
410
397
|
state: currentTurnState,
|
|
411
398
|
lastSeq: seqCounter
|
|
@@ -416,7 +403,7 @@ async function runBridge(options) {
|
|
|
416
403
|
const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
|
|
417
404
|
parsed = JSON.parse(text);
|
|
418
405
|
} catch (err) {
|
|
419
|
-
sendControl({
|
|
406
|
+
sendControl(ws, {
|
|
420
407
|
type: "error",
|
|
421
408
|
error: `protocol parse error: ${err.message}`
|
|
422
409
|
});
|
|
@@ -427,7 +414,6 @@ async function runBridge(options) {
|
|
|
427
414
|
ws.on("close", () => {
|
|
428
415
|
if (activeSocket === ws) {
|
|
429
416
|
activeSocket = void 0;
|
|
430
|
-
activeSocketReadyForLiveEvents = false;
|
|
431
417
|
}
|
|
432
418
|
});
|
|
433
419
|
ws.on("error", () => {
|
|
@@ -454,6 +440,14 @@ async function runBridge(options) {
|
|
|
454
440
|
})
|
|
455
441
|
};
|
|
456
442
|
}
|
|
443
|
+
function sendControl(socket, message) {
|
|
444
|
+
if (socket?.readyState === WS_OPEN) {
|
|
445
|
+
try {
|
|
446
|
+
socket.send(JSON.stringify(message));
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
457
451
|
function serialiseError(err) {
|
|
458
452
|
if (err instanceof Error) {
|
|
459
453
|
return { name: err.name, message: err.message, stack: err.stack };
|
|
@@ -972,6 +966,7 @@ function createEmitStreamEvent({
|
|
|
972
966
|
if (type === "session.next.tool.called") {
|
|
973
967
|
const callID = String(props.callID ?? event.id);
|
|
974
968
|
const rawToolName = String(props.tool ?? "unknown");
|
|
969
|
+
if (rawToolName === "StructuredOutput") return;
|
|
975
970
|
const toolName = toWireToolName2(rawToolName);
|
|
976
971
|
state.toolNames.set(callID, { rawToolName, toolName });
|
|
977
972
|
const hostToolName = getHostToolName2(toolName, props.tool);
|
|
@@ -1000,6 +995,7 @@ function createEmitStreamEvent({
|
|
|
1000
995
|
const callID = String(props.callID ?? event.id);
|
|
1001
996
|
const cachedTool = state.toolNames.get(callID);
|
|
1002
997
|
const rawToolName = cachedTool?.rawToolName ?? String(props.tool ?? "");
|
|
998
|
+
if (rawToolName === "StructuredOutput") return;
|
|
1003
999
|
const toolName = cachedTool?.toolName ?? toWireToolName2(rawToolName || "unknown");
|
|
1004
1000
|
if (getHostToolName2(toolName, rawToolName)) return;
|
|
1005
1001
|
emit({
|
|
@@ -1118,6 +1114,7 @@ function emitLegacyToolPart({
|
|
|
1118
1114
|
}
|
|
1119
1115
|
const callID = toolPart.callID;
|
|
1120
1116
|
const rawToolName = toolPart.tool;
|
|
1117
|
+
if (rawToolName === "StructuredOutput") return;
|
|
1121
1118
|
const toolName = toWireToolName2(rawToolName);
|
|
1122
1119
|
state.toolNames.set(callID, { rawToolName, toolName });
|
|
1123
1120
|
const hostToolName = getHostToolName2(toolName, rawToolName);
|
|
@@ -1259,6 +1256,19 @@ function prependOpenCodeBinToPath({
|
|
|
1259
1256
|
].join(path.delimiter);
|
|
1260
1257
|
}
|
|
1261
1258
|
|
|
1259
|
+
// src/bridge/opencode-server-auth.ts
|
|
1260
|
+
import { randomBytes } from "crypto";
|
|
1261
|
+
function configureOpenCodeServerAuth({
|
|
1262
|
+
env
|
|
1263
|
+
}) {
|
|
1264
|
+
const username = env.OPENCODE_SERVER_USERNAME ?? "opencode";
|
|
1265
|
+
const password = randomBytes(32).toString("hex");
|
|
1266
|
+
env.OPENCODE_SERVER_PASSWORD = password;
|
|
1267
|
+
return {
|
|
1268
|
+
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1262
1272
|
// src/bridge/tool-relay.ts
|
|
1263
1273
|
import { createServer } from "http";
|
|
1264
1274
|
|
|
@@ -1540,6 +1550,7 @@ async function ensureRuntime({
|
|
|
1540
1550
|
requestToolResult: turn.requestToolResult
|
|
1541
1551
|
});
|
|
1542
1552
|
}
|
|
1553
|
+
const serverAuthHeaders = configureOpenCodeServerAuth({ env: procEnv2 });
|
|
1543
1554
|
const server = await createOpencodeServer({
|
|
1544
1555
|
hostname: "127.0.0.1",
|
|
1545
1556
|
port: 0,
|
|
@@ -1552,7 +1563,8 @@ async function ensureRuntime({
|
|
|
1552
1563
|
runtime.server = server;
|
|
1553
1564
|
runtime.client = createOpencodeClient({
|
|
1554
1565
|
baseUrl: server.url,
|
|
1555
|
-
directory: workdir
|
|
1566
|
+
directory: workdir,
|
|
1567
|
+
headers: serverAuthHeaders
|
|
1556
1568
|
});
|
|
1557
1569
|
const mcpStatus = await runtime.client.mcp.status();
|
|
1558
1570
|
const mcpServers = asOpenCodeObject(mcpStatus.data) ?? {};
|
|
@@ -1721,6 +1733,12 @@ async function legacySessionPrompt({
|
|
|
1721
1733
|
sessionID: sessionId,
|
|
1722
1734
|
...start.instructions ? { system: start.instructions } : {},
|
|
1723
1735
|
...start.variant ? { variant: start.variant } : {},
|
|
1736
|
+
...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? {
|
|
1737
|
+
format: {
|
|
1738
|
+
type: "json_schema",
|
|
1739
|
+
schema: start.responseFormat.schema
|
|
1740
|
+
}
|
|
1741
|
+
} : {},
|
|
1724
1742
|
parts: [{ type: "text", text: start.prompt }]
|
|
1725
1743
|
});
|
|
1726
1744
|
}
|
|
@@ -1860,11 +1878,36 @@ async function runPrompt({
|
|
|
1860
1878
|
state,
|
|
1861
1879
|
emit
|
|
1862
1880
|
});
|
|
1881
|
+
const info = asOpenCodeObject(event.properties?.info);
|
|
1882
|
+
if (start.responseFormat?.type === "json" && info?.structured !== void 0) {
|
|
1883
|
+
const id = String(info.id ?? randomUUID());
|
|
1884
|
+
emit({ type: "text-start", id });
|
|
1885
|
+
emit({
|
|
1886
|
+
type: "text-delta",
|
|
1887
|
+
id,
|
|
1888
|
+
delta: JSON.stringify(info.structured)
|
|
1889
|
+
});
|
|
1890
|
+
emit({ type: "text-end", id });
|
|
1891
|
+
emit({
|
|
1892
|
+
type: "finish-step",
|
|
1893
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
1894
|
+
usage: defaultUsage()
|
|
1895
|
+
});
|
|
1896
|
+
sawFinishStep = true;
|
|
1897
|
+
turnSettled.resolve();
|
|
1898
|
+
return true;
|
|
1899
|
+
}
|
|
1863
1900
|
}
|
|
1864
1901
|
if (event.type === "session.updated") {
|
|
1865
1902
|
latestSessionTokens = extractSessionTokens(event.properties) ?? latestSessionTokens;
|
|
1866
1903
|
}
|
|
1867
1904
|
if (isStepSettlementEvent(event)) {
|
|
1905
|
+
if (event.type === "session.error") {
|
|
1906
|
+
terminalError = formatError(event.properties?.error ?? event);
|
|
1907
|
+
}
|
|
1908
|
+
if (start.responseFormat?.type === "json" && event.type === "session.next.step.ended") {
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1868
1911
|
turnSettled.resolve();
|
|
1869
1912
|
return true;
|
|
1870
1913
|
}
|
|
@@ -1875,13 +1918,10 @@ async function runPrompt({
|
|
|
1875
1918
|
sawBusy = true;
|
|
1876
1919
|
turn.emitWarning({ message: legacyRetryStatusMessage(event) });
|
|
1877
1920
|
} else if (sawBusy && status === "idle") {
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
terminalError = formatError(event.properties?.error ?? event);
|
|
1883
|
-
turnSettled.resolve();
|
|
1884
|
-
return true;
|
|
1921
|
+
if (start.responseFormat?.type !== "json") {
|
|
1922
|
+
turnSettled.resolve();
|
|
1923
|
+
return true;
|
|
1924
|
+
}
|
|
1885
1925
|
}
|
|
1886
1926
|
}
|
|
1887
1927
|
}).finally(() => {
|