opencode-anthropic-multi-account 0.2.15 → 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/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/pi-ai-adapter.ts
49
- import { AsyncLocalStorage } from "async_hooks";
50
- import * as piAiOauth from "@mariozechner/pi-ai/oauth";
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": "application/json",
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/pi-ai-adapter.ts
332
- function fromPiAiCredentials(creds) {
333
- return {
334
- accessToken: creds.access,
335
- refreshToken: creds.refresh,
336
- expiresAt: creds.expires
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
- var ANTHROPIC_REFRESH_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
340
- var ANTHROPIC_TOKEN_HOST = "platform.claude.com";
341
- var REFRESH_NODE_EXECUTABLE = process.env.OPENCODE_REFRESH_NODE_EXECUTABLE || "node";
342
- var tokenProxyContext = new AsyncLocalStorage();
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
- const url = new URL(rawUrl);
361
- return url.origin === refreshEndpointUrl.origin && url.pathname === refreshEndpointUrl.pathname;
362
- } catch {
363
- return rawUrl === ANTHROPIC_REFRESH_ENDPOINT;
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 getRequestBodySource(input, init) {
367
- if (init?.body !== void 0) {
368
- return init.body;
369
- }
370
- if (input instanceof Request) {
371
- return input.body;
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 void 0;
494
+ return v3.parse(TokenResponseSchema, parsed);
374
495
  }
375
- function stringifyBinaryBody(body) {
376
- if (body instanceof ArrayBuffer) {
377
- return Buffer.from(body).toString("utf8");
496
+ function getOpenBrowserCommand(url, platform = process.platform) {
497
+ if (platform === "win32") {
498
+ return `start "" ${JSON.stringify(url)}`;
378
499
  }
379
- return Buffer.from(body.buffer, body.byteOffset, body.byteLength).toString("utf8");
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
- if (typeof Blob !== "undefined" && body instanceof Blob) return await body.text();
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 shouldProxyTokenRequest(input) {
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
- const url = new URL(rawUrl);
406
- return url.host === ANTHROPIC_TOKEN_HOST;
507
+ browserExec(getOpenBrowserCommand(url), () => {
508
+ });
407
509
  } catch {
408
- return rawUrl.includes(ANTHROPIC_TOKEN_HOST);
409
510
  }
410
511
  }
411
- async function postAnthropicTokenViaNode(body) {
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
- endpoint: ANTHROPIC_REFRESH_ENDPOINT,
417
- executable: REFRESH_NODE_EXECUTABLE,
418
- timeoutMs: TOKEN_REFRESH_TIMEOUT_MS
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 buildRefreshRequestError(details);
529
+ throw buildTokenRequestError(endpoint, details);
423
530
  }
424
- let parsed;
425
- try {
426
- parsed = JSON.parse(output);
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
- const result = parsed;
432
- if (!result.ok) {
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
- return new Response(result.body ?? "", {
439
- status: 200,
440
- headers: {
441
- "content-type": "application/json"
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
- function createAnthropicTokenProxyFetch(originalFetch) {
446
- return (async (input, init) => {
447
- if (shouldProxyTokenRequest(input)) {
448
- const method = getRequestMethod(input, init).toUpperCase();
449
- if (method !== "POST") {
450
- throw buildRefreshRequestError(`Unsupported token endpoint method: ${method}`);
451
- }
452
- return await postAnthropicTokenViaNode(await getRequestBody(input, init));
453
- }
454
- if (shouldInjectAnthropicUserAgent(input)) {
455
- const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
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
- function ensureAnthropicTokenProxyFetchInstalled() {
463
- if (tokenProxyInstalled) return;
464
- tokenProxyOriginalFetch = globalThis.fetch;
465
- globalThis.fetch = createAnthropicTokenProxyFetch(tokenProxyOriginalFetch);
466
- tokenProxyInstalled = true;
467
- }
468
- async function withAnthropicTokenProxyFetch(operation, options) {
469
- ensureAnthropicTokenProxyFetchInstalled();
470
- return await tokenProxyContext.run({ proxyTokenRequests: true, userAgent: options?.userAgent }, operation);
471
- }
472
- async function fetchProfileWithSingleRetry(accessToken) {
473
- let profileResult = await fetchProfile(accessToken);
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 base = fromPiAiCredentials(piCreds);
492
- const profileResult = await fetchProfileWithSingleRetry(piCreds.access);
493
- const profileData = profileResult.ok ? profileResult.data : void 0;
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 refreshWithPiAi(currentRefreshToken);
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 now = Date.now();
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) > now
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 now = /* @__PURE__ */ new Date();
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() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
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 { createInterface } from "readline";
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 promptLine(message) {
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 loginWithPiAi({
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" && callbackResult.refresh) {
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" && callbackResult.refresh) {
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 now = Date.now();
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: now,
1089
- lastUsed: now,
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 startPiAiFlow(), manager);
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 startPiAiFlow(), manager);
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 startPiAiFlow(), manager);
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 startPiAiFlow(), manager, manageResult.account);
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 startPiAiFlow(), manager);
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 { createHash } from "crypto";
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,590 +1471,449 @@ function getModelBetas(modelId, excluded) {
1374
1471
  return betas.filter((beta) => !excluded.has(beta));
1375
1472
  }
1376
1473
 
1377
- // src/anthropic-prompt.ts
1378
- var INJECTED_SYSTEM_PROMPT = "You are Claude Code, an interactive CLI tool that helps users with software engineering tasks.";
1379
- var SYSTEM_PROMPT = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
1380
-
1381
- 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.
1382
- IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
1383
-
1384
- If the user asks for help or wants to give feedback inform them of the following:
1385
- - /help: Get help with using Claude Code
1386
- - To give feedback, users should report the issue at https://github.com/anthropics/claude-code/issues
1387
-
1388
- When the user directly asks about Claude Code (eg. "can Claude Code do...", "does Claude Code have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific Claude Code feature (eg. implement a hook, or write a slash command), use the WebFetch tool to gather information to answer the question from Claude Code docs. The list of available docs is available at https://docs.claude.com/en/docs/claude-code/claude_code_docs_map.md.
1389
-
1390
- # Tone and style
1391
- You should be concise, direct, and to the point, while providing complete information and matching the level of detail you provide in your response with the level of complexity of the user's query or the work you have completed.
1392
- A concise response is generally less than 4 lines, not including tool calls or code generated. You should provide more detail when the task is complex or when the user asks you to.
1393
- IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do.
1394
- IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to.
1395
- Do not add additional code explanation summary unless requested by the user. After working on a file, briefly confirm that you have completed the task, rather than providing an explanation of what you did.
1396
- Answer the user's question directly, avoiding any elaboration, explanation, introduction, conclusion, or excessive details. Brief answers are best, but be sure to provide complete information. You MUST avoid extra preamble before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...".
1397
-
1398
- Here are some examples to demonstrate appropriate verbosity:
1399
- <example>
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_MASK_PREFIX = "tool_";
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
- var DOCUMENTED_BUILTIN_TOOL_NAMES = /* @__PURE__ */ new Set([
1570
- "Agent",
1571
- "AskUserQuestion",
1572
- "Bash",
1573
- "CronCreate",
1574
- "CronDelete",
1575
- "CronList",
1576
- "Edit",
1577
- "EnterPlanMode",
1578
- "EnterWorktree",
1579
- "ExitPlanMode",
1580
- "ExitWorktree",
1581
- "Glob",
1582
- "Grep",
1583
- "ListMcpResourcesTool",
1584
- "LSP",
1585
- "Monitor",
1586
- "NotebookEdit",
1587
- "PowerShell",
1588
- "Read",
1589
- "ReadMcpResourceTool",
1590
- "SendMessage",
1591
- "Skill",
1592
- "TaskCreate",
1593
- "TaskGet",
1594
- "TaskList",
1595
- "TaskOutput",
1596
- "TaskStop",
1597
- "TaskUpdate",
1598
- "TeamCreate",
1599
- "TeamDelete",
1600
- "TodoWrite",
1601
- "ToolSearch",
1602
- "WebFetch",
1603
- "WebSearch",
1604
- "Write"
1605
- ]);
1606
- function isTypedTool(tool2) {
1607
- return typeof tool2.type === "string" && tool2.type.trim().length > 0;
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 {
1497
+ }
1498
+ return paths;
1608
1499
  }
1609
- function shouldMaskToolName(name) {
1610
- if (!name) {
1611
- return false;
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;
1612
1512
  }
1613
- return !DOCUMENTED_BUILTIN_TOOL_NAMES.has(name) && !name.startsWith(TOOL_MASK_PREFIX);
1614
1513
  }
1615
- function extractFirstUserTextFromMessageContent(content) {
1616
- if (typeof content === "string") {
1617
- return content;
1514
+ function loadClaudeIdentity() {
1515
+ if (testOverrideIdentity) {
1516
+ return testOverrideIdentity;
1618
1517
  }
1619
- if (!Array.isArray(content)) {
1620
- return "";
1518
+ try {
1519
+ for (const path of getCandidatePaths()) {
1520
+ const identity = parseIdentityFile(path);
1521
+ if (identity) {
1522
+ return identity;
1523
+ }
1524
+ }
1525
+ } catch {
1621
1526
  }
1622
- return content.filter((block) => isRecord(block) && block.type === "text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n");
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();
1623
1575
  }
1624
- function extractFirstUserText(parsed) {
1625
- if (!Array.isArray(parsed.messages)) {
1626
- return "";
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();
1627
1583
  }
1628
- const firstUserMessage = parsed.messages.find((message) => message.role === "user");
1629
- if (!firstUserMessage) {
1630
- return "";
1584
+ sessionLastUsed = currentTime;
1585
+ return sessionId;
1586
+ }
1587
+ function getUpstreamSessionId() {
1588
+ return getActiveSessionId();
1589
+ }
1590
+ function isRecord(value) {
1591
+ return typeof value === "object" && value !== null;
1592
+ }
1593
+ function cloneBody(value) {
1594
+ return structuredClone(value);
1595
+ }
1596
+ function sanitizeContent(text) {
1597
+ let result = text;
1598
+ for (const pattern of ORCHESTRATION_PATTERNS) {
1599
+ pattern.lastIndex = 0;
1600
+ result = result.replace(pattern, "");
1631
1601
  }
1632
- return extractFirstUserTextFromMessageContent(firstUserMessage.content).trim();
1602
+ return result.replace(/\n{3,}/g, "\n\n").trim();
1633
1603
  }
1634
- function buildMaskedToolName(seed, toolName, length = 8) {
1635
- const digest = createHash("sha256").update(`tool-mask:${seed}:${toolName}`).digest("hex").slice(0, length);
1636
- return `${TOOL_MASK_PREFIX}${digest}`;
1604
+ function sanitizeAndScrubText(text) {
1605
+ return scrubFrameworkIdentifiers(sanitizeContent(text)).replace(/\n{3,}/g, "\n\n").trim();
1637
1606
  }
1638
- function collectMaskCandidates(parsed) {
1639
- const candidates = /* @__PURE__ */ new Set();
1640
- if (Array.isArray(parsed.tools)) {
1641
- for (const tool2 of parsed.tools) {
1642
- if (!isRecord(tool2) || isTypedTool(tool2) || !shouldMaskToolName(tool2.name)) {
1643
- continue;
1644
- }
1645
- candidates.add(tool2.name);
1607
+ function stripCacheControl(value) {
1608
+ if (Array.isArray(value)) {
1609
+ for (const item of value) {
1610
+ stripCacheControl(item);
1646
1611
  }
1612
+ return;
1647
1613
  }
1648
- if (Array.isArray(parsed.messages)) {
1649
- for (const message of parsed.messages) {
1650
- if (!Array.isArray(message.content)) {
1651
- continue;
1652
- }
1653
- for (const contentBlock of message.content) {
1654
- if (contentBlock.type !== "tool_use" || !shouldMaskToolName(contentBlock.name)) {
1655
- continue;
1656
- }
1657
- candidates.add(contentBlock.name);
1658
- }
1659
- }
1614
+ if (!isRecord(value)) {
1615
+ return;
1660
1616
  }
1661
- if (parsed.tool_choice?.type === "tool" && shouldMaskToolName(parsed.tool_choice.name)) {
1662
- candidates.add(parsed.tool_choice.name);
1617
+ delete value.cache_control;
1618
+ for (const nested of Object.values(value)) {
1619
+ stripCacheControl(nested);
1663
1620
  }
1664
- return [...candidates];
1665
1621
  }
1666
- function buildToolMaskMap(parsed) {
1667
- const maskMap = /* @__PURE__ */ new Map();
1668
- const candidates = collectMaskCandidates(parsed);
1669
- const firstUserText = extractFirstUserText(parsed);
1670
- const usedMaskedNames = /* @__PURE__ */ new Set();
1671
- for (const candidate of candidates) {
1672
- let hashLength = 8;
1673
- let maskedName = buildMaskedToolName(firstUserText, candidate, hashLength);
1674
- while (usedMaskedNames.has(maskedName)) {
1675
- hashLength += 2;
1676
- maskedName = buildMaskedToolName(firstUserText, candidate, hashLength);
1677
- }
1678
- maskMap.set(candidate, maskedName);
1679
- usedMaskedNames.add(maskedName);
1622
+ function sanitizeMessageBlock(block) {
1623
+ if (typeof block.text === "string") {
1624
+ block.text = sanitizeAndScrubText(block.text);
1680
1625
  }
1681
- return maskMap;
1682
- }
1683
- function extractRequestToolMaskMap(body) {
1684
- if (!body) {
1685
- return /* @__PURE__ */ new Map();
1626
+ if (block.type !== "tool_result") {
1627
+ return;
1686
1628
  }
1687
- try {
1688
- const parsed = JSON.parse(body);
1689
- return buildToolMaskMap(parsed);
1690
- } catch {
1691
- return /* @__PURE__ */ new Map();
1629
+ if (typeof block.content === "string") {
1630
+ block.content = truncateToolResultText(sanitizeAndScrubText(block.content));
1631
+ return;
1632
+ }
1633
+ if (!Array.isArray(block.content)) {
1634
+ return;
1635
+ }
1636
+ for (const item of block.content) {
1637
+ if (isRecord(item) && typeof item.text === "string") {
1638
+ item.text = truncateToolResultText(sanitizeAndScrubText(item.text));
1639
+ }
1692
1640
  }
1693
1641
  }
1694
- function renameMaskedToolName(name, maskMap) {
1695
- if (!name) {
1696
- return name;
1642
+ function stripAssistantThinkingBlocks(messages) {
1643
+ for (const message of messages) {
1644
+ if (message.role !== "assistant" || !Array.isArray(message.content)) {
1645
+ continue;
1646
+ }
1647
+ message.content = message.content.filter((block) => block.type !== "thinking");
1697
1648
  }
1698
- return maskMap.get(name) ?? name;
1699
1649
  }
1700
- function remapToolUseNames(messages, maskMap) {
1701
- if (!Array.isArray(messages)) {
1702
- return messages;
1650
+ function hasMeaningfulContent(content) {
1651
+ if (typeof content === "string") {
1652
+ return content.trim().length > 0;
1703
1653
  }
1704
- return messages.map((message) => {
1705
- if (!Array.isArray(message.content)) {
1706
- return message;
1654
+ if (!Array.isArray(content)) {
1655
+ return false;
1656
+ }
1657
+ return content.some((block) => {
1658
+ if (!isRecord(block)) {
1659
+ return false;
1707
1660
  }
1708
- return {
1709
- ...message,
1710
- content: message.content.map((contentBlock) => {
1711
- if (contentBlock.type !== "tool_use" || !contentBlock.name) {
1712
- return contentBlock;
1713
- }
1714
- const nextName = renameMaskedToolName(contentBlock.name, maskMap);
1715
- return nextName === contentBlock.name ? contentBlock : { ...contentBlock, name: nextName };
1716
- })
1717
- };
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;
1718
1668
  });
1719
1669
  }
1720
- function remapToolChoice(toolChoice, maskMap) {
1721
- if (!toolChoice || toolChoice.type !== "tool" || typeof toolChoice.name !== "string") {
1722
- return toolChoice;
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();
1723
1677
  }
1724
- const nextName = renameMaskedToolName(toolChoice.name, maskMap);
1725
- return nextName === toolChoice.name ? toolChoice : { ...toolChoice, name: nextName };
1726
1678
  }
1727
- function isRecord(value) {
1728
- return typeof value === "object" && value !== null;
1729
- }
1730
- function isProtectedSystemText(text) {
1731
- return text === SYSTEM_PROMPT || text === INJECTED_SYSTEM_PROMPT || text.startsWith(BILLING_HEADER_PREFIX);
1732
- }
1733
- function sanitizeSystemText(text) {
1734
- const paragraphs = text.split(/\n\n+/).map((paragraph) => paragraph.trim()).filter(Boolean).filter((paragraph) => !PARAGRAPH_REMOVAL_ANCHORS.some((anchor) => paragraph.includes(anchor)));
1735
- return paragraphs.join("\n\n").replace(OPENCODE_CAMEL_RE, "Claude Code").replace(OPENCODE_LOWER_RE, "Claude").replace(/\n{3,}/g, "\n\n").trim();
1736
- }
1737
- function normalizeSystemEntries(system) {
1679
+ function normalizeSystemTexts(system) {
1738
1680
  if (typeof system === "string") {
1739
- const text = system.trim();
1740
- return text ? [{ type: "text", text }] : [];
1681
+ const next = sanitizeAndScrubText(system);
1682
+ return next ? [next] : [];
1741
1683
  }
1742
1684
  if (!Array.isArray(system)) {
1743
1685
  return [];
1744
1686
  }
1745
- const normalized = [];
1687
+ const texts = [];
1746
1688
  for (const entry of system) {
1747
1689
  if (typeof entry === "string") {
1748
- const text = entry.trim();
1749
- if (text) {
1750
- normalized.push({ type: "text", text });
1690
+ const next = sanitizeAndScrubText(entry);
1691
+ if (next) {
1692
+ texts.push(next);
1751
1693
  }
1752
1694
  continue;
1753
1695
  }
1754
- if (!isRecord(entry)) {
1755
- continue;
1756
- }
1757
- const rawText = typeof entry.text === "string" ? entry.text.trim() : "";
1758
- if (!rawText) {
1759
- continue;
1696
+ if (isRecord(entry) && typeof entry.text === "string") {
1697
+ const next = sanitizeAndScrubText(entry.text);
1698
+ if (next) {
1699
+ texts.push(next);
1700
+ }
1760
1701
  }
1761
- normalized.push({
1762
- ...entry,
1763
- type: "text",
1764
- text: rawText
1765
- });
1766
1702
  }
1767
- return normalized;
1703
+ return texts;
1768
1704
  }
1769
- function prependToMessageContent(content, prefix) {
1770
- if (!prefix) {
1771
- return content;
1772
- }
1773
- if (typeof content === "string") {
1774
- return content ? `${prefix}
1775
-
1776
- ${content}` : prefix;
1777
- }
1778
- if (!Array.isArray(content)) {
1779
- return [{ type: "text", text: prefix }];
1780
- }
1781
- const firstTextIndex = content.findIndex(
1782
- (block) => isRecord(block) && block.type === "text" && typeof block.text === "string"
1783
- );
1784
- if (firstTextIndex === -1) {
1785
- return [{ type: "text", text: prefix }, ...content];
1786
- }
1787
- return content.map((block, index) => {
1788
- if (index !== firstTextIndex || !isRecord(block) || typeof block.text !== "string") {
1789
- return block;
1790
- }
1791
- return {
1792
- ...block,
1793
- text: block.text ? `${prefix}
1794
-
1795
- ${block.text}` : prefix
1796
- };
1797
- });
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:"));
1798
1707
  }
1799
- function relocateSystemTextToFirstUser(parsed, systemEntries) {
1800
- if (!Array.isArray(parsed.messages) || parsed.messages.length === 0) {
1801
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
1708
+ function extractFirstUserMessage(messages) {
1709
+ if (!Array.isArray(messages)) {
1710
+ return "";
1802
1711
  }
1803
- const preservedEntries = [];
1804
- const relocatedTexts = [];
1805
- for (const entry of systemEntries) {
1806
- if (isProtectedSystemText(entry.text)) {
1807
- preservedEntries.push(entry);
1712
+ for (const message of messages) {
1713
+ if (message.role !== "user") {
1808
1714
  continue;
1809
1715
  }
1810
- const sanitizedText = sanitizeSystemText(entry.text);
1811
- if (!sanitizedText) {
1812
- continue;
1716
+ if (typeof message.content === "string") {
1717
+ return message.content;
1718
+ }
1719
+ if (!Array.isArray(message.content)) {
1720
+ return "";
1813
1721
  }
1814
- relocatedTexts.push(sanitizedText);
1722
+ const text = message.content.filter((block) => typeof block.text === "string").map((block) => block.text).join("\n\n").trim();
1723
+ return text;
1815
1724
  }
1816
- if (relocatedTexts.length === 0) {
1817
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
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;
1818
1741
  }
1819
- const prefix = relocatedTexts.join("\n\n");
1820
- const nextMessages = [...parsed.messages];
1821
- const userMessageIndex = nextMessages.findIndex((message) => message.role === "user");
1822
- if (userMessageIndex === -1) {
1823
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
1742
+ return `${text.slice(0, MAX_TOOL_RESULT_TEXT_LENGTH)}${TRUNCATION_SUFFIX}`;
1743
+ }
1744
+ function getReverseName(name, reverseLookup) {
1745
+ if (!reverseLookup) {
1746
+ return name;
1824
1747
  }
1825
- const userMessage = nextMessages[userMessageIndex];
1826
- if (!userMessage) {
1827
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
1748
+ if (reverseLookup instanceof Map) {
1749
+ return reverseLookup.get(name) ?? name;
1828
1750
  }
1829
- nextMessages[userMessageIndex] = {
1830
- ...userMessage,
1831
- content: prependToMessageContent(userMessage.content, prefix)
1832
- };
1833
- parsed.messages = nextMessages;
1834
- return preservedEntries;
1751
+ return typeof reverseLookup[name] === "string" ? String(reverseLookup[name]) : name;
1835
1752
  }
1836
- function extractToolNamesFromRequestBody(body) {
1837
- if (!body) {
1838
- return [];
1753
+ function reverseMapToolUseNames(value, reverseLookup) {
1754
+ if (Array.isArray(value)) {
1755
+ return value.map((item) => reverseMapToolUseNames(item, reverseLookup));
1839
1756
  }
1840
- try {
1841
- const parsed = JSON.parse(body);
1842
- if (!Array.isArray(parsed.tools)) {
1843
- return [];
1844
- }
1845
- return parsed.tools.map((tool2) => typeof tool2.name === "string" ? tool2.name : null).filter((toolName) => Boolean(toolName));
1846
- } catch {
1847
- return [];
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);
1848
1763
  }
1764
+ if (cloned.type === "tool_use" && typeof cloned.name === "string") {
1765
+ cloned.name = getReverseName(cloned.name, reverseLookup);
1766
+ }
1767
+ return cloned;
1849
1768
  }
1850
- function stripToolPrefixFromLine(line, maskMap) {
1851
- if (!ANTHROPIC_OAUTH_ADAPTER.transform.stripToolPrefixInResponse) {
1769
+ function remapSseLine(line, reverseLookup) {
1770
+ if (!line.startsWith("data:")) {
1852
1771
  return line;
1853
1772
  }
1854
- let nextLine = line;
1855
- for (const [originalName, maskedName] of maskMap) {
1856
- nextLine = nextLine.replace(
1857
- new RegExp(`"name"\\s*:\\s*"${maskedName}"`, "g"),
1858
- `"name": "${originalName}"`
1859
- );
1773
+ const payload = line.slice(5).trimStart();
1774
+ if (payload.length === 0 || payload === "[DONE]") {
1775
+ return line;
1860
1776
  }
1861
- return nextLine;
1862
- }
1863
- function processCompleteLines(buffer, maskMap) {
1864
- const lines = buffer.split("\n");
1865
- const remaining = lines.pop() ?? "";
1866
- if (lines.length === 0) {
1867
- return { output: "", remaining };
1777
+ try {
1778
+ const parsed = JSON.parse(payload);
1779
+ return `data: ${JSON.stringify(reverseMapResponse(parsed, reverseLookup))}`;
1780
+ } catch {
1781
+ return line;
1868
1782
  }
1869
- const output = `${lines.map((line) => stripToolPrefixFromLine(line, maskMap)).join("\n")}
1870
- `;
1871
- return { output, remaining };
1872
1783
  }
1873
- function buildRequestHeaders(input, init, accessToken, modelId = "unknown", excludedBetas2) {
1874
- const headers = new Headers();
1875
- if (input instanceof Request) {
1876
- input.headers.forEach((value, key) => {
1877
- headers.set(key, value);
1878
- });
1784
+ function sanitizeMessages(body) {
1785
+ const messages = body.messages;
1786
+ if (!Array.isArray(messages)) {
1787
+ return;
1879
1788
  }
1880
- if (init?.headers) {
1881
- if (init.headers instanceof Headers) {
1882
- init.headers.forEach((value, key) => {
1883
- headers.set(key, value);
1884
- });
1885
- } else if (Array.isArray(init.headers)) {
1886
- for (const [key, value] of init.headers) {
1887
- if (value !== void 0) headers.set(key, String(value));
1888
- }
1889
- } else {
1890
- for (const [key, value] of Object.entries(init.headers)) {
1891
- if (value !== void 0) headers.set(key, String(value));
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);
1892
1803
  }
1893
1804
  }
1894
1805
  }
1895
- const incomingBetas = (headers.get("anthropic-beta") || "").split(",").map((b) => b.trim()).filter(Boolean);
1896
- const modelBetas = getModelBetas(modelId, excludedBetas2);
1897
- const mergedBetas = [.../* @__PURE__ */ new Set([
1898
- ...modelBetas,
1899
- ...incomingBetas
1900
- ])].join(",");
1901
- headers.set("authorization", `Bearer ${accessToken}`);
1902
- headers.set("anthropic-beta", mergedBetas);
1903
- headers.set("user-agent", getUserAgent());
1904
- headers.set("anthropic-dangerous-direct-browser-access", "true");
1905
- headers.set("x-app", "cli");
1906
- headers.delete("x-api-key");
1907
- return headers;
1908
- }
1909
- function transformRequestBody(body) {
1910
- if (!body) return body;
1911
- try {
1912
- const parsed = JSON.parse(body);
1913
- const toolMaskMap = buildToolMaskMap(parsed);
1914
- if (ANTHROPIC_OAUTH_ADAPTER.transform.rewriteOpenCodeBranding) {
1915
- const normalizedSystemEntries = normalizeSystemEntries(parsed.system);
1916
- parsed.system = relocateSystemTextToFirstUser(parsed, normalizedSystemEntries);
1917
- }
1918
- if (parsed.tools && Array.isArray(parsed.tools)) {
1919
- parsed.tools = parsed.tools.map((tool2) => ({
1920
- ...tool2,
1921
- name: renameMaskedToolName(tool2.name, toolMaskMap)
1922
- }));
1923
- }
1924
- parsed.messages = remapToolUseNames(parsed.messages, toolMaskMap);
1925
- parsed.tool_choice = remapToolChoice(parsed.tool_choice, toolMaskMap);
1926
- return JSON.stringify(parsed);
1927
- } catch {
1928
- return body;
1929
- }
1930
1806
  }
1931
- function extractModelIdFromBody(body) {
1932
- if (typeof body !== "string") {
1933
- return "unknown";
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
+ });
1934
1827
  }
1935
- try {
1936
- const parsed = JSON.parse(body);
1937
- return parsed.model ?? "unknown";
1938
- } catch {
1939
- return "unknown";
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
+ }
1940
1852
  }
1941
- }
1942
- function transformRequestUrl(input) {
1943
- let url = null;
1944
- try {
1945
- if (typeof input === "string" || input instanceof URL) {
1946
- url = new URL(input.toString());
1947
- } else if (input instanceof Request) {
1948
- url = new URL(input.url);
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);
1949
1903
  }
1950
- } catch {
1951
- return input;
1952
1904
  }
1953
- if (ANTHROPIC_OAUTH_ADAPTER.transform.enableMessagesBetaQuery && url && url.pathname === "/v1/messages" && !url.searchParams.has("beta")) {
1954
- url.searchParams.set("beta", "true");
1955
- return input instanceof Request ? new Request(url.toString(), input) : url;
1905
+ for (const k of Object.keys(body)) {
1906
+ if (!seen.has(k)) ordered[k] = body[k];
1956
1907
  }
1957
- return input;
1908
+ return ordered;
1909
+ }
1910
+ function reverseMapResponse(response, reverseLookup) {
1911
+ return reverseMapToolUseNames(response, reverseLookup);
1958
1912
  }
1959
- function createResponseStreamTransform(response, maskMap = /* @__PURE__ */ new Map()) {
1960
- if (!response.body) return response;
1913
+ function createStreamingReverseMapper(response, reverseLookup) {
1914
+ if (!response.body) {
1915
+ return response;
1916
+ }
1961
1917
  const reader = response.body.getReader();
1962
1918
  const decoder = new TextDecoder();
1963
1919
  const encoder = new TextEncoder();
@@ -1970,24 +1926,26 @@ function createResponseStreamTransform(response, maskMap = /* @__PURE__ */ new M
1970
1926
  if (done) {
1971
1927
  buffer += decoder.decode();
1972
1928
  if (buffer) {
1973
- controller.enqueue(encoder.encode(stripToolPrefixFromLine(buffer, maskMap)));
1929
+ const lines2 = buffer.split("\n").map((line) => remapSseLine(line, reverseLookup));
1930
+ controller.enqueue(encoder.encode(lines2.join("\n")));
1974
1931
  buffer = "";
1975
1932
  }
1976
1933
  controller.close();
1977
1934
  return;
1978
1935
  }
1979
1936
  buffer += decoder.decode(value, { stream: true });
1980
- const { output, remaining } = processCompleteLines(buffer, maskMap);
1981
- buffer = remaining;
1982
- if (output) {
1983
- controller.enqueue(encoder.encode(output));
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));
1984
1943
  return;
1985
1944
  }
1986
1945
  }
1987
1946
  } catch (error) {
1988
1947
  try {
1989
- reader.cancel().catch(() => {
1990
- });
1948
+ await reader.cancel();
1991
1949
  } catch {
1992
1950
  }
1993
1951
  controller.error(error);
@@ -2004,26 +1962,142 @@ function createResponseStreamTransform(response, maskMap = /* @__PURE__ */ new M
2004
1962
  });
2005
1963
  }
2006
1964
 
2007
- // src/proactive-refresh.ts
2008
- import { createProactiveRefreshQueueForProvider } from "opencode-multi-account-core";
2009
- var ProactiveRefreshQueue = createProactiveRefreshQueueForProvider({
2010
- providerAuthId: "anthropic",
2011
- getConfig,
2012
- isTokenExpired,
2013
- refreshToken,
2014
- debugLog
2015
- });
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
+ }
2016
2049
 
2017
- // src/runtime-factory.ts
2018
- import { TokenRefreshError } from "opencode-multi-account-core";
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
+ }
2019
2093
 
2020
2094
  // src/tool-observation.ts
2021
2095
  import { promises as fs } from "fs";
2022
- import { dirname, join } from "path";
2096
+ import { dirname, join as join2 } from "path";
2023
2097
  var OBSERVED_TOOL_FILE = "anthropic-observed-tools.json";
2024
2098
  var FILE_MODE = 384;
2025
2099
  function getObservedToolPath() {
2026
- return join(getConfigDir(), OBSERVED_TOOL_FILE);
2100
+ return join2(getConfigDir(), OBSERVED_TOOL_FILE);
2027
2101
  }
2028
2102
  async function loadObservedToolInventory() {
2029
2103
  try {
@@ -2047,34 +2121,194 @@ async function recordObservedToolNames(toolNames) {
2047
2121
  return;
2048
2122
  }
2049
2123
  const inventory = await loadObservedToolInventory();
2050
- const now = (/* @__PURE__ */ new Date()).toISOString();
2124
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
2051
2125
  for (const toolName of toolNames) {
2052
2126
  const entry = inventory.observedTools[toolName];
2053
2127
  if (!entry) {
2054
2128
  inventory.observedTools[toolName] = {
2055
2129
  count: 1,
2056
- firstSeenAt: now,
2057
- lastSeenAt: now
2130
+ firstSeenAt: now2,
2131
+ lastSeenAt: now2
2058
2132
  };
2059
2133
  continue;
2060
2134
  }
2061
2135
  entry.count += 1;
2062
- entry.lastSeenAt = now;
2136
+ entry.lastSeenAt = now2;
2063
2137
  }
2064
2138
  await saveObservedToolInventory(inventory);
2065
2139
  }
2066
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
+ }
2209
+
2067
2210
  // src/runtime-factory.ts
2068
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
+ }
2069
2290
  var AccountRuntimeFactory = class {
2070
- constructor(store, client) {
2291
+ constructor(store, client, identity = loadClaudeIdentity()) {
2071
2292
  this.store = store;
2072
2293
  this.client = client;
2294
+ this.identity = identity;
2073
2295
  }
2074
2296
  store;
2075
2297
  client;
2298
+ identity;
2076
2299
  runtimes = /* @__PURE__ */ new Map();
2077
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
+ }
2078
2312
  async getRuntime(uuid) {
2079
2313
  const cached = this.runtimes.get(uuid);
2080
2314
  if (cached) return cached;
@@ -2126,22 +2360,77 @@ var AccountRuntimeFactory = class {
2126
2360
  });
2127
2361
  return { accessToken: refreshed.patch.accessToken, expiresAt: refreshed.patch.expiresAt };
2128
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
+ }
2129
2379
  async executeTransformedFetch(input, init, accessToken) {
2130
2380
  const transformedInput = transformRequestUrl(input);
2131
2381
  const modelId = extractModelIdFromBody(init?.body);
2132
2382
  const excludedBetas2 = getExcludedBetas(modelId);
2133
- const headers = buildRequestHeaders(transformedInput, init, accessToken, modelId, excludedBetas2);
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
+ );
2134
2392
  if (typeof init?.body === "string") {
2135
2393
  void recordObservedToolNames(extractToolNamesFromRequestBody(init.body)).catch(() => {
2136
2394
  });
2137
2395
  }
2138
- const toolMaskMap = typeof init?.body === "string" ? extractRequestToolMaskMap(init.body) : /* @__PURE__ */ new Map();
2139
- const transformedBody = typeof init?.body === "string" ? transformRequestBody(init.body) : init?.body;
2140
- let response = await fetch(transformedInput, {
2141
- ...init,
2142
- headers,
2143
- body: transformedBody
2144
- });
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);
2145
2434
  for (let attempt = 0; attempt < LONG_CONTEXT_BETAS.length; attempt += 1) {
2146
2435
  if (response.status !== 400 && response.status !== 429) {
2147
2436
  break;
@@ -2155,20 +2444,16 @@ var AccountRuntimeFactory = class {
2155
2444
  break;
2156
2445
  }
2157
2446
  addExcludedBeta(modelId, betaToExclude);
2158
- const retryHeaders = buildRequestHeaders(
2159
- transformedInput,
2160
- init,
2447
+ const retryHeaders = this.buildOutboundHeaders(
2448
+ incomingHeaders,
2449
+ sessionId2,
2161
2450
  accessToken,
2162
2451
  modelId,
2163
2452
  getExcludedBetas(modelId)
2164
2453
  );
2165
- response = await fetch(transformedInput, {
2166
- ...init,
2167
- headers: retryHeaders,
2168
- body: transformedBody
2169
- });
2454
+ response = await performFetch(retryHeaders);
2170
2455
  }
2171
- return createResponseStreamTransform(response, toolMaskMap);
2456
+ return createStreamingReverseMapper(response);
2172
2457
  }
2173
2458
  async createRuntime(uuid) {
2174
2459
  const fetchWithAccount = async (input, init) => {
@@ -2194,7 +2479,7 @@ var AccountRuntimeFactory = class {
2194
2479
 
2195
2480
  // src/bootstrap-auth.ts
2196
2481
  import { promises as fs2 } from "fs";
2197
- import { join as join2 } from "path";
2482
+ import { join as join3 } from "path";
2198
2483
  import { getConfigDir as getConfigDir2 } from "opencode-multi-account-core";
2199
2484
  var AUTH_JSON_FILENAME = "auth.json";
2200
2485
  function hasCompleteOAuthCredential(account) {
@@ -2215,7 +2500,7 @@ function selectBootstrapAccount(accounts, activeAccountUuid) {
2215
2500
  return firstUsableAccount ?? completeAccounts[0] ?? null;
2216
2501
  }
2217
2502
  async function readCurrentAuth(providerId) {
2218
- const authPath = join2(getConfigDir2(), AUTH_JSON_FILENAME);
2503
+ const authPath = join3(getConfigDir2(), AUTH_JSON_FILENAME);
2219
2504
  let raw;
2220
2505
  try {
2221
2506
  raw = await fs2.readFile(authPath, "utf-8");
@@ -2270,6 +2555,70 @@ async function syncBootstrapAuth(client, store) {
2270
2555
  return true;
2271
2556
  }
2272
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
+
2273
2622
  // src/index.ts
2274
2623
  var EMPTY_OAUTH_CREDENTIALS = {
2275
2624
  type: "oauth",
@@ -2277,7 +2626,7 @@ var EMPTY_OAUTH_CREDENTIALS = {
2277
2626
  access: "",
2278
2627
  expires: 0
2279
2628
  };
2280
- function extractFirstUserText2(input) {
2629
+ function extractFirstUserText(input) {
2281
2630
  try {
2282
2631
  const raw = input;
2283
2632
  const messages = raw.messages ?? raw.request?.messages;
@@ -2295,19 +2644,118 @@ function extractFirstUserText2(input) {
2295
2644
  }
2296
2645
  return "";
2297
2646
  }
2298
- function injectSystemPrompt(output) {
2299
- const systemPrompt = getInjectedSystemPrompt();
2300
- if (!Array.isArray(output.system)) {
2301
- output.system = [systemPrompt];
2302
- return;
2303
- }
2304
- if (!output.system.includes(systemPrompt)) {
2305
- output.system.unshift(systemPrompt);
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
+ }
2306
2657
  }
2307
2658
  }
2659
+ function applyOrderedHeaders(output, headers) {
2660
+ const orderedHeaders = orderHeadersForOutbound(headers);
2661
+ output.headers = Array.isArray(orderedHeaders) ? Object.fromEntries(orderedHeaders) : orderedHeaders;
2662
+ }
2308
2663
  var ClaudeMultiAuthPlugin = async (ctx) => {
2309
2664
  const { client } = ctx;
2310
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
+ });
2311
2759
  const store = new AccountStore();
2312
2760
  await syncBootstrapAuth(client, store).catch(() => {
2313
2761
  });
@@ -2318,7 +2766,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2318
2766
  let cascadeStateManager = null;
2319
2767
  let poolChainConfig = { pools: [], chains: [] };
2320
2768
  async function ensureExecutionInfrastructure() {
2321
- runtimeFactory ??= new AccountRuntimeFactory(store, client);
2769
+ runtimeFactory ??= new AccountRuntimeFactory(store, client, claudeIdentity);
2322
2770
  poolChainConfig = await loadPoolChainConfig();
2323
2771
  poolManager ??= new PoolManager();
2324
2772
  poolManager.loadPools(poolChainConfig.pools);
@@ -2357,6 +2805,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2357
2805
  manager = await AccountManager.create(store, EMPTY_OAUTH_CREDENTIALS, client);
2358
2806
  await ensureExecutionInfrastructure();
2359
2807
  await startRefreshQueueIfNeeded();
2808
+ ensureHeartbeat(manager.getActiveAccount()?.accessToken);
2360
2809
  return manager.getAccountCount() > 0;
2361
2810
  }
2362
2811
  async function initializeManagerFromAuth(credentials) {
@@ -2370,11 +2819,12 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2370
2819
  });
2371
2820
  return {
2372
2821
  "experimental.chat.system.transform": (input, output) => {
2373
- injectSystemPrompt(output);
2374
- const billingHeader = buildBillingHeader(extractFirstUserText2(input));
2375
- if (billingHeader && !output.system?.includes(billingHeader)) {
2376
- output.system?.unshift(billingHeader);
2377
- }
2822
+ const billingHeader = composeBillingSystemEntry(extractFirstUserText(input), claudeCodeVersion);
2823
+ prependMissingSystemEntries(output, [
2824
+ billingHeader,
2825
+ upstreamAgentIdentity,
2826
+ upstreamSystemPrompt
2827
+ ]);
2378
2828
  },
2379
2829
  tool: {
2380
2830
  [ANTHROPIC_OAUTH_ADAPTER.statusToolName]: tool({
@@ -2412,10 +2862,10 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2412
2862
  }
2413
2863
  }
2414
2864
  if (account.cachedUsage) {
2415
- const now = Date.now();
2865
+ const now2 = Date.now();
2416
2866
  const usage2 = account.cachedUsage;
2417
2867
  const exhaustedTiers = [usage2.five_hour, usage2.seven_day].filter(
2418
- (tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now
2868
+ (tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now2
2419
2869
  );
2420
2870
  if (exhaustedTiers.length > 0) {
2421
2871
  statusParts.push("USAGE EXHAUSTED");
@@ -2446,6 +2896,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2446
2896
  async loader(getAuth, provider) {
2447
2897
  const auth = await getAuth();
2448
2898
  if (auth.type !== "oauth") {
2899
+ stopHeartbeat();
2449
2900
  return { apiKey: "", fetch };
2450
2901
  }
2451
2902
  for (const model of Object.values(provider.models ?? {})) {
@@ -2456,6 +2907,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2456
2907
  const credentials = auth;
2457
2908
  await migrateFromAuthJson("anthropic", store);
2458
2909
  await initializeManagerFromAuth(credentials);
2910
+ ensureHeartbeat(credentials.access);
2459
2911
  const initializedManager = manager;
2460
2912
  if (!initializedManager) {
2461
2913
  return { apiKey: "", fetch };
@@ -2478,24 +2930,32 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2478
2930
  );
2479
2931
  }
2480
2932
  }
2933
+ const authProfile = await loadCCDerivedAuthProfile();
2481
2934
  return {
2482
2935
  apiKey: "",
2483
- baseURL: "https://api.anthropic.com/v1",
2936
+ baseURL: authProfile.apiV1BaseUrl,
2484
2937
  "chat.headers": async (input, output) => {
2485
2938
  if (input.provider?.info?.id !== ANTHROPIC_OAUTH_ADAPTER.authProviderId) return;
2486
- output.headers["user-agent"] = getUserAgent();
2487
- output.headers["anthropic-beta"] = ANTHROPIC_BETA_HEADER;
2488
- output.headers["x-app"] = "cli";
2939
+ const sessionId2 = getUpstreamSessionId();
2940
+ applyOrderedHeaders(output, {
2941
+ ...output.headers,
2942
+ ...getStaticHeaders(),
2943
+ ...getPerRequestHeaders(sessionId2),
2944
+ "anthropic-beta": getBetaHeader()
2945
+ });
2489
2946
  },
2490
2947
  async fetch(input, init) {
2491
2948
  if (!initializedManager || !runtimeFactory) {
2949
+ stopHeartbeat();
2492
2950
  return fetch(input, init);
2493
2951
  }
2494
2952
  if (initializedManager.getAccountCount() === 0) {
2953
+ stopHeartbeat();
2495
2954
  throw new Error(
2496
2955
  "No Anthropic accounts configured. Run `opencode auth login` to add an account."
2497
2956
  );
2498
2957
  }
2958
+ ensureHeartbeat(initializedManager.getActiveAccount()?.accessToken);
2499
2959
  if (!poolManager || !cascadeStateManager) {
2500
2960
  poolManager = new PoolManager();
2501
2961
  poolManager.loadPools(poolChainConfig.pools);