opencode-anthropic-multi-account 0.2.13 → 0.2.15
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 +328 -84
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10,6 +10,16 @@ import {
|
|
|
10
10
|
// src/account-manager.ts
|
|
11
11
|
import { createAccountManagerForProvider } from "opencode-multi-account-core";
|
|
12
12
|
|
|
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
|
+
// src/claims.ts
|
|
21
|
+
import { createClaimsManager } from "opencode-multi-account-core";
|
|
22
|
+
|
|
13
23
|
// src/constants.ts
|
|
14
24
|
import { anthropicOAuthAdapter } from "opencode-multi-account-core";
|
|
15
25
|
var ANTHROPIC_OAUTH_ADAPTER = anthropicOAuthAdapter;
|
|
@@ -21,10 +31,20 @@ var ANTHROPIC_BETA_HEADER = ANTHROPIC_OAUTH_ADAPTER.requestBetaHeader;
|
|
|
21
31
|
var CLAUDE_CLI_USER_AGENT = ANTHROPIC_OAUTH_ADAPTER.cliUserAgent;
|
|
22
32
|
var TOOL_PREFIX = ANTHROPIC_OAUTH_ADAPTER.toolPrefix;
|
|
23
33
|
var ACCOUNTS_FILENAME = ANTHROPIC_OAUTH_ADAPTER.accountStorageFilename;
|
|
34
|
+
var CLAIMS_FILENAME = "anthropic-multi-account-claims.json";
|
|
24
35
|
var PLAN_LABELS = ANTHROPIC_OAUTH_ADAPTER.planLabels;
|
|
25
36
|
var TOKEN_EXPIRY_BUFFER_MS = 6e4;
|
|
26
37
|
var TOKEN_REFRESH_TIMEOUT_MS = 3e4;
|
|
27
38
|
|
|
39
|
+
// src/claims.ts
|
|
40
|
+
var claimsManager = createClaimsManager(CLAIMS_FILENAME);
|
|
41
|
+
var {
|
|
42
|
+
isClaimedByOther,
|
|
43
|
+
readClaims,
|
|
44
|
+
releaseClaim,
|
|
45
|
+
writeClaim
|
|
46
|
+
} = claimsManager;
|
|
47
|
+
|
|
28
48
|
// src/pi-ai-adapter.ts
|
|
29
49
|
import { AsyncLocalStorage } from "async_hooks";
|
|
30
50
|
import * as piAiOauth from "@mariozechner/pi-ai/oauth";
|
|
@@ -118,31 +138,29 @@ async function runNodeTokenRequest(options) {
|
|
|
118
138
|
return await nodeTokenRequestRunner(options);
|
|
119
139
|
}
|
|
120
140
|
|
|
121
|
-
// src/utils.ts
|
|
122
|
-
import { setConfigGetter } from "opencode-multi-account-core";
|
|
123
|
-
|
|
124
|
-
// src/config.ts
|
|
125
|
-
import {
|
|
126
|
-
getConfig,
|
|
127
|
-
initCoreConfig,
|
|
128
|
-
loadConfig,
|
|
129
|
-
resetConfigCache,
|
|
130
|
-
updateConfigField
|
|
131
|
-
} from "opencode-multi-account-core";
|
|
132
|
-
initCoreConfig("claude-multiauth.json");
|
|
133
|
-
|
|
134
141
|
// src/utils.ts
|
|
135
142
|
import {
|
|
136
143
|
createMinimalClient,
|
|
137
|
-
debugLog,
|
|
138
144
|
formatWaitTime,
|
|
139
145
|
getAccountLabel,
|
|
140
146
|
getConfigDir,
|
|
141
147
|
getErrorCode,
|
|
142
|
-
showToast,
|
|
143
148
|
sleep
|
|
144
149
|
} from "opencode-multi-account-core";
|
|
145
|
-
|
|
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
|
+
}
|
|
146
164
|
|
|
147
165
|
// src/usage.ts
|
|
148
166
|
import * as v2 from "valibot";
|
|
@@ -319,6 +337,7 @@ function fromPiAiCredentials(creds) {
|
|
|
319
337
|
};
|
|
320
338
|
}
|
|
321
339
|
var ANTHROPIC_REFRESH_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
|
|
340
|
+
var ANTHROPIC_TOKEN_HOST = "platform.claude.com";
|
|
322
341
|
var REFRESH_NODE_EXECUTABLE = process.env.OPENCODE_REFRESH_NODE_EXECUTABLE || "node";
|
|
323
342
|
var tokenProxyContext = new AsyncLocalStorage();
|
|
324
343
|
var tokenProxyInstalled = false;
|
|
@@ -376,7 +395,18 @@ function getRequestMethod(input, init) {
|
|
|
376
395
|
return init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
377
396
|
}
|
|
378
397
|
function shouldProxyTokenRequest(input) {
|
|
379
|
-
return tokenProxyContext.getStore() === true && isAnthropicTokenEndpoint(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);
|
|
404
|
+
try {
|
|
405
|
+
const url = new URL(rawUrl);
|
|
406
|
+
return url.host === ANTHROPIC_TOKEN_HOST;
|
|
407
|
+
} catch {
|
|
408
|
+
return rawUrl.includes(ANTHROPIC_TOKEN_HOST);
|
|
409
|
+
}
|
|
380
410
|
}
|
|
381
411
|
async function postAnthropicTokenViaNode(body) {
|
|
382
412
|
let output;
|
|
@@ -414,14 +444,19 @@ async function postAnthropicTokenViaNode(body) {
|
|
|
414
444
|
}
|
|
415
445
|
function createAnthropicTokenProxyFetch(originalFetch) {
|
|
416
446
|
return (async (input, init) => {
|
|
417
|
-
if (
|
|
418
|
-
|
|
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));
|
|
419
453
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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 });
|
|
423
458
|
}
|
|
424
|
-
return
|
|
459
|
+
return originalFetch(input, init);
|
|
425
460
|
});
|
|
426
461
|
}
|
|
427
462
|
function ensureAnthropicTokenProxyFetchInstalled() {
|
|
@@ -430,9 +465,9 @@ function ensureAnthropicTokenProxyFetchInstalled() {
|
|
|
430
465
|
globalThis.fetch = createAnthropicTokenProxyFetch(tokenProxyOriginalFetch);
|
|
431
466
|
tokenProxyInstalled = true;
|
|
432
467
|
}
|
|
433
|
-
async function withAnthropicTokenProxyFetch(operation) {
|
|
468
|
+
async function withAnthropicTokenProxyFetch(operation, options) {
|
|
434
469
|
ensureAnthropicTokenProxyFetchInstalled();
|
|
435
|
-
return await tokenProxyContext.run(true, operation);
|
|
470
|
+
return await tokenProxyContext.run({ proxyTokenRequests: true, userAgent: options?.userAgent }, operation);
|
|
436
471
|
}
|
|
437
472
|
async function fetchProfileWithSingleRetry(accessToken) {
|
|
438
473
|
let profileResult = await fetchProfile(accessToken);
|
|
@@ -444,12 +479,15 @@ async function fetchProfileWithSingleRetry(accessToken) {
|
|
|
444
479
|
return profileResult;
|
|
445
480
|
}
|
|
446
481
|
async function loginWithPiAi(callbacks) {
|
|
447
|
-
const piCreds = await withAnthropicTokenProxyFetch(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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 }
|
|
490
|
+
);
|
|
453
491
|
const base = fromPiAiCredentials(piCreds);
|
|
454
492
|
const profileResult = await fetchProfileWithSingleRetry(piCreds.access);
|
|
455
493
|
const profileData = profileResult.ok ? profileResult.data : void 0;
|
|
@@ -520,8 +558,12 @@ async function refreshToken(currentRefreshToken, accountId, client) {
|
|
|
520
558
|
// src/account-manager.ts
|
|
521
559
|
var AccountManager = createAccountManagerForProvider({
|
|
522
560
|
providerAuthId: "anthropic",
|
|
561
|
+
getConfig,
|
|
523
562
|
isTokenExpired,
|
|
524
|
-
|
|
563
|
+
isClaimedByOther,
|
|
564
|
+
readClaims,
|
|
565
|
+
refreshToken,
|
|
566
|
+
writeClaim
|
|
525
567
|
});
|
|
526
568
|
|
|
527
569
|
// src/executor.ts
|
|
@@ -947,18 +989,7 @@ function promptLine(message) {
|
|
|
947
989
|
function normalizePromptMessage(prompt) {
|
|
948
990
|
return prompt.message ?? prompt.text ?? prompt.label ?? prompt.title ?? prompt.placeholder ?? "Continue authentication: ";
|
|
949
991
|
}
|
|
950
|
-
var ANTHROPIC_TOKEN_HOST = "platform.claude.com";
|
|
951
992
|
async function startPiAiFlow() {
|
|
952
|
-
const originalFetch = globalThis.fetch;
|
|
953
|
-
globalThis.fetch = ((input, init) => {
|
|
954
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
955
|
-
if (url.includes(ANTHROPIC_TOKEN_HOST)) {
|
|
956
|
-
const headers = new Headers(init?.headers);
|
|
957
|
-
headers.set("user-agent", CLAUDE_CLI_USER_AGENT);
|
|
958
|
-
return originalFetch(input, { ...init, headers });
|
|
959
|
-
}
|
|
960
|
-
return originalFetch(input, init);
|
|
961
|
-
});
|
|
962
993
|
try {
|
|
963
994
|
const completedAccount = await loginWithPiAi({
|
|
964
995
|
onAuth: (info) => {
|
|
@@ -975,7 +1006,8 @@ ${instruction}${urlLine}
|
|
|
975
1006
|
onPrompt: async (prompt) => {
|
|
976
1007
|
const text = normalizePromptMessage(prompt);
|
|
977
1008
|
return promptLine(text.endsWith(":") || text.endsWith("?") ? `${text} ` : `${text}: `);
|
|
978
|
-
}
|
|
1009
|
+
},
|
|
1010
|
+
userAgent: CLAUDE_CLI_USER_AGENT
|
|
979
1011
|
});
|
|
980
1012
|
const completedResult = asOAuthCallbackResponse(completedAccount);
|
|
981
1013
|
const accountEmail = completedAccount.email;
|
|
@@ -988,8 +1020,6 @@ ${instruction}${urlLine}
|
|
|
988
1020
|
};
|
|
989
1021
|
} catch {
|
|
990
1022
|
return makeFailedFlowResult("Failed to start OAuth flow");
|
|
991
|
-
} finally {
|
|
992
|
-
globalThis.fetch = originalFetch;
|
|
993
1023
|
}
|
|
994
1024
|
}
|
|
995
1025
|
function wrapCallbackWithAccountReplace(result, manager, targetAccount) {
|
|
@@ -998,7 +1028,7 @@ function wrapCallbackWithAccountReplace(result, manager, targetAccount) {
|
|
|
998
1028
|
...result,
|
|
999
1029
|
callback: async function(code) {
|
|
1000
1030
|
const callbackResult = await originalCallback(code);
|
|
1001
|
-
if (callbackResult
|
|
1031
|
+
if (callbackResult.type === "success" && callbackResult.refresh) {
|
|
1002
1032
|
if (targetAccount.uuid) {
|
|
1003
1033
|
await manager.replaceAccountCredentials(targetAccount.uuid, toOAuthCredentials(callbackResult));
|
|
1004
1034
|
}
|
|
@@ -1017,7 +1047,7 @@ function wrapCallbackWithManagerSync(result, manager) {
|
|
|
1017
1047
|
...result,
|
|
1018
1048
|
callback: async function(code) {
|
|
1019
1049
|
const callbackResult = await originalCallback(code);
|
|
1020
|
-
if (callbackResult
|
|
1050
|
+
if (callbackResult.type === "success" && callbackResult.refresh) {
|
|
1021
1051
|
const auth = toOAuthCredentials(callbackResult);
|
|
1022
1052
|
if (manager) {
|
|
1023
1053
|
const countBefore = manager.getAccounts().length;
|
|
@@ -1082,8 +1112,7 @@ async function loadManagerFromDisk(client) {
|
|
|
1082
1112
|
const stored = await store.load();
|
|
1083
1113
|
if (stored.accounts.length === 0) return null;
|
|
1084
1114
|
const emptyAuth = { type: "oauth", refresh: "", access: "", expires: 0 };
|
|
1085
|
-
|
|
1086
|
-
return mgr;
|
|
1115
|
+
return AccountManager.create(store, emptyAuth, client);
|
|
1087
1116
|
}
|
|
1088
1117
|
async function runAccountManagementMenu(manager, client) {
|
|
1089
1118
|
while (true) {
|
|
@@ -1531,20 +1560,169 @@ function buildBillingHeader(firstUserMessage) {
|
|
|
1531
1560
|
}
|
|
1532
1561
|
var OPENCODE_CAMEL_RE = /OpenCode/g;
|
|
1533
1562
|
var OPENCODE_LOWER_RE = /(?<!\/)opencode/gi;
|
|
1534
|
-
var
|
|
1563
|
+
var TOOL_MASK_PREFIX = "tool_";
|
|
1535
1564
|
var PARAGRAPH_REMOVAL_ANCHORS = [
|
|
1536
1565
|
"github.com/anomalyco/opencode",
|
|
1537
1566
|
"opencode.ai/docs"
|
|
1538
1567
|
];
|
|
1539
1568
|
var BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1569
|
+
var DOCUMENTED_BUILTIN_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1570
|
+
"Agent",
|
|
1571
|
+
"AskUserQuestion",
|
|
1572
|
+
"Bash",
|
|
1573
|
+
"CronCreate",
|
|
1574
|
+
"CronDelete",
|
|
1575
|
+
"CronList",
|
|
1576
|
+
"Edit",
|
|
1577
|
+
"EnterPlanMode",
|
|
1578
|
+
"EnterWorktree",
|
|
1579
|
+
"ExitPlanMode",
|
|
1580
|
+
"ExitWorktree",
|
|
1581
|
+
"Glob",
|
|
1582
|
+
"Grep",
|
|
1583
|
+
"ListMcpResourcesTool",
|
|
1584
|
+
"LSP",
|
|
1585
|
+
"Monitor",
|
|
1586
|
+
"NotebookEdit",
|
|
1587
|
+
"PowerShell",
|
|
1588
|
+
"Read",
|
|
1589
|
+
"ReadMcpResourceTool",
|
|
1590
|
+
"SendMessage",
|
|
1591
|
+
"Skill",
|
|
1592
|
+
"TaskCreate",
|
|
1593
|
+
"TaskGet",
|
|
1594
|
+
"TaskList",
|
|
1595
|
+
"TaskOutput",
|
|
1596
|
+
"TaskStop",
|
|
1597
|
+
"TaskUpdate",
|
|
1598
|
+
"TeamCreate",
|
|
1599
|
+
"TeamDelete",
|
|
1600
|
+
"TodoWrite",
|
|
1601
|
+
"ToolSearch",
|
|
1602
|
+
"WebFetch",
|
|
1603
|
+
"WebSearch",
|
|
1604
|
+
"Write"
|
|
1605
|
+
]);
|
|
1606
|
+
function isTypedTool(tool2) {
|
|
1607
|
+
return typeof tool2.type === "string" && tool2.type.trim().length > 0;
|
|
1608
|
+
}
|
|
1609
|
+
function shouldMaskToolName(name) {
|
|
1610
|
+
if (!name) {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
return !DOCUMENTED_BUILTIN_TOOL_NAMES.has(name) && !name.startsWith(TOOL_MASK_PREFIX);
|
|
1614
|
+
}
|
|
1615
|
+
function extractFirstUserTextFromMessageContent(content) {
|
|
1616
|
+
if (typeof content === "string") {
|
|
1617
|
+
return content;
|
|
1618
|
+
}
|
|
1619
|
+
if (!Array.isArray(content)) {
|
|
1620
|
+
return "";
|
|
1621
|
+
}
|
|
1622
|
+
return content.filter((block) => isRecord(block) && block.type === "text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n");
|
|
1623
|
+
}
|
|
1624
|
+
function extractFirstUserText(parsed) {
|
|
1625
|
+
if (!Array.isArray(parsed.messages)) {
|
|
1626
|
+
return "";
|
|
1627
|
+
}
|
|
1628
|
+
const firstUserMessage = parsed.messages.find((message) => message.role === "user");
|
|
1629
|
+
if (!firstUserMessage) {
|
|
1630
|
+
return "";
|
|
1631
|
+
}
|
|
1632
|
+
return extractFirstUserTextFromMessageContent(firstUserMessage.content).trim();
|
|
1633
|
+
}
|
|
1634
|
+
function buildMaskedToolName(seed, toolName, length = 8) {
|
|
1635
|
+
const digest = createHash("sha256").update(`tool-mask:${seed}:${toolName}`).digest("hex").slice(0, length);
|
|
1636
|
+
return `${TOOL_MASK_PREFIX}${digest}`;
|
|
1637
|
+
}
|
|
1638
|
+
function collectMaskCandidates(parsed) {
|
|
1639
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1640
|
+
if (Array.isArray(parsed.tools)) {
|
|
1641
|
+
for (const tool2 of parsed.tools) {
|
|
1642
|
+
if (!isRecord(tool2) || isTypedTool(tool2) || !shouldMaskToolName(tool2.name)) {
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
candidates.add(tool2.name);
|
|
1646
|
+
}
|
|
1543
1647
|
}
|
|
1544
|
-
if (
|
|
1648
|
+
if (Array.isArray(parsed.messages)) {
|
|
1649
|
+
for (const message of parsed.messages) {
|
|
1650
|
+
if (!Array.isArray(message.content)) {
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
for (const contentBlock of message.content) {
|
|
1654
|
+
if (contentBlock.type !== "tool_use" || !shouldMaskToolName(contentBlock.name)) {
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
candidates.add(contentBlock.name);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (parsed.tool_choice?.type === "tool" && shouldMaskToolName(parsed.tool_choice.name)) {
|
|
1662
|
+
candidates.add(parsed.tool_choice.name);
|
|
1663
|
+
}
|
|
1664
|
+
return [...candidates];
|
|
1665
|
+
}
|
|
1666
|
+
function buildToolMaskMap(parsed) {
|
|
1667
|
+
const maskMap = /* @__PURE__ */ new Map();
|
|
1668
|
+
const candidates = collectMaskCandidates(parsed);
|
|
1669
|
+
const firstUserText = extractFirstUserText(parsed);
|
|
1670
|
+
const usedMaskedNames = /* @__PURE__ */ new Set();
|
|
1671
|
+
for (const candidate of candidates) {
|
|
1672
|
+
let hashLength = 8;
|
|
1673
|
+
let maskedName = buildMaskedToolName(firstUserText, candidate, hashLength);
|
|
1674
|
+
while (usedMaskedNames.has(maskedName)) {
|
|
1675
|
+
hashLength += 2;
|
|
1676
|
+
maskedName = buildMaskedToolName(firstUserText, candidate, hashLength);
|
|
1677
|
+
}
|
|
1678
|
+
maskMap.set(candidate, maskedName);
|
|
1679
|
+
usedMaskedNames.add(maskedName);
|
|
1680
|
+
}
|
|
1681
|
+
return maskMap;
|
|
1682
|
+
}
|
|
1683
|
+
function extractRequestToolMaskMap(body) {
|
|
1684
|
+
if (!body) {
|
|
1685
|
+
return /* @__PURE__ */ new Map();
|
|
1686
|
+
}
|
|
1687
|
+
try {
|
|
1688
|
+
const parsed = JSON.parse(body);
|
|
1689
|
+
return buildToolMaskMap(parsed);
|
|
1690
|
+
} catch {
|
|
1691
|
+
return /* @__PURE__ */ new Map();
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
function renameMaskedToolName(name, maskMap) {
|
|
1695
|
+
if (!name) {
|
|
1545
1696
|
return name;
|
|
1546
1697
|
}
|
|
1547
|
-
return
|
|
1698
|
+
return maskMap.get(name) ?? name;
|
|
1699
|
+
}
|
|
1700
|
+
function remapToolUseNames(messages, maskMap) {
|
|
1701
|
+
if (!Array.isArray(messages)) {
|
|
1702
|
+
return messages;
|
|
1703
|
+
}
|
|
1704
|
+
return messages.map((message) => {
|
|
1705
|
+
if (!Array.isArray(message.content)) {
|
|
1706
|
+
return message;
|
|
1707
|
+
}
|
|
1708
|
+
return {
|
|
1709
|
+
...message,
|
|
1710
|
+
content: message.content.map((contentBlock) => {
|
|
1711
|
+
if (contentBlock.type !== "tool_use" || !contentBlock.name) {
|
|
1712
|
+
return contentBlock;
|
|
1713
|
+
}
|
|
1714
|
+
const nextName = renameMaskedToolName(contentBlock.name, maskMap);
|
|
1715
|
+
return nextName === contentBlock.name ? contentBlock : { ...contentBlock, name: nextName };
|
|
1716
|
+
})
|
|
1717
|
+
};
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
function remapToolChoice(toolChoice, maskMap) {
|
|
1721
|
+
if (!toolChoice || toolChoice.type !== "tool" || typeof toolChoice.name !== "string") {
|
|
1722
|
+
return toolChoice;
|
|
1723
|
+
}
|
|
1724
|
+
const nextName = renameMaskedToolName(toolChoice.name, maskMap);
|
|
1725
|
+
return nextName === toolChoice.name ? toolChoice : { ...toolChoice, name: nextName };
|
|
1548
1726
|
}
|
|
1549
1727
|
function isRecord(value) {
|
|
1550
1728
|
return typeof value === "object" && value !== null;
|
|
@@ -1655,19 +1833,40 @@ function relocateSystemTextToFirstUser(parsed, systemEntries) {
|
|
|
1655
1833
|
parsed.messages = nextMessages;
|
|
1656
1834
|
return preservedEntries;
|
|
1657
1835
|
}
|
|
1658
|
-
function
|
|
1836
|
+
function extractToolNamesFromRequestBody(body) {
|
|
1837
|
+
if (!body) {
|
|
1838
|
+
return [];
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
const parsed = JSON.parse(body);
|
|
1842
|
+
if (!Array.isArray(parsed.tools)) {
|
|
1843
|
+
return [];
|
|
1844
|
+
}
|
|
1845
|
+
return parsed.tools.map((tool2) => typeof tool2.name === "string" ? tool2.name : null).filter((toolName) => Boolean(toolName));
|
|
1846
|
+
} catch {
|
|
1847
|
+
return [];
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
function stripToolPrefixFromLine(line, maskMap) {
|
|
1659
1851
|
if (!ANTHROPIC_OAUTH_ADAPTER.transform.stripToolPrefixInResponse) {
|
|
1660
1852
|
return line;
|
|
1661
1853
|
}
|
|
1662
|
-
|
|
1854
|
+
let nextLine = line;
|
|
1855
|
+
for (const [originalName, maskedName] of maskMap) {
|
|
1856
|
+
nextLine = nextLine.replace(
|
|
1857
|
+
new RegExp(`"name"\\s*:\\s*"${maskedName}"`, "g"),
|
|
1858
|
+
`"name": "${originalName}"`
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
return nextLine;
|
|
1663
1862
|
}
|
|
1664
|
-
function processCompleteLines(buffer) {
|
|
1863
|
+
function processCompleteLines(buffer, maskMap) {
|
|
1665
1864
|
const lines = buffer.split("\n");
|
|
1666
1865
|
const remaining = lines.pop() ?? "";
|
|
1667
1866
|
if (lines.length === 0) {
|
|
1668
1867
|
return { output: "", remaining };
|
|
1669
1868
|
}
|
|
1670
|
-
const output = `${lines.map(stripToolPrefixFromLine).join("\n")}
|
|
1869
|
+
const output = `${lines.map((line) => stripToolPrefixFromLine(line, maskMap)).join("\n")}
|
|
1671
1870
|
`;
|
|
1672
1871
|
return { output, remaining };
|
|
1673
1872
|
}
|
|
@@ -1711,6 +1910,7 @@ function transformRequestBody(body) {
|
|
|
1711
1910
|
if (!body) return body;
|
|
1712
1911
|
try {
|
|
1713
1912
|
const parsed = JSON.parse(body);
|
|
1913
|
+
const toolMaskMap = buildToolMaskMap(parsed);
|
|
1714
1914
|
if (ANTHROPIC_OAUTH_ADAPTER.transform.rewriteOpenCodeBranding) {
|
|
1715
1915
|
const normalizedSystemEntries = normalizeSystemEntries(parsed.system);
|
|
1716
1916
|
parsed.system = relocateSystemTextToFirstUser(parsed, normalizedSystemEntries);
|
|
@@ -1718,22 +1918,11 @@ function transformRequestBody(body) {
|
|
|
1718
1918
|
if (parsed.tools && Array.isArray(parsed.tools)) {
|
|
1719
1919
|
parsed.tools = parsed.tools.map((tool2) => ({
|
|
1720
1920
|
...tool2,
|
|
1721
|
-
name:
|
|
1921
|
+
name: renameMaskedToolName(tool2.name, toolMaskMap)
|
|
1722
1922
|
}));
|
|
1723
1923
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
if (message.content && Array.isArray(message.content)) {
|
|
1727
|
-
message.content = message.content.map((contentBlock) => {
|
|
1728
|
-
if (contentBlock.type === "tool_use" && contentBlock.name) {
|
|
1729
|
-
return { ...contentBlock, name: addToolPrefix(contentBlock.name) };
|
|
1730
|
-
}
|
|
1731
|
-
return contentBlock;
|
|
1732
|
-
});
|
|
1733
|
-
}
|
|
1734
|
-
return message;
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1924
|
+
parsed.messages = remapToolUseNames(parsed.messages, toolMaskMap);
|
|
1925
|
+
parsed.tool_choice = remapToolChoice(parsed.tool_choice, toolMaskMap);
|
|
1737
1926
|
return JSON.stringify(parsed);
|
|
1738
1927
|
} catch {
|
|
1739
1928
|
return body;
|
|
@@ -1767,7 +1956,7 @@ function transformRequestUrl(input) {
|
|
|
1767
1956
|
}
|
|
1768
1957
|
return input;
|
|
1769
1958
|
}
|
|
1770
|
-
function createResponseStreamTransform(response) {
|
|
1959
|
+
function createResponseStreamTransform(response, maskMap = /* @__PURE__ */ new Map()) {
|
|
1771
1960
|
if (!response.body) return response;
|
|
1772
1961
|
const reader = response.body.getReader();
|
|
1773
1962
|
const decoder = new TextDecoder();
|
|
@@ -1781,14 +1970,14 @@ function createResponseStreamTransform(response) {
|
|
|
1781
1970
|
if (done) {
|
|
1782
1971
|
buffer += decoder.decode();
|
|
1783
1972
|
if (buffer) {
|
|
1784
|
-
controller.enqueue(encoder.encode(stripToolPrefixFromLine(buffer)));
|
|
1973
|
+
controller.enqueue(encoder.encode(stripToolPrefixFromLine(buffer, maskMap)));
|
|
1785
1974
|
buffer = "";
|
|
1786
1975
|
}
|
|
1787
1976
|
controller.close();
|
|
1788
1977
|
return;
|
|
1789
1978
|
}
|
|
1790
1979
|
buffer += decoder.decode(value, { stream: true });
|
|
1791
|
-
const { output, remaining } = processCompleteLines(buffer);
|
|
1980
|
+
const { output, remaining } = processCompleteLines(buffer, maskMap);
|
|
1792
1981
|
buffer = remaining;
|
|
1793
1982
|
if (output) {
|
|
1794
1983
|
controller.enqueue(encoder.encode(output));
|
|
@@ -1827,6 +2016,55 @@ var ProactiveRefreshQueue = createProactiveRefreshQueueForProvider({
|
|
|
1827
2016
|
|
|
1828
2017
|
// src/runtime-factory.ts
|
|
1829
2018
|
import { TokenRefreshError } from "opencode-multi-account-core";
|
|
2019
|
+
|
|
2020
|
+
// src/tool-observation.ts
|
|
2021
|
+
import { promises as fs } from "fs";
|
|
2022
|
+
import { dirname, join } from "path";
|
|
2023
|
+
var OBSERVED_TOOL_FILE = "anthropic-observed-tools.json";
|
|
2024
|
+
var FILE_MODE = 384;
|
|
2025
|
+
function getObservedToolPath() {
|
|
2026
|
+
return join(getConfigDir(), OBSERVED_TOOL_FILE);
|
|
2027
|
+
}
|
|
2028
|
+
async function loadObservedToolInventory() {
|
|
2029
|
+
try {
|
|
2030
|
+
const content = await fs.readFile(getObservedToolPath(), "utf8");
|
|
2031
|
+
const parsed = JSON.parse(content);
|
|
2032
|
+
return typeof parsed === "object" && parsed && typeof parsed.observedTools === "object" ? parsed : { observedTools: {} };
|
|
2033
|
+
} catch {
|
|
2034
|
+
return { observedTools: {} };
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
async function saveObservedToolInventory(inventory) {
|
|
2038
|
+
const targetPath = getObservedToolPath();
|
|
2039
|
+
await fs.mkdir(dirname(targetPath), { recursive: true });
|
|
2040
|
+
await fs.writeFile(targetPath, `${JSON.stringify(inventory, null, 2)}
|
|
2041
|
+
`, { mode: FILE_MODE });
|
|
2042
|
+
await fs.chmod(targetPath, FILE_MODE).catch(() => {
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
async function recordObservedToolNames(toolNames) {
|
|
2046
|
+
if (toolNames.length === 0) {
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
const inventory = await loadObservedToolInventory();
|
|
2050
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2051
|
+
for (const toolName of toolNames) {
|
|
2052
|
+
const entry = inventory.observedTools[toolName];
|
|
2053
|
+
if (!entry) {
|
|
2054
|
+
inventory.observedTools[toolName] = {
|
|
2055
|
+
count: 1,
|
|
2056
|
+
firstSeenAt: now,
|
|
2057
|
+
lastSeenAt: now
|
|
2058
|
+
};
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
entry.count += 1;
|
|
2062
|
+
entry.lastSeenAt = now;
|
|
2063
|
+
}
|
|
2064
|
+
await saveObservedToolInventory(inventory);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// src/runtime-factory.ts
|
|
1830
2068
|
var TOKEN_REFRESH_PERMANENT_FAILURE_STATUS = 401;
|
|
1831
2069
|
var AccountRuntimeFactory = class {
|
|
1832
2070
|
constructor(store, client) {
|
|
@@ -1893,6 +2131,11 @@ var AccountRuntimeFactory = class {
|
|
|
1893
2131
|
const modelId = extractModelIdFromBody(init?.body);
|
|
1894
2132
|
const excludedBetas2 = getExcludedBetas(modelId);
|
|
1895
2133
|
const headers = buildRequestHeaders(transformedInput, init, accessToken, modelId, excludedBetas2);
|
|
2134
|
+
if (typeof init?.body === "string") {
|
|
2135
|
+
void recordObservedToolNames(extractToolNamesFromRequestBody(init.body)).catch(() => {
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
const toolMaskMap = typeof init?.body === "string" ? extractRequestToolMaskMap(init.body) : /* @__PURE__ */ new Map();
|
|
1896
2139
|
const transformedBody = typeof init?.body === "string" ? transformRequestBody(init.body) : init?.body;
|
|
1897
2140
|
let response = await fetch(transformedInput, {
|
|
1898
2141
|
...init,
|
|
@@ -1925,7 +2168,7 @@ var AccountRuntimeFactory = class {
|
|
|
1925
2168
|
body: transformedBody
|
|
1926
2169
|
});
|
|
1927
2170
|
}
|
|
1928
|
-
return createResponseStreamTransform(response);
|
|
2171
|
+
return createResponseStreamTransform(response, toolMaskMap);
|
|
1929
2172
|
}
|
|
1930
2173
|
async createRuntime(uuid) {
|
|
1931
2174
|
const fetchWithAccount = async (input, init) => {
|
|
@@ -1950,8 +2193,8 @@ var AccountRuntimeFactory = class {
|
|
|
1950
2193
|
};
|
|
1951
2194
|
|
|
1952
2195
|
// src/bootstrap-auth.ts
|
|
1953
|
-
import { promises as
|
|
1954
|
-
import { join } from "path";
|
|
2196
|
+
import { promises as fs2 } from "fs";
|
|
2197
|
+
import { join as join2 } from "path";
|
|
1955
2198
|
import { getConfigDir as getConfigDir2 } from "opencode-multi-account-core";
|
|
1956
2199
|
var AUTH_JSON_FILENAME = "auth.json";
|
|
1957
2200
|
function hasCompleteOAuthCredential(account) {
|
|
@@ -1972,10 +2215,10 @@ function selectBootstrapAccount(accounts, activeAccountUuid) {
|
|
|
1972
2215
|
return firstUsableAccount ?? completeAccounts[0] ?? null;
|
|
1973
2216
|
}
|
|
1974
2217
|
async function readCurrentAuth(providerId) {
|
|
1975
|
-
const authPath =
|
|
2218
|
+
const authPath = join2(getConfigDir2(), AUTH_JSON_FILENAME);
|
|
1976
2219
|
let raw;
|
|
1977
2220
|
try {
|
|
1978
|
-
raw = await
|
|
2221
|
+
raw = await fs2.readFile(authPath, "utf-8");
|
|
1979
2222
|
} catch {
|
|
1980
2223
|
return null;
|
|
1981
2224
|
}
|
|
@@ -2034,7 +2277,7 @@ var EMPTY_OAUTH_CREDENTIALS = {
|
|
|
2034
2277
|
access: "",
|
|
2035
2278
|
expires: 0
|
|
2036
2279
|
};
|
|
2037
|
-
function
|
|
2280
|
+
function extractFirstUserText2(input) {
|
|
2038
2281
|
try {
|
|
2039
2282
|
const raw = input;
|
|
2040
2283
|
const messages = raw.messages ?? raw.request?.messages;
|
|
@@ -2128,7 +2371,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2128
2371
|
return {
|
|
2129
2372
|
"experimental.chat.system.transform": (input, output) => {
|
|
2130
2373
|
injectSystemPrompt(output);
|
|
2131
|
-
const billingHeader = buildBillingHeader(
|
|
2374
|
+
const billingHeader = buildBillingHeader(extractFirstUserText2(input));
|
|
2132
2375
|
if (billingHeader && !output.system?.includes(billingHeader)) {
|
|
2133
2376
|
output.system?.unshift(billingHeader);
|
|
2134
2377
|
}
|
|
@@ -2237,6 +2480,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2237
2480
|
}
|
|
2238
2481
|
return {
|
|
2239
2482
|
apiKey: "",
|
|
2483
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
2240
2484
|
"chat.headers": async (input, output) => {
|
|
2241
2485
|
if (input.provider?.info?.id !== ANTHROPIC_OAUTH_ADAPTER.authProviderId) return;
|
|
2242
2486
|
output.headers["user-agent"] = getUserAgent();
|