@juspay/neurolink 12.9.1 → 12.9.3
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 +382 -382
- package/dist/constants/enums.d.ts +17 -2
- package/dist/constants/enums.js +17 -2
- package/dist/core/baseProvider.d.ts +56 -0
- package/dist/core/baseProvider.js +308 -17
- package/dist/providers/catalog/cerebras.json +15 -5
- package/dist/providers/catalog/cloudflare.json +17 -13
- package/dist/providers/catalog/fireworks.json +74 -43
- package/dist/providers/catalog/groq.json +81 -32
- package/dist/providers/catalog/loader.js +3 -0
- package/dist/providers/catalog/mistral.json +27 -23
- package/dist/providers/catalog/provider-catalog.schema.json +5 -0
- package/dist/providers/catalog/sambanova.json +36 -11
- package/dist/providers/catalog/schema.d.ts +2 -1
- package/dist/providers/catalog/schema.js +1 -0
- package/dist/providers/catalog/xai.json +39 -26
- package/dist/providers/configuredOpenAICompat.d.ts +18 -1
- package/dist/providers/configuredOpenAICompat.js +48 -0
- package/dist/providers/openaiChatCompletionsBase.d.ts +15 -0
- package/dist/providers/openaiChatCompletionsBase.js +20 -0
- package/dist/proxy/accountLedger.d.ts +6 -3
- package/dist/proxy/accountLedger.js +37 -10
- package/dist/server/routes/claudeProxyRoutes.d.ts +2 -0
- package/dist/server/routes/claudeProxyRoutes.js +155 -45
- package/dist/types/providerCatalog.d.ts +6 -0
- package/dist/types/providers.d.ts +3 -0
- package/dist/types/proxy.d.ts +21 -1
- package/dist/types/proxyClient.d.ts +20 -2
- package/package.json +2 -1
|
@@ -8,23 +8,38 @@
|
|
|
8
8
|
"baseURL": "https://api.x.ai/v1"
|
|
9
9
|
},
|
|
10
10
|
"models": {
|
|
11
|
-
"default": "grok-
|
|
12
|
-
"fallbacks": [
|
|
13
|
-
"grok-3",
|
|
14
|
-
"grok-3-mini",
|
|
15
|
-
"grok-2-latest",
|
|
16
|
-
"grok-2-vision-latest",
|
|
17
|
-
"grok-beta"
|
|
18
|
-
],
|
|
11
|
+
"default": "grok-4.6",
|
|
12
|
+
"fallbacks": ["grok-4.6", "grok-4.5", "grok-4.3", "grok-3", "grok-3-mini"],
|
|
19
13
|
"defaultContextWindow": 131072,
|
|
20
14
|
"defaultMaxOutputTokens": 4096,
|
|
21
15
|
"catalog": {
|
|
16
|
+
"grok-4.6": {
|
|
17
|
+
"enumMember": "GROK_4_6",
|
|
18
|
+
"contextWindow": 131072,
|
|
19
|
+
"vision": true,
|
|
20
|
+
"status": "production",
|
|
21
|
+
"description": "Recommended - Grok 4.6, current flagship; vision-capable"
|
|
22
|
+
},
|
|
23
|
+
"grok-4.5": {
|
|
24
|
+
"enumMember": "GROK_4_5",
|
|
25
|
+
"contextWindow": 131072,
|
|
26
|
+
"vision": true,
|
|
27
|
+
"status": "production",
|
|
28
|
+
"description": "Grok 4.5 — vision-capable"
|
|
29
|
+
},
|
|
30
|
+
"grok-4.3": {
|
|
31
|
+
"enumMember": "GROK_4_3",
|
|
32
|
+
"contextWindow": 131072,
|
|
33
|
+
"vision": true,
|
|
34
|
+
"status": "production",
|
|
35
|
+
"description": "Grok 4.3 — vision-capable"
|
|
36
|
+
},
|
|
22
37
|
"grok-3": {
|
|
23
38
|
"contextWindow": 131072,
|
|
24
39
|
"pricingPerMTok": { "input": 3.0, "output": 15.0 },
|
|
25
40
|
"vision": false,
|
|
26
41
|
"status": "production",
|
|
27
|
-
"description": "
|
|
42
|
+
"description": "Grok 3 — still served, though absent from the /models roster"
|
|
28
43
|
},
|
|
29
44
|
"grok-3-mini": {
|
|
30
45
|
"contextWindow": 131072,
|
|
@@ -37,31 +52,26 @@
|
|
|
37
52
|
"contextWindow": 131072,
|
|
38
53
|
"pricingPerMTok": { "input": 2.0, "output": 10.0 },
|
|
39
54
|
"vision": false,
|
|
40
|
-
"status": "
|
|
41
|
-
"description": "
|
|
55
|
+
"status": "retired",
|
|
56
|
+
"description": "Retired 2026-08 — xAI rejects it as an invalid model"
|
|
42
57
|
},
|
|
43
58
|
"grok-2-vision-latest": {
|
|
44
59
|
"contextWindow": 32768,
|
|
45
60
|
"pricingPerMTok": { "input": 2.0, "output": 10.0 },
|
|
46
|
-
"vision":
|
|
47
|
-
"status": "
|
|
48
|
-
"description": "
|
|
61
|
+
"vision": false,
|
|
62
|
+
"status": "retired",
|
|
63
|
+
"description": "Retired 2026-08 — xAI rejects it as an invalid model; vision moved to the grok-4.x line"
|
|
49
64
|
},
|
|
50
65
|
"grok-beta": {
|
|
51
66
|
"contextWindow": 131072,
|
|
52
67
|
"pricingPerMTok": { "input": 5.0, "output": 15.0 },
|
|
53
68
|
"vision": false,
|
|
54
|
-
"status": "
|
|
55
|
-
"description": "
|
|
69
|
+
"status": "retired",
|
|
70
|
+
"description": "Retired 2026-08 — xAI rejects it as an invalid model"
|
|
56
71
|
}
|
|
57
72
|
},
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
"grok-3-mini",
|
|
61
|
-
"grok-2-vision-latest",
|
|
62
|
-
"grok-2-latest",
|
|
63
|
-
"grok-beta"
|
|
64
|
-
]
|
|
73
|
+
"visionModel": "grok-4.6",
|
|
74
|
+
"topModels": ["grok-4.6", "grok-4.5", "grok-4.3", "grok-3"]
|
|
65
75
|
},
|
|
66
76
|
"capabilities": {
|
|
67
77
|
"text": true,
|
|
@@ -99,10 +109,13 @@
|
|
|
99
109
|
},
|
|
100
110
|
"evidence": {
|
|
101
111
|
"rosterVerified": {
|
|
102
|
-
"date": "2026-08-
|
|
103
|
-
"method": "
|
|
112
|
+
"date": "2026-08-30",
|
|
113
|
+
"method": "GET /models against the live account, cross-checked with a real chat call per id"
|
|
114
|
+
},
|
|
115
|
+
"liveMatrix": {
|
|
116
|
+
"date": "2026-08-30",
|
|
117
|
+
"result": "grok-2-latest, grok-2-vision-latest and grok-beta are rejected as invalid models. The grok-4.x line replaces them: 4.3/4.5/4.6 all answer, and all three read a 64x64 PNG correctly, so vision moved there from the retired grok-2-vision-latest. An earlier probe with an 8x8 PNG reported no vision — that was xAI answering 'Invalid PNG image', a complaint about the file rather than the capability. grok-3 and grok-3-mini are kept because they still serve, even though xAI's /models roster omits them: roster absence is not evidence of death here."
|
|
104
118
|
},
|
|
105
|
-
"liveMatrix": null,
|
|
106
119
|
"addedInPR": "https://github.com/juspay/neurolink/pull/1587"
|
|
107
120
|
}
|
|
108
121
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AIProviderName } from "../constants/enums.js";
|
|
2
|
-
import type { OpenAICompatCatalogEntry, OpenAICompatCredentials } from "../types/index.js";
|
|
2
|
+
import type { OpenAICompatCatalogEntry, OpenAICompatChatRequest, OpenAICompatCredentials } from "../types/index.js";
|
|
3
3
|
import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
4
4
|
/**
|
|
5
5
|
* Generic OpenAI-compatible provider driven entirely by an
|
|
@@ -12,6 +12,22 @@ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
|
12
12
|
* adjustBodyAfter400, getChatCompletionsURL, getAuthHeaders,
|
|
13
13
|
* suppressResponseFormatWithTools, ...) it does NOT belong in the catalog —
|
|
14
14
|
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts).
|
|
15
|
+
*
|
|
16
|
+
* The exception is a WIRE DIALECT: a vendor that speaks OpenAI for ordinary
|
|
17
|
+
* chat but encodes one part of the request differently. Expressing that as
|
|
18
|
+
* data (the catalog's `messageContentFormat`) keeps the provider a
|
|
19
|
+
* one-JSON-file entry instead of promoting it to a hand-written subclass
|
|
20
|
+
* over a single incompatibility.
|
|
21
|
+
*
|
|
22
|
+
* Today that is Cloudflare Workers AI, whose OpenAI-compatible endpoint
|
|
23
|
+
* accepts `messages[].content` only as a plain string — never the
|
|
24
|
+
* content-parts array OpenAI allows, and never the `null` that OpenAI uses
|
|
25
|
+
* on an assistant message carrying tool_calls. Its schema rejects both with
|
|
26
|
+
* HTTP 400 ("Type mismatch of '/messages/N/content'"). That bites precisely
|
|
27
|
+
* on the second turn of a tool call, so single-turn chat looks healthy while
|
|
28
|
+
* every tool round-trip fails. Normalizing content to a string below is the
|
|
29
|
+
* whole fix: tools, tool_choice, the `tool` role and `tool_calls` are all
|
|
30
|
+
* accepted as-is.
|
|
15
31
|
*/
|
|
16
32
|
export declare class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvider {
|
|
17
33
|
private readonly entry;
|
|
@@ -20,5 +36,6 @@ export declare class ConfiguredOpenAICompatProvider extends OpenAIChatCompletion
|
|
|
20
36
|
protected getDefaultModel(): string;
|
|
21
37
|
protected getFallbackModelName(): string;
|
|
22
38
|
protected getFallbackModels(): string[];
|
|
39
|
+
protected adjustRequestBody(body: OpenAICompatChatRequest, modelId: string): OpenAICompatChatRequest;
|
|
23
40
|
protected formatProviderError(error: unknown): Error;
|
|
24
41
|
}
|
|
@@ -4,6 +4,25 @@ import { getProviderModel, resolveOpenAICompatConfig, } from "../utils/providerC
|
|
|
4
4
|
import { classifyProviderError } from "../utils/errorClassifier.js";
|
|
5
5
|
import { TimeoutError } from "../utils/timeout.js";
|
|
6
6
|
import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
7
|
+
/**
|
|
8
|
+
* Collapse OpenAI's `content` union down to the plain string that
|
|
9
|
+
* string-only vendors accept. Image parts carry no string representation and
|
|
10
|
+
* are dropped — a provider that cannot accept a content array cannot accept
|
|
11
|
+
* inline images either.
|
|
12
|
+
*/
|
|
13
|
+
const flattenMessageContent = (content) => {
|
|
14
|
+
if (typeof content === "string") {
|
|
15
|
+
return content;
|
|
16
|
+
}
|
|
17
|
+
// An assistant message with tool_calls legitimately has null content;
|
|
18
|
+
// the empty string is its string-only equivalent.
|
|
19
|
+
if (content === null || content === undefined) {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
return content
|
|
23
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
24
|
+
.join("");
|
|
25
|
+
};
|
|
7
26
|
/**
|
|
8
27
|
* Generic OpenAI-compatible provider driven entirely by an
|
|
9
28
|
* OpenAICompatCatalogEntry. Replaces a hand-written subclass for any
|
|
@@ -15,6 +34,22 @@ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
|
15
34
|
* adjustBodyAfter400, getChatCompletionsURL, getAuthHeaders,
|
|
16
35
|
* suppressResponseFormatWithTools, ...) it does NOT belong in the catalog —
|
|
17
36
|
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts).
|
|
37
|
+
*
|
|
38
|
+
* The exception is a WIRE DIALECT: a vendor that speaks OpenAI for ordinary
|
|
39
|
+
* chat but encodes one part of the request differently. Expressing that as
|
|
40
|
+
* data (the catalog's `messageContentFormat`) keeps the provider a
|
|
41
|
+
* one-JSON-file entry instead of promoting it to a hand-written subclass
|
|
42
|
+
* over a single incompatibility.
|
|
43
|
+
*
|
|
44
|
+
* Today that is Cloudflare Workers AI, whose OpenAI-compatible endpoint
|
|
45
|
+
* accepts `messages[].content` only as a plain string — never the
|
|
46
|
+
* content-parts array OpenAI allows, and never the `null` that OpenAI uses
|
|
47
|
+
* on an assistant message carrying tool_calls. Its schema rejects both with
|
|
48
|
+
* HTTP 400 ("Type mismatch of '/messages/N/content'"). That bites precisely
|
|
49
|
+
* on the second turn of a tool call, so single-turn chat looks healthy while
|
|
50
|
+
* every tool round-trip fails. Normalizing content to a string below is the
|
|
51
|
+
* whole fix: tools, tool_choice, the `tool` role and `tool_calls` are all
|
|
52
|
+
* accepted as-is.
|
|
18
53
|
*/
|
|
19
54
|
export class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvider {
|
|
20
55
|
entry;
|
|
@@ -51,6 +86,19 @@ export class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvide
|
|
|
51
86
|
getFallbackModels() {
|
|
52
87
|
return this.entry.fallbackModels;
|
|
53
88
|
}
|
|
89
|
+
adjustRequestBody(body, modelId) {
|
|
90
|
+
const adjusted = super.adjustRequestBody(body, modelId);
|
|
91
|
+
if (this.entry.messageContentFormat !== "string") {
|
|
92
|
+
return adjusted;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
...adjusted,
|
|
96
|
+
messages: adjusted.messages.map((message) => ({
|
|
97
|
+
...message,
|
|
98
|
+
content: flattenMessageContent(message.content),
|
|
99
|
+
})),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
54
102
|
formatProviderError(error) {
|
|
55
103
|
// classifyProviderError hard-codes TimeoutError -> NetworkError ahead
|
|
56
104
|
// of any rule table and does not allow a per-provider override. An
|
|
@@ -45,6 +45,21 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
45
45
|
* Hardcoded model names returned from `getAvailableModels()` when the
|
|
46
46
|
* remote `/models` endpoint can't be reached. Default empty.
|
|
47
47
|
*/
|
|
48
|
+
/**
|
|
49
|
+
* Feed the catalog's `fallbacks` to BaseProvider's invalid-model retry, so
|
|
50
|
+
* a default the vendor has retired degrades to the next live model in the
|
|
51
|
+
* entry instead of failing the call outright.
|
|
52
|
+
*/
|
|
53
|
+
protected getModelFallbacks(): string[];
|
|
54
|
+
/**
|
|
55
|
+
* `resolvedModel` memoizes the first id this provider resolved, and
|
|
56
|
+
* getAISDKModel() builds its wire model from that memo rather than from
|
|
57
|
+
* `modelName`. Leaving it stale here silently defeats the invalid-model
|
|
58
|
+
* fallback: modelName advances to the next candidate while every request
|
|
59
|
+
* still carries the retired id, so each retry fails for the same reason
|
|
60
|
+
* the first attempt did.
|
|
61
|
+
*/
|
|
62
|
+
protected refreshHandlersForModel(model: string): void;
|
|
48
63
|
protected getFallbackModels(): string[];
|
|
49
64
|
/**
|
|
50
65
|
* Hook to mutate the `buildBody` options before the wire body is
|
|
@@ -68,6 +68,26 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
68
68
|
* Hardcoded model names returned from `getAvailableModels()` when the
|
|
69
69
|
* remote `/models` endpoint can't be reached. Default empty.
|
|
70
70
|
*/
|
|
71
|
+
/**
|
|
72
|
+
* Feed the catalog's `fallbacks` to BaseProvider's invalid-model retry, so
|
|
73
|
+
* a default the vendor has retired degrades to the next live model in the
|
|
74
|
+
* entry instead of failing the call outright.
|
|
75
|
+
*/
|
|
76
|
+
getModelFallbacks() {
|
|
77
|
+
return this.getFallbackModels();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `resolvedModel` memoizes the first id this provider resolved, and
|
|
81
|
+
* getAISDKModel() builds its wire model from that memo rather than from
|
|
82
|
+
* `modelName`. Leaving it stale here silently defeats the invalid-model
|
|
83
|
+
* fallback: modelName advances to the next candidate while every request
|
|
84
|
+
* still carries the retired id, so each retry fails for the same reason
|
|
85
|
+
* the first attempt did.
|
|
86
|
+
*/
|
|
87
|
+
refreshHandlersForModel(model) {
|
|
88
|
+
this.resolvedModel = model;
|
|
89
|
+
super.refreshHandlersForModel(model);
|
|
90
|
+
}
|
|
71
91
|
getFallbackModels() {
|
|
72
92
|
return [];
|
|
73
93
|
}
|
|
@@ -29,10 +29,13 @@ import type { CliAccountUsageTotals } from "../types/index.js";
|
|
|
29
29
|
/** UTC date stamp of the log file the totals cover. */
|
|
30
30
|
export declare function currentUsageDate(now?: Date): string;
|
|
31
31
|
/**
|
|
32
|
-
* Token and cost totals for one UTC day, keyed by
|
|
32
|
+
* Token and cost totals for one UTC day, keyed by provider-qualified account
|
|
33
|
+
* key ("anthropic:<label>" / "codex:<label>").
|
|
33
34
|
*
|
|
34
|
-
*
|
|
35
|
-
* with an Anthropic one
|
|
35
|
+
* Both engines' rows are attributed, each under its own key, so a Codex login
|
|
36
|
+
* sharing an email with an Anthropic one contributes to its own row and never
|
|
37
|
+
* to the other's. Keying by bare label used to force a choice between merging
|
|
38
|
+
* them and dropping Codex outright; it dropped Codex.
|
|
36
39
|
*/
|
|
37
40
|
export declare function readAccountUsage(date?: string): Promise<Map<string, CliAccountUsageTotals>>;
|
|
38
41
|
/** Drop all cached cursors. Exported for tests, which vary HOME per case. */
|
|
@@ -33,9 +33,29 @@ const REQUEST_FILE_PATTERN = /^proxy-\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
|
33
33
|
/**
|
|
34
34
|
* Account types written by the Anthropic pool. The request log is shared with
|
|
35
35
|
* the Codex engine, and an operator can use the same email for both, so rows
|
|
36
|
-
*
|
|
36
|
+
* are attributed by provider-qualified key, never by bare label.
|
|
37
37
|
*/
|
|
38
38
|
const ANTHROPIC_ACCOUNT_TYPES = new Set(["oauth", "api_key"]);
|
|
39
|
+
const CODEX_ACCOUNT_TYPE = "codex-oauth";
|
|
40
|
+
/**
|
|
41
|
+
* The key a log row is attributed under. Current builds write `accountKey`;
|
|
42
|
+
* rows from before that field existed carry only the label and the engine's
|
|
43
|
+
* account type, which is enough to rebuild it — the two engines never share a
|
|
44
|
+
* type string.
|
|
45
|
+
*/
|
|
46
|
+
function ledgerAccountKey(record, account) {
|
|
47
|
+
if (typeof record.accountKey === "string" && record.accountKey.length > 0) {
|
|
48
|
+
return record.accountKey;
|
|
49
|
+
}
|
|
50
|
+
const type = typeof record.accountType === "string" ? record.accountType : "";
|
|
51
|
+
if (ANTHROPIC_ACCOUNT_TYPES.has(type)) {
|
|
52
|
+
return `anthropic:${account.trim().toLowerCase()}`;
|
|
53
|
+
}
|
|
54
|
+
if (type === CODEX_ACCOUNT_TYPE) {
|
|
55
|
+
return `codex:${account}`;
|
|
56
|
+
}
|
|
57
|
+
return account;
|
|
58
|
+
}
|
|
39
59
|
const cursors = new Map();
|
|
40
60
|
function getLogsDir() {
|
|
41
61
|
return join(homedir(), ".neurolink", "logs");
|
|
@@ -134,9 +154,11 @@ async function advanceCursor(fileName, cursor) {
|
|
|
134
154
|
// on the triple means two distinct calls that collide on id but differ in
|
|
135
155
|
// account or model are still counted separately; a true re-log of the same
|
|
136
156
|
// request keeps the same triple and merges.
|
|
137
|
-
const
|
|
157
|
+
const accountKey = ledgerAccountKey(record, account);
|
|
158
|
+
const entryKey = `${requestId}\u0000${accountKey}\u0000${typeof record.model === "string" ? record.model : ""}`;
|
|
138
159
|
const next = {
|
|
139
160
|
account,
|
|
161
|
+
accountKey,
|
|
140
162
|
accountType: typeof record.accountType === "string" ? record.accountType : "",
|
|
141
163
|
model: typeof record.model === "string" ? record.model : "",
|
|
142
164
|
provider: typeof record.provider === "string" ? record.provider : undefined,
|
|
@@ -254,10 +276,13 @@ function emptyTotals() {
|
|
|
254
276
|
};
|
|
255
277
|
}
|
|
256
278
|
/**
|
|
257
|
-
* Token and cost totals for one UTC day, keyed by
|
|
279
|
+
* Token and cost totals for one UTC day, keyed by provider-qualified account
|
|
280
|
+
* key ("anthropic:<label>" / "codex:<label>").
|
|
258
281
|
*
|
|
259
|
-
*
|
|
260
|
-
* with an Anthropic one
|
|
282
|
+
* Both engines' rows are attributed, each under its own key, so a Codex login
|
|
283
|
+
* sharing an email with an Anthropic one contributes to its own row and never
|
|
284
|
+
* to the other's. Keying by bare label used to force a choice between merging
|
|
285
|
+
* them and dropping Codex outright; it dropped Codex.
|
|
261
286
|
*/
|
|
262
287
|
export async function readAccountUsage(date = currentUsageDate()) {
|
|
263
288
|
const fileName = `proxy-${date}.jsonl`;
|
|
@@ -281,10 +306,12 @@ export async function readAccountUsage(date = currentUsageDate()) {
|
|
|
281
306
|
const totals = new Map();
|
|
282
307
|
const unpriced = new Map();
|
|
283
308
|
for (const entry of cursor.entries.values()) {
|
|
284
|
-
if (!ANTHROPIC_ACCOUNT_TYPES.has(entry.accountType)
|
|
309
|
+
if (!ANTHROPIC_ACCOUNT_TYPES.has(entry.accountType) &&
|
|
310
|
+
entry.accountType !== CODEX_ACCOUNT_TYPE) {
|
|
311
|
+
// Translation and internal rows are proxy plumbing, not a login's spend.
|
|
285
312
|
continue;
|
|
286
313
|
}
|
|
287
|
-
const row = totals.get(entry.
|
|
314
|
+
const row = totals.get(entry.accountKey) ?? emptyTotals();
|
|
288
315
|
row.requests += 1;
|
|
289
316
|
row.inputTokens += entry.inputTokens;
|
|
290
317
|
row.outputTokens += entry.outputTokens;
|
|
@@ -318,12 +345,12 @@ export async function readAccountUsage(date = currentUsageDate()) {
|
|
|
318
345
|
}
|
|
319
346
|
else {
|
|
320
347
|
row.unpricedRequests += 1;
|
|
321
|
-
const seen = unpriced.get(entry.
|
|
348
|
+
const seen = unpriced.get(entry.accountKey) ?? new Set();
|
|
322
349
|
seen.add(entry.model);
|
|
323
|
-
unpriced.set(entry.
|
|
350
|
+
unpriced.set(entry.accountKey, seen);
|
|
324
351
|
}
|
|
325
352
|
}
|
|
326
|
-
totals.set(entry.
|
|
353
|
+
totals.set(entry.accountKey, row);
|
|
327
354
|
}
|
|
328
355
|
for (const [account, row] of totals) {
|
|
329
356
|
row.costUsd = Number(row.costUsd.toFixed(6));
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
|
|
13
13
|
import { ProxyTracer } from "../../proxy/proxyTracer.js";
|
|
14
14
|
import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.js";
|
|
15
|
+
import type { ProxyAccountDirectoryOverride } from "../../types/index.js";
|
|
15
16
|
import type { AccountAllowlist, AccountAdmissionLease, JsonObject, AccountCooldownPlan, AccountQuota, AccountQuotaWindow, AccountUsageFetchResult, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicEntitlementFailure, AnthropicInvalidRequestFailure, AnthropicLoopState, AnthropicScopedExhaustion, AnthropicNonOkResult, AnthropicSuccessResult, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyAccountRoutingDecision, ProxyAccountSortMetrics, ProxyBodyCaptureLogger, ProxyLimitsRefreshResponse, ProxyQuotaCooldownUpdate, ProxyOveragePolicy, ProxyPassthroughAccount, QueuedAccountAdmission, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
|
|
16
17
|
declare function tryAcquireAccountAdmission(accountKey: string, capacity: number | undefined): AccountAdmissionLease | undefined;
|
|
17
18
|
declare function enqueueAccountAdmission(accountKey: string, capacity: number): QueuedAccountAdmission;
|
|
@@ -467,6 +468,7 @@ export declare const __testHooks: {
|
|
|
467
468
|
refreshAccountLimits: typeof refreshAccountLimits;
|
|
468
469
|
applyAccountUsageResult: typeof applyAccountUsageResult;
|
|
469
470
|
clearLimitsRefreshStateForTests: () => void;
|
|
471
|
+
setAccountDirectoryForTests: (override: ProxyAccountDirectoryOverride | null) => void;
|
|
470
472
|
isRetryableNetworkError: typeof isRetryableNetworkError;
|
|
471
473
|
isPermanentRefreshFailure: typeof isPermanentRefreshFailure;
|
|
472
474
|
getStreamFailureDetails: typeof getStreamFailureDetails;
|