@juspay/neurolink 13.8.18 → 13.8.19
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 +5 -2
- package/dist/browser/neurolink.min.js +421 -421
- package/dist/providers/openaiChatCompletionsClient.js +12 -3
- package/dist/proxy/accountLedger.js +6 -8
- package/dist/proxy/geminiFormat.d.ts +3 -11
- package/dist/proxy/geminiFormat.js +11 -2
- package/dist/proxy/openaiFormat.d.ts +3 -9
- package/dist/proxy/openaiFormat.js +21 -15
- package/dist/proxy/proxyTranslationEngine.d.ts +2 -6
- package/dist/proxy/proxyTranslationEngine.js +25 -14
- package/dist/proxy/routingPolicy.d.ts +3 -1
- package/dist/proxy/routingPolicy.js +7 -8
- package/dist/types/openaiCompatible.d.ts +1 -1
- package/dist/types/proxy.d.ts +9 -13
- package/dist/types/proxyClient.d.ts +2 -0
- package/dist/utils/retryAfter.d.ts +32 -0
- package/dist/utils/retryAfter.js +135 -11
- package/dist/workflow/workflows/adaptiveWorkflow.d.ts +2 -2
- package/dist/workflow/workflows/adaptiveWorkflow.js +10 -10
- package/dist/workflow/workflows/consensusWorkflow.d.ts +4 -4
- package/dist/workflow/workflows/consensusWorkflow.js +12 -12
- package/dist/workflow/workflows/fallbackWorkflow.d.ts +3 -3
- package/dist/workflow/workflows/fallbackWorkflow.js +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.d.ts +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.js +31 -31
- package/docs-site/static/search-index.json +3 -3
- package/package.json +3 -1
|
@@ -725,6 +725,8 @@ export const parseSSEStream = async (body, onTextDelta, onReasoningDelta) => {
|
|
|
725
725
|
};
|
|
726
726
|
const decoder = new TextDecoder();
|
|
727
727
|
let parseErr;
|
|
728
|
+
// Key of the tool call the previous delta wrote to (for unindexed deltas).
|
|
729
|
+
let lastToolCallKey;
|
|
728
730
|
const handleEvent = (msg) => {
|
|
729
731
|
const data = msg.data;
|
|
730
732
|
if (!data || data === "[DONE]") {
|
|
@@ -768,14 +770,21 @@ export const parseSSEStream = async (body, onTextDelta, onReasoningDelta) => {
|
|
|
768
770
|
}
|
|
769
771
|
if (delta?.tool_calls) {
|
|
770
772
|
for (const tc of delta.tool_calls) {
|
|
771
|
-
|
|
773
|
+
// Gemini's OpenAI-compatible endpoint omits `index`: an unindexed
|
|
774
|
+
// delta with an id or name starts a call, otherwise it continues one.
|
|
775
|
+
const index = tc.index ??
|
|
776
|
+
(tc.id || tc.function?.name || lastToolCallKey === undefined
|
|
777
|
+
? Math.max(-1, ...result.toolCalls.keys()) + 1
|
|
778
|
+
: lastToolCallKey);
|
|
779
|
+
lastToolCallKey = index;
|
|
780
|
+
let state = result.toolCalls.get(index);
|
|
772
781
|
if (!state) {
|
|
773
782
|
state = {
|
|
774
|
-
id: tc.id ?? `call_${
|
|
783
|
+
id: tc.id ?? `call_${index}_${Date.now()}`,
|
|
775
784
|
name: tc.function?.name ?? "",
|
|
776
785
|
argsBuffered: "",
|
|
777
786
|
};
|
|
778
|
-
result.toolCalls.set(
|
|
787
|
+
result.toolCalls.set(index, state);
|
|
779
788
|
}
|
|
780
789
|
else if (tc.id) {
|
|
781
790
|
state.id = tc.id;
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { homedir } from "node:os";
|
|
29
29
|
import { join } from "node:path";
|
|
30
30
|
import { calculateCost, hasPricing } from "../utils/pricing.js";
|
|
31
|
+
import { proxyTokenUsage } from "./proxyTokenUsage.js";
|
|
31
32
|
/** Same pattern proxyAnalysis.ts uses, deliberately duplicated as a constant. */
|
|
32
33
|
const REQUEST_FILE_PATTERN = /^proxy-\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
33
34
|
/**
|
|
@@ -163,6 +164,7 @@ async function advanceCursor(fileName, cursor) {
|
|
|
163
164
|
model: typeof record.model === "string" ? record.model : "",
|
|
164
165
|
provider: typeof record.provider === "string" ? record.provider : undefined,
|
|
165
166
|
inputTokens: finiteNumber(record.inputTokens),
|
|
167
|
+
inputIncludesCachedTokens: record.inputIncludesCachedTokens === true,
|
|
166
168
|
outputTokens: finiteNumber(record.outputTokens),
|
|
167
169
|
cacheReadTokens: finiteNumber(record.cacheReadTokens),
|
|
168
170
|
cacheCreationTokens: finiteNumber(record.cacheCreationTokens),
|
|
@@ -196,6 +198,9 @@ async function advanceCursor(fileName, cursor) {
|
|
|
196
198
|
...previous,
|
|
197
199
|
...next,
|
|
198
200
|
inputTokens: Math.max(previous.inputTokens, next.inputTokens),
|
|
201
|
+
inputIncludesCachedTokens: next.inputTokens > previous.inputTokens
|
|
202
|
+
? next.inputIncludesCachedTokens
|
|
203
|
+
: previous.inputIncludesCachedTokens,
|
|
199
204
|
outputTokens: Math.max(previous.outputTokens, next.outputTokens),
|
|
200
205
|
cacheReadTokens: Math.max(previous.cacheReadTokens, next.cacheReadTokens),
|
|
201
206
|
cacheCreationTokens: Math.max(previous.cacheCreationTokens, next.cacheCreationTokens),
|
|
@@ -335,14 +340,7 @@ export async function readAccountUsage(date = currentUsageDate()) {
|
|
|
335
340
|
const provider = resolveProvider(entry);
|
|
336
341
|
if (entry.model && entry.model !== "-") {
|
|
337
342
|
if (hasPricing(provider, entry.model)) {
|
|
338
|
-
const cost = calculateCost(provider, entry.model,
|
|
339
|
-
input: entry.inputTokens,
|
|
340
|
-
output: entry.outputTokens,
|
|
341
|
-
total: entry.inputTokens + entry.outputTokens,
|
|
342
|
-
cacheReadTokens: entry.cacheReadTokens,
|
|
343
|
-
cacheCreationTokens: entry.cacheCreationTokens,
|
|
344
|
-
cacheCreation1hTokens: entry.cacheCreation1hTokens,
|
|
345
|
-
});
|
|
343
|
+
const cost = calculateCost(provider, entry.model, proxyTokenUsage(entry));
|
|
346
344
|
row.costUsd += cost;
|
|
347
345
|
client.costUsd += cost;
|
|
348
346
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* its own usage line, so getting them wrong is visible to the user rather than
|
|
19
19
|
* merely wrong in a log.
|
|
20
20
|
*/
|
|
21
|
-
import type { ParsedGeminiRequest, StreamSerializerAdapter } from "../types/index.js";
|
|
21
|
+
import type { ParsedGeminiRequest, StreamSerializerAdapter, TokenUsage } from "../types/index.js";
|
|
22
22
|
/**
|
|
23
23
|
* Parse a `generateContent` body into the shape the translation engine takes.
|
|
24
24
|
*
|
|
@@ -46,11 +46,7 @@ export declare function parseGeminiRequest(model: string, body: Record<string, u
|
|
|
46
46
|
* cannot drift.
|
|
47
47
|
*/
|
|
48
48
|
export declare function renderGeminiToolUse(name: string, input: unknown): string;
|
|
49
|
-
export declare function buildGeminiResponse(text: string, finishReason: string, usage: {
|
|
50
|
-
input: number;
|
|
51
|
-
output: number;
|
|
52
|
-
total: number;
|
|
53
|
-
}, modelVersion: string, toolCalls?: ReadonlyArray<{
|
|
49
|
+
export declare function buildGeminiResponse(text: string, finishReason: string, usage: TokenUsage, modelVersion: string, toolCalls?: ReadonlyArray<{
|
|
54
50
|
toolName: string;
|
|
55
51
|
args: Record<string, unknown>;
|
|
56
52
|
}>): Record<string, unknown>;
|
|
@@ -81,11 +77,7 @@ export declare class GeminiStreamSerializer {
|
|
|
81
77
|
* that is never coming; rendering it as text keeps the turn terminating.
|
|
82
78
|
*/
|
|
83
79
|
pushToolUse(_id: string, name: string, input: unknown): string[];
|
|
84
|
-
finish(finishReason: string, usage:
|
|
85
|
-
input: number;
|
|
86
|
-
output: number;
|
|
87
|
-
total: number;
|
|
88
|
-
}): string[];
|
|
80
|
+
finish(finishReason: string, usage: TokenUsage): string[];
|
|
89
81
|
/**
|
|
90
82
|
* Errors ride the stream as a Google error object.
|
|
91
83
|
*
|
|
@@ -206,10 +206,19 @@ function toGeminiFinishReason(reason) {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
function usageMetadata(usage) {
|
|
209
|
+
const promptTokens = usage.input +
|
|
210
|
+
(usage.cacheReadTokens ?? 0) +
|
|
211
|
+
(usage.cacheCreationTokens ?? 0);
|
|
209
212
|
return {
|
|
210
|
-
promptTokenCount:
|
|
213
|
+
promptTokenCount: promptTokens,
|
|
211
214
|
candidatesTokenCount: usage.output,
|
|
212
|
-
totalTokenCount: usage.total ||
|
|
215
|
+
totalTokenCount: usage.total || promptTokens + usage.output,
|
|
216
|
+
...(usage.cacheReadTokens !== undefined && {
|
|
217
|
+
cachedContentTokenCount: usage.cacheReadTokens,
|
|
218
|
+
}),
|
|
219
|
+
...(usage.reasoning !== undefined && {
|
|
220
|
+
thoughtsTokenCount: usage.reasoning,
|
|
221
|
+
}),
|
|
213
222
|
};
|
|
214
223
|
}
|
|
215
224
|
/** Build a complete `generateContent` response body. */
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Reference: https://platform.openai.com/docs/api-reference/chat/create
|
|
10
10
|
*/
|
|
11
|
-
import type { ClaudeRequest, ClaudeResponse, InternalResult, OpenAICompletionRequest, OpenAICompletionResponse, OpenAIErrorResponse, ParsedOpenAIRequest, StreamLifecycleState } from "../types/index.js";
|
|
11
|
+
import type { ClaudeRequest, ClaudeResponse, InternalResult, OpenAICompletionRequest, OpenAICompletionResponse, OpenAIErrorResponse, ParsedOpenAIRequest, StreamLifecycleState, TokenUsage } from "../types/index.js";
|
|
12
12
|
/** Generate a unique chat completion ID in the OpenAI format. */
|
|
13
13
|
export declare function generateChatCompletionId(): string;
|
|
14
14
|
/** Generate an OpenAI-format tool call ID (`call_` + random chars). */
|
|
@@ -25,9 +25,7 @@ export declare function generateOpenAIToolCallId(): string;
|
|
|
25
25
|
* - top_p, temperature, max_tokens pass-through
|
|
26
26
|
*/
|
|
27
27
|
export declare function parseOpenAIRequest(body: OpenAICompletionRequest): ParsedOpenAIRequest;
|
|
28
|
-
/**
|
|
29
|
-
* Serialize a NeuroLink GenerateResult into an OpenAI Chat Completions response.
|
|
30
|
-
*/
|
|
28
|
+
/** Serialize a NeuroLink GenerateResult into an OpenAI Chat Completions response. */
|
|
31
29
|
export declare function serializeOpenAIResponse(result: InternalResult, requestModel: string): OpenAICompletionResponse;
|
|
32
30
|
/**
|
|
33
31
|
* Build an OpenAI-compatible error envelope.
|
|
@@ -97,11 +95,7 @@ export declare class OpenAIStreamSerializer {
|
|
|
97
95
|
/**
|
|
98
96
|
* Finalize the stream: emit finish_reason chunk, then `data: [DONE]`.
|
|
99
97
|
*/
|
|
100
|
-
finish(finishReason?: string, usage?:
|
|
101
|
-
input: number;
|
|
102
|
-
output: number;
|
|
103
|
-
total: number;
|
|
104
|
-
}): Generator<string>;
|
|
98
|
+
finish(finishReason?: string, usage?: TokenUsage): Generator<string>;
|
|
105
99
|
/**
|
|
106
100
|
* Emit an error event. Transitions to terminal ERROR state.
|
|
107
101
|
*/
|
|
@@ -205,9 +205,25 @@ function mapFinishReason(finishReason) {
|
|
|
205
205
|
return finishReason ? "stop" : null;
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
-
/**
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
/** Add cache to the prompt total; reasoning is already part of completion. */
|
|
209
|
+
function toOpenAIUsage(usage) {
|
|
210
|
+
const promptTokens = (usage?.input ?? 0) +
|
|
211
|
+
(usage?.cacheReadTokens ?? 0) +
|
|
212
|
+
(usage?.cacheCreationTokens ?? 0);
|
|
213
|
+
const completionTokens = usage?.output ?? 0;
|
|
214
|
+
return {
|
|
215
|
+
prompt_tokens: promptTokens,
|
|
216
|
+
completion_tokens: completionTokens,
|
|
217
|
+
total_tokens: usage?.total ?? promptTokens + completionTokens,
|
|
218
|
+
...(usage?.cacheReadTokens !== undefined && {
|
|
219
|
+
prompt_tokens_details: { cached_tokens: usage.cacheReadTokens },
|
|
220
|
+
}),
|
|
221
|
+
...(usage?.reasoning !== undefined && {
|
|
222
|
+
completion_tokens_details: { reasoning_tokens: usage.reasoning },
|
|
223
|
+
}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** Serialize a NeuroLink GenerateResult into an OpenAI Chat Completions response. */
|
|
211
227
|
export function serializeOpenAIResponse(result, requestModel) {
|
|
212
228
|
const inferredFinishReason = result.toolCalls &&
|
|
213
229
|
result.toolCalls.length > 0 &&
|
|
@@ -244,11 +260,7 @@ export function serializeOpenAIResponse(result, requestModel) {
|
|
|
244
260
|
finish_reason: mapFinishReason(inferredFinishReason),
|
|
245
261
|
},
|
|
246
262
|
],
|
|
247
|
-
usage:
|
|
248
|
-
prompt_tokens: result.usage?.input ?? 0,
|
|
249
|
-
completion_tokens: result.usage?.output ?? 0,
|
|
250
|
-
total_tokens: result.usage?.total ?? 0,
|
|
251
|
-
},
|
|
263
|
+
usage: toOpenAIUsage(result.usage),
|
|
252
264
|
};
|
|
253
265
|
}
|
|
254
266
|
// ---------------------------------------------------------------------------
|
|
@@ -450,13 +462,7 @@ export class OpenAIStreamSerializer {
|
|
|
450
462
|
return;
|
|
451
463
|
}
|
|
452
464
|
const mappedReason = mapFinishReason(finishReason) ?? "stop";
|
|
453
|
-
const usagePayload = usage
|
|
454
|
-
? {
|
|
455
|
-
prompt_tokens: usage.input,
|
|
456
|
-
completion_tokens: usage.output,
|
|
457
|
-
total_tokens: usage.total,
|
|
458
|
-
}
|
|
459
|
-
: undefined;
|
|
465
|
+
const usagePayload = usage ? toOpenAIUsage(usage) : undefined;
|
|
460
466
|
yield formatOpenAISSE(this.makeChunk({}, mappedReason, usagePayload));
|
|
461
467
|
yield "data: [DONE]\n\n";
|
|
462
468
|
this.state = "done";
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* JSON handlers that accept a format discriminator.
|
|
13
13
|
*/
|
|
14
14
|
import type { ProxyTracer } from "./proxyTracer.js";
|
|
15
|
-
import type { ParsedClaudeRequest, ParsedGeminiRequest, ParsedOpenAIRequest, ProxyFormat, ProxyTranslationAttempt, ServerContext } from "../types/index.js";
|
|
15
|
+
import type { ParsedClaudeRequest, ParsedGeminiRequest, ParsedOpenAIRequest, ProxyFormat, ProxyTranslationAttempt, ServerContext, TokenUsage } from "../types/index.js";
|
|
16
16
|
/**
|
|
17
17
|
* Extract text content from a stream chunk (handles various chunk formats).
|
|
18
18
|
*/
|
|
@@ -29,11 +29,7 @@ export declare function hasTranslatedOutput(collectedText: string, toolCalls: un
|
|
|
29
29
|
* - AI SDK v4: promptTokens / completionTokens
|
|
30
30
|
* - NeuroLink internal: input / output
|
|
31
31
|
*/
|
|
32
|
-
export declare function extractUsageFromStreamResult(usage: unknown):
|
|
33
|
-
input: number;
|
|
34
|
-
output: number;
|
|
35
|
-
total: number;
|
|
36
|
-
};
|
|
32
|
+
export declare function extractUsageFromStreamResult(usage: unknown): TokenUsage;
|
|
37
33
|
/**
|
|
38
34
|
* Detect which proxy format a request is using based on path and headers.
|
|
39
35
|
*/
|
|
@@ -77,17 +77,25 @@ export function hasTranslatedOutput(collectedText, toolCalls) {
|
|
|
77
77
|
* - NeuroLink internal: input / output
|
|
78
78
|
*/
|
|
79
79
|
export function extractUsageFromStreamResult(usage) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
const observed = readTranslationUsage(usage);
|
|
81
|
+
const cacheReadTokens = observed.cacheReadTokens ?? 0;
|
|
82
|
+
const cacheCreationTokens = observed.cacheCreationTokens ?? 0;
|
|
83
|
+
const input = observed.inputIncludesCachedTokens
|
|
84
|
+
? Math.max(0, (observed.inputTokens ?? 0) - cacheReadTokens - cacheCreationTokens)
|
|
85
|
+
: (observed.inputTokens ?? 0);
|
|
86
|
+
const output = observed.outputTokens ?? 0;
|
|
87
|
+
return {
|
|
88
|
+
input,
|
|
89
|
+
output,
|
|
90
|
+
total: input + cacheReadTokens + cacheCreationTokens + output,
|
|
91
|
+
...(observed.cacheReadTokens !== undefined ? { cacheReadTokens } : {}),
|
|
92
|
+
...(observed.cacheCreationTokens !== undefined
|
|
93
|
+
? { cacheCreationTokens }
|
|
94
|
+
: {}),
|
|
95
|
+
...(observed.reasoningTokens !== undefined
|
|
96
|
+
? { reasoning: observed.reasoningTokens }
|
|
97
|
+
: {}),
|
|
98
|
+
};
|
|
91
99
|
}
|
|
92
100
|
// ---------------------------------------------------------------------------
|
|
93
101
|
// Format detection
|
|
@@ -250,11 +258,11 @@ function defaultFinishReason(format) {
|
|
|
250
258
|
return format === "claude" ? "end_turn" : "stop";
|
|
251
259
|
}
|
|
252
260
|
/** Preserve unknown usage as absent; zero is only reported when observed. */
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
if (!raw) {
|
|
261
|
+
function readTranslationUsage(usage) {
|
|
262
|
+
if (!usage || typeof usage !== "object") {
|
|
256
263
|
return {};
|
|
257
264
|
}
|
|
265
|
+
const raw = usage;
|
|
258
266
|
const number = (...values) => values.find((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
|
|
259
267
|
const details = raw.outputTokenDetails;
|
|
260
268
|
const inputs = raw.inputTokenDetails;
|
|
@@ -275,6 +283,9 @@ function translationUsage(result) {
|
|
|
275
283
|
reasoningTokens: number(raw.reasoningTokens, raw.reasoning, details?.reasoningTokens),
|
|
276
284
|
};
|
|
277
285
|
}
|
|
286
|
+
function translationUsage(result) {
|
|
287
|
+
return readTranslationUsage(result?.usage);
|
|
288
|
+
}
|
|
278
289
|
/** Shared request owner, independent of a consumer asking for another chunk. */
|
|
279
290
|
function createTranslationRequest(args) {
|
|
280
291
|
const { ctx, requestModel, parsed, tracer, requestStartTime, stream } = args;
|
|
@@ -14,6 +14,8 @@ export declare function buildProxyTranslationPlan(primary: {
|
|
|
14
14
|
}, fallbackChain: FallbackEntry[], requestedModel: string, _parsed?: ParsedClaudeRequest, allowAutoFallback?: boolean): ProxyTranslationPlan;
|
|
15
15
|
/**
|
|
16
16
|
* Parse the retry-after header from an upstream 429 response.
|
|
17
|
-
*
|
|
17
|
+
*
|
|
18
|
+
* @param retryAfterHeader - The raw Retry-After header string or null
|
|
19
|
+
* @returns Milliseconds to wait (minimum 1000ms if valid), or 0 if no valid header present
|
|
18
20
|
*/
|
|
19
21
|
export declare function parseRetryAfterMs(retryAfterHeader: string | null): number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseRetryAfterHeaderValue } from "../utils/retryAfter.js";
|
|
1
2
|
export function inferClaudeProxyModelTier(modelName) {
|
|
2
3
|
const normalized = modelName.toLowerCase();
|
|
3
4
|
if (normalized.includes("opus")) {
|
|
@@ -59,19 +60,17 @@ export function buildProxyTranslationPlan(primary, fallbackChain, requestedModel
|
|
|
59
60
|
// ---------------------------------------------------------------------------
|
|
60
61
|
/**
|
|
61
62
|
* Parse the retry-after header from an upstream 429 response.
|
|
62
|
-
*
|
|
63
|
+
*
|
|
64
|
+
* @param retryAfterHeader - The raw Retry-After header string or null
|
|
65
|
+
* @returns Milliseconds to wait (minimum 1000ms if valid), or 0 if no valid header present
|
|
63
66
|
*/
|
|
64
67
|
export function parseRetryAfterMs(retryAfterHeader) {
|
|
65
68
|
if (!retryAfterHeader) {
|
|
66
69
|
return 0;
|
|
67
70
|
}
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
return Math.max(
|
|
71
|
-
}
|
|
72
|
-
const date = new Date(retryAfterHeader);
|
|
73
|
-
if (!Number.isNaN(date.getTime())) {
|
|
74
|
-
return Math.max(1000, date.getTime() - Date.now());
|
|
71
|
+
const delay = parseRetryAfterHeaderValue(retryAfterHeader);
|
|
72
|
+
if (delay !== undefined) {
|
|
73
|
+
return Math.max(1000, delay);
|
|
75
74
|
}
|
|
76
75
|
return 0;
|
|
77
76
|
}
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProxyTokenBudgetLease, ProxyTokenBudgetSnapshot } from "./proxyBudget.js";
|
|
2
2
|
import type { CodexRuntimeAccount } from "./codex.js";
|
|
3
|
+
import type { TokenUsage } from "./analytics.js";
|
|
3
4
|
import type { JsonObject, JsonValue } from "./common.js";
|
|
4
5
|
import type { ProxyContextEvidence, ProxyPreparedContext } from "./proxyContext.js";
|
|
5
6
|
/**
|
|
@@ -258,14 +259,7 @@ export type InternalResult = {
|
|
|
258
259
|
finishReason?: string;
|
|
259
260
|
/** Thinking/reasoning text from provider (Anthropic thinking blocks, Gemini thought parts) */
|
|
260
261
|
reasoning?: string;
|
|
261
|
-
usage?:
|
|
262
|
-
input: number;
|
|
263
|
-
output: number;
|
|
264
|
-
total: number;
|
|
265
|
-
cacheCreationTokens?: number;
|
|
266
|
-
cacheReadTokens?: number;
|
|
267
|
-
reasoning?: number;
|
|
268
|
-
};
|
|
262
|
+
usage?: TokenUsage;
|
|
269
263
|
toolCalls?: Array<{
|
|
270
264
|
toolCallId: string;
|
|
271
265
|
toolName: string;
|
|
@@ -3524,11 +3518,7 @@ export type StreamSerializerAdapter = {
|
|
|
3524
3518
|
start(): Iterable<string>;
|
|
3525
3519
|
pushDelta(text: string): Iterable<string>;
|
|
3526
3520
|
pushToolUse(id: string, name: string, input: unknown): Iterable<string>;
|
|
3527
|
-
finish(finishReason: string, usage:
|
|
3528
|
-
input: number;
|
|
3529
|
-
output: number;
|
|
3530
|
-
total: number;
|
|
3531
|
-
}): Iterable<string>;
|
|
3521
|
+
finish(finishReason: string, usage: TokenUsage): Iterable<string>;
|
|
3532
3522
|
emitError(message: string): Iterable<string>;
|
|
3533
3523
|
};
|
|
3534
3524
|
/** OpenAI content part in a user message. */
|
|
@@ -3615,6 +3605,12 @@ export type OpenAIUsage = {
|
|
|
3615
3605
|
prompt_tokens: number;
|
|
3616
3606
|
completion_tokens: number;
|
|
3617
3607
|
total_tokens: number;
|
|
3608
|
+
prompt_tokens_details?: {
|
|
3609
|
+
cached_tokens: number;
|
|
3610
|
+
};
|
|
3611
|
+
completion_tokens_details?: {
|
|
3612
|
+
reasoning_tokens: number;
|
|
3613
|
+
};
|
|
3618
3614
|
};
|
|
3619
3615
|
/** OpenAI non-streaming response. */
|
|
3620
3616
|
export type OpenAICompletionResponse = {
|
|
@@ -268,6 +268,8 @@ export type ProxyLedgerEntry = {
|
|
|
268
268
|
model: string;
|
|
269
269
|
provider?: string;
|
|
270
270
|
inputTokens: number;
|
|
271
|
+
/** Native Codex input includes cache reads and writes. */
|
|
272
|
+
inputIncludesCachedTokens: boolean;
|
|
271
273
|
outputTokens: number;
|
|
272
274
|
cacheReadTokens: number;
|
|
273
275
|
cacheCreationTokens: number;
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a duration string (e.g., "6m0s", "30s", "1h30m", "500ms") into milliseconds.
|
|
3
|
+
*
|
|
4
|
+
* @param raw - The raw duration string
|
|
5
|
+
* @returns Duration in milliseconds, or undefined if invalid
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseDurationMs(raw: string): number | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Parses a single Retry-After header value into milliseconds.
|
|
10
|
+
*
|
|
11
|
+
* Checks in strict order:
|
|
12
|
+
* 1. Strict numeric seconds (e.g. "5", "120", "1.5") without truncation of dates or durations
|
|
13
|
+
* 2. HTTP-date or RFC 3339 / ISO 8601 date (e.g. "2026-10-18T12:00:00Z" or "Sun, 18 Oct 2026 12:00:00 GMT")
|
|
14
|
+
* 3. Duration string (e.g. "6m0s", "30s", "1h30m", "500ms")
|
|
15
|
+
*
|
|
16
|
+
* @param value - The header value string
|
|
17
|
+
* @returns Milliseconds to wait, or undefined if not parseable
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseRetryAfterHeaderValue(value: string | null | undefined): number | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Parses X-RateLimit-Reset header value into milliseconds until reset.
|
|
22
|
+
*
|
|
23
|
+
* Handles:
|
|
24
|
+
* - Delta seconds when value < 1e9 (e.g. "30", "60")
|
|
25
|
+
* - Epoch seconds when 1e9 <= value <= 1e12
|
|
26
|
+
* - Epoch milliseconds when value > 1e12
|
|
27
|
+
* - Fallback to date or duration strings
|
|
28
|
+
*
|
|
29
|
+
* @param value - The X-RateLimit-Reset header value
|
|
30
|
+
* @returns Milliseconds until reset, or undefined if not parseable
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseRateLimitResetValue(value: string | null | undefined): number | undefined;
|
|
1
33
|
/**
|
|
2
34
|
* Parse standard and commonly used rate-limit response headers.
|
|
3
35
|
*
|
package/dist/utils/retryAfter.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves a header value case-insensitively from either a Headers object or a plain record.
|
|
3
|
+
*
|
|
4
|
+
* @param headers - The HTTP headers object or record
|
|
5
|
+
* @param headerName - The case-insensitive name of the header to retrieve
|
|
6
|
+
* @returns The header value, or null if not found
|
|
7
|
+
*/
|
|
1
8
|
function getHeader(headers, headerName) {
|
|
2
9
|
if ("get" in headers && typeof headers.get === "function") {
|
|
3
10
|
return headers.get(headerName);
|
|
@@ -10,6 +17,128 @@ function getHeader(headers, headerName) {
|
|
|
10
17
|
}
|
|
11
18
|
return null;
|
|
12
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Parses a duration string (e.g., "6m0s", "30s", "1h30m", "500ms") into milliseconds.
|
|
22
|
+
*
|
|
23
|
+
* @param raw - The raw duration string
|
|
24
|
+
* @returns Duration in milliseconds, or undefined if invalid
|
|
25
|
+
*/
|
|
26
|
+
export function parseDurationMs(raw) {
|
|
27
|
+
const trimmed = raw.trim();
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
// Must consist of one or more [number][unit] components
|
|
32
|
+
const durationPattern = /^(\d+(?:\.\d+)?(?:ms|s|m|h))+$/i;
|
|
33
|
+
if (!durationPattern.test(trimmed)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const tokenRegex = /(\d+(?:\.\d+)?)(ms|s|m|h)/gi;
|
|
37
|
+
let totalMs = 0;
|
|
38
|
+
let match;
|
|
39
|
+
let matchedLength = 0;
|
|
40
|
+
while ((match = tokenRegex.exec(trimmed)) !== null) {
|
|
41
|
+
matchedLength += match[0].length;
|
|
42
|
+
const num = parseFloat(match[1]);
|
|
43
|
+
const unit = match[2].toLowerCase();
|
|
44
|
+
switch (unit) {
|
|
45
|
+
case "h":
|
|
46
|
+
totalMs += num * 3600 * 1000;
|
|
47
|
+
break;
|
|
48
|
+
case "m":
|
|
49
|
+
totalMs += num * 60 * 1000;
|
|
50
|
+
break;
|
|
51
|
+
case "s":
|
|
52
|
+
totalMs += num * 1000;
|
|
53
|
+
break;
|
|
54
|
+
case "ms":
|
|
55
|
+
totalMs += num;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return matchedLength === trimmed.length ? Math.round(totalMs) : undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parses a single Retry-After header value into milliseconds.
|
|
63
|
+
*
|
|
64
|
+
* Checks in strict order:
|
|
65
|
+
* 1. Strict numeric seconds (e.g. "5", "120", "1.5") without truncation of dates or durations
|
|
66
|
+
* 2. HTTP-date or RFC 3339 / ISO 8601 date (e.g. "2026-10-18T12:00:00Z" or "Sun, 18 Oct 2026 12:00:00 GMT")
|
|
67
|
+
* 3. Duration string (e.g. "6m0s", "30s", "1h30m", "500ms")
|
|
68
|
+
*
|
|
69
|
+
* @param value - The header value string
|
|
70
|
+
* @returns Milliseconds to wait, or undefined if not parseable
|
|
71
|
+
*/
|
|
72
|
+
export function parseRetryAfterHeaderValue(value) {
|
|
73
|
+
if (!value) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
if (!trimmed) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
// 1. Strict numeric representation (delta seconds)
|
|
81
|
+
// Note: Avoid parseInt, which reads leading digits from dates ("2026-10-13..." -> 2026) or durations ("6m0s" -> 6)
|
|
82
|
+
if (/^\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
83
|
+
const seconds = Number(trimmed);
|
|
84
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
85
|
+
return Math.round(seconds * 1000);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// 2. HTTP-date or RFC 3339 / ISO 8601 date
|
|
89
|
+
const parsedDate = Date.parse(trimmed);
|
|
90
|
+
if (Number.isFinite(parsedDate)) {
|
|
91
|
+
return Math.max(0, parsedDate - Date.now());
|
|
92
|
+
}
|
|
93
|
+
// 3. Duration string (e.g. "6m0s", "30s", "500ms")
|
|
94
|
+
const duration = parseDurationMs(trimmed);
|
|
95
|
+
if (duration !== undefined && duration >= 0) {
|
|
96
|
+
return duration;
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Parses X-RateLimit-Reset header value into milliseconds until reset.
|
|
102
|
+
*
|
|
103
|
+
* Handles:
|
|
104
|
+
* - Delta seconds when value < 1e9 (e.g. "30", "60")
|
|
105
|
+
* - Epoch seconds when 1e9 <= value <= 1e12
|
|
106
|
+
* - Epoch milliseconds when value > 1e12
|
|
107
|
+
* - Fallback to date or duration strings
|
|
108
|
+
*
|
|
109
|
+
* @param value - The X-RateLimit-Reset header value
|
|
110
|
+
* @returns Milliseconds until reset, or undefined if not parseable
|
|
111
|
+
*/
|
|
112
|
+
export function parseRateLimitResetValue(value) {
|
|
113
|
+
if (!value) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const trimmed = value.trim();
|
|
117
|
+
if (!trimmed) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (/^\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
121
|
+
const num = Number(trimmed);
|
|
122
|
+
if (Number.isFinite(num) && num >= 0) {
|
|
123
|
+
if (num < 1e9) {
|
|
124
|
+
// Delta seconds (e.g. 30, 60, 300)
|
|
125
|
+
return Math.round(num * 1000);
|
|
126
|
+
}
|
|
127
|
+
// Unix epoch timestamp
|
|
128
|
+
const resetTime = num > 1e12 ? num : num * 1000;
|
|
129
|
+
return Math.max(0, Math.round(resetTime - Date.now()));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const parsedDate = Date.parse(trimmed);
|
|
133
|
+
if (Number.isFinite(parsedDate)) {
|
|
134
|
+
return Math.max(0, parsedDate - Date.now());
|
|
135
|
+
}
|
|
136
|
+
const duration = parseDurationMs(trimmed);
|
|
137
|
+
if (duration !== undefined && duration >= 0) {
|
|
138
|
+
return duration;
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
13
142
|
/**
|
|
14
143
|
* Parse standard and commonly used rate-limit response headers.
|
|
15
144
|
*
|
|
@@ -19,21 +148,16 @@ function getHeader(headers, headerName) {
|
|
|
19
148
|
export function parseRetryAfterMs(headers) {
|
|
20
149
|
const retryAfter = getHeader(headers, "Retry-After");
|
|
21
150
|
if (retryAfter) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
const retryDate = new Date(retryAfter);
|
|
27
|
-
if (!Number.isNaN(retryDate.getTime())) {
|
|
28
|
-
return Math.max(0, retryDate.getTime() - Date.now());
|
|
151
|
+
const delay = parseRetryAfterHeaderValue(retryAfter);
|
|
152
|
+
if (delay !== undefined) {
|
|
153
|
+
return delay;
|
|
29
154
|
}
|
|
30
155
|
}
|
|
31
156
|
const rateLimitReset = getHeader(headers, "X-RateLimit-Reset");
|
|
32
157
|
if (rateLimitReset) {
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
return Math.max(0, resetTime - Date.now());
|
|
158
|
+
const delay = parseRateLimitResetValue(rateLimitReset);
|
|
159
|
+
if (delay !== undefined) {
|
|
160
|
+
return delay;
|
|
37
161
|
}
|
|
38
162
|
}
|
|
39
163
|
if (getHeader(headers, "X-RateLimit-Remaining") === "0") {
|
|
@@ -42,7 +42,7 @@ export declare const QUALITY_MAX_WORKFLOW: WorkflowConfig;
|
|
|
42
42
|
*
|
|
43
43
|
* Optimizes for speed with quality fallback:
|
|
44
44
|
* 1. Fast tier: Single fast model (GPT-4o-mini)
|
|
45
|
-
* 2. Balanced tier: If fast fails, use Gemini
|
|
45
|
+
* 2. Balanced tier: If fast fails, use Gemini 3.8 Flash
|
|
46
46
|
* 3. Quality tier: If both fail, use GPT-4o
|
|
47
47
|
*/
|
|
48
48
|
export declare const SPEED_FIRST_WORKFLOW: WorkflowConfig;
|
|
@@ -51,7 +51,7 @@ export declare const SPEED_FIRST_WORKFLOW: WorkflowConfig;
|
|
|
51
51
|
*
|
|
52
52
|
* Balances speed, cost, and quality:
|
|
53
53
|
* 1. Standard tier (parallel): GPT-4o-mini + Gemini Flash
|
|
54
|
-
* 2. Premium tier (parallel): GPT-4o + Claude
|
|
54
|
+
* 2. Premium tier (parallel): GPT-4o + Claude Sonnet 4.6 if standard uncertain
|
|
55
55
|
*/
|
|
56
56
|
export declare const BALANCED_ADAPTIVE_WORKFLOW: WorkflowConfig;
|
|
57
57
|
/**
|