@juspay/neurolink 10.5.0 → 10.5.2
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 +12 -0
- package/dist/browser/neurolink.min.js +348 -348
- package/dist/core/baseProvider.d.ts +6 -1
- package/dist/core/baseProvider.js +23 -5
- package/dist/lib/core/baseProvider.d.ts +6 -1
- package/dist/lib/core/baseProvider.js +23 -5
- package/dist/lib/models/modelRegistry.d.ts +9 -1
- package/dist/lib/models/modelRegistry.js +18 -2
- package/dist/lib/neurolink.js +3 -2
- package/dist/lib/providers/openaiChatCompletionsBase.d.ts +0 -1
- package/dist/lib/providers/openaiChatCompletionsBase.js +0 -3
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +11 -0
- package/dist/lib/server/routes/claudeProxyRoutes.js +55 -0
- package/dist/models/modelRegistry.d.ts +9 -1
- package/dist/models/modelRegistry.js +18 -2
- package/dist/neurolink.js +3 -2
- package/dist/providers/openaiChatCompletionsBase.d.ts +0 -1
- package/dist/providers/openaiChatCompletionsBase.js +0 -3
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/server/routes/claudeProxyRoutes.d.ts +11 -0
- package/dist/server/routes/claudeProxyRoutes.js +55 -0
- package/package.json +3 -2
|
@@ -43,9 +43,14 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
43
43
|
/**
|
|
44
44
|
* Check if this provider supports tool/function calling
|
|
45
45
|
* Override in subclasses to disable tools for specific providers or models
|
|
46
|
-
* @returns
|
|
46
|
+
* @returns the current model's registered capability, or true when unknown
|
|
47
47
|
*/
|
|
48
48
|
supportsTools(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Apply the shared tool gate and optionally report registry-backed
|
|
51
|
+
* suppression at the request entry point.
|
|
52
|
+
*/
|
|
53
|
+
private shouldUseTools;
|
|
49
54
|
/**
|
|
50
55
|
* Primary streaming method - implements AIProvider interface
|
|
51
56
|
* When tools are involved, falls back to generate() with synthetic streaming
|
|
@@ -2,6 +2,7 @@ import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
|
2
2
|
import { directAgentTools } from "../agent/directTools.js";
|
|
3
3
|
import { IMAGE_GENERATION_MODELS } from "../core/constants.js";
|
|
4
4
|
import { MiddlewareFactory } from "../middleware/factory.js";
|
|
5
|
+
import { modelSupports } from "../models/modelRegistry.js";
|
|
5
6
|
import { ATTR, tracers } from "../telemetry/index.js";
|
|
6
7
|
import { isAbortError } from "../utils/errorHandling.js";
|
|
7
8
|
import { hasLifecycleErrorFired, markLifecycleErrorFired, } from "../utils/lifecycleCallbacks.js";
|
|
@@ -105,10 +106,27 @@ export class BaseProvider {
|
|
|
105
106
|
/**
|
|
106
107
|
* Check if this provider supports tool/function calling
|
|
107
108
|
* Override in subclasses to disable tools for specific providers or models
|
|
108
|
-
* @returns
|
|
109
|
+
* @returns the current model's registered capability, or true when unknown
|
|
109
110
|
*/
|
|
110
111
|
supportsTools() {
|
|
111
|
-
return
|
|
112
|
+
return modelSupports("functionCalling", this.providerName, this.modelName);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Apply the shared tool gate and optionally report registry-backed
|
|
116
|
+
* suppression at the request entry point.
|
|
117
|
+
*/
|
|
118
|
+
shouldUseTools(options, warnWhenUnsupported = false) {
|
|
119
|
+
if (options.disableTools) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const supportsTools = this.supportsTools();
|
|
123
|
+
if (!supportsTools && warnWhenUnsupported) {
|
|
124
|
+
logger.warn(`Tools disabled for ${this.providerName}/${this.modelName} because the model does not support function calling`, {
|
|
125
|
+
provider: this.providerName,
|
|
126
|
+
model: this.modelName,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return supportsTools;
|
|
112
130
|
}
|
|
113
131
|
// ===================
|
|
114
132
|
// PUBLIC API METHODS
|
|
@@ -178,7 +196,7 @@ export class BaseProvider {
|
|
|
178
196
|
// tools (e.g. RAG tools) into options.tools. This way, every provider's
|
|
179
197
|
// executeStream() can simply use options.tools (or getAllTools() + options.tools)
|
|
180
198
|
// and get the complete tool set without needing per-provider merge logic.
|
|
181
|
-
if (
|
|
199
|
+
if (this.shouldUseTools(options, true)) {
|
|
182
200
|
const mergedTools = await this.getToolsForStream(options);
|
|
183
201
|
options = { ...options, tools: mergedTools };
|
|
184
202
|
}
|
|
@@ -731,7 +749,7 @@ export class BaseProvider {
|
|
|
731
749
|
* Prepare generation context including tools and model
|
|
732
750
|
*/
|
|
733
751
|
async prepareGenerationContext(options) {
|
|
734
|
-
const shouldUseTools =
|
|
752
|
+
const shouldUseTools = this.shouldUseTools(options, true);
|
|
735
753
|
const baseTools = shouldUseTools ? await this.getAllTools() : {};
|
|
736
754
|
let tools = shouldUseTools
|
|
737
755
|
? {
|
|
@@ -765,7 +783,7 @@ export class BaseProvider {
|
|
|
765
783
|
* All providers should call this instead of getAllTools() directly.
|
|
766
784
|
*/
|
|
767
785
|
async getToolsForStream(options) {
|
|
768
|
-
const shouldUseTools =
|
|
786
|
+
const shouldUseTools = this.shouldUseTools(options);
|
|
769
787
|
if (!shouldUseTools) {
|
|
770
788
|
return {};
|
|
771
789
|
}
|
|
@@ -43,9 +43,14 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
43
43
|
/**
|
|
44
44
|
* Check if this provider supports tool/function calling
|
|
45
45
|
* Override in subclasses to disable tools for specific providers or models
|
|
46
|
-
* @returns
|
|
46
|
+
* @returns the current model's registered capability, or true when unknown
|
|
47
47
|
*/
|
|
48
48
|
supportsTools(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Apply the shared tool gate and optionally report registry-backed
|
|
51
|
+
* suppression at the request entry point.
|
|
52
|
+
*/
|
|
53
|
+
private shouldUseTools;
|
|
49
54
|
/**
|
|
50
55
|
* Primary streaming method - implements AIProvider interface
|
|
51
56
|
* When tools are involved, falls back to generate() with synthetic streaming
|
|
@@ -2,6 +2,7 @@ import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
|
2
2
|
import { directAgentTools } from "../agent/directTools.js";
|
|
3
3
|
import { IMAGE_GENERATION_MODELS } from "../core/constants.js";
|
|
4
4
|
import { MiddlewareFactory } from "../middleware/factory.js";
|
|
5
|
+
import { modelSupports } from "../models/modelRegistry.js";
|
|
5
6
|
import { ATTR, tracers } from "../telemetry/index.js";
|
|
6
7
|
import { isAbortError } from "../utils/errorHandling.js";
|
|
7
8
|
import { hasLifecycleErrorFired, markLifecycleErrorFired, } from "../utils/lifecycleCallbacks.js";
|
|
@@ -105,10 +106,27 @@ export class BaseProvider {
|
|
|
105
106
|
/**
|
|
106
107
|
* Check if this provider supports tool/function calling
|
|
107
108
|
* Override in subclasses to disable tools for specific providers or models
|
|
108
|
-
* @returns
|
|
109
|
+
* @returns the current model's registered capability, or true when unknown
|
|
109
110
|
*/
|
|
110
111
|
supportsTools() {
|
|
111
|
-
return
|
|
112
|
+
return modelSupports("functionCalling", this.providerName, this.modelName);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Apply the shared tool gate and optionally report registry-backed
|
|
116
|
+
* suppression at the request entry point.
|
|
117
|
+
*/
|
|
118
|
+
shouldUseTools(options, warnWhenUnsupported = false) {
|
|
119
|
+
if (options.disableTools) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const supportsTools = this.supportsTools();
|
|
123
|
+
if (!supportsTools && warnWhenUnsupported) {
|
|
124
|
+
logger.warn(`Tools disabled for ${this.providerName}/${this.modelName} because the model does not support function calling`, {
|
|
125
|
+
provider: this.providerName,
|
|
126
|
+
model: this.modelName,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return supportsTools;
|
|
112
130
|
}
|
|
113
131
|
// ===================
|
|
114
132
|
// PUBLIC API METHODS
|
|
@@ -178,7 +196,7 @@ export class BaseProvider {
|
|
|
178
196
|
// tools (e.g. RAG tools) into options.tools. This way, every provider's
|
|
179
197
|
// executeStream() can simply use options.tools (or getAllTools() + options.tools)
|
|
180
198
|
// and get the complete tool set without needing per-provider merge logic.
|
|
181
|
-
if (
|
|
199
|
+
if (this.shouldUseTools(options, true)) {
|
|
182
200
|
const mergedTools = await this.getToolsForStream(options);
|
|
183
201
|
options = { ...options, tools: mergedTools };
|
|
184
202
|
}
|
|
@@ -731,7 +749,7 @@ export class BaseProvider {
|
|
|
731
749
|
* Prepare generation context including tools and model
|
|
732
750
|
*/
|
|
733
751
|
async prepareGenerationContext(options) {
|
|
734
|
-
const shouldUseTools =
|
|
752
|
+
const shouldUseTools = this.shouldUseTools(options, true);
|
|
735
753
|
const baseTools = shouldUseTools ? await this.getAllTools() : {};
|
|
736
754
|
let tools = shouldUseTools
|
|
737
755
|
? {
|
|
@@ -765,7 +783,7 @@ export class BaseProvider {
|
|
|
765
783
|
* All providers should call this instead of getAllTools() directly.
|
|
766
784
|
*/
|
|
767
785
|
async getToolsForStream(options) {
|
|
768
|
-
const shouldUseTools =
|
|
786
|
+
const shouldUseTools = this.shouldUseTools(options);
|
|
769
787
|
if (!shouldUseTools) {
|
|
770
788
|
return {};
|
|
771
789
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Part of Phase 4.1 - Models Command System
|
|
5
5
|
*/
|
|
6
6
|
import { AIProviderName } from "../constants/enums.js";
|
|
7
|
-
import type { JsonValue, ModelInfo } from "../types/index.js";
|
|
7
|
+
import type { JsonValue, ModelCapabilities, ModelInfo } from "../types/index.js";
|
|
8
8
|
/**
|
|
9
9
|
* Comprehensive model registry
|
|
10
10
|
*/
|
|
@@ -25,6 +25,14 @@ export declare function getAllModels(): ModelInfo[];
|
|
|
25
25
|
* Get model by ID
|
|
26
26
|
*/
|
|
27
27
|
export declare function getModelById(id: string): ModelInfo | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Check whether a model supports a registered capability.
|
|
30
|
+
*
|
|
31
|
+
* Aliases resolve to their canonical model ID before the provider is
|
|
32
|
+
* cross-checked. Unknown provider/model pairs default to supported so new or
|
|
33
|
+
* custom models retain the existing behavior until capability data is added.
|
|
34
|
+
*/
|
|
35
|
+
export declare function modelSupports(capability: keyof ModelCapabilities, provider: string, model: string): boolean;
|
|
28
36
|
/**
|
|
29
37
|
* Get models by provider
|
|
30
38
|
*/
|
|
@@ -726,7 +726,7 @@ export const MODEL_REGISTRY = {
|
|
|
726
726
|
description: "Preview version of O1 reasoning model",
|
|
727
727
|
capabilities: {
|
|
728
728
|
vision: false,
|
|
729
|
-
functionCalling:
|
|
729
|
+
functionCalling: false,
|
|
730
730
|
codeGeneration: true,
|
|
731
731
|
reasoning: true,
|
|
732
732
|
multimodal: false,
|
|
@@ -770,7 +770,7 @@ export const MODEL_REGISTRY = {
|
|
|
770
770
|
description: "Cost-effective O1 variant with strong reasoning capabilities",
|
|
771
771
|
capabilities: {
|
|
772
772
|
vision: false,
|
|
773
|
-
functionCalling:
|
|
773
|
+
functionCalling: false,
|
|
774
774
|
codeGeneration: true,
|
|
775
775
|
reasoning: true,
|
|
776
776
|
multimodal: false,
|
|
@@ -2342,6 +2342,22 @@ export function getAllModels() {
|
|
|
2342
2342
|
export function getModelById(id) {
|
|
2343
2343
|
return MODEL_REGISTRY[id];
|
|
2344
2344
|
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Check whether a model supports a registered capability.
|
|
2347
|
+
*
|
|
2348
|
+
* Aliases resolve to their canonical model ID before the provider is
|
|
2349
|
+
* cross-checked. Unknown provider/model pairs default to supported so new or
|
|
2350
|
+
* custom models retain the existing behavior until capability data is added.
|
|
2351
|
+
*/
|
|
2352
|
+
export function modelSupports(capability, provider, model) {
|
|
2353
|
+
const normalizedModel = model.toLowerCase();
|
|
2354
|
+
const modelId = MODEL_ALIASES[normalizedModel] ?? normalizedModel;
|
|
2355
|
+
const modelInfo = MODEL_REGISTRY[modelId];
|
|
2356
|
+
if (!modelInfo || modelInfo.provider !== provider.toLowerCase()) {
|
|
2357
|
+
return true;
|
|
2358
|
+
}
|
|
2359
|
+
return modelInfo.capabilities[capability];
|
|
2360
|
+
}
|
|
2345
2361
|
/**
|
|
2346
2362
|
* Get models by provider
|
|
2347
2363
|
*/
|
package/dist/lib/neurolink.js
CHANGED
|
@@ -299,8 +299,9 @@ export const NEUROLINK_BRAND = Symbol.for("@juspay/neurolink/sdk-brand");
|
|
|
299
299
|
* exists yet to ask directly; every other provider gets tool definitions
|
|
300
300
|
* natively via its `tools` parameter, so repeating them in the prompt was
|
|
301
301
|
* pure token duplication. The stream path asks the provider instance
|
|
302
|
-
* (`provider.supportsTools()`) instead of this list.
|
|
303
|
-
* `
|
|
302
|
+
* (`provider.supportsTools()`) instead of this list. BaseProvider resolves its
|
|
303
|
+
* default through MODEL_REGISTRY's `modelSupports()` facade; keep this list in
|
|
304
|
+
* sync with provider-specific `supportsTools()` overrides when adding providers.
|
|
304
305
|
*/
|
|
305
306
|
const PROMPT_ONLY_TOOL_PROVIDERS = new Set([
|
|
306
307
|
"ollama",
|
|
@@ -159,7 +159,6 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
159
159
|
* uses an `api-key` header instead of `Authorization: Bearer`).
|
|
160
160
|
*/
|
|
161
161
|
protected getAuthHeaders(): Record<string, string>;
|
|
162
|
-
supportsTools(): boolean;
|
|
163
162
|
/**
|
|
164
163
|
* Health-check hook — part of the documented public provider contract
|
|
165
164
|
* (`docs/provider-integration/00-architecture.md`). Default returns true
|
|
@@ -261,9 +261,6 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
261
261
|
// ===========================================================================
|
|
262
262
|
// Public/protected concrete methods (shared by all subclasses)
|
|
263
263
|
// ===========================================================================
|
|
264
|
-
supportsTools() {
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
264
|
/**
|
|
268
265
|
* Health-check hook — part of the documented public provider contract
|
|
269
266
|
* (`docs/provider-integration/00-architecture.md`). Default returns true
|
|
@@ -249,6 +249,17 @@ export declare function parseClaudeErrorBody(errBody: string): ParsedClaudeError
|
|
|
249
249
|
* Detect malformed request errors that should not trigger account/provider failover.
|
|
250
250
|
*/
|
|
251
251
|
export declare function isInvalidRequestError(status: number, errBody: string): boolean;
|
|
252
|
+
/**
|
|
253
|
+
* A subscription-specific beta rejection. Anthropic returns
|
|
254
|
+
* `400 invalid_request_error` with a message like
|
|
255
|
+
* "The long context beta is not yet available for this subscription." when an
|
|
256
|
+
* account's plan tier does not grant an optional beta the proxy injected (see
|
|
257
|
+
* CLAUDE_CODE_OAUTH_BETAS in anthropicOAuth.ts). Unlike a genuinely malformed
|
|
258
|
+
* request, the SAME request can succeed on a different account whose tier
|
|
259
|
+
* grants the beta — so this must be treated as retryable on the next account
|
|
260
|
+
* rather than a terminal client-facing 400.
|
|
261
|
+
*/
|
|
262
|
+
export declare function isSubscriptionBetaRejection(status: number, errBody: string): boolean;
|
|
252
263
|
/**
|
|
253
264
|
* Backward-compatible alias — delegates to the shared translation engine.
|
|
254
265
|
*/
|
|
@@ -3131,6 +3131,38 @@ async function handleAnthropicNonOkResponse(args) {
|
|
|
3131
3131
|
durationMs: Date.now() - fetchStartMs,
|
|
3132
3132
|
});
|
|
3133
3133
|
if (isInvalidRequestError(response.status, errBody)) {
|
|
3134
|
+
if (isSubscriptionBetaRejection(response.status, errBody)) {
|
|
3135
|
+
// Subscription-specific beta rejection (an account whose plan tier lacks
|
|
3136
|
+
// an optional beta the proxy injected). The SAME request can succeed on
|
|
3137
|
+
// another account whose tier grants the beta — or on a fallback provider —
|
|
3138
|
+
// so advance to the next account instead of failing the client here.
|
|
3139
|
+
//
|
|
3140
|
+
// Deliberately do NOT store this in `currentInvalidRequestFailure`: that
|
|
3141
|
+
// field is a deterministic "malformed request" signal that both
|
|
3142
|
+
// suppresses provider fallback (shouldAttemptClaudeFallback) and outranks
|
|
3143
|
+
// transient/rate-limit failures in the final response. A beta rejection is
|
|
3144
|
+
// neither terminal nor higher-priority — a later account's real 429/5xx
|
|
3145
|
+
// must still take precedence, and fallback must stay eligible. The reason
|
|
3146
|
+
// is carried in `lastError`, so if every account rejects the beta the
|
|
3147
|
+
// exhaustion response still explains why.
|
|
3148
|
+
logger.always(`[proxy] ← ${response.status} account=${account.label} beta unavailable for subscription; advancing to next account`);
|
|
3149
|
+
logAttempt(response.status, "invalid_request_error", summarizeErrorMessage(errBody));
|
|
3150
|
+
tracer?.setError("invalid_request_error", summarizeErrorMessage(errBody));
|
|
3151
|
+
tracer?.recordRetry(account.label, "beta_unavailable");
|
|
3152
|
+
// A tier's lack of a beta is stable, not transient — advance the
|
|
3153
|
+
// fill-first primary pointer (like the auth/rate-limit rotation paths) so
|
|
3154
|
+
// this account stops being retried first on every future request.
|
|
3155
|
+
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
|
|
3156
|
+
currentLastError = summarizeErrorMessage(errBody);
|
|
3157
|
+
return {
|
|
3158
|
+
continueLoop: true,
|
|
3159
|
+
lastError: currentLastError,
|
|
3160
|
+
authFailureMessage: currentAuthFailureMessage,
|
|
3161
|
+
sawTransientFailure: currentSawTransientFailure,
|
|
3162
|
+
invalidRequestFailure: currentInvalidRequestFailure,
|
|
3163
|
+
upstreamSpan: undefined,
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3134
3166
|
logger.always(`[proxy] ← ${response.status} upstream invalid_request_error`);
|
|
3135
3167
|
logAttempt(response.status, "invalid_request_error", summarizeErrorMessage(errBody));
|
|
3136
3168
|
tracer?.setError("invalid_request_error", summarizeErrorMessage(errBody));
|
|
@@ -4613,6 +4645,29 @@ export function isInvalidRequestError(status, errBody) {
|
|
|
4613
4645
|
return (parsed.errorType === "invalid_request_error" ||
|
|
4614
4646
|
errBody.includes("invalid_request_error"));
|
|
4615
4647
|
}
|
|
4648
|
+
/**
|
|
4649
|
+
* A subscription-specific beta rejection. Anthropic returns
|
|
4650
|
+
* `400 invalid_request_error` with a message like
|
|
4651
|
+
* "The long context beta is not yet available for this subscription." when an
|
|
4652
|
+
* account's plan tier does not grant an optional beta the proxy injected (see
|
|
4653
|
+
* CLAUDE_CODE_OAUTH_BETAS in anthropicOAuth.ts). Unlike a genuinely malformed
|
|
4654
|
+
* request, the SAME request can succeed on a different account whose tier
|
|
4655
|
+
* grants the beta — so this must be treated as retryable on the next account
|
|
4656
|
+
* rather than a terminal client-facing 400.
|
|
4657
|
+
*/
|
|
4658
|
+
export function isSubscriptionBetaRejection(status, errBody) {
|
|
4659
|
+
if (status !== 400) {
|
|
4660
|
+
return false;
|
|
4661
|
+
}
|
|
4662
|
+
const parsed = parseClaudeErrorBody(errBody);
|
|
4663
|
+
if (parsed.errorType !== "invalid_request_error") {
|
|
4664
|
+
return false;
|
|
4665
|
+
}
|
|
4666
|
+
const message = (parsed.message ?? "").toLowerCase();
|
|
4667
|
+
return (message.includes("beta") &&
|
|
4668
|
+
message.includes("subscription") &&
|
|
4669
|
+
message.includes("available"));
|
|
4670
|
+
}
|
|
4616
4671
|
function normalizeClaudeRequestForAnthropic(body) {
|
|
4617
4672
|
return {
|
|
4618
4673
|
...body,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Part of Phase 4.1 - Models Command System
|
|
5
5
|
*/
|
|
6
6
|
import { AIProviderName } from "../constants/enums.js";
|
|
7
|
-
import type { JsonValue, ModelInfo } from "../types/index.js";
|
|
7
|
+
import type { JsonValue, ModelCapabilities, ModelInfo } from "../types/index.js";
|
|
8
8
|
/**
|
|
9
9
|
* Comprehensive model registry
|
|
10
10
|
*/
|
|
@@ -25,6 +25,14 @@ export declare function getAllModels(): ModelInfo[];
|
|
|
25
25
|
* Get model by ID
|
|
26
26
|
*/
|
|
27
27
|
export declare function getModelById(id: string): ModelInfo | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Check whether a model supports a registered capability.
|
|
30
|
+
*
|
|
31
|
+
* Aliases resolve to their canonical model ID before the provider is
|
|
32
|
+
* cross-checked. Unknown provider/model pairs default to supported so new or
|
|
33
|
+
* custom models retain the existing behavior until capability data is added.
|
|
34
|
+
*/
|
|
35
|
+
export declare function modelSupports(capability: keyof ModelCapabilities, provider: string, model: string): boolean;
|
|
28
36
|
/**
|
|
29
37
|
* Get models by provider
|
|
30
38
|
*/
|
|
@@ -726,7 +726,7 @@ export const MODEL_REGISTRY = {
|
|
|
726
726
|
description: "Preview version of O1 reasoning model",
|
|
727
727
|
capabilities: {
|
|
728
728
|
vision: false,
|
|
729
|
-
functionCalling:
|
|
729
|
+
functionCalling: false,
|
|
730
730
|
codeGeneration: true,
|
|
731
731
|
reasoning: true,
|
|
732
732
|
multimodal: false,
|
|
@@ -770,7 +770,7 @@ export const MODEL_REGISTRY = {
|
|
|
770
770
|
description: "Cost-effective O1 variant with strong reasoning capabilities",
|
|
771
771
|
capabilities: {
|
|
772
772
|
vision: false,
|
|
773
|
-
functionCalling:
|
|
773
|
+
functionCalling: false,
|
|
774
774
|
codeGeneration: true,
|
|
775
775
|
reasoning: true,
|
|
776
776
|
multimodal: false,
|
|
@@ -2342,6 +2342,22 @@ export function getAllModels() {
|
|
|
2342
2342
|
export function getModelById(id) {
|
|
2343
2343
|
return MODEL_REGISTRY[id];
|
|
2344
2344
|
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Check whether a model supports a registered capability.
|
|
2347
|
+
*
|
|
2348
|
+
* Aliases resolve to their canonical model ID before the provider is
|
|
2349
|
+
* cross-checked. Unknown provider/model pairs default to supported so new or
|
|
2350
|
+
* custom models retain the existing behavior until capability data is added.
|
|
2351
|
+
*/
|
|
2352
|
+
export function modelSupports(capability, provider, model) {
|
|
2353
|
+
const normalizedModel = model.toLowerCase();
|
|
2354
|
+
const modelId = MODEL_ALIASES[normalizedModel] ?? normalizedModel;
|
|
2355
|
+
const modelInfo = MODEL_REGISTRY[modelId];
|
|
2356
|
+
if (!modelInfo || modelInfo.provider !== provider.toLowerCase()) {
|
|
2357
|
+
return true;
|
|
2358
|
+
}
|
|
2359
|
+
return modelInfo.capabilities[capability];
|
|
2360
|
+
}
|
|
2345
2361
|
/**
|
|
2346
2362
|
* Get models by provider
|
|
2347
2363
|
*/
|
package/dist/neurolink.js
CHANGED
|
@@ -299,8 +299,9 @@ export const NEUROLINK_BRAND = Symbol.for("@juspay/neurolink/sdk-brand");
|
|
|
299
299
|
* exists yet to ask directly; every other provider gets tool definitions
|
|
300
300
|
* natively via its `tools` parameter, so repeating them in the prompt was
|
|
301
301
|
* pure token duplication. The stream path asks the provider instance
|
|
302
|
-
* (`provider.supportsTools()`) instead of this list.
|
|
303
|
-
* `
|
|
302
|
+
* (`provider.supportsTools()`) instead of this list. BaseProvider resolves its
|
|
303
|
+
* default through MODEL_REGISTRY's `modelSupports()` facade; keep this list in
|
|
304
|
+
* sync with provider-specific `supportsTools()` overrides when adding providers.
|
|
304
305
|
*/
|
|
305
306
|
const PROMPT_ONLY_TOOL_PROVIDERS = new Set([
|
|
306
307
|
"ollama",
|
|
@@ -159,7 +159,6 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
159
159
|
* uses an `api-key` header instead of `Authorization: Bearer`).
|
|
160
160
|
*/
|
|
161
161
|
protected getAuthHeaders(): Record<string, string>;
|
|
162
|
-
supportsTools(): boolean;
|
|
163
162
|
/**
|
|
164
163
|
* Health-check hook — part of the documented public provider contract
|
|
165
164
|
* (`docs/provider-integration/00-architecture.md`). Default returns true
|
|
@@ -261,9 +261,6 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
261
261
|
// ===========================================================================
|
|
262
262
|
// Public/protected concrete methods (shared by all subclasses)
|
|
263
263
|
// ===========================================================================
|
|
264
|
-
supportsTools() {
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
264
|
/**
|
|
268
265
|
* Health-check hook — part of the documented public provider contract
|
|
269
266
|
* (`docs/provider-integration/00-architecture.md`). Default returns true
|
|
@@ -153,7 +153,7 @@ export declare class SageMakerLanguageModel implements SageMakerAsLanguageModel
|
|
|
153
153
|
provider: string;
|
|
154
154
|
specificationVersion: "v2";
|
|
155
155
|
endpointName: string;
|
|
156
|
-
modelType: "huggingface" | "mistral" | "custom" | "
|
|
156
|
+
modelType: "huggingface" | "mistral" | "custom" | "llama" | "claude" | "jumpstart" | undefined;
|
|
157
157
|
region: string;
|
|
158
158
|
};
|
|
159
159
|
/**
|
|
@@ -200,7 +200,7 @@ export declare class SageMakerLanguageModel implements SageMakerAsLanguageModel
|
|
|
200
200
|
provider: string;
|
|
201
201
|
specificationVersion: "v2";
|
|
202
202
|
endpointName: string;
|
|
203
|
-
modelType: "huggingface" | "mistral" | "custom" | "
|
|
203
|
+
modelType: "huggingface" | "mistral" | "custom" | "llama" | "claude" | "jumpstart" | undefined;
|
|
204
204
|
region: string;
|
|
205
205
|
};
|
|
206
206
|
}
|
|
@@ -249,6 +249,17 @@ export declare function parseClaudeErrorBody(errBody: string): ParsedClaudeError
|
|
|
249
249
|
* Detect malformed request errors that should not trigger account/provider failover.
|
|
250
250
|
*/
|
|
251
251
|
export declare function isInvalidRequestError(status: number, errBody: string): boolean;
|
|
252
|
+
/**
|
|
253
|
+
* A subscription-specific beta rejection. Anthropic returns
|
|
254
|
+
* `400 invalid_request_error` with a message like
|
|
255
|
+
* "The long context beta is not yet available for this subscription." when an
|
|
256
|
+
* account's plan tier does not grant an optional beta the proxy injected (see
|
|
257
|
+
* CLAUDE_CODE_OAUTH_BETAS in anthropicOAuth.ts). Unlike a genuinely malformed
|
|
258
|
+
* request, the SAME request can succeed on a different account whose tier
|
|
259
|
+
* grants the beta — so this must be treated as retryable on the next account
|
|
260
|
+
* rather than a terminal client-facing 400.
|
|
261
|
+
*/
|
|
262
|
+
export declare function isSubscriptionBetaRejection(status: number, errBody: string): boolean;
|
|
252
263
|
/**
|
|
253
264
|
* Backward-compatible alias — delegates to the shared translation engine.
|
|
254
265
|
*/
|
|
@@ -3131,6 +3131,38 @@ async function handleAnthropicNonOkResponse(args) {
|
|
|
3131
3131
|
durationMs: Date.now() - fetchStartMs,
|
|
3132
3132
|
});
|
|
3133
3133
|
if (isInvalidRequestError(response.status, errBody)) {
|
|
3134
|
+
if (isSubscriptionBetaRejection(response.status, errBody)) {
|
|
3135
|
+
// Subscription-specific beta rejection (an account whose plan tier lacks
|
|
3136
|
+
// an optional beta the proxy injected). The SAME request can succeed on
|
|
3137
|
+
// another account whose tier grants the beta — or on a fallback provider —
|
|
3138
|
+
// so advance to the next account instead of failing the client here.
|
|
3139
|
+
//
|
|
3140
|
+
// Deliberately do NOT store this in `currentInvalidRequestFailure`: that
|
|
3141
|
+
// field is a deterministic "malformed request" signal that both
|
|
3142
|
+
// suppresses provider fallback (shouldAttemptClaudeFallback) and outranks
|
|
3143
|
+
// transient/rate-limit failures in the final response. A beta rejection is
|
|
3144
|
+
// neither terminal nor higher-priority — a later account's real 429/5xx
|
|
3145
|
+
// must still take precedence, and fallback must stay eligible. The reason
|
|
3146
|
+
// is carried in `lastError`, so if every account rejects the beta the
|
|
3147
|
+
// exhaustion response still explains why.
|
|
3148
|
+
logger.always(`[proxy] ← ${response.status} account=${account.label} beta unavailable for subscription; advancing to next account`);
|
|
3149
|
+
logAttempt(response.status, "invalid_request_error", summarizeErrorMessage(errBody));
|
|
3150
|
+
tracer?.setError("invalid_request_error", summarizeErrorMessage(errBody));
|
|
3151
|
+
tracer?.recordRetry(account.label, "beta_unavailable");
|
|
3152
|
+
// A tier's lack of a beta is stable, not transient — advance the
|
|
3153
|
+
// fill-first primary pointer (like the auth/rate-limit rotation paths) so
|
|
3154
|
+
// this account stops being retried first on every future request.
|
|
3155
|
+
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
|
|
3156
|
+
currentLastError = summarizeErrorMessage(errBody);
|
|
3157
|
+
return {
|
|
3158
|
+
continueLoop: true,
|
|
3159
|
+
lastError: currentLastError,
|
|
3160
|
+
authFailureMessage: currentAuthFailureMessage,
|
|
3161
|
+
sawTransientFailure: currentSawTransientFailure,
|
|
3162
|
+
invalidRequestFailure: currentInvalidRequestFailure,
|
|
3163
|
+
upstreamSpan: undefined,
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3134
3166
|
logger.always(`[proxy] ← ${response.status} upstream invalid_request_error`);
|
|
3135
3167
|
logAttempt(response.status, "invalid_request_error", summarizeErrorMessage(errBody));
|
|
3136
3168
|
tracer?.setError("invalid_request_error", summarizeErrorMessage(errBody));
|
|
@@ -4613,6 +4645,29 @@ export function isInvalidRequestError(status, errBody) {
|
|
|
4613
4645
|
return (parsed.errorType === "invalid_request_error" ||
|
|
4614
4646
|
errBody.includes("invalid_request_error"));
|
|
4615
4647
|
}
|
|
4648
|
+
/**
|
|
4649
|
+
* A subscription-specific beta rejection. Anthropic returns
|
|
4650
|
+
* `400 invalid_request_error` with a message like
|
|
4651
|
+
* "The long context beta is not yet available for this subscription." when an
|
|
4652
|
+
* account's plan tier does not grant an optional beta the proxy injected (see
|
|
4653
|
+
* CLAUDE_CODE_OAUTH_BETAS in anthropicOAuth.ts). Unlike a genuinely malformed
|
|
4654
|
+
* request, the SAME request can succeed on a different account whose tier
|
|
4655
|
+
* grants the beta — so this must be treated as retryable on the next account
|
|
4656
|
+
* rather than a terminal client-facing 400.
|
|
4657
|
+
*/
|
|
4658
|
+
export function isSubscriptionBetaRejection(status, errBody) {
|
|
4659
|
+
if (status !== 400) {
|
|
4660
|
+
return false;
|
|
4661
|
+
}
|
|
4662
|
+
const parsed = parseClaudeErrorBody(errBody);
|
|
4663
|
+
if (parsed.errorType !== "invalid_request_error") {
|
|
4664
|
+
return false;
|
|
4665
|
+
}
|
|
4666
|
+
const message = (parsed.message ?? "").toLowerCase();
|
|
4667
|
+
return (message.includes("beta") &&
|
|
4668
|
+
message.includes("subscription") &&
|
|
4669
|
+
message.includes("available"));
|
|
4670
|
+
}
|
|
4616
4671
|
function normalizeClaudeRequestForAnthropic(body) {
|
|
4617
4672
|
return {
|
|
4618
4673
|
...body,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.5.
|
|
3
|
+
"version": "10.5.2",
|
|
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-dedup": "pnpm run test:tool-dedup:vitest && npx tsx test/continuous-test-suite-tool-dedup.ts",
|
|
142
142
|
"test:model-pool:vitest": "pnpm exec vitest run test/modelPool.test.ts",
|
|
143
143
|
"test:model-pool": "pnpm run test:model-pool:vitest && npx tsx test/continuous-test-suite-model-pool.ts",
|
|
144
|
+
"test:model-capabilities:vitest": "pnpm exec vitest run test/modelCapabilities.test.ts",
|
|
144
145
|
"test:ci": "pnpm run test && pnpm run test:client && pnpm run test:hitl",
|
|
145
146
|
"// CI tier — fast, no live AI calls, safe for every commit": "",
|
|
146
147
|
"test:unit:vitest": "pnpm exec vitest run test/toolRouting.test.ts",
|
|
@@ -149,7 +150,7 @@
|
|
|
149
150
|
"test:system-messages:vitest": "pnpm exec vitest run test/systemMessages.test.ts",
|
|
150
151
|
"test:tool-routing-semantic:vitest": "pnpm exec vitest run test/toolRoutingSemantic.test.ts",
|
|
151
152
|
"test:tool-routing-semantic": "pnpm run test:tool-routing-semantic:vitest && npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
|
|
152
|
-
"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:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:litellm-context:vitest && pnpm run test:step-budget-guard:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
|
|
153
|
+
"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:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:litellm-context:vitest && pnpm run test:step-budget-guard:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && 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:vitest",
|
|
153
154
|
"// 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)": "",
|
|
154
155
|
"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",
|
|
155
156
|
"// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",
|