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 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
- setConfigGetter(getConfig);
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 (!shouldProxyTokenRequest(input)) {
418
- return originalFetch(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));
419
453
  }
420
- const method = getRequestMethod(input, init).toUpperCase();
421
- if (method !== "POST") {
422
- throw buildRefreshRequestError(`Unsupported token endpoint method: ${method}`);
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 await postAnthropicTokenViaNode(await getRequestBody(input, init));
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(() => piAiOauth.loginAnthropic({
448
- onAuth: callbacks.onAuth,
449
- onPrompt: callbacks.onPrompt,
450
- onProgress: callbacks.onProgress,
451
- onManualCodeInput: callbacks.onManualCodeInput
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
- refreshToken
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?.type === "success" && callbackResult.refresh) {
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?.type === "success" && callbackResult.refresh) {
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
- const mgr = await AccountManager.create(store, emptyAuth, client);
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 TOOL_PREFIX_RESPONSE_RE = /"name"\s*:\s*"mcp_([^"]+)"/g;
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
- function addToolPrefix(name) {
1541
- if (!ANTHROPIC_OAUTH_ADAPTER.transform.addToolPrefix) {
1542
- return name;
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 (!name || name.startsWith(TOOL_PREFIX)) {
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 `${TOOL_PREFIX}${name}`;
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 stripToolPrefixFromLine(line) {
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
- return line.replace(TOOL_PREFIX_RESPONSE_RE, '"name": "$1"');
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: addToolPrefix(tool2.name)
1921
+ name: renameMaskedToolName(tool2.name, toolMaskMap)
1722
1922
  }));
1723
1923
  }
1724
- if (parsed.messages && Array.isArray(parsed.messages)) {
1725
- parsed.messages = parsed.messages.map((message) => {
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 fs } from "fs";
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 = join(getConfigDir2(), AUTH_JSON_FILENAME);
2218
+ const authPath = join2(getConfigDir2(), AUTH_JSON_FILENAME);
1976
2219
  let raw;
1977
2220
  try {
1978
- raw = await fs.readFile(authPath, "utf-8");
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 extractFirstUserText(input) {
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(extractFirstUserText(input));
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();