@ai-sdk/harness-claude-code 1.0.110 → 1.0.112
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 +21 -0
- package/dist/index.js +208 -19
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/claude-code-auth.ts +3 -0
- package/src/claude-code-harness.ts +15 -4
- package/src/claude-code-subscription.ts +258 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @ai-sdk/harness-claude-code
|
|
2
2
|
|
|
3
|
+
## 1.0.112
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cdc12a1: feat(harness): add support for using adapter-native subscriptions where available
|
|
8
|
+
- Updated dependencies [4d1bf28]
|
|
9
|
+
- Updated dependencies [cdc12a1]
|
|
10
|
+
- Updated dependencies [d70a334]
|
|
11
|
+
- Updated dependencies [81ba84c]
|
|
12
|
+
- Updated dependencies [813bb36]
|
|
13
|
+
- Updated dependencies [c43e4b7]
|
|
14
|
+
- @ai-sdk/harness@1.0.108
|
|
15
|
+
- @ai-sdk/provider-utils@5.0.40
|
|
16
|
+
|
|
17
|
+
## 1.0.111
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- @ai-sdk/harness@1.0.107
|
|
22
|
+
- @ai-sdk/provider-utils@5.0.39
|
|
23
|
+
|
|
3
24
|
## 1.0.110
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
writeSkills
|
|
29
29
|
} from "@ai-sdk/harness/utils";
|
|
30
30
|
import {
|
|
31
|
-
safeParseJSON,
|
|
31
|
+
safeParseJSON as safeParseJSON2,
|
|
32
32
|
tool
|
|
33
33
|
} from "@ai-sdk/provider-utils";
|
|
34
34
|
import { WebSocket } from "ws";
|
|
@@ -86,7 +86,8 @@ import {
|
|
|
86
86
|
var CLAUDE_CODE_CREDENTIAL_ENVIRONMENT_VARIABLES = [
|
|
87
87
|
"AI_GATEWAY_API_KEY",
|
|
88
88
|
"ANTHROPIC_API_KEY",
|
|
89
|
-
"ANTHROPIC_AUTH_TOKEN"
|
|
89
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
90
|
+
"CLAUDE_CODE_OAUTH_TOKEN"
|
|
90
91
|
];
|
|
91
92
|
function createClaudeCodeRequestTransformations({
|
|
92
93
|
env: environment,
|
|
@@ -164,8 +165,10 @@ function pickAnthropic({
|
|
|
164
165
|
const helperKey = readApiKey();
|
|
165
166
|
const apiKey = processEnv.ANTHROPIC_API_KEY ?? helperKey;
|
|
166
167
|
const authToken = processEnv.ANTHROPIC_AUTH_TOKEN ?? helperKey;
|
|
168
|
+
const oauthToken = processEnv.CLAUDE_CODE_OAUTH_TOKEN;
|
|
167
169
|
if (apiKey) env.ANTHROPIC_API_KEY = apiKey;
|
|
168
170
|
if (authToken) env.ANTHROPIC_AUTH_TOKEN = authToken;
|
|
171
|
+
if (oauthToken) env.CLAUDE_CODE_OAUTH_TOKEN = oauthToken;
|
|
169
172
|
const baseUrl = processEnv.ANTHROPIC_BASE_URL;
|
|
170
173
|
if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
|
|
171
174
|
return env;
|
|
@@ -213,6 +216,184 @@ function pickGateway({
|
|
|
213
216
|
return env;
|
|
214
217
|
}
|
|
215
218
|
|
|
219
|
+
// src/claude-code-subscription.ts
|
|
220
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
221
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
222
|
+
import { homedir as homedir2 } from "os";
|
|
223
|
+
import { dirname, join as join2, resolve } from "path";
|
|
224
|
+
import {
|
|
225
|
+
createCredentialRequestTransformation as createCredentialRequestTransformation2,
|
|
226
|
+
isAccessTokenExpiringSoon,
|
|
227
|
+
isHarnessAuthenticationEnvironment as isHarnessAuthenticationEnvironment2,
|
|
228
|
+
isMacOS,
|
|
229
|
+
readMacOSKeychainPassword,
|
|
230
|
+
refreshOAuthAccessToken,
|
|
231
|
+
shouldResolveNativeSubscription
|
|
232
|
+
} from "@ai-sdk/harness/utils";
|
|
233
|
+
import { isRecord, safeParseJSON } from "@ai-sdk/provider-utils";
|
|
234
|
+
var CLAUDE_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
235
|
+
var CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
236
|
+
function createClaudeCodeSubscriptionRequestTransformations({
|
|
237
|
+
env: environment,
|
|
238
|
+
sandboxEnv: sandboxEnvironment,
|
|
239
|
+
auth: authenticationMode
|
|
240
|
+
}) {
|
|
241
|
+
if (environment.CLAUDE_CODE_OAUTH_TOKEN == null || sandboxEnvironment.CLAUDE_CODE_OAUTH_TOKEN == null) {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
return [
|
|
245
|
+
createCredentialRequestTransformation2({
|
|
246
|
+
matchUrl: authenticationMode === "ai-gateway" ? environment.ANTHROPIC_BASE_URL : environment.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
|
|
247
|
+
matchHeaders: {
|
|
248
|
+
Authorization: `Bearer ${sandboxEnvironment.CLAUDE_CODE_OAUTH_TOKEN}`
|
|
249
|
+
},
|
|
250
|
+
transformHeaders: {
|
|
251
|
+
Authorization: `Bearer ${environment.CLAUDE_CODE_OAUTH_TOKEN}`
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
async function resolveClaudeCodeAuthentication({
|
|
257
|
+
auth,
|
|
258
|
+
processEnv = process.env,
|
|
259
|
+
options = {},
|
|
260
|
+
readSubscription = readClaudeCodeSubscription
|
|
261
|
+
}) {
|
|
262
|
+
const environmentWithoutHelper = resolveClaudeCodeEnv(auth, processEnv, {
|
|
263
|
+
readApiKeyHelper: () => void 0
|
|
264
|
+
});
|
|
265
|
+
if (isHarnessAuthenticationEnvironment2(auth)) {
|
|
266
|
+
return environmentWithoutHelper;
|
|
267
|
+
}
|
|
268
|
+
if (!shouldResolveNativeSubscription({
|
|
269
|
+
auth,
|
|
270
|
+
env: environmentWithoutHelper,
|
|
271
|
+
hasDirectCredential: environmentWithoutHelper.ANTHROPIC_API_KEY != null || environmentWithoutHelper.ANTHROPIC_AUTH_TOKEN != null || environmentWithoutHelper.CLAUDE_CODE_OAUTH_TOKEN != null
|
|
272
|
+
})) {
|
|
273
|
+
return environmentWithoutHelper;
|
|
274
|
+
}
|
|
275
|
+
return await readSubscription() ?? resolveClaudeCodeEnv(auth, processEnv, options);
|
|
276
|
+
}
|
|
277
|
+
async function readClaudeCodeSubscription({
|
|
278
|
+
env = process.env,
|
|
279
|
+
homeDirectory = homedir2(),
|
|
280
|
+
platform = process.platform,
|
|
281
|
+
fetch
|
|
282
|
+
} = {}) {
|
|
283
|
+
const configDirectory = resolve(
|
|
284
|
+
env.CLAUDE_CONFIG_DIR ?? join2(homeDirectory, ".claude")
|
|
285
|
+
);
|
|
286
|
+
const credentialPath = join2(configDirectory, ".credentials.json");
|
|
287
|
+
const stored = await readClaudeCredentialStore({
|
|
288
|
+
credentialPath,
|
|
289
|
+
homeDirectory,
|
|
290
|
+
configDirectory,
|
|
291
|
+
platform
|
|
292
|
+
});
|
|
293
|
+
if (stored == null) return void 0;
|
|
294
|
+
const oauth = getClaudeOAuthCredential({ value: stored.value });
|
|
295
|
+
if (oauth == null) return void 0;
|
|
296
|
+
const { accessToken, refreshToken, expiresAt } = oauth;
|
|
297
|
+
let resolvedAccessToken = accessToken;
|
|
298
|
+
if (isAccessTokenExpiringSoon({ expiresAt })) {
|
|
299
|
+
const refreshed = await refreshOAuthAccessToken({
|
|
300
|
+
tokenUrl: CLAUDE_TOKEN_URL,
|
|
301
|
+
clientId: CLAUDE_CLIENT_ID,
|
|
302
|
+
refreshToken,
|
|
303
|
+
requestFormat: "json",
|
|
304
|
+
...fetch == null ? {} : { fetch }
|
|
305
|
+
});
|
|
306
|
+
resolvedAccessToken = refreshed.accessToken;
|
|
307
|
+
await stored.write({
|
|
308
|
+
...stored.value,
|
|
309
|
+
claudeAiOauth: {
|
|
310
|
+
...oauth,
|
|
311
|
+
accessToken: refreshed.accessToken,
|
|
312
|
+
refreshToken: refreshed.refreshToken ?? refreshToken,
|
|
313
|
+
expiresAt: refreshed.expiresAt
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
CLAUDE_CODE_OAUTH_TOKEN: resolvedAccessToken,
|
|
319
|
+
ANTHROPIC_BASE_URL: "https://api.anthropic.com"
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function readClaudeCredentialStore({
|
|
323
|
+
credentialPath,
|
|
324
|
+
homeDirectory,
|
|
325
|
+
configDirectory,
|
|
326
|
+
platform
|
|
327
|
+
}) {
|
|
328
|
+
const text = await readFile(credentialPath, "utf8").catch(() => void 0);
|
|
329
|
+
if (text != null) {
|
|
330
|
+
const value2 = await parseClaudeCredentialFile(text);
|
|
331
|
+
if (value2 != null && getClaudeOAuthCredential({ value: value2 }) != null) {
|
|
332
|
+
return {
|
|
333
|
+
value: value2,
|
|
334
|
+
write: (updated) => writeClaudeCredentialFile({ credentialPath, value: updated })
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (!isMacOS(platform)) return void 0;
|
|
339
|
+
if (configDirectory !== resolve(join2(homeDirectory, ".claude"))) {
|
|
340
|
+
return void 0;
|
|
341
|
+
}
|
|
342
|
+
const service = "Claude Code-credentials";
|
|
343
|
+
const keychainText = await readMacOSKeychainPassword({
|
|
344
|
+
service,
|
|
345
|
+
account: process.env.USER ?? ""
|
|
346
|
+
});
|
|
347
|
+
if (keychainText == null) return void 0;
|
|
348
|
+
const value = await parseClaudeCredentialFile(keychainText);
|
|
349
|
+
if (value == null) return void 0;
|
|
350
|
+
return {
|
|
351
|
+
value,
|
|
352
|
+
write: async (updated) => {
|
|
353
|
+
execFileSync2(
|
|
354
|
+
"/usr/bin/security",
|
|
355
|
+
[
|
|
356
|
+
"add-generic-password",
|
|
357
|
+
"-U",
|
|
358
|
+
"-s",
|
|
359
|
+
service,
|
|
360
|
+
"-a",
|
|
361
|
+
process.env.USER ?? "",
|
|
362
|
+
"-w",
|
|
363
|
+
JSON.stringify(updated)
|
|
364
|
+
],
|
|
365
|
+
{ stdio: "ignore" }
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function getClaudeOAuthCredential({
|
|
371
|
+
value
|
|
372
|
+
}) {
|
|
373
|
+
const oauth = value.claudeAiOauth;
|
|
374
|
+
if (!isRecord(oauth) || typeof oauth.accessToken !== "string" || typeof oauth.refreshToken !== "string" || typeof oauth.expiresAt !== "number") {
|
|
375
|
+
return void 0;
|
|
376
|
+
}
|
|
377
|
+
return oauth;
|
|
378
|
+
}
|
|
379
|
+
async function parseClaudeCredentialFile(text) {
|
|
380
|
+
const parsed = await safeParseJSON({ text });
|
|
381
|
+
return parsed.success && isRecord(parsed.value) ? parsed.value : void 0;
|
|
382
|
+
}
|
|
383
|
+
async function writeClaudeCredentialFile({
|
|
384
|
+
credentialPath,
|
|
385
|
+
value
|
|
386
|
+
}) {
|
|
387
|
+
await mkdir(dirname(credentialPath), { recursive: true });
|
|
388
|
+
const temporaryPath = `${credentialPath}.${process.pid}.tmp`;
|
|
389
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}
|
|
390
|
+
`, {
|
|
391
|
+
mode: 384
|
|
392
|
+
});
|
|
393
|
+
await chmod(temporaryPath, 384);
|
|
394
|
+
await rename(temporaryPath, credentialPath);
|
|
395
|
+
}
|
|
396
|
+
|
|
216
397
|
// src/claude-code-bridge-protocol.ts
|
|
217
398
|
import {
|
|
218
399
|
harnessV1BridgeInboundCommandSchemas,
|
|
@@ -261,7 +442,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
261
442
|
]);
|
|
262
443
|
|
|
263
444
|
// src/version.ts
|
|
264
|
-
var VERSION = true ? "1.0.
|
|
445
|
+
var VERSION = true ? "1.0.112" : "0.0.0-test";
|
|
265
446
|
|
|
266
447
|
// src/claude-code-harness.ts
|
|
267
448
|
var CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
|
|
@@ -901,7 +1082,9 @@ function createClaudeCode(settings = {}) {
|
|
|
901
1082
|
const authenticationMode = resolveClaudeCodeAuthenticationMode(
|
|
902
1083
|
settings.auth
|
|
903
1084
|
);
|
|
904
|
-
const resolvedAuthEnvironment =
|
|
1085
|
+
const resolvedAuthEnvironment = await resolveClaudeCodeAuthentication({
|
|
1086
|
+
auth: settings.auth
|
|
1087
|
+
});
|
|
905
1088
|
const claudeEnvironment = {
|
|
906
1089
|
...resolvedAuthEnvironment,
|
|
907
1090
|
/*
|
|
@@ -928,11 +1111,17 @@ function createClaudeCode(settings = {}) {
|
|
|
928
1111
|
...claudeEnvironment,
|
|
929
1112
|
...sandboxCredentialEnvironment
|
|
930
1113
|
};
|
|
931
|
-
const
|
|
1114
|
+
const transformationSources = {
|
|
932
1115
|
env: claudeEnvironment,
|
|
933
1116
|
sandboxEnv: sandboxClaudeEnvironment,
|
|
934
1117
|
auth: authenticationMode
|
|
935
|
-
}
|
|
1118
|
+
};
|
|
1119
|
+
const requestTransformations = [
|
|
1120
|
+
...createClaudeCodeRequestTransformations(transformationSources),
|
|
1121
|
+
...createClaudeCodeSubscriptionRequestTransformations(
|
|
1122
|
+
transformationSources
|
|
1123
|
+
)
|
|
1124
|
+
];
|
|
936
1125
|
if (requestTransformations.length > 0) {
|
|
937
1126
|
await sandboxSession.addRequestTransformations(
|
|
938
1127
|
requestTransformations
|
|
@@ -1201,7 +1390,7 @@ function openWebSocketAndWaitForBridgeHello({
|
|
|
1201
1390
|
getHelloTimeoutMs,
|
|
1202
1391
|
onHello
|
|
1203
1392
|
}) {
|
|
1204
|
-
return new Promise((
|
|
1393
|
+
return new Promise((resolve2, reject) => {
|
|
1205
1394
|
const ws = new WebSocket(endpoint.url, {
|
|
1206
1395
|
headers: endpoint.headers == null ? void 0 : { ...endpoint.headers }
|
|
1207
1396
|
});
|
|
@@ -1235,7 +1424,7 @@ function openWebSocketAndWaitForBridgeHello({
|
|
|
1235
1424
|
reject(err);
|
|
1236
1425
|
} else {
|
|
1237
1426
|
cleanup();
|
|
1238
|
-
|
|
1427
|
+
resolve2(ws);
|
|
1239
1428
|
}
|
|
1240
1429
|
};
|
|
1241
1430
|
const tryResolve = () => {
|
|
@@ -1265,7 +1454,7 @@ function openWebSocketAndWaitForBridgeHello({
|
|
|
1265
1454
|
};
|
|
1266
1455
|
const onMessage = (raw) => {
|
|
1267
1456
|
void (async () => {
|
|
1268
|
-
const parsed = await
|
|
1457
|
+
const parsed = await safeParseJSON2({
|
|
1269
1458
|
text: webSocketMessageToString(raw)
|
|
1270
1459
|
});
|
|
1271
1460
|
if (!parsed.success || settled) return;
|
|
@@ -1338,8 +1527,8 @@ function webSocketMessageToString(raw) {
|
|
|
1338
1527
|
return String(raw);
|
|
1339
1528
|
}
|
|
1340
1529
|
function sleep(ms) {
|
|
1341
|
-
return new Promise((
|
|
1342
|
-
const timer = setTimeout(
|
|
1530
|
+
return new Promise((resolve2) => {
|
|
1531
|
+
const timer = setTimeout(resolve2, ms);
|
|
1343
1532
|
timer.unref?.();
|
|
1344
1533
|
});
|
|
1345
1534
|
}
|
|
@@ -1378,8 +1567,8 @@ function createSession({
|
|
|
1378
1567
|
const wireTurn = (turnOpts) => {
|
|
1379
1568
|
let pendingResolve;
|
|
1380
1569
|
let pendingReject;
|
|
1381
|
-
const done = new Promise((
|
|
1382
|
-
pendingResolve =
|
|
1570
|
+
const done = new Promise((resolve2, reject) => {
|
|
1571
|
+
pendingResolve = resolve2;
|
|
1383
1572
|
pendingReject = reject;
|
|
1384
1573
|
});
|
|
1385
1574
|
const userMessageSubmitter = supportsUserMessageResponses() ? experimental_createBridgeUserMessageSubmitter({
|
|
@@ -1642,8 +1831,8 @@ function createSession({
|
|
|
1642
1831
|
if (proc) {
|
|
1643
1832
|
await Promise.race([
|
|
1644
1833
|
proc.wait(),
|
|
1645
|
-
new Promise((
|
|
1646
|
-
stopTimer = setTimeout(
|
|
1834
|
+
new Promise((resolve2) => {
|
|
1835
|
+
stopTimer = setTimeout(resolve2, 5e3);
|
|
1647
1836
|
stopTimer.unref?.();
|
|
1648
1837
|
})
|
|
1649
1838
|
]);
|
|
@@ -1667,7 +1856,7 @@ function createSession({
|
|
|
1667
1856
|
}
|
|
1668
1857
|
stopped = true;
|
|
1669
1858
|
channel.beginClose();
|
|
1670
|
-
const data = channel.isClosed() ? {} : await new Promise((
|
|
1859
|
+
const data = channel.isClosed() ? {} : await new Promise((resolve2, reject) => {
|
|
1671
1860
|
const timer = setTimeout(() => {
|
|
1672
1861
|
unsub();
|
|
1673
1862
|
reject(
|
|
@@ -1680,7 +1869,7 @@ function createSession({
|
|
|
1680
1869
|
const unsub = channel.on("bridge-stop", (msg) => {
|
|
1681
1870
|
clearTimeout(timer);
|
|
1682
1871
|
unsub();
|
|
1683
|
-
|
|
1872
|
+
resolve2(msg.data);
|
|
1684
1873
|
});
|
|
1685
1874
|
try {
|
|
1686
1875
|
channel.send({ type: "stop" });
|
|
@@ -1695,8 +1884,8 @@ function createSession({
|
|
|
1695
1884
|
if (proc) {
|
|
1696
1885
|
await Promise.race([
|
|
1697
1886
|
proc.wait(),
|
|
1698
|
-
new Promise((
|
|
1699
|
-
stopTimer = setTimeout(
|
|
1887
|
+
new Promise((resolve2) => {
|
|
1888
|
+
stopTimer = setTimeout(resolve2, 5e3);
|
|
1700
1889
|
stopTimer.unref?.();
|
|
1701
1890
|
})
|
|
1702
1891
|
]);
|