@ai-sdk/harness-claude-code 1.0.73 → 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,25 @@
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
+
15
+ ## 1.0.74
16
+
17
+ ### Patch Changes
18
+
19
+ - 83fe754: chore(harness): simplify the `auth` param to be a simple string to choose the auth method
20
+ - Updated dependencies [8d717b3]
21
+ - @ai-sdk/harness@1.0.71
22
+
3
23
  ## 1.0.73
4
24
 
5
25
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -4,7 +4,12 @@ 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 ClaudeCodeAuthOptions = {
7
+ type ClaudeCodeResolvedAuthenticationMode = 'direct' | 'ai-gateway';
8
+ type ClaudeCodeAuthenticationMode = ClaudeCodeResolvedAuthenticationMode | 'auto';
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.
11
+ */
12
+ type LegacyClaudeCodeAuthOptions = {
8
13
  readonly anthropic?: {
9
14
  readonly apiKey?: string;
10
15
  readonly authToken?: string;
@@ -15,6 +20,7 @@ type ClaudeCodeAuthOptions = {
15
20
  readonly baseUrl?: string;
16
21
  };
17
22
  };
23
+ type ClaudeCodeAuthOptions = ClaudeCodeAuthenticationMode | LegacyClaudeCodeAuthOptions;
18
24
 
19
25
  type ClaudeCodeThinkingConfig = {
20
26
  readonly type: 'adaptive' | 'enabled';
@@ -25428,4 +25434,4 @@ declare const claudeCode: _ai_sdk_harness.HarnessV1<{
25428
25434
  };
25429
25435
  }>;
25430
25436
 
25431
- export { type ClaudeCodeAuthOptions, type ClaudeCodeHarnessSettings, type ClaudeCodeThinkingConfig, VERSION, claudeCode, createClaudeCode };
25437
+ export { type ClaudeCodeAuthOptions, type ClaudeCodeAuthenticationMode, type ClaudeCodeHarnessSettings, type ClaudeCodeThinkingConfig, VERSION, claudeCode, createClaudeCode };
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,21 +28,98 @@ 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 = {}) {
110
+ const normalizedAuth = normalizeClaudeCodeAuthToLegacyAuth(auth);
38
111
  const readApiKey = options.readApiKeyHelper ?? readApiKeyHelper;
39
- if (auth?.anthropic) {
40
- return pickAnthropic({ explicit: auth.anthropic, processEnv, readApiKey });
112
+ if (normalizedAuth?.anthropic) {
113
+ return pickAnthropic({
114
+ explicit: normalizedAuth.anthropic,
115
+ processEnv,
116
+ readApiKey
117
+ });
41
118
  }
42
119
  const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env: processEnv });
43
- if (auth?.gateway) {
120
+ if (normalizedAuth?.gateway) {
44
121
  return pickGateway({
45
- explicit: auth.gateway,
122
+ explicit: normalizedAuth.gateway,
46
123
  gatewayAuthFromEnv
47
124
  });
48
125
  }
@@ -54,6 +131,34 @@ function resolveClaudeCodeEnv(auth, processEnv = process.env, options = {}) {
54
131
  }
55
132
  return pickAnthropic({ processEnv, readApiKey });
56
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
+ }
143
+ function normalizeClaudeCodeAuthToLegacyAuth(auth) {
144
+ if (auth == null || auth === "auto") {
145
+ return void 0;
146
+ }
147
+ if (typeof auth === "string") {
148
+ switch (auth) {
149
+ case "direct":
150
+ return { anthropic: {} };
151
+ case "ai-gateway":
152
+ return { gateway: {} };
153
+ default:
154
+ return void 0;
155
+ }
156
+ }
157
+ console.warn(
158
+ '[claude-code] Passing an object to auth options is deprecated. Use a string mode ("auto" | "direct" | "ai-gateway") instead, and pass credentials via environment variables.'
159
+ );
160
+ return auth;
161
+ }
57
162
  function pickAnthropic({
58
163
  explicit,
59
164
  processEnv,
@@ -153,7 +258,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
153
258
  ]);
154
259
 
155
260
  // src/version.ts
156
- var VERSION = true ? "1.0.73" : "0.0.0-test";
261
+ var VERSION = true ? "1.0.75" : "0.0.0-test";
157
262
 
158
263
  // src/claude-code-harness.ts
159
264
  var CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
@@ -750,7 +855,6 @@ var CLAUDE_CODE_BUILTIN_TOOLS = {
750
855
  toolUseKind: "edit"
751
856
  }
752
857
  };
