@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.
@@ -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
- // TextGenerationOptions does not declare the flag (it lives on
1303
- // StreamOptions), but callers that own fallback order pass it on both
1304
- // paths, so honour it here as well.
1305
- const callerOwnsFallback = "disableInternalFallback" in options &&
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: !requestedProvider || !!preferredOrchestrated,
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 tryProviders) {
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: tryProviders,
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
- return { provider, model };
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,
@@ -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";
@@ -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.
@@ -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.2",
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": {