@juspay/neurolink 12.12.2 → 12.12.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 +94 -94
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/core/baseProvider.js +4 -5
- package/dist/neurolink.js +21 -4
- package/dist/proxy/codexFallback.d.ts +2 -2
- package/dist/proxy/codexFallback.js +4 -1
- package/dist/proxy/proxyConfig.js +45 -2
- package/dist/proxy/routingPolicy.js +3 -0
- package/dist/server/routes/claudeProxyRoutes.js +4 -3
- package/dist/types/codex.d.ts +5 -0
- package/dist/types/generate.d.ts +21 -0
- package/dist/types/proxy.d.ts +1 -0
- package/dist/types/subscription.d.ts +3 -0
- package/package.json +1 -1
|
@@ -118,6 +118,10 @@ export const textGenerationOptionsSchema = {
|
|
|
118
118
|
type: "boolean",
|
|
119
119
|
description: "Disable tool result caching for this request (overrides global mcp.cache.enabled).",
|
|
120
120
|
},
|
|
121
|
+
disableInternalFallback: {
|
|
122
|
+
type: "boolean",
|
|
123
|
+
description: "Own fallback order yourself: skip NeuroLink's provider-priority walk and the catalog model fallback, so an invalid model or unavailable provider surfaces as its own error.",
|
|
124
|
+
},
|
|
121
125
|
disableToolCallRepair: {
|
|
122
126
|
type: "boolean",
|
|
123
127
|
description: "Disable the schema-driven tool call repair mechanism (near-miss tool names, mis-typed arguments). Repair is enabled by default.",
|
|
@@ -1299,11 +1299,10 @@ export class BaseProvider {
|
|
|
1299
1299
|
const otelSpanState = { ended: false };
|
|
1300
1300
|
return await context.with(activeCtx, async () => this.runGenerateInActiveContext(options, startTime, otelSpan, otelSpanState));
|
|
1301
1301
|
};
|
|
1302
|
-
//
|
|
1303
|
-
//
|
|
1304
|
-
//
|
|
1305
|
-
const callerOwnsFallback =
|
|
1306
|
-
options.disableInternalFallback === true;
|
|
1302
|
+
// Callers that own fallback order (providerFallback / modelChain callers,
|
|
1303
|
+
// or a router that retries on its own) pass the flag on both paths;
|
|
1304
|
+
// TextGenerationOptions declares it, so a plain read is enough here.
|
|
1305
|
+
const callerOwnsFallback = options.disableInternalFallback === true;
|
|
1307
1306
|
return await this.runGenerateWithModelFallback(attempt, callerOwnsFallback);
|
|
1308
1307
|
}
|
|
1309
1308
|
/**
|
package/dist/neurolink.js
CHANGED
|
@@ -4121,6 +4121,11 @@ Current user's request: ${currentInput}`;
|
|
|
4121
4121
|
disableTools: options.disableTools,
|
|
4122
4122
|
toolFilter: options.toolFilter,
|
|
4123
4123
|
excludeTools: options.excludeTools,
|
|
4124
|
+
// This explicit field list is the only road into the provider, so a
|
|
4125
|
+
// flag left out here never reaches BaseProvider — which is exactly how
|
|
4126
|
+
// disableInternalFallback was dropped on generate() while stream()
|
|
4127
|
+
// (which spreads its options) honoured it.
|
|
4128
|
+
disableInternalFallback: options.disableInternalFallback,
|
|
4124
4129
|
maxSteps: options.maxSteps,
|
|
4125
4130
|
toolChoice: options.toolChoice,
|
|
4126
4131
|
prepareStep: options.prepareStep,
|
|
@@ -5930,11 +5935,23 @@ Current user's request: ${currentInput}`;
|
|
|
5930
5935
|
: requestedProvider
|
|
5931
5936
|
? [requestedProvider]
|
|
5932
5937
|
: providerPriority;
|
|
5938
|
+
// The caller owns fallback order (a providerFallback / modelChain caller,
|
|
5939
|
+
// or a router that retries on its own): bound the walk to its first
|
|
5940
|
+
// candidate so an unavailable provider surfaces as its own error instead
|
|
5941
|
+
// of a silent switch. An explicit provider is already a one-element
|
|
5942
|
+
// list; this only changes the "auto" and orchestrated-preference walks.
|
|
5943
|
+
const providersToTry = options.disableInternalFallback === true
|
|
5944
|
+
? tryProviders.slice(0, 1)
|
|
5945
|
+
: tryProviders;
|
|
5946
|
+
// Caller-owned fallback never enters tryProviders: providerFallback and
|
|
5947
|
+
// modelChain are walked by runWithFallbackOrchestration around the public
|
|
5948
|
+
// generate() call, and a configured ModelPool is consumed by the block
|
|
5949
|
+
// above. Slicing here therefore never clips a caller's own list.
|
|
5933
5950
|
logger.debug(`[${functionTag}] Starting direct generation`, {
|
|
5934
5951
|
requestedProvider: requestedProvider || "auto",
|
|
5935
5952
|
preferredOrchestrated: preferredOrchestrated || "none",
|
|
5936
|
-
tryProviders,
|
|
5937
|
-
allowFallback:
|
|
5953
|
+
tryProviders: providersToTry,
|
|
5954
|
+
allowFallback: providersToTry.length > 1,
|
|
5938
5955
|
});
|
|
5939
5956
|
// ─── ModelPool path ──────────────────────────────────────────────────────
|
|
5940
5957
|
// When a ModelPool is configured, source the candidate sequence from the
|
|
@@ -6080,7 +6097,7 @@ Current user's request: ${currentInput}`;
|
|
|
6080
6097
|
// ─── End ModelPool path ───────────────────────────────────────────────────
|
|
6081
6098
|
let lastError = null;
|
|
6082
6099
|
// Try each provider in order
|
|
6083
|
-
for (const providerName of
|
|
6100
|
+
for (const providerName of providersToTry) {
|
|
6084
6101
|
if (options.abortSignal?.aborted) {
|
|
6085
6102
|
throw new DOMException("The operation was aborted", "AbortError");
|
|
6086
6103
|
}
|
|
@@ -6437,7 +6454,7 @@ Current user's request: ${currentInput}`;
|
|
|
6437
6454
|
// All providers failed
|
|
6438
6455
|
const responseTime = Date.now() - startTime;
|
|
6439
6456
|
logger.error(`[${functionTag}] All providers failed`, {
|
|
6440
|
-
triedProviders:
|
|
6457
|
+
triedProviders: providersToTry,
|
|
6441
6458
|
lastError: lastError?.message,
|
|
6442
6459
|
responseTime,
|
|
6443
6460
|
});
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* in the native Codex proxy handler so fallback traffic follows the same pool
|
|
7
7
|
* rules as a native Codex request.
|
|
8
8
|
*/
|
|
9
|
-
import type { ClaudeRequest, CodexFallbackResult, CodexResponsesRequest } from "../types/index.js";
|
|
9
|
+
import type { ClaudeRequest, CodexFallbackResult, CodexReasoningEffort, CodexResponsesRequest } from "../types/index.js";
|
|
10
10
|
export declare class CodexFallbackResponseError extends Error {
|
|
11
11
|
readonly status: number;
|
|
12
12
|
readonly responseBody: string;
|
|
13
13
|
constructor(status: number, responseBody: string);
|
|
14
14
|
}
|
|
15
15
|
/** Convert a Claude Messages request into the ChatGPT Codex Responses shape. */
|
|
16
|
-
export declare function convertClaudeRequestToCodex(body: ClaudeRequest, model: string): CodexResponsesRequest;
|
|
16
|
+
export declare function convertClaudeRequestToCodex(body: ClaudeRequest, model: string, reasoningEffort?: CodexReasoningEffort): CodexResponsesRequest;
|
|
17
17
|
/**
|
|
18
18
|
* Parse a complete Codex Responses SSE stream before emitting Claude output.
|
|
19
19
|
*
|
|
@@ -131,7 +131,7 @@ function convertClaudeMessage(role, content) {
|
|
|
131
131
|
return input;
|
|
132
132
|
}
|
|
133
133
|
/** Convert a Claude Messages request into the ChatGPT Codex Responses shape. */
|
|
134
|
-
export function convertClaudeRequestToCodex(body, model) {
|
|
134
|
+
export function convertClaudeRequestToCodex(body, model, reasoningEffort) {
|
|
135
135
|
const input = body.messages.flatMap((message) => convertClaudeMessage(message.role, message.content));
|
|
136
136
|
const request = {
|
|
137
137
|
model,
|
|
@@ -139,6 +139,9 @@ export function convertClaudeRequestToCodex(body, model) {
|
|
|
139
139
|
stream: true,
|
|
140
140
|
// ChatGPT's backend rejects requests unless this is explicitly false.
|
|
141
141
|
store: false,
|
|
142
|
+
...(reasoningEffort !== undefined
|
|
143
|
+
? { reasoning: { effort: reasoningEffort } }
|
|
144
|
+
: {}),
|
|
142
145
|
};
|
|
143
146
|
const instructions = buildSystemInstructions(body);
|
|
144
147
|
if (instructions) {
|
|
@@ -189,6 +189,18 @@ function applyAccountDefaults(account) {
|
|
|
189
189
|
metadata: account.metadata,
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
|
+
const CODEX_REASONING_EFFORTS = [
|
|
193
|
+
"none",
|
|
194
|
+
"minimal",
|
|
195
|
+
"low",
|
|
196
|
+
"medium",
|
|
197
|
+
"high",
|
|
198
|
+
"xhigh",
|
|
199
|
+
"max",
|
|
200
|
+
];
|
|
201
|
+
function isCodexReasoningEffort(value) {
|
|
202
|
+
return CODEX_REASONING_EFFORTS.some((effort) => effort === value);
|
|
203
|
+
}
|
|
192
204
|
/**
|
|
193
205
|
* Validate the shape of a parsed proxy config.
|
|
194
206
|
* Returns an array of human-readable error strings (empty = valid).
|
|
@@ -215,6 +227,28 @@ export function validateProxyConfig(config) {
|
|
|
215
227
|
}
|
|
216
228
|
if (hasRouting) {
|
|
217
229
|
const routing = cfg.routing;
|
|
230
|
+
const rawFallback = routing["fallback-chain"] ?? routing.fallbackChain;
|
|
231
|
+
if (Array.isArray(rawFallback)) {
|
|
232
|
+
rawFallback.forEach((entry, index) => {
|
|
233
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const fallback = entry;
|
|
237
|
+
const effort = fallback["reasoning-effort"] !== undefined
|
|
238
|
+
? fallback["reasoning-effort"]
|
|
239
|
+
: fallback.reasoningEffort;
|
|
240
|
+
if (effort === undefined) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const field = `routing.fallback-chain[${index}].reasoning-effort`;
|
|
244
|
+
if (!isCodexReasoningEffort(effort)) {
|
|
245
|
+
errors.push(`${field} must be one of: ${CODEX_REASONING_EFFORTS.join(", ")}`);
|
|
246
|
+
}
|
|
247
|
+
if (String(fallback.provider ?? "").trim() !== "codex") {
|
|
248
|
+
errors.push(`${field} is only supported for provider codex`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
218
252
|
const rawAccountAllowlist = routing["account-allowlist"] ?? routing.accountAllowlist;
|
|
219
253
|
if (rawAccountAllowlist !== undefined) {
|
|
220
254
|
if (!Array.isArray(rawAccountAllowlist)) {
|
|
@@ -350,7 +384,7 @@ function warnPlaintextApiKeys(accounts) {
|
|
|
350
384
|
* Extracts:
|
|
351
385
|
* - `strategy` ("round-robin" | "fill-first")
|
|
352
386
|
* - `model-mappings` / `modelMappings` — array of {from, to, provider}
|
|
353
|
-
* - `fallback-chain` / `fallbackChain` — array of {provider, model}
|
|
387
|
+
* - `fallback-chain` / `fallbackChain` — array of {provider, model, reasoningEffort?}
|
|
354
388
|
* - `auto-fallback` / `autoFallback` — opt in to an unspecified provider
|
|
355
389
|
* - `max-inflight-per-account` / `maxInflightPerAccount` — concurrency cap
|
|
356
390
|
* - `passthroughModels` / `passthrough-models` — array of model IDs
|
|
@@ -404,7 +438,16 @@ function parseRoutingConfig(raw) {
|
|
|
404
438
|
logger.warn(`[proxy-config] Skipping fallback entry with empty "provider" or "model": ${JSON.stringify(e)}`);
|
|
405
439
|
return null;
|
|
406
440
|
}
|
|
407
|
-
|
|
441
|
+
const effort = e["reasoning-effort"] !== undefined
|
|
442
|
+
? e["reasoning-effort"]
|
|
443
|
+
: e.reasoningEffort;
|
|
444
|
+
return {
|
|
445
|
+
provider,
|
|
446
|
+
model,
|
|
447
|
+
...(isCodexReasoningEffort(effort)
|
|
448
|
+
? { reasoningEffort: effort }
|
|
449
|
+
: {}),
|
|
450
|
+
};
|
|
408
451
|
})
|
|
409
452
|
.filter((e) => e !== null);
|
|
410
453
|
}
|
|
@@ -35,6 +35,9 @@ export function buildProxyTranslationPlan(primary, fallbackChain, requestedModel
|
|
|
35
35
|
attempts.push({
|
|
36
36
|
provider: fallback.provider,
|
|
37
37
|
model: fallback.model,
|
|
38
|
+
...(fallback.reasoningEffort !== undefined
|
|
39
|
+
? { reasoningEffort: fallback.reasoningEffort }
|
|
40
|
+
: {}),
|
|
38
41
|
label: `${fallback.provider}/${fallback.model}`,
|
|
39
42
|
});
|
|
40
43
|
}
|
|
@@ -3628,7 +3628,7 @@ async function executeClaudeFallbackWithRetry(args) {
|
|
|
3628
3628
|
* output guarantee when Codex returns an incomplete stream.
|
|
3629
3629
|
*/
|
|
3630
3630
|
async function executeClaudeCodexFallback(args) {
|
|
3631
|
-
const { ctx, body, model, tracer, requestStartTime, logProxyBody, logFinalRequest, } = args;
|
|
3631
|
+
const { ctx, body, model, reasoningEffort, tracer, requestStartTime, logProxyBody, logFinalRequest, } = args;
|
|
3632
3632
|
const codexCtx = {
|
|
3633
3633
|
...ctx,
|
|
3634
3634
|
requestId: `${ctx.requestId}:codex-fallback`,
|
|
@@ -3640,7 +3640,7 @@ async function executeClaudeCodexFallback(args) {
|
|
|
3640
3640
|
},
|
|
3641
3641
|
query: {},
|
|
3642
3642
|
params: {},
|
|
3643
|
-
body: convertClaudeRequestToCodex(body, model),
|
|
3643
|
+
body: convertClaudeRequestToCodex(body, model, reasoningEffort),
|
|
3644
3644
|
metadata: { ...ctx.metadata, "neurolink.codexFallback": true },
|
|
3645
3645
|
// Keep the child attribution isolated until its stream has passed
|
|
3646
3646
|
// validation. A failed Codex attempt must not look like a served request.
|
|
@@ -3827,7 +3827,7 @@ async function tryConfiguredClaudeFallbackChain(args) {
|
|
|
3827
3827
|
}
|
|
3828
3828
|
const fallbackStart = Date.now();
|
|
3829
3829
|
try {
|
|
3830
|
-
logger.always(`[proxy] fallback → ${fallback.provider}/${fallback.model}`);
|
|
3830
|
+
logger.always(`[proxy] fallback → ${fallback.provider}/${fallback.model}${fallback.reasoningEffort ? ` reasoning=${fallback.reasoningEffort}` : ""}`);
|
|
3831
3831
|
let response;
|
|
3832
3832
|
if (fallback.provider === "codex") {
|
|
3833
3833
|
// Codex is a local OAuth account pool, not a generic SDK provider.
|
|
@@ -3836,6 +3836,7 @@ async function tryConfiguredClaudeFallbackChain(args) {
|
|
|
3836
3836
|
ctx,
|
|
3837
3837
|
body,
|
|
3838
3838
|
model: fallback.model,
|
|
3839
|
+
reasoningEffort: fallback.reasoningEffort,
|
|
3839
3840
|
tracer,
|
|
3840
3841
|
requestStartTime,
|
|
3841
3842
|
logProxyBody,
|
package/dist/types/codex.d.ts
CHANGED
|
@@ -138,12 +138,17 @@ export type CodexResponsesInputItem = {
|
|
|
138
138
|
call_id: string;
|
|
139
139
|
output: string;
|
|
140
140
|
};
|
|
141
|
+
/** Codex reasoning settings; supported levels depend on the selected model. */
|
|
142
|
+
export type CodexReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
141
143
|
/** Request shape used to bridge Anthropic Messages traffic to Codex Responses. */
|
|
142
144
|
export type CodexResponsesRequest = {
|
|
143
145
|
model: string;
|
|
144
146
|
input: CodexResponsesInputItem[];
|
|
145
147
|
stream: true;
|
|
146
148
|
store: false;
|
|
149
|
+
reasoning?: {
|
|
150
|
+
effort: CodexReasoningEffort;
|
|
151
|
+
};
|
|
147
152
|
instructions?: string;
|
|
148
153
|
tools?: Array<{
|
|
149
154
|
type: "function";
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -459,6 +459,19 @@ export type GenerateOptions = {
|
|
|
459
459
|
skipToolPromptInjection?: boolean;
|
|
460
460
|
/** Disable tool result caching for this request (overrides global mcp.cache.enabled) */
|
|
461
461
|
disableToolCache?: boolean;
|
|
462
|
+
/**
|
|
463
|
+
* Disable NeuroLink's internal fallback for this request: the static
|
|
464
|
+
* provider-priority walk that runs when no provider was requested, and the
|
|
465
|
+
* catalog model-fallback walk a provider performs when its model is
|
|
466
|
+
* rejected as invalid. Callers that own fallback order (a caller-supplied
|
|
467
|
+
* `providerFallback` / `modelChain`, or a router that retries on its own,
|
|
468
|
+
* as the Claude proxy does for its streams) set this so an invalid model
|
|
469
|
+
* or an unavailable provider surfaces as exactly that.
|
|
470
|
+
* A configured `ModelPool`, `providerFallback` and `modelChain` are the
|
|
471
|
+
* caller's own fallback and are unaffected. Mirrors the same flag on
|
|
472
|
+
* `StreamOptions`.
|
|
473
|
+
*/
|
|
474
|
+
disableInternalFallback?: boolean;
|
|
462
475
|
/** Maximum number of tool execution steps (default: 200) */
|
|
463
476
|
maxSteps?: number;
|
|
464
477
|
/**
|
|
@@ -1204,6 +1217,14 @@ export type TextGenerationOptions = {
|
|
|
1204
1217
|
excludeTools?: string[];
|
|
1205
1218
|
/** Disable tool result caching for this request (overrides global mcp.cache.enabled) */
|
|
1206
1219
|
disableToolCache?: boolean;
|
|
1220
|
+
/**
|
|
1221
|
+
* Caller owns fallback order. Read in two places: `directProviderGeneration`
|
|
1222
|
+
* bounds its static provider-priority walk to one candidate, and
|
|
1223
|
+
* `BaseProvider.generate()` skips the catalog model-fallback walk so an
|
|
1224
|
+
* invalid-model error surfaces as itself. Mapped from
|
|
1225
|
+
* `GenerateOptions.disableInternalFallback`.
|
|
1226
|
+
*/
|
|
1227
|
+
disableInternalFallback?: boolean;
|
|
1207
1228
|
/**
|
|
1208
1229
|
* Tool choice configuration for the generation.
|
|
1209
1230
|
* Controls whether and which tools the model must call.
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -1392,6 +1392,7 @@ export type ClaudeProxyModelTier = "opus" | "sonnet" | "haiku" | "other";
|
|
|
1392
1392
|
export type ProxyTranslationAttempt = {
|
|
1393
1393
|
provider?: string;
|
|
1394
1394
|
model?: string;
|
|
1395
|
+
reasoningEffort?: FallbackEntry["reasoningEffort"];
|
|
1395
1396
|
label: string;
|
|
1396
1397
|
};
|
|
1397
1398
|
/** Ordered plan of provider attempts for a proxy request. */
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* and usage tracking for Anthropic API access.
|
|
6
6
|
*/
|
|
7
7
|
import type { StoredOAuthTokens } from "./auth.js";
|
|
8
|
+
import type { CodexReasoningEffort } from "./codex.js";
|
|
8
9
|
export type { StoredOAuthTokens, TokenRefresher, TokenStorageData, StoredProviderTokens, } from "./auth.js";
|
|
9
10
|
/**
|
|
10
11
|
* Claude subscription tier levels
|
|
@@ -979,6 +980,8 @@ export type ModelMapping = {
|
|
|
979
980
|
export type FallbackEntry = {
|
|
980
981
|
provider: string;
|
|
981
982
|
model: string;
|
|
983
|
+
/** Explicit Codex fallback effort. Omit to use the upstream default. */
|
|
984
|
+
reasoningEffort?: CodexReasoningEffort;
|
|
982
985
|
};
|
|
983
986
|
/** Full proxy routing config */
|
|
984
987
|
export type ProxyRoutingConfig = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.12.
|
|
3
|
+
"version": "12.12.4",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|