@juspay/neurolink 12.7.3 → 12.7.5
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 +399 -399
- package/dist/cli/commands/auth.js +252 -125
- package/dist/cli/commands/proxy.js +110 -91
- package/dist/cli/factories/authCommandFactory.js +8 -11
- package/dist/memory/memoryRetrievalTools.d.ts +5 -2
- package/dist/memory/memoryRetrievalTools.js +11 -3
- package/dist/neurolink.js +5 -4
- package/dist/proxy/codexAccountUsage.d.ts +1 -1
- package/dist/proxy/codexAccountUsage.js +13 -1
- 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 +2 -1
- package/dist/server/routes/claudeProxyRoutes.js +62 -12
- package/dist/server/routes/codexProxyRoutes.d.ts +8 -2
- package/dist/server/routes/codexProxyRoutes.js +191 -58
- package/dist/types/cli.d.ts +37 -5
- package/dist/types/proxy.d.ts +37 -0
- package/package.json +2 -2
|
@@ -24,7 +24,7 @@ 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
28
|
import { resolveProxyStatusAccountIdentity } from "../../proxy/codexAccountUsage.js";
|
|
29
29
|
import { beginProxyRequest, getProxyActivitySnapshot, trackProxyResponse, } from "../../proxy/proxyActivity.js";
|
|
30
30
|
import { flushProxyLifecycleEvents, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../proxy/proxyLifecycle.js";
|
|
@@ -66,11 +66,9 @@ const gatedShareRequests = new WeakSet();
|
|
|
66
66
|
const PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
67
67
|
/** How long shutdown waits on the share listener before moving on. */
|
|
68
68
|
const SHARE_LISTENER_CLOSE_TIMEOUT_MS = 10_000;
|
|
69
|
-
const LEGACY_STATUS_ACCOUNT_CACHE_TTL_MS = 5_000;
|
|
70
69
|
const PROXY_STATUS_TOKEN_READ_TIMEOUT_MS = 2_000;
|
|
71
70
|
const PROXY_STATUS_RECONCILE_TIMEOUT_MS = 750;
|
|
72
71
|
const PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS = 750;
|
|
73
|
-
let legacyStatusAccountCache;
|
|
74
72
|
// Allowed drift between a pid's OS-reported start time and the persisted
|
|
75
73
|
// ProxySupervisorState.startTime before processLooksLikeProxySupervisor
|
|
76
74
|
// treats it as a confident mismatch (recycled pid). Generous on purpose:
|
|
@@ -386,34 +384,6 @@ async function resolveStatusPrimaryAccount(proxyConfig) {
|
|
|
386
384
|
source: "fallback",
|
|
387
385
|
};
|
|
388
386
|
}
|
|
389
|
-
async function resolveLegacyStatusAccountLabel(storedAnthropicAccountCount) {
|
|
390
|
-
if (storedAnthropicAccountCount !== 0) {
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
const credentialsPath = join(homedir(), ".neurolink", "anthropic-credentials.json");
|
|
394
|
-
const now = Date.now();
|
|
395
|
-
if (legacyStatusAccountCache?.credentialsPath === credentialsPath &&
|
|
396
|
-
legacyStatusAccountCache.expiresAt > now) {
|
|
397
|
-
return legacyStatusAccountCache.label;
|
|
398
|
-
}
|
|
399
|
-
let label = null;
|
|
400
|
-
try {
|
|
401
|
-
const { readFile } = await import("node:fs/promises");
|
|
402
|
-
const parsed = JSON.parse(await readFile(credentialsPath, "utf8"));
|
|
403
|
-
if (parsed.oauth?.accessToken) {
|
|
404
|
-
label = parsed.email?.trim() || "legacy-default";
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
catch {
|
|
408
|
-
label = null;
|
|
409
|
-
}
|
|
410
|
-
legacyStatusAccountCache = {
|
|
411
|
-
credentialsPath,
|
|
412
|
-
expiresAt: now + LEGACY_STATUS_ACCOUNT_CACHE_TTL_MS,
|
|
413
|
-
label,
|
|
414
|
-
};
|
|
415
|
-
return label;
|
|
416
|
-
}
|
|
417
387
|
function deriveAccountAllowance(accountKey, now, allowlist, expirations, cooldowns) {
|
|
418
388
|
const allowed = isAccountAllowed(accountKey, allowlist);
|
|
419
389
|
const expiresAt = expirations.get(accountKey);
|
|
@@ -1067,6 +1037,7 @@ function redactStatusAccounts(rows, allowed) {
|
|
|
1067
1037
|
return rows.map((row, index) => ({
|
|
1068
1038
|
...row,
|
|
1069
1039
|
label: `account-${index + 1}`,
|
|
1040
|
+
...(row.key !== undefined ? { key: null } : {}),
|
|
1070
1041
|
...(row.email !== undefined ? { email: null } : {}),
|
|
1071
1042
|
}));
|
|
1072
1043
|
}
|
|
@@ -1686,21 +1657,28 @@ export async function createProxyStartApp(params) {
|
|
|
1686
1657
|
logger.debug(`[proxy] /status using empty cooldown snapshot: ${error instanceof Error ? error.message : String(error)}`);
|
|
1687
1658
|
return {};
|
|
1688
1659
|
});
|
|
1689
|
-
const
|
|
1660
|
+
const storedAnthropicAccountKeys = new Set();
|
|
1661
|
+
const storedCodexAccountKeys = new Set();
|
|
1690
1662
|
const storedAccountExpirations = new Map();
|
|
1691
|
-
const disabledAccountKeys = new Set();
|
|
1692
1663
|
const disabledProviderAccountKeys = new Set();
|
|
1693
1664
|
let accountInventoryLoaded = false;
|
|
1694
1665
|
try {
|
|
1695
1666
|
const { tokenStore } = await import("../../auth/tokenStore.js");
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
|
|
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);
|
|
1699
1676
|
}
|
|
1700
1677
|
// Once account names are known, preserve them even when optional token
|
|
1701
1678
|
// metadata is slow. That keeps the status table useful and avoids
|
|
1702
1679
|
// incorrectly presenting known accounts as removed.
|
|
1703
1680
|
accountInventoryLoaded = true;
|
|
1681
|
+
const storedKeys = [...anthropicKeys, ...codexKeys];
|
|
1704
1682
|
const inventory = await withTimeout((async () => {
|
|
1705
1683
|
const tokenExpirations = await Promise.all(storedKeys.map(async (key) => {
|
|
1706
1684
|
try {
|
|
@@ -1717,20 +1695,21 @@ export async function createProxyStartApp(params) {
|
|
|
1717
1695
|
})(), PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS, "[proxy] /status account metadata timed out");
|
|
1718
1696
|
for (const expiration of inventory.tokenExpirations) {
|
|
1719
1697
|
if (expiration) {
|
|
1720
|
-
|
|
1698
|
+
const key = expiration[0].startsWith("anthropic:")
|
|
1699
|
+
? normalizeAnthropicAccountKey(expiration[0])
|
|
1700
|
+
: expiration[0];
|
|
1701
|
+
storedAccountExpirations.set(key, expiration[1]);
|
|
1721
1702
|
}
|
|
1722
1703
|
}
|
|
1723
1704
|
for (const key of inventory.disabledKeys) {
|
|
1724
|
-
|
|
1725
|
-
|
|
1705
|
+
disabledProviderAccountKeys.add(key.startsWith("anthropic:")
|
|
1706
|
+
? normalizeAnthropicAccountKey(key)
|
|
1707
|
+
: key);
|
|
1726
1708
|
}
|
|
1727
1709
|
}
|
|
1728
1710
|
catch (err) {
|
|
1729
1711
|
logger.debug(`[proxy] /status: failed to resolve account cooldown labels: ${err instanceof Error ? err.message : String(err)}`);
|
|
1730
1712
|
}
|
|
1731
|
-
const legacyAccountLabel = accountInventoryLoaded
|
|
1732
|
-
? await withTimeout(resolveLegacyStatusAccountLabel(storedAccountKeys.size), PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS, "[proxy] /status legacy account inspection timed out").catch(() => null)
|
|
1733
|
-
: null;
|
|
1734
1713
|
const now = Date.now();
|
|
1735
1714
|
const health = buildProxyHealthResponse(readiness, {
|
|
1736
1715
|
strategy: activeStrategy,
|
|
@@ -1744,52 +1723,54 @@ export async function createProxyStartApp(params) {
|
|
|
1744
1723
|
source: "fallback",
|
|
1745
1724
|
}));
|
|
1746
1725
|
const activeUpdaterPid = supervisorState?.updaterPid ?? runtimeState?.updaterPid;
|
|
1747
|
-
const accountRows = Object.
|
|
1748
|
-
|
|
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");
|
|
1749
1738
|
const isAnthropicAccount = identity.provider === "anthropic";
|
|
1750
1739
|
const isCodexAccount = identity.provider === "codex";
|
|
1751
|
-
const
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
? normalizedKey
|
|
1756
|
-
: isLegacyAccount
|
|
1757
|
-
? LEGACY_ANTHROPIC_ACCOUNT_KEY
|
|
1758
|
-
: account.label === "env"
|
|
1759
|
-
? ENV_ANTHROPIC_ACCOUNT_KEY
|
|
1760
|
-
: normalizedKey
|
|
1761
|
-
: identity.key;
|
|
1762
|
-
const isStored = isAnthropicAccount &&
|
|
1763
|
-
accountKey !== null &&
|
|
1764
|
-
(storedAccountKeys.has(accountKey) || isLegacyAccount);
|
|
1765
|
-
const { allowed, expired, cooling } = isAnthropicAccount && accountKey !== null
|
|
1740
|
+
const accountKey = identity.key ?? (persistedKey || persistedMapKey);
|
|
1741
|
+
const isStored = (isAnthropicAccount && storedAnthropicAccountKeys.has(accountKey)) ||
|
|
1742
|
+
(isCodexAccount && storedCodexAccountKeys.has(accountKey));
|
|
1743
|
+
const providerState = isAnthropicAccount
|
|
1766
1744
|
? deriveAccountAllowance(accountKey, now, activeAccountAllowlist, storedAccountExpirations, cooldowns)
|
|
1767
|
-
:
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
(isCodexAccount &&
|
|
1777
|
-
accountKey !== null &&
|
|
1778
|
-
disabledProviderAccountKeys.has(accountKey));
|
|
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);
|
|
1779
1754
|
const accountStatus = account.type === "internal"
|
|
1780
1755
|
? "internal"
|
|
1781
|
-
:
|
|
1782
|
-
? "
|
|
1783
|
-
:
|
|
1784
|
-
? "
|
|
1785
|
-
:
|
|
1786
|
-
? "
|
|
1787
|
-
:
|
|
1788
|
-
? "
|
|
1789
|
-
:
|
|
1790
|
-
|
|
1791
|
-
|
|
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";
|
|
1792
1771
|
return {
|
|
1772
|
+
key: isLegacyUnattributed ? null : accountKey,
|
|
1773
|
+
provider: isLegacyUnattributed ? "unknown" : identity.provider,
|
|
1793
1774
|
label: account.label,
|
|
1794
1775
|
type: account.type,
|
|
1795
1776
|
attempts: account.attemptCount,
|
|
@@ -1800,21 +1781,26 @@ export async function createProxyStartApp(params) {
|
|
|
1800
1781
|
rateLimits: account.rateLimitCount,
|
|
1801
1782
|
transientRateLimits: account.transientRateLimitCount,
|
|
1802
1783
|
quotaRateLimits: account.quotaRateLimitCount,
|
|
1803
|
-
cooling,
|
|
1784
|
+
cooling: providerState.cooling,
|
|
1804
1785
|
status: accountStatus,
|
|
1805
|
-
allowed: account.type === "internal" ? undefined : allowed,
|
|
1806
|
-
expired: isAnthropicAccount
|
|
1786
|
+
allowed: account.type === "internal" ? undefined : providerState.allowed,
|
|
1787
|
+
expired: isAnthropicAccount || isCodexAccount
|
|
1788
|
+
? providerState.expired
|
|
1789
|
+
: undefined,
|
|
1807
1790
|
};
|
|
1808
1791
|
});
|
|
1809
|
-
const representedAccountKeys = new Set(accountRows
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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) {
|
|
1813
1797
|
if (representedAccountKeys.has(accountKey)) {
|
|
1814
1798
|
continue;
|
|
1815
1799
|
}
|
|
1816
1800
|
const { allowed, expired, cooling } = deriveAccountAllowance(accountKey, now, activeAccountAllowlist, storedAccountExpirations, cooldowns);
|
|
1817
1801
|
accountRows.push({
|
|
1802
|
+
key: accountKey,
|
|
1803
|
+
provider: "anthropic",
|
|
1818
1804
|
label: accountKey.slice("anthropic:".length),
|
|
1819
1805
|
type: "oauth",
|
|
1820
1806
|
attempts: 0,
|
|
@@ -1826,7 +1812,7 @@ export async function createProxyStartApp(params) {
|
|
|
1826
1812
|
transientRateLimits: 0,
|
|
1827
1813
|
quotaRateLimits: 0,
|
|
1828
1814
|
cooling,
|
|
1829
|
-
status:
|
|
1815
|
+
status: disabledProviderAccountKeys.has(accountKey)
|
|
1830
1816
|
? "disabled"
|
|
1831
1817
|
: expired
|
|
1832
1818
|
? "expired"
|
|
@@ -1839,6 +1825,38 @@ export async function createProxyStartApp(params) {
|
|
|
1839
1825
|
expired,
|
|
1840
1826
|
});
|
|
1841
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
|
+
}
|
|
1842
1860
|
const attributed = accountRows.reduce((total, account) => ({
|
|
1843
1861
|
attempts: total.attempts + (account.attempts ?? 0),
|
|
1844
1862
|
requests: total.requests + (account.requests ?? 0),
|
|
@@ -3031,7 +3049,8 @@ function printStatusStats(stats) {
|
|
|
3031
3049
|
if (lastError) {
|
|
3032
3050
|
const cause = lastError.errorType ?? lastError.category;
|
|
3033
3051
|
const code = lastError.errorCode ? `/${lastError.errorCode}` : "";
|
|
3034
|
-
const
|
|
3052
|
+
const accountIdentity = lastError.accountKey ?? lastError.account;
|
|
3053
|
+
const account = accountIdentity ? ` account=${accountIdentity}` : "";
|
|
3035
3054
|
console.info(` Last error: ${new Date(lastError.at).toISOString()} ${cause}${code} status=${lastError.status}${account}`);
|
|
3036
3055
|
if (lastError.message) {
|
|
3037
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", {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import type { ConversationMemoryManager } from "../core/conversationMemoryManager.js";
|
|
1
2
|
import type { RedisConversationMemoryManager } from "../core/redisConversationMemoryManager.js";
|
|
2
3
|
import type { ArtifactStore, Tool } from "../types/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Factory function that creates memory retrieval tools bound to a memory manager.
|
|
5
6
|
*
|
|
6
|
-
* @param memoryManager
|
|
7
|
+
* @param memoryManager Conversation memory manager instance. Session history
|
|
8
|
+
* retrieval requires the Redis-backed manager; with the
|
|
9
|
+
* in-memory manager the tool returns a descriptive error.
|
|
7
10
|
* @param artifactStore Optional artifact store for externalized MCP outputs.
|
|
8
11
|
* When provided, retrieve_context gains an `artifactId`
|
|
9
12
|
* parameter that fetches the full payload written by
|
|
10
13
|
* McpOutputNormalizer under strategy="externalize".
|
|
11
14
|
* @returns Record of tool name to Vercel AI SDK tool definition
|
|
12
15
|
*/
|
|
13
|
-
export declare function createMemoryRetrievalTools(memoryManager: RedisConversationMemoryManager | undefined, artifactStore?: ArtifactStore): Record<string, Tool>;
|
|
16
|
+
export declare function createMemoryRetrievalTools(memoryManager: ConversationMemoryManager | RedisConversationMemoryManager | undefined, artifactStore?: ArtifactStore): Record<string, Tool>;
|
|
@@ -15,7 +15,9 @@ const MAX_SEARCH_MATCHES = 50;
|
|
|
15
15
|
/**
|
|
16
16
|
* Factory function that creates memory retrieval tools bound to a memory manager.
|
|
17
17
|
*
|
|
18
|
-
* @param memoryManager
|
|
18
|
+
* @param memoryManager Conversation memory manager instance. Session history
|
|
19
|
+
* retrieval requires the Redis-backed manager; with the
|
|
20
|
+
* in-memory manager the tool returns a descriptive error.
|
|
19
21
|
* @param artifactStore Optional artifact store for externalized MCP outputs.
|
|
20
22
|
* When provided, retrieve_context gains an `artifactId`
|
|
21
23
|
* parameter that fetches the full payload written by
|
|
@@ -146,10 +148,16 @@ async function executeRetrieveContext(args, memoryManager, artifactStore, otelSp
|
|
|
146
148
|
error: "sessionId is required when artifactId is not provided",
|
|
147
149
|
};
|
|
148
150
|
}
|
|
149
|
-
|
|
151
|
+
// getSessionRaw exists only on the Redis-backed manager. A truthy manager
|
|
152
|
+
// can still be the in-memory one (tool registered for an artifact store, or
|
|
153
|
+
// Redis init fell back to in-memory), so guard on capability — not just
|
|
154
|
+
// presence — instead of throwing "getSessionRaw is not a function".
|
|
155
|
+
if (!memoryManager || !("getSessionRaw" in memoryManager)) {
|
|
150
156
|
otelSpan.setStatus({
|
|
151
157
|
code: SpanStatusCode.ERROR,
|
|
152
|
-
message:
|
|
158
|
+
message: memoryManager
|
|
159
|
+
? "Conversation memory backend is not Redis"
|
|
160
|
+
: "Memory manager not configured",
|
|
153
161
|
});
|
|
154
162
|
return {
|
|
155
163
|
error: "Session history retrieval requires Redis conversation memory — " +
|
package/dist/neurolink.js
CHANGED
|
@@ -1401,10 +1401,11 @@ export class NeuroLink {
|
|
|
1401
1401
|
inputSchema: retrieveContextDef.inputSchema,
|
|
1402
1402
|
execute: async (params) => {
|
|
1403
1403
|
// Lazy: conversationMemory is initialized on the first generate() call.
|
|
1404
|
-
//
|
|
1405
|
-
//
|
|
1406
|
-
|
|
1407
|
-
|
|
1404
|
+
// It may be undefined (artifact-store-only), the Redis manager, or the
|
|
1405
|
+
// in-memory manager (no Redis config, or Redis init fell back) —
|
|
1406
|
+
// createMemoryRetrievalTools guards session retrieval on getSessionRaw
|
|
1407
|
+
// capability and returns a descriptive error otherwise.
|
|
1408
|
+
const tools = createMemoryRetrievalTools(this.conversationMemory ?? undefined, this.mcpArtifactStore);
|
|
1408
1409
|
// Return the result directly so the LLM receives clean output instead
|
|
1409
1410
|
// of a nested { success, data, metadata } wrapper.
|
|
1410
1411
|
// Bounded by TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS so a stalled Redis or
|
|
@@ -12,7 +12,7 @@ export declare const CODEX_ACCOUNT_PREFIX = "codex:";
|
|
|
12
12
|
/** Keep an account label in the Codex namespace used by its token store. */
|
|
13
13
|
export declare function normalizeCodexAccountKey(value: string): string;
|
|
14
14
|
/** Resolve the provider-qualified key used to render a proxy status row. */
|
|
15
|
-
export declare function resolveProxyStatusAccountIdentity(label: string, type: string): CodexProxyStatusAccountIdentity;
|
|
15
|
+
export declare function resolveProxyStatusAccountIdentity(label: string, type: string, persistedKey?: string): CodexProxyStatusAccountIdentity;
|
|
16
16
|
/** Enumerate Codex OAuth accounts from the token store for usage refresh. */
|
|
17
17
|
export declare function listCodexAccountsForUsage(): Promise<ProxyPassthroughAccount[]>;
|
|
18
18
|
/** Normalise a Codex rate-limit block into the shared AccountQuota shape. */
|
|
@@ -19,7 +19,19 @@ export function normalizeCodexAccountKey(value) {
|
|
|
19
19
|
: `${CODEX_ACCOUNT_PREFIX}${trimmed}`;
|
|
20
20
|
}
|
|
21
21
|
/** Resolve the provider-qualified key used to render a proxy status row. */
|
|
22
|
-
export function resolveProxyStatusAccountIdentity(label, type) {
|
|
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
|
+
}
|
|
23
35
|
if (type === "oauth" || type === "api_key") {
|
|
24
36
|
return { provider: "anthropic", key: normalizeAnthropicAccountKey(label) };
|
|
25
37
|
}
|
|
@@ -63,6 +63,13 @@ export function trackProxyResponse(response, finishRequest, observer) {
|
|
|
63
63
|
let observedBodyBytes = 0;
|
|
64
64
|
let responseChunks = 0;
|
|
65
65
|
let settled = false;
|
|
66
|
+
let sourceClosed = false;
|
|
67
|
+
// A framework can cancel its adapter after the upstream stream has already
|
|
68
|
+
// closed. Snapshot reader.closed before cancel() so that normal cleanup is
|
|
69
|
+
// not recorded as a client-aborted request.
|
|
70
|
+
void reader.closed.then(() => {
|
|
71
|
+
sourceClosed = true;
|
|
72
|
+
}, () => undefined);
|
|
66
73
|
const settle = (outcome) => {
|
|
67
74
|
if (settled) {
|
|
68
75
|
return;
|
|
@@ -100,7 +107,11 @@ export function trackProxyResponse(response, finishRequest, observer) {
|
|
|
100
107
|
}
|
|
101
108
|
},
|
|
102
109
|
async cancel(reason) {
|
|
103
|
-
|
|
110
|
+
// Read the state before cancelling: reader.cancel() itself rejects the
|
|
111
|
+
// closed promise for a genuinely active source, which is too late to
|
|
112
|
+
// distinguish it from a source that had already ended normally.
|
|
113
|
+
await Promise.resolve();
|
|
114
|
+
settle(sourceClosed ? "completed" : "client_cancelled");
|
|
104
115
|
await withTimeout(reader.cancel(reason), PROXY_RESPONSE_CANCEL_TIMEOUT_MS, "Timed out cancelling the upstream proxy response");
|
|
105
116
|
},
|
|
106
117
|
});
|
|
@@ -40,7 +40,7 @@ export declare class ProxyUsageStatsStore {
|
|
|
40
40
|
recordAttemptError(accountLabel: string, accountType: string, status: number, rateLimitKind?: "transient" | "quota"): void;
|
|
41
41
|
recordFinalError(status: number, accountLabel?: string, accountType?: string, details?: ProxyTerminalErrorDetails): void;
|
|
42
42
|
getStats(): ProxyStats;
|
|
43
|
-
getAccountStats(label: string): AccountStats | undefined;
|
|
43
|
+
getAccountStats(label: string, type?: string): AccountStats | undefined;
|
|
44
44
|
getTerminalErrors(): ProxyTerminalErrorJournal;
|
|
45
45
|
getUsageSnapshot(): ProxyUsageStatsSnapshot;
|
|
46
46
|
getPersistenceStatus(): ProxyStatsPersistenceStatus;
|
|
@@ -81,7 +81,7 @@ export declare function getStats(): ProxyStats;
|
|
|
81
81
|
export declare function getUsageSnapshot(): ProxyUsageStatsSnapshot;
|
|
82
82
|
export declare function getReconciledStats(): Promise<ProxyStats>;
|
|
83
83
|
export declare function getReconciledUsageSnapshot(): Promise<ProxyUsageStatsSnapshot>;
|
|
84
|
-
export declare function getAccountStats(label: string): AccountStats | undefined;
|
|
84
|
+
export declare function getAccountStats(label: string, type?: string): AccountStats | undefined;
|
|
85
85
|
export declare function getTerminalErrors(): ProxyTerminalErrorJournal;
|
|
86
86
|
export declare function getUsageStatsPersistenceStatus(): ProxyStatsPersistenceStatus;
|
|
87
87
|
export declare function flushUsageStats(): Promise<void>;
|