@juspay/neurolink 10.8.16 → 10.8.18

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.
@@ -99,7 +99,7 @@ import { getWorkflow } from "./workflow/core/workflowRegistry.js";
99
99
  import { runWorkflow } from "./workflow/core/workflowRunner.js";
100
100
  import { ModelPool, classifyProviderError } from "./routing/index.js";
101
101
  import { ClassifierRouter } from "./routing/classifierRouter.js";
102
- import { looksLikeModelAccessDenied as sharedLooksLikeModelAccessDenied, isNonRetryableProviderError as sharedIsNonRetryableProviderError, } from "./utils/providerErrorClassification.js";
102
+ import { looksLikeModelAccessDenied as sharedLooksLikeModelAccessDenied, isNonRetryableProviderError as sharedIsNonRetryableProviderError, isNonRetryableForPool as sharedIsNonRetryableForPool, } from "./utils/providerErrorClassification.js";
103
103
  import { getErrorStatusCode } from "./utils/providerRetry.js";
104
104
  import { detectAndRedactPII } from "./utils/piiDetector.js";
105
105
  import { validateResponse } from "./utils/responseValidator.js";
@@ -173,6 +173,12 @@ function mcpCategoryToErrorCategory(mcpCategory) {
173
173
  */
174
174
  const looksLikeModelAccessDenied = sharedLooksLikeModelAccessDenied;
175
175
  const isNonRetryableProviderError = sharedIsNonRetryableProviderError;
176
+ /**
177
+ * Pool-scoped variant: model-not-found is a fact about one member's model, not
178
+ * about the request, so it must not stop the whole pool. Used only at the two
179
+ * ModelPool catch sites; every other path keeps the general contract.
180
+ */
181
+ const isNonRetryableForPool = sharedIsNonRetryableForPool;
176
182
  /**
177
183
  * NeuroLink - Universal AI Development Platform
178
184
  *
@@ -5829,7 +5835,7 @@ Current user's request: ${currentInput}`;
5829
5835
  if (isAbortError(poolError)) {
5830
5836
  throw poolError;
5831
5837
  }
5832
- if (isNonRetryableProviderError(poolError)) {
5838
+ if (isNonRetryableForPool(poolError)) {
5833
5839
  const poolErrMsg = poolError instanceof Error
5834
5840
  ? poolError.message
5835
5841
  : String(poolError);
@@ -8367,7 +8373,7 @@ Current user's request: ${currentInput}`;
8367
8373
  if (isAbortError(poolStreamError)) {
8368
8374
  throw poolStreamError;
8369
8375
  }
8370
- if (isNonRetryableProviderError(poolStreamError)) {
8376
+ if (isNonRetryableForPool(poolStreamError)) {
8371
8377
  const poolStreamErrMsg = poolStreamError instanceof Error
8372
8378
  ? poolStreamError.message
8373
8379
  : String(poolStreamError);
@@ -12,6 +12,18 @@
12
12
  * plus typed-error name/code markers when the full typed class is not present.
13
13
  */
14
14
  export declare function looksLikeModelAccessDenied(error: unknown): boolean;
15
+ /**
16
+ * Detects "the provider does not have this model": HTTP 404 model lookups,
17
+ * Google's NOT_FOUND, Anthropic's `not_found_error` naming the model, and the
18
+ * typed InvalidModelError providers raise for an unrecognised model id.
19
+ *
20
+ * Deliberately narrow. A 404 alone is not enough — only a 404 that names a
21
+ * model qualifies, so an unrelated missing endpoint stays non-retryable.
22
+ * Auth-flavoured messages are excluded outright: PERMISSION_DENIED means the
23
+ * key is not entitled to the model, which every member sharing that key would
24
+ * hit identically.
25
+ */
26
+ export declare function looksLikeModelNotFound(error: unknown): boolean;
15
27
  /**
16
28
  * Returns true when the error is definitively non-retryable: typed error
17
29
  * classes (auth, access-denied, invalid-model), non-retryable HTTP status
@@ -20,5 +32,27 @@ export declare function looksLikeModelAccessDenied(error: unknown): boolean;
20
32
  * NOTE: ContextBudgetExceededError is intentionally NOT non-retryable —
21
33
  * each provider has its own context window, so a budget rejection on one
22
34
  * provider does not preclude another provider accepting the same payload.
35
+ *
36
+ * NOTE: model-not-found IS non-retryable here, and intentionally so. This is
37
+ * the general contract, used by provider fallback where the same model id
38
+ * typically carries across providers — retrying a typo everywhere would turn
39
+ * one crisp error into a slow fan-out of identical ones. ModelPool is the
40
+ * exception and uses {@link isNonRetryableForPool} instead.
23
41
  */
24
42
  export declare function isNonRetryableProviderError(error: unknown): boolean;
43
+ /**
44
+ * Non-retryability as ModelPool sees it.
45
+ *
46
+ * A pool member is an explicit {provider, model} pair, and members are chosen
47
+ * precisely because they differ. "This provider has no such model" is therefore
48
+ * a fact about ONE member, not about the request: member#2 runs a different
49
+ * model and may well serve it. Short-circuiting the whole pool on it defeats
50
+ * the point of configuring a pool — the observed case was a member naming a
51
+ * retired model, where Anthropic answered 404 and the pool gave up instead of
52
+ * failing over to the member that was fine.
53
+ *
54
+ * Everything else keeps the general contract. Auth failures, malformed
55
+ * payloads, and access-denied still stop the pool immediately, because those
56
+ * describe the credentials or the request and every member would hit them.
57
+ */
58
+ export declare function isNonRetryableForPool(error: unknown): boolean;
@@ -37,6 +37,39 @@ export function looksLikeModelAccessDenied(error) {
37
37
  lower.includes("team can only access") ||
38
38
  /not\s+allowed\s+to\s+access\s+(this\s+)?model/i.test(msg));
39
39
  }
