opencode-anthropic-multi-account 0.2.14 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,430 +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_PREFIX_RESPONSE_RE = /"name"\s*:\s*"mcp_([^"]+)"/g;
1564
- var PARAGRAPH_REMOVAL_ANCHORS = [
1565
- "github.com/anomalyco/opencode",
1566
- "opencode.ai/docs"
1567
- ];
1568
- var BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
1569
- function addToolPrefix(name) {
1570
- if (!ANTHROPIC_OAUTH_ADAPTER.transform.addToolPrefix) {
1571
- return name;
1474
+ // src/claude-identity.ts
1475
+ import { readFileSync, readdirSync } from "fs";
1476
+ import { homedir } from "os";
1477
+ import { join } from "path";
1478
+ var EMPTY_IDENTITY = {
1479
+ deviceId: "",
1480
+ accountUuid: ""
1481
+ };
1482
+ var testOverrideIdentity = null;
1483
+ function getCandidatePaths() {
1484
+ const home = homedir();
1485
+ const paths = [
1486
+ join(home, ".claude.json"),
1487
+ join(home, ".claude", ".claude.json"),
1488
+ join(home, ".claude", "claude.json")
1489
+ ];
1490
+ try {
1491
+ const backupDir = join(home, ".claude", "backups");
1492
+ const backups = readdirSync(backupDir).filter((file) => file.startsWith(".claude.json.backup.")).sort().reverse();
1493
+ for (const backup of backups) {
1494
+ paths.push(join(backupDir, backup));
1495
+ }
1496
+ } catch {
1572
1497
  }
1573
- if (!name || name.startsWith(TOOL_PREFIX)) {
1574
- return name;
1498
+ return paths;
1499
+ }
1500
+ function parseIdentityFile(path) {
1501
+ try {
1502
+ const data = JSON.parse(readFileSync(path, "utf-8"));
1503
+ if (!data.userID) {
1504
+ return null;
1505
+ }
1506
+ return {
1507
+ deviceId: data.userID,
1508
+ accountUuid: data.oauthAccount?.accountUuid ?? data.accountUuid ?? ""
1509
+ };
1510
+ } catch {
1511
+ return null;
1512
+ }
1513
+ }
1514
+ function loadClaudeIdentity() {
1515
+ if (testOverrideIdentity) {
1516
+ return testOverrideIdentity;
1517
+ }
1518
+ try {
1519
+ for (const path of getCandidatePaths()) {
1520
+ const identity = parseIdentityFile(path);
1521
+ if (identity) {
1522
+ return identity;
1523
+ }
1524
+ }
1525
+ } catch {
1526
+ }
1527
+ return EMPTY_IDENTITY;
1528
+ }
1529
+
1530
+ // src/upstream-request.ts
1531
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
1532
+ var BILLING_SEED = "59cf53e54c78";
1533
+ var DEFAULT_CC_VERSION = "2.1.100";
1534
+ var SESSION_IDLE_ROTATE_MS = 15 * 60 * 1e3;
1535
+ var MAX_TOOL_RESULT_TEXT_LENGTH = 30 * 1024;
1536
+ var TRUNCATION_SUFFIX = "[...truncated]";
1537
+ var DEFAULT_CONTEXT_MANAGEMENT = {};
1538
+ var DEFAULT_OUTPUT_CONFIG = {};
1539
+ var ORCHESTRATION_TAG_NAMES = [
1540
+ "system-reminder",
1541
+ "env",
1542
+ "system_information",
1543
+ "current_working_directory",
1544
+ "operating_system",
1545
+ "default_shell",
1546
+ "home_directory",
1547
+ "task_metadata",
1548
+ "directories",
1549
+ "thinking",
1550
+ "agent_persona",
1551
+ "agent_context",
1552
+ "tool_context",
1553
+ "persona",
1554
+ "tool_call"
1555
+ ];
1556
+ var ORCHESTRATION_PATTERNS = ORCHESTRATION_TAG_NAMES.flatMap((tag) => [
1557
+ new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi"),
1558
+ new RegExp(`<${tag}\\b[^>]*/>`, "gi")
1559
+ ]);
1560
+ var FRAMEWORK_PATTERNS = [
1561
+ /\b(roo[- ]?cline|roo[- ]?code|big[- ]?agi|claude[- ]?bridge|amazon\s+q)\b/gi,
1562
+ /\b(openclaw|hermes|aider|cursor|windsurf|cline|continue|copilot|cody)\b/gi,
1563
+ /\b(zed|plandex|tabby|opencode|daytona)\b/gi,
1564
+ /\b(librechat|typingmind)\b/gi,
1565
+ /\b(openai|gpt-4|gpt-3\.5)\b/gi,
1566
+ /powered by [a-z]+/gi,
1567
+ /\bgateway\b/gi,
1568
+ /\bsessions_[a-z_]+\b/gi
1569
+ ];
1570
+ var upstreamRequestTestOverrides = {};
1571
+ var sessionId = randomUUID2();
1572
+ var sessionLastUsed = 0;
1573
+ function now() {
1574
+ return upstreamRequestTestOverrides.now?.() ?? Date.now();
1575
+ }
1576
+ function createSessionId() {
1577
+ return upstreamRequestTestOverrides.createSessionId?.() ?? randomUUID2();
1578
+ }
1579
+ function getActiveSessionId() {
1580
+ const currentTime = now();
1581
+ if (sessionLastUsed === 0 || currentTime - sessionLastUsed > SESSION_IDLE_ROTATE_MS) {
1582
+ sessionId = createSessionId();
1575
1583
  }
1576
- return `${TOOL_PREFIX}${name}`;
1584
+ sessionLastUsed = currentTime;
1585
+ return sessionId;
1586
+ }
1587
+ function getUpstreamSessionId() {
1588
+ return getActiveSessionId();
1577
1589
  }
1578
1590
  function isRecord(value) {
1579
1591
  return typeof value === "object" && value !== null;
1580
1592
  }
1581
- function isProtectedSystemText(text) {
1582
- return text === SYSTEM_PROMPT || text === INJECTED_SYSTEM_PROMPT || text.startsWith(BILLING_HEADER_PREFIX);
1593
+ function cloneBody(value) {
1594
+ return structuredClone(value);
1583
1595
  }
1584
- function sanitizeSystemText(text) {
1585
- const paragraphs = text.split(/\n\n+/).map((paragraph) => paragraph.trim()).filter(Boolean).filter((paragraph) => !PARAGRAPH_REMOVAL_ANCHORS.some((anchor) => paragraph.includes(anchor)));
1586
- return paragraphs.join("\n\n").replace(OPENCODE_CAMEL_RE, "Claude Code").replace(OPENCODE_LOWER_RE, "Claude").replace(/\n{3,}/g, "\n\n").trim();
1596
+ function sanitizeContent(text) {
1597
+ let result = text;
1598
+ for (const pattern of ORCHESTRATION_PATTERNS) {
1599
+ pattern.lastIndex = 0;
1600
+ result = result.replace(pattern, "");
1601
+ }
1602
+ return result.replace(/\n{3,}/g, "\n\n").trim();
1587
1603
  }
1588
- function normalizeSystemEntries(system) {
1589
- if (typeof system === "string") {
1590
- const text = system.trim();
1591
- return text ? [{ type: "text", text }] : [];
1604
+ function sanitizeAndScrubText(text) {
1605
+ return scrubFrameworkIdentifiers(sanitizeContent(text)).replace(/\n{3,}/g, "\n\n").trim();
1606
+ }
1607
+ function stripCacheControl(value) {
1608
+ if (Array.isArray(value)) {
1609
+ for (const item of value) {
1610
+ stripCacheControl(item);
1611
+ }
1612
+ return;
1592
1613
  }
1593
- if (!Array.isArray(system)) {
1594
- return [];
1614
+ if (!isRecord(value)) {
1615
+ return;
1595
1616
  }
1596
- const normalized = [];
1597
- for (const entry of system) {
1598
- if (typeof entry === "string") {
1599
- const text = entry.trim();
1600
- if (text) {
1601
- normalized.push({ type: "text", text });
1602
- }
1603
- continue;
1604
- }
1605
- if (!isRecord(entry)) {
1606
- continue;
1607
- }
1608
- const rawText = typeof entry.text === "string" ? entry.text.trim() : "";
1609
- if (!rawText) {
1610
- continue;
1611
- }
1612
- normalized.push({
1613
- ...entry,
1614
- type: "text",
1615
- text: rawText
1616
- });
1617
+ delete value.cache_control;
1618
+ for (const nested of Object.values(value)) {
1619
+ stripCacheControl(nested);
1617
1620
  }
1618
- return normalized;
1619
1621
  }
1620
- function prependToMessageContent(content, prefix) {
1621
- if (!prefix) {
1622
- return content;
1622
+ function sanitizeMessageBlock(block) {
1623
+ if (typeof block.text === "string") {
1624
+ block.text = sanitizeAndScrubText(block.text);
1623
1625
  }
1624
- if (typeof content === "string") {
1625
- return content ? `${prefix}
1626
-
1627
- ${content}` : prefix;
1626
+ if (block.type !== "tool_result") {
1627
+ return;
1628
1628
  }
1629
- if (!Array.isArray(content)) {
1630
- return [{ type: "text", text: prefix }];
1629
+ if (typeof block.content === "string") {
1630
+ block.content = truncateToolResultText(sanitizeAndScrubText(block.content));
1631
+ return;
1631
1632
  }
1632
- const firstTextIndex = content.findIndex(
1633
- (block) => isRecord(block) && block.type === "text" && typeof block.text === "string"
1634
- );
1635
- if (firstTextIndex === -1) {
1636
- return [{ type: "text", text: prefix }, ...content];
1633
+ if (!Array.isArray(block.content)) {
1634
+ return;
1637
1635
  }
1638
- return content.map((block, index) => {
1639
- if (index !== firstTextIndex || !isRecord(block) || typeof block.text !== "string") {
1640
- return block;
1636
+ for (const item of block.content) {
1637
+ if (isRecord(item) && typeof item.text === "string") {
1638
+ item.text = truncateToolResultText(sanitizeAndScrubText(item.text));
1641
1639
  }
1642
- return {
1643
- ...block,
1644
- text: block.text ? `${prefix}
1645
-
1646
- ${block.text}` : prefix
1647
- };
1648
- });
1649
- }
1650
- function relocateSystemTextToFirstUser(parsed, systemEntries) {
1651
- if (!Array.isArray(parsed.messages) || parsed.messages.length === 0) {
1652
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
1653
1640
  }
1654
- const preservedEntries = [];
1655
- const relocatedTexts = [];
1656
- for (const entry of systemEntries) {
1657
- if (isProtectedSystemText(entry.text)) {
1658
- preservedEntries.push(entry);
1659
- continue;
1660
- }
1661
- const sanitizedText = sanitizeSystemText(entry.text);
1662
- if (!sanitizedText) {
1641
+ }
1642
+ function stripAssistantThinkingBlocks(messages) {
1643
+ for (const message of messages) {
1644
+ if (message.role !== "assistant" || !Array.isArray(message.content)) {
1663
1645
  continue;
1664
1646
  }
1665
- relocatedTexts.push(sanitizedText);
1647
+ message.content = message.content.filter((block) => block.type !== "thinking");
1666
1648
  }
1667
- if (relocatedTexts.length === 0) {
1668
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
1649
+ }
1650
+ function hasMeaningfulContent(content) {
1651
+ if (typeof content === "string") {
1652
+ return content.trim().length > 0;
1669
1653
  }
1670
- const prefix = relocatedTexts.join("\n\n");
1671
- const nextMessages = [...parsed.messages];
1672
- const userMessageIndex = nextMessages.findIndex((message) => message.role === "user");
1673
- if (userMessageIndex === -1) {
1674
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
1675
- }
1676
- const userMessage = nextMessages[userMessageIndex];
1677
- if (!userMessage) {
1678
- return systemEntries.map((entry) => isProtectedSystemText(entry.text) ? entry : { ...entry, text: sanitizeSystemText(entry.text) }).filter((entry) => entry.text);
1654
+ if (!Array.isArray(content)) {
1655
+ return false;
1679
1656
  }
1680
- nextMessages[userMessageIndex] = {
1681
- ...userMessage,
1682
- content: prependToMessageContent(userMessage.content, prefix)
1683
- };
1684
- parsed.messages = nextMessages;
1685
- return preservedEntries;
1657
+ return content.some((block) => {
1658
+ if (!isRecord(block)) {
1659
+ return false;
1660
+ }
1661
+ if (typeof block.text === "string" && block.text.trim().length > 0) {
1662
+ return true;
1663
+ }
1664
+ if (typeof block.content === "string" && block.content.trim().length > 0) {
1665
+ return true;
1666
+ }
1667
+ return false;
1668
+ });
1686
1669
  }
1687
- function stripToolPrefixFromLine(line) {
1688
- if (!ANTHROPIC_OAUTH_ADAPTER.transform.stripToolPrefixInResponse) {
1689
- return line;
1670
+ function trimTrailingEmptyTurns(messages) {
1671
+ while (messages.length > 0) {
1672
+ const lastMessage = messages[messages.length - 1];
1673
+ if (!lastMessage || hasMeaningfulContent(lastMessage.content)) {
1674
+ return;
1675
+ }
1676
+ messages.pop();
1690
1677
  }
1691
- return line.replace(TOOL_PREFIX_RESPONSE_RE, '"name": "$1"');
1692
1678
  }
1693
- function processCompleteLines(buffer) {
1694
- const lines = buffer.split("\n");
1695
- const remaining = lines.pop() ?? "";
1696
- if (lines.length === 0) {
1697
- return { output: "", remaining };
1679
+ function normalizeSystemTexts(system) {
1680
+ if (typeof system === "string") {
1681
+ const next = sanitizeAndScrubText(system);
1682
+ return next ? [next] : [];
1698
1683
  }
1699
- const output = `${lines.map(stripToolPrefixFromLine).join("\n")}
1700
- `;
1701
- return { output, remaining };
1702
- }
1703
- function buildRequestHeaders(input, init, accessToken, modelId = "unknown", excludedBetas2) {
1704
- const headers = new Headers();
1705
- if (input instanceof Request) {
1706
- input.headers.forEach((value, key) => {
1707
- headers.set(key, value);
1708
- });
1684
+ if (!Array.isArray(system)) {
1685
+ return [];
1709
1686
  }
1710
- if (init?.headers) {
1711
- if (init.headers instanceof Headers) {
1712
- init.headers.forEach((value, key) => {
1713
- headers.set(key, value);
1714
- });
1715
- } else if (Array.isArray(init.headers)) {
1716
- for (const [key, value] of init.headers) {
1717
- if (value !== void 0) headers.set(key, String(value));
1687
+ const texts = [];
1688
+ for (const entry of system) {
1689
+ if (typeof entry === "string") {
1690
+ const next = sanitizeAndScrubText(entry);
1691
+ if (next) {
1692
+ texts.push(next);
1718
1693
  }
1719
- } else {
1720
- for (const [key, value] of Object.entries(init.headers)) {
1721
- if (value !== void 0) headers.set(key, String(value));
1694
+ continue;
1695
+ }
1696
+ if (isRecord(entry) && typeof entry.text === "string") {
1697
+ const next = sanitizeAndScrubText(entry.text);
1698
+ if (next) {
1699
+ texts.push(next);
1722
1700
  }
1723
1701
  }
1724
1702
  }
1725
- const incomingBetas = (headers.get("anthropic-beta") || "").split(",").map((b) => b.trim()).filter(Boolean);
1726
- const modelBetas = getModelBetas(modelId, excludedBetas2);
1727
- const mergedBetas = [.../* @__PURE__ */ new Set([
1728
- ...modelBetas,
1729
- ...incomingBetas
1730
- ])].join(",");
1731
- headers.set("authorization", `Bearer ${accessToken}`);
1732
- headers.set("anthropic-beta", mergedBetas);
1733
- headers.set("user-agent", getUserAgent());
1734
- headers.set("anthropic-dangerous-direct-browser-access", "true");
1735
- headers.set("x-app", "cli");
1736
- headers.delete("x-api-key");
1737
- return headers;
1703
+ return texts;
1738
1704
  }
1739
- function transformRequestBody(body) {
1740
- if (!body) return body;
1741
- try {
1742
- const parsed = JSON.parse(body);
1743
- if (ANTHROPIC_OAUTH_ADAPTER.transform.rewriteOpenCodeBranding) {
1744
- const normalizedSystemEntries = normalizeSystemEntries(parsed.system);
1745
- parsed.system = relocateSystemTextToFirstUser(parsed, normalizedSystemEntries);
1746
- }
1747
- if (parsed.tools && Array.isArray(parsed.tools)) {
1748
- parsed.tools = parsed.tools.map((tool2) => ({
1749
- ...tool2,
1750
- name: addToolPrefix(tool2.name)
1751
- }));
1752
- }
1753
- if (parsed.messages && Array.isArray(parsed.messages)) {
1754
- parsed.messages = parsed.messages.map((message) => {
1755
- if (message.content && Array.isArray(message.content)) {
1756
- message.content = message.content.map((contentBlock) => {
1757
- if (contentBlock.type === "tool_use" && contentBlock.name) {
1758
- return { ...contentBlock, name: addToolPrefix(contentBlock.name) };
1759
- }
1760
- return contentBlock;
1761
- });
1762
- }
1763
- return message;
1764
- });
1705
+ function filterInjectedSystemTexts(systemTexts, template, billingHeader) {
1706
+ return systemTexts.filter((entry) => entry !== billingHeader && entry !== template.agent_identity && entry !== template.system_prompt && !entry.startsWith("x-anthropic-billing-header:"));
1707
+ }
1708
+ function extractFirstUserMessage(messages) {
1709
+ if (!Array.isArray(messages)) {
1710
+ return "";
1711
+ }
1712
+ for (const message of messages) {
1713
+ if (message.role !== "user") {
1714
+ continue;
1765
1715
  }
1766
- return JSON.stringify(parsed);
1767
- } catch {
1768
- return body;
1716
+ if (typeof message.content === "string") {
1717
+ return message.content;
1718
+ }
1719
+ if (!Array.isArray(message.content)) {
1720
+ return "";
1721
+ }
1722
+ const text = message.content.filter((block) => typeof block.text === "string").map((block) => block.text).join("\n\n").trim();
1723
+ return text;
1724
+ }
1725
+ return "";
1726
+ }
1727
+ function hasCompleteToolSchemas(tools) {
1728
+ return tools.length > 0 && tools.every((tool2) => typeof tool2 === "object" && tool2 !== null && "input_schema" in tool2);
1729
+ }
1730
+ function getCcVersion(template) {
1731
+ return template.cc_version ?? DEFAULT_CC_VERSION;
1732
+ }
1733
+ function buildBillingHeader(firstUserMessage, template) {
1734
+ const version = getCcVersion(template);
1735
+ const buildTag = computeBuildTag(firstUserMessage, version);
1736
+ return `x-anthropic-billing-header: cc_version=${version}.${buildTag}; cc_entrypoint=cli; cch=00000;`;
1737
+ }
1738
+ function truncateToolResultText(text) {
1739
+ if (text.length <= MAX_TOOL_RESULT_TEXT_LENGTH) {
1740
+ return text;
1769
1741
  }
1742
+ return `${text.slice(0, MAX_TOOL_RESULT_TEXT_LENGTH)}${TRUNCATION_SUFFIX}`;
1770
1743
  }
1771
- function extractModelIdFromBody(body) {
1772
- if (typeof body !== "string") {
1773
- return "unknown";
1744
+ function getReverseName(name, reverseLookup) {
1745
+ if (!reverseLookup) {
1746
+ return name;
1747
+ }
1748
+ if (reverseLookup instanceof Map) {
1749
+ return reverseLookup.get(name) ?? name;
1750
+ }
1751
+ return typeof reverseLookup[name] === "string" ? String(reverseLookup[name]) : name;
1752
+ }
1753
+ function reverseMapToolUseNames(value, reverseLookup) {
1754
+ if (Array.isArray(value)) {
1755
+ return value.map((item) => reverseMapToolUseNames(item, reverseLookup));
1756
+ }
1757
+ if (!isRecord(value)) {
1758
+ return value;
1759
+ }
1760
+ const cloned = {};
1761
+ for (const [key, nested] of Object.entries(value)) {
1762
+ cloned[key] = reverseMapToolUseNames(nested, reverseLookup);
1763
+ }
1764
+ if (cloned.type === "tool_use" && typeof cloned.name === "string") {
1765
+ cloned.name = getReverseName(cloned.name, reverseLookup);
1766
+ }
1767
+ return cloned;
1768
+ }
1769
+ function remapSseLine(line, reverseLookup) {
1770
+ if (!line.startsWith("data:")) {
1771
+ return line;
1772
+ }
1773
+ const payload = line.slice(5).trimStart();
1774
+ if (payload.length === 0 || payload === "[DONE]") {
1775
+ return line;
1774
1776
  }
1775
1777
  try {
1776
- const parsed = JSON.parse(body);
1777
- return parsed.model ?? "unknown";
1778
+ const parsed = JSON.parse(payload);
1779
+ return `data: ${JSON.stringify(reverseMapResponse(parsed, reverseLookup))}`;
1778
1780
  } catch {
1779
- return "unknown";
1781
+ return line;
1780
1782
  }
1781
1783
  }
1782
- function transformRequestUrl(input) {
1783
- let url = null;
1784
- try {
1785
- if (typeof input === "string" || input instanceof URL) {
1786
- url = new URL(input.toString());
1787
- } else if (input instanceof Request) {
1788
- url = new URL(input.url);
1784
+ function sanitizeMessages(body) {
1785
+ const messages = body.messages;
1786
+ if (!Array.isArray(messages)) {
1787
+ return;
1788
+ }
1789
+ for (const message of messages) {
1790
+ if (!isRecord(message)) {
1791
+ continue;
1792
+ }
1793
+ if (typeof message.content === "string") {
1794
+ message.content = sanitizeContent(message.content);
1795
+ continue;
1796
+ }
1797
+ if (!Array.isArray(message.content)) {
1798
+ continue;
1799
+ }
1800
+ for (const block of message.content) {
1801
+ if (isRecord(block) && typeof block.text === "string") {
1802
+ block.text = sanitizeContent(block.text);
1803
+ }
1789
1804
  }
1790
- } catch {
1791
- return input;
1792
1805
  }
1793
- if (ANTHROPIC_OAUTH_ADAPTER.transform.enableMessagesBetaQuery && url && url.pathname === "/v1/messages" && !url.searchParams.has("beta")) {
1794
- url.searchParams.set("beta", "true");
1795
- return input instanceof Request ? new Request(url.toString(), input) : url;
1806
+ }
1807
+ function scrubFrameworkIdentifiers(text) {
1808
+ let result = text;
1809
+ for (const pattern of FRAMEWORK_PATTERNS) {
1810
+ pattern.lastIndex = 0;
1811
+ result = result.replace(pattern, (match, ...args) => {
1812
+ const offset = args.at(-2);
1813
+ const source = args.at(-1);
1814
+ if (typeof offset !== "number" || typeof source !== "string") {
1815
+ return match;
1816
+ }
1817
+ const before = offset > 0 ? source[offset - 1] ?? "" : "";
1818
+ const after = offset + match.length < source.length ? source[offset + match.length] ?? "" : "";
1819
+ if (before === "." || before === "/" || before === "\\" || before === "-" || before === "_") {
1820
+ return match;
1821
+ }
1822
+ if (after === "/" || after === "\\") {
1823
+ return match;
1824
+ }
1825
+ return "";
1826
+ });
1796
1827
  }
1797
- return input;
1828
+ return result;
1829
+ }
1830
+ function computeBuildTag(userMessage, version) {
1831
+ const chars = [4, 7, 20].map((index) => userMessage[index] ?? "0").join("");
1832
+ return createHash2("sha256").update(`${BILLING_SEED}${chars}${version}`).digest("hex").slice(0, 3);
1833
+ }
1834
+ function buildUpstreamRequest(inputBody, identity, template, options) {
1835
+ const body = cloneBody(inputBody);
1836
+ const messages = Array.isArray(body.messages) ? body.messages : [];
1837
+ const systemTexts = normalizeSystemTexts(body.system);
1838
+ stripCacheControl(body);
1839
+ sanitizeMessages(body);
1840
+ stripAssistantThinkingBlocks(messages);
1841
+ for (const message of messages) {
1842
+ if (typeof message.content === "string") {
1843
+ message.content = sanitizeAndScrubText(message.content);
1844
+ continue;
1845
+ }
1846
+ if (!Array.isArray(message.content)) {
1847
+ continue;
1848
+ }
1849
+ for (const block of message.content) {
1850
+ sanitizeMessageBlock(block);
1851
+ }
1852
+ }
1853
+ trimTrailingEmptyTurns(messages);
1854
+ const firstUserMessage = extractFirstUserMessage(messages);
1855
+ const billingHeader = buildBillingHeader(firstUserMessage, template);
1856
+ const mergedSystemPrompt = [
1857
+ template.system_prompt,
1858
+ ...filterInjectedSystemTexts(systemTexts, template, billingHeader)
1859
+ ].map((entry) => sanitizeAndScrubText(entry)).filter(Boolean).join("\n\n");
1860
+ const activeSessionId = options?.sessionId ?? getActiveSessionId();
1861
+ body.messages = messages;
1862
+ const incomingTools = Array.isArray(body.tools) ? body.tools : [];
1863
+ body.tools = hasCompleteToolSchemas(template.tools) ? template.tools.map((tool2) => ({ ...tool2 })) : incomingTools;
1864
+ body.system = [
1865
+ {
1866
+ type: "text",
1867
+ text: billingHeader
1868
+ },
1869
+ {
1870
+ type: "text",
1871
+ text: template.agent_identity,
1872
+ cache_control: { type: "ephemeral" }
1873
+ },
1874
+ {
1875
+ type: "text",
1876
+ text: mergedSystemPrompt,
1877
+ cache_control: { type: "ephemeral" }
1878
+ }
1879
+ ];
1880
+ body.metadata = {
1881
+ ...isRecord(body.metadata) ? body.metadata : {},
1882
+ user_id: JSON.stringify({
1883
+ device_id: identity.deviceId,
1884
+ account_uuid: identity.accountUuid,
1885
+ session_id: activeSessionId
1886
+ })
1887
+ };
1888
+ body.thinking = { type: "adaptive" };
1889
+ body.context_management = DEFAULT_CONTEXT_MANAGEMENT;
1890
+ body.output_config = DEFAULT_OUTPUT_CONFIG;
1891
+ body.max_tokens = 64e3;
1892
+ return orderBodyForOutbound(body, template.body_field_order);
1893
+ }
1894
+ function orderBodyForOutbound(body, overrideOrder) {
1895
+ if (!Array.isArray(overrideOrder) || overrideOrder.length === 0) return body;
1896
+ const ordered = {};
1897
+ const seen = /* @__PURE__ */ new Set();
1898
+ for (const name of overrideOrder) {
1899
+ if (seen.has(name)) continue;
1900
+ if (Object.prototype.hasOwnProperty.call(body, name)) {
1901
+ ordered[name] = body[name];
1902
+ seen.add(name);
1903
+ }
1904
+ }
1905
+ for (const k of Object.keys(body)) {
1906
+ if (!seen.has(k)) ordered[k] = body[k];
1907
+ }
1908
+ return ordered;
1798
1909
  }
1799
- function createResponseStreamTransform(response) {
1800
- if (!response.body) return response;
1910
+ function reverseMapResponse(response, reverseLookup) {
1911
+ return reverseMapToolUseNames(response, reverseLookup);
1912
+ }
1913
+ function createStreamingReverseMapper(response, reverseLookup) {
1914
+ if (!response.body) {
1915
+ return response;
1916
+ }
1801
1917
  const reader = response.body.getReader();
1802
1918
  const decoder = new TextDecoder();
1803
1919
  const encoder = new TextEncoder();
@@ -1810,24 +1926,26 @@ function createResponseStreamTransform(response) {
1810
1926
  if (done) {
1811
1927
  buffer += decoder.decode();
1812
1928
  if (buffer) {
1813
- controller.enqueue(encoder.encode(stripToolPrefixFromLine(buffer)));
1929
+ const lines2 = buffer.split("\n").map((line) => remapSseLine(line, reverseLookup));
1930
+ controller.enqueue(encoder.encode(lines2.join("\n")));
1814
1931
  buffer = "";
1815
1932
  }
1816
1933
  controller.close();
1817
1934
  return;
1818
1935
  }
1819
1936
  buffer += decoder.decode(value, { stream: true });
1820
- const { output, remaining } = processCompleteLines(buffer);
1821
- buffer = remaining;
1822
- if (output) {
1823
- 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));
1824
1943
  return;
1825
1944
  }
1826
1945
  }
1827
1946
  } catch (error) {
1828
1947
  try {
1829
- reader.cancel().catch(() => {
1830
- });
1948
+ await reader.cancel();
1831
1949
  } catch {
1832
1950
  }
1833
1951
  controller.error(error);
@@ -1844,28 +1962,353 @@ function createResponseStreamTransform(response) {
1844
1962
  });
1845
1963
  }
1846
1964
 
1847
- // src/proactive-refresh.ts
1848
- import { createProactiveRefreshQueueForProvider } from "opencode-multi-account-core";
1849
- var ProactiveRefreshQueue = createProactiveRefreshQueueForProvider({
1850
- providerAuthId: "anthropic",
1851
- getConfig,
1852
- isTokenExpired,
1853
- refreshToken,
1854
- debugLog
1855
- });
1965
+ // src/upstream-headers.ts
1966
+ import { randomUUID as randomUUID3 } from "crypto";
1967
+ var UPSTREAM_TIMEOUT_MS = 3e5;
1968
+ var STAINLESS_PACKAGE_VERSION = "0.81.0";
1969
+ var BILLABLE_BETA_PREFIXES = ["extended-cache-ttl-"];
1970
+ function getOsName() {
1971
+ const platform = process.platform;
1972
+ if (platform === "win32") return "Windows";
1973
+ if (platform === "darwin") return "MacOS";
1974
+ return "Linux";
1975
+ }
1976
+ function getStaticHeaders() {
1977
+ const profile = loadCCDerivedRequestProfile();
1978
+ const headers = {
1979
+ "accept": "application/json",
1980
+ "content-type": "application/json",
1981
+ "anthropic-dangerous-direct-browser-access": "true",
1982
+ "user-agent": profile.userAgent,
1983
+ "x-app": profile.xApp,
1984
+ "x-stainless-arch": process.arch,
1985
+ "x-stainless-lang": "js",
1986
+ "x-stainless-os": getOsName(),
1987
+ "x-stainless-package-version": STAINLESS_PACKAGE_VERSION,
1988
+ "x-stainless-retry-count": "0",
1989
+ "x-stainless-runtime": "node",
1990
+ "x-stainless-runtime-version": process.version
1991
+ };
1992
+ const { template } = profile;
1993
+ if (template.header_values) {
1994
+ for (const [key, value] of Object.entries(template.header_values)) {
1995
+ headers[key] = value;
1996
+ }
1997
+ }
1998
+ return headers;
1999
+ }
2000
+ function getPerRequestHeaders(sessionId2) {
2001
+ return {
2002
+ "x-claude-code-session-id": sessionId2,
2003
+ "x-client-request-id": randomUUID3(),
2004
+ "anthropic-version": getAnthropicVersion(),
2005
+ "x-stainless-timeout": String(UPSTREAM_TIMEOUT_MS / 1e3)
2006
+ };
2007
+ }
2008
+ function getAnthropicVersion() {
2009
+ return loadCCDerivedRequestProfile().anthropicVersion;
2010
+ }
2011
+ function getBetaHeader() {
2012
+ return loadCCDerivedRequestProfile().betaHeader;
2013
+ }
2014
+ function orderHeadersForOutbound(headers, overrideHeaderOrder) {
2015
+ const { template } = loadCCDerivedRequestProfile();
2016
+ const order = overrideHeaderOrder ?? template.header_order;
2017
+ if (!Array.isArray(order) || order.length === 0) {
2018
+ return headers;
2019
+ }
2020
+ const lowerToValue = /* @__PURE__ */ new Map();
2021
+ for (const [key, value] of Object.entries(headers)) {
2022
+ lowerToValue.set(key.toLowerCase(), value);
2023
+ }
2024
+ const ordered = [];
2025
+ const seen = /* @__PURE__ */ new Set();
2026
+ for (const name of order) {
2027
+ const key = name.toLowerCase();
2028
+ if (seen.has(key)) {
2029
+ continue;
2030
+ }
2031
+ const value = lowerToValue.get(key);
2032
+ if (value !== void 0) {
2033
+ ordered.push([name, value]);
2034
+ seen.add(key);
2035
+ }
2036
+ }
2037
+ for (const [key, value] of Object.entries(headers)) {
2038
+ if (!seen.has(key.toLowerCase())) {
2039
+ ordered.push([key, value]);
2040
+ }
2041
+ }
2042
+ return ordered;
2043
+ }
2044
+ function filterBillableBetas(betas) {
2045
+ return betas.split(",").map((beta) => beta.trim()).filter(
2046
+ (beta) => beta.length > 0 && !BILLABLE_BETA_PREFIXES.some((prefix) => beta.startsWith(prefix))
2047
+ ).join(",");
2048
+ }
2049
+
2050
+ // src/request-transform.ts
2051
+ function extractModelIdFromBody(body) {
2052
+ if (typeof body !== "string") {
2053
+ return "unknown";
2054
+ }
2055
+ try {
2056
+ const parsed = JSON.parse(body);
2057
+ return typeof parsed.model === "string" ? parsed.model : "unknown";
2058
+ } catch {
2059
+ return "unknown";
2060
+ }
2061
+ }
2062
+ function transformRequestUrl(input) {
2063
+ let url = null;
2064
+ try {
2065
+ if (typeof input === "string" || input instanceof URL) {
2066
+ url = new URL(input.toString());
2067
+ } else if (input instanceof Request) {
2068
+ url = new URL(input.url);
2069
+ }
2070
+ } catch {
2071
+ return input;
2072
+ }
2073
+ if (ANTHROPIC_OAUTH_ADAPTER.transform.enableMessagesBetaQuery && url && url.pathname === "/v1/messages" && !url.searchParams.has("beta")) {
2074
+ url.searchParams.set("beta", "true");
2075
+ return input instanceof Request ? new Request(url.toString(), input) : url;
2076
+ }
2077
+ return input;
2078
+ }
2079
+ function extractToolNamesFromRequestBody(body) {
2080
+ if (!body) {
2081
+ return [];
2082
+ }
2083
+ try {
2084
+ const parsed = JSON.parse(body);
2085
+ if (!Array.isArray(parsed.tools)) {
2086
+ return [];
2087
+ }
2088
+ return parsed.tools.map((tool2) => typeof tool2.name === "string" ? tool2.name : null).filter((toolName) => Boolean(toolName));
2089
+ } catch {
2090
+ return [];
2091
+ }
2092
+ }
2093
+
2094
+ // src/tool-observation.ts
2095
+ import { promises as fs } from "fs";
2096
+ import { dirname, join as join2 } from "path";
2097
+ var OBSERVED_TOOL_FILE = "anthropic-observed-tools.json";
2098
+ var FILE_MODE = 384;
2099
+ function getObservedToolPath() {
2100
+ return join2(getConfigDir(), OBSERVED_TOOL_FILE);
2101
+ }
2102
+ async function loadObservedToolInventory() {
2103
+ try {
2104
+ const content = await fs.readFile(getObservedToolPath(), "utf8");
2105
+ const parsed = JSON.parse(content);
2106
+ return typeof parsed === "object" && parsed && typeof parsed.observedTools === "object" ? parsed : { observedTools: {} };
2107
+ } catch {
2108
+ return { observedTools: {} };
2109
+ }
2110
+ }
2111
+ async function saveObservedToolInventory(inventory) {
2112
+ const targetPath = getObservedToolPath();
2113
+ await fs.mkdir(dirname(targetPath), { recursive: true });
2114
+ await fs.writeFile(targetPath, `${JSON.stringify(inventory, null, 2)}
2115
+ `, { mode: FILE_MODE });
2116
+ await fs.chmod(targetPath, FILE_MODE).catch(() => {
2117
+ });
2118
+ }
2119
+ async function recordObservedToolNames(toolNames) {
2120
+ if (toolNames.length === 0) {
2121
+ return;
2122
+ }
2123
+ const inventory = await loadObservedToolInventory();
2124
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
2125
+ for (const toolName of toolNames) {
2126
+ const entry = inventory.observedTools[toolName];
2127
+ if (!entry) {
2128
+ inventory.observedTools[toolName] = {
2129
+ count: 1,
2130
+ firstSeenAt: now2,
2131
+ lastSeenAt: now2
2132
+ };
2133
+ continue;
2134
+ }
2135
+ entry.count += 1;
2136
+ entry.lastSeenAt = now2;
2137
+ }
2138
+ await saveObservedToolInventory(inventory);
2139
+ }
2140
+
2141
+ // src/error-utils.ts
2142
+ function sanitizeError(err) {
2143
+ const msg = err instanceof Error ? err.message : String(err);
2144
+ return msg.replace(/sk-ant-[a-zA-Z0-9_-]+/g, "[REDACTED]").replace(/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g, "[REDACTED_JWT]").replace(/Bearer\s+[^\s,;]+/gi, "Bearer [REDACTED]");
2145
+ }
2146
+ function enrich429(body, headers) {
2147
+ try {
2148
+ const parsed = JSON.parse(body);
2149
+ const error = parsed.error;
2150
+ if (error && (error.message === "Error" || !error.message)) {
2151
+ const claim = headers.get("anthropic-ratelimit-unified-representative-claim") || "unknown";
2152
+ const status = headers.get("anthropic-ratelimit-unified-status") || "rejected";
2153
+ const util5h = headers.get("anthropic-ratelimit-unified-5h-utilization");
2154
+ const util7d = headers.get("anthropic-ratelimit-unified-7d-utilization");
2155
+ const reset = headers.get("anthropic-ratelimit-unified-reset");
2156
+ const parts = [`Rate limited (${status}). Limiting window: ${claim}`];
2157
+ if (util5h) {
2158
+ const parsedUtil5h = Number.parseFloat(util5h);
2159
+ if (Number.isFinite(parsedUtil5h)) {
2160
+ parts.push(`5h utilization: ${Math.round(parsedUtil5h * 100)}%`);
2161
+ }
2162
+ }
2163
+ if (util7d) {
2164
+ const parsedUtil7d = Number.parseFloat(util7d);
2165
+ if (Number.isFinite(parsedUtil7d)) {
2166
+ parts.push(`7d utilization: ${Math.round(parsedUtil7d * 100)}%`);
2167
+ }
2168
+ }
2169
+ if (reset) {
2170
+ const parsedReset = Number.parseInt(reset, 10);
2171
+ if (Number.isFinite(parsedReset)) {
2172
+ const resetDate = new Date(parsedReset * 1e3);
2173
+ const minutesUntilReset = Math.max(0, Math.round((resetDate.getTime() - Date.now()) / 6e4));
2174
+ parts.push(`resets in ${minutesUntilReset}m`);
2175
+ }
2176
+ }
2177
+ error.message = parts.join(". ");
2178
+ }
2179
+ return JSON.stringify(parsed);
2180
+ } catch {
2181
+ return body;
2182
+ }
2183
+ }
2184
+
2185
+ // src/pacing.ts
2186
+ function pickNonNegativeInt(...values) {
2187
+ for (const v4 of values) {
2188
+ if (v4 === void 0 || v4 === null) continue;
2189
+ const n = typeof v4 === "number" ? v4 : Number.parseInt(v4, 10);
2190
+ if (Number.isFinite(n) && n >= 0) return n;
2191
+ }
2192
+ return void 0;
2193
+ }
2194
+ function computePacingDelay(now2, lastRequestTime, cfg, rng = Math.random) {
2195
+ if (lastRequestTime <= 0) return 0;
2196
+ const minGap = Math.max(0, cfg.minGapMs);
2197
+ const jitter = Math.max(0, cfg.jitterMs);
2198
+ const jitterAdd = jitter > 0 ? Math.floor(rng() * jitter) : 0;
2199
+ const effectiveGap = minGap + jitterAdd;
2200
+ const elapsed = now2 - lastRequestTime;
2201
+ if (elapsed >= effectiveGap) return 0;
2202
+ return effectiveGap - elapsed;
2203
+ }
2204
+ function resolvePacingConfig(explicit = {}, env = process.env) {
2205
+ const minGap = pickNonNegativeInt(explicit.minGapMs, env.ANTHROPIC_PACE_MIN_MS, env.MIN_REQUEST_INTERVAL_MS) ?? 500;
2206
+ const jitter = pickNonNegativeInt(explicit.jitterMs, env.ANTHROPIC_PACE_JITTER_MS) ?? 0;
2207
+ return { minGapMs: minGap, jitterMs: jitter };
2208
+ }
1856
2209
 
1857
2210
  // src/runtime-factory.ts
1858
- import { TokenRefreshError } from "opencode-multi-account-core";
1859
2211
  var TOKEN_REFRESH_PERMANENT_FAILURE_STATUS = 401;
2212
+ function mergeHeaders(target, headers) {
2213
+ if (!headers) {
2214
+ return;
2215
+ }
2216
+ if (headers instanceof Headers) {
2217
+ headers.forEach((value, key) => {
2218
+ target[key.toLowerCase()] = value;
2219
+ });
2220
+ return;
2221
+ }
2222
+ if (Array.isArray(headers)) {
2223
+ for (const [key, value] of headers) {
2224
+ target[String(key).toLowerCase()] = String(value);
2225
+ }
2226
+ return;
2227
+ }
2228
+ for (const [key, value] of Object.entries(headers)) {
2229
+ if (value !== void 0) {
2230
+ target[key.toLowerCase()] = String(value);
2231
+ }
2232
+ }
2233
+ }
2234
+ function extractIncomingHeaders(input, init) {
2235
+ const headers = {};
2236
+ if (input instanceof Request) {
2237
+ mergeHeaders(headers, input.headers);
2238
+ }
2239
+ mergeHeaders(headers, init?.headers);
2240
+ return headers;
2241
+ }
2242
+ function splitBetaValues(value) {
2243
+ if (!value) {
2244
+ return [];
2245
+ }
2246
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
2247
+ }
2248
+ function deduplicateBetas(values) {
2249
+ return [...new Set(values.filter(Boolean))];
2250
+ }
2251
+ function excludeBetas(values, excludedBetas2) {
2252
+ if (excludedBetas2.size === 0) {
2253
+ return values;
2254
+ }
2255
+ return values.filter((beta) => !excludedBetas2.has(beta));
2256
+ }
2257
+ function transformBodyToUpstream(body, identity, sessionId2) {
2258
+ try {
2259
+ const parsed = JSON.parse(body);
2260
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2261
+ return body;
2262
+ }
2263
+ return JSON.stringify(
2264
+ buildUpstreamRequest(
2265
+ parsed,
2266
+ identity,
2267
+ loadTemplate(),
2268
+ { sessionId: sessionId2 }
2269
+ )
2270
+ );
2271
+ } catch {
2272
+ return body;
2273
+ }
2274
+ }
2275
+ async function enrichRateLimitResponse(response) {
2276
+ if (response.status !== 429) {
2277
+ return response;
2278
+ }
2279
+ const body = await response.clone().text();
2280
+ const enrichedBody = enrich429(body, response.headers);
2281
+ if (enrichedBody === body) {
2282
+ return response;
2283
+ }
2284
+ return new Response(enrichedBody, {
2285
+ status: response.status,
2286
+ statusText: response.statusText,
2287
+ headers: new Headers(response.headers)
2288
+ });
2289
+ }
1860
2290
  var AccountRuntimeFactory = class {
1861
- constructor(store, client) {
2291
+ constructor(store, client, identity = loadClaudeIdentity()) {
1862
2292
  this.store = store;
1863
2293
  this.client = client;
2294
+ this.identity = identity;
1864
2295
  }
1865
2296
  store;
1866
2297
  client;
2298
+ identity;
1867
2299
  runtimes = /* @__PURE__ */ new Map();
1868
2300
  initLocks = /* @__PURE__ */ new Map();
2301
+ lastRequestTime = 0;
2302
+ pacingGate = Promise.resolve();
2303
+ pacingTestOverrides = {};
2304
+ setPacingTestOverrides(overrides) {
2305
+ this.pacingTestOverrides = overrides;
2306
+ }
2307
+ resetPacingForTest() {
2308
+ this.lastRequestTime = 0;
2309
+ this.pacingGate = Promise.resolve();
2310
+ this.pacingTestOverrides = {};
2311
+ }
1869
2312
  async getRuntime(uuid) {
1870
2313
  const cached = this.runtimes.get(uuid);
1871
2314
  if (cached) return cached;
@@ -1917,17 +2360,77 @@ var AccountRuntimeFactory = class {
1917
2360
  });
1918
2361
  return { accessToken: refreshed.patch.accessToken, expiresAt: refreshed.patch.expiresAt };
1919
2362
  }
2363
+ buildOutboundHeaders(incomingHeaders, sessionId2, accessToken, modelId, excludedBetas2) {
2364
+ const mergedBetas = deduplicateBetas([
2365
+ ...excludeBetas(splitBetaValues(getBetaHeader()), excludedBetas2),
2366
+ ...getModelBetas(modelId, excludedBetas2),
2367
+ ...excludeBetas(splitBetaValues(incomingHeaders["anthropic-beta"]), excludedBetas2)
2368
+ ]).join(",");
2369
+ const outbound = {
2370
+ ...incomingHeaders,
2371
+ ...getStaticHeaders(),
2372
+ ...getPerRequestHeaders(sessionId2),
2373
+ "authorization": `Bearer ${accessToken}`,
2374
+ "anthropic-beta": filterBillableBetas(mergedBetas)
2375
+ };
2376
+ delete outbound["x-api-key"];
2377
+ return orderHeadersForOutbound(outbound);
2378
+ }
1920
2379
  async executeTransformedFetch(input, init, accessToken) {
1921
2380
  const transformedInput = transformRequestUrl(input);
1922
2381
  const modelId = extractModelIdFromBody(init?.body);
1923
2382
  const excludedBetas2 = getExcludedBetas(modelId);
1924
- const headers = buildRequestHeaders(transformedInput, init, accessToken, modelId, excludedBetas2);
1925
- const transformedBody = typeof init?.body === "string" ? transformRequestBody(init.body) : init?.body;
1926
- let response = await fetch(transformedInput, {
1927
- ...init,
1928
- headers,
1929
- body: transformedBody
1930
- });
2383
+ const incomingHeaders = extractIncomingHeaders(transformedInput, init);
2384
+ const sessionId2 = incomingHeaders["x-claude-code-session-id"] ?? getUpstreamSessionId();
2385
+ const headers = this.buildOutboundHeaders(
2386
+ incomingHeaders,
2387
+ sessionId2,
2388
+ accessToken,
2389
+ modelId,
2390
+ excludedBetas2
2391
+ );
2392
+ if (typeof init?.body === "string") {
2393
+ void recordObservedToolNames(extractToolNamesFromRequestBody(init.body)).catch(() => {
2394
+ });
2395
+ }
2396
+ const transformedBody = typeof init?.body === "string" ? transformBodyToUpstream(init.body, this.identity, sessionId2) : init?.body;
2397
+ const pacingCfg = resolvePacingConfig();
2398
+ const getNow = this.pacingTestOverrides.now ?? Date.now;
2399
+ const sleepFn = this.pacingTestOverrides.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2400
+ const reservePacingSlot = async () => {
2401
+ let releaseGate;
2402
+ const previousGate = this.pacingGate;
2403
+ this.pacingGate = new Promise((resolve) => {
2404
+ releaseGate = resolve;
2405
+ });
2406
+ await previousGate;
2407
+ try {
2408
+ const delay = computePacingDelay(getNow(), this.lastRequestTime, pacingCfg);
2409
+ if (delay > 0) {
2410
+ await sleepFn(delay);
2411
+ }
2412
+ this.lastRequestTime = getNow();
2413
+ } finally {
2414
+ releaseGate?.();
2415
+ }
2416
+ };
2417
+ const performFetch = async (requestHeaders) => {
2418
+ await reservePacingSlot();
2419
+ try {
2420
+ const response2 = await fetch(transformedInput, {
2421
+ ...init,
2422
+ headers: requestHeaders,
2423
+ body: transformedBody
2424
+ });
2425
+ return await enrichRateLimitResponse(response2);
2426
+ } catch (error) {
2427
+ debugLog(this.client, "Anthropic upstream fetch failed", {
2428
+ error: sanitizeError(error)
2429
+ });
2430
+ throw error;
2431
+ }
2432
+ };
2433
+ let response = await performFetch(headers);
1931
2434
  for (let attempt = 0; attempt < LONG_CONTEXT_BETAS.length; attempt += 1) {
1932
2435
  if (response.status !== 400 && response.status !== 429) {
1933
2436
  break;
@@ -1941,20 +2444,16 @@ var AccountRuntimeFactory = class {
1941
2444
  break;
1942
2445
  }
1943
2446
  addExcludedBeta(modelId, betaToExclude);
1944
- const retryHeaders = buildRequestHeaders(
1945
- transformedInput,
1946
- init,
2447
+ const retryHeaders = this.buildOutboundHeaders(
2448
+ incomingHeaders,
2449
+ sessionId2,
1947
2450
  accessToken,
1948
2451
  modelId,
1949
2452
  getExcludedBetas(modelId)
1950
2453
  );
1951
- response = await fetch(transformedInput, {
1952
- ...init,
1953
- headers: retryHeaders,
1954
- body: transformedBody
1955
- });
2454
+ response = await performFetch(retryHeaders);
1956
2455
  }
1957
- return createResponseStreamTransform(response);
2456
+ return createStreamingReverseMapper(response);
1958
2457
  }
1959
2458
  async createRuntime(uuid) {
1960
2459
  const fetchWithAccount = async (input, init) => {
@@ -1979,8 +2478,8 @@ var AccountRuntimeFactory = class {
1979
2478
  };
1980
2479
 
1981
2480
  // src/bootstrap-auth.ts
1982
- import { promises as fs } from "fs";
1983
- import { join } from "path";
2481
+ import { promises as fs2 } from "fs";
2482
+ import { join as join3 } from "path";
1984
2483
  import { getConfigDir as getConfigDir2 } from "opencode-multi-account-core";
1985
2484
  var AUTH_JSON_FILENAME = "auth.json";
1986
2485
  function hasCompleteOAuthCredential(account) {
@@ -2001,10 +2500,10 @@ function selectBootstrapAccount(accounts, activeAccountUuid) {
2001
2500
  return firstUsableAccount ?? completeAccounts[0] ?? null;
2002
2501
  }
2003
2502
  async function readCurrentAuth(providerId) {
2004
- const authPath = join(getConfigDir2(), AUTH_JSON_FILENAME);
2503
+ const authPath = join3(getConfigDir2(), AUTH_JSON_FILENAME);
2005
2504
  let raw;
2006
2505
  try {
2007
- raw = await fs.readFile(authPath, "utf-8");
2506
+ raw = await fs2.readFile(authPath, "utf-8");
2008
2507
  } catch {
2009
2508
  return null;
2010
2509
  }
@@ -2056,6 +2555,70 @@ async function syncBootstrapAuth(client, store) {
2056
2555
  return true;
2057
2556
  }
2058
2557
 
2558
+ // src/session-heartbeat.ts
2559
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
2560
+ var CLIENT_PLATFORM = "cli";
2561
+ var testOverrides = {};
2562
+ function presenceUrl(sessionId2) {
2563
+ return `${loadCCDerivedRequestProfile().baseApiUrl}/v1/code/sessions/${sessionId2}/client/presence`;
2564
+ }
2565
+ function fetchFn() {
2566
+ return testOverrides.fetch ?? globalThis.fetch;
2567
+ }
2568
+ function startHeartbeat(options) {
2569
+ testOverrides.onStart?.(options);
2570
+ const {
2571
+ sessionId: sessionId2,
2572
+ deviceId,
2573
+ accessToken,
2574
+ intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS
2575
+ } = options;
2576
+ let activeController = null;
2577
+ let stopped = false;
2578
+ const sendPresence = async () => {
2579
+ if (stopped) return;
2580
+ const controller = new AbortController();
2581
+ activeController = controller;
2582
+ try {
2583
+ await fetchFn()(presenceUrl(sessionId2), {
2584
+ method: "POST",
2585
+ headers: {
2586
+ Authorization: `Bearer ${accessToken}`,
2587
+ "anthropic-version": getAnthropicVersion(),
2588
+ "anthropic-client-platform": CLIENT_PLATFORM,
2589
+ "Content-Type": "application/json"
2590
+ },
2591
+ body: JSON.stringify({
2592
+ client_id: deviceId,
2593
+ connected_at: (/* @__PURE__ */ new Date()).toISOString()
2594
+ }),
2595
+ signal: controller.signal
2596
+ });
2597
+ } catch {
2598
+ } finally {
2599
+ if (activeController === controller) {
2600
+ activeController = null;
2601
+ }
2602
+ }
2603
+ };
2604
+ const timer = setInterval(sendPresence, intervalMs);
2605
+ if (typeof timer === "object" && "unref" in timer && typeof timer.unref === "function") {
2606
+ timer.unref();
2607
+ }
2608
+ return {
2609
+ stop() {
2610
+ if (stopped) return;
2611
+ stopped = true;
2612
+ clearInterval(timer);
2613
+ activeController?.abort();
2614
+ activeController = null;
2615
+ }
2616
+ };
2617
+ }
2618
+ function getSessionId() {
2619
+ return getUpstreamSessionId();
2620
+ }
2621
+
2059
2622
  // src/index.ts
2060
2623
  var EMPTY_OAUTH_CREDENTIALS = {
2061
2624
  type: "oauth",
@@ -2081,19 +2644,118 @@ function extractFirstUserText(input) {
2081
2644
  }
2082
2645
  return "";
2083
2646
  }
2084
- function injectSystemPrompt(output) {
2085
- const systemPrompt = getInjectedSystemPrompt();
2086
- if (!Array.isArray(output.system)) {
2087
- output.system = [systemPrompt];
2088
- return;
2089
- }
2090
- if (!output.system.includes(systemPrompt)) {
2091
- 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
+ }
2092
2657
  }
2093
2658
  }
2659
+ function applyOrderedHeaders(output, headers) {
2660
+ const orderedHeaders = orderHeadersForOutbound(headers);
2661
+ output.headers = Array.isArray(orderedHeaders) ? Object.fromEntries(orderedHeaders) : orderedHeaders;
2662
+ }
2094
2663
  var ClaudeMultiAuthPlugin = async (ctx) => {
2095
2664
  const { client } = ctx;
2096
2665
  await loadConfig();
2666
+ const requestProfile = loadCCDerivedRequestProfile();
2667
+ const template = requestProfile.template;
2668
+ const claudeIdentity = loadClaudeIdentity();
2669
+ const claudeCodeVersion = template.cc_version ?? requestProfile.cliVersion;
2670
+ const upstreamAgentIdentity = template.agent_identity;
2671
+ const upstreamSystemPrompt = template.system_prompt;
2672
+ let heartbeatHandle = null;
2673
+ let heartbeatToken = null;
2674
+ let heartbeatSessionId = null;
2675
+ const stopHeartbeat = () => {
2676
+ heartbeatHandle?.stop();
2677
+ heartbeatHandle = null;
2678
+ heartbeatToken = null;
2679
+ heartbeatSessionId = null;
2680
+ };
2681
+ const ensureHeartbeat = (accessToken) => {
2682
+ if (!accessToken || !claudeIdentity.deviceId) {
2683
+ stopHeartbeat();
2684
+ return;
2685
+ }
2686
+ const sessionId2 = getSessionId();
2687
+ if (heartbeatHandle && heartbeatToken === accessToken && heartbeatSessionId === sessionId2) {
2688
+ return;
2689
+ }
2690
+ stopHeartbeat();
2691
+ heartbeatToken = accessToken;
2692
+ heartbeatSessionId = sessionId2;
2693
+ heartbeatHandle = startHeartbeat({
2694
+ sessionId: sessionId2,
2695
+ deviceId: claudeIdentity.deviceId,
2696
+ accessToken
2697
+ });
2698
+ };
2699
+ const startupDrift = detectDrift(template);
2700
+ if (startupDrift.drifted) {
2701
+ client.app.log({
2702
+ body: {
2703
+ service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
2704
+ level: "warn",
2705
+ message: startupDrift.message,
2706
+ extra: {
2707
+ cachedVersion: startupDrift.cachedVersion,
2708
+ installedVersion: startupDrift.installedVersion
2709
+ }
2710
+ }
2711
+ }).catch(() => {
2712
+ });
2713
+ }
2714
+ const compat = checkCCCompat();
2715
+ if (compat.status !== "ok" && compat.status !== "unknown") {
2716
+ client.app.log({
2717
+ body: {
2718
+ service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
2719
+ level: "warn",
2720
+ message: compat.message,
2721
+ extra: {
2722
+ installedVersion: compat.installedVersion,
2723
+ range: compat.range
2724
+ }
2725
+ }
2726
+ }).catch(() => {
2727
+ });
2728
+ }
2729
+ void refreshLiveFingerprintAsync({ silent: true }).then((refreshedTemplate) => {
2730
+ const refreshedDrift = detectDrift(refreshedTemplate ?? template);
2731
+ if (!refreshedDrift.drifted) {
2732
+ return;
2733
+ }
2734
+ return client.app.log({
2735
+ body: {
2736
+ service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
2737
+ level: "warn",
2738
+ message: refreshedDrift.message,
2739
+ extra: {
2740
+ cachedVersion: refreshedDrift.cachedVersion,
2741
+ installedVersion: refreshedDrift.installedVersion
2742
+ }
2743
+ }
2744
+ }).catch(() => {
2745
+ });
2746
+ }).catch((error) => {
2747
+ client.app.log({
2748
+ body: {
2749
+ service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
2750
+ level: "debug",
2751
+ message: "live fingerprint refresh failed",
2752
+ extra: {
2753
+ error: sanitizeError(error)
2754
+ }
2755
+ }
2756
+ }).catch(() => {
2757
+ });
2758
+ });
2097
2759
  const store = new AccountStore();
2098
2760
  await syncBootstrapAuth(client, store).catch(() => {
2099
2761
  });
@@ -2104,7 +2766,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2104
2766
  let cascadeStateManager = null;
2105
2767
  let poolChainConfig = { pools: [], chains: [] };
2106
2768
  async function ensureExecutionInfrastructure() {
2107
- runtimeFactory ??= new AccountRuntimeFactory(store, client);
2769
+ runtimeFactory ??= new AccountRuntimeFactory(store, client, claudeIdentity);
2108
2770
  poolChainConfig = await loadPoolChainConfig();
2109
2771
  poolManager ??= new PoolManager();
2110
2772
  poolManager.loadPools(poolChainConfig.pools);
@@ -2143,6 +2805,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2143
2805
  manager = await AccountManager.create(store, EMPTY_OAUTH_CREDENTIALS, client);
2144
2806
  await ensureExecutionInfrastructure();
2145
2807
  await startRefreshQueueIfNeeded();
2808
+ ensureHeartbeat(manager.getActiveAccount()?.accessToken);
2146
2809
  return manager.getAccountCount() > 0;
2147
2810
  }
2148
2811
  async function initializeManagerFromAuth(credentials) {
@@ -2156,11 +2819,12 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2156
2819
  });
2157
2820
  return {
2158
2821
  "experimental.chat.system.transform": (input, output) => {
2159
- injectSystemPrompt(output);
2160
- const billingHeader = buildBillingHeader(extractFirstUserText(input));
2161
- if (billingHeader && !output.system?.includes(billingHeader)) {
2162
- output.system?.unshift(billingHeader);
2163
- }
2822
+ const billingHeader = composeBillingSystemEntry(extractFirstUserText(input), claudeCodeVersion);
2823
+ prependMissingSystemEntries(output, [
2824
+ billingHeader,
2825
+ upstreamAgentIdentity,
2826
+ upstreamSystemPrompt
2827
+ ]);
2164
2828
  },
2165
2829
  tool: {
2166
2830
  [ANTHROPIC_OAUTH_ADAPTER.statusToolName]: tool({
@@ -2198,10 +2862,10 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2198
2862
  }
2199
2863
  }
2200
2864
  if (account.cachedUsage) {
2201
- const now = Date.now();
2865
+ const now2 = Date.now();
2202
2866
  const usage2 = account.cachedUsage;
2203
2867
  const exhaustedTiers = [usage2.five_hour, usage2.seven_day].filter(
2204
- (tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now
2868
+ (tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now2
2205
2869
  );
2206
2870
  if (exhaustedTiers.length > 0) {
2207
2871
  statusParts.push("USAGE EXHAUSTED");
@@ -2232,6 +2896,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2232
2896
  async loader(getAuth, provider) {
2233
2897
  const auth = await getAuth();
2234
2898
  if (auth.type !== "oauth") {
2899
+ stopHeartbeat();
2235
2900
  return { apiKey: "", fetch };
2236
2901
  }
2237
2902
  for (const model of Object.values(provider.models ?? {})) {
@@ -2242,6 +2907,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2242
2907
  const credentials = auth;
2243
2908
  await migrateFromAuthJson("anthropic", store);
2244
2909
  await initializeManagerFromAuth(credentials);
2910
+ ensureHeartbeat(credentials.access);
2245
2911
  const initializedManager = manager;
2246
2912
  if (!initializedManager) {
2247
2913
  return { apiKey: "", fetch };
@@ -2264,23 +2930,32 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
2264
2930
  );
2265
2931
  }
2266
2932
  }
2933
+ const authProfile = await loadCCDerivedAuthProfile();
2267
2934
  return {
2268
2935
  apiKey: "",
2936
+ baseURL: authProfile.apiV1BaseUrl,
2269
2937
  "chat.headers": async (input, output) => {
2270
2938
  if (input.provider?.info?.id !== ANTHROPIC_OAUTH_ADAPTER.authProviderId) return;
2271
- output.headers["user-agent"] = getUserAgent();
2272
- output.headers["anthropic-beta"] = ANTHROPIC_BETA_HEADER;
2273
- 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
+ });
2274
2946
  },
2275
2947
  async fetch(input, init) {
2276
2948
  if (!initializedManager || !runtimeFactory) {
2949
+ stopHeartbeat();
2277
2950
  return fetch(input, init);
2278
2951
  }
2279
2952
  if (initializedManager.getAccountCount() === 0) {
2953
+ stopHeartbeat();
2280
2954
  throw new Error(
2281
2955
  "No Anthropic accounts configured. Run `opencode auth login` to add an account."
2282
2956
  );
2283
2957
  }
2958
+ ensureHeartbeat(initializedManager.getActiveAccount()?.accessToken);
2284
2959
  if (!poolManager || !cascadeStateManager) {
2285
2960
  poolManager = new PoolManager();
2286
2961
  poolManager.loadPools(poolChainConfig.pools);