@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 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
@@ -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 (activeSocketReadyForLiveEvents && activeSocket?.readyState === WS_OPEN) {
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
- if (activeSocket !== ws) return;
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
- if (activeSocket !== ws) return;
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
- activeSocket = ws;
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
- turnSettled.resolve();
1879
- return true;
1880
- }
1881
- if (event.type === "session.error") {
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(() => {