@ai-sdk/harness-opencode 1.0.94 → 1.0.95

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,21 @@
1
1
  # @ai-sdk/harness-opencode
2
2
 
3
+ ## 1.0.95
4
+
5
+ ### Patch Changes
6
+
7
+ - b1fd1bb: feat(harness-opencode): pass native OpenCode configuration through the adapter
8
+ - 7608210: feat(harness): add `model` parameter to `HarnessAgent` instead of having each harness adapter support it on their own constructor functions
9
+ - f7bd978: chore(harness): update underlying adapter SDKs to their latest versions
10
+ - 14d4fc0: feat(harness): allow changing harness settings between turns via `prepareCall()` support on `HarnessAgent`
11
+ - Updated dependencies [cc9f6ce]
12
+ - Updated dependencies [7608210]
13
+ - Updated dependencies [6f8a2d7]
14
+ - Updated dependencies [14d4fc0]
15
+ - Updated dependencies [90192f1]
16
+ - @ai-sdk/harness@1.0.93
17
+ - @ai-sdk/provider-utils@5.0.33
18
+
3
19
  ## 1.0.94
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -3,4 +3,28 @@
3
3
  The OpenCode harness connects `HarnessAgent` to OpenCode through a sandboxed
4
4
  bridge.
5
5
 
6
+ ## Native OpenCode configuration
7
+
8
+ Use `openCodeConfig` to pass native OpenCode settings that do not have a
9
+ dedicated adapter option. For example, OpenCode supports selecting a model for
10
+ an individual agent:
11
+
12
+ ```ts
13
+ import { createOpenCode } from '@ai-sdk/harness-opencode';
14
+
15
+ const adapter = createOpenCode({
16
+ openCodeConfig: {
17
+ agent: {
18
+ general: {
19
+ model: 'provider/model-id',
20
+ },
21
+ },
22
+ },
23
+ });
24
+ ```
25
+
26
+ Adapter-managed settings take precedence when the same key is present in
27
+ `openCodeConfig`. Agent-local `permission` and deprecated `tools` settings are
28
+ ignored so they cannot bypass harness permissions or built-in tool filtering.
29
+
6
30
  See the AI SDK documentation for usage.
@@ -141,6 +141,7 @@ var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
141
141
  var WS_OPEN = 1;
