@juspay/neurolink 12.9.2 → 12.9.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/browser/neurolink.min.js +391 -391
- package/dist/providers/catalog/cloudflare.json +3 -3
- package/dist/proxy/accountLedger.d.ts +6 -3
- package/dist/proxy/accountLedger.js +37 -10
- package/dist/proxy/providerTransportCoordinator.d.ts +4 -1
- package/dist/proxy/providerTransportCoordinator.js +8 -1
- package/dist/server/routes/claudeProxyRoutes.d.ts +19 -0
- package/dist/server/routes/claudeProxyRoutes.js +231 -49
- package/dist/types/proxy.d.ts +30 -1
- package/dist/types/proxyClient.d.ts +20 -2
- package/package.json +2 -1
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"contextWindow": 24000,
|
|
48
48
|
"vision": false,
|
|
49
49
|
"status": "production",
|
|
50
|
-
"description": "Llama 3.2 11B Vision Instruct —
|
|
50
|
+
"description": "Llama 3.2 11B Vision Instruct — Meta Community License accepted on the account 2026-09-02, but Cloudflare's OpenAI-compatible endpoint accepts no image input for it: an image content part is rejected (400) and a top-level image is ignored ('There is no image'). The model reads images only through the native /ai/run prompt+image shape, which this OpenAI-compatible provider does not speak, so vision stays false here"
|
|
51
51
|
},
|
|
52
52
|
"@cf/mistral/mistral-7b-instruct-v0.2": {
|
|
53
53
|
"enumMember": "MISTRAL_7B_INSTRUCT_V0_2",
|
|
@@ -114,8 +114,8 @@
|
|
|
114
114
|
"method": "real chat call per id against the live account"
|
|
115
115
|
},
|
|
116
116
|
"liveMatrix": {
|
|
117
|
-
"date": "2026-
|
|
118
|
-
"result": "Live tool round-trip verified on @cf/meta/llama-3.1-8b-instruct-fast, both non-streaming and streaming, once messageContentFormat normalization was applied; before it the follow-up turn was rejected with HTTP 400 (content sent as array/null). Of the three ids that failed, only two are actually gone: mistral-7b-instruct-v0.2 ('No such model') and qwen1.5-14b-chat-awq ('deprecated on 2025-10-01'). llama-3.2-11b-vision-instruct is alive but gated behind a one-time Community License acceptance on the account, so it answers 403 and is left production with vision false rather than retired."
|
|
117
|
+
"date": "2026-09-02",
|
|
118
|
+
"result": "Live tool round-trip verified on @cf/meta/llama-3.1-8b-instruct-fast, both non-streaming and streaming, once messageContentFormat normalization was applied; before it the follow-up turn was rejected with HTTP 400 (content sent as array/null). Of the three ids that failed, only two are actually gone: mistral-7b-instruct-v0.2 ('No such model') and qwen1.5-14b-chat-awq ('deprecated on 2025-10-01'). llama-3.2-11b-vision-instruct is alive but gated behind a one-time Community License acceptance on the account, so it answers 403 and is left production with vision false rather than retired. Update 2026-09-02: the Community License was accepted on the account; plain chat on llama-3.2-11b-vision-instruct now returns 200 on both endpoints, and the native /ai/run prompt+image call describes a 64x64 test image correctly. The OpenAI-compatible endpoint still cannot carry an image (content-part array → 400, top-level image ignored), so the vision flag remains false for this provider."
|
|
119
119
|
},
|
|
120
120
|
"addedInPR": "https://github.com/juspay/neurolink/pull/1587"
|
|
121
121
|
},
|
|
@@ -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));
|
|
@@ -5,10 +5,13 @@ export declare class ProviderTransportCoordinator {
|
|
|
5
5
|
private backoffUntil;
|
|
6
6
|
private lastErrorCode;
|
|
7
7
|
private lastTransportScope;
|
|
8
|
+
/** Whether the failure that degraded the transport happened before any
|
|
9
|
+
* request byte was sent. Waiters use it to pick their retry budget. */
|
|
10
|
+
private lastFailureConnectPhase;
|
|
8
11
|
private probe;
|
|
9
12
|
acquire(signal?: AbortSignal): Promise<ProxyProviderTransportPermit>;
|
|
10
13
|
reportSuccess(permit: ProxyProviderTransportPermit): void;
|
|
11
|
-
reportTransportFailure(errorCode: string | undefined, transportScope: "shared_provider_transport" | "connection_transport", permit: ProxyProviderTransportPermit): void;
|
|
14
|
+
reportTransportFailure(errorCode: string | undefined, transportScope: "shared_provider_transport" | "connection_transport", permit: ProxyProviderTransportPermit, connectPhase?: boolean): void;
|
|
12
15
|
reportProbeAbandoned(permit: ProxyProviderTransportPermit): void;
|
|
13
16
|
clear(): void;
|
|
14
17
|
private wait;
|
|
@@ -7,6 +7,9 @@ export class ProviderTransportCoordinator {
|
|
|
7
7
|
backoffUntil = 0;
|
|
8
8
|
lastErrorCode = null;
|
|
9
9
|
lastTransportScope = "shared_provider_transport";
|
|
10
|
+
/** Whether the failure that degraded the transport happened before any
|
|
11
|
+
* request byte was sent. Waiters use it to pick their retry budget. */
|
|
12
|
+
lastFailureConnectPhase = false;
|
|
10
13
|
probe;
|
|
11
14
|
async acquire(signal) {
|
|
12
15
|
if (!this.degraded) {
|
|
@@ -31,6 +34,7 @@ export class ProviderTransportCoordinator {
|
|
|
31
34
|
allowed: false,
|
|
32
35
|
errorCode: this.lastErrorCode,
|
|
33
36
|
transportScope: this.lastTransportScope,
|
|
37
|
+
connectPhase: this.lastFailureConnectPhase,
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
let resolveProbe;
|
|
@@ -55,12 +59,13 @@ export class ProviderTransportCoordinator {
|
|
|
55
59
|
this.backoffUntil = 0;
|
|
56
60
|
this.lastErrorCode = null;
|
|
57
61
|
this.lastTransportScope = "shared_provider_transport";
|
|
62
|
+
this.lastFailureConnectPhase = false;
|
|
58
63
|
if (permit.probe && this.probe?.generation === permit.generation) {
|
|
59
64
|
this.probe.resolve("recovered");
|
|
60
65
|
this.probe = undefined;
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
|
-
reportTransportFailure(errorCode, transportScope, permit) {
|
|
68
|
+
reportTransportFailure(errorCode, transportScope, permit, connectPhase = false) {
|
|
64
69
|
if (!permit.allowed || permit.generation !== this.generation) {
|
|
65
70
|
return;
|
|
66
71
|
}
|
|
@@ -71,6 +76,7 @@ export class ProviderTransportCoordinator {
|
|
|
71
76
|
this.backoffUntil = Date.now() + SHARED_TRANSPORT_BACKOFF_MS;
|
|
72
77
|
this.lastErrorCode = errorCode ?? null;
|
|
73
78
|
this.lastTransportScope = transportScope;
|
|
79
|
+
this.lastFailureConnectPhase = connectPhase;
|
|
74
80
|
if (permit.probe && this.probe?.generation === permit.generation) {
|
|
75
81
|
this.probe.resolve("failed");
|
|
76
82
|
this.probe = undefined;
|
|
@@ -96,6 +102,7 @@ export class ProviderTransportCoordinator {
|
|
|
96
102
|
this.backoffUntil = 0;
|
|
97
103
|
this.lastErrorCode = null;
|
|
98
104
|
this.lastTransportScope = "shared_provider_transport";
|
|
105
|
+
this.lastFailureConnectPhase = false;
|
|
99
106
|
}
|
|
100
107
|
async wait(ms, signal) {
|
|
101
108
|
if (signal?.aborted) {
|
|
@@ -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;
|
|
@@ -388,6 +389,20 @@ declare function reconcileEligibleAccountRuntimeState(account: ProxyPassthroughA
|
|
|
388
389
|
export declare function getTransientSameAccountRetryDelayMs(retryNumber: number): number;
|
|
389
390
|
export declare function getOverloadRotationDelayMs(attemptNumber: number): number;
|
|
390
391
|
declare function describeTransportError(error: unknown): string;
|
|
392
|
+
/**
|
|
393
|
+
* Whether a transport failure provably happened while connecting, before any
|
|
394
|
+
* byte of the request left the process.
|
|
395
|
+
*
|
|
396
|
+
* Node tags its connect errors with `syscall: "connect"`; with happy-eyeballs
|
|
397
|
+
* enabled (the default) the error is an AggregateError whose every entry is
|
|
398
|
+
* one such attempt, and undici wraps either as `fetch failed` with the
|
|
399
|
+
* original as `cause`. The code alone cannot make this call: an `ETIMEDOUT`
|
|
400
|
+
* from the connect timer and an `ETIMEDOUT` from a socket that went quiet
|
|
401
|
+
* after dispatch look identical by code, and only the first is safe to
|
|
402
|
+
* retry. Both proxies behind one lossy Wi-Fi uplink returned 116 terminal
|
|
403
|
+
* 502s in a day on exactly that ambiguity.
|
|
404
|
+
*/
|
|
405
|
+
declare function isConnectPhaseNetworkError(error: unknown): boolean;
|
|
391
406
|
/**
|
|
392
407
|
* Determine whether a POST can be retried without risking duplicate provider
|
|
393
408
|
* work. Only failures that prove connection establishment did not complete are
|
|
@@ -467,7 +482,11 @@ export declare const __testHooks: {
|
|
|
467
482
|
refreshAccountLimits: typeof refreshAccountLimits;
|
|
468
483
|
applyAccountUsageResult: typeof applyAccountUsageResult;
|
|
469
484
|
clearLimitsRefreshStateForTests: () => void;
|
|
485
|
+
setAccountDirectoryForTests: (override: ProxyAccountDirectoryOverride | null) => void;
|
|
470
486
|
isRetryableNetworkError: typeof isRetryableNetworkError;
|
|
487
|
+
isConnectPhaseNetworkError: typeof isConnectPhaseNetworkError;
|
|
488
|
+
MAX_CONNECT_PHASE_SAME_ACCOUNT_RETRIES: number;
|
|
489
|
+
clearProviderTransportCoordinatorForTests: () => void;
|
|
471
490
|
isPermanentRefreshFailure: typeof isPermanentRefreshFailure;
|
|
472
491
|
getStreamFailureDetails: typeof getStreamFailureDetails;
|
|
473
492
|
trackUpstreamReadableStream: typeof trackUpstreamReadableStream;
|