@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.
- package/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +377 -377
- package/dist/cli/commands/auth.js +6 -2
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +119 -39
- package/dist/cli/index.js +8 -18
- package/dist/cli/lifecycle.d.ts +8 -0
- package/dist/cli/lifecycle.js +90 -0
- package/dist/constants/enums.d.ts +11 -0
- package/dist/constants/enums.js +12 -0
- package/dist/providers/catalog/api-route.json +130 -0
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +3 -0
- package/dist/providers/catalog/schema.d.ts +1 -1
- package/dist/proxy/codexAccountUsage.js +18 -6
- package/dist/proxy/codexFallback.js +17 -10
- package/dist/proxy/codexUsage.js +3 -0
- package/dist/proxy/proxyAnalysis.js +14 -11
- package/dist/proxy/proxyTokenUsage.d.ts +7 -0
- package/dist/proxy/proxyTokenUsage.js +21 -0
- package/dist/proxy/proxyTracer.d.ts +2 -0
- package/dist/proxy/proxyTracer.js +21 -40
- package/dist/proxy/quotaHeaders.js +7 -3
- package/dist/server/routes/claudeProxyRoutes.js +35 -1
- package/dist/server/routes/codexProxyRoutes.d.ts +5 -9
- package/dist/server/routes/codexProxyRoutes.js +106 -18
- package/dist/types/codex.d.ts +10 -0
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +4 -0
- package/dist/types/proxy.d.ts +24 -6
- package/dist/utils/pricing.js +4 -2
- package/docs-site/static/search-index.json +1 -1
- package/package.json +1 -1
|
@@ -103,9 +103,14 @@ function windowResetEpochSeconds(window, nowSeconds) {
|
|
|
103
103
|
}
|
|
104
104
|
return 0;
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
|
|
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(
|
|
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(
|
|
151
|
+
sessionStatus: deriveWindowStatus(primary?.used_percent),
|
|
142
152
|
sessionResetAt: windowResetEpochSeconds(primary, nowSeconds),
|
|
143
153
|
weeklyUsed,
|
|
144
|
-
weeklyStatus: deriveWindowStatus(
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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)) {
|
package/dist/proxy/codexUsage.js
CHANGED
|
@@ -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 +=
|
|
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
|
|
367
|
-
|
|
368
|
-
|
|
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":
|
|
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.
|
|
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.
|
|
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
|
|
677
|
-
|
|
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
|
|
715
|
-
|
|
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",
|
|
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",
|
|
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:
|
|
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
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
-
|
|
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
|
};
|