142
142
  async function runBridge(options) {
143
143
  const { bridgeType, bridgeStateDir: bridgeStateDir2, onStart, onStop, onDestroy } = options;
144
+ const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
144
145
  const expectedToken = options.token ?? procEnv.BRIDGE_CHANNEL_TOKEN ?? "";
145
146
  const bridgeWsPort = options.port ?? parseInt(procEnv.BRIDGE_WS_PORT ?? "0", 10);
146
147
  const bridgeMetaPath = `${bridgeStateDir2}/bridge-meta.json`;
@@ -157,6 +158,7 @@ async function runBridge(options) {
157
158
  let isFirstTurn = true;
158
159
  let turnAbort;
159
160
  let currentUserMessages;
161
+ let activeTurn;
160
162
  let debugConfig;
161
163
  let consoleCaptureInstalled = false;
162
164
  const envDebugEnabled = ENV_TRUTHY.has(
@@ -347,10 +349,28 @@ async function runBridge(options) {
347
349
  const handleInbound = async (msg, ws) => {
348
350
  switch (msg.type) {
349
351
  case "start": {
352
+ for (; ; ) {
353
+ const pendingTurn = activeTurn;
354
+ if (pendingTurn == null) break;
355
+ turnAbort?.abort();
356
+ currentUserMessages?.close(
357
+ new Error("A new bridge turn replaced the active turn.")
358
+ );
359
+ let graceTimer;
360
+ const settled = await Promise.race([
361
+ pendingTurn.then(() => true),
362
+ new Promise((resolve) => {
363
+ graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
364
+ graceTimer.unref?.();
365
+ })
366
+ ]);
367
+ clearTimeout(graceTimer);
368
+ if (!settled) break;
369
+ }
370
+ let turnFinished;
371
+ const thisTurn = new Promise((resolve) => turnFinished = resolve);
372
+ activeTurn = thisTurn;
350
373
  activeSocket = ws;
351
- currentUserMessages?.close(
352
- new Error("A new bridge turn replaced the active turn.")
353
- );
354
374
  const firstTurn = isFirstTurn;
355
375
  isFirstTurn = false;
356
376
  eventLog = [];
@@ -407,8 +427,12 @@ async function runBridge(options) {
407
427
  if (currentUserMessages === userMessages) {
408
428
  currentUserMessages = void 0;
409
429
  }
410
- currentTurnState = "waiting";
411
- void writeBridgeMeta("waiting");
430
+ if (activeTurn === thisTurn) {
431
+ activeTurn = void 0;
432
+ currentTurnState = "waiting";
433
+ void writeBridgeMeta("waiting");
434
+ }
435
+ turnFinished();
412
436
  }
413
437
  return;
414
438
  }
@@ -592,7 +616,6 @@ function serialiseError(err) {
592
616
 
593
617
  // src/bridge/index.ts
594
618
  import { randomUUID as randomUUID2 } from "crypto";
595
- import { mkdirSync } from "fs";
596
619
  import path2 from "path";
597
620
  import { argv, env as procEnv2 } from "process";
598
621
  import {
@@ -1674,7 +1697,6 @@ var runtime = {
1674
1697
  mcpToolPrefixes: /* @__PURE__ */ new Set()
1675
1698
  };
1676
1699
  prependOpenCodeBinToPath({ bootstrapDir, env: procEnv2 });
1677
- mkdirSync(process.env.HOME ?? "/tmp/opencode-home", { recursive: true });
1678
1700
  await runBridge({
1679
1701
  bridgeType: "opencode",
1680
1702
  bridgeStateDir,
@@ -1687,6 +1709,9 @@ async function runTurn(start, turn) {
1687
1709
  try {
1688
1710
  await ensureRuntime({ start, turn, emit });
1689
1711
  const client = runtime.client;
1712
+ if (start.skillsChanged) {
1713
+ await client.instance.dispose({ directory: workdir });
1714
+ }
1690
1715
  const sessionId = await ensureSession({ client, start, emit });
1691
1716
  if (start.operation === "compact") {
1692
1717
  await runCompaction({ client, sessionId, start, turn, emit });
@@ -1747,6 +1772,7 @@ function buildOpenCodeConfig({
1747
1772
  relayPort
1748
1773
  }) {
1749
1774
  const config = {
1775
+ ...withoutAgentPolicyOverrides(start.openCodeConfig),
1750
1776
  share: "disabled",
1751
1777
  autoupdate: false,
1752
1778
  permission: {
@@ -1800,6 +1826,24 @@ function buildOpenCodeConfig({
1800
1826
  if (Object.keys(mcp).length > 0) config.mcp = mcp;
1801
1827
  return config;
1802
1828
  }
1829
+ function withoutAgentPolicyOverrides(input) {
1830
+ const config = { ...input };
1831
+ for (const key of ["agent", "mode"]) {
1832
+ const agents = asOpenCodeObject(config[key]);
1833
+ if (!agents) continue;
1834
+ config[key] = Object.fromEntries(
1835
+ Object.entries(agents).map(([name, value]) => {
1836
+ const agent = asOpenCodeObject(value);
1837
+ if (!agent) return [name, value];
1838
+ const safeAgent = { ...agent };
1839
+ delete safeAgent.permission;
1840
+ delete safeAgent.tools;
1841
+ return [name, safeAgent];
1842
+ })
1843
+ );
1844
+ }
1845
+ return config;
1846
+ }
1803
1847
  function buildProviderConfig(start) {
1804
1848
  const model = splitModel(start.model, start.provider);
1805
1849
  const providerID = model.providerID ?? start.provider ?? procEnv2.OPENAI_NAME ?? "anthropic";