@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 +12 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +146 -65
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/claude-code-auth.ts +55 -2
- package/src/claude-code-bootstrap.ts +65 -0
- package/src/claude-code-harness.ts +84 -87
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
|
|
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 {
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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 = (
|
|
859
|
-
return openBridgeWebSocket({
|
|
942
|
+
const buildConnect = (endpoint2) => async () => {
|
|
943
|
+
return openBridgeWebSocket({ endpoint: endpoint2, timeoutMs });
|
|
860
944
|
};
|
|
861
945
|
if (coords) {
|
|
862
946
|
try {
|
|
863
|
-
const
|
|
947
|
+
const endpoint2 = await sandboxSession.getPortEndpoint({
|
|
864
948
|
port: coords.port,
|
|
865
949
|
protocol: "ws"
|
|
866
|
-
})
|
|
950
|
+
});
|
|
951
|
+
const attachEndpoint = withBridgeToken({
|
|
952
|
+
endpoint: endpoint2,
|
|
953
|
+
token: coords.token
|
|
954
|
+
});
|
|
867
955
|
const attachChannel = new SandboxChannel({
|
|
868
|
-
connect: buildConnect(
|
|
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:
|
|
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
|
|
1081
|
+
const endpoint = await sandboxSession.getPortEndpoint({
|
|
995
1082
|
port: boundPort,
|
|
996
1083
|
protocol: "ws"
|
|
997
|
-
})
|
|
1084
|
+
});
|
|
1085
|
+
const bridgeEndpoint = withBridgeToken({ endpoint, token });
|
|
998
1086
|
const channel = new SandboxChannel({
|
|
999
|
-
connect: buildConnect(
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|