opencode-anthropic-multi-account 0.2.4 → 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.
- package/dist/constants.d.ts +3 -0
- package/dist/index.js +373 -45
- package/dist/pi-ai-adapter.d.ts +4 -0
- package/dist/request-transform.d.ts +1 -1
- package/dist/token-node-request.d.ts +10 -0
- package/package.json +2 -2
package/dist/constants.d.ts
CHANGED
|
@@ -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:
|
|
1831
|
-
tokenEndpoint:
|
|
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:
|
|
1836
|
-
cliUserAgent:
|
|
1837
|
-
cliVersion:
|
|
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;
|
|
@@ -2194,9 +2225,100 @@ var TOOL_PREFIX = ANTHROPIC_OAUTH_ADAPTER.toolPrefix;
|
|
|
2194
2225
|
var ACCOUNTS_FILENAME2 = ANTHROPIC_OAUTH_ADAPTER.accountStorageFilename;
|
|
2195
2226
|
var PLAN_LABELS = ANTHROPIC_OAUTH_ADAPTER.planLabels;
|
|
2196
2227
|
var TOKEN_EXPIRY_BUFFER_MS = 6e4;
|
|
2228
|
+
var TOKEN_REFRESH_TIMEOUT_MS = 3e4;
|
|
2197
2229
|
|
|
2198
2230
|
// src/pi-ai-adapter.ts
|
|
2199
|
-
import {
|
|
2231
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2232
|
+
import * as piAiOauth from "@mariozechner/pi-ai/oauth";
|
|
2233
|
+
|
|
2234
|
+
// src/token-node-request.ts
|
|
2235
|
+
import * as childProcess from "node:child_process";
|
|
2236
|
+
function buildNodeTokenRequestScript() {
|
|
2237
|
+
return `
|
|
2238
|
+
const https = require("node:https");
|
|
2239
|
+
const endpoint = process.env.ANTHROPIC_REFRESH_ENDPOINT;
|
|
2240
|
+
const timeoutMs = Number(process.env.ANTHROPIC_REFRESH_TIMEOUT_MS || "30000");
|
|
2241
|
+
const payload = process.env.ANTHROPIC_REFRESH_REQUEST_BODY || "";
|
|
2242
|
+
|
|
2243
|
+
function printSuccess(body) {
|
|
2244
|
+
console.log(JSON.stringify({ ok: true, body }));
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
function printFailure(error) {
|
|
2248
|
+
console.log(JSON.stringify({ ok: false, ...error }));
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
const request = https.request(endpoint, {
|
|
2252
|
+
method: "POST",
|
|
2253
|
+
headers: {
|
|
2254
|
+
"Content-Type": "application/json",
|
|
2255
|
+
Accept: "application/json",
|
|
2256
|
+
"Content-Length": Buffer.byteLength(payload).toString(),
|
|
2257
|
+
},
|
|
2258
|
+
}, (response) => {
|
|
2259
|
+
let body = "";
|
|
2260
|
+
response.setEncoding("utf8");
|
|
2261
|
+
response.on("data", (chunk) => {
|
|
2262
|
+
body += chunk;
|
|
2263
|
+
});
|
|
2264
|
+
response.on("end", () => {
|
|
2265
|
+
const status = response.statusCode ?? 0;
|
|
2266
|
+
if (status < 200 || status >= 300) {
|
|
2267
|
+
printFailure({ status, body });
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
printSuccess(body);
|
|
2272
|
+
});
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
request.setTimeout(timeoutMs, () => {
|
|
2276
|
+
request.destroy(new Error("Request timed out after " + timeoutMs + "ms"));
|
|
2277
|
+
});
|
|
2278
|
+
|
|
2279
|
+
request.on("error", (error) => {
|
|
2280
|
+
printFailure({ error: error instanceof Error ? error.name + ": " + error.message : String(error) });
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
request.write(payload);
|
|
2284
|
+
request.end();
|
|
2285
|
+
`;
|
|
2286
|
+
}
|
|
2287
|
+
async function defaultRunNodeTokenRequest(options) {
|
|
2288
|
+
const script = buildNodeTokenRequestScript();
|
|
2289
|
+
return await new Promise((resolve, reject) => {
|
|
2290
|
+
childProcess.execFile(
|
|
2291
|
+
options.executable,
|
|
2292
|
+
["-e", script],
|
|
2293
|
+
{
|
|
2294
|
+
timeout: options.timeoutMs + 1e3,
|
|
2295
|
+
maxBuffer: 1024 * 1024,
|
|
2296
|
+
env: {
|
|
2297
|
+
...process.env,
|
|
2298
|
+
ANTHROPIC_REFRESH_ENDPOINT: options.endpoint,
|
|
2299
|
+
ANTHROPIC_REFRESH_REQUEST_BODY: options.body,
|
|
2300
|
+
ANTHROPIC_REFRESH_TIMEOUT_MS: String(options.timeoutMs)
|
|
2301
|
+
}
|
|
2302
|
+
},
|
|
2303
|
+
(error, stdout, stderr) => {
|
|
2304
|
+
const trimmedStdout = stdout.trim();
|
|
2305
|
+
if (error) {
|
|
2306
|
+
reject(new Error(stderr.trim() || error.message));
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
if (!trimmedStdout) {
|
|
2310
|
+
reject(new Error("Empty response from Node refresh helper"));
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
resolve(trimmedStdout);
|
|
2314
|
+
}
|
|
2315
|
+
);
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
var nodeTokenRequestRunner = defaultRunNodeTokenRequest;
|
|
2319
|
+
async function runNodeTokenRequest(options) {
|
|
2320
|
+
return await nodeTokenRequestRunner(options);
|
|
2321
|
+
}
|
|
2200
2322
|
|
|
2201
2323
|
// src/config.ts
|
|
2202
2324
|
initCoreConfig("claude-multiauth.json");
|
|
@@ -2378,19 +2500,204 @@ function fromPiAiCredentials(creds) {
|
|
|
2378
2500
|
expiresAt: creds.expires
|
|
2379
2501
|
};
|
|
2380
2502
|
}
|
|
2503
|
+
var ANTHROPIC_REFRESH_ENDPOINT = ANTHROPIC_TOKEN_ENDPOINT;
|
|
2504
|
+
var LEGACY_ANTHROPIC_TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
|
|
2505
|
+
var REFRESH_NODE_EXECUTABLE = process.env.OPENCODE_REFRESH_NODE_EXECUTABLE || "node";
|
|
2506
|
+
var tokenProxyContext = new AsyncLocalStorage();
|
|
2507
|
+
var tokenProxyInstalled = false;
|
|
2508
|
+
var tokenProxyOriginalFetch = null;
|
|
2509
|
+
var refreshEndpointUrl = new URL(ANTHROPIC_REFRESH_ENDPOINT);
|
|
2510
|
+
var legacyRefreshEndpointUrl = new URL(LEGACY_ANTHROPIC_TOKEN_ENDPOINT);
|
|
2511
|
+
function buildRefreshRequestError(details) {
|
|
2512
|
+
return new Error(`Anthropic token refresh request failed. url=${ANTHROPIC_REFRESH_ENDPOINT}; details=${details}`);
|
|
2513
|
+
}
|
|
2514
|
+
function buildRefreshInvalidJsonError(body, details) {
|
|
2515
|
+
return new Error(`Anthropic token refresh returned invalid JSON. url=${ANTHROPIC_REFRESH_ENDPOINT}; body=${body}; details=${details}`);
|
|
2516
|
+
}
|
|
2517
|
+
function getRequestUrlString(input) {
|
|
2518
|
+
if (typeof input === "string") return input;
|
|
2519
|
+
if (input instanceof URL) return input.toString();
|
|
2520
|
+
return input.url;
|
|
2521
|
+
}
|
|
2522
|
+
function isAnthropicTokenEndpoint(input) {
|
|
2523
|
+
const rawUrl = getRequestUrlString(input);
|
|
2524
|
+
try {
|
|
2525
|
+
const url = new URL(rawUrl);
|
|
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();
|
|
2583
|
+
} catch {
|
|
2584
|
+
return rawUrl;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
function getRequestBodySource(input, init) {
|
|
2588
|
+
if (init?.body !== void 0) {
|
|
2589
|
+
return init.body;
|
|
2590
|
+
}
|
|
2591
|
+
if (input instanceof Request) {
|
|
2592
|
+
return input.body;
|
|
2593
|
+
}
|
|
2594
|
+
return void 0;
|
|
2595
|
+
}
|
|
2596
|
+
function stringifyBinaryBody(body) {
|
|
2597
|
+
if (body instanceof ArrayBuffer) {
|
|
2598
|
+
return Buffer.from(body).toString("utf8");
|
|
2599
|
+
}
|
|
2600
|
+
return Buffer.from(body.buffer, body.byteOffset, body.byteLength).toString("utf8");
|
|
2601
|
+
}
|
|
2602
|
+
async function getRequestBody(input, init) {
|
|
2603
|
+
const body = getRequestBodySource(input, init);
|
|
2604
|
+
if (typeof body === "string") return body;
|
|
2605
|
+
if (body instanceof URLSearchParams) return body.toString();
|
|
2606
|
+
if (body instanceof Uint8Array || body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
2607
|
+
return stringifyBinaryBody(body);
|
|
2608
|
+
}
|
|
2609
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return await body.text();
|
|
2610
|
+
if (body instanceof ReadableStream) return await new Response(body).text();
|
|
2611
|
+
if (input instanceof Request && init?.body === void 0) return await input.clone().text();
|
|
2612
|
+
if (body == null) return "";
|
|
2613
|
+
throw buildRefreshRequestError(`Unsupported token request body type: ${Object.prototype.toString.call(body)}`);
|
|
2614
|
+
}
|
|
2615
|
+
function getRequestMethod(input, init) {
|
|
2616
|
+
return init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
2617
|
+
}
|
|
2618
|
+
function shouldProxyTokenRequest(input) {
|
|
2619
|
+
return tokenProxyContext.getStore() === true && isAnthropicTokenEndpoint(input);
|
|
2620
|
+
}
|
|
2621
|
+
async function postAnthropicTokenViaNode(body) {
|
|
2622
|
+
const overriddenBody = applyAnthropicTokenRequestOverrides(body);
|
|
2623
|
+
let output;
|
|
2624
|
+
try {
|
|
2625
|
+
output = await runNodeTokenRequest({
|
|
2626
|
+
body: overriddenBody,
|
|
2627
|
+
endpoint: ANTHROPIC_REFRESH_ENDPOINT,
|
|
2628
|
+
executable: REFRESH_NODE_EXECUTABLE,
|
|
2629
|
+
timeoutMs: TOKEN_REFRESH_TIMEOUT_MS
|
|
2630
|
+
});
|
|
2631
|
+
} catch (error) {
|
|
2632
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
2633
|
+
throw buildRefreshRequestError(details);
|
|
2634
|
+
}
|
|
2635
|
+
let parsed;
|
|
2636
|
+
try {
|
|
2637
|
+
parsed = JSON.parse(output);
|
|
2638
|
+
} catch (error) {
|
|
2639
|
+
const details = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
2640
|
+
throw buildRefreshInvalidJsonError(output, details);
|
|
2641
|
+
}
|
|
2642
|
+
const result = parsed;
|
|
2643
|
+
if (!result.ok) {
|
|
2644
|
+
if (result.error) {
|
|
2645
|
+
throw buildRefreshRequestError(result.error);
|
|
2646
|
+
}
|
|
2647
|
+
throw buildRefreshRequestError(`Error: HTTP request failed. status=${result.status ?? 0}; url=${ANTHROPIC_REFRESH_ENDPOINT}; body=${result.body ?? ""}`);
|
|
2648
|
+
}
|
|
2649
|
+
return new Response(result.body ?? "", {
|
|
2650
|
+
status: 200,
|
|
2651
|
+
headers: {
|
|
2652
|
+
"content-type": "application/json"
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
function createAnthropicTokenProxyFetch(originalFetch) {
|
|
2657
|
+
return (async (input, init) => {
|
|
2658
|
+
if (!shouldProxyTokenRequest(input)) {
|
|
2659
|
+
return originalFetch(input, init);
|
|
2660
|
+
}
|
|
2661
|
+
const method = getRequestMethod(input, init).toUpperCase();
|
|
2662
|
+
if (method !== "POST") {
|
|
2663
|
+
throw buildRefreshRequestError(`Unsupported token endpoint method: ${method}`);
|
|
2664
|
+
}
|
|
2665
|
+
return await postAnthropicTokenViaNode(await getRequestBody(input, init));
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
function ensureAnthropicTokenProxyFetchInstalled() {
|
|
2669
|
+
if (tokenProxyInstalled) return;
|
|
2670
|
+
tokenProxyOriginalFetch = globalThis.fetch;
|
|
2671
|
+
globalThis.fetch = createAnthropicTokenProxyFetch(tokenProxyOriginalFetch);
|
|
2672
|
+
tokenProxyInstalled = true;
|
|
2673
|
+
}
|
|
2674
|
+
async function withAnthropicTokenProxyFetch(operation) {
|
|
2675
|
+
ensureAnthropicTokenProxyFetchInstalled();
|
|
2676
|
+
return await tokenProxyContext.run(true, operation);
|
|
2677
|
+
}
|
|
2678
|
+
async function fetchProfileWithSingleRetry(accessToken) {
|
|
2679
|
+
let profileResult = await fetchProfile(accessToken);
|
|
2680
|
+
if (profileResult.ok) {
|
|
2681
|
+
return profileResult;
|
|
2682
|
+
}
|
|
2683
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
2684
|
+
profileResult = await fetchProfile(accessToken);
|
|
2685
|
+
return profileResult;
|
|
2686
|
+
}
|
|
2381
2687
|
async function loginWithPiAi(callbacks) {
|
|
2382
|
-
const piCreds = await loginAnthropic({
|
|
2383
|
-
onAuth:
|
|
2688
|
+
const piCreds = await withAnthropicTokenProxyFetch(() => piAiOauth.loginAnthropic({
|
|
2689
|
+
onAuth: (info) => {
|
|
2690
|
+
callbacks.onAuth({
|
|
2691
|
+
...info,
|
|
2692
|
+
url: info.url ? rewriteAnthropicAuthUrl(info.url) : info.url
|
|
2693
|
+
});
|
|
2694
|
+
},
|
|
2384
2695
|
onPrompt: callbacks.onPrompt,
|
|
2385
2696
|
onProgress: callbacks.onProgress,
|
|
2386
2697
|
onManualCodeInput: callbacks.onManualCodeInput
|
|
2387
|
-
});
|
|
2698
|
+
}));
|
|
2388
2699
|
const base = fromPiAiCredentials(piCreds);
|
|
2389
|
-
|
|
2390
|
-
if (!profileResult.ok) {
|
|
2391
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
2392
|
-
profileResult = await fetchProfile(piCreds.access);
|
|
2393
|
-
}
|
|
2700
|
+
const profileResult = await fetchProfileWithSingleRetry(piCreds.access);
|
|
2394
2701
|
const profileData = profileResult.ok ? profileResult.data : void 0;
|
|
2395
2702
|
return {
|
|
2396
2703
|
...base,
|
|
@@ -2401,7 +2708,7 @@ async function loginWithPiAi(callbacks) {
|
|
|
2401
2708
|
};
|
|
2402
2709
|
}
|
|
2403
2710
|
async function refreshWithPiAi(currentRefreshToken) {
|
|
2404
|
-
const piCreds = await refreshAnthropicToken(currentRefreshToken);
|
|
2711
|
+
const piCreds = await withAnthropicTokenProxyFetch(() => piAiOauth.refreshAnthropicToken(currentRefreshToken));
|
|
2405
2712
|
return {
|
|
2406
2713
|
accessToken: piCreds.access,
|
|
2407
2714
|
refreshToken: piCreds.refresh,
|
|
@@ -2412,6 +2719,13 @@ var PI_AI_ADAPTER_SERVICE = ANTHROPIC_OAUTH_ADAPTER.serviceLogName;
|
|
|
2412
2719
|
|
|
2413
2720
|
// src/token.ts
|
|
2414
2721
|
var PERMANENT_FAILURE_HTTP_STATUSES = /* @__PURE__ */ new Set([400, 401, 403]);
|
|
2722
|
+
var PERMANENT_FAILURE_MESSAGE_PATTERNS = [
|
|
2723
|
+
/\binvalid_grant\b/i,
|
|
2724
|
+
/\binvalid_scope\b/i,
|
|
2725
|
+
/\bunauthorized_client\b/i,
|
|
2726
|
+
/\brefresh token\b.*\b(invalid|expired|revoked|no longer valid)\b/i,
|
|
2727
|
+
/\bauth(?:entication)?(?:[_\s-]+)?invalid\b/i
|
|
2728
|
+
];
|
|
2415
2729
|
var refreshMutexByAccountId = /* @__PURE__ */ new Map();
|
|
2416
2730
|
function isTokenExpired(account) {
|
|
2417
2731
|
if (!account.accessToken || !account.expiresAt) return true;
|
|
@@ -2428,7 +2742,9 @@ async function refreshToken(currentRefreshToken, accountId, client) {
|
|
|
2428
2742
|
} catch (error) {
|
|
2429
2743
|
const message = error instanceof Error ? error.message : String(error);
|
|
2430
2744
|
const statusMatch = message.match(/\b(400|401|403)\b/);
|
|
2431
|
-
const
|
|
2745
|
+
const hasPermanentStatus = statusMatch !== null && PERMANENT_FAILURE_HTTP_STATUSES.has(Number(statusMatch[1]));
|
|
2746
|
+
const hasPermanentMessage = PERMANENT_FAILURE_MESSAGE_PATTERNS.some((pattern) => pattern.test(message));
|
|
2747
|
+
const isPermanent = hasPermanentStatus || hasPermanentMessage;
|
|
2432
2748
|
await client.app.log({
|
|
2433
2749
|
body: {
|
|
2434
2750
|
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
@@ -2755,7 +3071,7 @@ function printUsageEntry(name, entry, isLast) {
|
|
|
2755
3071
|
return;
|
|
2756
3072
|
}
|
|
2757
3073
|
const bar = createProgressBar(entry.utilization);
|
|
2758
|
-
const reset = entry.
|
|
3074
|
+
const reset = entry.resets_at ? formatResetTime(entry.resets_at) : "";
|
|
2759
3075
|
console.log(` ${connector} ${name.padEnd(16)} ${bar}${reset}`);
|
|
2760
3076
|
}
|
|
2761
3077
|
function printQuotaReport(account, usage) {
|
|
@@ -3039,7 +3355,14 @@ async function checkAccountQuota(manager, account, client) {
|
|
|
3039
3355
|
}
|
|
3040
3356
|
const refreshResult = await manager.ensureValidToken(account.uuid, client);
|
|
3041
3357
|
if (!refreshResult.ok) {
|
|
3042
|
-
|
|
3358
|
+
await manager.markAuthFailure(account.uuid, refreshResult);
|
|
3359
|
+
await manager.refresh();
|
|
3360
|
+
const updatedAccount = manager.getAccounts().find((candidate) => candidate.uuid === account.uuid);
|
|
3361
|
+
if (!updatedAccount) {
|
|
3362
|
+
printQuotaError(account, refreshResult.permanent ? "Refresh failed permanently; account removed" : "Failed to refresh token");
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
printQuotaError(updatedAccount, updatedAccount.isAuthDisabled ? `${updatedAccount.authDisabledReason ?? "Auth disabled"} (refresh failed)` : "Failed to refresh token");
|
|
3043
3366
|
return;
|
|
3044
3367
|
}
|
|
3045
3368
|
await manager.refresh();
|
|
@@ -3307,11 +3630,30 @@ function buildBillingHeader(firstUserMessage) {
|
|
|
3307
3630
|
if (!version || !salt) return "";
|
|
3308
3631
|
const sampled = sampleCodeUnits(firstUserMessage, [4, 7, 20]);
|
|
3309
3632
|
const hash = createHash("sha256").update(`${salt}${sampled}${version}`).digest("hex").slice(0, 3);
|
|
3310
|
-
return `
|
|
3633
|
+
return `cc_version=${version}.${hash}; cc_entrypoint=cli; cch=00000;`;
|
|
3311
3634
|
}
|
|
3312
3635
|
var OPENCODE_CAMEL_RE = /OpenCode/g;
|
|
3313
3636
|
var OPENCODE_LOWER_RE = /(?<!\/)opencode/gi;
|
|
3314
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
|
+
}
|
|
3315
3657
|
function addToolPrefix(name) {
|
|
3316
3658
|
if (!ANTHROPIC_OAUTH_ADAPTER.transform.addToolPrefix) {
|
|
3317
3659
|
return name;
|
|
@@ -3337,7 +3679,7 @@ function processCompleteLines(buffer) {
|
|
|
3337
3679
|
`;
|
|
3338
3680
|
return { output, remaining };
|
|
3339
3681
|
}
|
|
3340
|
-
function buildRequestHeaders(input, init, accessToken) {
|
|
3682
|
+
function buildRequestHeaders(input, init, accessToken, bodyString) {
|
|
3341
3683
|
const headers = new Headers();
|
|
3342
3684
|
if (input instanceof Request) {
|
|
3343
3685
|
input.headers.forEach((value, key) => {
|
|
@@ -3369,6 +3711,13 @@ function buildRequestHeaders(input, init, accessToken) {
|
|
|
3369
3711
|
headers.set("user-agent", CLAUDE_CLI_USER_AGENT);
|
|
3370
3712
|
headers.set("anthropic-dangerous-direct-browser-access", "true");
|
|
3371
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
|
+
}
|
|
3372
3721
|
headers.delete("x-api-key");
|
|
3373
3722
|
return headers;
|
|
3374
3723
|
}
|
|
@@ -3547,8 +3896,9 @@ var AccountRuntimeFactory = class {
|
|
|
3547
3896
|
}
|
|
3548
3897
|
async executeTransformedFetch(input, init, accessToken) {
|
|
3549
3898
|
const transformedInput = transformRequestUrl(input);
|
|
3550
|
-
const
|
|
3551
|
-
const
|
|
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;
|
|
3552
3902
|
const response = await fetch(transformedInput, {
|
|
3553
3903
|
...init,
|
|
3554
3904
|
headers,
|
|
@@ -3579,24 +3929,6 @@ var AccountRuntimeFactory = class {
|
|
|
3579
3929
|
};
|
|
3580
3930
|
|
|
3581
3931
|
// src/index.ts
|
|
3582
|
-
function extractFirstUserText(input) {
|
|
3583
|
-
try {
|
|
3584
|
-
const raw = input;
|
|
3585
|
-
const messages = raw.messages ?? raw.request?.messages;
|
|
3586
|
-
if (!Array.isArray(messages)) return "";
|
|
3587
|
-
for (const msg of messages) {
|
|
3588
|
-
if (msg.role !== "user") continue;
|
|
3589
|
-
if (typeof msg.content === "string") return msg.content;
|
|
3590
|
-
if (Array.isArray(msg.content)) {
|
|
3591
|
-
for (const block of msg.content) {
|
|
3592
|
-
if (block.type === "text" && block.text) return block.text;
|
|
3593
|
-
}
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
} catch {
|
|
3597
|
-
}
|
|
3598
|
-
return "";
|
|
3599
|
-
}
|
|
3600
3932
|
function injectSystemPrompt(output) {
|
|
3601
3933
|
const systemPrompt = getSystemPrompt();
|
|
3602
3934
|
if (!Array.isArray(output.system)) {
|
|
@@ -3618,12 +3950,8 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
3618
3950
|
let cascadeStateManager = null;
|
|
3619
3951
|
let poolChainConfig = { pools: [], chains: [] };
|
|
3620
3952
|
return {
|
|
3621
|
-
"experimental.chat.system.transform": (
|
|
3953
|
+
"experimental.chat.system.transform": (_input, output) => {
|
|
3622
3954
|
injectSystemPrompt(output);
|
|
3623
|
-
const billingHeader = buildBillingHeader(extractFirstUserText(input));
|
|
3624
|
-
if (billingHeader && !output.system?.includes(billingHeader)) {
|
|
3625
|
-
output.system?.unshift(billingHeader);
|
|
3626
|
-
}
|
|
3627
3955
|
},
|
|
3628
3956
|
tool: {
|
|
3629
3957
|
[ANTHROPIC_OAUTH_ADAPTER.statusToolName]: tool({
|
package/dist/pi-ai-adapter.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ 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;
|
|
16
|
+
export declare function withAnthropicTokenProxyFetch<T>(operation: () => Promise<T>): Promise<T>;
|
|
17
|
+
export declare function resetAnthropicTokenProxyStateForTest(): void;
|
|
14
18
|
export declare function loginWithPiAi(callbacks: LoginWithPiAiCallbacks): Promise<Partial<StoredAccount>>;
|
|
15
19
|
export declare function refreshWithPiAi(currentRefreshToken: string): Promise<CredentialRefreshPatch>;
|
|
16
20
|
export declare const PI_AI_ADAPTER_SERVICE: string;
|
|
@@ -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;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface NodeTokenRequestOptions {
|
|
2
|
+
body: string;
|
|
3
|
+
endpoint: string;
|
|
4
|
+
executable: string;
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
}
|
|
7
|
+
type NodeTokenRequestRunner = (options: NodeTokenRequestOptions) => Promise<string>;
|
|
8
|
+
export declare function runNodeTokenRequest(options: NodeTokenRequestOptions): Promise<string>;
|
|
9
|
+
export declare function setNodeTokenRequestRunnerForTest(runner: NodeTokenRequestRunner | null): void;
|
|
10
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-anthropic-multi-account",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
42
|
+
"opencode-multi-account-core": "^0.2.6",
|
|
43
43
|
"@mariozechner/pi-ai": "^0.61.0",
|
|
44
44
|
"valibot": "^1.2.0"
|
|
45
45
|
},
|