@juspay/neurolink 12.25.2 → 12.25.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.
- package/CHANGELOG.md +2 -2
- package/dist/adapters/audioFormatSupport.d.ts +13 -2
- package/dist/adapters/audioFormatSupport.js +35 -3
- package/dist/browser/neurolink.min.js +301 -301
- package/dist/context/nativeGenerateGuard.d.ts +22 -0
- package/dist/context/nativeGenerateGuard.js +22 -0
- package/dist/core/modules/ToolsManager.js +13 -1
- package/dist/proxy/codexFallback.js +16 -2
- package/dist/proxy/codexUsage.js +8 -0
- package/dist/proxy/proxyAnalysis.js +21 -3
- package/dist/proxy/proxyTracer.js +17 -4
- package/dist/proxy/requestLogger.js +15 -1
- package/dist/server/routes/claudeProxyRoutes.js +24 -4
- package/dist/server/routes/codexProxyRoutes.js +17 -2
- package/dist/types/codex.d.ts +2 -0
- package/dist/types/generate.d.ts +7 -1
- package/dist/types/proxy.d.ts +50 -0
- package/dist/utils/messageBuilder.js +6 -5
- package/docs-site/static/search-index.json +44 -0
- package/package.json +3 -2
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import type { NativeGenerateGuardConfig } from "../types/index.js";
|
|
2
2
|
/** V3-shape adapter for the reclaim policy shared with streaming guards. */
|
|
3
3
|
export declare function createNativeGenerateGuard(config: NativeGenerateGuardConfig): {
|
|
4
|
+
/**
|
|
5
|
+
* Record the prompt-token count the vendor reported for the step just
|
|
6
|
+
* sent.
|
|
7
|
+
*
|
|
8
|
+
* Paired with the estimate this guard made for that same step it yields a
|
|
9
|
+
* calibration ratio — how far the local estimator undercounts what the
|
|
10
|
+
* vendor actually charges — and that ratio scales the reclaim threshold,
|
|
11
|
+
* so the guard fires against real token accounting rather than its own.
|
|
12
|
+
* A non-finite or non-positive figure is ignored, leaving calibration at
|
|
13
|
+
* 1 rather than skewing it toward a number the vendor never reported.
|
|
14
|
+
*/
|
|
4
15
|
observeUsage(usage: unknown): void;
|
|
16
|
+
/**
|
|
17
|
+
* Reclaim context from `conversation` when it has crossed the guard's
|
|
18
|
+
* threshold, or return `undefined` to send it unchanged.
|
|
19
|
+
*
|
|
20
|
+
* The initial task prefix — every message up to and including the first
|
|
21
|
+
* user turn — is protected as a unit, so leading system messages cannot
|
|
22
|
+
* displace the task itself from entry zero. Only the entries strictly
|
|
23
|
+
* between that prefix and the newest `DEFAULT_LOOP_GUARD_PROTECTED_TAIL`
|
|
24
|
+
* are eligible for truncation or dropping under the shared reclaim
|
|
25
|
+
* policy; that protected tail — the live working set — is left intact.
|
|
26
|
+
*/
|
|
5
27
|
guardConversation(conversation: Array<Record<string, unknown>>): Array<Record<string, unknown>> | undefined;
|
|
6
28
|
};
|
|
@@ -52,6 +52,17 @@ export function createNativeGenerateGuard(config) {
|
|
|
52
52
|
let observedPromptTokens;
|
|
53
53
|
const cost = (message) => estimateTokens(textOf(message), config.provider) + TOKENS_PER_MESSAGE;
|
|
54
54
|
return {
|
|
55
|
+
/**
|
|
56
|
+
* Record the prompt-token count the vendor reported for the step just
|
|
57
|
+
* sent.
|
|
58
|
+
*
|
|
59
|
+
* Paired with the estimate this guard made for that same step it yields a
|
|
60
|
+
* calibration ratio — how far the local estimator undercounts what the
|
|
61
|
+
* vendor actually charges — and that ratio scales the reclaim threshold,
|
|
62
|
+
* so the guard fires against real token accounting rather than its own.
|
|
63
|
+
* A non-finite or non-positive figure is ignored, leaving calibration at
|
|
64
|
+
* 1 rather than skewing it toward a number the vendor never reported.
|
|
65
|
+
*/
|
|
55
66
|
observeUsage(usage) {
|
|
56
67
|
const input = isRecord(usage) ? usage.inputTokens : undefined;
|
|
57
68
|
const total = isRecord(input) ? input.total : input;
|
|
@@ -60,6 +71,17 @@ export function createNativeGenerateGuard(config) {
|
|
|
60
71
|
? total
|
|
61
72
|
: undefined;
|
|
62
73
|
},
|
|
74
|
+
/**
|
|
75
|
+
* Reclaim context from `conversation` when it has crossed the guard's
|
|
76
|
+
* threshold, or return `undefined` to send it unchanged.
|
|
77
|
+
*
|
|
78
|
+
* The initial task prefix — every message up to and including the first
|
|
79
|
+
* user turn — is protected as a unit, so leading system messages cannot
|
|
80
|
+
* displace the task itself from entry zero. Only the entries strictly
|
|
81
|
+
* between that prefix and the newest `DEFAULT_LOOP_GUARD_PROTECTED_TAIL`
|
|
82
|
+
* are eligible for truncation or dropping under the shared reclaim
|
|
83
|
+
* policy; that protected tail — the live working set — is left intact.
|
|
84
|
+
*/
|
|
63
85
|
guardConversation(conversation) {
|
|
64
86
|
// Policy entry zero is protected. Fold the entire initial task prefix
|
|
65
87
|
// into it so leading system messages cannot displace task protection.
|
|
@@ -322,17 +322,29 @@ export class ToolsManager {
|
|
|
322
322
|
for (let pass = 0; pass < MAX_ENVELOPE_TIGHTENING_PASSES; pass++) {
|
|
323
323
|
const allocations = waterFillTextBudget(sizes, textBudget);
|
|
324
324
|
const replacements = new Map();
|
|
325
|
+
// Record the sizes here, log them only if this pass is the one that
|
|
326
|
+
// survives. A rejected pass never happened as far as the caller is
|
|
327
|
+
// concerned, and up to MAX_ENVELOPE_TIGHTENING_PASSES of them can be
|
|
328
|
+
// rejected on a single call — building their template strings, and the
|
|
329
|
+
// byteLength each one interpolates, is work paid whether or not debug
|
|
330
|
+
// logging is even enabled.
|
|
331
|
+
const truncatedItems = [];
|
|
325
332
|
textIndexes.forEach((index, k) => {
|
|
326
333
|
const item = content[index];
|
|
327
334
|
const { preview, truncated, originalSize } = generateToolOutputPreview(item.text, { maxBytes: allocations[k], notice });
|
|
328
335
|
if (truncated) {
|
|
329
|
-
|
|
336
|
+
truncatedItems.push({ index, originalSize });
|
|
330
337
|
replacements.set(index, preview);
|
|
331
338
|
}
|
|
332
339
|
});
|
|
333
340
|
const candidate = rebuild(replacements);
|
|
334
341
|
const candidateSize = serializedByteLength(candidate);
|
|
335
342
|
if (candidateSize !== undefined && candidateSize <= maxBytes) {
|
|
343
|
+
if (logger.shouldLog("debug")) {
|
|
344
|
+
for (const { index, originalSize } of truncatedItems) {
|
|
345
|
+
logger.debug(`[ToolsManager] Truncated '${toolName}' MCP content text item ${index}: ${originalSize} bytes → ${Buffer.byteLength(replacements.get(index) ?? "", "utf-8")} bytes (envelope budget ${maxBytes})`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
336
348
|
return candidate;
|
|
337
349
|
}
|
|
338
350
|
if (candidateSize === undefined || replacements.size === 0) {
|
|
@@ -66,6 +66,8 @@ export class CodexFallbackStreamError extends Error {
|
|
|
66
66
|
}),
|
|
67
67
|
inputTokensObserved: observed.inputTokensObserved,
|
|
68
68
|
outputTokensObserved: observed.outputTokensObserved,
|
|
69
|
+
cacheReadTokensObserved: observed.cacheReadTokensObserved,
|
|
70
|
+
cacheCreationTokensObserved: observed.cacheCreationTokensObserved,
|
|
69
71
|
}
|
|
70
72
|
: usage;
|
|
71
73
|
}
|
|
@@ -566,6 +568,8 @@ export function parseCodexFallbackSSE(sse) {
|
|
|
566
568
|
}),
|
|
567
569
|
inputTokensObserved: parsedUsage.inputTokensObserved,
|
|
568
570
|
outputTokensObserved: parsedUsage.outputTokensObserved,
|
|
571
|
+
cacheReadTokensObserved: parsedUsage.cacheReadTokensObserved,
|
|
572
|
+
cacheCreationTokensObserved: parsedUsage.cacheCreationTokensObserved,
|
|
569
573
|
};
|
|
570
574
|
}
|
|
571
575
|
textFromResponse = outputTextFromResponse(payload);
|
|
@@ -725,6 +729,8 @@ export async function createCodexFallbackStream(response, model) {
|
|
|
725
729
|
}),
|
|
726
730
|
inputTokensObserved: parsedUsage.inputTokensObserved,
|
|
727
731
|
outputTokensObserved: parsedUsage.outputTokensObserved,
|
|
732
|
+
cacheReadTokensObserved: parsedUsage.cacheReadTokensObserved,
|
|
733
|
+
cacheCreationTokensObserved: parsedUsage.cacheCreationTokensObserved,
|
|
728
734
|
};
|
|
729
735
|
}
|
|
730
736
|
const responseBody = payload.response;
|
|
@@ -789,10 +795,18 @@ export async function createCodexFallbackStream(response, model) {
|
|
|
789
795
|
}, "empty_response", usage);
|
|
790
796
|
}
|
|
791
797
|
const finishReason = toolCalls.size > 0 ? "tool_use" : "end_turn";
|
|
798
|
+
// A count Codex never reported is omitted rather than sent as 0: this is
|
|
799
|
+
// what the Claude client reads, and a zero there is indistinguishable
|
|
800
|
+
// from an observed cache miss. `proxyTokenUsage` floors an absent count
|
|
801
|
+
// to 0, so the observation flags are the only thing that still knows.
|
|
792
802
|
yield* serializer.finish(usage?.output, finishReason, {
|
|
793
803
|
input_tokens: usage?.input,
|
|
794
|
-
cache_read_input_tokens: usage?.
|
|
795
|
-
|
|
804
|
+
cache_read_input_tokens: usage?.cacheReadTokensObserved
|
|
805
|
+
? usage.cacheReadTokens
|
|
806
|
+
: undefined,
|
|
807
|
+
cache_creation_input_tokens: usage?.cacheCreationTokensObserved
|
|
808
|
+
? usage.cacheCreationTokens
|
|
809
|
+
: undefined,
|
|
796
810
|
});
|
|
797
811
|
return {
|
|
798
812
|
text: textParts.join(""),
|
package/dist/proxy/codexUsage.js
CHANGED
|
@@ -45,6 +45,12 @@ import { sanitizeForLog } from "../utils/logSanitize.js";
|
|
|
45
45
|
const nonNegativeInt = (value) => typeof value === "number" && Number.isFinite(value) && value > 0
|
|
46
46
|
? Math.floor(value)
|
|
47
47
|
: 0;
|
|
48
|
+
/**
|
|
49
|
+
* Whether a count was actually reported, matching what `nonNegativeInt` will
|
|
50
|
+
* keep. A negative or non-finite value is floored to zero there, so treating
|
|
51
|
+
* it as observed would record it as a real zero.
|
|
52
|
+
*/
|
|
53
|
+
const observedCount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
48
54
|
function usefulOutputItem(item) {
|
|
49
55
|
if (!item || typeof item !== "object") {
|
|
50
56
|
return false;
|
|
@@ -108,6 +114,8 @@ export function extractCodexUsage(payload) {
|
|
|
108
114
|
outputTokensObserved: typeof output === "number" && Number.isFinite(output) && output >= 0,
|
|
109
115
|
cacheReadTokens: nonNegativeInt(inputDetails?.cached_tokens),
|
|
110
116
|
cacheCreationTokens: nonNegativeInt(inputDetails?.cache_write_tokens),
|
|
117
|
+
cacheReadTokensObserved: observedCount(inputDetails?.cached_tokens),
|
|
118
|
+
cacheCreationTokensObserved: observedCount(inputDetails?.cache_write_tokens),
|
|
111
119
|
reasoningTokens: nonNegativeInt(outputDetails?.reasoning_tokens),
|
|
112
120
|
reasoningTokensObserved: typeof outputDetails?.reasoning_tokens === "number" &&
|
|
113
121
|
Number.isFinite(outputDetails.reasoning_tokens) &&
|
|
@@ -439,6 +439,10 @@ function summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByR
|
|
|
439
439
|
let recoveredAfterRetry = 0;
|
|
440
440
|
let requestsWithUsage = 0;
|
|
441
441
|
let requestsWithCacheRead = 0;
|
|
442
|
+
// Turns whose cache breakdown the provider actually reported. A turn that
|
|
443
|
+
// reported none cannot count as a miss, so it is excluded from the hit-rate
|
|
444
|
+
// denominator rather than folded in as a zero.
|
|
445
|
+
let requestsWithCacheObservation = 0;
|
|
442
446
|
let cacheReadTokens = 0;
|
|
443
447
|
let cacheCreationTokens = 0;
|
|
444
448
|
let inputTokens = 0;
|
|
@@ -514,7 +518,10 @@ function summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByR
|
|
|
514
518
|
outputTokens += request.outputTokens ?? 0;
|
|
515
519
|
cacheReadTokens += request.cacheReadTokens ?? 0;
|
|
516
520
|
cacheCreationTokens += request.cacheCreationTokens ?? 0;
|
|
517
|
-
|
|
521
|
+
if (request.cacheReadTokensObserved !== false) {
|
|
522
|
+
requestsWithCacheObservation += 1;
|
|
523
|
+
requestsWithCacheRead += (request.cacheReadTokens ?? 0) > 0 ? 1 : 0;
|
|
524
|
+
}
|
|
518
525
|
if (request.model) {
|
|
519
526
|
// Records written before `provider` existed carry only a model name.
|
|
520
527
|
// "openai-compatible" resolves to the cross-provider table search in
|
|
@@ -557,6 +564,7 @@ function summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByR
|
|
|
557
564
|
cache: {
|
|
558
565
|
requestsWithUsage,
|
|
559
566
|
requestsWithCacheRead,
|
|
567
|
+
requestsWithCacheObservation,
|
|
560
568
|
cacheReadTokens,
|
|
561
569
|
cacheCreationTokens,
|
|
562
570
|
inputTokens,
|
|
@@ -567,8 +575,8 @@ function summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByR
|
|
|
567
575
|
modelsPricedByPrefix: [...modelsPricedByPrefix].sort(),
|
|
568
576
|
requestsUnpriced,
|
|
569
577
|
unpricedModels: [...unpricedModels].sort(),
|
|
570
|
-
requestHitRate:
|
|
571
|
-
? Number((requestsWithCacheRead /
|
|
578
|
+
requestHitRate: requestsWithCacheObservation > 0
|
|
579
|
+
? Number((requestsWithCacheRead / requestsWithCacheObservation).toFixed(4))
|
|
572
580
|
: null,
|
|
573
581
|
},
|
|
574
582
|
finalRequestLatency,
|
|
@@ -1080,6 +1088,16 @@ export async function analyzeProxyLogs(options) {
|
|
|
1080
1088
|
outputTokens: finiteNumber(record.outputTokens),
|
|
1081
1089
|
cacheReadTokens: finiteNumber(record.cacheReadTokens),
|
|
1082
1090
|
cacheCreationTokens: finiteNumber(record.cacheCreationTokens),
|
|
1091
|
+
// Read as a boolean only. An absent flag means observed, which keeps
|
|
1092
|
+
// every record written before the flag existed counting as it did.
|
|
1093
|
+
// The merge below drops undefined from the later record, so a
|
|
1094
|
+
// headers-only first record cannot erase a flag the body record sets.
|
|
1095
|
+
cacheReadTokensObserved: typeof record.cacheReadTokensObserved === "boolean"
|
|
1096
|
+
? record.cacheReadTokensObserved
|
|
1097
|
+
: undefined,
|
|
1098
|
+
cacheCreationTokensObserved: typeof record.cacheCreationTokensObserved === "boolean"
|
|
1099
|
+
? record.cacheCreationTokensObserved
|
|
1100
|
+
: undefined,
|
|
1083
1101
|
errorType: stringValue(record.errorType),
|
|
1084
1102
|
errorCode: stringValue(record.errorCode),
|
|
1085
1103
|
routingDecision,
|
|
@@ -385,8 +385,14 @@ class ProxyTracer {
|
|
|
385
385
|
"ai.tokens.input": billingUsage.input,
|
|
386
386
|
"ai.tokens.output": ctx.outputTokens,
|
|
387
387
|
"ai.tokens.total": totalTokens,
|
|
388
|
-
|
|
389
|
-
|
|
388
|
+
// An unreported cache count is left off the span rather than written as
|
|
389
|
+
// zero, which would read back as an observed cache miss.
|
|
390
|
+
...(ctx.cacheCreationTokensObserved === false
|
|
391
|
+
? {}
|
|
392
|
+
: { "ai.tokens.cache_creation": ctx.cacheCreationTokens }),
|
|
393
|
+
...(ctx.cacheReadTokensObserved === false
|
|
394
|
+
? {}
|
|
395
|
+
: { "ai.tokens.cache_read": ctx.cacheReadTokens }),
|
|
390
396
|
});
|
|
391
397
|
if (ctx.reasoningTokens !== undefined) {
|
|
392
398
|
this.rootSpan.setAttribute("ai.tokens.reasoning", ctx.reasoningTokens);
|
|
@@ -687,8 +693,15 @@ class ProxyTracer {
|
|
|
687
693
|
};
|
|
688
694
|
m.tokensInput.add(proxyTokenUsage(this.usage).input, tokenLabels);
|
|
689
695
|
m.tokensOutput.add(this.usage.outputTokens, tokenLabels);
|
|
690
|
-
|
|
691
|
-
|
|
696
|
+
// Skipped, not zeroed, when the provider reported no breakdown: a zero
|
|
697
|
+
// here is read back as an observed cache miss and drags the measured
|
|
698
|
+
// cache rate down by however many such replies arrive.
|
|
699
|
+
if (this.usage.cacheReadTokensObserved !== false) {
|
|
700
|
+
m.tokensCacheRead.add(this.usage.cacheReadTokens, tokenLabels);
|
|
701
|
+
}
|
|
702
|
+
if (this.usage.cacheCreationTokensObserved !== false) {
|
|
703
|
+
m.tokensCacheCreation.add(this.usage.cacheCreationTokens, tokenLabels);
|
|
704
|
+
}
|
|
692
705
|
if (this.usage.reasoningTokens) {
|
|
693
706
|
m.tokensReasoning.add(this.usage.reasoningTokens, tokenLabels);
|
|
694
707
|
}
|
|
@@ -144,6 +144,13 @@ export const __requestLoggerTestHooks = {
|
|
|
144
144
|
defaultFlushTimeoutMs: REQUEST_LOG_FLUSH_TIMEOUT_MS,
|
|
145
145
|
pendingOperationCount: () => pendingLogOperations.size,
|
|
146
146
|
trackLogOperation,
|
|
147
|
+
/**
|
|
148
|
+
* Pricing annotation, exposed because it is otherwise reachable only through
|
|
149
|
+
* a full log-write cycle. What it refuses to price matters as much as what it
|
|
150
|
+
* prices: a turn whose cached portion is unknown must not be charged as if
|
|
151
|
+
* the cache were empty.
|
|
152
|
+
*/
|
|
153
|
+
annotateRequestPricing,
|
|
147
154
|
setAppendFileForTests: (writer) => {
|
|
148
155
|
appendMetadataFile = writer;
|
|
149
156
|
},
|
|
@@ -226,7 +233,14 @@ function annotateRequestPricing(entry) {
|
|
|
226
233
|
!Number.isFinite(cacheCreation) ||
|
|
227
234
|
cacheCreation < 0 ||
|
|
228
235
|
(entry.inputIncludesCachedTokens === true &&
|
|
229
|
-
cacheRead + cacheCreation > input)
|
|
236
|
+
cacheRead + cacheCreation > input) ||
|
|
237
|
+
// When input includes the cached portion but the provider never reported
|
|
238
|
+
// it, subtracting a defaulted 0 prices the cached tokens at full input
|
|
239
|
+
// rate and reports zero cache savings. That is a guess, not a price, so
|
|
240
|
+
// the turn is incomplete rather than silently mispriced.
|
|
241
|
+
(entry.inputIncludesCachedTokens === true &&
|
|
242
|
+
(entry.cacheReadTokensObserved === false ||
|
|
243
|
+
entry.cacheCreationTokensObserved === false))) {
|
|
230
244
|
entry.pricingStatus = "usage_incomplete";
|
|
231
245
|
return;
|
|
232
246
|
}
|
|
@@ -3909,8 +3909,14 @@ async function executeClaudeCodexFallback(args) {
|
|
|
3909
3909
|
tracer?.setUsage({
|
|
3910
3910
|
inputTokens: result.usage.input,
|
|
3911
3911
|
outputTokens: result.usage.output,
|
|
3912
|
+
// `?? 0` alone would record a Codex reply that carried no
|
|
3913
|
+
// `input_tokens_details` as an observed cache miss. This is the
|
|
3914
|
+
// leg most fallback traffic actually takes, so it is the one that
|
|
3915
|
+
// skews the measured Codex cache rate.
|
|
3912
3916
|
cacheReadTokens: result.usage.cacheReadTokens ?? 0,
|
|
3913
3917
|
cacheCreationTokens: result.usage.cacheCreationTokens ?? 0,
|
|
3918
|
+
cacheReadTokensObserved: result.usage.cacheReadTokensObserved === true,
|
|
3919
|
+
cacheCreationTokensObserved: result.usage.cacheCreationTokensObserved === true,
|
|
3914
3920
|
reasoningTokens: result.usage.reasoning,
|
|
3915
3921
|
}, { recordMetrics: false });
|
|
3916
3922
|
}
|
|
@@ -3951,8 +3957,14 @@ async function executeClaudeCodexFallback(args) {
|
|
|
3951
3957
|
outputTokens: result?.usage?.outputTokensObserved
|
|
3952
3958
|
? result.usage.output
|
|
3953
3959
|
: undefined,
|
|
3954
|
-
cacheCreationTokens: result?.usage?.
|
|
3955
|
-
|
|
3960
|
+
cacheCreationTokens: result?.usage?.cacheCreationTokensObserved
|
|
3961
|
+
? result.usage.cacheCreationTokens
|
|
3962
|
+
: undefined,
|
|
3963
|
+
cacheReadTokens: result?.usage?.cacheReadTokensObserved
|
|
3964
|
+
? result.usage.cacheReadTokens
|
|
3965
|
+
: undefined,
|
|
3966
|
+
cacheReadTokensObserved: result?.usage?.cacheReadTokensObserved === true,
|
|
3967
|
+
cacheCreationTokensObserved: result?.usage?.cacheCreationTokensObserved === true,
|
|
3956
3968
|
reasoningTokens: result?.usage?.reasoning,
|
|
3957
3969
|
errorCode: failure?.code,
|
|
3958
3970
|
retryable: failure?.retryable,
|
|
@@ -6394,8 +6406,14 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
6394
6406
|
outputTokens: fallbackFailureUsage.outputTokensObserved
|
|
6395
6407
|
? fallbackFailureUsage.output
|
|
6396
6408
|
: undefined,
|
|
6397
|
-
cacheReadTokens: fallbackFailureUsage.
|
|
6398
|
-
|
|
6409
|
+
cacheReadTokens: fallbackFailureUsage.cacheReadTokensObserved
|
|
6410
|
+
? fallbackFailureUsage.cacheReadTokens
|
|
6411
|
+
: undefined,
|
|
6412
|
+
cacheCreationTokens: fallbackFailureUsage.cacheCreationTokensObserved
|
|
6413
|
+
? fallbackFailureUsage.cacheCreationTokens
|
|
6414
|
+
: undefined,
|
|
6415
|
+
cacheReadTokensObserved: fallbackFailureUsage.cacheReadTokensObserved === true,
|
|
6416
|
+
cacheCreationTokensObserved: fallbackFailureUsage.cacheCreationTokensObserved === true,
|
|
6399
6417
|
reasoningTokens: fallbackFailureUsage.reasoning,
|
|
6400
6418
|
...extra,
|
|
6401
6419
|
};
|
|
@@ -6420,6 +6438,8 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
6420
6438
|
outputTokens: fallbackFailureUsage.output,
|
|
6421
6439
|
cacheReadTokens: fallbackFailureUsage.cacheReadTokens ?? 0,
|
|
6422
6440
|
cacheCreationTokens: fallbackFailureUsage.cacheCreationTokens ?? 0,
|
|
6441
|
+
cacheReadTokensObserved: fallbackFailureUsage.cacheReadTokensObserved === true,
|
|
6442
|
+
cacheCreationTokensObserved: fallbackFailureUsage.cacheCreationTokensObserved === true,
|
|
6423
6443
|
reasoningTokens: fallbackFailureUsage.reasoning,
|
|
6424
6444
|
}, { recordMetrics: false });
|
|
6425
6445
|
}
|
|
@@ -440,6 +440,15 @@ function captureCodexResponse(ctx, response, phase, account, attempt) {
|
|
|
440
440
|
headers: response.headers,
|
|
441
441
|
});
|
|
442
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Serve one native `/backend-api/codex/responses` turn.
|
|
445
|
+
*
|
|
446
|
+
* Owns the whole hop: account selection and rotation, the upstream request,
|
|
447
|
+
* the SSE relay back to the caller, and the terminal accounting that turns the
|
|
448
|
+
* observed wire usage into request-log and tracer records. Usage counts the
|
|
449
|
+
* provider did not report are carried as unobserved rather than zero, so a
|
|
450
|
+
* silent cache breakdown is never recorded as a cache miss.
|
|
451
|
+
*/
|
|
443
452
|
async function executeCodexResponsesRequest(ctx) {
|
|
444
453
|
const requestStartTime = Date.now();
|
|
445
454
|
let body = (ctx.body ?? {});
|
|
@@ -889,8 +898,12 @@ async function executeCodexResponsesRequest(ctx) {
|
|
|
889
898
|
outputTokens: usage.outputTokensObserved
|
|
890
899
|
? usage.outputTokens
|
|
891
900
|
: undefined,
|
|
892
|
-
cacheReadTokens: usage.
|
|
893
|
-
|
|
901
|
+
cacheReadTokens: usage.cacheReadTokensObserved
|
|
902
|
+
? usage.cacheReadTokens
|
|
903
|
+
: undefined,
|
|
904
|
+
cacheCreationTokens: usage.cacheCreationTokensObserved
|
|
905
|
+
? usage.cacheCreationTokens
|
|
906
|
+
: undefined,
|
|
894
907
|
reasoningTokens: usage.reasoningTokensObserved
|
|
895
908
|
? usage.reasoningTokens
|
|
896
909
|
: undefined,
|
|
@@ -904,6 +917,8 @@ async function executeCodexResponsesRequest(ctx) {
|
|
|
904
917
|
reasoningTokens: usage.reasoningTokensObserved
|
|
905
918
|
? usage.reasoningTokens
|
|
906
919
|
: undefined,
|
|
920
|
+
cacheReadTokensObserved: usage.cacheReadTokensObserved === true,
|
|
921
|
+
cacheCreationTokensObserved: usage.cacheCreationTokensObserved === true,
|
|
907
922
|
inputIncludesCachedTokens: true,
|
|
908
923
|
});
|
|
909
924
|
}
|
package/dist/types/codex.d.ts
CHANGED
|
@@ -184,6 +184,8 @@ export type CodexFallbackResult = {
|
|
|
184
184
|
/** Numeric serializer compatibility must not imply provider observation. */
|
|
185
185
|
inputTokensObserved?: boolean;
|
|
186
186
|
outputTokensObserved?: boolean;
|
|
187
|
+
cacheReadTokensObserved?: boolean;
|
|
188
|
+
cacheCreationTokensObserved?: boolean;
|
|
187
189
|
};
|
|
188
190
|
finishReason: "end_turn" | "tool_use";
|
|
189
191
|
};
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -1685,7 +1685,13 @@ export type NativeGenerateLoopArgs = {
|
|
|
1685
1685
|
};
|
|
1686
1686
|
export type NativeGenerateLoopResult = {
|
|
1687
1687
|
text: string;
|
|
1688
|
-
/**
|
|
1688
|
+
/**
|
|
1689
|
+
* Joined reasoning content parts from EVERY step, when the vendor sent any.
|
|
1690
|
+
*
|
|
1691
|
+
* Was final-step-only until the native generate loop began accumulating
|
|
1692
|
+
* across steps; a multi-step turn would otherwise report only the reasoning
|
|
1693
|
+
* that happened after its last tool call.
|
|
1694
|
+
*/
|
|
1689
1695
|
reasoning?: string;
|
|
1690
1696
|
finishReason: string;
|
|
1691
1697
|
rawFinishReason?: string;
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -676,6 +676,15 @@ export type RequestLogEntry = {
|
|
|
676
676
|
outputTokens?: number;
|
|
677
677
|
cacheCreationTokens?: number;
|
|
678
678
|
cacheReadTokens?: number;
|
|
679
|
+
/**
|
|
680
|
+
* Whether the provider reported each cache count. Omitted means observed,
|
|
681
|
+
* so every path that genuinely reports a breakdown is unchanged. When
|
|
682
|
+
* false the count is absent rather than zero, and the reporting and
|
|
683
|
+
* pricing paths must not read it as a cache miss: a zero folded into a
|
|
684
|
+
* hit-rate denominator biases every rate built on these records down.
|
|
685
|
+
*/
|
|
686
|
+
cacheReadTokensObserved?: boolean;
|
|
687
|
+
cacheCreationTokensObserved?: boolean;
|
|
679
688
|
/** Reasoning tokens are a subset of output, never additional usage. */
|
|
680
689
|
reasoningTokens?: number;
|
|
681
690
|
/**
|
|
@@ -1032,6 +1041,9 @@ export type ClaudeFinalRequestLogger = (status: number, accountLabel: string, ac
|
|
|
1032
1041
|
outputTokens?: number;
|
|
1033
1042
|
cacheCreationTokens?: number;
|
|
1034
1043
|
cacheReadTokens?: number;
|
|
1044
|
+
/** False when the provider reported no cache breakdown; see RequestLogEntry. */
|
|
1045
|
+
cacheReadTokensObserved?: boolean;
|
|
1046
|
+
cacheCreationTokensObserved?: boolean;
|
|
1035
1047
|
reasoningTokens?: number;
|
|
1036
1048
|
errorCode?: string;
|
|
1037
1049
|
retryable?: boolean;
|
|
@@ -1899,6 +1911,15 @@ export type UsageContext = {
|
|
|
1899
1911
|
outputTokens: number;
|
|
1900
1912
|
cacheCreationTokens: number;
|
|
1901
1913
|
cacheReadTokens: number;
|
|
1914
|
+
/**
|
|
1915
|
+
* Whether the provider actually reported each cache count. Omitted means
|
|
1916
|
+
* observed, so every path that genuinely reports a breakdown is unchanged.
|
|
1917
|
+
* When false the count is not recorded at all, because a zero written into
|
|
1918
|
+
* `proxy_tokens_cache_read` is indistinguishable from a real cache miss and
|
|
1919
|
+
* biases every rate built on that counter downward.
|
|
1920
|
+
*/
|
|
1921
|
+
cacheReadTokensObserved?: boolean;
|
|
1922
|
+
cacheCreationTokensObserved?: boolean;
|
|
1902
1923
|
reasoningTokens?: number;
|
|
1903
1924
|
rateLimitAfter5h?: number;
|
|
1904
1925
|
rateLimitAfter7d?: number;
|
|
@@ -2223,6 +2244,12 @@ export type ProxyAnalysisReport = {
|
|
|
2223
2244
|
cache: {
|
|
2224
2245
|
requestsWithUsage: number;
|
|
2225
2246
|
requestsWithCacheRead: number;
|
|
2247
|
+
/**
|
|
2248
|
+
* Turns whose cache breakdown the provider actually reported, and the
|
|
2249
|
+
* denominator of `requestHitRate`. A turn that reported none is not a
|
|
2250
|
+
* miss, so counting it would bias the rate down.
|
|
2251
|
+
*/
|
|
2252
|
+
requestsWithCacheObservation: number;
|
|
2226
2253
|
cacheReadTokens: number;
|
|
2227
2254
|
cacheCreationTokens: number;
|
|
2228
2255
|
/** Ordinary input, excluding the separately reported cache read/write buckets. */
|
|
@@ -2294,6 +2321,13 @@ export type ProxyAnalysisFinalRequestRecord = {
|
|
|
2294
2321
|
outputTokens: number | null;
|
|
2295
2322
|
cacheReadTokens: number | null;
|
|
2296
2323
|
cacheCreationTokens: number | null;
|
|
2324
|
+
/**
|
|
2325
|
+
* False when the provider reported no cache breakdown. Such a turn is not a
|
|
2326
|
+
* cache miss, so it is excluded from the hit-rate denominator rather than
|
|
2327
|
+
* counted as a zero.
|
|
2328
|
+
*/
|
|
2329
|
+
cacheReadTokensObserved?: boolean;
|
|
2330
|
+
cacheCreationTokensObserved?: boolean;
|
|
2297
2331
|
errorType: string | null;
|
|
2298
2332
|
errorCode: string | null;
|
|
2299
2333
|
routingDecision: ProxyAccountRoutingDecision | null;
|
|
@@ -2328,6 +2362,22 @@ export type CodexStreamUsage = {
|
|
|
2328
2362
|
cacheReadTokens: number;
|
|
2329
2363
|
/** Cache writes, which bill at a premium over both reads and plain input. */
|
|
2330
2364
|
cacheCreationTokens: number;
|
|
2365
|
+
/**
|
|
2366
|
+
* Whether the provider supplied each cache count, tracked per field.
|
|
2367
|
+
*
|
|
2368
|
+
* Without this a reply that omits `input_tokens_details` is recorded as a
|
|
2369
|
+
* total cache miss rather than as unknown, which biases every measured cache
|
|
2370
|
+
* rate downward. Input, output and reasoning already carry this distinction.
|
|
2371
|
+
*
|
|
2372
|
+
* The two fields are tracked separately because a reply can carry one and
|
|
2373
|
+
* not the other: a single flag covering both would let a missing
|
|
2374
|
+
* `cache_write_tokens` ride in on an observed `cached_tokens` and be
|
|
2375
|
+
* recorded as a real zero. A count that is present but not a finite,
|
|
2376
|
+
* non-negative number is not observed either — `nonNegativeInt` floors it to
|
|
2377
|
+
* zero, which is indistinguishable from a genuine zero once recorded.
|
|
2378
|
+
*/
|
|
2379
|
+
cacheReadTokensObserved?: boolean;
|
|
2380
|
+
cacheCreationTokensObserved?: boolean;
|
|
2331
2381
|
reasoningTokens: number;
|
|
2332
2382
|
/** Whether the provider supplied a valid reasoning breakdown. */
|
|
2333
2383
|
reasoningTokensObserved?: boolean;
|
|
@@ -1374,7 +1374,8 @@ export async function buildMultimodalMessagesArray(options, provider, model) {
|
|
|
1374
1374
|
// ordinary "transcribe this recording" request — took the text-only branch
|
|
1375
1375
|
// below, where the collected bytes have nowhere to go and only the metadata
|
|
1376
1376
|
// summary survives.
|
|
1377
|
-
const hasNativeAudio = (inp.nativeAudioFiles?.length ?? 0) > 0 &&
|
|
1377
|
+
const hasNativeAudio = (inp.nativeAudioFiles?.length ?? 0) > 0 &&
|
|
1378
|
+
supportsNativeAudio(provider, model);
|
|
1378
1379
|
// If no images, PDFs or audio, use standard message building and convert to MultimodalChatMessage[]
|
|
1379
1380
|
if (!hasImages && !hasPDFs && !hasNativeAudio) {
|
|
1380
1381
|
// #289: CSV content[] items don't need vision, so they never reach the
|
|
@@ -1574,7 +1575,7 @@ async function appendCsvContentToText(csvItems, baseText) {
|
|
|
1574
1575
|
/**
|
|
1575
1576
|
* Convert advanced content format to provider-specific format
|
|
1576
1577
|
*/
|
|
1577
|
-
async function convertContentToProviderFormat(content, provider,
|
|
1578
|
+
async function convertContentToProviderFormat(content, provider, model, pdfOptions, audioFiles = [], outputFormat) {
|
|
1578
1579
|
const textContent = content.find((c) => c.type === "text");
|
|
1579
1580
|
const imageContent = content.filter((c) => c.type === "image");
|
|
1580
1581
|
const pdfContent = content.filter((c) => c.type === "pdf");
|
|
@@ -1596,7 +1597,7 @@ async function convertContentToProviderFormat(content, provider, _model, pdfOpti
|
|
|
1596
1597
|
// change exists to stop, reached through a different branch. Gated on the
|
|
1597
1598
|
// provider accepting audio so one that cannot keeps the cheaper plain-text
|
|
1598
1599
|
// shape rather than an array carrying a part it will ignore.
|
|
1599
|
-
const deliversAudio = audioFiles.length > 0 && supportsNativeAudio(provider);
|
|
1600
|
+
const deliversAudio = audioFiles.length > 0 && supportsNativeAudio(provider, model);
|
|
1600
1601
|
const hasMultimodal = imageContent.length > 0 || pdfContent.length > 0 || deliversAudio;
|
|
1601
1602
|
// Validate that we have at least some content
|
|
1602
1603
|
if (!hasMultimodal && !text) {
|
|
@@ -1627,7 +1628,7 @@ async function convertContentToProviderFormat(content, provider, _model, pdfOpti
|
|
|
1627
1628
|
await enforceAggregatePdfLimits(pdfFiles, provider, {
|
|
1628
1629
|
trustSuppliedPageCounts: false,
|
|
1629
1630
|
});
|
|
1630
|
-
return await convertMultimodalToProviderFormat(text, images, pdfFiles, provider,
|
|
1631
|
+
return await convertMultimodalToProviderFormat(text, images, pdfFiles, provider, model, {
|
|
1631
1632
|
audioFiles,
|
|
1632
1633
|
outputFormat,
|
|
1633
1634
|
});
|
|
@@ -2225,7 +2226,7 @@ mediaOptions = {}) {
|
|
|
2225
2226
|
// itself rather than replacing the description — a model asked "how long is
|
|
2226
2227
|
// this?" keeps the exact answer, and one asked "what is said?" can now
|
|
2227
2228
|
// answer at all.
|
|
2228
|
-
if (audioFiles.length > 0 && supportsNativeAudio(provider)) {
|
|
2229
|
+
if (audioFiles.length > 0 && supportsNativeAudio(provider, model)) {
|
|
2229
2230
|
for (const audio of audioFiles) {
|
|
2230
2231
|
// Derived from the trimmed basename so a directory containing a dot
|
|
2231
2232
|
// (`/srv/v1.2/recording`) cannot be mistaken for the file's extension.
|