40
+ /**
41
+ * Detects "the provider does not have this model": HTTP 404 model lookups,
42
+ * Google's NOT_FOUND, Anthropic's `not_found_error` naming the model, and the
43
+ * typed InvalidModelError providers raise for an unrecognised model id.
44
+ *
45
+ * Deliberately narrow. A 404 alone is not enough — only a 404 that names a
46
+ * model qualifies, so an unrelated missing endpoint stays non-retryable.
47
+ * Auth-flavoured messages are excluded outright: PERMISSION_DENIED means the
48
+ * key is not entitled to the model, which every member sharing that key would
49
+ * hit identically.
50
+ */
51
+ export function looksLikeModelNotFound(error) {
52
+ if (error instanceof InvalidModelError) {
53
+ return true;
54
+ }
55
+ if (!(error instanceof Error)) {
56
+ return false;
57
+ }
58
+ const msg = error.message;
59
+ if (msg.includes("PERMISSION_DENIED") ||
60
+ msg.includes("UNAUTHENTICATED") ||
61
+ looksLikeModelAccessDenied(error)) {
62
+ return false;
63
+ }
64
+ const lower = msg.toLowerCase();
65
+ const namesAModel = lower.includes("model");
66
+ return ((lower.includes("model not found") ||
67
+ lower.includes("not_found_error") ||
68
+ msg.includes("NOT_FOUND") ||
69
+ lower.includes("does not exist") ||
70
+ lower.includes("unknown model")) &&
71
+ namesAModel);
72
+ }
40
73
  /**
41
74
  * Returns true when the error is definitively non-retryable: typed error
42
75
  * classes (auth, access-denied, invalid-model), non-retryable HTTP status
@@ -45,6 +78,12 @@ export function looksLikeModelAccessDenied(error) {
45
78
  * NOTE: ContextBudgetExceededError is intentionally NOT non-retryable —
46
79
  * each provider has its own context window, so a budget rejection on one
47
80
  * provider does not preclude another provider accepting the same payload.
81
+ *
82
+ * NOTE: model-not-found IS non-retryable here, and intentionally so. This is
83
+ * the general contract, used by provider fallback where the same model id
84
+ * typically carries across providers — retrying a typo everywhere would turn
85
+ * one crisp error into a slow fan-out of identical ones. ModelPool is the
86
+ * exception and uses {@link isNonRetryableForPool} instead.
48
87
  */
