@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.
@@ -17,7 +17,12 @@
17
17
  * never collides with anthropic entries) and does pre-commit rotation only, not
18
18
  * the full transient-budget / admission machinery.
19
19
  */
20
- import type { AccountCoolingReason, AccountQuota, CodexRuntimeAccount, RouteGroup } from "../../types/index.js";
20
+ import type { AccountQuota, CodexRefreshTokenStore, CodexRuntimeAccount, CodexTokenRefresher, RateLimitCoolingReason, RouteGroup, ServerContext } from "../../types/index.js";
21
+ declare function refreshCodexTokenOnceWithDependencies(key: string, refreshToken: string, store: CodexRefreshTokenStore, refresh: CodexTokenRefresher): Promise<{
22
+ accessToken: string;
23
+ refreshToken: string;
24
+ expiresAt?: number;
25
+ }>;
21
26
  /** Refresh an account's token at most once at a time. */
22
27
  declare function refreshCodexTokenOnce(key: string, refreshToken: string): Promise<{
23
28
  accessToken: string;
@@ -44,8 +49,10 @@ declare function buildCodexUpstreamHeaders(clientHeaders: Record<string, string>
44
49
  */
45
50
  declare function planCodexCooldown(quota: AccountQuota | null, retryAfterMs: number, now: number): {
46
51
  coolingUntil: number;
47
- reason: AccountCoolingReason;
52
+ reason: RateLimitCoolingReason;
48
53
  };
54
+ /** Core pooled handler for POST /backend-api/codex/responses. */
55
+ export declare function handleCodexResponsesRequest(ctx: ServerContext): Promise<Response>;
49
56
  /**
50
57
  * Create Codex proxy routes.
51
58
  *
@@ -59,6 +66,7 @@ export declare const __testHooks: {
59
66
  buildCodexUpstreamHeaders: typeof buildCodexUpstreamHeaders;
60
67
  planCodexCooldown: typeof planCodexCooldown;
61
68
  refreshCodexTokenOnce: typeof refreshCodexTokenOnce;
69
+ refreshCodexTokenOnceWithDependencies: typeof refreshCodexTokenOnceWithDependencies;
62
70
  codexRefreshInFlightSize: () => number;
63
71
  };
64
72
  export {};
@@ -24,8 +24,10 @@ import { loadAccountQuotas, saveAccountQuota, } from "../../proxy/accountQuota.j
24
24
  import { createCodexUsageTap } from "../../proxy/codexUsage.js";
25
25
  import { CODEX_ACCOUNT_PREFIX, parseCodexRateLimitHeaders, } from "../../proxy/codexAccountUsage.js";
26
26
  import { buildClientAttribution } from "../../proxy/clientAttribution.js";
27
- import { logRequest } from "../../proxy/requestLogger.js";
27
+ import { trackProxyResponse } from "../../proxy/proxyActivity.js";
28
+ import { logRequest, logRequestAttempt } from "../../proxy/requestLogger.js";
28
29
  import { parseRetryAfterMs } from "../../proxy/routingPolicy.js";
30
+ import { recordAttempt, recordAttemptError, recordFinalError, recordFinalSuccess, } from "../../proxy/usageStats.js";
29
31
  import { sanitizeForLog } from "../../utils/logSanitize.js";
30
32
  import { logger } from "../../utils/logger.js";
31
33
  const CODEX_UPSTREAM_TIMEOUT_MS = 15 * 60 * 1000; // 15 min, matches Claude path
@@ -33,6 +35,32 @@ const DEFAULT_TRANSIENT_COOLDOWN_MS = 60_000;
33
35
  const MAX_TRANSIENT_COOLDOWN_MS = 15 * 60 * 1000;
34
36
  /** Brief park after a refresh attempt that never reached a verdict. */
35
37
  const CODEX_AUTH_COOLDOWN_MS = 60_000;
38
+ const CODEX_ACCOUNT_TYPE = "codex-oauth";
39
+ const CODEX_FALLBACK_METADATA_KEY = "neurolink.codexFallback";
40
+ function getCodexTransportErrorCode(error) {
41
+ if (!error || typeof error !== "object") {
42
+ return undefined;
43
+ }
44
+ const directCode = error.code;
45
+ if (typeof directCode === "string") {
46
+ return directCode;
47
+ }
48
+ const cause = error.cause;
49
+ if (!cause || typeof cause !== "object") {
50
+ return undefined;
51
+ }
52
+ const causeCode = cause.code;
53
+ return typeof causeCode === "string" ? causeCode : undefined;
54
+ }
55
+ function codexTransportScope(error) {
56
+ const code = getCodexTransportErrorCode(error);
57
+ return code === "ENOTFOUND" || code === "EAI_AGAIN"
58
+ ? "shared_provider_transport"
59
+ : "connection_transport";
60
+ }
61
+ function summarizeCodexUpstreamError(errorText, fallback) {
62
+ return sanitizeForLog(errorText).slice(0, 200) || fallback;
63
+ }
36
64
  /**
37
65
  * In-flight proactive refreshes, keyed by account.
38
66
  *
@@ -43,8 +71,7 @@ const CODEX_AUTH_COOLDOWN_MS = 60_000;
43
71
  * that is perfectly healthy.
44
72
  */
45
73
  const codexRefreshInFlight = new Map();
46
- /** Refresh an account's token at most once at a time. */
47
- async function refreshCodexTokenOnce(key, refreshToken) {
74
+ async function refreshCodexTokenOnceWithDependencies(key, refreshToken, store, refresh) {
48
75
  const existing = codexRefreshInFlight.get(key);
49
76
  if (existing) {
50
77
  return existing;
@@ -54,20 +81,36 @@ async function refreshCodexTokenOnce(key, refreshToken) {
54
81
  // request that captured the pool just before a previous refresh completed
55
82
  // holds a token that has since been rotated; using it would spend a real
56
83
  // attempt on a grant the server has already invalidated.
57
- const latest = await tokenStore.peekTokens(key).catch(() => null);
84
+ const latest = await store.peekTokens(key).catch(() => null);
58
85
  const current = latest?.refreshToken ?? refreshToken;
59
- const refreshed = await refreshCodexToken(current);
60
- return {
86
+ const refreshed = await refresh(current);
87
+ const resolved = {
61
88
  accessToken: refreshed.accessToken,
62
89
  refreshToken: refreshed.refreshToken ?? current,
63
- expiresAt: refreshed.expiresAt,
90
+ expiresAt: refreshed.expiresAt ?? latest?.expiresAt ?? Date.now() + 3_600_000,
64
91
  };
92
+ // Hold the single-flight slot through persistence. Releasing it after the
93
+ // OAuth response but before this write lets a third request read the old
94
+ // rotating refresh token, receive an invalid_grant, and disable an account
95
+ // another request has already healed.
96
+ if (latest) {
97
+ await store.saveTokens(key, {
98
+ ...resolved,
99
+ tokenType: "Bearer",
100
+ ...(latest.scope ? { scope: latest.scope } : {}),
101
+ });
102
+ }
103
+ return resolved;
65
104
  })().finally(() => {
66
105
  codexRefreshInFlight.delete(key);
67
106
  });
68
107
  codexRefreshInFlight.set(key, pending);
69
108
  return pending;
70
109
  }
110
+ /** Refresh an account's token at most once at a time. */
111
+ async function refreshCodexTokenOnce(key, refreshToken) {
112
+ return refreshCodexTokenOnceWithDependencies(key, refreshToken, tokenStore, refreshCodexToken);
113
+ }
71
114
  // Headers we never forward upstream (hop-by-hop, client creds, or things we
72
115
  // re-derive). The client's own auth is replaced with the pooled account's.
73
116
  const BLOCKED_UPSTREAM_HEADERS = new Set([
@@ -114,13 +157,6 @@ async function loadCodexProxyAccounts() {
114
157
  const refreshed = await refreshCodexTokenOnce(key, tokens.refreshToken);
115
158
  accessToken = refreshed.accessToken;
116
159
  expiresAt = refreshed.expiresAt ?? expiresAt;
117
- await tokenStore.saveTokens(key, {
118
- accessToken,
119
- refreshToken: refreshed.refreshToken,
120
- expiresAt: expiresAt ?? Date.now() + 3_600_000,
121
- tokenType: "Bearer",
122
- scope: tokens.scope,
123
- });
124
160
  }
125
161
  catch (error) {
126
162
  // Keep the stale token; a 401 upstream will trigger rotation.
@@ -234,14 +270,18 @@ function publishCodexHeaders(ctx, account, attempt, quota) {
234
270
  }
235
271
  }
236
272
  /** Core pooled handler for POST /backend-api/codex/responses. */
237
- async function handleCodexResponsesRequest(ctx) {
273
+ export async function handleCodexResponsesRequest(ctx) {
238
274
  const requestStartTime = Date.now();
239
275
  const body = ctx.body ?? {};
240
276
  const bodyStr = JSON.stringify(body);
241
277
  const model = typeof body.model === "string"
242
278
  ? body.model
243
279
  : "-";
244
- const writeLog = (account, responseStatus, extra = {}) => logRequest({
280
+ // A Codex call made as an inner Anthropic fallback is an upstream attempt,
281
+ // not an independently final client request. The parent fallback owns the
282
+ // final status and can still recover with a later provider.
283
+ const isFallbackRequest = ctx.metadata?.[CODEX_FALLBACK_METADATA_KEY] === true;
284
+ const writeFinalLog = (account, responseStatus, extra = {}) => logRequest({
245
285
  timestamp: new Date().toISOString(),
246
286
  requestId: ctx.requestId,
247
287
  method: ctx.method,
@@ -251,16 +291,63 @@ async function handleCodexResponsesRequest(ctx) {
251
291
  toolCount: Array.isArray(body.tools)
252
292
  ? body.tools.length
253
293
  : 0,
254
- account,
255
- accountType: "codex-oauth",
294
+ account: account?.label ?? "",
295
+ ...(account ? { accountKey: account.key } : {}),
296
+ accountType: account ? CODEX_ACCOUNT_TYPE : "",
297
+ // This is the cost provider. accountKey and the response header identify
298
+ // the actual Codex pool that supplied the credential.
299
+ provider: "openai",
256
300
  ...buildClientAttribution(ctx.headers),
257
301
  responseStatus,
258
302
  responseTimeMs: Date.now() - requestStartTime,
259
303
  ...extra,
260
304
  });
305
+ let finalOutcomeRecorded = false;
306
+ const recordFinalOutcome = async (account, responseStatus, extra = {}) => {
307
+ if (isFallbackRequest || finalOutcomeRecorded) {
308
+ return;
309
+ }
310
+ finalOutcomeRecorded = true;
311
+ if (responseStatus >= 400) {
312
+ recordFinalError(responseStatus, account?.label, account ? CODEX_ACCOUNT_TYPE : undefined, {
313
+ requestId: ctx.requestId,
314
+ ...(account ? { accountKey: account.key } : {}),
315
+ errorType: extra.errorType,
316
+ terminalOutcome: extra.terminalOutcome ?? "handler_error",
317
+ message: extra.errorMessage,
318
+ errorCode: extra.errorCode,
319
+ });
320
+ }
321
+ else {
322
+ recordFinalSuccess(account?.label, account ? CODEX_ACCOUNT_TYPE : undefined);
323
+ }
324
+ await writeFinalLog(account, responseStatus, extra);
325
+ };
326
+ const writeAttempt = (account, attempt, startedAt, responseStatus, extra = {}) => {
327
+ void logRequestAttempt({
328
+ timestamp: new Date().toISOString(),
329
+ requestId: ctx.requestId,
330
+ attempt,
331
+ method: ctx.method,
332
+ path: ctx.path,
333
+ model,
334
+ stream: true,
335
+ toolCount: Array.isArray(body.tools)
336
+ ? body.tools.length
337
+ : 0,
338
+ account: account.label,
339
+ accountKey: account.key,
340
+ accountType: CODEX_ACCOUNT_TYPE,
341
+ provider: "openai",
342
+ responseStatus,
343
+ responseTimeMs: Date.now() - requestStartTime,
344
+ attemptDurationMs: Date.now() - startedAt,
345
+ ...extra,
346
+ }).catch(() => undefined);
347
+ };
261
348
  const accounts = await loadCodexProxyAccounts();
262
349
  if (accounts.length === 0) {
263
- await writeLog("", 401, {
350
+ await recordFinalOutcome(undefined, 401, {
264
351
  errorType: "no_accounts",
265
352
  errorMessage: "No Codex accounts",
266
353
  });
@@ -280,7 +367,7 @@ async function handleCodexResponsesRequest(ctx) {
280
367
  const retryAfterSec = soonest
281
368
  ? Math.max(1, Math.ceil((soonest - now) / 1000))
282
369
  : 60;
283
- await writeLog("", 429, {
370
+ await recordFinalOutcome(undefined, 429, {
284
371
  errorType: "all_accounts_cooling",
285
372
  errorMessage: "All Codex accounts are rate-limited",
286
373
  });
@@ -300,11 +387,15 @@ async function handleCodexResponsesRequest(ctx) {
300
387
  let attempt = 0;
301
388
  let lastErrorMessage = "All Codex accounts failed";
302
389
  let lastErrorStatus = 502;
390
+ let lastAttemptedAccount;
303
391
  for (const account of eligible) {
304
- attempt += 1;
305
392
  let authRetried = false;
306
393
  // Same-account loop only re-runs once, for a post-401 token refresh.
307
394
  for (;;) {
395
+ attempt += 1;
396
+ const attemptStartedAt = Date.now();
397
+ lastAttemptedAccount = account;
398
+ recordAttempt(account.label, CODEX_ACCOUNT_TYPE);
308
399
  let upstream;
309
400
  try {
310
401
  upstream = await fetch(CODEX_RESPONSES_URL, {
@@ -320,6 +411,17 @@ async function handleCodexResponsesRequest(ctx) {
320
411
  // can act on. Keep the detail in the log and return a fixed string, so
321
412
  // internal topology never reaches the client.
322
413
  logger.debug(`Codex upstream fetch failed (${account.label}): ${sanitizeForLog(error instanceof Error ? error.message : String(error))}`);
414
+ const errorMessage = summarizeCodexUpstreamError(error instanceof Error ? error.message : String(error), "Codex upstream request failed");
415
+ const errorCode = getCodexTransportErrorCode(error);
416
+ const transportScope = codexTransportScope(error);
417
+ recordAttemptError(account.label, CODEX_ACCOUNT_TYPE, 502);
418
+ writeAttempt(account, attempt, attemptStartedAt, 502, {
419
+ errorType: "network_error",
420
+ errorMessage,
421
+ ...(errorCode ? { errorCode } : {}),
422
+ transportScope,
423
+ retryable: true,
424
+ });
323
425
  lastErrorMessage = "Codex upstream request failed";
324
426
  lastErrorStatus = 502;
325
427
  break; // rotate to next account
@@ -336,50 +438,72 @@ async function handleCodexResponsesRequest(ctx) {
336
438
  clearAccountCooldown(account.key, account.expiredCooldownUntil).catch(() => undefined);
337
439
  }
338
440
  publishCodexHeaders(ctx, account, attempt, quota);
339
- await writeLog(account.label, upstream.status);
441
+ writeAttempt(account, attempt, attemptStartedAt, upstream.status);
340
442
  const headers = {
341
443
  "content-type": upstream.headers.get("content-type") ?? "text/event-stream",
342
444
  "cache-control": "no-cache",
343
445
  connection: "keep-alive",
344
446
  ...(ctx.responseHeaders ?? {}),
345
447
  };
346
- // Tap the relay for token usage. The log above is written first and
347
- // unconditionally so a request is never lost when a client hangs up
348
- // mid-stream; this emits a second record for the same requestId
349
- // carrying the counts, which proxyAnalysis merges. If the stream shape
350
- // is not recognised, usage resolves null and nothing extra is written —
351
- // i.e. exactly the previous behaviour.
352
448
  if (!upstream.body) {
449
+ await recordFinalOutcome(account, upstream.status, {
450
+ terminalOutcome: "bodyless",
451
+ });
353
452
  return new Response(upstream.body, {
354
453
  status: upstream.status,
355
454
  headers,
356
455
  });
357
456
  }
358
457
  const { stream: usageTap, usage: usageSeen } = createCodexUsageTap();
359
- usageSeen
360
- .then((usage) => {
361
- if (!usage) {
362
- return;
363
- }
364
- return writeLog(account.label, upstream.status, {
365
- provider: "openai",
366
- inputTokens: usage.inputTokens,
367
- outputTokens: usage.outputTokens,
368
- cacheReadTokens: usage.cacheReadTokens,
369
- cacheCreationTokens: usage.cacheCreationTokens,
370
- });
371
- })
372
- .catch(() => undefined);
373
- return new Response(upstream.body.pipeThrough(usageTap), {
458
+ const relay = new Response(upstream.body.pipeThrough(usageTap), {
374
459
  status: upstream.status,
375
460
  headers,
376
461
  });
462
+ return trackProxyResponse(relay, () => undefined, {
463
+ onTerminal: ({ outcome }) => {
464
+ void usageSeen
465
+ .then((usage) => {
466
+ const usageExtra = usage
467
+ ? {
468
+ inputTokens: usage.inputTokens,
469
+ outputTokens: usage.outputTokens,
470
+ cacheReadTokens: usage.cacheReadTokens,
471
+ cacheCreationTokens: usage.cacheCreationTokens,
472
+ }
473
+ : {};
474
+ if (outcome === "completed" || outcome === "bodyless") {
475
+ return recordFinalOutcome(account, upstream.status, {
476
+ terminalOutcome: outcome,
477
+ ...usageExtra,
478
+ });
479
+ }
480
+ return recordFinalOutcome(account, outcome === "client_cancelled" ? 499 : 502, {
481
+ errorType: outcome === "client_cancelled"
482
+ ? "client_cancelled"
483
+ : "stream_error",
484
+ errorMessage: outcome === "client_cancelled"
485
+ ? "Client cancelled Codex stream"
486
+ : "Codex upstream stream failed",
487
+ terminalOutcome: outcome,
488
+ ...usageExtra,
489
+ });
490
+ })
491
+ .catch(() => undefined);
492
+ },
493
+ });
377
494
  }
378
495
  const errText = await upstream.text().catch(() => "");
379
496
  // 401/403 → try a forced token refresh once, then rotate.
380
497
  if ((upstream.status === 401 || upstream.status === 403) &&
381
498
  !authRetried &&
382
499
  account.refreshToken) {
500
+ const errorMessage = summarizeCodexUpstreamError(errText, "Codex authentication rejected upstream");
501
+ recordAttemptError(account.label, CODEX_ACCOUNT_TYPE, upstream.status);
502
+ writeAttempt(account, attempt, attemptStartedAt, upstream.status, {
503
+ errorType: "authentication_error",
504
+ errorMessage,
505
+ retryable: true,
506
+ });
383
507
  authRetried = true;
384
508
  const staleTokens = {
385
509
  accessToken: account.token,
@@ -387,17 +511,11 @@ async function handleCodexResponsesRequest(ctx) {
387
511
  expiresAt: account.expiresAt ?? 0,
388
512
  };
389
513
  try {
390
- const refreshed = await refreshCodexToken(account.refreshToken);
514
+ const refreshed = await refreshCodexTokenOnce(account.key, account.refreshToken);
391
515
  account.token = refreshed.accessToken;
392
516
  account.refreshToken = refreshed.refreshToken ?? account.refreshToken;
393
517
  account.expiresAt = refreshed.expiresAt ?? account.expiresAt;
394
518
  account.accountId = resolveCodexAccountId(refreshed.accessToken);
395
- await tokenStore.saveTokens(account.key, {
396
- accessToken: account.token,
397
- refreshToken: account.refreshToken,
398
- expiresAt: account.expiresAt ?? Date.now() + 3_600_000,
399
- tokenType: "Bearer",
400
- });
401
519
  continue; // retry same account with the fresh token
402
520
  }
403
521
  catch (error) {
@@ -432,6 +550,16 @@ async function handleCodexResponsesRequest(ctx) {
432
550
  const retryAfterMs = parseRetryAfterMs(upstream.headers.get("retry-after"));
433
551
  const plan = planCodexCooldown(quota, retryAfterMs, Date.now());
434
552
  await saveAccountCooldown(account.key, plan.coolingUntil, plan.reason).catch(() => undefined);
553
+ const rateLimitKind = plan.reason === "transient" ? "transient" : "quota";
554
+ const errorMessage = summarizeCodexUpstreamError(errText, "Codex account rate-limited");
555
+ recordAttemptError(account.label, CODEX_ACCOUNT_TYPE, upstream.status, rateLimitKind);
556
+ writeAttempt(account, attempt, attemptStartedAt, upstream.status, {
557
+ errorType: "rate_limit_error",
558
+ errorMessage,
559
+ retryable: true,
560
+ rateLimitKind,
561
+ cooldownReason: plan.reason,
562
+ });
435
563
  lastErrorStatus = 429;
436
564
  lastErrorMessage = "Codex account rate-limited";
437
565
  break; // rotate
@@ -443,12 +571,22 @@ async function handleCodexResponsesRequest(ctx) {
443
571
  // of letting it stay first in line with unknown quota.
444
572
  await saveAccountCooldown(account.key, Date.now() + CODEX_AUTH_COOLDOWN_MS, "auth").catch(() => undefined);
445
573
  }
574
+ const errorMessage = summarizeCodexUpstreamError(errText, "Codex error");
575
+ const errorType = upstream.status === 401 || upstream.status === 403
576
+ ? "authentication_error"
577
+ : "api_error";
578
+ recordAttemptError(account.label, CODEX_ACCOUNT_TYPE, upstream.status);
579
+ writeAttempt(account, attempt, attemptStartedAt, upstream.status, {
580
+ errorType,
581
+ errorMessage,
582
+ retryable: upstream.status >= 500,
583
+ });
446
584
  lastErrorStatus = upstream.status >= 500 ? 502 : upstream.status;
447
- lastErrorMessage = sanitizeForLog(errText).slice(0, 200) || "Codex error";
585
+ lastErrorMessage = errorMessage;
448
586
  break; // rotate
449
587
  }
450
588
  }
451
- await writeLog("", lastErrorStatus, {
589
+ await recordFinalOutcome(lastAttemptedAccount, lastErrorStatus, {
452
590
  errorType: "all_accounts_failed",
453
591
  errorMessage: lastErrorMessage,
454
592
  });
@@ -529,18 +667,12 @@ async function handleCodexModelsRequest(ctx) {
529
667
  if (!authRetried && account.refreshToken) {
530
668
  authRetried = true;
531
669
  try {
532
- const refreshed = await refreshCodexToken(account.refreshToken);
670
+ const refreshed = await refreshCodexTokenOnce(account.key, account.refreshToken);
533
671
  account.token = refreshed.accessToken;
534
672
  account.refreshToken =
535
673
  refreshed.refreshToken ?? account.refreshToken;
536
674
  account.expiresAt = refreshed.expiresAt ?? account.expiresAt;
537
675
  account.accountId = resolveCodexAccountId(refreshed.accessToken);
538
- await tokenStore.saveTokens(account.key, {
539
- accessToken: account.token,
540
- refreshToken: account.refreshToken,
541
- expiresAt: account.expiresAt ?? Date.now() + 3_600_000,
542
- tokenType: "Bearer",
543
- });
544
676
  continue; // retry this account with the fresh token
545
677
  }
546
678
  catch {
@@ -605,5 +737,6 @@ export const __testHooks = {
605
737
  buildCodexUpstreamHeaders,
606
738
  planCodexCooldown,
607
739
  refreshCodexTokenOnce,
740
+ refreshCodexTokenOnceWithDependencies,
608
741
  codexRefreshInFlightSize: () => codexRefreshInFlight.size,
609
742
  };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared internal types for the Anthropic-compatible proxy route.
3
+ */
4
+ /** A deterministic upstream validation failure that must reach the caller. */
5
+ export type AnthropicInvalidRequestFailure = {
6
+ status: number;
7
+ body: string;
8
+ contentType?: string;
9
+ };
10
+ /** A credential failure deferred until peer and provider fallbacks are tried. */
11
+ export type DeferredClaudeAccountFailure = {
12
+ status: number;
13
+ message: string;
14
+ errorType: string;
15
+ responseHeaders?: Record<string, string>;
16
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared internal types for the Anthropic-compatible proxy route.
3
+ */
4
+ export {};
@@ -11,7 +11,7 @@ import type { PPTGenerationResult } from "./ppt.js";
11
11
  import type { AvatarResult } from "./avatar.js";
12
12
  import type { MusicResult } from "./music.js";
13
13
  import type { OAuthTokens } from "./auth.js";
14
- import type { AccountQuota } from "./proxy.js";
14
+ import type { AccountQuota, ProxyPassthroughAccount } from "./proxy.js";
15
15
  import type { ClaudeSubscriptionTier } from "./subscription.js";
16
16
  import type { ServerFramework } from "./server.js";
17
17
  import type { AuthProviderType } from "./auth.js";
@@ -954,7 +954,7 @@ export type AuthCommandArgs = BaseCommandArgs & {
954
954
  label?: string;
955
955
  account?: string;
956
956
  force?: boolean;
957
- /** `auth list --refresh`: fetch fresh limits from Anthropic before listing */
957
+ /** `auth list --refresh`: fetch fresh provider limits before listing */
958
958
  refresh?: boolean;
959
959
  /** Path to the proxy config YAML, used by set-/get-/clear-primary */
960
960
  config?: string;
@@ -969,12 +969,44 @@ export type AuthCommandArgs = BaseCommandArgs & {
969
969
  /** Yargs positional arguments */
970
970
  _?: (string | number)[];
971
971
  };
972
+ /** Refresh state for a provider-qualified account. */
973
+ export type AuthListRefreshStatus = "refreshed" | "snapshot" | "unavailable" | "not_supported";
974
+ /** Fresh-limit result for one provider-qualified account. */
975
+ export type AuthListRefreshAccountResult = {
976
+ /** Provider prefix parsed from the configured account key. */
977
+ provider: string;
978
+ /** A missing limit is explicit rather than being rendered as an unexplained dash. */
979
+ status: AuthListRefreshStatus;
980
+ error?: string;
981
+ };
982
+ /** One direct quota-adapter result used by `auth list --refresh`. */
983
+ export type AuthListDirectQuotaRefreshResult = {
984
+ status: "refreshed";
985
+ quota: AccountQuota;
986
+ } | {
987
+ status: "unavailable" | "not_supported";
988
+ error?: string;
989
+ };
990
+ /** Provider-specific quota capability used by the generic auth-list refresh. */
991
+ export type AuthListQuotaRefreshAdapter = {
992
+ /** A successful local proxy `/limits` response is authoritative for this provider. */
993
+ supportsProxyRefresh?: boolean;
994
+ listAccounts: () => Promise<ProxyPassthroughAccount[]>;
995
+ priorQuotaKeys: (account: ProxyPassthroughAccount) => readonly string[];
996
+ refreshAccount: (account: ProxyPassthroughAccount, options: {
997
+ prior: AccountQuota | null;
998
+ }) => Promise<AuthListDirectQuotaRefreshResult>;
999
+ };
1000
+ /** Applies one account's explicit auth-list refresh state. */
1001
+ export type AuthListRefreshResultSetter = (key: string, status: AuthListRefreshStatus, error?: string) => void;
972
1002
  /** Outcome of the `auth list --refresh` fresh-limit fetch. */
973
1003
  export type AuthListRefreshOutcome = {
974
1004
  /** How the fresh limits were obtained ("none" when every path failed). */
975
- via: "proxy" | "direct" | "none";
976
- /** Freshly fetched quotas keyed by account label; null when none fetched. */
1005
+ via: "proxy" | "direct" | "mixed" | "none";
1006
+ /** Freshly fetched quotas keyed by provider-qualified account key. */
977
1007
  quotas: Record<string, AccountQuota> | null;
1008
+ /** Per-account refresh status, also keyed by provider-qualified account key. */
1009
+ accounts: Record<string, AuthListRefreshAccountResult>;
978
1010
  /** Per-account and transport errors, already formatted for display. */
979
1011
  errors: string[];
980
1012
  };
@@ -1220,7 +1252,7 @@ export type ProviderSetupConfig = {
1220
1252
  endpoint?: string;
1221
1253
  isReconfiguring?: boolean;
1222
1254
  };
1223
- /** Providers supported by the `neurolink auth` command. */
1255
+ /** Providers with first-class credential flows implemented by `neurolink auth`. */
1224
1256
  export type SupportedProvider = "anthropic" | "codex";
1225
1257
  /** Arguments for `neurolink autoresearch init`. */
1226
1258
  export type AutoresearchInitArgs = {
@@ -11,7 +11,7 @@
11
11
  * stored through the same AccountQuota shape as Anthropic — its primary window
12
12
  * maps onto the session fields and its secondary window onto the weekly fields.
13
13
  */
14
- import type { AccountCoolingReason, AccountQuota } from "./proxy.js";
14
+ import type { AccountCoolingReason, AccountQuota, InternalResult } from "./proxy.js";
15
15
  /** Token block inside `~/.codex/auth.json`. */
16
16
  export type CodexAuthFileTokens = {
17
17
  id_token?: string;
@@ -93,3 +93,64 @@ export type CodexRuntimeAccount = {
93
93
  * spent record — nothing else ever reaps it. */
94
94
  expiredCooldownUntil?: number;
95
95
  };
96
+ /** Provider-qualified account identity used by proxy status rendering. */
97
+ export type CodexProxyStatusAccountIdentity = {
98
+ provider: "anthropic";
99
+ key: string;
100
+ } | {
101
+ provider: "codex";
102
+ key: string;
103
+ } | {
104
+ provider: "other";
105
+ key: null;
106
+ };
107
+ /** A text or image content part accepted by the Codex Responses backend. */
108
+ export type CodexContentPart = {
109
+ type: "input_text";
110
+ text: string;
111
+ } | {
112
+ type: "output_text";
113
+ text: string;
114
+ } | {
115
+ type: "input_image";
116
+ image_url: string;
117
+ };
118
+ /** A single item in a Codex Responses request. */
119
+ export type CodexResponsesInputItem = {
120
+ role: "user" | "assistant";
121
+ content: CodexContentPart[];
122
+ } | {
123
+ type: "function_call";
124
+ call_id: string;
125
+ name: string;
126
+ arguments: string;
127
+ } | {
128
+ type: "function_call_output";
129
+ call_id: string;
130
+ output: string;
131
+ };
132
+ /** Request shape used to bridge Anthropic Messages traffic to Codex Responses. */
133
+ export type CodexResponsesRequest = {
134
+ model: string;
135
+ input: CodexResponsesInputItem[];
136
+ stream: true;
137
+ store: false;
138
+ instructions?: string;
139
+ tools?: Array<{
140
+ type: "function";
141
+ name: string;
142
+ description?: string;
143
+ parameters: Record<string, unknown>;
144
+ }>;
145
+ tool_choice?: "auto" | "required" | "none" | {
146
+ type: "function";
147
+ name: string;
148
+ };
149
+ };
150
+ /** Fully buffered Codex result rendered back as an Anthropic response. */
151
+ export type CodexFallbackResult = {
152
+ text: string;
153
+ toolCalls: NonNullable<InternalResult["toolCalls"]>;
154
+ usage?: NonNullable<InternalResult["usage"]>;
155
+ finishReason: "end_turn" | "tool_use";
156
+ };
@@ -10,6 +10,7 @@ export * from "./artifact.js";
10
10
  export * from "./auth.js";
11
11
  export * from "./autoresearch.js";
12
12
  export * from "./circuitBreakerErrors.js";
13
+ export * from "./claudeProxy.js";
13
14
  export * from "./cli.js";
14
15
  export * from "./client.js";
15
16
  export * from "./codex.js";
@@ -11,6 +11,7 @@ export * from "./artifact.js";
11
11
  export * from "./auth.js";
12
12
  export * from "./autoresearch.js";
13
13
  export * from "./circuitBreakerErrors.js";
14
+ export * from "./claudeProxy.js";
14
15
  export * from "./cli.js";
15
16
  export * from "./client.js";
16
17
  export * from "./codex.js";