@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.
@@ -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, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
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 storedAccountKeys = new Set();
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 storedKeys = await withTimeout(tokenStore.listByPrefix("anthropic:"), PROXY_STATUS_ACCOUNT_INVENTORY_TIMEOUT_MS, "[proxy] /status account enumeration timed out");
1697
- for (const key of storedKeys) {
1698
- storedAccountKeys.add(normalizeAnthropicAccountKey(key));
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
- storedAccountExpirations.set(normalizeAnthropicAccountKey(expiration[0]), expiration[1]);
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
- disabledAccountKeys.add(normalizeAnthropicAccountKey(key));
1725
- disabledProviderAccountKeys.add(key);
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.values(stats.accounts).map((account) => {
1748
- const identity = resolveProxyStatusAccountIdentity(account.label, account.type);
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 normalizedKey = identity.provider === "anthropic" ? identity.key : null;
1752
- const isLegacyAccount = isAnthropicAccount && account.label === legacyAccountLabel;
1753
- const accountKey = isAnthropicAccount
1754
- ? storedAccountKeys.has(normalizedKey ?? "")
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
- allowed: true,
1769
- expired: false,
1770
- cooling: accountKey !== null &&
1771
- (cooldowns[accountKey]?.coolingUntil ?? 0) > now,
1772
- };
1773
- const isDisabled = (isAnthropicAccount &&
1774
- accountKey !== null &&
1775
- disabledAccountKeys.has(accountKey)) ||
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
- : isDisabled
1782
- ? "disabled"
1783
- : isAnthropicAccount && expired
1784
- ? "expired"
1785
- : isAnthropicAccount && !allowed
1786
- ? "excluded"
1787
- : accountInventoryLoaded && isAnthropicAccount && !isStored
1788
- ? "removed"
1789
- : cooling
1790
- ? "cooling"
1791
- : "active";
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 ? expired : undefined,
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
- .filter((account) => account.type === "oauth" || account.type === "api_key")
1811
- .map((account) => normalizeAnthropicAccountKey(account.label)));
1812
- for (const accountKey of storedAccountKeys) {
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: disabledAccountKeys.has(accountKey)
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 account = lastError.account ? ` account=${lastError.account}` : "";
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
- * Supported providers for authentication
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: SUPPORTED_PROVIDERS,
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: SUPPORTED_PROVIDERS,
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: SUPPORTED_PROVIDERS,
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: SUPPORTED_PROVIDERS,
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: "Fetch fresh limits from Anthropic for all OAuth accounts before listing (via the running proxy when available)",
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: "AI provider to remove account from",
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 Redis conversation memory manager instance.
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 Redis conversation memory manager instance.
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
- if (!memoryManager) {
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: "Memory manager not configured",
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
- // When only an artifact store is present (no Redis), memoryManager is
1405
- // undefined createMemoryRetrievalTools handles that via an explicit guard.
1406
- const memoryManager = this.conversationMemory;
1407
- const tools = createMemoryRetrievalTools(memoryManager, this.mcpArtifactStore);
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
- settle("client_cancelled");
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>;