@juspay/neurolink 12.7.2 → 12.7.4
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/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +422 -414
- package/dist/cli/commands/auth.js +252 -125
- package/dist/cli/commands/proxy.js +115 -75
- package/dist/cli/factories/authCommandFactory.js +8 -11
- package/dist/proxy/codexAccountUsage.d.ts +5 -0
- package/dist/proxy/codexAccountUsage.js +30 -0
- package/dist/proxy/codexFallback.d.ts +26 -0
- package/dist/proxy/codexFallback.js +371 -0
- package/dist/proxy/proxyActivity.js +12 -1
- package/dist/proxy/usageStats.d.ts +2 -2
- package/dist/proxy/usageStats.js +66 -10
- package/dist/server/routes/claudeProxyRoutes.d.ts +25 -2
- package/dist/server/routes/claudeProxyRoutes.js +413 -108
- package/dist/server/routes/codexProxyRoutes.d.ts +10 -2
- package/dist/server/routes/codexProxyRoutes.js +192 -59
- package/dist/types/claudeProxy.d.ts +16 -0
- package/dist/types/claudeProxy.js +4 -0
- package/dist/types/cli.d.ts +37 -5
- package/dist/types/codex.d.ts +62 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/proxy.d.ts +37 -0
- package/package.json +1 -1
|
@@ -24,7 +24,8 @@ import { formatUptime, isProcessRunning, StateFileManager, } from "../utils/serv
|
|
|
24
24
|
import { configureProxyKeepAliveDispatcher } from "../../proxy/proxyDispatcher.js";
|
|
25
25
|
import { ProxyRuntimeConfigStore } from "../../proxy/runtimeConfig.js";
|
|
26
26
|
import { startProxyLogCleanupScheduler } from "../../proxy/logCleanupScheduler.js";
|
|
27
|
-
import { anthropicAccountKeysEqual, createAccountAllowlist,
|
|
27
|
+
import { anthropicAccountKeysEqual, createAccountAllowlist, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
|
|
28
|
+
import { resolveProxyStatusAccountIdentity } from "../../proxy/codexAccountUsage.js";
|
|
28
29
|
import { beginProxyRequest, getProxyActivitySnapshot, trackProxyResponse, } from "../../proxy/proxyActivity.js";
|
|
29
30
|
import { flushProxyLifecycleEvents, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../proxy/proxyLifecycle.js";
|
|
30
31
|
import { describeInstallFailure, getGlobalInstallArgs, isTransientInstallFailure, resolveGlobalInstaller, validateInstalledVersion, } from "../../proxy/globalInstaller.js";
|
|
@@ -65,11 +66,9 @@ const gatedShareRequests = new WeakSet();
|
|
|
65
66
|
const PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
66
67
|
/** How long shutdown waits on the share listener before moving on. */
|
|
67
68
|
const SHARE_LISTENER_CLOSE_TIMEOUT_MS = 10_000;
|
|
68
|
-
const LEGACY_STATUS_ACCOUNT_CACHE_TTL_MS = 5_000;
|
|
69
69
|
const PROXY_STATUS_TOKEN_READ_TIMEOUT_MS = 2_000;
|
|
70
70
|
const PROXY_STATUS_RECONCILE_TIMEOUT_MS = 750;
|
|
71
71
|
const PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS = 750;
|
|
72
|
-
let legacyStatusAccountCache;
|
|
73
72
|
// Allowed drift between a pid's OS-reported start time and the persisted
|
|
74
73
|
// ProxySupervisorState.startTime before processLooksLikeProxySupervisor
|
|
75
74
|
// treats it as a confident mismatch (recycled pid). Generous on purpose:
|
|
@@ -385,34 +384,6 @@ async function resolveStatusPrimaryAccount(proxyConfig) {
|
|
|
385
384
|
source: "fallback",
|
|
386
385
|
};
|
|
387
386
|
}
|
|
388
|
-
async function resolveLegacyStatusAccountLabel(storedAnthropicAccountCount) {
|
|
389
|
-
if (storedAnthropicAccountCount !== 0) {
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
const credentialsPath = join(homedir(), ".neurolink", "anthropic-credentials.json");
|
|
393
|
-
const now = Date.now();
|
|
394
|
-
if (legacyStatusAccountCache?.credentialsPath === credentialsPath &&
|
|
395
|
-
legacyStatusAccountCache.expiresAt > now) {
|
|
396
|
-
return legacyStatusAccountCache.label;
|
|
397
|
-
}
|
|
398
|
-
let label = null;
|
|
399
|
-
try {
|
|
400
|
-
const { readFile } = await import("node:fs/promises");
|
|
401
|
-
const parsed = JSON.parse(await readFile(credentialsPath, "utf8"));
|
|
402
|
-
if (parsed.oauth?.accessToken) {
|
|
403
|
-
label = parsed.email?.trim() || "legacy-default";
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
catch {
|
|
407
|
-
label = null;
|
|
408
|
-
}
|
|
409
|
-
legacyStatusAccountCache = {
|
|
410
|
-
credentialsPath,
|
|
411
|
-
expiresAt: now + LEGACY_STATUS_ACCOUNT_CACHE_TTL_MS,
|
|
412
|
-
label,
|
|
413
|
-
};
|
|
414
|
-
return label;
|
|
415
|
-
}
|
|
416
387
|
function deriveAccountAllowance(accountKey, now, allowlist, expirations, cooldowns) {
|
|
417
388
|
const allowed = isAccountAllowed(accountKey, allowlist);
|
|
418
389
|
const expiresAt = expirations.get(accountKey);
|
|
@@ -1066,6 +1037,7 @@ function redactStatusAccounts(rows, allowed) {
|
|
|
1066
1037
|
return rows.map((row, index) => ({
|
|
1067
1038
|
...row,
|
|
1068
1039
|
label: `account-${index + 1}`,
|
|
1040
|
+
...(row.key !== undefined ? { key: null } : {}),
|
|
1069
1041
|
...(row.email !== undefined ? { email: null } : {}),
|
|
1070
1042
|
}));
|
|
1071
1043
|
}
|
|
@@ -1685,20 +1657,28 @@ export async function createProxyStartApp(params) {
|
|
|
1685
1657
|
logger.debug(`[proxy] /status using empty cooldown snapshot: ${error instanceof Error ? error.message : String(error)}`);
|
|
1686
1658
|
return {};
|
|
1687
1659
|
});
|
|
1688
|
-
const
|
|
1660
|
+
const storedAnthropicAccountKeys = new Set();
|
|
1661
|
+
const storedCodexAccountKeys = new Set();
|
|
1689
1662
|
const storedAccountExpirations = new Map();
|
|
1690
|
-
const
|
|
1663
|
+
const disabledProviderAccountKeys = new Set();
|
|
1691
1664
|
let accountInventoryLoaded = false;
|
|
1692
1665
|
try {
|
|
1693
1666
|
const { tokenStore } = await import("../../auth/tokenStore.js");
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
|
|
1667
|
+
const [anthropicKeys, codexKeys] = await withTimeout(Promise.all([
|
|
1668
|
+
tokenStore.listByPrefix("anthropic:"),
|
|
1669
|
+
tokenStore.listByPrefix("codex:"),
|
|
1670
|
+
]), PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS, "[proxy] /status account enumeration timed out");
|
|
1671
|
+
for (const key of anthropicKeys) {
|
|
1672
|
+
storedAnthropicAccountKeys.add(normalizeAnthropicAccountKey(key));
|
|
1673
|
+
}
|
|
1674
|
+
for (const key of codexKeys) {
|
|
1675
|
+
storedCodexAccountKeys.add(key);
|
|
1697
1676
|
}
|
|
1698
1677
|
// Once account names are known, preserve them even when optional token
|
|
1699
1678
|
// metadata is slow. That keeps the status table useful and avoids
|
|
1700
1679
|
// incorrectly presenting known accounts as removed.
|
|
1701
1680
|
accountInventoryLoaded = true;
|
|
1681
|
+
const storedKeys = [...anthropicKeys, ...codexKeys];
|
|
1702
1682
|
const inventory = await withTimeout((async () => {
|
|
1703
1683
|
const tokenExpirations = await Promise.all(storedKeys.map(async (key) => {
|
|
1704
1684
|
try {
|
|
@@ -1715,19 +1695,21 @@ export async function createProxyStartApp(params) {
|
|
|
1715
1695
|
})(), PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS, "[proxy] /status account metadata timed out");
|
|
1716
1696
|
for (const expiration of inventory.tokenExpirations) {
|
|
1717
1697
|
if (expiration) {
|
|
1718
|
-
|
|
1698
|
+
const key = expiration[0].startsWith("anthropic:")
|
|
1699
|
+
? normalizeAnthropicAccountKey(expiration[0])
|
|
1700
|
+
: expiration[0];
|
|
1701
|
+
storedAccountExpirations.set(key, expiration[1]);
|
|
1719
1702
|
}
|
|
1720
1703
|
}
|
|
1721
1704
|
for (const key of inventory.disabledKeys) {
|
|
1722
|
-
|
|
1705
|
+
disabledProviderAccountKeys.add(key.startsWith("anthropic:")
|
|
1706
|
+
? normalizeAnthropicAccountKey(key)
|
|
1707
|
+
: key);
|
|
1723
1708
|
}
|
|
1724
1709
|
}
|
|
1725
1710
|
catch (err) {
|
|
1726
1711
|
logger.debug(`[proxy] /status: failed to resolve account cooldown labels: ${err instanceof Error ? err.message : String(err)}`);
|
|
1727
1712
|
}
|
|
1728
|
-
const legacyAccountLabel = accountInventoryLoaded
|
|
1729
|
-
? await withTimeout(resolveLegacyStatusAccountLabel(storedAccountKeys.size), PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS, "[proxy] /status legacy account inspection timed out").catch(() => null)
|
|
1730
|
-
: null;
|
|
1731
1713
|
const now = Date.now();
|
|
1732
1714
|
const health = buildProxyHealthResponse(readiness, {
|
|
1733
1715
|
strategy: activeStrategy,
|
|
@@ -1741,34 +1723,54 @@ export async function createProxyStartApp(params) {
|
|
|
1741
1723
|
source: "fallback",
|
|
1742
1724
|
}));
|
|
1743
1725
|
const activeUpdaterPid = supervisorState?.updaterPid ?? runtimeState?.updaterPid;
|
|
1744
|
-
const accountRows = Object.
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1726
|
+
const accountRows = Object.entries(stats.accounts).map(([persistedMapKey, account]) => {
|
|
1727
|
+
// Legacy snapshots used their bare map key as an inferred identity. Do
|
|
1728
|
+
// not map those old rows to a provider based on an email: the same email
|
|
1729
|
+
// can be present in both pools. New records carry account.key.
|
|
1730
|
+
const persistedKey = account.key ?? null;
|
|
1731
|
+
const identity = persistedKey
|
|
1732
|
+
? resolveProxyStatusAccountIdentity(account.label, account.type, persistedKey)
|
|
1733
|
+
: { provider: "other", key: null };
|
|
1734
|
+
const isLegacyUnattributed = persistedKey === null &&
|
|
1735
|
+
(account.type === "oauth" ||
|
|
1736
|
+
account.type === "api_key" ||
|
|
1737
|
+
account.type === "codex-oauth");
|
|
1738
|
+
const isAnthropicAccount = identity.provider === "anthropic";
|
|
1739
|
+
const isCodexAccount = identity.provider === "codex";
|
|
1740
|
+
const accountKey = identity.key ?? (persistedKey || persistedMapKey);
|
|
1741
|
+
const isStored = (isAnthropicAccount && storedAnthropicAccountKeys.has(accountKey)) ||
|
|
1742
|
+
(isCodexAccount && storedCodexAccountKeys.has(accountKey));
|
|
1743
|
+
const providerState = isAnthropicAccount
|
|
1744
|
+
? deriveAccountAllowance(accountKey, now, activeAccountAllowlist, storedAccountExpirations, cooldowns)
|
|
1745
|
+
: isCodexAccount
|
|
1746
|
+
? {
|
|
1747
|
+
allowed: true,
|
|
1748
|
+
expired: (storedAccountExpirations.get(accountKey) ?? 0) > 0 &&
|
|
1749
|
+
(storedAccountExpirations.get(accountKey) ?? 0) <= now,
|
|
1750
|
+
cooling: (cooldowns[accountKey]?.coolingUntil ?? 0) > now,
|
|
1751
|
+
}
|
|
1752
|
+
: { allowed: true, expired: false, cooling: false };
|
|
1753
|
+
const isDisabled = !isLegacyUnattributed && disabledProviderAccountKeys.has(accountKey);
|
|
1756
1754
|
const accountStatus = account.type === "internal"
|
|
1757
1755
|
? "internal"
|
|
1758
|
-
:
|
|
1759
|
-
? "
|
|
1760
|
-
:
|
|
1761
|
-
? "
|
|
1762
|
-
:
|
|
1763
|
-
? "
|
|
1764
|
-
:
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
? "
|
|
1770
|
-
:
|
|
1756
|
+
: isLegacyUnattributed
|
|
1757
|
+
? "unattributed"
|
|
1758
|
+
: isDisabled
|
|
1759
|
+
? "disabled"
|
|
1760
|
+
: providerState.expired
|
|
1761
|
+
? "expired"
|
|
1762
|
+
: isAnthropicAccount && !providerState.allowed
|
|
1763
|
+
? "excluded"
|
|
1764
|
+
: accountInventoryLoaded &&
|
|
1765
|
+
(isAnthropicAccount || isCodexAccount) &&
|
|
1766
|
+
!isStored
|
|
1767
|
+
? "removed"
|
|
1768
|
+
: providerState.cooling
|
|
1769
|
+
? "cooling"
|
|
1770
|
+
: "active";
|
|
1771
1771
|
return {
|
|
1772
|
+
key: isLegacyUnattributed ? null : accountKey,
|
|
1773
|
+
provider: isLegacyUnattributed ? "unknown" : identity.provider,
|
|
1772
1774
|
label: account.label,
|
|
1773
1775
|
type: account.type,
|
|
1774
1776
|
attempts: account.attemptCount,
|
|
@@ -1779,21 +1781,26 @@ export async function createProxyStartApp(params) {
|
|
|
1779
1781
|
rateLimits: account.rateLimitCount,
|
|
1780
1782
|
transientRateLimits: account.transientRateLimitCount,
|
|
1781
1783
|
quotaRateLimits: account.quotaRateLimitCount,
|
|
1782
|
-
cooling,
|
|
1784
|
+
cooling: providerState.cooling,
|
|
1783
1785
|
status: accountStatus,
|
|
1784
|
-
allowed: account.type === "internal" ? undefined : allowed,
|
|
1785
|
-
expired:
|
|
1786
|
+
allowed: account.type === "internal" ? undefined : providerState.allowed,
|
|
1787
|
+
expired: isAnthropicAccount || isCodexAccount
|
|
1788
|
+
? providerState.expired
|
|
1789
|
+
: undefined,
|
|
1786
1790
|
};
|
|
1787
1791
|
});
|
|
1788
|
-
const representedAccountKeys = new Set(accountRows
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
+
const representedAccountKeys = new Set(accountRows.flatMap((account) => account.key &&
|
|
1793
|
+
(account.provider === "anthropic" || account.provider === "codex")
|
|
1794
|
+
? [account.key]
|
|
1795
|
+
: []));
|
|
1796
|
+
for (const accountKey of storedAnthropicAccountKeys) {
|
|
1792
1797
|
if (representedAccountKeys.has(accountKey)) {
|
|
1793
1798
|
continue;
|
|
1794
1799
|
}
|
|
1795
1800
|
const { allowed, expired, cooling } = deriveAccountAllowance(accountKey, now, activeAccountAllowlist, storedAccountExpirations, cooldowns);
|
|
1796
1801
|
accountRows.push({
|
|
1802
|
+
key: accountKey,
|
|
1803
|
+
provider: "anthropic",
|
|
1797
1804
|
label: accountKey.slice("anthropic:".length),
|
|
1798
1805
|
type: "oauth",
|
|
1799
1806
|
attempts: 0,
|
|
@@ -1805,7 +1812,7 @@ export async function createProxyStartApp(params) {
|
|
|
1805
1812
|
transientRateLimits: 0,
|
|
1806
1813
|
quotaRateLimits: 0,
|
|
1807
1814
|
cooling,
|
|
1808
|
-
status:
|
|
1815
|
+
status: disabledProviderAccountKeys.has(accountKey)
|
|
1809
1816
|
? "disabled"
|
|
1810
1817
|
: expired
|
|
1811
1818
|
? "expired"
|
|
@@ -1818,6 +1825,38 @@ export async function createProxyStartApp(params) {
|
|
|
1818
1825
|
expired,
|
|
1819
1826
|
});
|
|
1820
1827
|
}
|
|
1828
|
+
for (const accountKey of storedCodexAccountKeys) {
|
|
1829
|
+
if (representedAccountKeys.has(accountKey)) {
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
const expiresAt = storedAccountExpirations.get(accountKey);
|
|
1833
|
+
const expired = expiresAt !== undefined && expiresAt > 0 && expiresAt <= now;
|
|
1834
|
+
const cooling = (cooldowns[accountKey]?.coolingUntil ?? 0) > now;
|
|
1835
|
+
accountRows.push({
|
|
1836
|
+
key: accountKey,
|
|
1837
|
+
provider: "codex",
|
|
1838
|
+
label: accountKey.slice("codex:".length),
|
|
1839
|
+
type: "codex-oauth",
|
|
1840
|
+
attempts: 0,
|
|
1841
|
+
requests: 0,
|
|
1842
|
+
success: 0,
|
|
1843
|
+
errors: 0,
|
|
1844
|
+
attemptErrors: 0,
|
|
1845
|
+
rateLimits: 0,
|
|
1846
|
+
transientRateLimits: 0,
|
|
1847
|
+
quotaRateLimits: 0,
|
|
1848
|
+
cooling,
|
|
1849
|
+
status: disabledProviderAccountKeys.has(accountKey)
|
|
1850
|
+
? "disabled"
|
|
1851
|
+
: expired
|
|
1852
|
+
? "expired"
|
|
1853
|
+
: cooling
|
|
1854
|
+
? "cooling"
|
|
1855
|
+
: "active",
|
|
1856
|
+
allowed: true,
|
|
1857
|
+
expired,
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1821
1860
|
const attributed = accountRows.reduce((total, account) => ({
|
|
1822
1861
|
attempts: total.attempts + (account.attempts ?? 0),
|
|
1823
1862
|
requests: total.requests + (account.requests ?? 0),
|
|
@@ -3010,7 +3049,8 @@ function printStatusStats(stats) {
|
|
|
3010
3049
|
if (lastError) {
|
|
3011
3050
|
const cause = lastError.errorType ?? lastError.category;
|
|
3012
3051
|
const code = lastError.errorCode ? `/${lastError.errorCode}` : "";
|
|
3013
|
-
const
|
|
3052
|
+
const accountIdentity = lastError.accountKey ?? lastError.account;
|
|
3053
|
+
const account = accountIdentity ? ` account=${accountIdentity}` : "";
|
|
3014
3054
|
console.info(` Last error: ${new Date(lastError.at).toISOString()} ${cause}${code} status=${lastError.status}${account}`);
|
|
3015
3055
|
if (lastError.message) {
|
|
3016
3056
|
console.info(` Last cause: ${sanitizeProxyStatusTerminalErrorMessage(lastError.message)}`);
|
|
@@ -6,10 +6,8 @@
|
|
|
6
6
|
* - login, logout, status, refresh: Anthropic OAuth (API key + OAuth)
|
|
7
7
|
* - providers, validate, health: Multi-provider auth management
|
|
8
8
|
*/
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
const SUPPORTED_PROVIDERS = ["anthropic", "codex"];
|
|
9
|
+
/** Providers with first-class credential flows in `neurolink auth`. */
|
|
10
|
+
const AUTH_LOGIN_PROVIDERS = ["anthropic", "codex"];
|
|
13
11
|
/**
|
|
14
12
|
* Auth Command Factory
|
|
15
13
|
*
|
|
@@ -167,7 +165,7 @@ export class AuthCommandFactory {
|
|
|
167
165
|
.positional("provider", {
|
|
168
166
|
type: "string",
|
|
169
167
|
description: "AI provider to authenticate with",
|
|
170
|
-
choices:
|
|
168
|
+
choices: AUTH_LOGIN_PROVIDERS,
|
|
171
169
|
demandOption: true,
|
|
172
170
|
})
|
|
173
171
|
.option("method", {
|
|
@@ -204,7 +202,7 @@ export class AuthCommandFactory {
|
|
|
204
202
|
.positional("provider", {
|
|
205
203
|
type: "string",
|
|
206
204
|
description: "AI provider to log out from",
|
|
207
|
-
choices:
|
|
205
|
+
choices: AUTH_LOGIN_PROVIDERS,
|
|
208
206
|
demandOption: true,
|
|
209
207
|
})
|
|
210
208
|
.example("$0 auth logout anthropic", "Clear all Anthropic credentials");
|
|
@@ -217,7 +215,7 @@ export class AuthCommandFactory {
|
|
|
217
215
|
.positional("provider", {
|
|
218
216
|
type: "string",
|
|
219
217
|
description: "AI provider to check (optional, shows all if not specified)",
|
|
220
|
-
choices:
|
|
218
|
+
choices: AUTH_LOGIN_PROVIDERS,
|
|
221
219
|
})
|
|
222
220
|
.example("$0 auth status", "Show status for all configured providers")
|
|
223
221
|
.example("$0 auth status anthropic", "Show Anthropic authentication status")
|
|
@@ -231,7 +229,7 @@ export class AuthCommandFactory {
|
|
|
231
229
|
.positional("provider", {
|
|
232
230
|
type: "string",
|
|
233
231
|
description: "AI provider to refresh tokens for",
|
|
234
|
-
choices:
|
|
232
|
+
choices: AUTH_LOGIN_PROVIDERS,
|
|
235
233
|
demandOption: true,
|
|
236
234
|
})
|
|
237
235
|
.example("$0 auth refresh anthropic", "Refresh Anthropic OAuth tokens");
|
|
@@ -244,7 +242,7 @@ export class AuthCommandFactory {
|
|
|
244
242
|
.option("refresh", {
|
|
245
243
|
type: "boolean",
|
|
246
244
|
default: false,
|
|
247
|
-
description: "
|
|
245
|
+
description: "Refresh available provider limits for authenticated accounts; show unsupported providers explicitly",
|
|
248
246
|
})
|
|
249
247
|
.example("$0 auth list", "List all authenticated accounts")
|
|
250
248
|
.example("$0 auth list --format json", "List accounts in JSON format")
|
|
@@ -257,8 +255,7 @@ export class AuthCommandFactory {
|
|
|
257
255
|
return yargs
|
|
258
256
|
.positional("provider", {
|
|
259
257
|
type: "string",
|
|
260
|
-
description: "
|
|
261
|
-
choices: SUPPORTED_PROVIDERS,
|
|
258
|
+
description: "Provider namespace to remove an account from",
|
|
262
259
|
demandOption: true,
|
|
263
260
|
})
|
|
264
261
|
.option("label", {
|
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
* map onto the shared AccountQuota session/weekly fields so the same routing,
|
|
7
7
|
* cooldown, and display code works for both providers.
|
|
8
8
|
*/
|
|
9
|
+
import type { CodexProxyStatusAccountIdentity } from "../types/index.js";
|
|
9
10
|
import type { AccountQuota, CodexRateLimits, CodexUsageFetchResult, ProxyPassthroughAccount } from "../types/index.js";
|
|
10
11
|
export declare const CODEX_ACCOUNT_PREFIX = "codex:";
|
|
12
|
+
/** Keep an account label in the Codex namespace used by its token store. */
|
|
13
|
+
export declare function normalizeCodexAccountKey(value: string): string;
|
|
14
|
+
/** Resolve the provider-qualified key used to render a proxy status row. */
|
|
15
|
+
export declare function resolveProxyStatusAccountIdentity(label: string, type: string, persistedKey?: string): CodexProxyStatusAccountIdentity;
|
|
11
16
|
/** Enumerate Codex OAuth accounts from the token store for usage refresh. */
|
|
12
17
|
export declare function listCodexAccountsForUsage(): Promise<ProxyPassthroughAccount[]>;
|
|
13
18
|
/** Normalise a Codex rate-limit block into the shared AccountQuota shape. */
|
|
@@ -7,9 +7,39 @@
|
|
|
7
7
|
* cooldown, and display code works for both providers.
|
|
8
8
|
*/
|
|
9
9
|
import { tokenStore } from "../auth/tokenStore.js";
|
|
10
|
+
import { normalizeAnthropicAccountKey } from "./accountSelection.js";
|
|
10
11
|
import { CODEX_ORIGINATOR, CODEX_USAGE_URL, CODEX_USER_AGENT, decodeCodexAccessToken, resolveCodexAccountId, } from "../auth/codexOAuth.js";
|
|
11
12
|
import { logger } from "../utils/logger.js";
|
|
12
13
|
export const CODEX_ACCOUNT_PREFIX = "codex:";
|
|
14
|
+
/** Keep an account label in the Codex namespace used by its token store. */
|
|
15
|
+
export function normalizeCodexAccountKey(value) {
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
return trimmed.startsWith(CODEX_ACCOUNT_PREFIX)
|
|
18
|
+
? trimmed
|
|
19
|
+
: `${CODEX_ACCOUNT_PREFIX}${trimmed}`;
|
|
20
|
+
}
|
|
21
|
+
/** Resolve the provider-qualified key used to render a proxy status row. */
|
|
22
|
+
export function resolveProxyStatusAccountIdentity(label, type, persistedKey) {
|
|
23
|
+
// Current counter snapshots persist the provider-qualified key. Prefer it
|
|
24
|
+
// over the display type so a future type rename cannot move an account into
|
|
25
|
+
// the wrong cooldown/disabled namespace.
|
|
26
|
+
if (persistedKey?.toLowerCase().startsWith("anthropic:")) {
|
|
27
|
+
return {
|
|
28
|
+
provider: "anthropic",
|
|
29
|
+
key: normalizeAnthropicAccountKey(persistedKey),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (persistedKey?.startsWith(CODEX_ACCOUNT_PREFIX)) {
|
|
33
|
+
return { provider: "codex", key: persistedKey };
|
|
34
|
+
}
|
|
35
|
+
if (type === "oauth" || type === "api_key") {
|
|
36
|
+
return { provider: "anthropic", key: normalizeAnthropicAccountKey(label) };
|
|
37
|
+
}
|
|
38
|
+
if (type === "codex-oauth") {
|
|
39
|
+
return { provider: "codex", key: normalizeCodexAccountKey(label) };
|
|
40
|
+
}
|
|
41
|
+
return { provider: "other", key: null };
|
|
42
|
+
}
|
|
13
43
|
/** Enumerate Codex OAuth accounts from the token store for usage refresh. */
|
|
14
44
|
export async function listCodexAccountsForUsage() {
|
|
15
45
|
const keys = await tokenStore.listByPrefix(CODEX_ACCOUNT_PREFIX);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Messages API fallback over the pooled Codex Responses transport.
|
|
3
|
+
*
|
|
4
|
+
* This module deliberately contains only wire-format conversion and buffered
|
|
5
|
+
* SSE parsing. Account selection, OAuth, cooldowns, and quota persistence stay
|
|
6
|
+
* in the native Codex proxy handler so fallback traffic follows the same pool
|
|
7
|
+
* rules as a native Codex request.
|
|
8
|
+
*/
|
|
9
|
+
import type { ClaudeRequest, CodexFallbackResult, CodexResponsesRequest } from "../types/index.js";
|
|
10
|
+
export declare class CodexFallbackResponseError extends Error {
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly responseBody: string;
|
|
13
|
+
constructor(status: number, responseBody: string);
|
|
14
|
+
}
|
|
15
|
+
/** Convert a Claude Messages request into the ChatGPT Codex Responses shape. */
|
|
16
|
+
export declare function convertClaudeRequestToCodex(body: ClaudeRequest, model: string): CodexResponsesRequest;
|
|
17
|
+
/**
|
|
18
|
+
* Parse a complete Codex Responses SSE stream before emitting Claude output.
|
|
19
|
+
*
|
|
20
|
+
* A missing terminal event, malformed JSON, terminal error, or empty response
|
|
21
|
+
* is rejected. That makes it safe for the caller to try the next configured
|
|
22
|
+
* fallback without ever replaying output already sent to a client.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseCodexFallbackSSE(sse: string): CodexFallbackResult;
|
|
25
|
+
/** Consume and validate a native Codex response before producing Claude output. */
|
|
26
|
+
export declare function consumeCodexFallbackResponse(response: Response): Promise<CodexFallbackResult>;
|