opencode-anthropic-multi-account 0.2.14 → 0.2.16
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/dist/chunk-IETVH43F.js +114 -0
- package/dist/chunk-IETVH43F.js.map +1 -0
- package/dist/chunk-RVXWLAVK.js +1130 -0
- package/dist/chunk-RVXWLAVK.js.map +1 -0
- package/dist/fingerprint-capture.d.ts +67 -0
- package/dist/fingerprint-capture.js +28 -0
- package/dist/fingerprint-capture.js.map +1 -0
- package/dist/index.js +1379 -704
- package/dist/index.js.map +1 -1
- package/dist/scrub-template.d.ts +12 -0
- package/dist/scrub-template.js +15 -0
- package/dist/scrub-template.js.map +1 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCOUNTS_FILENAME,
|
|
3
|
+
ANTHROPIC_OAUTH_ADAPTER,
|
|
4
|
+
ANTHROPIC_PROFILE_ENDPOINT,
|
|
5
|
+
ANTHROPIC_USAGE_ENDPOINT,
|
|
6
|
+
CLAIMS_FILENAME,
|
|
7
|
+
PLAN_LABELS,
|
|
8
|
+
TOKEN_EXPIRY_BUFFER_MS,
|
|
9
|
+
TOKEN_REFRESH_TIMEOUT_MS,
|
|
10
|
+
cc_derived_defaults_default,
|
|
11
|
+
checkCCCompat,
|
|
12
|
+
createMinimalClient,
|
|
13
|
+
debugLog,
|
|
14
|
+
detectCliVersion,
|
|
15
|
+
detectDrift,
|
|
16
|
+
detectOAuthConfig,
|
|
17
|
+
fingerprint_data_default,
|
|
18
|
+
formatWaitTime,
|
|
19
|
+
getAccountLabel,
|
|
20
|
+
getConfig,
|
|
21
|
+
getConfigDir,
|
|
22
|
+
loadConfig,
|
|
23
|
+
loadTemplate,
|
|
24
|
+
refreshLiveFingerprintAsync,
|
|
25
|
+
showToast,
|
|
26
|
+
sleep,
|
|
27
|
+
updateConfigField
|
|
28
|
+
} from "./chunk-RVXWLAVK.js";
|
|
29
|
+
import "./chunk-IETVH43F.js";
|
|
30
|
+
|
|
1
31
|
// src/index.ts
|
|
2
32
|
import { tool } from "@opencode-ai/plugin";
|
|
3
33
|
import {
|
|
@@ -10,33 +40,8 @@ import {
|
|
|
10
40
|
// src/account-manager.ts
|
|
11
41
|
import { createAccountManagerForProvider } from "opencode-multi-account-core";
|
|
12
42
|
|
|
13
|
-
// src/config.ts
|
|
14
|
-
import {
|
|
15
|
-
createConfigLoader
|
|
16
|
-
} from "opencode-multi-account-core";
|
|
17
|
-
var configLoader = createConfigLoader("claude-multiauth.json");
|
|
18
|
-
var { getConfig, loadConfig, resetConfigCache, updateConfigField } = configLoader;
|
|
19
|
-
|
|
20
43
|
// src/claims.ts
|
|
21
44
|
import { createClaimsManager } from "opencode-multi-account-core";
|
|
22
|
-
|
|
23
|
-
// src/constants.ts
|
|
24
|
-
import { anthropicOAuthAdapter } from "opencode-multi-account-core";
|
|
25
|
-
var ANTHROPIC_OAUTH_ADAPTER = anthropicOAuthAdapter;
|
|
26
|
-
var ANTHROPIC_CLIENT_ID = ANTHROPIC_OAUTH_ADAPTER.oauthClientId;
|
|
27
|
-
var ANTHROPIC_TOKEN_ENDPOINT = ANTHROPIC_OAUTH_ADAPTER.tokenEndpoint;
|
|
28
|
-
var ANTHROPIC_USAGE_ENDPOINT = ANTHROPIC_OAUTH_ADAPTER.usageEndpoint;
|
|
29
|
-
var ANTHROPIC_PROFILE_ENDPOINT = ANTHROPIC_OAUTH_ADAPTER.profileEndpoint;
|
|
30
|
-
var ANTHROPIC_BETA_HEADER = ANTHROPIC_OAUTH_ADAPTER.requestBetaHeader;
|
|
31
|
-
var CLAUDE_CLI_USER_AGENT = ANTHROPIC_OAUTH_ADAPTER.cliUserAgent;
|
|
32
|
-
var TOOL_PREFIX = ANTHROPIC_OAUTH_ADAPTER.toolPrefix;
|
|
33
|
-
var ACCOUNTS_FILENAME = ANTHROPIC_OAUTH_ADAPTER.accountStorageFilename;
|
|
34
|
-
var CLAIMS_FILENAME = "anthropic-multi-account-claims.json";
|
|
35
|
-
var PLAN_LABELS = ANTHROPIC_OAUTH_ADAPTER.planLabels;
|
|
36
|
-
var TOKEN_EXPIRY_BUFFER_MS = 6e4;
|
|
37
|
-
var TOKEN_REFRESH_TIMEOUT_MS = 3e4;
|
|
38
|
-
|
|
39
|
-
// src/claims.ts
|
|
40
45
|
var claimsManager = createClaimsManager(CLAIMS_FILENAME);
|
|
41
46
|
var {
|
|
42
47
|
isClaimedByOther,
|
|
@@ -45,9 +50,155 @@ var {
|
|
|
45
50
|
writeClaim
|
|
46
51
|
} = claimsManager;
|
|
47
52
|
|
|
48
|
-
// src/
|
|
49
|
-
import {
|
|
50
|
-
import * as
|
|
53
|
+
// src/anthropic-oauth.ts
|
|
54
|
+
import { exec } from "child_process";
|
|
55
|
+
import * as v3 from "valibot";
|
|
56
|
+
|
|
57
|
+
// src/cc-derived-profile.ts
|
|
58
|
+
var bundledTemplate = fingerprint_data_default;
|
|
59
|
+
var derivedDefaults = cc_derived_defaults_default;
|
|
60
|
+
var DEFAULT_BASE_API_URL = derivedDefaults.request?.baseApiUrl || "https://api.anthropic.com";
|
|
61
|
+
var DEFAULT_ANTHROPIC_VERSION = bundledTemplate.header_values?.["anthropic-version"] || derivedDefaults.request?.anthropicVersion || "2023-06-01";
|
|
62
|
+
var DEFAULT_X_APP = bundledTemplate.header_values?.["x-app"] || derivedDefaults.request?.xApp || "cli";
|
|
63
|
+
var DEFAULT_BETA_HEADER = bundledTemplate.anthropic_beta || bundledTemplate.header_values?.["anthropic-beta"] || derivedDefaults.request?.betaHeader || "oauth-2025-04-20,interleaved-thinking-2025-05-14";
|
|
64
|
+
function loadCCDerivedRequestProfile() {
|
|
65
|
+
const template = loadTemplate();
|
|
66
|
+
const cliVersion = detectCliVersion();
|
|
67
|
+
const anthropicVersion = template.header_values?.["anthropic-version"] || DEFAULT_ANTHROPIC_VERSION;
|
|
68
|
+
const betaHeader = template.anthropic_beta || template.header_values?.["anthropic-beta"] || DEFAULT_BETA_HEADER;
|
|
69
|
+
const xApp = template.header_values?.["x-app"] || DEFAULT_X_APP;
|
|
70
|
+
return {
|
|
71
|
+
template,
|
|
72
|
+
cliVersion,
|
|
73
|
+
userAgent: `claude-cli/${cliVersion} (external, cli)`,
|
|
74
|
+
anthropicVersion,
|
|
75
|
+
betaHeader,
|
|
76
|
+
xApp,
|
|
77
|
+
baseApiUrl: DEFAULT_BASE_API_URL,
|
|
78
|
+
apiV1BaseUrl: `${DEFAULT_BASE_API_URL}/v1`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function loadCCDerivedAuthProfile() {
|
|
82
|
+
const requestProfile = loadCCDerivedRequestProfile();
|
|
83
|
+
const oauthConfig = await detectOAuthConfig();
|
|
84
|
+
const baseApiUrl = oauthConfig.baseApiUrl || requestProfile.baseApiUrl;
|
|
85
|
+
return {
|
|
86
|
+
...requestProfile,
|
|
87
|
+
oauthConfig,
|
|
88
|
+
baseApiUrl,
|
|
89
|
+
apiV1BaseUrl: `${baseApiUrl}/v1`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/oauth-callback-server.ts
|
|
94
|
+
import { createServer } from "http";
|
|
95
|
+
var DEFAULT_TIMEOUT_MS = 3e5;
|
|
96
|
+
var SUCCESS_REDIRECT_URL = "https://platform.claude.com/oauth/code/success?app=claude-code";
|
|
97
|
+
function startCallbackServer(options) {
|
|
98
|
+
const { expectedState, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
|
|
99
|
+
return new Promise((resolveServer, rejectServer) => {
|
|
100
|
+
let settled = false;
|
|
101
|
+
let resolveCode = null;
|
|
102
|
+
let rejectCode = null;
|
|
103
|
+
let timeoutHandle = null;
|
|
104
|
+
const waitForCode = new Promise((resolve, reject) => {
|
|
105
|
+
resolveCode = resolve;
|
|
106
|
+
rejectCode = reject;
|
|
107
|
+
});
|
|
108
|
+
function settle(error, result) {
|
|
109
|
+
if (settled) return;
|
|
110
|
+
settled = true;
|
|
111
|
+
if (timeoutHandle !== null) {
|
|
112
|
+
clearTimeout(timeoutHandle);
|
|
113
|
+
timeoutHandle = null;
|
|
114
|
+
}
|
|
115
|
+
if (error) {
|
|
116
|
+
rejectCode?.(error);
|
|
117
|
+
} else if (result) {
|
|
118
|
+
resolveCode?.(result);
|
|
119
|
+
}
|
|
120
|
+
resolveCode = null;
|
|
121
|
+
rejectCode = null;
|
|
122
|
+
server.close();
|
|
123
|
+
}
|
|
124
|
+
function stop() {
|
|
125
|
+
settle(new Error("OAuth callback server stopped"));
|
|
126
|
+
}
|
|
127
|
+
function sendResponse(res, status, headers, body, onFinish) {
|
|
128
|
+
res.writeHead(status, { ...headers, Connection: "close" });
|
|
129
|
+
res.end(body ?? "", onFinish);
|
|
130
|
+
}
|
|
131
|
+
const server = createServer(
|
|
132
|
+
(req, res) => {
|
|
133
|
+
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
134
|
+
if (url.pathname !== "/callback") {
|
|
135
|
+
res.writeHead(404, {
|
|
136
|
+
"Content-Type": "text/plain",
|
|
137
|
+
Connection: "close"
|
|
138
|
+
});
|
|
139
|
+
res.end("Not Found");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const code = url.searchParams.get("code");
|
|
143
|
+
const state = url.searchParams.get("state");
|
|
144
|
+
if (state !== expectedState) {
|
|
145
|
+
sendResponse(
|
|
146
|
+
res,
|
|
147
|
+
400,
|
|
148
|
+
{ "Content-Type": "text/plain" },
|
|
149
|
+
"State mismatch",
|
|
150
|
+
() => settle(new Error("OAuth callback state mismatch"))
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (!code) {
|
|
155
|
+
sendResponse(
|
|
156
|
+
res,
|
|
157
|
+
400,
|
|
158
|
+
{ "Content-Type": "text/plain" },
|
|
159
|
+
"Missing code",
|
|
160
|
+
() => settle(new Error("OAuth callback missing code"))
|
|
161
|
+
);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
sendResponse(
|
|
165
|
+
res,
|
|
166
|
+
302,
|
|
167
|
+
{ Location: SUCCESS_REDIRECT_URL },
|
|
168
|
+
void 0,
|
|
169
|
+
() => settle(null, { code, state })
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
server.listen(0, "localhost", () => {
|
|
174
|
+
const addr = server.address();
|
|
175
|
+
const port = addr.port;
|
|
176
|
+
timeoutHandle = setTimeout(() => {
|
|
177
|
+
settle(new Error("OAuth callback timed out"));
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
resolveServer({ port, waitForCode, stop });
|
|
180
|
+
});
|
|
181
|
+
server.on("error", (err) => {
|
|
182
|
+
if (!settled) {
|
|
183
|
+
rejectServer(err);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/oauth-pkce.ts
|
|
190
|
+
import { createHash, randomBytes } from "crypto";
|
|
191
|
+
function base64url(buffer) {
|
|
192
|
+
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
193
|
+
}
|
|
194
|
+
function generatePKCE() {
|
|
195
|
+
const verifier = base64url(randomBytes(32));
|
|
196
|
+
const challenge = base64url(createHash("sha256").update(verifier).digest());
|
|
197
|
+
return { verifier, challenge };
|
|
198
|
+
}
|
|
199
|
+
function generateState() {
|
|
200
|
+
return base64url(randomBytes(32));
|
|
201
|
+
}
|
|
51
202
|
|
|
52
203
|
// src/token-node-request.ts
|
|
53
204
|
import * as childProcess from "child_process";
|
|
@@ -55,8 +206,10 @@ function buildNodeTokenRequestScript() {
|
|
|
55
206
|
return `
|
|
56
207
|
const https = require("node:https");
|
|
57
208
|
const endpoint = process.env.ANTHROPIC_REFRESH_ENDPOINT;
|
|
209
|
+
const contentType = process.env.ANTHROPIC_REFRESH_CONTENT_TYPE || "application/json";
|
|
58
210
|
const timeoutMs = Number(process.env.ANTHROPIC_REFRESH_TIMEOUT_MS || "30000");
|
|
59
211
|
const payload = process.env.ANTHROPIC_REFRESH_REQUEST_BODY || "";
|
|
212
|
+
const userAgent = process.env.ANTHROPIC_REFRESH_USER_AGENT;
|
|
60
213
|
|
|
61
214
|
function printSuccess(body) {
|
|
62
215
|
console.log(JSON.stringify({ ok: true, body }));
|
|
@@ -69,9 +222,10 @@ function printFailure(error) {
|
|
|
69
222
|
const request = https.request(endpoint, {
|
|
70
223
|
method: "POST",
|
|
71
224
|
headers: {
|
|
72
|
-
"Content-Type":
|
|
225
|
+
"Content-Type": contentType,
|
|
73
226
|
Accept: "application/json",
|
|
74
227
|
"Content-Length": Buffer.byteLength(payload).toString(),
|
|
228
|
+
...(userAgent ? { "User-Agent": userAgent } : {}),
|
|
75
229
|
},
|
|
76
230
|
}, (response) => {
|
|
77
231
|
let body = "";
|
|
@@ -104,6 +258,7 @@ request.end();
|
|
|
104
258
|
}
|
|
105
259
|
async function defaultRunNodeTokenRequest(options) {
|
|
106
260
|
const script = buildNodeTokenRequestScript();
|
|
261
|
+
const contentType = options.contentType ?? "application/json";
|
|
107
262
|
return await new Promise((resolve, reject) => {
|
|
108
263
|
childProcess.execFile(
|
|
109
264
|
options.executable,
|
|
@@ -113,9 +268,11 @@ async function defaultRunNodeTokenRequest(options) {
|
|
|
113
268
|
maxBuffer: 1024 * 1024,
|
|
114
269
|
env: {
|
|
115
270
|
...process.env,
|
|
271
|
+
ANTHROPIC_REFRESH_CONTENT_TYPE: contentType,
|
|
116
272
|
ANTHROPIC_REFRESH_ENDPOINT: options.endpoint,
|
|
117
273
|
ANTHROPIC_REFRESH_REQUEST_BODY: options.body,
|
|
118
|
-
ANTHROPIC_REFRESH_TIMEOUT_MS: String(options.timeoutMs)
|
|
274
|
+
ANTHROPIC_REFRESH_TIMEOUT_MS: String(options.timeoutMs),
|
|
275
|
+
ANTHROPIC_REFRESH_USER_AGENT: options.userAgent ?? ""
|
|
119
276
|
}
|
|
120
277
|
},
|
|
121
278
|
(error, stdout, stderr) => {
|
|
@@ -138,30 +295,6 @@ async function runNodeTokenRequest(options) {
|
|
|
138
295
|
return await nodeTokenRequestRunner(options);
|
|
139
296
|
}
|
|
140
297
|
|
|
141
|
-
// src/utils.ts
|
|
142
|
-
import {
|
|
143
|
-
createMinimalClient,
|
|
144
|
-
formatWaitTime,
|
|
145
|
-
getAccountLabel,
|
|
146
|
-
getConfigDir,
|
|
147
|
-
getErrorCode,
|
|
148
|
-
sleep
|
|
149
|
-
} from "opencode-multi-account-core";
|
|
150
|
-
async function showToast(client, message, variant) {
|
|
151
|
-
if (getConfig().quiet_mode) return;
|
|
152
|
-
try {
|
|
153
|
-
await client.tui.showToast({ body: { message, variant } });
|
|
154
|
-
} catch {
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function debugLog(client, message, extra) {
|
|
158
|
-
if (!getConfig().debug) return;
|
|
159
|
-
client.app.log({
|
|
160
|
-
body: { service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName, level: "debug", message, extra }
|
|
161
|
-
}).catch(() => {
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
298
|
// src/usage.ts
|
|
166
299
|
import * as v2 from "valibot";
|
|
167
300
|
|
|
@@ -328,186 +461,180 @@ function getPlanLabel(account) {
|
|
|
328
461
|
return PLAN_LABELS[account.planTier] ?? account.planTier.charAt(0).toUpperCase() + account.planTier.slice(1);
|
|
329
462
|
}
|
|
330
463
|
|
|
331
|
-
// src/
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
464
|
+
// src/anthropic-oauth.ts
|
|
465
|
+
var TOKEN_REQUEST_EXECUTABLE = process.env.OPENCODE_REFRESH_NODE_EXECUTABLE || "node";
|
|
466
|
+
var browserExec = (command, callback) => {
|
|
467
|
+
exec(command, callback);
|
|
468
|
+
};
|
|
469
|
+
var callbackServerStarter = startCallbackServer;
|
|
470
|
+
var profileFetcher = fetchProfile;
|
|
471
|
+
var usageFetcher = fetchUsage;
|
|
472
|
+
function buildTokenRequestError(url, details) {
|
|
473
|
+
return new Error(`Anthropic token request failed. url=${url}; details=${details}`);
|
|
338
474
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
var tokenProxyInstalled = false;
|
|
344
|
-
var tokenProxyOriginalFetch = null;
|
|
345
|
-
var refreshEndpointUrl = new URL(ANTHROPIC_REFRESH_ENDPOINT);
|
|
346
|
-
function buildRefreshRequestError(details) {
|
|
347
|
-
return new Error(`Anthropic token refresh request failed. url=${ANTHROPIC_REFRESH_ENDPOINT}; details=${details}`);
|
|
348
|
-
}
|
|
349
|
-
function buildRefreshInvalidJsonError(body, details) {
|
|
350
|
-
return new Error(`Anthropic token refresh returned invalid JSON. url=${ANTHROPIC_REFRESH_ENDPOINT}; body=${body}; details=${details}`);
|
|
351
|
-
}
|
|
352
|
-
function getRequestUrlString(input) {
|
|
353
|
-
if (typeof input === "string") return input;
|
|
354
|
-
if (input instanceof URL) return input.toString();
|
|
355
|
-
return input.url;
|
|
356
|
-
}
|
|
357
|
-
function isAnthropicTokenEndpoint(input) {
|
|
358
|
-
const rawUrl = getRequestUrlString(input);
|
|
475
|
+
function buildTokenInvalidJsonError(url, body, details) {
|
|
476
|
+
return new Error(`Anthropic token request returned invalid JSON. url=${url}; body=${body}; details=${details}`);
|
|
477
|
+
}
|
|
478
|
+
function parseNodeTokenEnvelope(output, endpoint) {
|
|
359
479
|
try {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
480
|
+
return JSON.parse(output);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
const details = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
483
|
+
throw buildTokenInvalidJsonError(endpoint, output, details);
|
|
364
484
|
}
|
|
365
485
|
}
|
|
366
|
-
function
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
486
|
+
function parseTokenResponseBody(body, endpoint) {
|
|
487
|
+
let parsed;
|
|
488
|
+
try {
|
|
489
|
+
parsed = JSON.parse(body);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
const details = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
492
|
+
throw buildTokenInvalidJsonError(endpoint, body, details);
|
|
372
493
|
}
|
|
373
|
-
return
|
|
494
|
+
return v3.parse(TokenResponseSchema, parsed);
|
|
374
495
|
}
|
|
375
|
-
function
|
|
376
|
-
if (
|
|
377
|
-
return
|
|
496
|
+
function getOpenBrowserCommand(url, platform = process.platform) {
|
|
497
|
+
if (platform === "win32") {
|
|
498
|
+
return `start "" ${JSON.stringify(url)}`;
|
|
378
499
|
}
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
async function getRequestBody(input, init) {
|
|
382
|
-
const body = getRequestBodySource(input, init);
|
|
383
|
-
if (typeof body === "string") return body;
|
|
384
|
-
if (body instanceof URLSearchParams) return body.toString();
|
|
385
|
-
if (body instanceof Uint8Array || body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
386
|
-
return stringifyBinaryBody(body);
|
|
500
|
+
if (platform === "darwin") {
|
|
501
|
+
return `open ${JSON.stringify(url)}`;
|
|
387
502
|
}
|
|
388
|
-
|
|
389
|
-
if (body instanceof ReadableStream) return await new Response(body).text();
|
|
390
|
-
if (input instanceof Request && init?.body === void 0) return await input.clone().text();
|
|
391
|
-
if (body == null) return "";
|
|
392
|
-
throw buildRefreshRequestError(`Unsupported token request body type: ${Object.prototype.toString.call(body)}`);
|
|
393
|
-
}
|
|
394
|
-
function getRequestMethod(input, init) {
|
|
395
|
-
return init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
503
|
+
return `xdg-open ${JSON.stringify(url)}`;
|
|
396
504
|
}
|
|
397
|
-
function
|
|
398
|
-
return tokenProxyContext.getStore()?.proxyTokenRequests === true && isAnthropicTokenEndpoint(input);
|
|
399
|
-
}
|
|
400
|
-
function shouldInjectAnthropicUserAgent(input) {
|
|
401
|
-
const userAgent = tokenProxyContext.getStore()?.userAgent;
|
|
402
|
-
if (!userAgent) return false;
|
|
403
|
-
const rawUrl = getRequestUrlString(input);
|
|
505
|
+
function openBrowser(url) {
|
|
404
506
|
try {
|
|
405
|
-
|
|
406
|
-
|
|
507
|
+
browserExec(getOpenBrowserCommand(url), () => {
|
|
508
|
+
});
|
|
407
509
|
} catch {
|
|
408
|
-
return rawUrl.includes(ANTHROPIC_TOKEN_HOST);
|
|
409
510
|
}
|
|
410
511
|
}
|
|
411
|
-
async function
|
|
512
|
+
async function postTokenEndpoint(contentType, body, timeoutMs = TOKEN_REFRESH_TIMEOUT_MS, userAgent) {
|
|
513
|
+
const derivedProfile = await loadCCDerivedAuthProfile();
|
|
514
|
+
const oauthConfig = derivedProfile.oauthConfig;
|
|
515
|
+
const endpoint = oauthConfig.tokenUrl;
|
|
516
|
+
const resolvedUserAgent = userAgent ?? derivedProfile.userAgent;
|
|
412
517
|
let output;
|
|
413
518
|
try {
|
|
414
519
|
output = await runNodeTokenRequest({
|
|
415
520
|
body,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
521
|
+
contentType,
|
|
522
|
+
endpoint,
|
|
523
|
+
executable: TOKEN_REQUEST_EXECUTABLE,
|
|
524
|
+
timeoutMs,
|
|
525
|
+
userAgent: resolvedUserAgent
|
|
419
526
|
});
|
|
420
527
|
} catch (error) {
|
|
421
528
|
const details = error instanceof Error ? error.message : String(error);
|
|
422
|
-
throw
|
|
529
|
+
throw buildTokenRequestError(endpoint, details);
|
|
423
530
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
} catch (error) {
|
|
428
|
-
const details = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
429
|
-
throw buildRefreshInvalidJsonError(output, details);
|
|
531
|
+
const result = parseNodeTokenEnvelope(output, endpoint);
|
|
532
|
+
if (result.ok) {
|
|
533
|
+
return parseTokenResponseBody(result.body ?? "", endpoint);
|
|
430
534
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (result.error) {
|
|
434
|
-
throw buildRefreshRequestError(result.error);
|
|
435
|
-
}
|
|
436
|
-
throw buildRefreshRequestError(`Error: HTTP request failed. status=${result.status ?? 0}; url=${ANTHROPIC_REFRESH_ENDPOINT}; body=${result.body ?? ""}`);
|
|
535
|
+
if (result.error) {
|
|
536
|
+
throw buildTokenRequestError(endpoint, result.error);
|
|
437
537
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
});
|
|
538
|
+
throw buildTokenRequestError(
|
|
539
|
+
endpoint,
|
|
540
|
+
`Error: HTTP request failed. status=${result.status ?? 0}; url=${endpoint}; body=${result.body ?? ""}`
|
|
541
|
+
);
|
|
444
542
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
headers.set("user-agent", tokenProxyContext.getStore().userAgent);
|
|
457
|
-
return originalFetch(input, { ...init, headers });
|
|
458
|
-
}
|
|
459
|
-
return originalFetch(input, init);
|
|
543
|
+
var CODE_EXCHANGE_TIMEOUT_MS = 3e4;
|
|
544
|
+
async function exchangeCodeForTokens(params) {
|
|
545
|
+
const derivedProfile = await loadCCDerivedAuthProfile();
|
|
546
|
+
const oauthConfig = derivedProfile.oauthConfig;
|
|
547
|
+
const body = JSON.stringify({
|
|
548
|
+
grant_type: "authorization_code",
|
|
549
|
+
client_id: oauthConfig.clientId,
|
|
550
|
+
code: params.code,
|
|
551
|
+
redirect_uri: params.redirectUri,
|
|
552
|
+
code_verifier: params.codeVerifier,
|
|
553
|
+
state: params.state
|
|
460
554
|
});
|
|
555
|
+
return postTokenEndpoint("application/json", body, CODE_EXCHANGE_TIMEOUT_MS);
|
|
556
|
+
}
|
|
557
|
+
async function loginWithOAuth(callbacks) {
|
|
558
|
+
const { oauthConfig: cfg } = await loadCCDerivedAuthProfile();
|
|
559
|
+
const { verifier: codeVerifier, challenge: codeChallenge } = generatePKCE();
|
|
560
|
+
const state = generateState();
|
|
561
|
+
const { port, waitForCode, stop } = await callbackServerStarter({ expectedState: state });
|
|
562
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
563
|
+
try {
|
|
564
|
+
const authorizeUrl = `${cfg.authorizeUrl}?${new URLSearchParams({
|
|
565
|
+
code: "true",
|
|
566
|
+
client_id: cfg.clientId,
|
|
567
|
+
response_type: "code",
|
|
568
|
+
redirect_uri: redirectUri,
|
|
569
|
+
scope: cfg.scopes,
|
|
570
|
+
code_challenge: codeChallenge,
|
|
571
|
+
code_challenge_method: "S256",
|
|
572
|
+
state
|
|
573
|
+
}).toString()}`;
|
|
574
|
+
callbacks.onAuth({
|
|
575
|
+
url: authorizeUrl,
|
|
576
|
+
instructions: "Complete authorization in your browser."
|
|
577
|
+
});
|
|
578
|
+
openBrowser(authorizeUrl);
|
|
579
|
+
callbacks.onProgress?.("Waiting for browser authorization...");
|
|
580
|
+
const { code } = await waitForCode;
|
|
581
|
+
callbacks.onProgress?.("Exchanging authorization code...");
|
|
582
|
+
const tokens = await exchangeCodeForTokens({
|
|
583
|
+
code,
|
|
584
|
+
codeVerifier,
|
|
585
|
+
state,
|
|
586
|
+
redirectUri
|
|
587
|
+
});
|
|
588
|
+
callbacks.onProgress?.("Fetching profile...");
|
|
589
|
+
const profileResult = await profileFetcher(tokens.access_token);
|
|
590
|
+
try {
|
|
591
|
+
await usageFetcher(tokens.access_token);
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
const profileData = profileResult.ok ? profileResult.data : void 0;
|
|
595
|
+
const now2 = Date.now();
|
|
596
|
+
return {
|
|
597
|
+
accessToken: tokens.access_token,
|
|
598
|
+
refreshToken: tokens.refresh_token,
|
|
599
|
+
expiresAt: now2 + tokens.expires_in * 1e3,
|
|
600
|
+
email: profileData?.email,
|
|
601
|
+
planTier: profileData?.planTier ?? "",
|
|
602
|
+
addedAt: now2,
|
|
603
|
+
lastUsed: now2
|
|
604
|
+
};
|
|
605
|
+
} catch (error) {
|
|
606
|
+
stop();
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
461
609
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (profileResult.ok) {
|
|
475
|
-
return profileResult;
|
|
476
|
-
}
|
|
477
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
478
|
-
profileResult = await fetchProfile(accessToken);
|
|
479
|
-
return profileResult;
|
|
480
|
-
}
|
|
481
|
-
async function loginWithPiAi(callbacks) {
|
|
482
|
-
const piCreds = await withAnthropicTokenProxyFetch(
|
|
483
|
-
() => piAiOauth.loginAnthropic({
|
|
484
|
-
onAuth: callbacks.onAuth,
|
|
485
|
-
onPrompt: callbacks.onPrompt,
|
|
486
|
-
onProgress: callbacks.onProgress,
|
|
487
|
-
onManualCodeInput: callbacks.onManualCodeInput
|
|
488
|
-
}),
|
|
489
|
-
{ userAgent: callbacks.userAgent }
|
|
610
|
+
var REFRESH_TIMEOUT_MS = 15e3;
|
|
611
|
+
async function refreshWithOAuth(currentRefreshToken) {
|
|
612
|
+
const { oauthConfig } = await loadCCDerivedAuthProfile();
|
|
613
|
+
const body = new URLSearchParams({
|
|
614
|
+
grant_type: "refresh_token",
|
|
615
|
+
refresh_token: currentRefreshToken,
|
|
616
|
+
client_id: oauthConfig.clientId
|
|
617
|
+
}).toString();
|
|
618
|
+
const response = await postTokenEndpoint(
|
|
619
|
+
"application/x-www-form-urlencoded",
|
|
620
|
+
body,
|
|
621
|
+
REFRESH_TIMEOUT_MS
|
|
490
622
|
);
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
return {
|
|
495
|
-
...base,
|
|
496
|
-
email: profileData?.email,
|
|
497
|
-
planTier: profileData?.planTier ?? "",
|
|
498
|
-
addedAt: Date.now(),
|
|
499
|
-
lastUsed: Date.now()
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
async function refreshWithPiAi(currentRefreshToken) {
|
|
503
|
-
const piCreds = await withAnthropicTokenProxyFetch(() => piAiOauth.refreshAnthropicToken(currentRefreshToken));
|
|
504
|
-
return {
|
|
505
|
-
accessToken: piCreds.access,
|
|
506
|
-
refreshToken: piCreds.refresh,
|
|
507
|
-
expiresAt: piCreds.expires
|
|
623
|
+
const patch = {
|
|
624
|
+
accessToken: response.access_token,
|
|
625
|
+
expiresAt: Date.now() + response.expires_in * 1e3
|
|
508
626
|
};
|
|
627
|
+
if (response.refresh_token) {
|
|
628
|
+
patch.refreshToken = response.refresh_token;
|
|
629
|
+
}
|
|
630
|
+
if (response.account?.uuid) {
|
|
631
|
+
patch.uuid = response.account.uuid;
|
|
632
|
+
}
|
|
633
|
+
if (response.account?.email_address) {
|
|
634
|
+
patch.email = response.account.email_address;
|
|
635
|
+
}
|
|
636
|
+
return patch;
|
|
509
637
|
}
|
|
510
|
-
var PI_AI_ADAPTER_SERVICE = ANTHROPIC_OAUTH_ADAPTER.serviceLogName;
|
|
511
638
|
|
|
512
639
|
// src/token.ts
|
|
513
640
|
var PERMANENT_FAILURE_HTTP_STATUSES = /* @__PURE__ */ new Set([400, 401, 403]);
|
|
@@ -529,7 +656,7 @@ async function refreshToken(currentRefreshToken, accountId, client) {
|
|
|
529
656
|
if (inFlightRefresh) return inFlightRefresh;
|
|
530
657
|
const refreshPromise = (async () => {
|
|
531
658
|
try {
|
|
532
|
-
const patch = await
|
|
659
|
+
const patch = await refreshWithOAuth(currentRefreshToken);
|
|
533
660
|
return { ok: true, patch };
|
|
534
661
|
} catch (error) {
|
|
535
662
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -754,11 +881,11 @@ function getAccountStatus(account) {
|
|
|
754
881
|
if (account.isAuthDisabled) return "auth-disabled";
|
|
755
882
|
if (!account.enabled) return "disabled";
|
|
756
883
|
if (account.cachedUsage) {
|
|
757
|
-
const
|
|
884
|
+
const now2 = Date.now();
|
|
758
885
|
const usage = account.cachedUsage;
|
|
759
886
|
const hasEvaluableUsageTier = [usage.five_hour, usage.seven_day].some((tier) => tier != null);
|
|
760
887
|
const exhaustedTiers = [usage.five_hour, usage.seven_day].filter(
|
|
761
|
-
(tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) >
|
|
888
|
+
(tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now2
|
|
762
889
|
);
|
|
763
890
|
if (exhaustedTiers.length > 0) {
|
|
764
891
|
return "rate-limited";
|
|
@@ -870,10 +997,10 @@ function createProgressBar(utilization, width = 20) {
|
|
|
870
997
|
function formatResetTime(resetAt) {
|
|
871
998
|
if (!resetAt) return "";
|
|
872
999
|
const date = new Date(resetAt);
|
|
873
|
-
const
|
|
1000
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
874
1001
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
875
1002
|
const timeStr = date.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit", hour12: true });
|
|
876
|
-
const isSameDay = date.getFullYear() ===
|
|
1003
|
+
const isSameDay = date.getFullYear() === now2.getFullYear() && date.getMonth() === now2.getMonth() && date.getDate() === now2.getDate();
|
|
877
1004
|
if (isSameDay) {
|
|
878
1005
|
return ` (resets ${timeStr}, ${tz})`;
|
|
879
1006
|
}
|
|
@@ -952,8 +1079,7 @@ var AccountStore = class extends CoreAccountStore {
|
|
|
952
1079
|
|
|
953
1080
|
// src/auth-handler.ts
|
|
954
1081
|
import { randomUUID } from "crypto";
|
|
955
|
-
import {
|
|
956
|
-
import { exec } from "child_process";
|
|
1082
|
+
import { exec as exec2 } from "child_process";
|
|
957
1083
|
function makeFailedFlowResult(message) {
|
|
958
1084
|
return {
|
|
959
1085
|
url: "",
|
|
@@ -976,38 +1102,17 @@ function asOAuthCallbackResponse(account) {
|
|
|
976
1102
|
expires: account.expiresAt
|
|
977
1103
|
};
|
|
978
1104
|
}
|
|
979
|
-
function
|
|
980
|
-
if (!isTTY()) return Promise.resolve("");
|
|
981
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
982
|
-
return new Promise((resolve) => {
|
|
983
|
-
rl.question(message, (answer) => {
|
|
984
|
-
rl.close();
|
|
985
|
-
resolve(answer.trim());
|
|
986
|
-
});
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
function normalizePromptMessage(prompt) {
|
|
990
|
-
return prompt.message ?? prompt.text ?? prompt.label ?? prompt.title ?? prompt.placeholder ?? "Continue authentication: ";
|
|
991
|
-
}
|
|
992
|
-
async function startPiAiFlow() {
|
|
1105
|
+
async function startOAuthFlow() {
|
|
993
1106
|
try {
|
|
994
|
-
const completedAccount = await
|
|
1107
|
+
const completedAccount = await loginWithOAuth({
|
|
995
1108
|
onAuth: (info) => {
|
|
996
|
-
if (info.url) {
|
|
997
|
-
openBrowser(info.url);
|
|
998
|
-
}
|
|
999
1109
|
const instruction = info.instructions ?? "Complete authorization in your browser.";
|
|
1000
1110
|
const urlLine = info.url ? `
|
|
1001
1111
|
Auth URL (manual fallback): ${info.url}` : "";
|
|
1002
1112
|
console.log(`
|
|
1003
1113
|
${instruction}${urlLine}
|
|
1004
1114
|
`);
|
|
1005
|
-
}
|
|
1006
|
-
onPrompt: async (prompt) => {
|
|
1007
|
-
const text = normalizePromptMessage(prompt);
|
|
1008
|
-
return promptLine(text.endsWith(":") || text.endsWith("?") ? `${text} ` : `${text}: `);
|
|
1009
|
-
},
|
|
1010
|
-
userAgent: CLAUDE_CLI_USER_AGENT
|
|
1115
|
+
}
|
|
1011
1116
|
});
|
|
1012
1117
|
const completedResult = asOAuthCallbackResponse(completedAccount);
|
|
1013
1118
|
const accountEmail = completedAccount.email;
|
|
@@ -1028,7 +1133,7 @@ function wrapCallbackWithAccountReplace(result, manager, targetAccount) {
|
|
|
1028
1133
|
...result,
|
|
1029
1134
|
callback: async function(code) {
|
|
1030
1135
|
const callbackResult = await originalCallback(code);
|
|
1031
|
-
if (callbackResult.type === "success"
|
|
1136
|
+
if (callbackResult.type === "success") {
|
|
1032
1137
|
if (targetAccount.uuid) {
|
|
1033
1138
|
await manager.replaceAccountCredentials(targetAccount.uuid, toOAuthCredentials(callbackResult));
|
|
1034
1139
|
}
|
|
@@ -1047,7 +1152,7 @@ function wrapCallbackWithManagerSync(result, manager) {
|
|
|
1047
1152
|
...result,
|
|
1048
1153
|
callback: async function(code) {
|
|
1049
1154
|
const callbackResult = await originalCallback(code);
|
|
1050
|
-
if (callbackResult.type === "success"
|
|
1155
|
+
if (callbackResult.type === "success") {
|
|
1051
1156
|
const auth = toOAuthCredentials(callbackResult);
|
|
1052
1157
|
if (manager) {
|
|
1053
1158
|
const countBefore = manager.getAccounts().length;
|
|
@@ -1068,25 +1173,17 @@ function wrapCallbackWithManagerSync(result, manager) {
|
|
|
1068
1173
|
}
|
|
1069
1174
|
};
|
|
1070
1175
|
}
|
|
1071
|
-
function openBrowser(url) {
|
|
1072
|
-
const commands = {
|
|
1073
|
-
darwin: "open",
|
|
1074
|
-
win32: "start"
|
|
1075
|
-
};
|
|
1076
|
-
const cmd = commands[process.platform] ?? "xdg-open";
|
|
1077
|
-
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
1078
|
-
}
|
|
1079
1176
|
async function persistFallback(auth) {
|
|
1080
1177
|
try {
|
|
1081
1178
|
const store = new AccountStore();
|
|
1082
|
-
const
|
|
1179
|
+
const now2 = Date.now();
|
|
1083
1180
|
const account = {
|
|
1084
1181
|
uuid: randomUUID(),
|
|
1085
1182
|
refreshToken: auth.refresh,
|
|
1086
1183
|
accessToken: auth.access,
|
|
1087
1184
|
expiresAt: auth.expires,
|
|
1088
|
-
addedAt:
|
|
1089
|
-
lastUsed:
|
|
1185
|
+
addedAt: now2,
|
|
1186
|
+
lastUsed: now2,
|
|
1090
1187
|
enabled: true,
|
|
1091
1188
|
planTier: "",
|
|
1092
1189
|
consecutiveAuthFailures: 0,
|
|
@@ -1099,11 +1196,11 @@ async function persistFallback(auth) {
|
|
|
1099
1196
|
}
|
|
1100
1197
|
async function handleAuthorize(manager, inputs, client) {
|
|
1101
1198
|
if (!inputs || !isTTY()) {
|
|
1102
|
-
return wrapCallbackWithManagerSync(await
|
|
1199
|
+
return wrapCallbackWithManagerSync(await startOAuthFlow(), manager);
|
|
1103
1200
|
}
|
|
1104
1201
|
const effectiveManager = manager ?? await loadManagerFromDisk(client);
|
|
1105
1202
|
if (!effectiveManager || effectiveManager.getAccounts().length === 0) {
|
|
1106
|
-
return wrapCallbackWithManagerSync(await
|
|
1203
|
+
return wrapCallbackWithManagerSync(await startOAuthFlow(), manager);
|
|
1107
1204
|
}
|
|
1108
1205
|
return runAccountManagementMenu(effectiveManager, client);
|
|
1109
1206
|
}
|
|
@@ -1120,7 +1217,7 @@ async function runAccountManagementMenu(manager, client) {
|
|
|
1120
1217
|
const menuAction = await showAuthMenu(allAccounts);
|
|
1121
1218
|
switch (menuAction.type) {
|
|
1122
1219
|
case "add":
|
|
1123
|
-
return wrapCallbackWithManagerSync(await
|
|
1220
|
+
return wrapCallbackWithManagerSync(await startOAuthFlow(), manager);
|
|
1124
1221
|
case "check-quotas":
|
|
1125
1222
|
await handleCheckQuotas(manager, client);
|
|
1126
1223
|
continue;
|
|
@@ -1129,7 +1226,7 @@ async function runAccountManagementMenu(manager, client) {
|
|
|
1129
1226
|
if (result.action === "back" || result.action === "cancel") continue;
|
|
1130
1227
|
const manageResult = await handleManageAction(manager, result.action, result.account, client);
|
|
1131
1228
|
if (manageResult.triggerOAuth) {
|
|
1132
|
-
return wrapCallbackWithAccountReplace(await
|
|
1229
|
+
return wrapCallbackWithAccountReplace(await startOAuthFlow(), manager, manageResult.account);
|
|
1133
1230
|
}
|
|
1134
1231
|
continue;
|
|
1135
1232
|
}
|
|
@@ -1139,7 +1236,7 @@ async function runAccountManagementMenu(manager, client) {
|
|
|
1139
1236
|
case "delete-all":
|
|
1140
1237
|
await manager.clearAllAccounts();
|
|
1141
1238
|
console.log("\nAll accounts deleted.\n");
|
|
1142
|
-
return wrapCallbackWithManagerSync(await
|
|
1239
|
+
return wrapCallbackWithManagerSync(await startOAuthFlow(), manager);
|
|
1143
1240
|
case "cancel":
|
|
1144
1241
|
return makeFailedFlowResult("Authentication cancelled");
|
|
1145
1242
|
}
|
|
@@ -1256,15 +1353,27 @@ Retrying authentication for ${label}...
|
|
|
1256
1353
|
return { triggerOAuth: false };
|
|
1257
1354
|
}
|
|
1258
1355
|
|
|
1356
|
+
// src/proactive-refresh.ts
|
|
1357
|
+
import { createProactiveRefreshQueueForProvider } from "opencode-multi-account-core";
|
|
1358
|
+
var ProactiveRefreshQueue = createProactiveRefreshQueueForProvider({
|
|
1359
|
+
providerAuthId: "anthropic",
|
|
1360
|
+
getConfig,
|
|
1361
|
+
isTokenExpired,
|
|
1362
|
+
refreshToken,
|
|
1363
|
+
debugLog
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
// src/runtime-factory.ts
|
|
1367
|
+
import { TokenRefreshError } from "opencode-multi-account-core";
|
|
1368
|
+
|
|
1259
1369
|
// src/request-transform.ts
|
|
1260
|
-
import {
|
|
1370
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1261
1371
|
|
|
1262
1372
|
// src/model-config.ts
|
|
1263
1373
|
function splitBetaFlags(value) {
|
|
1264
1374
|
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1265
1375
|
}
|
|
1266
1376
|
var config = {
|
|
1267
|
-
ccVersion: ANTHROPIC_OAUTH_ADAPTER.cliVersion,
|
|
1268
1377
|
baseBetas: splitBetaFlags(ANTHROPIC_OAUTH_ADAPTER.requestBetaHeader),
|
|
1269
1378
|
longContextBetas: ["context-1m-2025-08-07", "interleaved-thinking-2025-05-14"],
|
|
1270
1379
|
modelOverrides: {
|
|
@@ -1273,18 +1382,6 @@ var config = {
|
|
|
1273
1382
|
}
|
|
1274
1383
|
}
|
|
1275
1384
|
};
|
|
1276
|
-
function getCliVersion() {
|
|
1277
|
-
return process.env.ANTHROPIC_CLI_VERSION ?? config.ccVersion;
|
|
1278
|
-
}
|
|
1279
|
-
function getUserAgent() {
|
|
1280
|
-
if (process.env.ANTHROPIC_USER_AGENT) {
|
|
1281
|
-
return process.env.ANTHROPIC_USER_AGENT;
|
|
1282
|
-
}
|
|
1283
|
-
if (process.env.ANTHROPIC_CLI_VERSION) {
|
|
1284
|
-
return `claude-cli/${getCliVersion()} (external, cli)`;
|
|
1285
|
-
}
|
|
1286
|
-
return ANTHROPIC_OAUTH_ADAPTER.cliUserAgent;
|
|
1287
|
-
}
|
|
1288
1385
|
function getRequiredBetas() {
|
|
1289
1386
|
return splitBetaFlags(process.env.ANTHROPIC_BETA_FLAGS ?? config.baseBetas.join(","));
|
|
1290
1387
|
}
|
|
@@ -1374,430 +1471,449 @@ function getModelBetas(modelId, excluded) {
|
|
|
1374
1471
|
return betas.filter((beta) => !excluded.has(beta));
|
|
1375
1472
|
}
|
|
1376
1473
|
|
|
1377
|
-
// src/
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
user: 2 + 2
|
|
1401
|
-
assistant: 4
|
|
1402
|
-
</example>
|
|
1403
|
-
|
|
1404
|
-
<example>
|
|
1405
|
-
user: what is 2+2?
|
|
1406
|
-
assistant: 4
|
|
1407
|
-
</example>
|
|
1408
|
-
|
|
1409
|
-
<example>
|
|
1410
|
-
user: is 11 a prime number?
|
|
1411
|
-
assistant: Yes
|
|
1412
|
-
</example>
|
|
1413
|
-
|
|
1414
|
-
<example>
|
|
1415
|
-
user: what command should I run to list files in the current directory?
|
|
1416
|
-
assistant: ls
|
|
1417
|
-
</example>
|
|
1418
|
-
|
|
1419
|
-
<example>
|
|
1420
|
-
user: what command should I run to watch files in the current directory?
|
|
1421
|
-
assistant: [runs ls to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files]
|
|
1422
|
-
npm run dev
|
|
1423
|
-
</example>
|
|
1424
|
-
|
|
1425
|
-
<example>
|
|
1426
|
-
user: How many golf balls fit inside a jetta?
|
|
1427
|
-
assistant: 150000
|
|
1428
|
-
</example>
|
|
1429
|
-
|
|
1430
|
-
<example>
|
|
1431
|
-
user: what files are in the directory src/?
|
|
1432
|
-
assistant: [runs ls and sees foo.c, bar.c, baz.c]
|
|
1433
|
-
user: which file contains the implementation of foo?
|
|
1434
|
-
assistant: src/foo.c
|
|
1435
|
-
</example>
|
|
1436
|
-
When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
|
|
1437
|
-
Remember that your output will be displayed on a command line interface. Your responses can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
|
|
1438
|
-
Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
|
|
1439
|
-
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
|
|
1440
|
-
Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
|
|
1441
|
-
IMPORTANT: Keep your responses short, since they will be displayed on a command line interface.
|
|
1442
|
-
|
|
1443
|
-
# Proactiveness
|
|
1444
|
-
You are allowed to be proactive, but only when the user asks you to do something. You should strive to strike a balance between:
|
|
1445
|
-
- Doing the right thing when asked, including taking actions and follow-up actions
|
|
1446
|
-
- Not surprising the user with actions you take without asking
|
|
1447
|
-
For example, if the user asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions.
|
|
1448
|
-
|
|
1449
|
-
# Professional objectivity
|
|
1450
|
-
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.
|
|
1451
|
-
|
|
1452
|
-
# Task Management
|
|
1453
|
-
You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
|
|
1454
|
-
These tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
|
|
1455
|
-
|
|
1456
|
-
It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.
|
|
1457
|
-
|
|
1458
|
-
Examples:
|
|
1459
|
-
|
|
1460
|
-
<example>
|
|
1461
|
-
user: Run the build and fix any type errors
|
|
1462
|
-
assistant: I'm going to use the TodoWrite tool to write the following items to the todo list:
|
|
1463
|
-
- Run the build
|
|
1464
|
-
- Fix any type errors
|
|
1465
|
-
|
|
1466
|
-
I'm now going to run the build using Bash.
|
|
1467
|
-
|
|
1468
|
-
Looks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.
|
|
1469
|
-
|
|
1470
|
-
marking the first todo as in_progress
|
|
1471
|
-
|
|
1472
|
-
Let me start working on the first item...
|
|
1473
|
-
|
|
1474
|
-
The first item has been fixed, let me mark the first todo as completed, and move on to the second item...
|
|
1475
|
-
..
|
|
1476
|
-
..
|
|
1477
|
-
</example>
|
|
1478
|
-
In the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.
|
|
1479
|
-
|
|
1480
|
-
<example>
|
|
1481
|
-
user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
|
|
1482
|
-
|
|
1483
|
-
assistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.
|
|
1484
|
-
Adding the following todos to the todo list:
|
|
1485
|
-
1. Research existing metrics tracking in the codebase
|
|
1486
|
-
2. Design the metrics collection system
|
|
1487
|
-
3. Implement core metrics tracking functionality
|
|
1488
|
-
4. Create export functionality for different formats
|
|
1489
|
-
|
|
1490
|
-
Let me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.
|
|
1491
|
-
|
|
1492
|
-
I'm going to search for any existing metrics or telemetry code in the project.
|
|
1493
|
-
|
|
1494
|
-
I've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...
|
|
1495
|
-
|
|
1496
|
-
[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]
|
|
1497
|
-
</example>
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
Users may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including <user-prompt-submit-hook>, as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.
|
|
1501
|
-
|
|
1502
|
-
# Doing tasks
|
|
1503
|
-
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
|
|
1504
|
-
- Use the TodoWrite tool to plan the task if required
|
|
1505
|
-
|
|
1506
|
-
- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
# Tool usage policy
|
|
1510
|
-
- When doing file search, prefer to use the Task tool in order to reduce context usage.
|
|
1511
|
-
- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.
|
|
1512
|
-
|
|
1513
|
-
- When WebFetch returns a message about a redirect to a different host, you should immediately make a new WebFetch request with the redirect URL provided in the response.
|
|
1514
|
-
- You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. When making multiple bash tool calls, you MUST send a single message with multiple tools calls to run the calls in parallel. For example, if you need to run "git status" and "git diff", send a single message with two tool calls to run the calls in parallel.
|
|
1515
|
-
- If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to launch multiple agents in parallel, send a single message with multiple Task tool calls.
|
|
1516
|
-
- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
Here is useful information about the environment you are running in:
|
|
1520
|
-
<env>
|
|
1521
|
-
Working directory: /home/thdxr/dev/projects/anomalyco/opencode/packages/opencode
|
|
1522
|
-
Is directory a git repo: Yes
|
|
1523
|
-
Platform: linux
|
|
1524
|
-
OS Version: Linux 6.12.4-arch1-1
|
|
1525
|
-
Today's date: 2025-09-30
|
|
1526
|
-
</env>
|
|
1527
|
-
You are powered by the model named Sonnet 4.5. The exact model ID is claude-sonnet-4-5-20250929.
|
|
1528
|
-
|
|
1529
|
-
Assistant knowledge cutoff is January 2025.
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Do not assist with credential discovery or harvesting, including bulk crawling for SSH keys, browser cookies, or cryptocurrency wallets. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation.
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
|
|
1536
|
-
|
|
1537
|
-
# Code References
|
|
1538
|
-
|
|
1539
|
-
When referencing specific functions or pieces of code include the pattern \`file_path:line_number\` to allow the user to easily navigate to the source code location.
|
|
1540
|
-
|
|
1541
|
-
<example>
|
|
1542
|
-
user: Where are errors from the client handled?
|
|
1543
|
-
assistant: Clients are marked as failed in the \`connectToServer\` function in src/services/process.ts:712.
|
|
1544
|
-
</example>`;
|
|
1545
|
-
|
|
1546
|
-
// src/request-transform.ts
|
|
1547
|
-
function getInjectedSystemPrompt() {
|
|
1548
|
-
return INJECTED_SYSTEM_PROMPT;
|
|
1549
|
-
}
|
|
1550
|
-
function sampleCodeUnits(text, indices) {
|
|
1551
|
-
return indices.map((i) => i < text.length ? text.charCodeAt(i).toString(16) : "30").join("");
|
|
1552
|
-
}
|
|
1553
|
-
function buildBillingHeader(firstUserMessage) {
|
|
1554
|
-
const version = ANTHROPIC_OAUTH_ADAPTER.cliVersion;
|
|
1555
|
-
const salt = ANTHROPIC_OAUTH_ADAPTER.billingSalt;
|
|
1556
|
-
if (!version || !salt) return "";
|
|
1557
|
-
const sampled = sampleCodeUnits(firstUserMessage, [4, 7, 20]);
|
|
1558
|
-
const hash = createHash("sha256").update(`${salt}${sampled}${version}`).digest("hex").slice(0, 3);
|
|
1559
|
-
return `x-anthropic-billing-header: cc_version=${version}.${hash}; cc_entrypoint=cli; cch=00000;`;
|
|
1560
|
-
}
|
|
1561
|
-
var OPENCODE_CAMEL_RE = /OpenCode/g;
|
|
1562
|
-
var OPENCODE_LOWER_RE = /(?<!\/)opencode/gi;
|
|
1563
|
-
var TOOL_PREFIX_RESPONSE_RE = /"name"\s*:\s*"mcp_([^"]+)"/g;
|
|
1564
|
-
var PARAGRAPH_REMOVAL_ANCHORS = [
|
|
1565
|
-
"github.com/anomalyco/opencode",
|
|
1566
|
-
"opencode.ai/docs"
|
|
1567
|
-
];
|
|
1568
|
-
var BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
|
|
1569
|
-
function addToolPrefix(name) {
|
|
1570
|
-
if (!ANTHROPIC_OAUTH_ADAPTER.transform.addToolPrefix) {
|
|
1571
|
-
return name;
|
|
1474
|
+
// src/claude-identity.ts
|
|
1475
|
+
import { readFileSync, readdirSync } from "fs";
|
|
1476
|
+
import { homedir } from "os";
|
|
1477
|
+
import { join } from "path";
|
|
1478
|
+
var EMPTY_IDENTITY = {
|
|
1479
|
+
deviceId: "",
|
|
1480
|
+
accountUuid: ""
|
|
1481
|
+
};
|
|
1482
|
+
var testOverrideIdentity = null;
|
|
1483
|
+
function getCandidatePaths() {
|
|
1484
|
+
const home = homedir();
|
|
1485
|
+
const paths = [
|
|
1486
|
+
join(home, ".claude.json"),
|
|
1487
|
+
join(home, ".claude", ".claude.json"),
|
|
1488
|
+
join(home, ".claude", "claude.json")
|
|
1489
|
+
];
|
|
1490
|
+
try {
|
|
1491
|
+
const backupDir = join(home, ".claude", "backups");
|
|
1492
|
+
const backups = readdirSync(backupDir).filter((file) => file.startsWith(".claude.json.backup.")).sort().reverse();
|
|
1493
|
+
for (const backup of backups) {
|
|
1494
|
+
paths.push(join(backupDir, backup));
|
|
1495
|
+
}
|
|
1496
|
+
} catch {
|
|
1572
1497
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1498
|
+
return paths;
|
|
1499
|
+
}
|
|
1500
|
+
function parseIdentityFile(path) {
|
|
1501
|
+
try {
|
|
1502
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
1503
|
+
if (!data.userID) {
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
return {
|
|
1507
|
+
deviceId: data.userID,
|
|
1508
|
+
accountUuid: data.oauthAccount?.accountUuid ?? data.accountUuid ?? ""
|
|
1509
|
+
};
|
|
1510
|
+
} catch {
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
function loadClaudeIdentity() {
|
|
1515
|
+
if (testOverrideIdentity) {
|
|
1516
|
+
return testOverrideIdentity;
|
|
1517
|
+
}
|
|
1518
|
+
try {
|
|
1519
|
+
for (const path of getCandidatePaths()) {
|
|
1520
|
+
const identity = parseIdentityFile(path);
|
|
1521
|
+
if (identity) {
|
|
1522
|
+
return identity;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
} catch {
|
|
1526
|
+
}
|
|
1527
|
+
return EMPTY_IDENTITY;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// src/upstream-request.ts
|
|
1531
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
1532
|
+
var BILLING_SEED = "59cf53e54c78";
|
|
1533
|
+
var DEFAULT_CC_VERSION = "2.1.100";
|
|
1534
|
+
var SESSION_IDLE_ROTATE_MS = 15 * 60 * 1e3;
|
|
1535
|
+
var MAX_TOOL_RESULT_TEXT_LENGTH = 30 * 1024;
|
|
1536
|
+
var TRUNCATION_SUFFIX = "[...truncated]";
|
|
1537
|
+
var DEFAULT_CONTEXT_MANAGEMENT = {};
|
|
1538
|
+
var DEFAULT_OUTPUT_CONFIG = {};
|
|
1539
|
+
var ORCHESTRATION_TAG_NAMES = [
|
|
1540
|
+
"system-reminder",
|
|
1541
|
+
"env",
|
|
1542
|
+
"system_information",
|
|
1543
|
+
"current_working_directory",
|
|
1544
|
+
"operating_system",
|
|
1545
|
+
"default_shell",
|
|
1546
|
+
"home_directory",
|
|
1547
|
+
"task_metadata",
|
|
1548
|
+
"directories",
|
|
1549
|
+
"thinking",
|
|
1550
|
+
"agent_persona",
|
|
1551
|
+
"agent_context",
|
|
1552
|
+
"tool_context",
|
|
1553
|
+
"persona",
|
|
1554
|
+
"tool_call"
|
|
1555
|
+
];
|
|
1556
|
+
var ORCHESTRATION_PATTERNS = ORCHESTRATION_TAG_NAMES.flatMap((tag) => [
|
|
1557
|
+
new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi"),
|
|
1558
|
+
new RegExp(`<${tag}\\b[^>]*/>`, "gi")
|
|
1559
|
+
]);
|
|
1560
|
+
var FRAMEWORK_PATTERNS = [
|
|
1561
|
+
/\b(roo[- ]?cline|roo[- ]?code|big[- ]?agi|claude[- ]?bridge|amazon\s+q)\b/gi,
|
|
1562
|
+
/\b(openclaw|hermes|aider|cursor|windsurf|cline|continue|copilot|cody)\b/gi,
|
|
1563
|
+
/\b(zed|plandex|tabby|opencode|daytona)\b/gi,
|
|
1564
|
+
/\b(librechat|typingmind)\b/gi,
|
|
1565
|
+
/\b(openai|gpt-4|gpt-3\.5)\b/gi,
|
|
1566
|
+
/powered by [a-z]+/gi,
|
|
1567
|
+
/\bgateway\b/gi,
|
|
1568
|
+
/\bsessions_[a-z_]+\b/gi
|
|
1569
|
+
];
|
|
1570
|
+
var upstreamRequestTestOverrides = {};
|
|
1571
|
+
var sessionId = randomUUID2();
|
|
1572
|
+
var sessionLastUsed = 0;
|
|
1573
|
+
function now() {
|
|
1574
|
+
return upstreamRequestTestOverrides.now?.() ?? Date.now();
|
|
1575
|
+
}
|
|
1576
|
+
function createSessionId() {
|
|
1577
|
+
return upstreamRequestTestOverrides.createSessionId?.() ?? randomUUID2();
|
|
1578
|
+
}
|
|
1579
|
+
function getActiveSessionId() {
|
|
1580
|
+
const currentTime = now();
|
|
1581
|
+
if (sessionLastUsed === 0 || currentTime - sessionLastUsed > SESSION_IDLE_ROTATE_MS) {
|
|
1582
|
+
sessionId = createSessionId();
|
|
1575
1583
|
}
|
|
1576
|
-
|
|
1584
|
+
sessionLastUsed = currentTime;
|
|
1585
|
+
return sessionId;
|
|
1586
|
+
}
|
|
1587
|
+
function getUpstreamSessionId() {
|
|
1588
|
+
return getActiveSessionId();
|
|
1577
1589
|
}
|
|
1578
1590
|
function isRecord(value) {
|
|
1579
1591
|
return typeof value === "object" && value !== null;
|
|
1580
1592
|
}
|
|
1581
|
-
function
|
|
1582
|
-
return
|
|
1593
|
+
function cloneBody(value) {
|
|
1594
|
+
return structuredClone(value);
|
|
1583
1595
|
}
|
|
1584
|
-
function
|
|
1585
|
-
|
|
1586
|
-
|
|
1596
|
+
function sanitizeContent(text) {
|
|
1597
|
+
let result = text;
|
|
1598
|
+
for (const pattern of ORCHESTRATION_PATTERNS) {
|
|
1599
|
+
pattern.lastIndex = 0;
|
|
1600
|
+
result = result.replace(pattern, "");
|
|
1601
|
+
}
|
|
1602
|
+
return result.replace(/\n{3,}/g, "\n\n").trim();
|
|
1587
1603
|
}
|
|
1588
|
-
function
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1604
|
+
function sanitizeAndScrubText(text) {
|
|
1605
|
+
return scrubFrameworkIdentifiers(sanitizeContent(text)).replace(/\n{3,}/g, "\n\n").trim();
|
|
1606
|
+
}
|
|
1607
|
+
function stripCacheControl(value) {
|
|
1608
|
+
if (Array.isArray(value)) {
|
|
1609
|
+
for (const item of value) {
|
|
1610
|
+
stripCacheControl(item);
|
|
1611
|
+
}
|
|
1612
|
+
return;
|
|
1592
1613
|
}
|
|
1593
|
-
if (!
|
|
1594
|
-
return
|
|
1614
|
+
if (!isRecord(value)) {
|
|
1615
|
+
return;
|
|
1595
1616
|
}
|
|
1596
|
-
|
|
1597
|
-
for (const
|
|
1598
|
-
|
|
1599
|
-
const text = entry.trim();
|
|
1600
|
-
if (text) {
|
|
1601
|
-
normalized.push({ type: "text", text });
|
|
1602
|
-
}
|
|
1603
|
-
continue;
|
|
1604
|
-
}
|
|
1605
|
-
if (!isRecord(entry)) {
|
|
1606
|
-
continue;
|
|
1607
|
-
}
|
|
1608
|
-
const rawText = typeof entry.text === "string" ? entry.text.trim() : "";
|
|
1609
|
-
if (!rawText) {
|
|
1610
|
-
continue;
|
|
1611
|
-
}
|
|
1612
|
-
normalized.push({
|
|
1613
|
-
...entry,
|
|
1614
|
-
type: "text",
|
|
1615
|
-
text: rawText
|
|
1616
|
-
});
|
|
1617
|
+
delete value.cache_control;
|
|
1618
|
+
for (const nested of Object.values(value)) {
|
|
1619
|
+
stripCacheControl(nested);
|
|
1617
1620
|
}
|
|
1618
|
-
return normalized;
|
|
1619
1621
|
}
|
|
1620
|
-
function
|
|
1621
|
-
if (
|
|
1622
|
-
|
|
1622
|
+
function sanitizeMessageBlock(block) {
|
|
1623
|
+
if (typeof block.text === "string") {
|
|
1624
|
+
block.text = sanitizeAndScrubText(block.text);
|
|
1623
1625
|
}
|
|
1624
|
-
if (
|
|
1625
|
-
return
|
|
1626
|
-
|
|
1627
|
-
${content}` : prefix;
|
|
1626
|
+
if (block.type !== "tool_result") {
|
|
1627
|
+
return;
|
|
1628
1628
|
}
|
|
1629
|
-
if (
|
|
1630
|
-
|
|
1629
|
+
if (typeof block.content === "string") {
|
|
1630
|
+
block.content = truncateToolResultText(sanitizeAndScrubText(block.content));
|
|
1631
|
+
return;
|
|
1631
1632
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
);
|
|
1635
|
-
if (firstTextIndex === -1) {
|
|
1636
|
-
return [{ type: "text", text: prefix }, ...content];
|
|
1633
|
+
if (!Array.isArray(block.content)) {
|
|
1634
|
+
return;
|
|
1637
1635
|
}
|
|
1638
|
-
|
|
1639
|
-
if (
|
|
1640
|
-
|
|
1636
|
+
for (const item of block.content) {
|
|
1637
|
+
if (isRecord(item) && typeof item.text === "string") {
|
|
1638
|
+
item.text = truncateToolResultText(sanitizeAndScrubText(item.text));
|
|
1641
1639
|
}
|
|
1642
|
-
return {
|
|
1643
|
-
...block,
|
|
1644
|
-
text: block.text ? `${prefix}
|
|
1645
|
-
|
|
1646
|
-
${block.text}` : prefix
|
|
1647
|
-
};
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
function relocateSystemTextToFirstUser(parsed, systemEntries) {
|
|
1651
|
-
if (!Array.isArray(parsed.messages) || parsed.messages.length === 0) {
|
|
1652
|
-
return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
|
|
1653
1640
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
for (const
|
|
1657
|
-
if (
|
|
1658
|
-
preservedEntries.push(entry);
|
|
1659
|
-
continue;
|
|
1660
|
-
}
|
|
1661
|
-
const sanitizedText = sanitizeSystemText(entry.text);
|
|
1662
|
-
if (!sanitizedText) {
|
|
1641
|
+
}
|
|
1642
|
+
function stripAssistantThinkingBlocks(messages) {
|
|
1643
|
+
for (const message of messages) {
|
|
1644
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) {
|
|
1663
1645
|
continue;
|
|
1664
1646
|
}
|
|
1665
|
-
|
|
1647
|
+
message.content = message.content.filter((block) => block.type !== "thinking");
|
|
1666
1648
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1649
|
+
}
|
|
1650
|
+
function hasMeaningfulContent(content) {
|
|
1651
|
+
if (typeof content === "string") {
|
|
1652
|
+
return content.trim().length > 0;
|
|
1669
1653
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
const userMessageIndex = nextMessages.findIndex((message) => message.role === "user");
|
|
1673
|
-
if (userMessageIndex === -1) {
|
|
1674
|
-
return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
|
|
1675
|
-
}
|
|
1676
|
-
const userMessage = nextMessages[userMessageIndex];
|
|
1677
|
-
if (!userMessage) {
|
|
1678
|
-
return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
|
|
1654
|
+
if (!Array.isArray(content)) {
|
|
1655
|
+
return false;
|
|
1679
1656
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1657
|
+
return content.some((block) => {
|
|
1658
|
+
if (!isRecord(block)) {
|
|
1659
|
+
return false;
|
|
1660
|
+
}
|
|
1661
|
+
if (typeof block.text === "string" && block.text.trim().length > 0) {
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
if (typeof block.content === "string" && block.content.trim().length > 0) {
|
|
1665
|
+
return true;
|
|
1666
|
+
}
|
|
1667
|
+
return false;
|
|
1668
|
+
});
|
|
1686
1669
|
}
|
|
1687
|
-
function
|
|
1688
|
-
|
|
1689
|
-
|
|
1670
|
+
function trimTrailingEmptyTurns(messages) {
|
|
1671
|
+
while (messages.length > 0) {
|
|
1672
|
+
const lastMessage = messages[messages.length - 1];
|
|
1673
|
+
if (!lastMessage || hasMeaningfulContent(lastMessage.content)) {
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
messages.pop();
|
|
1690
1677
|
}
|
|
1691
|
-
return line.replace(TOOL_PREFIX_RESPONSE_RE, '"name": "$1"');
|
|
1692
1678
|
}
|
|
1693
|
-
function
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
return { output: "", remaining };
|
|
1679
|
+
function normalizeSystemTexts(system) {
|
|
1680
|
+
if (typeof system === "string") {
|
|
1681
|
+
const next = sanitizeAndScrubText(system);
|
|
1682
|
+
return next ? [next] : [];
|
|
1698
1683
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
return { output, remaining };
|
|
1702
|
-
}
|
|
1703
|
-
function buildRequestHeaders(input, init, accessToken, modelId = "unknown", excludedBetas2) {
|
|
1704
|
-
const headers = new Headers();
|
|
1705
|
-
if (input instanceof Request) {
|
|
1706
|
-
input.headers.forEach((value, key) => {
|
|
1707
|
-
headers.set(key, value);
|
|
1708
|
-
});
|
|
1684
|
+
if (!Array.isArray(system)) {
|
|
1685
|
+
return [];
|
|
1709
1686
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
for (const [key, value] of init.headers) {
|
|
1717
|
-
if (value !== void 0) headers.set(key, String(value));
|
|
1687
|
+
const texts = [];
|
|
1688
|
+
for (const entry of system) {
|
|
1689
|
+
if (typeof entry === "string") {
|
|
1690
|
+
const next = sanitizeAndScrubText(entry);
|
|
1691
|
+
if (next) {
|
|
1692
|
+
texts.push(next);
|
|
1718
1693
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
if (isRecord(entry) && typeof entry.text === "string") {
|
|
1697
|
+
const next = sanitizeAndScrubText(entry.text);
|
|
1698
|
+
if (next) {
|
|
1699
|
+
texts.push(next);
|
|
1722
1700
|
}
|
|
1723
1701
|
}
|
|
1724
1702
|
}
|
|
1725
|
-
|
|
1726
|
-
const modelBetas = getModelBetas(modelId, excludedBetas2);
|
|
1727
|
-
const mergedBetas = [.../* @__PURE__ */ new Set([
|
|
1728
|
-
...modelBetas,
|
|
1729
|
-
...incomingBetas
|
|
1730
|
-
])].join(",");
|
|
1731
|
-
headers.set("authorization", `Bearer ${accessToken}`);
|
|
1732
|
-
headers.set("anthropic-beta", mergedBetas);
|
|
1733
|
-
headers.set("user-agent", getUserAgent());
|
|
1734
|
-
headers.set("anthropic-dangerous-direct-browser-access", "true");
|
|
1735
|
-
headers.set("x-app", "cli");
|
|
1736
|
-
headers.delete("x-api-key");
|
|
1737
|
-
return headers;
|
|
1703
|
+
return texts;
|
|
1738
1704
|
}
|
|
1739
|
-
function
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
if (
|
|
1748
|
-
|
|
1749
|
-
...tool2,
|
|
1750
|
-
name: addToolPrefix(tool2.name)
|
|
1751
|
-
}));
|
|
1752
|
-
}
|
|
1753
|
-
if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
1754
|
-
parsed.messages = parsed.messages.map((message) => {
|
|
1755
|
-
if (message.content && Array.isArray(message.content)) {
|
|
1756
|
-
message.content = message.content.map((contentBlock) => {
|
|
1757
|
-
if (contentBlock.type === "tool_use" && contentBlock.name) {
|
|
1758
|
-
return { ...contentBlock, name: addToolPrefix(contentBlock.name) };
|
|
1759
|
-
}
|
|
1760
|
-
return contentBlock;
|
|
1761
|
-
});
|
|
1762
|
-
}
|
|
1763
|
-
return message;
|
|
1764
|
-
});
|
|
1705
|
+
function filterInjectedSystemTexts(systemTexts, template, billingHeader) {
|
|
1706
|
+
return systemTexts.filter((entry) => entry !== billingHeader && entry !== template.agent_identity && entry !== template.system_prompt && !entry.startsWith("x-anthropic-billing-header:"));
|
|
1707
|
+
}
|
|
1708
|
+
function extractFirstUserMessage(messages) {
|
|
1709
|
+
if (!Array.isArray(messages)) {
|
|
1710
|
+
return "";
|
|
1711
|
+
}
|
|
1712
|
+
for (const message of messages) {
|
|
1713
|
+
if (message.role !== "user") {
|
|
1714
|
+
continue;
|
|
1765
1715
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1716
|
+
if (typeof message.content === "string") {
|
|
1717
|
+
return message.content;
|
|
1718
|
+
}
|
|
1719
|
+
if (!Array.isArray(message.content)) {
|
|
1720
|
+
return "";
|
|
1721
|
+
}
|
|
1722
|
+
const text = message.content.filter((block) => typeof block.text === "string").map((block) => block.text).join("\n\n").trim();
|
|
1723
|
+
return text;
|
|
1724
|
+
}
|
|
1725
|
+
return "";
|
|
1726
|
+
}
|
|
1727
|
+
function hasCompleteToolSchemas(tools) {
|
|
1728
|
+
return tools.length > 0 && tools.every((tool2) => typeof tool2 === "object" && tool2 !== null && "input_schema" in tool2);
|
|
1729
|
+
}
|
|
1730
|
+
function getCcVersion(template) {
|
|
1731
|
+
return template.cc_version ?? DEFAULT_CC_VERSION;
|
|
1732
|
+
}
|
|
1733
|
+
function buildBillingHeader(firstUserMessage, template) {
|
|
1734
|
+
const version = getCcVersion(template);
|
|
1735
|
+
const buildTag = computeBuildTag(firstUserMessage, version);
|
|
1736
|
+
return `x-anthropic-billing-header: cc_version=${version}.${buildTag}; cc_entrypoint=cli; cch=00000;`;
|
|
1737
|
+
}
|
|
1738
|
+
function truncateToolResultText(text) {
|
|
1739
|
+
if (text.length <= MAX_TOOL_RESULT_TEXT_LENGTH) {
|
|
1740
|
+
return text;
|
|
1769
1741
|
}
|
|
1742
|
+
return `${text.slice(0, MAX_TOOL_RESULT_TEXT_LENGTH)}${TRUNCATION_SUFFIX}`;
|
|
1770
1743
|
}
|
|
1771
|
-
function
|
|
1772
|
-
if (
|
|
1773
|
-
return
|
|
1744
|
+
function getReverseName(name, reverseLookup) {
|
|
1745
|
+
if (!reverseLookup) {
|
|
1746
|
+
return name;
|
|
1747
|
+
}
|
|
1748
|
+
if (reverseLookup instanceof Map) {
|
|
1749
|
+
return reverseLookup.get(name) ?? name;
|
|
1750
|
+
}
|
|
1751
|
+
return typeof reverseLookup[name] === "string" ? String(reverseLookup[name]) : name;
|
|
1752
|
+
}
|
|
1753
|
+
function reverseMapToolUseNames(value, reverseLookup) {
|
|
1754
|
+
if (Array.isArray(value)) {
|
|
1755
|
+
return value.map((item) => reverseMapToolUseNames(item, reverseLookup));
|
|
1756
|
+
}
|
|
1757
|
+
if (!isRecord(value)) {
|
|
1758
|
+
return value;
|
|
1759
|
+
}
|
|
1760
|
+
const cloned = {};
|
|
1761
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
1762
|
+
cloned[key] = reverseMapToolUseNames(nested, reverseLookup);
|
|
1763
|
+
}
|
|
1764
|
+
if (cloned.type === "tool_use" && typeof cloned.name === "string") {
|
|
1765
|
+
cloned.name = getReverseName(cloned.name, reverseLookup);
|
|
1766
|
+
}
|
|
1767
|
+
return cloned;
|
|
1768
|
+
}
|
|
1769
|
+
function remapSseLine(line, reverseLookup) {
|
|
1770
|
+
if (!line.startsWith("data:")) {
|
|
1771
|
+
return line;
|
|
1772
|
+
}
|
|
1773
|
+
const payload = line.slice(5).trimStart();
|
|
1774
|
+
if (payload.length === 0 || payload === "[DONE]") {
|
|
1775
|
+
return line;
|
|
1774
1776
|
}
|
|
1775
1777
|
try {
|
|
1776
|
-
const parsed = JSON.parse(
|
|
1777
|
-
return parsed
|
|
1778
|
+
const parsed = JSON.parse(payload);
|
|
1779
|
+
return `data: ${JSON.stringify(reverseMapResponse(parsed, reverseLookup))}`;
|
|
1778
1780
|
} catch {
|
|
1779
|
-
return
|
|
1781
|
+
return line;
|
|
1780
1782
|
}
|
|
1781
1783
|
}
|
|
1782
|
-
function
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1784
|
+
function sanitizeMessages(body) {
|
|
1785
|
+
const messages = body.messages;
|
|
1786
|
+
if (!Array.isArray(messages)) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
for (const message of messages) {
|
|
1790
|
+
if (!isRecord(message)) {
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (typeof message.content === "string") {
|
|
1794
|
+
message.content = sanitizeContent(message.content);
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
if (!Array.isArray(message.content)) {
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
for (const block of message.content) {
|
|
1801
|
+
if (isRecord(block) && typeof block.text === "string") {
|
|
1802
|
+
block.text = sanitizeContent(block.text);
|
|
1803
|
+
}
|
|
1789
1804
|
}
|
|
1790
|
-
} catch {
|
|
1791
|
-
return input;
|
|
1792
1805
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1806
|
+
}
|
|
1807
|
+
function scrubFrameworkIdentifiers(text) {
|
|
1808
|
+
let result = text;
|
|
1809
|
+
for (const pattern of FRAMEWORK_PATTERNS) {
|
|
1810
|
+
pattern.lastIndex = 0;
|
|
1811
|
+
result = result.replace(pattern, (match, ...args) => {
|
|
1812
|
+
const offset = args.at(-2);
|
|
1813
|
+
const source = args.at(-1);
|
|
1814
|
+
if (typeof offset !== "number" || typeof source !== "string") {
|
|
1815
|
+
return match;
|
|
1816
|
+
}
|
|
1817
|
+
const before = offset > 0 ? source[offset - 1] ?? "" : "";
|
|
1818
|
+
const after = offset + match.length < source.length ? source[offset + match.length] ?? "" : "";
|
|
1819
|
+
if (before === "." || before === "/" || before === "\\" || before === "-" || before === "_") {
|
|
1820
|
+
return match;
|
|
1821
|
+
}
|
|
1822
|
+
if (after === "/" || after === "\\") {
|
|
1823
|
+
return match;
|
|
1824
|
+
}
|
|
1825
|
+
return "";
|
|
1826
|
+
});
|
|
1796
1827
|
}
|
|
1797
|
-
return
|
|
1828
|
+
return result;
|
|
1829
|
+
}
|
|
1830
|
+
function computeBuildTag(userMessage, version) {
|
|
1831
|
+
const chars = [4, 7, 20].map((index) => userMessage[index] ?? "0").join("");
|
|
1832
|
+
return createHash2("sha256").update(`${BILLING_SEED}${chars}${version}`).digest("hex").slice(0, 3);
|
|
1833
|
+
}
|
|
1834
|
+
function buildUpstreamRequest(inputBody, identity, template, options) {
|
|
1835
|
+
const body = cloneBody(inputBody);
|
|
1836
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
1837
|
+
const systemTexts = normalizeSystemTexts(body.system);
|
|
1838
|
+
stripCacheControl(body);
|
|
1839
|
+
sanitizeMessages(body);
|
|
1840
|
+
stripAssistantThinkingBlocks(messages);
|
|
1841
|
+
for (const message of messages) {
|
|
1842
|
+
if (typeof message.content === "string") {
|
|
1843
|
+
message.content = sanitizeAndScrubText(message.content);
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
if (!Array.isArray(message.content)) {
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
for (const block of message.content) {
|
|
1850
|
+
sanitizeMessageBlock(block);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
trimTrailingEmptyTurns(messages);
|
|
1854
|
+
const firstUserMessage = extractFirstUserMessage(messages);
|
|
1855
|
+
const billingHeader = buildBillingHeader(firstUserMessage, template);
|
|
1856
|
+
const mergedSystemPrompt = [
|
|
1857
|
+
template.system_prompt,
|
|
1858
|
+
...filterInjectedSystemTexts(systemTexts, template, billingHeader)
|
|
1859
|
+
].map((entry) => sanitizeAndScrubText(entry)).filter(Boolean).join("\n\n");
|
|
1860
|
+
const activeSessionId = options?.sessionId ?? getActiveSessionId();
|
|
1861
|
+
body.messages = messages;
|
|
1862
|
+
const incomingTools = Array.isArray(body.tools) ? body.tools : [];
|
|
1863
|
+
body.tools = hasCompleteToolSchemas(template.tools) ? template.tools.map((tool2) => ({ ...tool2 })) : incomingTools;
|
|
1864
|
+
body.system = [
|
|
1865
|
+
{
|
|
1866
|
+
type: "text",
|
|
1867
|
+
text: billingHeader
|
|
1868
|
+
},
|
|
1869
|
+
{
|
|
1870
|
+
type: "text",
|
|
1871
|
+
text: template.agent_identity,
|
|
1872
|
+
cache_control: { type: "ephemeral" }
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
type: "text",
|
|
1876
|
+
text: mergedSystemPrompt,
|
|
1877
|
+
cache_control: { type: "ephemeral" }
|
|
1878
|
+
}
|
|
1879
|
+
];
|
|
1880
|
+
body.metadata = {
|
|
1881
|
+
...isRecord(body.metadata) ? body.metadata : {},
|
|
1882
|
+
user_id: JSON.stringify({
|
|
1883
|
+
device_id: identity.deviceId,
|
|
1884
|
+
account_uuid: identity.accountUuid,
|
|
1885
|
+
session_id: activeSessionId
|
|
1886
|
+
})
|
|
1887
|
+
};
|
|
1888
|
+
body.thinking = { type: "adaptive" };
|
|
1889
|
+
body.context_management = DEFAULT_CONTEXT_MANAGEMENT;
|
|
1890
|
+
body.output_config = DEFAULT_OUTPUT_CONFIG;
|
|
1891
|
+
body.max_tokens = 64e3;
|
|
1892
|
+
return orderBodyForOutbound(body, template.body_field_order);
|
|
1893
|
+
}
|
|
1894
|
+
function orderBodyForOutbound(body, overrideOrder) {
|
|
1895
|
+
if (!Array.isArray(overrideOrder) || overrideOrder.length === 0) return body;
|
|
1896
|
+
const ordered = {};
|
|
1897
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1898
|
+
for (const name of overrideOrder) {
|
|
1899
|
+
if (seen.has(name)) continue;
|
|
1900
|
+
if (Object.prototype.hasOwnProperty.call(body, name)) {
|
|
1901
|
+
ordered[name] = body[name];
|
|
1902
|
+
seen.add(name);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
for (const k of Object.keys(body)) {
|
|
1906
|
+
if (!seen.has(k)) ordered[k] = body[k];
|
|
1907
|
+
}
|
|
1908
|
+
return ordered;
|
|
1798
1909
|
}
|
|
1799
|
-
function
|
|
1800
|
-
|
|
1910
|
+
function reverseMapResponse(response, reverseLookup) {
|
|
1911
|
+
return reverseMapToolUseNames(response, reverseLookup);
|
|
1912
|
+
}
|
|
1913
|
+
function createStreamingReverseMapper(response, reverseLookup) {
|
|
1914
|
+
if (!response.body) {
|
|
1915
|
+
return response;
|
|
1916
|
+
}
|
|
1801
1917
|
const reader = response.body.getReader();
|
|
1802
1918
|
const decoder = new TextDecoder();
|
|
1803
1919
|
const encoder = new TextEncoder();
|
|
@@ -1810,24 +1926,26 @@ function createResponseStreamTransform(response) {
|
|
|
1810
1926
|
if (done) {
|
|
1811
1927
|
buffer += decoder.decode();
|
|
1812
1928
|
if (buffer) {
|
|
1813
|
-
|
|
1929
|
+
const lines2 = buffer.split("\n").map((line) => remapSseLine(line, reverseLookup));
|
|
1930
|
+
controller.enqueue(encoder.encode(lines2.join("\n")));
|
|
1814
1931
|
buffer = "";
|
|
1815
1932
|
}
|
|
1816
1933
|
controller.close();
|
|
1817
1934
|
return;
|
|
1818
1935
|
}
|
|
1819
1936
|
buffer += decoder.decode(value, { stream: true });
|
|
1820
|
-
const
|
|
1821
|
-
buffer =
|
|
1822
|
-
if (
|
|
1823
|
-
|
|
1937
|
+
const lines = buffer.split("\n");
|
|
1938
|
+
buffer = lines.pop() ?? "";
|
|
1939
|
+
if (lines.length > 0) {
|
|
1940
|
+
const remapped = `${lines.map((line) => remapSseLine(line, reverseLookup)).join("\n")}
|
|
1941
|
+
`;
|
|
1942
|
+
controller.enqueue(encoder.encode(remapped));
|
|
1824
1943
|
return;
|
|
1825
1944
|
}
|
|
1826
1945
|
}
|
|
1827
1946
|
} catch (error) {
|
|
1828
1947
|
try {
|
|
1829
|
-
reader.cancel()
|
|
1830
|
-
});
|
|
1948
|
+
await reader.cancel();
|
|
1831
1949
|
} catch {
|
|
1832
1950
|
}
|
|
1833
1951
|
controller.error(error);
|
|
@@ -1844,28 +1962,353 @@ function createResponseStreamTransform(response) {
|
|
|
1844
1962
|
});
|
|
1845
1963
|
}
|
|
1846
1964
|
|
|
1847
|
-
// src/
|
|
1848
|
-
import {
|
|
1849
|
-
var
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1965
|
+
// src/upstream-headers.ts
|
|
1966
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1967
|
+
var UPSTREAM_TIMEOUT_MS = 3e5;
|
|
1968
|
+
var STAINLESS_PACKAGE_VERSION = "0.81.0";
|
|
1969
|
+
var BILLABLE_BETA_PREFIXES = ["extended-cache-ttl-"];
|
|
1970
|
+
function getOsName() {
|
|
1971
|
+
const platform = process.platform;
|
|
1972
|
+
if (platform === "win32") return "Windows";
|
|
1973
|
+
if (platform === "darwin") return "MacOS";
|
|
1974
|
+
return "Linux";
|
|
1975
|
+
}
|
|
1976
|
+
function getStaticHeaders() {
|
|
1977
|
+
const profile = loadCCDerivedRequestProfile();
|
|
1978
|
+
const headers = {
|
|
1979
|
+
"accept": "application/json",
|
|
1980
|
+
"content-type": "application/json",
|
|
1981
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
1982
|
+
"user-agent": profile.userAgent,
|
|
1983
|
+
"x-app": profile.xApp,
|
|
1984
|
+
"x-stainless-arch": process.arch,
|
|
1985
|
+
"x-stainless-lang": "js",
|
|
1986
|
+
"x-stainless-os": getOsName(),
|
|
1987
|
+
"x-stainless-package-version": STAINLESS_PACKAGE_VERSION,
|
|
1988
|
+
"x-stainless-retry-count": "0",
|
|
1989
|
+
"x-stainless-runtime": "node",
|
|
1990
|
+
"x-stainless-runtime-version": process.version
|
|
1991
|
+
};
|
|
1992
|
+
const { template } = profile;
|
|
1993
|
+
if (template.header_values) {
|
|
1994
|
+
for (const [key, value] of Object.entries(template.header_values)) {
|
|
1995
|
+
headers[key] = value;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return headers;
|
|
1999
|
+
}
|
|
2000
|
+
function getPerRequestHeaders(sessionId2) {
|
|
2001
|
+
return {
|
|
2002
|
+
"x-claude-code-session-id": sessionId2,
|
|
2003
|
+
"x-client-request-id": randomUUID3(),
|
|
2004
|
+
"anthropic-version": getAnthropicVersion(),
|
|
2005
|
+
"x-stainless-timeout": String(UPSTREAM_TIMEOUT_MS / 1e3)
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function getAnthropicVersion() {
|
|
2009
|
+
return loadCCDerivedRequestProfile().anthropicVersion;
|
|
2010
|
+
}
|
|
2011
|
+
function getBetaHeader() {
|
|
2012
|
+
return loadCCDerivedRequestProfile().betaHeader;
|
|
2013
|
+
}
|
|
2014
|
+
function orderHeadersForOutbound(headers, overrideHeaderOrder) {
|
|
2015
|
+
const { template } = loadCCDerivedRequestProfile();
|
|
2016
|
+
const order = overrideHeaderOrder ?? template.header_order;
|
|
2017
|
+
if (!Array.isArray(order) || order.length === 0) {
|
|
2018
|
+
return headers;
|
|
2019
|
+
}
|
|
2020
|
+
const lowerToValue = /* @__PURE__ */ new Map();
|
|
2021
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2022
|
+
lowerToValue.set(key.toLowerCase(), value);
|
|
2023
|
+
}
|
|
2024
|
+
const ordered = [];
|
|
2025
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2026
|
+
for (const name of order) {
|
|
2027
|
+
const key = name.toLowerCase();
|
|
2028
|
+
if (seen.has(key)) {
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
2031
|
+
const value = lowerToValue.get(key);
|
|
2032
|
+
if (value !== void 0) {
|
|
2033
|
+
ordered.push([name, value]);
|
|
2034
|
+
seen.add(key);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2038
|
+
if (!seen.has(key.toLowerCase())) {
|
|
2039
|
+
ordered.push([key, value]);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return ordered;
|
|
2043
|
+
}
|
|
2044
|
+
function filterBillableBetas(betas) {
|
|
2045
|
+
return betas.split(",").map((beta) => beta.trim()).filter(
|
|
2046
|
+
(beta) => beta.length > 0 && !BILLABLE_BETA_PREFIXES.some((prefix) => beta.startsWith(prefix))
|
|
2047
|
+
).join(",");
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// src/request-transform.ts
|
|
2051
|
+
function extractModelIdFromBody(body) {
|
|
2052
|
+
if (typeof body !== "string") {
|
|
2053
|
+
return "unknown";
|
|
2054
|
+
}
|
|
2055
|
+
try {
|
|
2056
|
+
const parsed = JSON.parse(body);
|
|
2057
|
+
return typeof parsed.model === "string" ? parsed.model : "unknown";
|
|
2058
|
+
} catch {
|
|
2059
|
+
return "unknown";
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
function transformRequestUrl(input) {
|
|
2063
|
+
let url = null;
|
|
2064
|
+
try {
|
|
2065
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
2066
|
+
url = new URL(input.toString());
|
|
2067
|
+
} else if (input instanceof Request) {
|
|
2068
|
+
url = new URL(input.url);
|
|
2069
|
+
}
|
|
2070
|
+
} catch {
|
|
2071
|
+
return input;
|
|
2072
|
+
}
|
|
2073
|
+
if (ANTHROPIC_OAUTH_ADAPTER.transform.enableMessagesBetaQuery && url && url.pathname === "/v1/messages" && !url.searchParams.has("beta")) {
|
|
2074
|
+
url.searchParams.set("beta", "true");
|
|
2075
|
+
return input instanceof Request ? new Request(url.toString(), input) : url;
|
|
2076
|
+
}
|
|
2077
|
+
return input;
|
|
2078
|
+
}
|
|
2079
|
+
function extractToolNamesFromRequestBody(body) {
|
|
2080
|
+
if (!body) {
|
|
2081
|
+
return [];
|
|
2082
|
+
}
|
|
2083
|
+
try {
|
|
2084
|
+
const parsed = JSON.parse(body);
|
|
2085
|
+
if (!Array.isArray(parsed.tools)) {
|
|
2086
|
+
return [];
|
|
2087
|
+
}
|
|
2088
|
+
return parsed.tools.map((tool2) => typeof tool2.name === "string" ? tool2.name : null).filter((toolName) => Boolean(toolName));
|
|
2089
|
+
} catch {
|
|
2090
|
+
return [];
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// src/tool-observation.ts
|
|
2095
|
+
import { promises as fs } from "fs";
|
|
2096
|
+
import { dirname, join as join2 } from "path";
|
|
2097
|
+
var OBSERVED_TOOL_FILE = "anthropic-observed-tools.json";
|
|
2098
|
+
var FILE_MODE = 384;
|
|
2099
|
+
function getObservedToolPath() {
|
|
2100
|
+
return join2(getConfigDir(), OBSERVED_TOOL_FILE);
|
|
2101
|
+
}
|
|
2102
|
+
async function loadObservedToolInventory() {
|
|
2103
|
+
try {
|
|
2104
|
+
const content = await fs.readFile(getObservedToolPath(), "utf8");
|
|
2105
|
+
const parsed = JSON.parse(content);
|
|
2106
|
+
return typeof parsed === "object" && parsed && typeof parsed.observedTools === "object" ? parsed : { observedTools: {} };
|
|
2107
|
+
} catch {
|
|
2108
|
+
return { observedTools: {} };
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
async function saveObservedToolInventory(inventory) {
|
|
2112
|
+
const targetPath = getObservedToolPath();
|
|
2113
|
+
await fs.mkdir(dirname(targetPath), { recursive: true });
|
|
2114
|
+
await fs.writeFile(targetPath, `${JSON.stringify(inventory, null, 2)}
|
|
2115
|
+
`, { mode: FILE_MODE });
|
|
2116
|
+
await fs.chmod(targetPath, FILE_MODE).catch(() => {
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
async function recordObservedToolNames(toolNames) {
|
|
2120
|
+
if (toolNames.length === 0) {
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
const inventory = await loadObservedToolInventory();
|
|
2124
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
2125
|
+
for (const toolName of toolNames) {
|
|
2126
|
+
const entry = inventory.observedTools[toolName];
|
|
2127
|
+
if (!entry) {
|
|
2128
|
+
inventory.observedTools[toolName] = {
|
|
2129
|
+
count: 1,
|
|
2130
|
+
firstSeenAt: now2,
|
|
2131
|
+
lastSeenAt: now2
|
|
2132
|
+
};
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
entry.count += 1;
|
|
2136
|
+
entry.lastSeenAt = now2;
|
|
2137
|
+
}
|
|
2138
|
+
await saveObservedToolInventory(inventory);
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
// src/error-utils.ts
|
|
2142
|
+
function sanitizeError(err) {
|
|
2143
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2144
|
+
return msg.replace(/sk-ant-[a-zA-Z0-9_-]+/g, "[REDACTED]").replace(/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g, "[REDACTED_JWT]").replace(/Bearer\s+[^\s,;]+/gi, "Bearer [REDACTED]");
|
|
2145
|
+
}
|
|
2146
|
+
function enrich429(body, headers) {
|
|
2147
|
+
try {
|
|
2148
|
+
const parsed = JSON.parse(body);
|
|
2149
|
+
const error = parsed.error;
|
|
2150
|
+
if (error && (error.message === "Error" || !error.message)) {
|
|
2151
|
+
const claim = headers.get("anthropic-ratelimit-unified-representative-claim") || "unknown";
|
|
2152
|
+
const status = headers.get("anthropic-ratelimit-unified-status") || "rejected";
|
|
2153
|
+
const util5h = headers.get("anthropic-ratelimit-unified-5h-utilization");
|
|
2154
|
+
const util7d = headers.get("anthropic-ratelimit-unified-7d-utilization");
|
|
2155
|
+
const reset = headers.get("anthropic-ratelimit-unified-reset");
|
|
2156
|
+
const parts = [`Rate limited (${status}). Limiting window: ${claim}`];
|
|
2157
|
+
if (util5h) {
|
|
2158
|
+
const parsedUtil5h = Number.parseFloat(util5h);
|
|
2159
|
+
if (Number.isFinite(parsedUtil5h)) {
|
|
2160
|
+
parts.push(`5h utilization: ${Math.round(parsedUtil5h * 100)}%`);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
if (util7d) {
|
|
2164
|
+
const parsedUtil7d = Number.parseFloat(util7d);
|
|
2165
|
+
if (Number.isFinite(parsedUtil7d)) {
|
|
2166
|
+
parts.push(`7d utilization: ${Math.round(parsedUtil7d * 100)}%`);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
if (reset) {
|
|
2170
|
+
const parsedReset = Number.parseInt(reset, 10);
|
|
2171
|
+
if (Number.isFinite(parsedReset)) {
|
|
2172
|
+
const resetDate = new Date(parsedReset * 1e3);
|
|
2173
|
+
const minutesUntilReset = Math.max(0, Math.round((resetDate.getTime() - Date.now()) / 6e4));
|
|
2174
|
+
parts.push(`resets in ${minutesUntilReset}m`);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
error.message = parts.join(". ");
|
|
2178
|
+
}
|
|
2179
|
+
return JSON.stringify(parsed);
|
|
2180
|
+
} catch {
|
|
2181
|
+
return body;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
// src/pacing.ts
|
|
2186
|
+
function pickNonNegativeInt(...values) {
|
|
2187
|
+
for (const v4 of values) {
|
|
2188
|
+
if (v4 === void 0 || v4 === null) continue;
|
|
2189
|
+
const n = typeof v4 === "number" ? v4 : Number.parseInt(v4, 10);
|
|
2190
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
2191
|
+
}
|
|
2192
|
+
return void 0;
|
|
2193
|
+
}
|
|
2194
|
+
function computePacingDelay(now2, lastRequestTime, cfg, rng = Math.random) {
|
|
2195
|
+
if (lastRequestTime <= 0) return 0;
|
|
2196
|
+
const minGap = Math.max(0, cfg.minGapMs);
|
|
2197
|
+
const jitter = Math.max(0, cfg.jitterMs);
|
|
2198
|
+
const jitterAdd = jitter > 0 ? Math.floor(rng() * jitter) : 0;
|
|
2199
|
+
const effectiveGap = minGap + jitterAdd;
|
|
2200
|
+
const elapsed = now2 - lastRequestTime;
|
|
2201
|
+
if (elapsed >= effectiveGap) return 0;
|
|
2202
|
+
return effectiveGap - elapsed;
|
|
2203
|
+
}
|
|
2204
|
+
function resolvePacingConfig(explicit = {}, env = process.env) {
|
|
2205
|
+
const minGap = pickNonNegativeInt(explicit.minGapMs, env.ANTHROPIC_PACE_MIN_MS, env.MIN_REQUEST_INTERVAL_MS) ?? 500;
|
|
2206
|
+
const jitter = pickNonNegativeInt(explicit.jitterMs, env.ANTHROPIC_PACE_JITTER_MS) ?? 0;
|
|
2207
|
+
return { minGapMs: minGap, jitterMs: jitter };
|
|
2208
|
+
}
|
|
1856
2209
|
|
|
1857
2210
|
// src/runtime-factory.ts
|
|
1858
|
-
import { TokenRefreshError } from "opencode-multi-account-core";
|
|
1859
2211
|
var TOKEN_REFRESH_PERMANENT_FAILURE_STATUS = 401;
|
|
2212
|
+
function mergeHeaders(target, headers) {
|
|
2213
|
+
if (!headers) {
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
if (headers instanceof Headers) {
|
|
2217
|
+
headers.forEach((value, key) => {
|
|
2218
|
+
target[key.toLowerCase()] = value;
|
|
2219
|
+
});
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
if (Array.isArray(headers)) {
|
|
2223
|
+
for (const [key, value] of headers) {
|
|
2224
|
+
target[String(key).toLowerCase()] = String(value);
|
|
2225
|
+
}
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2229
|
+
if (value !== void 0) {
|
|
2230
|
+
target[key.toLowerCase()] = String(value);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
function extractIncomingHeaders(input, init) {
|
|
2235
|
+
const headers = {};
|
|
2236
|
+
if (input instanceof Request) {
|
|
2237
|
+
mergeHeaders(headers, input.headers);
|
|
2238
|
+
}
|
|
2239
|
+
mergeHeaders(headers, init?.headers);
|
|
2240
|
+
return headers;
|
|
2241
|
+
}
|
|
2242
|
+
function splitBetaValues(value) {
|
|
2243
|
+
if (!value) {
|
|
2244
|
+
return [];
|
|
2245
|
+
}
|
|
2246
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2247
|
+
}
|
|
2248
|
+
function deduplicateBetas(values) {
|
|
2249
|
+
return [...new Set(values.filter(Boolean))];
|
|
2250
|
+
}
|
|
2251
|
+
function excludeBetas(values, excludedBetas2) {
|
|
2252
|
+
if (excludedBetas2.size === 0) {
|
|
2253
|
+
return values;
|
|
2254
|
+
}
|
|
2255
|
+
return values.filter((beta) => !excludedBetas2.has(beta));
|
|
2256
|
+
}
|
|
2257
|
+
function transformBodyToUpstream(body, identity, sessionId2) {
|
|
2258
|
+
try {
|
|
2259
|
+
const parsed = JSON.parse(body);
|
|
2260
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2261
|
+
return body;
|
|
2262
|
+
}
|
|
2263
|
+
return JSON.stringify(
|
|
2264
|
+
buildUpstreamRequest(
|
|
2265
|
+
parsed,
|
|
2266
|
+
identity,
|
|
2267
|
+
loadTemplate(),
|
|
2268
|
+
{ sessionId: sessionId2 }
|
|
2269
|
+
)
|
|
2270
|
+
);
|
|
2271
|
+
} catch {
|
|
2272
|
+
return body;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
async function enrichRateLimitResponse(response) {
|
|
2276
|
+
if (response.status !== 429) {
|
|
2277
|
+
return response;
|
|
2278
|
+
}
|
|
2279
|
+
const body = await response.clone().text();
|
|
2280
|
+
const enrichedBody = enrich429(body, response.headers);
|
|
2281
|
+
if (enrichedBody === body) {
|
|
2282
|
+
return response;
|
|
2283
|
+
}
|
|
2284
|
+
return new Response(enrichedBody, {
|
|
2285
|
+
status: response.status,
|
|
2286
|
+
statusText: response.statusText,
|
|
2287
|
+
headers: new Headers(response.headers)
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
1860
2290
|
var AccountRuntimeFactory = class {
|
|
1861
|
-
constructor(store, client) {
|
|
2291
|
+
constructor(store, client, identity = loadClaudeIdentity()) {
|
|
1862
2292
|
this.store = store;
|
|
1863
2293
|
this.client = client;
|
|
2294
|
+
this.identity = identity;
|
|
1864
2295
|
}
|
|
1865
2296
|
store;
|
|
1866
2297
|
client;
|
|
2298
|
+
identity;
|
|
1867
2299
|
runtimes = /* @__PURE__ */ new Map();
|
|
1868
2300
|
initLocks = /* @__PURE__ */ new Map();
|
|
2301
|
+
lastRequestTime = 0;
|
|
2302
|
+
pacingGate = Promise.resolve();
|
|
2303
|
+
pacingTestOverrides = {};
|
|
2304
|
+
setPacingTestOverrides(overrides) {
|
|
2305
|
+
this.pacingTestOverrides = overrides;
|
|
2306
|
+
}
|
|
2307
|
+
resetPacingForTest() {
|
|
2308
|
+
this.lastRequestTime = 0;
|
|
2309
|
+
this.pacingGate = Promise.resolve();
|
|
2310
|
+
this.pacingTestOverrides = {};
|
|
2311
|
+
}
|
|
1869
2312
|
async getRuntime(uuid) {
|
|
1870
2313
|
const cached = this.runtimes.get(uuid);
|
|
1871
2314
|
if (cached) return cached;
|
|
@@ -1917,17 +2360,77 @@ var AccountRuntimeFactory = class {
|
|
|
1917
2360
|
});
|
|
1918
2361
|
return { accessToken: refreshed.patch.accessToken, expiresAt: refreshed.patch.expiresAt };
|
|
1919
2362
|
}
|
|
2363
|
+
buildOutboundHeaders(incomingHeaders, sessionId2, accessToken, modelId, excludedBetas2) {
|
|
2364
|
+
const mergedBetas = deduplicateBetas([
|
|
2365
|
+
...excludeBetas(splitBetaValues(getBetaHeader()), excludedBetas2),
|
|
2366
|
+
...getModelBetas(modelId, excludedBetas2),
|
|
2367
|
+
...excludeBetas(splitBetaValues(incomingHeaders["anthropic-beta"]), excludedBetas2)
|
|
2368
|
+
]).join(",");
|
|
2369
|
+
const outbound = {
|
|
2370
|
+
...incomingHeaders,
|
|
2371
|
+
...getStaticHeaders(),
|
|
2372
|
+
...getPerRequestHeaders(sessionId2),
|
|
2373
|
+
"authorization": `Bearer ${accessToken}`,
|
|
2374
|
+
"anthropic-beta": filterBillableBetas(mergedBetas)
|
|
2375
|
+
};
|
|
2376
|
+
delete outbound["x-api-key"];
|
|
2377
|
+
return orderHeadersForOutbound(outbound);
|
|
2378
|
+
}
|
|
1920
2379
|
async executeTransformedFetch(input, init, accessToken) {
|
|
1921
2380
|
const transformedInput = transformRequestUrl(input);
|
|
1922
2381
|
const modelId = extractModelIdFromBody(init?.body);
|
|
1923
2382
|
const excludedBetas2 = getExcludedBetas(modelId);
|
|
1924
|
-
const
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2383
|
+
const incomingHeaders = extractIncomingHeaders(transformedInput, init);
|
|
2384
|
+
const sessionId2 = incomingHeaders["x-claude-code-session-id"] ?? getUpstreamSessionId();
|
|
2385
|
+
const headers = this.buildOutboundHeaders(
|
|
2386
|
+
incomingHeaders,
|
|
2387
|
+
sessionId2,
|
|
2388
|
+
accessToken,
|
|
2389
|
+
modelId,
|
|
2390
|
+
excludedBetas2
|
|
2391
|
+
);
|
|
2392
|
+
if (typeof init?.body === "string") {
|
|
2393
|
+
void recordObservedToolNames(extractToolNamesFromRequestBody(init.body)).catch(() => {
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
const transformedBody = typeof init?.body === "string" ? transformBodyToUpstream(init.body, this.identity, sessionId2) : init?.body;
|
|
2397
|
+
const pacingCfg = resolvePacingConfig();
|
|
2398
|
+
const getNow = this.pacingTestOverrides.now ?? Date.now;
|
|
2399
|
+
const sleepFn = this.pacingTestOverrides.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2400
|
+
const reservePacingSlot = async () => {
|
|
2401
|
+
let releaseGate;
|
|
2402
|
+
const previousGate = this.pacingGate;
|
|
2403
|
+
this.pacingGate = new Promise((resolve) => {
|
|
2404
|
+
releaseGate = resolve;
|
|
2405
|
+
});
|
|
2406
|
+
await previousGate;
|
|
2407
|
+
try {
|
|
2408
|
+
const delay = computePacingDelay(getNow(), this.lastRequestTime, pacingCfg);
|
|
2409
|
+
if (delay > 0) {
|
|
2410
|
+
await sleepFn(delay);
|
|
2411
|
+
}
|
|
2412
|
+
this.lastRequestTime = getNow();
|
|
2413
|
+
} finally {
|
|
2414
|
+
releaseGate?.();
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
const performFetch = async (requestHeaders) => {
|
|
2418
|
+
await reservePacingSlot();
|
|
2419
|
+
try {
|
|
2420
|
+
const response2 = await fetch(transformedInput, {
|
|
2421
|
+
...init,
|
|
2422
|
+
headers: requestHeaders,
|
|
2423
|
+
body: transformedBody
|
|
2424
|
+
});
|
|
2425
|
+
return await enrichRateLimitResponse(response2);
|
|
2426
|
+
} catch (error) {
|
|
2427
|
+
debugLog(this.client, "Anthropic upstream fetch failed", {
|
|
2428
|
+
error: sanitizeError(error)
|
|
2429
|
+
});
|
|
2430
|
+
throw error;
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
let response = await performFetch(headers);
|
|
1931
2434
|
for (let attempt = 0; attempt < LONG_CONTEXT_BETAS.length; attempt += 1) {
|
|
1932
2435
|
if (response.status !== 400 && response.status !== 429) {
|
|
1933
2436
|
break;
|
|
@@ -1941,20 +2444,16 @@ var AccountRuntimeFactory = class {
|
|
|
1941
2444
|
break;
|
|
1942
2445
|
}
|
|
1943
2446
|
addExcludedBeta(modelId, betaToExclude);
|
|
1944
|
-
const retryHeaders =
|
|
1945
|
-
|
|
1946
|
-
|
|
2447
|
+
const retryHeaders = this.buildOutboundHeaders(
|
|
2448
|
+
incomingHeaders,
|
|
2449
|
+
sessionId2,
|
|
1947
2450
|
accessToken,
|
|
1948
2451
|
modelId,
|
|
1949
2452
|
getExcludedBetas(modelId)
|
|
1950
2453
|
);
|
|
1951
|
-
response = await
|
|
1952
|
-
...init,
|
|
1953
|
-
headers: retryHeaders,
|
|
1954
|
-
body: transformedBody
|
|
1955
|
-
});
|
|
2454
|
+
response = await performFetch(retryHeaders);
|
|
1956
2455
|
}
|
|
1957
|
-
return
|
|
2456
|
+
return createStreamingReverseMapper(response);
|
|
1958
2457
|
}
|
|
1959
2458
|
async createRuntime(uuid) {
|
|
1960
2459
|
const fetchWithAccount = async (input, init) => {
|
|
@@ -1979,8 +2478,8 @@ var AccountRuntimeFactory = class {
|
|
|
1979
2478
|
};
|
|
1980
2479
|
|
|
1981
2480
|
// src/bootstrap-auth.ts
|
|
1982
|
-
import { promises as
|
|
1983
|
-
import { join } from "path";
|
|
2481
|
+
import { promises as fs2 } from "fs";
|
|
2482
|
+
import { join as join3 } from "path";
|
|
1984
2483
|
import { getConfigDir as getConfigDir2 } from "opencode-multi-account-core";
|
|
1985
2484
|
var AUTH_JSON_FILENAME = "auth.json";
|
|
1986
2485
|
function hasCompleteOAuthCredential(account) {
|
|
@@ -2001,10 +2500,10 @@ function selectBootstrapAccount(accounts, activeAccountUuid) {
|
|
|
2001
2500
|
return firstUsableAccount ?? completeAccounts[0] ?? null;
|
|
2002
2501
|
}
|
|
2003
2502
|
async function readCurrentAuth(providerId) {
|
|
2004
|
-
const authPath =
|
|
2503
|
+
const authPath = join3(getConfigDir2(), AUTH_JSON_FILENAME);
|
|
2005
2504
|
let raw;
|
|
2006
2505
|
try {
|
|
2007
|
-
raw = await
|
|
2506
|
+
raw = await fs2.readFile(authPath, "utf-8");
|
|
2008
2507
|
} catch {
|
|
2009
2508
|
return null;
|
|
2010
2509
|
}
|
|
@@ -2056,6 +2555,70 @@ async function syncBootstrapAuth(client, store) {
|
|
|
2056
2555
|
return true;
|
|
2057
2556
|
}
|
|
2058
2557
|
|
|
2558
|
+
// src/session-heartbeat.ts
|
|
2559
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
2560
|
+
var CLIENT_PLATFORM = "cli";
|
|
2561
|
+
var testOverrides = {};
|
|
2562
|
+
function presenceUrl(sessionId2) {
|
|
2563
|
+
return `${loadCCDerivedRequestProfile().baseApiUrl}/v1/code/sessions/${sessionId2}/client/presence`;
|
|
2564
|
+
}
|
|
2565
|
+
function fetchFn() {
|
|
2566
|
+
return testOverrides.fetch ?? globalThis.fetch;
|
|
2567
|
+
}
|
|
2568
|
+
function startHeartbeat(options) {
|
|
2569
|
+
testOverrides.onStart?.(options);
|
|
2570
|
+
const {
|
|
2571
|
+
sessionId: sessionId2,
|
|
2572
|
+
deviceId,
|
|
2573
|
+
accessToken,
|
|
2574
|
+
intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS
|
|
2575
|
+
} = options;
|
|
2576
|
+
let activeController = null;
|
|
2577
|
+
let stopped = false;
|
|
2578
|
+
const sendPresence = async () => {
|
|
2579
|
+
if (stopped) return;
|
|
2580
|
+
const controller = new AbortController();
|
|
2581
|
+
activeController = controller;
|
|
2582
|
+
try {
|
|
2583
|
+
await fetchFn()(presenceUrl(sessionId2), {
|
|
2584
|
+
method: "POST",
|
|
2585
|
+
headers: {
|
|
2586
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2587
|
+
"anthropic-version": getAnthropicVersion(),
|
|
2588
|
+
"anthropic-client-platform": CLIENT_PLATFORM,
|
|
2589
|
+
"Content-Type": "application/json"
|
|
2590
|
+
},
|
|
2591
|
+
body: JSON.stringify({
|
|
2592
|
+
client_id: deviceId,
|
|
2593
|
+
connected_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2594
|
+
}),
|
|
2595
|
+
signal: controller.signal
|
|
2596
|
+
});
|
|
2597
|
+
} catch {
|
|
2598
|
+
} finally {
|
|
2599
|
+
if (activeController === controller) {
|
|
2600
|
+
activeController = null;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
};
|
|
2604
|
+
const timer = setInterval(sendPresence, intervalMs);
|
|
2605
|
+
if (typeof timer === "object" && "unref" in timer && typeof timer.unref === "function") {
|
|
2606
|
+
timer.unref();
|
|
2607
|
+
}
|
|
2608
|
+
return {
|
|
2609
|
+
stop() {
|
|
2610
|
+
if (stopped) return;
|
|
2611
|
+
stopped = true;
|
|
2612
|
+
clearInterval(timer);
|
|
2613
|
+
activeController?.abort();
|
|
2614
|
+
activeController = null;
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
function getSessionId() {
|
|
2619
|
+
return getUpstreamSessionId();
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2059
2622
|
// src/index.ts
|
|
2060
2623
|
var EMPTY_OAUTH_CREDENTIALS = {
|
|
2061
2624
|
type: "oauth",
|
|
@@ -2081,19 +2644,118 @@ function extractFirstUserText(input) {
|
|
|
2081
2644
|
}
|
|
2082
2645
|
return "";
|
|
2083
2646
|
}
|
|
2084
|
-
function
|
|
2085
|
-
const
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
output.system.
|
|
2647
|
+
function composeBillingSystemEntry(firstUserMessage, version) {
|
|
2648
|
+
const buildTag = computeBuildTag(firstUserMessage, version);
|
|
2649
|
+
return `x-anthropic-billing-header: cc_version=${version}.${buildTag}; cc_entrypoint=cli; cch=00000;`;
|
|
2650
|
+
}
|
|
2651
|
+
function prependMissingSystemEntries(output, entries) {
|
|
2652
|
+
output.system ??= [];
|
|
2653
|
+
for (const entry of entries.toReversed()) {
|
|
2654
|
+
if (entry && !output.system.includes(entry)) {
|
|
2655
|
+
output.system.unshift(entry);
|
|
2656
|
+
}
|
|
2092
2657
|
}
|
|
2093
2658
|
}
|
|
2659
|
+
function applyOrderedHeaders(output, headers) {
|
|
2660
|
+
const orderedHeaders = orderHeadersForOutbound(headers);
|
|
2661
|
+
output.headers = Array.isArray(orderedHeaders) ? Object.fromEntries(orderedHeaders) : orderedHeaders;
|
|
2662
|
+
}
|
|
2094
2663
|
var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
2095
2664
|
const { client } = ctx;
|
|
2096
2665
|
await loadConfig();
|
|
2666
|
+
const requestProfile = loadCCDerivedRequestProfile();
|
|
2667
|
+
const template = requestProfile.template;
|
|
2668
|
+
const claudeIdentity = loadClaudeIdentity();
|
|
2669
|
+
const claudeCodeVersion = template.cc_version ?? requestProfile.cliVersion;
|
|
2670
|
+
const upstreamAgentIdentity = template.agent_identity;
|
|
2671
|
+
const upstreamSystemPrompt = template.system_prompt;
|
|
2672
|
+
let heartbeatHandle = null;
|
|
2673
|
+
let heartbeatToken = null;
|
|
2674
|
+
let heartbeatSessionId = null;
|
|
2675
|
+
const stopHeartbeat = () => {
|
|
2676
|
+
heartbeatHandle?.stop();
|
|
2677
|
+
heartbeatHandle = null;
|
|
2678
|
+
heartbeatToken = null;
|
|
2679
|
+
heartbeatSessionId = null;
|
|
2680
|
+
};
|
|
2681
|
+
const ensureHeartbeat = (accessToken) => {
|
|
2682
|
+
if (!accessToken || !claudeIdentity.deviceId) {
|
|
2683
|
+
stopHeartbeat();
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
const sessionId2 = getSessionId();
|
|
2687
|
+
if (heartbeatHandle && heartbeatToken === accessToken && heartbeatSessionId === sessionId2) {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
stopHeartbeat();
|
|
2691
|
+
heartbeatToken = accessToken;
|
|
2692
|
+
heartbeatSessionId = sessionId2;
|
|
2693
|
+
heartbeatHandle = startHeartbeat({
|
|
2694
|
+
sessionId: sessionId2,
|
|
2695
|
+
deviceId: claudeIdentity.deviceId,
|
|
2696
|
+
accessToken
|
|
2697
|
+
});
|
|
2698
|
+
};
|
|
2699
|
+
const startupDrift = detectDrift(template);
|
|
2700
|
+
if (startupDrift.drifted) {
|
|
2701
|
+
client.app.log({
|
|
2702
|
+
body: {
|
|
2703
|
+
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
2704
|
+
level: "warn",
|
|
2705
|
+
message: startupDrift.message,
|
|
2706
|
+
extra: {
|
|
2707
|
+
cachedVersion: startupDrift.cachedVersion,
|
|
2708
|
+
installedVersion: startupDrift.installedVersion
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}).catch(() => {
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
const compat = checkCCCompat();
|
|
2715
|
+
if (compat.status !== "ok" && compat.status !== "unknown") {
|
|
2716
|
+
client.app.log({
|
|
2717
|
+
body: {
|
|
2718
|
+
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
2719
|
+
level: "warn",
|
|
2720
|
+
message: compat.message,
|
|
2721
|
+
extra: {
|
|
2722
|
+
installedVersion: compat.installedVersion,
|
|
2723
|
+
range: compat.range
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}).catch(() => {
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
void refreshLiveFingerprintAsync({ silent: true }).then((refreshedTemplate) => {
|
|
2730
|
+
const refreshedDrift = detectDrift(refreshedTemplate ?? template);
|
|
2731
|
+
if (!refreshedDrift.drifted) {
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
return client.app.log({
|
|
2735
|
+
body: {
|
|
2736
|
+
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
2737
|
+
level: "warn",
|
|
2738
|
+
message: refreshedDrift.message,
|
|
2739
|
+
extra: {
|
|
2740
|
+
cachedVersion: refreshedDrift.cachedVersion,
|
|
2741
|
+
installedVersion: refreshedDrift.installedVersion
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}).catch(() => {
|
|
2745
|
+
});
|
|
2746
|
+
}).catch((error) => {
|
|
2747
|
+
client.app.log({
|
|
2748
|
+
body: {
|
|
2749
|
+
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
2750
|
+
level: "debug",
|
|
2751
|
+
message: "live fingerprint refresh failed",
|
|
2752
|
+
extra: {
|
|
2753
|
+
error: sanitizeError(error)
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
}).catch(() => {
|
|
2757
|
+
});
|
|
2758
|
+
});
|
|
2097
2759
|
const store = new AccountStore();
|
|
2098
2760
|
await syncBootstrapAuth(client, store).catch(() => {
|
|
2099
2761
|
});
|
|
@@ -2104,7 +2766,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2104
2766
|
let cascadeStateManager = null;
|
|
2105
2767
|
let poolChainConfig = { pools: [], chains: [] };
|
|
2106
2768
|
async function ensureExecutionInfrastructure() {
|
|
2107
|
-
runtimeFactory ??= new AccountRuntimeFactory(store, client);
|
|
2769
|
+
runtimeFactory ??= new AccountRuntimeFactory(store, client, claudeIdentity);
|
|
2108
2770
|
poolChainConfig = await loadPoolChainConfig();
|
|
2109
2771
|
poolManager ??= new PoolManager();
|
|
2110
2772
|
poolManager.loadPools(poolChainConfig.pools);
|
|
@@ -2143,6 +2805,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2143
2805
|
manager = await AccountManager.create(store, EMPTY_OAUTH_CREDENTIALS, client);
|
|
2144
2806
|
await ensureExecutionInfrastructure();
|
|
2145
2807
|
await startRefreshQueueIfNeeded();
|
|
2808
|
+
ensureHeartbeat(manager.getActiveAccount()?.accessToken);
|
|
2146
2809
|
return manager.getAccountCount() > 0;
|
|
2147
2810
|
}
|
|
2148
2811
|
async function initializeManagerFromAuth(credentials) {
|
|
@@ -2156,11 +2819,12 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2156
2819
|
});
|
|
2157
2820
|
return {
|
|
2158
2821
|
"experimental.chat.system.transform": (input, output) => {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2822
|
+
const billingHeader = composeBillingSystemEntry(extractFirstUserText(input), claudeCodeVersion);
|
|
2823
|
+
prependMissingSystemEntries(output, [
|
|
2824
|
+
billingHeader,
|
|
2825
|
+
upstreamAgentIdentity,
|
|
2826
|
+
upstreamSystemPrompt
|
|
2827
|
+
]);
|
|
2164
2828
|
},
|
|
2165
2829
|
tool: {
|
|
2166
2830
|
[ANTHROPIC_OAUTH_ADAPTER.statusToolName]: tool({
|
|
@@ -2198,10 +2862,10 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2198
2862
|
}
|
|
2199
2863
|
}
|
|
2200
2864
|
if (account.cachedUsage) {
|
|
2201
|
-
const
|
|
2865
|
+
const now2 = Date.now();
|
|
2202
2866
|
const usage2 = account.cachedUsage;
|
|
2203
2867
|
const exhaustedTiers = [usage2.five_hour, usage2.seven_day].filter(
|
|
2204
|
-
(tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) >
|
|
2868
|
+
(tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now2
|
|
2205
2869
|
);
|
|
2206
2870
|
if (exhaustedTiers.length > 0) {
|
|
2207
2871
|
statusParts.push("USAGE EXHAUSTED");
|
|
@@ -2232,6 +2896,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2232
2896
|
async loader(getAuth, provider) {
|
|
2233
2897
|
const auth = await getAuth();
|
|
2234
2898
|
if (auth.type !== "oauth") {
|
|
2899
|
+
stopHeartbeat();
|
|
2235
2900
|
return { apiKey: "", fetch };
|
|
2236
2901
|
}
|
|
2237
2902
|
for (const model of Object.values(provider.models ?? {})) {
|
|
@@ -2242,6 +2907,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2242
2907
|
const credentials = auth;
|
|
2243
2908
|
await migrateFromAuthJson("anthropic", store);
|
|
2244
2909
|
await initializeManagerFromAuth(credentials);
|
|
2910
|
+
ensureHeartbeat(credentials.access);
|
|
2245
2911
|
const initializedManager = manager;
|
|
2246
2912
|
if (!initializedManager) {
|
|
2247
2913
|
return { apiKey: "", fetch };
|
|
@@ -2264,23 +2930,32 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2264
2930
|
);
|
|
2265
2931
|
}
|
|
2266
2932
|
}
|
|
2933
|
+
const authProfile = await loadCCDerivedAuthProfile();
|
|
2267
2934
|
return {
|
|
2268
2935
|
apiKey: "",
|
|
2936
|
+
baseURL: authProfile.apiV1BaseUrl,
|
|
2269
2937
|
"chat.headers": async (input, output) => {
|
|
2270
2938
|
if (input.provider?.info?.id !== ANTHROPIC_OAUTH_ADAPTER.authProviderId) return;
|
|
2271
|
-
|
|
2272
|
-
output
|
|
2273
|
-
|
|
2939
|
+
const sessionId2 = getUpstreamSessionId();
|
|
2940
|
+
applyOrderedHeaders(output, {
|
|
2941
|
+
...output.headers,
|
|
2942
|
+
...getStaticHeaders(),
|
|
2943
|
+
...getPerRequestHeaders(sessionId2),
|
|
2944
|
+
"anthropic-beta": getBetaHeader()
|
|
2945
|
+
});
|
|
2274
2946
|
},
|
|
2275
2947
|
async fetch(input, init) {
|
|
2276
2948
|
if (!initializedManager || !runtimeFactory) {
|
|
2949
|
+
stopHeartbeat();
|
|
2277
2950
|
return fetch(input, init);
|
|
2278
2951
|
}
|
|
2279
2952
|
if (initializedManager.getAccountCount() === 0) {
|
|
2953
|
+
stopHeartbeat();
|
|
2280
2954
|
throw new Error(
|
|
2281
2955
|
"No Anthropic accounts configured. Run `opencode auth login` to add an account."
|
|
2282
2956
|
);
|
|
2283
2957
|
}
|
|
2958
|
+
ensureHeartbeat(initializedManager.getActiveAccount()?.accessToken);
|
|
2284
2959
|
if (!poolManager || !cascadeStateManager) {
|
|
2285
2960
|
poolManager = new PoolManager();
|
|
2286
2961
|
poolManager.loadPools(poolChainConfig.pools);
|