@ai-sdk/harness-opencode 1.0.93 → 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.
@@ -38,6 +38,7 @@ import {
38
38
  warnCredentialBrokeringUnavailable,
39
39
  waitForBridgeReady,
40
40
  writeSkills as writeHarnessSkills,
41
+ type WriteSkillsResult,
41
42
  } from '@ai-sdk/harness/utils';
42
43
  import {
43
44
  safeParseJSON,
@@ -57,7 +58,7 @@ import {
57
58
  resolveOpenCodeAuthenticationMode,
58
59
  resolveOpenCodeEnv,
59
60
  splitOpenCodeModel,
60
- type OpenCodeAuthOptions,
61
+ type OpenCodeAuthenticationMode,
61
62
  } from './opencode-auth';
62
63
  import {
63
64
  outboundMessageSchema,
@@ -69,28 +70,33 @@ import { VERSION } from './version';
69
70
  type OpenCodeChannel = SandboxChannel<OutboundMessage, InboundMessage>;
70
71
  type OpenCodeRespawnStrategy = 'replay' | 'rerun';
71
72
 
72
- type WriteSkillsResult = {
73
- readonly skillsDir: string;
74
- };
75
-
76
73
  /**
77
74
  * Value to use in User-Agent and `x-client-app` headers.
78
75
  */
79
76
  const OPENCODE_CLIENT_APP = `ai-sdk/harness-opencode/${VERSION}`;
80
77
 
81
78
  export type OpenCodeHarnessSettings = {
82
- readonly auth?: OpenCodeAuthOptions;
79
+ readonly auth?: OpenCodeAuthenticationMode;
83
80
  /**
84
81
  * Customizes each credential value before it is forwarded into a sandbox
85
82
  * process. This does not restrict which credentials the harness adapter can
86
83
  * discover, read, or otherwise access in the host process.
87
84
  */
88
85
  readonly credentialForwarding?: HarnessV1CredentialForwarding;
86
+ /**
87
+ * Additional configuration passed through to OpenCode as-is. OpenCode
88
+ * config keys must use their native names. Values managed by this adapter
89
+ * take precedence over conflicting entries.
90
+ */
91
+ readonly openCodeConfig?: Record<string, unknown>;
89
92
  /**
90
93
  * MCP server definitions keyed by server name. Each definition uses the
91
94
  * underlying runtime's native MCP server configuration format.
92
95
  */
93
96
  readonly mcpServers?: Record<string, unknown>;
97
+ /**
98
+ * @deprecated Use `model` on `HarnessAgent` instead.
99
+ */
94
100
  readonly model?: string;
95
101
  readonly provider?: string;
96
102
  /**
@@ -250,6 +256,7 @@ export function createOpenCode(
250
256
  lifecycleStateSchema: openCodeResumeStateSchema,
251
257
  getBootstrap: getOpenCodeBootstrap,
252
258
  doStart: async startOpts => {
259
+ const configuredModel = startOpts.model ?? settings.model;
253
260
  const sandboxSession = startOpts.sandboxSession;
254
261
  const toolSafeSandboxSession =
255
262
  getRestrictedSandboxSession(sandboxSession);
@@ -290,12 +297,12 @@ export function createOpenCode(
290
297
  const coords = resumeData?.bridge;
291
298
  const authenticationMode = resolveOpenCodeAuthenticationMode({
292
299
  auth: settings.auth,
293
- model: settings.model,
300
+ model: configuredModel,
294
301
  provider: settings.provider,
295
302
  });
296
303
  const resolvedAuthEnvironment = resolveOpenCodeEnv({
297
304
  auth: settings.auth,
298
- model: settings.model,
305
+ model: configuredModel,
299
306
  provider: settings.provider,
300
307
  });
301
308
  let sandboxAuthEnvironment = resolvedAuthEnvironment;
@@ -336,10 +343,18 @@ export function createOpenCode(
336
343
  BOOTSTRAP_DIR,
337
344
  );
338
345
  const workDir = startOpts.sessionWorkDir;
346
+ const sandboxHomeDir = await resolveSandboxHomeDir({
347
+ sandbox: toolSafeSandboxSession,
348
+ abortSignal: startOpts.abortSignal,
349
+ });
350
+ const skillsDir = path.posix.join(sandboxHomeDir, '.agents', 'skills');
339
351
  const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
340
352
  const bridgeStateDir = `${sessionDataDir}/bridge`;
341
353
  const timeoutMs = settings.startupTimeoutMs ?? 120_000;
342
- const model = splitOpenCodeModel(settings.model, settings.provider).model;
354
+ const model = splitOpenCodeModel(
355
+ configuredModel,
356
+ settings.provider,
357
+ ).model;
343
358
 
344
359
  const report = startOpts.observability?.report;
345
360
  const onDiagnostic = report
@@ -390,6 +405,7 @@ export function createOpenCode(
390
405
  model,
391
406
  provider: settings.provider,
392
407
  reasoningVariant: settings.reasoningVariant,
408
+ openCodeConfig: settings.openCodeConfig,
393
409
  mcpServers: settings.mcpServers,
394
410
  openCodeSessionId: resumeSessionId,
395
411
  isResume: true,
@@ -402,6 +418,8 @@ export function createOpenCode(
402
418
  debug: startOpts.observability?.debug,
403
419
  permissionMode: startOpts.permissionMode,
404
420
  builtinToolFiltering: startOpts.builtinToolFiltering,
421
+ sandbox: toolSafeSandboxSession,
422
+ sandboxHomeDir,
405
423
  supportsUserMessageResponses: () => supportsUserMessageResponses,
406
424
  });
407
425
  } catch {}
@@ -430,23 +448,6 @@ export function createOpenCode(
430
448
  settings.mintBridgeToken == null
431
449
  ? randomBytes(32).toString('hex')
432
450
  : settings.mintBridgeToken(sandboxId!);
433
- const sandboxHomeDir = await resolveSandboxHomeDir({
434
- sandbox: toolSafeSandboxSession,
435
- abortSignal: startOpts.abortSignal,
436
- });
437
- const xdgConfigHome = `${sandboxHomeDir}/.config`;
438
- const xdgCacheHome = `${sandboxHomeDir}/.cache`;
439
- const xdgDataHome = `${sandboxHomeDir}/.local/share`;
440
- const xdgStateHome = `${sandboxHomeDir}/.local/state`;
441
- const skillSetup =
442
- startOpts.skills && startOpts.skills.length > 0
443
- ? await writeOpenCodeSkills({
444
- sandbox: toolSafeSandboxSession,
445
- skills: startOpts.skills,
446
- homeDir: sandboxHomeDir,
447
- abortSignal: startOpts.abortSignal,
448
- })
449
- : undefined;
450
451
  const forwardedAuthEnvironment = credentialsBrokered
451
452
  ? sandboxAuthEnvironment
452
453
  : await applyCredentialForwarding({
@@ -460,12 +461,6 @@ export function createOpenCode(
460
461
  AI_SDK_HARNESS_CLIENT_APP: OPENCODE_CLIENT_APP,
461
462
  BRIDGE_CHANNEL_TOKEN: token,
462
463
  BRIDGE_WS_PORT: String(port),
463
- HOME: sandboxHomeDir,
464
- USERPROFILE: sandboxHomeDir,
465
- XDG_CONFIG_HOME: xdgConfigHome,
466
- XDG_CACHE_HOME: xdgCacheHome,
467
- XDG_DATA_HOME: xdgDataHome,
468
- XDG_STATE_HOME: xdgStateHome,
469
464
  ...(respawnStrategy === 'replay'
470
465
  ? { BRIDGE_REPLAY_FROM_DISK: '1' }
471
466
  : {}),
@@ -473,7 +468,7 @@ export function createOpenCode(
473
468
 
474
469
  if (respawnStrategy === undefined) {
475
470
  await toolSafeSandboxSession.run({
476
- command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)} ${shellQuote(xdgConfigHome)} ${shellQuote(xdgCacheHome)} ${shellQuote(xdgDataHome)} ${shellQuote(xdgStateHome)}`,
471
+ command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
477
472
  abortSignal: startOpts.abortSignal,
478
473
  });
479
474
  }
@@ -486,7 +481,7 @@ export function createOpenCode(
486
481
  });
487
482
 
488
483
  const proc = await toolSafeSandboxSession.spawn({
489
- command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(bootstrapDir)}${skillSetup ? ` --skills-dir ${shellQuote(skillSetup.skillsDir)}` : ''}`,
484
+ command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(bootstrapDir)} --skills-dir ${shellQuote(skillsDir)}`,
490
485
  env,
491
486
  abortSignal: startOpts.abortSignal,
492
487
  });
@@ -559,6 +554,7 @@ export function createOpenCode(
559
554
  model,
560
555
  provider: settings.provider,
561
556
  reasoningVariant: settings.reasoningVariant,
557
+ openCodeConfig: settings.openCodeConfig,
562
558
  mcpServers: settings.mcpServers,
563
559
  openCodeSessionId: resumeSessionId,
564
560
  isResume: respawnStrategy !== undefined,
@@ -571,6 +567,8 @@ export function createOpenCode(
571
567
  debug: startOpts.observability?.debug,
572
568
  permissionMode: startOpts.permissionMode,
573
569
  builtinToolFiltering: startOpts.builtinToolFiltering,
570
+ sandbox: toolSafeSandboxSession,
571
+ sandboxHomeDir,
574
572
  supportsUserMessageResponses: () => supportsUserMessageResponses,
575
573
  });
576
574
  },
@@ -654,7 +652,7 @@ async function writeOpenCodeSkills({
654
652
  abortSignal?: AbortSignal;
655
653
  }): Promise<WriteSkillsResult> {
656
654
  const skillsDir = path.posix.join(homeDir, '.agents', 'skills');
657
- await writeHarnessSkills({
655
+ return writeHarnessSkills({
658
656
  sandbox,
659
657
  rootDir: skillsDir,
660
658
  skills,
@@ -664,8 +662,6 @@ async function writeOpenCodeSkills({
664
662
  invalidSkillFilePathMessage: ({ skillName, filePath }) =>
665
663
  `Invalid OpenCode skill file path for ${skillName}: ${filePath}`,
666
664
  });
667
-
668
- return { skillsDir };
669
665
  }
670
666
 
671
667
  function openWebSocket({
@@ -787,6 +783,7 @@ function createSession({
787
783
  model,
788
784
  provider,
789
785
  reasoningVariant,
786
+ openCodeConfig,
790
787
  mcpServers,
791
788
  openCodeSessionId,
792
789
  isResume,
@@ -799,6 +796,8 @@ function createSession({
799
796
  debug,
800
797
  permissionMode,
801
798
  builtinToolFiltering,
799
+ sandbox,
800
+ sandboxHomeDir,
802
801
  supportsUserMessageResponses,
803
802
  }: {
804
803
  sessionId: string;
@@ -807,6 +806,7 @@ function createSession({
807
806
  model: string | undefined;
808
807
  provider: string | undefined;
809
808
  reasoningVariant: string | undefined;
809
+ openCodeConfig: Record<string, unknown> | undefined;
810
810
  mcpServers: Record<string, unknown> | undefined;
811
811
  openCodeSessionId: string | undefined;
812
812
  isResume: boolean;
@@ -819,6 +819,8 @@ function createSession({
819
819
  debug: HarnessV1DebugConfig | undefined;
820
820
  permissionMode: HarnessV1PermissionMode | undefined;
821
821
  builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
822
+ sandbox: SandboxSession;
823
+ sandboxHomeDir: string;
822
824
  supportsUserMessageResponses: () => boolean;
823
825
  }): HarnessV1Session {
824
826
  let stopped = false;
@@ -981,6 +983,7 @@ function createSession({
981
983
  model,
982
984
  provider,
983
985
  ...(reasoningVariant ? { variant: reasoningVariant } : {}),
986
+ ...(openCodeConfig == null ? {} : { openCodeConfig }),
984
987
  ...(mcpServers == null ? {} : { mcpServers }),
985
988
  ...(permissionMode ? { permissionMode } : {}),
986
989
  ...(builtinToolFiltering ? { builtinToolFiltering } : {}),
@@ -1007,6 +1010,12 @@ function createSession({
1007
1010
  harnessId: 'opencode',
1008
1011
  });
1009
1012
  }
1013
+ const skillWriteResult = await writeOpenCodeSkills({
1014
+ sandbox,
1015
+ skills: promptOpts.skills,
1016
+ homeDir: sandboxHomeDir,
1017
+ abortSignal: promptOpts.abortSignal,
1018
+ });
1010
1019
  const control = wireTurn({
1011
1020
  emit: promptOpts.emit,
1012
1021
  abortSignal: promptOpts.abortSignal,
@@ -1026,6 +1035,7 @@ function createSession({
1026
1035
  ...(promptOpts.instructions
1027
1036
  ? { instructions: promptOpts.instructions }
1028
1037
  : {}),
1038
+ skillsChanged: skillWriteResult.changed,
1029
1039
  ...startBase(),
1030
1040
  });
1031
1041
  pendingResumeSessionId = undefined;
@@ -1042,6 +1052,12 @@ function createSession({
1042
1052
  harnessId: 'opencode',
1043
1053
  });
1044
1054
  }
1055
+ const skillWriteResult = await writeOpenCodeSkills({
1056
+ sandbox,
1057
+ skills: continueOpts.skills,
1058
+ homeDir: sandboxHomeDir,
1059
+ abortSignal: continueOpts.abortSignal,
1060
+ });
1045
1061
  const control = wireTurn({
1046
1062
  emit: continueOpts.emit,
1047
1063
  abortSignal: continueOpts.abortSignal,
@@ -1062,6 +1078,7 @@ function createSession({
1062
1078
  ...(continueOpts.instructions
1063
1079
  ? { instructions: continueOpts.instructions }
1064
1080
  : {}),
1081
+ skillsChanged: skillWriteResult.changed,
1065
1082
  ...startBase(),
1066
1083
  });
1067
1084
  pendingResumeSessionId = undefined;
@@ -1089,6 +1106,7 @@ function createSession({
1089
1106
  provider,
1090
1107
  permissionMode,
1091
1108
  debug,
1109
+ openCodeConfig,
1092
1110
  mcpServers,
1093
1111
  resumeSessionId: latestOpenCodeSessionId,
1094
1112
  onCompaction: part => pendingCompactionParts.push(part),
@@ -1261,6 +1279,7 @@ async function runCompactOperation({
1261
1279
  provider,
1262
1280
  permissionMode,
1263
1281
  debug,
1282
+ openCodeConfig,
1264
1283
  mcpServers,
1265
1284
  resumeSessionId,
1266
1285
  onCompaction,
@@ -1270,6 +1289,7 @@ async function runCompactOperation({
1270
1289
  provider: string | undefined;
1271
1290
  permissionMode: HarnessV1PermissionMode | undefined;
1272
1291
  debug: HarnessV1DebugConfig | undefined;
1292
+ openCodeConfig: Record<string, unknown> | undefined;
1273
1293
  mcpServers: Record<string, unknown> | undefined;
1274
1294
  resumeSessionId: string | undefined;
1275
1295
  onCompaction: (part: HarnessV1StreamPart) => void;
@@ -1298,6 +1318,7 @@ async function runCompactOperation({
1298
1318
  tools: [],
1299
1319
  model,
1300
1320
  provider,
1321
+ ...(openCodeConfig == null ? {} : { openCodeConfig }),
1301
1322
  ...(mcpServers == null ? {} : { mcpServers }),
1302
1323
  ...(permissionMode ? { permissionMode } : {}),
1303
1324
  ...(resumeSessionId ? { resumeSessionId } : {}),