753
- var BOOTSTRAP_DIR = ".harness-bootstrap/claude-code";
754
858
  var claudeCodeBridgeCoordsSchema = z2.object({
755
859
  port: z2.number(),
756
860
  token: z2.string(),
@@ -766,7 +870,6 @@ function createClaudeCode(settings = {}) {
766
870
  'Claude Code MCP server name "harness-tools" is reserved for HarnessAgent tools.'
767
871
  );
768
872
  }
769
- let cachedBootstrap;
770
873
  const thinking = settings.thinking ?? {
771
874
  type: "adaptive",
772
875
  display: "summarized"
@@ -778,39 +881,44 @@ function createClaudeCode(settings = {}) {
778
881
  supportsBuiltinToolApprovals: true,
779
882
  supportsBuiltinToolFiltering: true,
780
883
  lifecycleStateSchema: claudeCodeResumeStateSchema,
781
- getBootstrap: async () => {
782
- if (cachedBootstrap != null) return cachedBootstrap;
783
- const [pkg, lock, bridge] = await Promise.all([
784
- readBridgeAsset("package.json"),
785
- readBridgeAsset("pnpm-lock.yaml"),
786
- readBridgeAsset("index.mjs")
787
- ]);
788
- cachedBootstrap = {
789
- harnessId: "claude-code",
790
- bootstrapDir: BOOTSTRAP_DIR,
791
- files: [
792
- { path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },
793
- { path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
794
- { path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge }
795
- ],
796
- commands: [
797
- {
798
- command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
799
- },
800
- {
801
- 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"
802
- }
803
- ]
804
- };
805
- return cachedBootstrap;
806
- },
884
+ getBootstrap: getClaudeCodeBootstrap,
807
885
  doStart: async (startOpts) => {
808
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
+ }
809
917
  const session = sandboxSession.restricted();
810
918
  const sandboxId = sandboxSession.id;
811
919
  const bootstrapDir = posix.resolve(
812
920
  sandboxSession.defaultWorkingDirectory,
813
- BOOTSTRAP_DIR
921
+ CLAUDE_CODE_BOOTSTRAP_DIR
814
922
  );
815
923
  const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
816
924
  const isResume = lifecycleState != null;
@@ -831,17 +939,21 @@ function createClaudeCode(settings = {}) {
831
939
  harnessId: "claude-code",
832
940
  sessionId: startOpts.sessionId
833
941
  });
834
- const buildConnect = (wsUrl2) => async () => {
835
- return openBridgeWebSocket({ wsUrl: wsUrl2, timeoutMs });
942
+ const buildConnect = (endpoint2) => async () => {
943
+ return openBridgeWebSocket({ endpoint: endpoint2, timeoutMs });
836
944
  };
837
945
  if (coords) {
838
946
  try {
839
- const attachUrl = await sandboxSession.getPortUrl({
947
+ const endpoint2 = await sandboxSession.getPortEndpoint({
840
948
  port: coords.port,
841
949
  protocol: "ws"
842
- }) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`;
950
+ });
951
+ const attachEndpoint = withBridgeToken({
952
+ endpoint: endpoint2,
953
+ token: coords.token
954
+ });
843
955
  const attachChannel = new SandboxChannel({
844
- connect: buildConnect(attachUrl),
956
+ connect: buildConnect(attachEndpoint),
845
957
  outboundSchema: outboundMessageSchema,
846
958
  initialLastSeenEventId: coords.lastSeenEventId,
847
959
  onDiagnostic,
@@ -857,7 +969,7 @@ function createClaudeCode(settings = {}) {
857
969
  proc: void 0,
858
970
  model: settings.model,
859
971
  maxTurns: settings.maxTurns,
860
- env: settings.env,
972
+ env: sandboxTurnEnvironment,
861
973
  thinking,
862
974
  effort: settings.effort,
863
975
  isResume: true,
@@ -893,7 +1005,6 @@ function createClaudeCode(settings = {}) {
893
1005
  }) : void 0;
894
1006
  const port = resolveBridgePort(sandboxSession, settings.port);
895
1007
  const token = settings.mintBridgeToken == null ? randomBytes(32).toString("hex") : settings.mintBridgeToken(sandboxId);
896
- const authEnv = resolveClaudeCodeEnv(settings.auth);
897
1008
  const env = {
898
1009
  ...authEnv,
899
1010
  /*
@@ -967,12 +1078,13 @@ function createClaudeCode(settings = {}) {
967
1078
  })
968
1079
  });
969
1080
  void drainBridgeProcessStream(proc.stdout);
970
- const wsUrl = await sandboxSession.getPortUrl({
1081
+ const endpoint = await sandboxSession.getPortEndpoint({
971
1082
  port: boundPort,
972
1083
  protocol: "ws"
973
- }) + `?agent_bridge_token=${encodeURIComponent(token)}`;
1084
+ });
1085
+ const bridgeEndpoint = withBridgeToken({ endpoint, token });
974
1086
  const channel = new SandboxChannel({
975
- connect: buildConnect(wsUrl),
1087
+ connect: buildConnect(bridgeEndpoint),
976
1088
  outboundSchema: outboundMessageSchema,
977
1089
  onDiagnostic,
978
1090
  onBridgeError,
@@ -990,7 +1102,7 @@ function createClaudeCode(settings = {}) {
990
1102
  proc,
991
1103
  model: settings.model,
992
1104
  maxTurns: settings.maxTurns,
993
- env: settings.env,
1105
+ env: sandboxTurnEnvironment,
994
1106
  thinking,
995
1107
  effort: settings.effort,
996
1108
  isResume: respawnStrategy !== void 0,
@@ -1032,30 +1144,15 @@ async function writeClaudeCodeSkills({
1032
1144
  trailingNewline: true
1033
1145
  });
1034
1146
  }
1035
- async function readBridgeAsset(name) {
1036
- const candidates = [
1037
- new URL(`./bridge/${name}`, import.meta.url),
1038
- new URL(`../bridge/${name}`, import.meta.url)
1039
- ];
1040
- let lastErr;
1041
- for (const url of candidates) {
1042
- try {
1043
- return await readFile(fileURLToPath(url), "utf8");
1044
- } catch (err) {
1045
- const code = err.code;
1046
- if (code !== "ENOENT") throw err;
1047
- lastErr = err;
1048
- }
1049
- }
1050
- throw lastErr ?? new Error(`bridge asset not found: ${name}`);
1051
- }
1052
1147
  function openWebSocketAndWaitForBridgeHello({
1053
- url,
1148
+ endpoint,
1054
1149
  openTimeoutMs,
1055
1150
  getHelloTimeoutMs
1056
1151
  }) {
1057
1152
  return new Promise((resolve, reject) => {
1058
- const ws = new WebSocket(url);
1153
+ const ws = new WebSocket(endpoint.url, {
1154
+ headers: endpoint.headers == null ? void 0 : { ...endpoint.headers }
1155
+ });
1059
1156
  let opened = false;
1060
1157
  let sawBridgeHello = false;
1061
1158
  let settled = false;
@@ -1139,7 +1236,7 @@ function openWebSocketAndWaitForBridgeHello({
1139
1236
  });
1140
1237
  }
1141
1238
  async function openBridgeWebSocket({
1142
- wsUrl,
1239
+ endpoint,
1143
1240
  timeoutMs
1144
1241
  }) {
1145
1242
  const deadline = Date.now() + timeoutMs;
@@ -1150,7 +1247,7 @@ async function openBridgeWebSocket({
1150
1247
  try {
1151
1248
  const remaining = Math.max(1, deadline - Date.now());
1152
1249
  return await openWebSocketAndWaitForBridgeHello({
1153
- url: wsUrl,
1250
+ endpoint,
1154
1251
  openTimeoutMs: Math.min(1e4, remaining),
1155
1252
  getHelloTimeoutMs: () => Math.min(5e3, Math.max(1, deadline - Date.now()))
1156
1253
  });
@@ -1177,6 +1274,14 @@ function webSocketMessageToString(raw) {
1177
1274
  }
1178
1275
  return String(raw);
1179
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
+ }
1180
1285
  function sleep(ms) {
1181
1286
  return new Promise((resolve) => {
1182
1287
  const timer = setTimeout(resolve, ms);