@ai-sdk/harness-claude-code 1.0.74 → 1.0.75

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,17 @@
1
1
  # @ai-sdk/harness-claude-code
2
2
 
3
+ ## 1.0.75
4
+
5
+ ### Patch Changes
6
+
7
+ - 69bb613: feat(harness): support request transformations in network sandbox abstraction and use it to apply credential brokering when available
8
+ - 52bc889: feat(harness): add `getPortEndpoint()` as more comprehensive replacement to `getPortUrl()` (now deprecated) in `HarnessV1NetworkSandboxSession`
9
+ - 4cd4989: chore(harness): decouple bridge based harness bootstrap recipe logic from dynamic params and enforce unique harnesses in `prepareSandboxForHarness()` helper
10
+ - Updated dependencies [69bb613]
11
+ - Updated dependencies [52bc889]
12
+ - Updated dependencies [4cd4989]
13
+ - @ai-sdk/harness@1.0.72
14
+
3
15
  ## 1.0.74
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@ import { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
4
4
  import * as _ai_sdk_harness from '@ai-sdk/harness';
5
5
  import { HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
6
6
 
7
- type ClaudeCodeAuthenticationMode = 'auto' | 'direct' | 'ai-gateway';
7
+ type ClaudeCodeResolvedAuthenticationMode = 'direct' | 'ai-gateway';
8
+ type ClaudeCodeAuthenticationMode = ClaudeCodeResolvedAuthenticationMode | 'auto';
8
9
  /**
9
10
  * @deprecated Passing an object to auth options is deprecated. Use a `ClaudeCodeAuthenticationMode` string value ("auto" | "direct" | "ai-gateway") instead, and pass credentials via environment variables.
10
11
  */
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  // src/claude-code-harness.ts
2
2
  import { randomBytes } from "crypto";
3
- import { readFile } from "fs/promises";
4
3
  import { posix } from "path";
5
- import { fileURLToPath } from "url";
6
4
  import {
7
5
  commonTool,
8
6
  harnessV1DiagnosticFromBridgeFrame,
@@ -15,9 +13,11 @@ import {
15
13
  drainBridgeProcessStream,
16
14
  forwardBridgeProcessStream,
17
15
  markBridgeStarting,
16
+ maskSandboxCredentials,
18
17
  resolveSandboxHomeDir,
19
18
  SandboxChannel,
20
19
  shellQuote,
20
+ warnCredentialBrokeringUnavailable,
21
21
  waitForBridgeReady,
22
22
  writeSkills as writeHarnessSkills
23
23
  } from "@ai-sdk/harness/utils";
@@ -28,12 +28,84 @@ import {
28
28
  import { WebSocket } from "ws";
29
29
  import { z as z2 } from "zod/v4";
30
30
 
31
+ // src/claude-code-bootstrap.ts
32
+ import { readFile } from "fs/promises";
33
+ import { fileURLToPath } from "url";
34
+ var CLAUDE_CODE_BOOTSTRAP_DIR = ".harness-bootstrap/claude-code";
35
+ var cachedBootstrap;
36
+ async function getClaudeCodeBootstrap() {
37
+ if (cachedBootstrap != null) return cachedBootstrap;
38
+ const [pkg, lock, bridge] = await Promise.all([
39
+ readBridgeAsset("package.json"),
40
+ readBridgeAsset("pnpm-lock.yaml"),
41
+ readBridgeAsset("index.mjs")
42
+ ]);
43
+ cachedBootstrap = {
44
+ harnessId: "claude-code",
45
+ bootstrapDir: CLAUDE_CODE_BOOTSTRAP_DIR,
46
+ files: [
47
+ { path: `${CLAUDE_CODE_BOOTSTRAP_DIR}/package.json`, content: pkg },
48
+ { path: `${CLAUDE_CODE_BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
49
+ { path: `${CLAUDE_CODE_BOOTSTRAP_DIR}/bridge.mjs`, content: bridge }
50
+ ],
51
+ commands: [
52
+ {
53
+ command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
54
+ },
55
+ {
56
+ command: "if [ -f node_modules/@anthropic-ai/claude-code/install.cjs ]; then node node_modules/@anthropic-ai/claude-code/install.cjs; fi && ./node_modules/.bin/claude --version"
57
+ }
58
+ ]
59
+ };
60
+ return cachedBootstrap;
61
+ }
62
+ async function readBridgeAsset(name) {
63
+ const candidates = [
64
+ new URL(`./bridge/${name}`, import.meta.url),
65
+ new URL(`../bridge/${name}`, import.meta.url)
66
+ ];
67
+ let lastErr;
68
+ for (const url of candidates) {
69
+ try {
70
+ return await readFile(fileURLToPath(url), "utf8");
71
+ } catch (err) {
72
+ const code = err.code;
73
+ if (code !== "ENOENT") throw err;
74
+ lastErr = err;
75
+ }
76
+ }
77
+ throw lastErr ?? new Error(`bridge asset not found: ${name}`);
78
+ }
79
+
31
80
  // src/claude-code-auth.ts
32
81
  import { execFileSync } from "child_process";
33
82
  import { readFileSync } from "fs";
34
83
  import { homedir } from "os";
35
84
  import { join } from "path";
36
- import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
85
+ import {
86
+ createCredentialRequestTransformation,
87
+ getAiGatewayAuthFromEnv
88
+ } from "@ai-sdk/harness/utils";
89
+ var CLAUDE_CODE_CREDENTIAL_ENVIRONMENT_VARIABLES = [
90
+ "AI_GATEWAY_API_KEY",
91
+ "ANTHROPIC_API_KEY",
92
+ "ANTHROPIC_AUTH_TOKEN"
93
+ ];
94
+ function createClaudeCodeRequestTransformations(env, auth) {
95
+ const headers = {};
96
+ if (env.ANTHROPIC_API_KEY) {
97
+ headers["x-api-key"] = env.ANTHROPIC_API_KEY;
98
+ }
99
+ if (env.ANTHROPIC_AUTH_TOKEN) {
100
+ headers.Authorization = `Bearer ${env.ANTHROPIC_AUTH_TOKEN}`;
101
+ }
102
+ return Object.keys(headers).length === 0 ? [] : [
103
+ createCredentialRequestTransformation({
104
+ baseUrl: auth === "ai-gateway" ? env.ANTHROPIC_BASE_URL : env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
105
+ headers
106
+ })
107
+ ];
108
+ }
37
109
  function resolveClaudeCodeEnv(auth, processEnv = process.env, options = {}) {
38
110
  const normalizedAuth = normalizeClaudeCodeAuthToLegacyAuth(auth);
39
111
  const readApiKey = options.readApiKeyHelper ?? readApiKeyHelper;
@@ -59,6 +131,15 @@ function resolveClaudeCodeEnv(auth, processEnv = process.env, options = {}) {
59
131
  }
60
132
  return pickAnthropic({ processEnv, readApiKey });
61
133
  }
134
+ function resolveClaudeCodeAuthenticationMode(auth, processEnv = process.env) {
135
+ if (auth === "direct" || typeof auth !== "string" && auth?.anthropic) {
136
+ return "direct";
137
+ }
138
+ if (auth === "ai-gateway" || typeof auth !== "string" && auth?.gateway) {
139
+ return "ai-gateway";
140
+ }
141
+ return getAiGatewayAuthFromEnv({ env: processEnv }).apiKey ? "ai-gateway" : "direct";
142
+ }
62
143
  function normalizeClaudeCodeAuthToLegacyAuth(auth) {
63
144
  if (auth == null || auth === "auto") {
64
145
  return void 0;
@@ -177,7 +258,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
177
258
  ]);
178
259
 
179
260
  // src/version.ts
180
- var VERSION = true ? "1.0.74" : "0.0.0-test";
261
+ var VERSION = true ? "1.0.75" : "0.0.0-test";
181
262
 
182
263
  // src/claude-code-harness.ts
183
264
  var CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
@@ -774,7 +855,6 @@ var CLAUDE_CODE_BUILTIN_TOOLS = {
774
855
  toolUseKind: "edit"
775
856
  }
776
857
  };
777
- var BOOTSTRAP_DIR = ".harness-bootstrap/claude-code";
778
858
  var claudeCodeBridgeCoordsSchema = z2.object({
779
859
  port: z2.number(),
780
860
  token: z2.string(),
@@ -790,7 +870,6 @@ function createClaudeCode(settings = {}) {
790
870
  'Claude Code MCP server name "harness-tools" is reserved for HarnessAgent tools.'
791
871
  );
792
872
  }
793
- let cachedBootstrap;
794
873
  const thinking = settings.thinking ?? {
795
874
  type: "adaptive",
796
875
  display: "summarized"
@@ -802,39 +881,44 @@ function createClaudeCode(settings = {}) {
802
881
  supportsBuiltinToolApprovals: true,
803
882
  supportsBuiltinToolFiltering: true,
804
883
  lifecycleStateSchema: claudeCodeResumeStateSchema,
805
- getBootstrap: async () => {
806
- if (cachedBootstrap != null) return cachedBootstrap;
807
- const [pkg, lock, bridge] = await Promise.all([
808
- readBridgeAsset("package.json"),
809
- readBridgeAsset("pnpm-lock.yaml"),
810
- readBridgeAsset("index.mjs")
811
- ]);
812
- cachedBootstrap = {
813
- harnessId: "claude-code",
814
- bootstrapDir: BOOTSTRAP_DIR,
815
- files: [
816
- { path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },
817
- { path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
818
- { path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge }
819
- ],
820
- commands: [
821
- {
822
- command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
823
- },
824
- {
825
- command: "if [ -f node_modules/@anthropic-ai/claude-code/install.cjs ]; then node node_modules/@anthropic-ai/claude-code/install.cjs; fi && ./node_modules/.bin/claude --version"
826
- }
827
- ]
828
- };
829
- return cachedBootstrap;
830
- },
884
+ getBootstrap: getClaudeCodeBootstrap,
831
885
  doStart: async (startOpts) => {
832
886
  const sandboxSession = startOpts.sandboxSession;
887
+ const authenticationMode = resolveClaudeCodeAuthenticationMode(
888
+ settings.auth
889
+ );
890
+ const resolvedAuthEnvironment = resolveClaudeCodeEnv(settings.auth);
891
+ let authEnv = resolvedAuthEnvironment;
892
+ let sandboxTurnEnvironment = settings.env;
893
+ if (sandboxSession.addRequestTransformations != null) {
894
+ const requestTransformations = createClaudeCodeRequestTransformations(
895
+ {
896
+ ...resolvedAuthEnvironment,
897
+ ...settings.env
898
+ },
899
+ authenticationMode
900
+ );
901
+ if (requestTransformations.length > 0) {
902
+ await sandboxSession.addRequestTransformations(
903
+ requestTransformations
904
+ );
905
+ }
906
+ authEnv = maskSandboxCredentials({
907
+ environment: resolvedAuthEnvironment,
908
+ credentialEnvironmentVariables: CLAUDE_CODE_CREDENTIAL_ENVIRONMENT_VARIABLES
909
+ });
910
+ sandboxTurnEnvironment = maskSandboxCredentials({
911
+ environment: settings.env ?? {},
912
+ credentialEnvironmentVariables: CLAUDE_CODE_CREDENTIAL_ENVIRONMENT_VARIABLES
913
+ });
914
+ } else {
915
+ warnCredentialBrokeringUnavailable();
916
+ }
833
917
  const session = sandboxSession.restricted();
834
918
  const sandboxId = sandboxSession.id;
835
919
  const bootstrapDir = posix.resolve(
836
920
  sandboxSession.defaultWorkingDirectory,
837
- BOOTSTRAP_DIR
921
+ CLAUDE_CODE_BOOTSTRAP_DIR
838
922
  );
839
923
  const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
840
924
  const isResume = lifecycleState != null;
@@ -855,17 +939,21 @@ function createClaudeCode(settings = {}) {
855
939
  harnessId: "claude-code",
856
940
  sessionId: startOpts.sessionId
857
941
  });
858
- const buildConnect = (wsUrl2) => async () => {
859
- return openBridgeWebSocket({ wsUrl: wsUrl2, timeoutMs });
942
+ const buildConnect = (endpoint2) => async () => {
943
+ return openBridgeWebSocket({ endpoint: endpoint2, timeoutMs });
860
944
  };
861
945
  if (coords) {
862
946
  try {
863
- const attachUrl = await sandboxSession.getPortUrl({
947
+ const endpoint2 = await sandboxSession.getPortEndpoint({
864
948
  port: coords.port,
865
949
  protocol: "ws"
866
- }) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`;
950
+ });
951
+ const attachEndpoint = withBridgeToken({
952
+ endpoint: endpoint2,
953
+ token: coords.token
954
+ });
867
955
  const attachChannel = new SandboxChannel({
868
- connect: buildConnect(attachUrl),
956
+ connect: buildConnect(attachEndpoint),
869
957
  outboundSchema: outboundMessageSchema,
870
958
  initialLastSeenEventId: coords.lastSeenEventId,
871
959
  onDiagnostic,
@@ -881,7 +969,7 @@ function createClaudeCode(settings = {}) {
881
969
  proc: void 0,
882
970
  model: settings.model,
883
971
  maxTurns: settings.maxTurns,
884
- env: settings.env,
972
+ env: sandboxTurnEnvironment,
885
973
  thinking,
886
974
  effort: settings.effort,
887
975
  isResume: true,
@@ -917,7 +1005,6 @@ function createClaudeCode(settings = {}) {
917
1005
  }) : void 0;
918
1006
  const port = resolveBridgePort(sandboxSession, settings.port);
919
1007
  const token = settings.mintBridgeToken == null ? randomBytes(32).toString("hex") : settings.mintBridgeToken(sandboxId);
920
- const authEnv = resolveClaudeCodeEnv(settings.auth);
921
1008
  const env = {
922
1009
  ...authEnv,
923
1010
  /*
@@ -991,12 +1078,13 @@ function createClaudeCode(settings = {}) {
991
1078
  })
992
1079
  });
993
1080
  void drainBridgeProcessStream(proc.stdout);
994
- const wsUrl = await sandboxSession.getPortUrl({
1081
+ const endpoint = await sandboxSession.getPortEndpoint({
995
1082
  port: boundPort,
996
1083
  protocol: "ws"
997
- }) + `?agent_bridge_token=${encodeURIComponent(token)}`;
1084
+ });
1085
+ const bridgeEndpoint = withBridgeToken({ endpoint, token });
998
1086
  const channel = new SandboxChannel({
999
- connect: buildConnect(wsUrl),
1087
+ connect: buildConnect(bridgeEndpoint),
1000
1088
  outboundSchema: outboundMessageSchema,
1001
1089
  onDiagnostic,
1002
1090
  onBridgeError,
@@ -1014,7 +1102,7 @@ function createClaudeCode(settings = {}) {
1014
1102
  proc,
1015
1103
  model: settings.model,
1016
1104
  maxTurns: settings.maxTurns,
1017
- env: settings.env,
1105
+ env: sandboxTurnEnvironment,
1018
1106
  thinking,
1019
1107
  effort: settings.effort,
1020
1108
  isResume: respawnStrategy !== void 0,
@@ -1056,30 +1144,15 @@ async function writeClaudeCodeSkills({
1056
1144
  trailingNewline: true
1057
1145
  });
1058
1146
  }
1059
- async function readBridgeAsset(name) {
1060
- const candidates = [
1061
- new URL(`./bridge/${name}`, import.meta.url),
1062
- new URL(`../bridge/${name}`, import.meta.url)
1063
- ];
1064
- let lastErr;
1065
- for (const url of candidates) {
1066
- try {
1067
- return await readFile(fileURLToPath(url), "utf8");
1068
- } catch (err) {
1069
- const code = err.code;
1070
- if (code !== "ENOENT") throw err;
1071
- lastErr = err;
1072
- }
1073
- }
1074
- throw lastErr ?? new Error(`bridge asset not found: ${name}`);
1075
- }
1076
1147
  function openWebSocketAndWaitForBridgeHello({
1077
- url,
1148
+ endpoint,
1078
1149
  openTimeoutMs,
1079
1150
  getHelloTimeoutMs
1080
1151
  }) {
1081
1152
  return new Promise((resolve, reject) => {
1082
- const ws = new WebSocket(url);
1153
+ const ws = new WebSocket(endpoint.url, {
1154
+ headers: endpoint.headers == null ? void 0 : { ...endpoint.headers }
1155
+ });
1083
1156
  let opened = false;
1084
1157
  let sawBridgeHello = false;
1085
1158
  let settled = false;
@@ -1163,7 +1236,7 @@ function openWebSocketAndWaitForBridgeHello({
1163
1236
  });
1164
1237
  }
1165
1238
  async function openBridgeWebSocket({
1166
- wsUrl,
1239
+ endpoint,
1167
1240
  timeoutMs
1168
1241
  }) {
1169
1242
  const deadline = Date.now() + timeoutMs;
@@ -1174,7 +1247,7 @@ async function openBridgeWebSocket({
1174
1247
  try {
1175
1248
  const remaining = Math.max(1, deadline - Date.now());
1176
1249
  return await openWebSocketAndWaitForBridgeHello({
1177
- url: wsUrl,
1250
+ endpoint,
1178
1251
  openTimeoutMs: Math.min(1e4, remaining),
1179
1252
  getHelloTimeoutMs: () => Math.min(5e3, Math.max(1, deadline - Date.now()))
1180
1253
  });
@@ -1201,6 +1274,14 @@ function webSocketMessageToString(raw) {
1201
1274
  }
1202
1275
  return String(raw);
1203
1276
  }
1277
+ function withBridgeToken({
1278
+ endpoint,
1279
+ token
1280
+ }) {
1281
+ const bridgeUrl = new URL(endpoint.url);
1282
+ bridgeUrl.searchParams.set("agent_bridge_token", token);
1283
+ return { ...endpoint, url: bridgeUrl.toString() };
1284
+ }
1204
1285
  function sleep(ms) {
1205
1286
  return new Promise((resolve) => {
1206
1287
  const timer = setTimeout(resolve, ms);