49
88
  export function isNonRetryableProviderError(error) {
50
89
  if (error instanceof InvalidModelError) {
@@ -86,4 +125,25 @@ export function isNonRetryableProviderError(error) {
86
125
  }
87
126
  return false;
88
127
  }
128
+ /**
129
+ * Non-retryability as ModelPool sees it.
130
+ *
131
+ * A pool member is an explicit {provider, model} pair, and members are chosen
132
+ * precisely because they differ. "This provider has no such model" is therefore
133
+ * a fact about ONE member, not about the request: member#2 runs a different
134
+ * model and may well serve it. Short-circuiting the whole pool on it defeats
135
+ * the point of configuring a pool — the observed case was a member naming a
136
+ * retired model, where Anthropic answered 404 and the pool gave up instead of
137
+ * failing over to the member that was fine.
138
+ *
139
+ * Everything else keeps the general contract. Auth failures, malformed
140
+ * payloads, and access-denied still stop the pool immediately, because those
141
+ * describe the credentials or the request and every member would hit them.
142
+ */
143
+ export function isNonRetryableForPool(error) {
144
+ if (looksLikeModelNotFound(error)) {
145
+ return false;
146
+ }
147
+ return isNonRetryableProviderError(error);
148
+ }
89
149
  //# sourceMappingURL=providerErrorClassification.js.map
package/dist/neurolink.js CHANGED
@@ -99,7 +99,7 @@ import { getWorkflow } from "./workflow/core/workflowRegistry.js";
99
99
  import { runWorkflow } from "./workflow/core/workflowRunner.js";
100
100
  import { ModelPool, classifyProviderError } from "./routing/index.js";
101
101
  import { ClassifierRouter } from "./routing/classifierRouter.js";
102
- import { looksLikeModelAccessDenied as sharedLooksLikeModelAccessDenied, isNonRetryableProviderError as sharedIsNonRetryableProviderError, } from "./utils/providerErrorClassification.js";
102
+ import { looksLikeModelAccessDenied as sharedLooksLikeModelAccessDenied, isNonRetryableProviderError as sharedIsNonRetryableProviderError, isNonRetryableForPool as sharedIsNonRetryableForPool, } from "./utils/providerErrorClassification.js";
103
103
  import { getErrorStatusCode } from "./utils/providerRetry.js";
104
104
  import { detectAndRedactPII } from "./utils/piiDetector.js";
105
105
  import { validateResponse } from "./utils/responseValidator.js";
@@ -173,6 +173,12 @@ function mcpCategoryToErrorCategory(mcpCategory) {
173
173
  */
174
174
  const looksLikeModelAccessDenied = sharedLooksLikeModelAccessDenied;
175
175
  const isNonRetryableProviderError = sharedIsNonRetryableProviderError;
176
+ /**
177
+ * Pool-scoped variant: model-not-found is a fact about one member's model, not
178
+ * about the request, so it must not stop the whole pool. Used only at the two
179
+ * ModelPool catch sites; every other path keeps the general contract.
180
+ */
181
+ const isNonRetryableForPool = sharedIsNonRetryableForPool;
176
182
  /**
177
183
  * NeuroLink - Universal AI Development Platform
178
184
  *
@@ -5829,7 +5835,7 @@ Current user's request: ${currentInput}`;
5829
5835
  if (isAbortError(poolError)) {
5830
5836
  throw poolError;
5831
5837
  }
5832
- if (isNonRetryableProviderError(poolError)) {
5838
+ if (isNonRetryableForPool(poolError)) {
5833
5839
  const poolErrMsg = poolError instanceof Error
5834
5840
  ? poolError.message
5835
5841
  : String(poolError);
@@ -8367,7 +8373,7 @@ Current user's request: ${currentInput}`;
8367
8373
  if (isAbortError(poolStreamError)) {
8368
8374
  throw poolStreamError;
8369
8375
  }
8370
- if (isNonRetryableProviderError(poolStreamError)) {
8376
+ if (isNonRetryableForPool(poolStreamError)) {
8371
8377
  const poolStreamErrMsg = poolStreamError instanceof Error
8372
8378
  ? poolStreamError.message
8373
8379
  : String(poolStreamError);
@@ -12,6 +12,18 @@
12
12
  * plus typed-error name/code markers when the full typed class is not present.
13
13
  */
14
14
  export declare function looksLikeModelAccessDenied(error: unknown): boolean;
15
+ /**
16
+ * Detects "the provider does not have this model": HTTP 404 model lookups,
17
+ * Google's NOT_FOUND, Anthropic's `not_found_error` naming the model, and the
18
+ * typed InvalidModelError providers raise for an unrecognised model id.
19
+ *
20
+ * Deliberately narrow. A 404 alone is not enough — only a 404 that names a
21
+ * model qualifies, so an unrelated missing endpoint stays non-retryable.
22
+ * Auth-flavoured messages are excluded outright: PERMISSION_DENIED means the
23
+ * key is not entitled to the model, which every member sharing that key would
24
+ * hit identically.
25
+ */
26
+ export declare function looksLikeModelNotFound(error: unknown): boolean;
15
27
  /**
16
28
  * Returns true when the error is definitively non-retryable: typed error
17
29
  * classes (auth, access-denied, invalid-model), non-retryable HTTP status
@@ -20,5 +32,27 @@ export declare function looksLikeModelAccessDenied(error: unknown): boolean;
20
32
  * NOTE: ContextBudgetExceededError is intentionally NOT non-retryable —
21
33
  * each provider has its own context window, so a budget rejection on one
22
34
  * provider does not preclude another provider accepting the same payload.
35
+ *
36
+ * NOTE: model-not-found IS non-retryable here, and intentionally so. This is
37
+ * the general contract, used by provider fallback where the same model id
38
+ * typically carries across providers — retrying a typo everywhere would turn
39
+ * one crisp error into a slow fan-out of identical ones. ModelPool is the
40
+ * exception and uses {@link isNonRetryableForPool} instead.
23
41
  */
24
42
  export declare function isNonRetryableProviderError(error: unknown): boolean;
43
+ /**
44
+ * Non-retryability as ModelPool sees it.
45
+ *
46
+ * A pool member is an explicit {provider, model} pair, and members are chosen
47
+ * precisely because they differ. "This provider has no such model" is therefore
48
+ * a fact about ONE member, not about the request: member#2 runs a different
49
+ * model and may well serve it. Short-circuiting the whole pool on it defeats
50
+ * the point of configuring a pool — the observed case was a member naming a
51
+ * retired model, where Anthropic answered 404 and the pool gave up instead of
52
+ * failing over to the member that was fine.
53
+ *
54
+ * Everything else keeps the general contract. Auth failures, malformed
55
+ * payloads, and access-denied still stop the pool immediately, because those
56
+ * describe the credentials or the request and every member would hit them.
57
+ */
58
+ export declare function isNonRetryableForPool(error: unknown): boolean;
@@ -37,6 +37,39 @@ export function looksLikeModelAccessDenied(error) {
37
37
  lower.includes("team can only access") ||
38
38
  /not\s+allowed\s+to\s+access\s+(this\s+)?model/i.test(msg));
39
39
  }
40
+ /**
41
+ * Detects "the provider does not have this model": HTTP 404 model lookups,
42
+ * Google's NOT_FOUND, Anthropic's `not_found_error` naming the model, and the
43
+ * typed InvalidModelError providers raise for an unrecognised model id.
44
+ *
45
+ * Deliberately narrow. A 404 alone is not enough — only a 404 that names a
46
+ * model qualifies, so an unrelated missing endpoint stays non-retryable.
47
+ * Auth-flavoured messages are excluded outright: PERMISSION_DENIED means the
48
+ * key is not entitled to the model, which every member sharing that key would
49
+ * hit identically.
50
+ */
51
+ export function looksLikeModelNotFound(error) {
52
+ if (error instanceof InvalidModelError) {
53
+ return true;
54
+ }
55
+ if (!(error instanceof Error)) {
56
+ return false;
57
+ }
58
+ const msg = error.message;
59
+ if (msg.includes("PERMISSION_DENIED") ||
60
+ msg.includes("UNAUTHENTICATED") ||
61
+ looksLikeModelAccessDenied(error)) {
62
+ return false;
63
+ }
64
+ const lower = msg.toLowerCase();
65
+ const namesAModel = lower.includes("model");
66
+ return ((lower.includes("model not found") ||
67
+ lower.includes("not_found_error") ||
68
+ msg.includes("NOT_FOUND") ||
69
+ lower.includes("does not exist") ||
70
+ lower.includes("unknown model")) &&
71
+ namesAModel);
72
+ }
40
73
  /**
41
74
  * Returns true when the error is definitively non-retryable: typed error
42
75
  * classes (auth, access-denied, invalid-model), non-retryable HTTP status
@@ -45,6 +78,12 @@ export function looksLikeModelAccessDenied(error) {
45
78
  * NOTE: ContextBudgetExceededError is intentionally NOT non-retryable —
46
79
  * each provider has its own context window, so a budget rejection on one
47
80
  * provider does not preclude another provider accepting the same payload.
81
+ *
82
+ * NOTE: model-not-found IS non-retryable here, and intentionally so. This is
83
+ * the general contract, used by provider fallback where the same model id
84
+ * typically carries across providers — retrying a typo everywhere would turn
85
+ * one crisp error into a slow fan-out of identical ones. ModelPool is the
86
+ * exception and uses {@link isNonRetryableForPool} instead.
48
87
  */
49
88
  export function isNonRetryableProviderError(error) {
50
89
  if (error instanceof InvalidModelError) {
@@ -86,3 +125,24 @@ export function isNonRetryableProviderError(error) {
86
125
  }
87
126
  return false;
88
127
  }
128
+ /**
129
+ * Non-retryability as ModelPool sees it.
130
+ *
131
+ * A pool member is an explicit {provider, model} pair, and members are chosen
132
+ * precisely because they differ. "This provider has no such model" is therefore
133
+ * a fact about ONE member, not about the request: member#2 runs a different
134
+ * model and may well serve it. Short-circuiting the whole pool on it defeats
135
+ * the point of configuring a pool — the observed case was a member naming a
136
+ * retired model, where Anthropic answered 404 and the pool gave up instead of
137
+ * failing over to the member that was fine.
138
+ *
139
+ * Everything else keeps the general contract. Auth failures, malformed
140
+ * payloads, and access-denied still stop the pool immediately, because those
141
+ * describe the credentials or the request and every member would hit them.
142
+ */
143
+ export function isNonRetryableForPool(error) {
144
+ if (looksLikeModelNotFound(error)) {
145
+ return false;
146
+ }
147
+ return isNonRetryableProviderError(error);
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.8.16",
3
+ "version": "10.8.18",
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": {
@@ -141,6 +141,7 @@
141
141
  "test:tool-routing-cli": "npx tsx test/continuous-test-suite-tool-routing-flags.ts && npx tsx test/continuous-test-suite-tool-routing-cli.ts",
142
142
  "test:tool-dedup": "npx tsx test/continuous-test-suite-tool-dedup.ts",
143
143
  "test:model-pool": "npx tsx test/continuous-test-suite-model-pool.ts",
144
+ "test:model-not-found-retryable": "npx tsx test/continuous-test-suite-model-not-found-retryable.ts",
144
145
  "test:model-capabilities": "npx tsx test/continuous-test-suite-model-capabilities.ts",
145
146
  "test:agent-runtime:vitest": "pnpm exec vitest run test/agentRuntime.test.ts test/agentDelegation.test.ts test/agentPlumbing.test.ts test/toolExecutionRecorder.test.ts",
146
147
  "test:retry-after:vitest": "pnpm exec vitest run test/retryAfter.test.ts",
@@ -151,7 +152,7 @@
151
152
  "test:system-messages": "npx tsx test/continuous-test-suite-system-messages.ts",
152
153
  "test:test-stubs": "npx tsx test/continuous-test-suite-test-stubs.ts",
153
154
  "test:tool-routing-semantic": "npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
154
- "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context:vitest && pnpm run test:step-budget-guard:vitest && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs",
155
+ "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context && pnpm run test:dedup-execute-map && pnpm run test:step-budget-guard:vitest && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs && pnpm run test:model-not-found-retryable",
155
156
  "// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
156
157
  "test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
157
158
  "// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",
@@ -213,7 +214,8 @@
213
214
  "pre-commit": "lint-staged",
214
215
  "pre-push": "pnpm run validate:commit && pnpm run validate:env && pnpm run validate && pnpm run test:ci",
215
216
  "check:all": "pnpm run lint && pnpm run format --check && pnpm run validate && pnpm run validate:commit",
216
- "test:litellm-context:vitest": "pnpm exec vitest run test/litellmContextWindows.test.ts",
217
+ "test:litellm-context": "npx tsx test/continuous-test-suite-litellm-context-windows.ts",
218
+ "test:dedup-execute-map": "npx tsx test/continuous-test-suite-dedup-execute-map.ts",
217
219
  "test:step-budget-guard:vitest": "pnpm exec vitest run test/stepBudgetGuard.test.ts",
218
220
  "test:audio": "npx tsx test/continuous-test-suite-audio.ts",
219
221
  "test:office": "npx tsx test/continuous-test-suite-office.ts",