@juspay/neurolink 12.15.0 → 12.16.0

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.
@@ -103,9 +103,14 @@ function windowResetEpochSeconds(window, nowSeconds) {
103
103
  }
104
104
  return 0;
105
105
  }
106
- function deriveWindowStatus(usedFraction) {
107
- return usedFraction >= 1 ? "rejected" : "allowed";
106
+ /** Classify only finite, nonnegative utilization; invalid measurements stay unknown. */
107
+ function deriveWindowStatus(percent) {
108
+ if (typeof percent !== "number" || !Number.isFinite(percent) || percent < 0) {
109
+ return "unknown";
110
+ }
111
+ return percent >= 100 ? "rejected" : "allowed";
108
112
  }
113
+ /** Normalize a reported window while retaining uncertainty in its status. */
109
114
  function toQuotaWindow(kind, group, window, nowSeconds) {
110
115
  if (!window) {
111
116
  return null;
@@ -115,7 +120,7 @@ function toQuotaWindow(kind, group, window, nowSeconds) {
115
120
  kind,
116
121
  group,
117
122
  used,
118
- status: deriveWindowStatus(used),
123
+ status: deriveWindowStatus(window.used_percent),
119
124
  resetsAt: windowResetEpochSeconds(window, nowSeconds),
120
125
  isActive: true,
121
126
  };
@@ -137,11 +142,16 @@ export function codexRateLimitsToQuota(rateLimits, now = Date.now()) {
137
142
  windows.push(secondaryWindow);
138
143
  }
139
144
  return {
145
+ unifiedStatus: rateLimits?.limit_reached === true || rateLimits?.allowed === false
146
+ ? "rejected"
147
+ : rateLimits?.allowed === true
148
+ ? "allowed"
149
+ : "unknown",
140
150
  sessionUsed,
141
- sessionStatus: deriveWindowStatus(sessionUsed),
151
+ sessionStatus: deriveWindowStatus(primary?.used_percent),
142
152
  sessionResetAt: windowResetEpochSeconds(primary, nowSeconds),
143
153
  weeklyUsed,
144
- weeklyStatus: deriveWindowStatus(weeklyUsed),
154
+ weeklyStatus: deriveWindowStatus(secondary?.used_percent),
145
155
  weeklyResetAt: windowResetEpochSeconds(secondary, nowSeconds),
146
156
  // Codex has no overage/fallback concept; keep neutral defaults.
147
157
  fallbackPercentage: 0,
@@ -169,7 +179,7 @@ export function parseCodexRateLimitHeaders(headers, now = Date.now()) {
169
179
  const rateLimits = "rate_limits" in parsed && parsed.rate_limits
170
180
  ? parsed.rate_limits
171
181
  : parsed;
172
- return codexRateLimitsToQuota(rateLimits, now);
182
+ return { ...codexRateLimitsToQuota(rateLimits, now), source: "headers" };
173
183
  }
174
184
  catch {
175
185
  return null;
@@ -204,6 +214,8 @@ export async function fetchCodexAccountUsage(account, options = {}) {
204
214
  const usage = (await response.json());
205
215
  const rateLimits = usage.rate_limit
206
216
  ? {
217
+ allowed: usage.rate_limit.allowed,
218
+ limit_reached: usage.rate_limit.limit_reached,
207
219
  primary: usage.rate_limit.primary_window,
208
220
  secondary: usage.rate_limit.secondary_window,
209
221
  }
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { ClaudeStreamSerializer, generateToolUseId } from "./claudeFormat.js";
10
10
  import { extractCodexUsage } from "./codexUsage.js";
11
+ import { proxyTokenUsage } from "./proxyTokenUsage.js";
11
12
  export class CodexFallbackResponseError extends Error {
12
13
  status;
13
14
  responseBody;
@@ -367,13 +368,16 @@ export function parseCodexFallbackSSE(sse) {
367
368
  }
368
369
  const parsedUsage = extractCodexUsage(payload);
369
370
  if (parsedUsage) {
370
- usage = {
371
- input: parsedUsage.inputTokens,
372
- output: parsedUsage.outputTokens,
373
- total: parsedUsage.inputTokens + parsedUsage.outputTokens,
371
+ usage = proxyTokenUsage({
372
+ inputTokens: parsedUsage.inputTokens,
373
+ outputTokens: parsedUsage.outputTokens,
374
+ reasoningTokens: parsedUsage.reasoningTokensObserved
375
+ ? parsedUsage.reasoningTokens
376
+ : undefined,
374
377
  cacheReadTokens: parsedUsage.cacheReadTokens,
375
378
  cacheCreationTokens: parsedUsage.cacheCreationTokens,
376
- };
379
+ inputIncludesCachedTokens: true,
380
+ });
377
381
  }
378
382
  textFromResponse = outputTextFromResponse(payload);
379
383
  }
@@ -516,13 +520,16 @@ export async function createCodexFallbackStream(response, model) {
516
520
  completed = true;
517
521
  const parsedUsage = extractCodexUsage(payload);
518
522
  if (parsedUsage) {
519
- usage = {
520
- input: parsedUsage.inputTokens,
521
- output: parsedUsage.outputTokens,
522
- total: parsedUsage.inputTokens + parsedUsage.outputTokens,
523
+ usage = proxyTokenUsage({
524
+ inputTokens: parsedUsage.inputTokens,
525
+ outputTokens: parsedUsage.outputTokens,
526
+ reasoningTokens: parsedUsage.reasoningTokensObserved
527
+ ? parsedUsage.reasoningTokens
528
+ : undefined,
523
529
  cacheReadTokens: parsedUsage.cacheReadTokens,
524
530
  cacheCreationTokens: parsedUsage.cacheCreationTokens,
525
- };
531
+ inputIncludesCachedTokens: true,
532
+ });
526
533
  }
527
534
  const responseBody = payload.response;
528
535
  if (isRecord(responseBody) && Array.isArray(responseBody.output)) {
@@ -107,6 +107,9 @@ export function extractCodexUsage(payload) {
107
107
  cacheReadTokens: nonNegativeInt(inputDetails?.cached_tokens),
108
108
  cacheCreationTokens: nonNegativeInt(inputDetails?.cache_write_tokens),
109
109
  reasoningTokens: nonNegativeInt(outputDetails?.reasoning_tokens),
110
+ reasoningTokensObserved: typeof outputDetails?.reasoning_tokens === "number" &&
111
+ Number.isFinite(outputDetails.reasoning_tokens) &&
112
+ outputDetails.reasoning_tokens >= 0,
110
113
  };
111
114
  }
112
115
  /**
@@ -1,3 +1,4 @@
1
+ import { proxyTokenUsage } from "./proxyTokenUsage.js";
1
2
  import { createReadStream } from "node:fs";
2
3
  import { isDeepStrictEqual } from "node:util";
3
4
  import { isProxyAuxiliaryRequest } from "./proxyRequestKind.js";
@@ -411,8 +412,15 @@ function summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByR
411
412
  request.outputTokens !== null ||
412
413
  request.cacheReadTokens !== null ||
413
414
  request.cacheCreationTokens !== null) {
415
+ const billingUsage = proxyTokenUsage({
416
+ inputIncludesCachedTokens: request.inputIncludesCachedTokens,
417
+ inputTokens: request.inputTokens ?? 0,
418
+ outputTokens: request.outputTokens ?? 0,
419
+ cacheCreationTokens: request.cacheCreationTokens ?? 0,
420
+ cacheReadTokens: request.cacheReadTokens ?? 0,
421
+ });
414
422
  requestsWithUsage += 1;
415
- inputTokens += request.inputTokens ?? 0;
423
+ inputTokens += billingUsage.input;
416
424
  outputTokens += request.outputTokens ?? 0;
417
425
  cacheReadTokens += request.cacheReadTokens ?? 0;
418
426
  cacheCreationTokens += request.cacheCreationTokens ?? 0;
@@ -422,16 +430,7 @@ function summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByR
422
430
  // "openai-compatible" resolves to the cross-provider table search in
423
431
  // pricing.ts, which finds the model wherever it lives — a far better
424
432
  // guess than assuming Anthropic and pricing a GPT model at $0.
425
- const cost = calculateCost(request.provider ?? "openai-compatible", request.model, {
426
- input: request.inputTokens ?? 0,
427
- output: request.outputTokens ?? 0,
428
- total: (request.inputTokens ?? 0) +
429
- (request.outputTokens ?? 0) +
430
- (request.cacheCreationTokens ?? 0) +
431
- (request.cacheReadTokens ?? 0),
432
- cacheCreationTokens: request.cacheCreationTokens ?? 0,
433
- cacheReadTokens: request.cacheReadTokens ?? 0,
434
- });
433
+ const cost = calculateCost(request.provider ?? "openai-compatible", request.model, billingUsage);
435
434
  // Ask the table directly rather than inferring from cost > 0: a real
436
435
  // request with trivial usage can round to $0.000000 and is priced, not
437
436
  // unpriced.
@@ -982,6 +981,10 @@ export async function analyzeProxyLogs(options) {
982
981
  accountType: stringValue(record.accountType) ?? "unknown",
983
982
  model: stringValue(record.model),
984
983
  provider: stringValue(record.provider),
984
+ inputIncludesCachedTokens: typeof record.inputIncludesCachedTokens === "boolean"
985
+ ? record.inputIncludesCachedTokens
986
+ : typeof record.path === "string" &&
987
+ record.path.includes("/backend-api/codex/"),
985
988
  inputTokens: finiteNumber(record.inputTokens),
986
989
  outputTokens: finiteNumber(record.outputTokens),
987
990
  cacheReadTokens: finiteNumber(record.cacheReadTokens),
@@ -0,0 +1,7 @@
1
+ import type { TokenUsage, UsageContext } from "../types/index.js";
2
+ /** Convert proxy wire usage into disjoint billing buckets. Codex input already
3
+ * includes cache tokens; reasoning is included in output for both providers.
4
+ * OpenAI's documented formula subtracts both cached_tokens and cache_write_tokens:
5
+ * https://developers.openai.com/api/docs/guides/prompt-caching#monitor-cache-performance
6
+ */
7
+ export declare function proxyTokenUsage(usage: UsageContext): TokenUsage;
@@ -0,0 +1,21 @@
1
+ /** Convert proxy wire usage into disjoint billing buckets. Codex input already
2
+ * includes cache tokens; reasoning is included in output for both providers.
3
+ * OpenAI's documented formula subtracts both cached_tokens and cache_write_tokens:
4
+ * https://developers.openai.com/api/docs/guides/prompt-caching#monitor-cache-performance
5
+ */
6
+ export function proxyTokenUsage(usage) {
7
+ const cached = usage.cacheReadTokens + usage.cacheCreationTokens;
8
+ const input = usage.inputIncludesCachedTokens
9
+ ? Math.max(0, usage.inputTokens - cached)
10
+ : usage.inputTokens;
11
+ return {
12
+ input,
13
+ output: usage.outputTokens,
14
+ total: usage.inputIncludesCachedTokens
15
+ ? usage.inputTokens + usage.outputTokens
16
+ : input + cached + usage.outputTokens,
17
+ cacheReadTokens: usage.cacheReadTokens,
18
+ cacheCreationTokens: usage.cacheCreationTokens,
19
+ reasoning: usage.reasoningTokens,
20
+ };
21
+ }
@@ -57,6 +57,8 @@ declare class ProxyTracer {
57
57
  startStream(): Span;
58
58
  /** Record account selection outcome on the root span. */
59
59
  setAccountSelection(ctx: AccountSelectionContext): void;
60
+ /** Attribute fallback metrics to the account that actually served them. */
61
+ setServedAccount(account: string, accountType: string): void;
60
62
  /** Record token usage and cost on the root span. */
61
63
  setUsage(ctx: UsageContext): void;
62
64
  /**
@@ -16,6 +16,7 @@
16
16
  import { SpanStatusCode, SpanKind, ROOT_CONTEXT, isSpanContextValid, propagation, context, metrics, trace, } from "@opentelemetry/api";
17
17
  import { getTracer, setLangfuseContext, } from "../services/server/ai/observability/instrumentation.js";
18
18
  import { OtelBridge } from "../observability/otelBridge.js";
19
+ import { proxyTokenUsage } from "./proxyTokenUsage.js";
19
20
  import { calculateCost } from "../utils/pricing.js";
20
21
  import { TelemetryService } from "../telemetry/telemetryService.js";
21
22
  import { logger } from "../utils/logger.js";
@@ -360,17 +361,22 @@ class ProxyTracer {
360
361
  // Non-fatal
361
362
  });
362
363
  }
364
+ /** Attribute fallback metrics to the account that actually served them. */
365
+ setServedAccount(account, accountType) {
366
+ this.accountEmail = account;
367
+ this.rootSpan.setAttributes({
368
+ "proxy.account.served": account,
369
+ "proxy.account.served_type": accountType,
370
+ });
371
+ }
363
372
  /** Record token usage and cost on the root span. */
364
373
  setUsage(ctx) {
365
374
  this.usage = ctx;
366
- const totalTokens = ctx.inputTokens +
367
- ctx.outputTokens +
368
- ctx.cacheCreationTokens +
369
- ctx.cacheReadTokens +
370
- (ctx.reasoningTokens ?? 0);
371
- // NeuroLink-format token attributes (from SpanAttributes)
375
+ const billingUsage = proxyTokenUsage(ctx);
376
+ const totalTokens = billingUsage.total;
377
+ // Custom token buckets are disjoint; standard GenAI input includes cache.
372
378
  this.rootSpan.setAttributes({
373
- "ai.tokens.input": ctx.inputTokens,
379
+ "ai.tokens.input": billingUsage.input,
374
380
  "ai.tokens.output": ctx.outputTokens,
375
381
  "ai.tokens.total": totalTokens,
376
382
  "ai.tokens.cache_creation": ctx.cacheCreationTokens,
@@ -381,18 +387,12 @@ class ProxyTracer {
381
387
  }
382
388
  // GenAI semantic convention attributes (for Langfuse compatibility)
383
389
  this.rootSpan.setAttributes({
384
- "gen_ai.usage.input_tokens": ctx.inputTokens,
390
+ "gen_ai.usage.input_tokens": totalTokens - ctx.outputTokens,
385
391
  "gen_ai.usage.output_tokens": ctx.outputTokens,
386
392
  "gen_ai.usage.total_tokens": totalTokens,
387
393
  });
388
394
  // Cost calculation via pricing.ts
389
- const cost = calculateCost(this.billingProvider, this.model, {
390
- input: ctx.inputTokens,
391
- output: ctx.outputTokens,
392
- total: totalTokens,
393
- cacheCreationTokens: ctx.cacheCreationTokens,
394
- cacheReadTokens: ctx.cacheReadTokens,
395
- });
395
+ const cost = calculateCost(this.billingProvider, this.model, billingUsage);
396
396
  if (cost > 0) {
397
397
  this.rootSpan.setAttributes({
398
398
  "ai.cost.total": cost,
@@ -665,7 +665,7 @@ class ProxyTracer {
665
665
  model: this.model,
666
666
  account: this.accountEmail ?? "unknown",
667
667
  };
668
- m.tokensInput.add(this.usage.inputTokens, tokenLabels);
668
+ m.tokensInput.add(proxyTokenUsage(this.usage).input, tokenLabels);
669
669
  m.tokensOutput.add(this.usage.outputTokens, tokenLabels);
670
670
  m.tokensCacheRead.add(this.usage.cacheReadTokens, tokenLabels);
671
671
  m.tokensCacheCreation.add(this.usage.cacheCreationTokens, tokenLabels);
@@ -673,18 +673,8 @@ class ProxyTracer {
673
673
  m.tokensReasoning.add(this.usage.reasoningTokens, tokenLabels);
674
674
  }
675
675
  // Cost
676
- const totalTokens = this.usage.inputTokens +
677
- this.usage.outputTokens +
678
- this.usage.cacheCreationTokens +
679
- this.usage.cacheReadTokens +
680
- (this.usage.reasoningTokens ?? 0);
681
- const cost = calculateCost(this.billingProvider, this.model, {
682
- input: this.usage.inputTokens,
683
- output: this.usage.outputTokens,
684
- total: totalTokens,
685
- cacheCreationTokens: this.usage.cacheCreationTokens,
686
- cacheReadTokens: this.usage.cacheReadTokens,
687
- });
676
+ const billingUsage = proxyTokenUsage(this.usage);
677
+ const cost = calculateCost(this.billingProvider, this.model, billingUsage);
688
678
  if (cost > 0) {
689
679
  m.costTotal.add(cost, tokenLabels);
690
680
  }
@@ -711,19 +701,10 @@ class ProxyTracer {
711
701
  if (!this.usage) {
712
702
  return;
713
703
  }
714
- const totalTokens = this.usage.inputTokens +
715
- this.usage.outputTokens +
716
- this.usage.cacheCreationTokens +
717
- this.usage.cacheReadTokens +
718
- (this.usage.reasoningTokens ?? 0);
704
+ const billingUsage = proxyTokenUsage(this.usage);
705
+ const totalTokens = billingUsage.total;
719
706
  const durationMs = Date.now() - this.startTime;
720
- const cost = calculateCost(this.billingProvider, this.model, {
721
- input: this.usage.inputTokens,
722
- output: this.usage.outputTokens,
723
- total: totalTokens,
724
- cacheCreationTokens: this.usage.cacheCreationTokens,
725
- cacheReadTokens: this.usage.cacheReadTokens,
726
- });
707
+ const cost = calculateCost(this.billingProvider, this.model, billingUsage);
727
708
  TelemetryService.getInstance().recordAIRequest(this.billingProvider, this.model, totalTokens, durationMs, cost > 0 ? cost : undefined);
728
709
  }
729
710
  // -------------------------------------------------------------------------
@@ -133,10 +133,14 @@ export function buildQuotaResponseHeaders(context, now = Date.now()) {
133
133
  }
134
134
  const quota = context.quota;
135
135
  if (quota && context.source !== "none") {
136
- set("x-neurolink-quota-session-left-pct", utilizationToLeftPct(quota.sessionUsed));
136
+ set("x-neurolink-quota-session-left-pct", quota.sessionStatus === "unknown"
137
+ ? undefined
138
+ : utilizationToLeftPct(quota.sessionUsed));
137
139
  set("x-neurolink-quota-session-status", quota.sessionStatus);
138
140
  set("x-neurolink-quota-session-resets-in", secondsUntil(quota.sessionResetAt, now));
139
- set("x-neurolink-quota-weekly-left-pct", utilizationToLeftPct(quota.weeklyUsed));
141
+ set("x-neurolink-quota-weekly-left-pct", quota.weeklyStatus === "unknown"
142
+ ? undefined
143
+ : utilizationToLeftPct(quota.weeklyUsed));
140
144
  set("x-neurolink-quota-weekly-status", quota.weeklyStatus);
141
145
  set("x-neurolink-quota-weekly-resets-in", secondsUntil(quota.weeklyResetAt, now));
142
146
  set("x-neurolink-quota-unified-status", quota.unifiedStatus);
@@ -173,7 +177,7 @@ export function summarizePoolHeadroom(entries, now = Date.now()) {
173
177
  continue;
174
178
  }
175
179
  available += 1;
176
- if (entry.quota) {
180
+ if (entry.quota && entry.quota.sessionStatus !== "unknown") {
177
181
  const left = utilizationToLeftPct(entry.quota.sessionUsed);
178
182
  if (bestSessionLeftPct === undefined || left > bestSessionLeftPct) {
179
183
  bestSessionLeftPct = left;
@@ -3693,6 +3693,25 @@ async function executeClaudeCodexFallback(args) {
3693
3693
  registerProxyResponseObserver(ctx.metadata, {
3694
3694
  onTerminal: () => childAccounting,
3695
3695
  });
3696
+ const recordCodexUsage = (result) => {
3697
+ ctx.metadata.codexFallbackModel = model;
3698
+ try {
3699
+ tracer?.setModelSubstitution(body.model, model, "openai");
3700
+ tracer?.setServedAccount(codexHeaders["x-neurolink-account"] ?? "unknown", "codex-oauth");
3701
+ if (result?.usage) {
3702
+ tracer?.setUsage({
3703
+ inputTokens: result.usage.input,
3704
+ outputTokens: result.usage.output,
3705
+ cacheReadTokens: result.usage.cacheReadTokens ?? 0,
3706
+ cacheCreationTokens: result.usage.cacheCreationTokens ?? 0,
3707
+ reasoningTokens: result.usage.reasoning,
3708
+ });
3709
+ }
3710
+ }
3711
+ catch {
3712
+ // Instrumentation must never turn completed output into a failed stream.
3713
+ }
3714
+ };
3696
3715
  const codexHeaders = { ...(codexCtx.responseHeaders ?? {}) };
3697
3716
  if (body.stream) {
3698
3717
  const bridge = await createCodexFallbackStream(codexResponse, body.model);
@@ -3712,12 +3731,14 @@ async function executeClaudeCodexFallback(args) {
3712
3731
  if (status >= 400) {
3713
3732
  ctx.metadata.terminalErrorType = errorType;
3714
3733
  }
3734
+ recordCodexUsage(result);
3715
3735
  tracer?.end(status, Date.now() - requestStartTime);
3716
3736
  logFinalRequest(status, account, accountType, errorType, message, {
3717
3737
  inputTokens: result?.usage?.input,
3718
3738
  outputTokens: result?.usage?.output,
3719
3739
  cacheCreationTokens: result?.usage?.cacheCreationTokens,
3720
3740
  cacheReadTokens: result?.usage?.cacheReadTokens,
3741
+ reasoningTokens: result?.usage?.reasoning,
3721
3742
  });
3722
3743
  recordFallbackAttempt({
3723
3744
  provider: "codex",
@@ -3862,6 +3883,7 @@ async function executeClaudeCodexFallback(args) {
3862
3883
  ...(parsed.usage ? { usage: parsed.usage } : {}),
3863
3884
  toolCalls: parsed.toolCalls,
3864
3885
  };
3886
+ recordCodexUsage(parsed);
3865
3887
  tracer?.end(200, Date.now() - requestStartTime);
3866
3888
  const clientResponse = serializeClaudeResponse(internal, body.model);
3867
3889
  observeClaudeJsonOutput(ctx.metadata, clientResponse);
@@ -3870,6 +3892,7 @@ async function executeClaudeCodexFallback(args) {
3870
3892
  outputTokens: parsed.usage?.output,
3871
3893
  cacheCreationTokens: parsed.usage?.cacheCreationTokens,
3872
3894
  cacheReadTokens: parsed.usage?.cacheReadTokens,
3895
+ reasoningTokens: parsed.usage?.reasoning,
3873
3896
  });
3874
3897
  const clientResponseText = JSON.stringify(clientResponse);
3875
3898
  logProxyBody({
@@ -6009,7 +6032,15 @@ function createClaudeRequestRuntimeContext(args) {
6009
6032
  requestId: ctx.requestId,
6010
6033
  method: ctx.method,
6011
6034
  path: ctx.path,
6012
- model: body.model,
6035
+ model: typeof ctx.metadata.codexFallbackModel === "string"
6036
+ ? ctx.metadata.codexFallbackModel
6037
+ : body.model,
6038
+ ...(typeof ctx.metadata.codexFallbackModel === "string"
6039
+ ? {
6040
+ requestedModel: body.model,
6041
+ inputIncludesCachedTokens: false,
6042
+ }
6043
+ : {}),
6013
6044
  stream: !!body.stream,
6014
6045
  toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
6015
6046
  account: finalAccountLabel ?? "",
@@ -6042,6 +6073,9 @@ function createClaudeRequestRuntimeContext(args) {
6042
6073
  ...(extra?.cacheReadTokens !== undefined
6043
6074
  ? { cacheReadTokens: extra.cacheReadTokens }
6044
6075
  : {}),
6076
+ ...(extra?.reasoningTokens !== undefined
6077
+ ? { reasoningTokens: extra.reasoningTokens }
6078
+ : {}),
6045
6079
  ...(traceCtx
6046
6080
  ? { traceId: traceCtx.traceId, spanId: traceCtx.spanId }
6047
6081
  : {}),
@@ -35,19 +35,15 @@ declare function refreshCodexTokenOnce(key: string, refreshToken: string): Promi
35
35
  */
36
36
  declare function loadCodexProxyAccounts(): Promise<CodexRuntimeAccount[]>;
37
37
  /**
38
- * Order accounts fill-first by quota: eligible (not cooling) first, then least
39
- * session utilization, treating unknown quota as "probe first" so a fresh
40
- * account gets observed rather than starved.
38
+ * Order non-cooling accounts ahead of cooling ones, then defer rejected quota.
39
+ * Within each group, probe unknown quota first and otherwise fill the least-used
40
+ * session so fresh accounts get observed rather than starved.
41
41
  */
42
42
  declare function orderCodexAccounts(accounts: CodexRuntimeAccount[], now: number): CodexRuntimeAccount[];
43
43
  /** Build the upstream request headers, replacing client auth with the account's. */
44
44
  declare function buildCodexUpstreamHeaders(clientHeaders: Record<string, string>, account: CodexRuntimeAccount): Record<string, string>;
45
- /**
46
- * Classify a 429 into a cooldown plan. Codex reports primary (session) and
47
- * secondary (weekly) windows; a rejected window cools until its reset, a plain
48
- * burst limit cools briefly and clamps to 15 min.
49
- */
50
- declare function planCodexCooldown(quota: AccountQuota | null, retryAfterMs: number, now: number): {
45
+ /** Honor known quota resets while bounding unscoped or transient account cooldowns. */
46
+ declare function planCodexCooldown(quota: AccountQuota | null, retryAfterMs: number, now: number, errorText?: string): {
51
47
  coolingUntil: number;
52
48
  reason: RateLimitCoolingReason;
53
49
  };