opencode-anthropic-multi-account 0.2.5 → 0.2.6

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.
@@ -1,6 +1,9 @@
1
1
  /** Anthropic OAuth adapter config */
2
2
  export declare const ANTHROPIC_OAUTH_ADAPTER: import("opencode-multi-account-core").OAuthAdapter;
3
3
  export declare const ANTHROPIC_CLIENT_ID: string;
4
+ export declare const ANTHROPIC_AUTHORIZE_ENDPOINT: string;
5
+ export declare const ANTHROPIC_REDIRECT_URI: string;
6
+ export declare const ANTHROPIC_SCOPES: string;
4
7
  /** Token exchange / refresh endpoint */
5
8
  export declare const ANTHROPIC_TOKEN_ENDPOINT: string;
6
9
  /** OAuth usage stats endpoint */
package/dist/index.js CHANGED
@@ -1820,6 +1820,33 @@ async function confirm(message, defaultYes = false) {
1820
1820
  }
1821
1821
 
1822
1822
  // ../multi-account-core/src/adapters/anthropic.ts
1823
+ var ANTHROPIC_DEFAULT_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1824
+ var ANTHROPIC_DEFAULT_CLI_VERSION = "2.1.80";
1825
+ var ANTHROPIC_DEFAULT_USER_AGENT = "claude-cli/2.1.2 (external, cli)";
1826
+ var ANTHROPIC_DEFAULT_AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
1827
+ var ANTHROPIC_DEFAULT_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
1828
+ var ANTHROPIC_DEFAULT_REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
1829
+ var ANTHROPIC_DEFAULT_SCOPES = "org:create_api_key user:profile user:inference";
1830
+ var ANTHROPIC_DEFAULT_BETA_FLAGS = "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05";
1831
+ function buildCliUserAgent(cliVersion) {
1832
+ return `claude-cli/${cliVersion} (external, cli)`;
1833
+ }
1834
+ function resolveAnthropicOAuthEnv(env = process.env) {
1835
+ const cliVersion = env.ANTHROPIC_CLI_VERSION || ANTHROPIC_DEFAULT_CLI_VERSION;
1836
+ const composedUserAgent = buildCliUserAgent(cliVersion);
1837
+ const userAgent = env.ANTHROPIC_USER_AGENT || (env.ANTHROPIC_CLI_VERSION ? composedUserAgent : "") || ANTHROPIC_DEFAULT_USER_AGENT;
1838
+ return {
1839
+ clientId: env.ANTHROPIC_CLIENT_ID || ANTHROPIC_DEFAULT_CLIENT_ID,
1840
+ cliVersion,
1841
+ userAgent,
1842
+ authorizeUrl: env.ANTHROPIC_AUTHORIZE_URL || ANTHROPIC_DEFAULT_AUTHORIZE_URL,
1843
+ tokenUrl: env.ANTHROPIC_TOKEN_URL || ANTHROPIC_DEFAULT_TOKEN_URL,
1844
+ redirectUri: env.ANTHROPIC_REDIRECT_URI || ANTHROPIC_DEFAULT_REDIRECT_URI,
1845
+ scopes: env.ANTHROPIC_SCOPES || ANTHROPIC_DEFAULT_SCOPES,
1846
+ betaFlags: env.ANTHROPIC_BETA_FLAGS || ANTHROPIC_DEFAULT_BETA_FLAGS
1847
+ };
1848
+ }
1849
+ var anthropicEnv = resolveAnthropicOAuthEnv();
1823
1850
  var anthropicOAuthAdapter = {
1824
1851
  id: "anthropic",
1825
1852
  authProviderId: "anthropic",
@@ -1827,14 +1854,14 @@ var anthropicOAuthAdapter = {
1827
1854
  statusToolName: "claude_multiauth_status",
1828
1855
  authMethodLabel: "Claude Pro/Max (Multi-Auth)",
1829
1856
  serviceLogName: "claude-multiauth",
1830
- oauthClientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
1831
- tokenEndpoint: "https://console.anthropic.com/v1/oauth/token",
1857
+ oauthClientId: anthropicEnv.clientId,
1858
+ tokenEndpoint: anthropicEnv.tokenUrl,
1832
1859
  usageEndpoint: "https://api.anthropic.com/api/oauth/usage",
1833
1860
  profileEndpoint: "https://api.anthropic.com/api/oauth/profile",
1834
1861
  oauthBetaHeader: "oauth-2025-04-20",
1835
- requestBetaHeader: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
1836
- cliUserAgent: "claude-cli/2.1.2 (external, cli)",
1837
- cliVersion: "2.1.80",
1862
+ requestBetaHeader: anthropicEnv.betaFlags,
1863
+ cliUserAgent: anthropicEnv.userAgent,
1864
+ cliVersion: anthropicEnv.cliVersion,
1838
1865
  billingSalt: "59cf53e54c78",
1839
1866
  toolPrefix: "mcp_",
1840
1867
  accountStorageFilename: "anthropic-multi-account-accounts.json",
@@ -2183,8 +2210,12 @@ var CascadeStateManager = class {
2183
2210
  };
2184
2211
 
2185
2212
  // src/constants.ts
2213
+ var resolvedAnthropicOAuthEnv = resolveAnthropicOAuthEnv();
2186
2214
  var ANTHROPIC_OAUTH_ADAPTER = anthropicOAuthAdapter;
2187
2215
  var ANTHROPIC_CLIENT_ID = ANTHROPIC_OAUTH_ADAPTER.oauthClientId;
2216
+ var ANTHROPIC_AUTHORIZE_ENDPOINT = resolvedAnthropicOAuthEnv.authorizeUrl;
2217
+ var ANTHROPIC_REDIRECT_URI = resolvedAnthropicOAuthEnv.redirectUri;
2218
+ var ANTHROPIC_SCOPES = resolvedAnthropicOAuthEnv.scopes;
2188
2219
  var ANTHROPIC_TOKEN_ENDPOINT = ANTHROPIC_OAUTH_ADAPTER.tokenEndpoint;
2189
2220
  var ANTHROPIC_USAGE_ENDPOINT = ANTHROPIC_OAUTH_ADAPTER.usageEndpoint;
2190
2221
  var ANTHROPIC_PROFILE_ENDPOINT = ANTHROPIC_OAUTH_ADAPTER.profileEndpoint;
@@ -2469,12 +2500,14 @@ function fromPiAiCredentials(creds) {
2469
2500
  expiresAt: creds.expires
2470
2501
  };
2471
2502
  }
2472
- var ANTHROPIC_REFRESH_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
2503
+ var ANTHROPIC_REFRESH_ENDPOINT = ANTHROPIC_TOKEN_ENDPOINT;
2504
+ var LEGACY_ANTHROPIC_TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
2473
2505
  var REFRESH_NODE_EXECUTABLE = process.env.OPENCODE_REFRESH_NODE_EXECUTABLE || "node";
2474
2506
  var tokenProxyContext = new AsyncLocalStorage();
2475
2507
  var tokenProxyInstalled = false;
2476
2508
  var tokenProxyOriginalFetch = null;
2477
2509
  var refreshEndpointUrl = new URL(ANTHROPIC_REFRESH_ENDPOINT);
2510
+ var legacyRefreshEndpointUrl = new URL(LEGACY_ANTHROPIC_TOKEN_ENDPOINT);
2478
2511
  function buildRefreshRequestError(details) {
2479
2512
  return new Error(`Anthropic token refresh request failed. url=${ANTHROPIC_REFRESH_ENDPOINT}; details=${details}`);
2480
2513
  }
@@ -2490,9 +2523,65 @@ function isAnthropicTokenEndpoint(input) {
2490
2523
  const rawUrl = getRequestUrlString(input);
2491
2524
  try {
2492
2525
  const url = new URL(rawUrl);
2493
- return url.origin === refreshEndpointUrl.origin && url.pathname === refreshEndpointUrl.pathname;
2526
+ const isConfiguredEndpoint = url.origin === refreshEndpointUrl.origin && url.pathname === refreshEndpointUrl.pathname;
2527
+ const isLegacyEndpoint = url.origin === legacyRefreshEndpointUrl.origin && url.pathname === legacyRefreshEndpointUrl.pathname;
2528
+ return isConfiguredEndpoint || isLegacyEndpoint;
2529
+ } catch {
2530
+ return rawUrl === ANTHROPIC_REFRESH_ENDPOINT || rawUrl === LEGACY_ANTHROPIC_TOKEN_ENDPOINT;
2531
+ }
2532
+ }
2533
+ function applyOverridesToTokenParams(params) {
2534
+ params.set("client_id", ANTHROPIC_CLIENT_ID);
2535
+ if (params.get("grant_type") === "authorization_code") {
2536
+ params.set("redirect_uri", ANTHROPIC_REDIRECT_URI);
2537
+ params.set("scope", ANTHROPIC_SCOPES);
2538
+ }
2539
+ }
2540
+ function isRecord(value) {
2541
+ return typeof value === "object" && value !== null;
2542
+ }
2543
+ function applyAnthropicTokenRequestOverrides(rawBody) {
2544
+ const trimmed = rawBody.trim();
2545
+ if (trimmed.length === 0) {
2546
+ return rawBody;
2547
+ }
2548
+ if (trimmed.startsWith("{")) {
2549
+ try {
2550
+ const parsed = JSON.parse(trimmed);
2551
+ if (!isRecord(parsed)) {
2552
+ return rawBody;
2553
+ }
2554
+ const grantType = typeof parsed.grant_type === "string" ? parsed.grant_type : "";
2555
+ parsed.client_id = ANTHROPIC_CLIENT_ID;
2556
+ if (grantType === "authorization_code") {
2557
+ parsed.redirect_uri = ANTHROPIC_REDIRECT_URI;
2558
+ parsed.scope = ANTHROPIC_SCOPES;
2559
+ }
2560
+ return JSON.stringify(parsed);
2561
+ } catch {
2562
+ return rawBody;
2563
+ }
2564
+ }
2565
+ const params = new URLSearchParams(rawBody);
2566
+ applyOverridesToTokenParams(params);
2567
+ return params.toString();
2568
+ }
2569
+ function rewriteAnthropicAuthUrl(rawUrl) {
2570
+ try {
2571
+ const configuredAuthorizeUrl = new URL(ANTHROPIC_AUTHORIZE_ENDPOINT);
2572
+ const rewritten = new URL(rawUrl);
2573
+ rewritten.protocol = configuredAuthorizeUrl.protocol;
2574
+ rewritten.host = configuredAuthorizeUrl.host;
2575
+ rewritten.pathname = configuredAuthorizeUrl.pathname;
2576
+ for (const [key, value] of configuredAuthorizeUrl.searchParams.entries()) {
2577
+ rewritten.searchParams.set(key, value);
2578
+ }
2579
+ rewritten.searchParams.set("client_id", ANTHROPIC_CLIENT_ID);
2580
+ rewritten.searchParams.set("redirect_uri", ANTHROPIC_REDIRECT_URI);
2581
+ rewritten.searchParams.set("scope", ANTHROPIC_SCOPES);
2582
+ return rewritten.toString();
2494
2583
  } catch {
2495
- return rawUrl === ANTHROPIC_REFRESH_ENDPOINT;
2584
+ return rawUrl;
2496
2585
  }
2497
2586
  }
2498
2587
  function getRequestBodySource(input, init) {
@@ -2530,10 +2619,11 @@ function shouldProxyTokenRequest(input) {
2530
2619
  return tokenProxyContext.getStore() === true && isAnthropicTokenEndpoint(input);
2531
2620
  }
2532
2621
  async function postAnthropicTokenViaNode(body) {
2622
+ const overriddenBody = applyAnthropicTokenRequestOverrides(body);
2533
2623
  let output;
2534
2624
  try {
2535
2625
  output = await runNodeTokenRequest({
2536
- body,
2626
+ body: overriddenBody,
2537
2627
  endpoint: ANTHROPIC_REFRESH_ENDPOINT,
2538
2628
  executable: REFRESH_NODE_EXECUTABLE,
2539
2629
  timeoutMs: TOKEN_REFRESH_TIMEOUT_MS
@@ -2596,7 +2686,12 @@ async function fetchProfileWithSingleRetry(accessToken) {
2596
2686
  }
2597
2687
  async function loginWithPiAi(callbacks) {
2598
2688
  const piCreds = await withAnthropicTokenProxyFetch(() => piAiOauth.loginAnthropic({
2599
- onAuth: callbacks.onAuth,
2689
+ onAuth: (info) => {
2690
+ callbacks.onAuth({
2691
+ ...info,
2692
+ url: info.url ? rewriteAnthropicAuthUrl(info.url) : info.url
2693
+ });
2694
+ },
2600
2695
  onPrompt: callbacks.onPrompt,
2601
2696
  onProgress: callbacks.onProgress,
2602
2697
  onManualCodeInput: callbacks.onManualCodeInput
@@ -2976,7 +3071,7 @@ function printUsageEntry(name, entry, isLast) {
2976
3071
  return;
2977
3072
  }
2978
3073
  const bar = createProgressBar(entry.utilization);
2979
- const reset = entry.utilization >= 100 && entry.resets_at ? formatResetTime(entry.resets_at) : "";
3074
+ const reset = entry.resets_at ? formatResetTime(entry.resets_at) : "";
2980
3075
  console.log(` ${connector} ${name.padEnd(16)} ${bar}${reset}`);
2981
3076
  }
2982
3077
  function printQuotaReport(account, usage) {
@@ -3535,11 +3630,30 @@ function buildBillingHeader(firstUserMessage) {
3535
3630
  if (!version || !salt) return "";
3536
3631
  const sampled = sampleCodeUnits(firstUserMessage, [4, 7, 20]);
3537
3632
  const hash = createHash("sha256").update(`${salt}${sampled}${version}`).digest("hex").slice(0, 3);
3538
- return `x-anthropic-billing-header: cc_version=${version}.${hash}; cc_entrypoint=cli; cch=00000;`;
3633
+ return `cc_version=${version}.${hash}; cc_entrypoint=cli; cch=00000;`;
3539
3634
  }
3540
3635
  var OPENCODE_CAMEL_RE = /OpenCode/g;
3541
3636
  var OPENCODE_LOWER_RE = /(?<!\/)opencode/gi;
3542
3637
  var TOOL_PREFIX_RESPONSE_RE = /"name"\s*:\s*"mcp_([^"]+)"/g;
3638
+ function extractFirstUserTextFromBody(body) {
3639
+ if (!body) return "";
3640
+ try {
3641
+ const parsed = JSON.parse(body);
3642
+ if (!Array.isArray(parsed.messages)) return "";
3643
+ for (const message of parsed.messages) {
3644
+ if (message.role !== "user") continue;
3645
+ if (typeof message.content === "string") return message.content;
3646
+ if (!Array.isArray(message.content)) continue;
3647
+ for (const block of message.content) {
3648
+ if (block.type === "text" && typeof block.text === "string") {
3649
+ return block.text;
3650
+ }
3651
+ }
3652
+ }
3653
+ } catch {
3654
+ }
3655
+ return "";
3656
+ }
3543
3657
  function addToolPrefix(name) {
3544
3658
  if (!ANTHROPIC_OAUTH_ADAPTER.transform.addToolPrefix) {
3545
3659
  return name;
@@ -3565,7 +3679,7 @@ function processCompleteLines(buffer) {
3565
3679
  `;
3566
3680
  return { output, remaining };
3567
3681
  }
3568
- function buildRequestHeaders(input, init, accessToken) {
3682
+ function buildRequestHeaders(input, init, accessToken, bodyString) {
3569
3683
  const headers = new Headers();
3570
3684
  if (input instanceof Request) {
3571
3685
  input.headers.forEach((value, key) => {
@@ -3597,6 +3711,13 @@ function buildRequestHeaders(input, init, accessToken) {
3597
3711
  headers.set("user-agent", CLAUDE_CLI_USER_AGENT);
3598
3712
  headers.set("anthropic-dangerous-direct-browser-access", "true");
3599
3713
  headers.set("x-app", "cli");
3714
+ const resolvedBody = bodyString ?? (typeof init?.body === "string" ? init.body : void 0);
3715
+ const billingHeader = buildBillingHeader(
3716
+ extractFirstUserTextFromBody(resolvedBody)
3717
+ );
3718
+ if (billingHeader) {
3719
+ headers.set("x-anthropic-billing-header", billingHeader);
3720
+ }
3600
3721
  headers.delete("x-api-key");
3601
3722
  return headers;
3602
3723
  }
@@ -3775,8 +3896,9 @@ var AccountRuntimeFactory = class {
3775
3896
  }
3776
3897
  async executeTransformedFetch(input, init, accessToken) {
3777
3898
  const transformedInput = transformRequestUrl(input);
3778
- const headers = buildRequestHeaders(transformedInput, init, accessToken);
3779
- const transformedBody = typeof init?.body === "string" ? transformRequestBody(init.body) : init?.body;
3899
+ const rawBody = typeof init?.body === "string" ? init.body : void 0;
3900
+ const headers = buildRequestHeaders(transformedInput, init, accessToken, rawBody);
3901
+ const transformedBody = rawBody !== void 0 ? transformRequestBody(rawBody) : init?.body;
3780
3902
  const response = await fetch(transformedInput, {
3781
3903
  ...init,
3782
3904
  headers,
@@ -3807,24 +3929,6 @@ var AccountRuntimeFactory = class {
3807
3929
  };
3808
3930
 
3809
3931
  // src/index.ts
3810
- function extractFirstUserText(input) {
3811
- try {
3812
- const raw = input;
3813
- const messages = raw.messages ?? raw.request?.messages;
3814
- if (!Array.isArray(messages)) return "";
3815
- for (const msg of messages) {
3816
- if (msg.role !== "user") continue;
3817
- if (typeof msg.content === "string") return msg.content;
3818
- if (Array.isArray(msg.content)) {
3819
- for (const block of msg.content) {
3820
- if (block.type === "text" && block.text) return block.text;
3821
- }
3822
- }
3823
- }
3824
- } catch {
3825
- }
3826
- return "";
3827
- }
3828
3932
  function injectSystemPrompt(output) {
3829
3933
  const systemPrompt = getSystemPrompt();
3830
3934
  if (!Array.isArray(output.system)) {
@@ -3846,12 +3950,8 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
3846
3950
  let cascadeStateManager = null;
3847
3951
  let poolChainConfig = { pools: [], chains: [] };
3848
3952
  return {
3849
- "experimental.chat.system.transform": (input, output) => {
3953
+ "experimental.chat.system.transform": (_input, output) => {
3850
3954
  injectSystemPrompt(output);
3851
- const billingHeader = buildBillingHeader(extractFirstUserText(input));
3852
- if (billingHeader && !output.system?.includes(billingHeader)) {
3853
- output.system?.unshift(billingHeader);
3854
- }
3855
3955
  },
3856
3956
  tool: {
3857
3957
  [ANTHROPIC_OAUTH_ADAPTER.statusToolName]: tool({
@@ -11,6 +11,8 @@ export interface LoginWithPiAiCallbacks {
11
11
  onProgress?: (message: string) => void;
12
12
  onManualCodeInput?: () => Promise<string>;
13
13
  }
14
+ export declare function applyAnthropicTokenRequestOverrides(rawBody: string): string;
15
+ export declare function rewriteAnthropicAuthUrl(rawUrl: string): string;
14
16
  export declare function withAnthropicTokenProxyFetch<T>(operation: () => Promise<T>): Promise<T>;
15
17
  export declare function resetAnthropicTokenProxyStateForTest(): void;
16
18
  export declare function loginWithPiAi(callbacks: LoginWithPiAiCallbacks): Promise<Partial<StoredAccount>>;
@@ -1,6 +1,6 @@
1
1
  export declare function getSystemPrompt(): string;
2
2
  export declare function buildBillingHeader(firstUserMessage: string): string;
3
- export declare function buildRequestHeaders(input: RequestInfo | URL, init: RequestInit | undefined, accessToken: string): Headers;
3
+ export declare function buildRequestHeaders(input: RequestInfo | URL, init: RequestInit | undefined, accessToken: string, bodyString?: string): Headers;
4
4
  export declare function transformRequestBody(body: string | undefined): string | undefined;
5
5
  export declare function transformRequestUrl(input: RequestInfo | URL): RequestInfo | URL;
6
6
  export declare function createResponseStreamTransform(response: Response): Response;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-anthropic-multi-account",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "OpenCode plugin for Anthropic multi-account management with automatic rate limit switching",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -39,7 +39,7 @@
39
39
  "directory": "packages/anthropic-multi-account"
40
40
  },
41
41
  "dependencies": {
42
- "opencode-multi-account-core": "^0.2.5",
42
+ "opencode-multi-account-core": "^0.2.6",
43
43
  "@mariozechner/pi-ai": "^0.61.0",
44
44
  "valibot": "^1.2.0"
45
45
  },