@juspay/neurolink 11.1.1 → 11.2.0
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 +6 -0
- package/dist/browser/neurolink.min.js +272 -272
- package/dist/lib/providers/configuredOpenAICompat.d.ts +24 -0
- package/dist/lib/providers/configuredOpenAICompat.js +60 -0
- package/dist/lib/providers/openaiCompatCatalog.d.ts +24 -0
- package/dist/lib/providers/openaiCompatCatalog.js +272 -0
- package/dist/lib/types/providers.d.ts +99 -0
- package/dist/lib/utils/providerConfig.d.ts +23 -1
- package/dist/lib/utils/providerConfig.js +60 -0
- package/dist/providers/configuredOpenAICompat.d.ts +24 -0
- package/dist/providers/configuredOpenAICompat.js +59 -0
- package/dist/providers/openaiCompatCatalog.d.ts +24 -0
- package/dist/providers/openaiCompatCatalog.js +271 -0
- package/dist/types/providers.d.ts +99 -0
- package/dist/utils/providerConfig.d.ts +23 -1
- package/dist/utils/providerConfig.js +60 -0
- package/package.json +2 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.js";
|
|
2
|
+
import { redactUrlCredentials } from "../utils/logSanitize.js";
|
|
3
|
+
import { getProviderModel, resolveOpenAICompatConfig, } from "../utils/providerConfig.js";
|
|
4
|
+
import { classifyProviderError } from "../utils/errorClassifier.js";
|
|
5
|
+
import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
6
|
+
/**
|
|
7
|
+
* Generic OpenAI-compatible provider driven entirely by an
|
|
8
|
+
* OpenAICompatCatalogEntry. Replaces a hand-written subclass for any
|
|
9
|
+
* provider whose only differences from its siblings are credentials, base
|
|
10
|
+
* URL, model defaults, and error-classification rules — see
|
|
11
|
+
* OPENAI_COMPAT_CATALOG in openaiCompatCatalog.ts for the entries.
|
|
12
|
+
*
|
|
13
|
+
* If a provider needs a real hook override (adjustRequestBody,
|
|
14
|
+
* adjustBodyAfter400, getChatCompletionsURL, getAuthHeaders,
|
|
15
|
+
* suppressResponseFormatWithTools, ...) it does NOT belong in the catalog —
|
|
16
|
+
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts).
|
|
17
|
+
*/
|
|
18
|
+
export class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvider {
|
|
19
|
+
entry;
|
|
20
|
+
constructor(entry, modelName, sdk, credentials) {
|
|
21
|
+
const { apiKey, baseURL } = resolveOpenAICompatConfig(entry, credentials);
|
|
22
|
+
// BaseProvider's constructor calls `this.getDefaultModel()` /
|
|
23
|
+
// `this.getProviderName()` synchronously inside `super()`, before this
|
|
24
|
+
// class's own constructor body (or field initializers) ever run — so
|
|
25
|
+
// `this.entry` is not yet assigned at that point and those overrides
|
|
26
|
+
// would read `undefined.modelEnvVar`. `entry.providerName` is always
|
|
27
|
+
// defined, so passing it straight through makes the base constructor's
|
|
28
|
+
// `providerName || this.getProviderName()` short-circuit; resolving the
|
|
29
|
+
// model up front and always passing a truthy `modelName` does the same
|
|
30
|
+
// for `getDefaultModel()`. Both overrides remain correct for any call
|
|
31
|
+
// made after construction, once `this.entry` is set below.
|
|
32
|
+
const resolvedModelName = modelName || getProviderModel(entry.modelEnvVar, entry.defaultModel);
|
|
33
|
+
super(entry.providerName, resolvedModelName, sdk, { baseURL, apiKey });
|
|
34
|
+
this.entry = entry;
|
|
35
|
+
logger.debug(`${entry.configOptions.providerName} Provider initialized`, {
|
|
36
|
+
modelName: this.modelName,
|
|
37
|
+
providerName: this.providerName,
|
|
38
|
+
baseURL: redactUrlCredentials(this.config.baseURL),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
getProviderName() {
|
|
42
|
+
return this.entry.providerName;
|
|
43
|
+
}
|
|
44
|
+
getDefaultModel() {
|
|
45
|
+
return getProviderModel(this.entry.modelEnvVar, this.entry.defaultModel);
|
|
46
|
+
}
|
|
47
|
+
getFallbackModelName() {
|
|
48
|
+
return this.entry.fallbackModelName;
|
|
49
|
+
}
|
|
50
|
+
getFallbackModels() {
|
|
51
|
+
return this.entry.fallbackModels;
|
|
52
|
+
}
|
|
53
|
+
formatProviderError(error) {
|
|
54
|
+
// classifyProviderError handles TimeoutError internally (always maps
|
|
55
|
+
// to NetworkError, ahead of any rule table) — no local pre-check
|
|
56
|
+
// needed or wanted here; see this task's design note.
|
|
57
|
+
return classifyProviderError(error, this.entry.errorRules, this.entry.providerName, this.modelName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { OpenAICompatCatalogEntry } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Config-driven catalog of the 7 zero-quirk OpenAI-compatible providers.
|
|
4
|
+
* Each entry fully replaces what used to be a hand-written
|
|
5
|
+
* OpenAIChatCompletionsProvider subclass — see ConfiguredOpenAICompatProvider
|
|
6
|
+
* for the class that reads these entries, and providerRegistry.ts for the
|
|
7
|
+
* registration loop that consumes this array.
|
|
8
|
+
*
|
|
9
|
+
* `errorRules` mirrors each provider's LIVE `formatProviderError` rule array
|
|
10
|
+
* (post plan-07/wave-2 migration), not the original hand-rolled ladder these
|
|
11
|
+
* providers had when plan 05 was first drafted: every provider below now
|
|
12
|
+
* keeps only its bespoke rule(s) — auth, plus Groq's model_decommissioned and
|
|
13
|
+
* xAI's insufficient_quota — before spreading the SAME exported
|
|
14
|
+
* `DEFAULT_ERROR_RULES` constant that the live subclasses spread (never an
|
|
15
|
+
* inlined copy, so this catalog cannot drift from that table independently).
|
|
16
|
+
* See plan-05/progress.md Ruling R4 for the full rationale.
|
|
17
|
+
*
|
|
18
|
+
* To add a new zero-quirk OpenAI-compatible provider: add one entry here.
|
|
19
|
+
* Do NOT add a provider here if it needs any hook override beyond the 3
|
|
20
|
+
* mandatory ones (getProviderName/getDefaultModel/formatProviderError) —
|
|
21
|
+
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts, and
|
|
22
|
+
* Task 14's docs task for the deciding criteria).
|
|
23
|
+
*/
|
|
24
|
+
export declare const OPENAI_COMPAT_CATALOG: readonly OpenAICompatCatalogEntry[];
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { AIProviderName } from "../constants/enums.js";
|
|
2
|
+
import { CloudflareModels, FireworksModels, GroqModels, MistralModels, PerplexityModels, TogetherAIModels, XaiModels, } from "../constants/enums.js";
|
|
3
|
+
import { AuthenticationError, InvalidModelError, ProviderError, } from "../types/index.js";
|
|
4
|
+
import { DEFAULT_ERROR_RULES } from "../utils/errorClassifier.js";
|
|
5
|
+
import { createCloudflareConfig, createFireworksConfig, createGroqConfig, createMistralConfig, createPerplexityConfig, createTogetherAIConfig, createXaiConfig, } from "../utils/providerConfig.js";
|
|
6
|
+
function buildCloudflareBaseURL(accountId) {
|
|
7
|
+
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Config-driven catalog of the 7 zero-quirk OpenAI-compatible providers.
|
|
11
|
+
* Each entry fully replaces what used to be a hand-written
|
|
12
|
+
* OpenAIChatCompletionsProvider subclass — see ConfiguredOpenAICompatProvider
|
|
13
|
+
* for the class that reads these entries, and providerRegistry.ts for the
|
|
14
|
+
* registration loop that consumes this array.
|
|
15
|
+
*
|
|
16
|
+
* `errorRules` mirrors each provider's LIVE `formatProviderError` rule array
|
|
17
|
+
* (post plan-07/wave-2 migration), not the original hand-rolled ladder these
|
|
18
|
+
* providers had when plan 05 was first drafted: every provider below now
|
|
19
|
+
* keeps only its bespoke rule(s) — auth, plus Groq's model_decommissioned and
|
|
20
|
+
* xAI's insufficient_quota — before spreading the SAME exported
|
|
21
|
+
* `DEFAULT_ERROR_RULES` constant that the live subclasses spread (never an
|
|
22
|
+
* inlined copy, so this catalog cannot drift from that table independently).
|
|
23
|
+
* See plan-05/progress.md Ruling R4 for the full rationale.
|
|
24
|
+
*
|
|
25
|
+
* To add a new zero-quirk OpenAI-compatible provider: add one entry here.
|
|
26
|
+
* Do NOT add a provider here if it needs any hook override beyond the 3
|
|
27
|
+
* mandatory ones (getProviderName/getDefaultModel/formatProviderError) —
|
|
28
|
+
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts, and
|
|
29
|
+
* Task 14's docs task for the deciding criteria).
|
|
30
|
+
*/
|
|
31
|
+
export const OPENAI_COMPAT_CATALOG = [
|
|
32
|
+
{
|
|
33
|
+
providerName: AIProviderName.GROQ,
|
|
34
|
+
aliases: ["groq"],
|
|
35
|
+
apiKeyEnvVar: "GROQ_API_KEY",
|
|
36
|
+
baseURLEnvVar: "GROQ_BASE_URL",
|
|
37
|
+
defaultBaseURL: "https://api.groq.com/openai/v1",
|
|
38
|
+
configOptions: createGroqConfig(),
|
|
39
|
+
modelEnvVar: "GROQ_MODEL",
|
|
40
|
+
defaultModel: GroqModels.LLAMA_3_3_70B_VERSATILE,
|
|
41
|
+
registryDefaultModel: GroqModels.LLAMA_3_3_70B_VERSATILE,
|
|
42
|
+
registryDefaultModelChecksEnvVar: true,
|
|
43
|
+
fallbackModelName: GroqModels.LLAMA_3_1_8B_INSTANT,
|
|
44
|
+
fallbackModels: [
|
|
45
|
+
GroqModels.LLAMA_3_3_70B_VERSATILE,
|
|
46
|
+
GroqModels.LLAMA_3_1_8B_INSTANT,
|
|
47
|
+
GroqModels.GEMMA_2_9B_IT,
|
|
48
|
+
GroqModels.MIXTRAL_8X7B_32768,
|
|
49
|
+
GroqModels.LLAMA_3_2_90B_VISION_PREVIEW,
|
|
50
|
+
GroqModels.LLAMA_3_2_11B_VISION_PREVIEW,
|
|
51
|
+
],
|
|
52
|
+
// KNOWN GAP (not reproducible by this data-only array — flagged for PR C,
|
|
53
|
+
// see plan-05/task-A-report.md): live GroqProvider.formatProviderError()
|
|
54
|
+
// intercepts TimeoutError and returns a plain ProviderError BEFORE ever
|
|
55
|
+
// calling classifyProviderError, overriding that function's own
|
|
56
|
+
// (non-overridable) rule that TimeoutError always maps to NetworkError.
|
|
57
|
+
// ConfiguredOpenAICompatProvider.formatProviderError() has no such
|
|
58
|
+
// pre-check hook, so migrating Groq onto it as-is would silently
|
|
59
|
+
// reclassify Groq timeouts as NetworkError. This entry's errorRules
|
|
60
|
+
// still mirrors Groq's live rule array faithfully for every other error
|
|
61
|
+
// shape; the TimeoutError special case is a structural gap in
|
|
62
|
+
// ConfiguredOpenAICompatProvider, not a data error here.
|
|
63
|
+
errorRules: [
|
|
64
|
+
{
|
|
65
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
66
|
+
/Invalid API key|Authentication|invalid_api_key/i.test(ctx.message),
|
|
67
|
+
errorClass: AuthenticationError,
|
|
68
|
+
message: "Invalid Groq API key. Check GROQ_API_KEY. Get one at https://console.groq.com/keys",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
match: (ctx) => /model_decommissioned/i.test(ctx.message),
|
|
72
|
+
errorClass: InvalidModelError,
|
|
73
|
+
message: (ctx) => `Groq model '${ctx.modelName}' was decommissioned. Pick a current model from https://console.groq.com/docs/models.`,
|
|
74
|
+
},
|
|
75
|
+
...DEFAULT_ERROR_RULES,
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
providerName: AIProviderName.XAI,
|
|
80
|
+
aliases: ["xai", "grok"],
|
|
81
|
+
apiKeyEnvVar: "XAI_API_KEY",
|
|
82
|
+
baseURLEnvVar: "XAI_BASE_URL",
|
|
83
|
+
defaultBaseURL: "https://api.x.ai/v1",
|
|
84
|
+
configOptions: createXaiConfig(),
|
|
85
|
+
modelEnvVar: "XAI_MODEL",
|
|
86
|
+
defaultModel: XaiModels.GROK_3,
|
|
87
|
+
registryDefaultModel: XaiModels.GROK_3,
|
|
88
|
+
registryDefaultModelChecksEnvVar: true,
|
|
89
|
+
fallbackModelName: XaiModels.GROK_3_MINI,
|
|
90
|
+
fallbackModels: [
|
|
91
|
+
XaiModels.GROK_3,
|
|
92
|
+
XaiModels.GROK_3_MINI,
|
|
93
|
+
XaiModels.GROK_2_LATEST,
|
|
94
|
+
XaiModels.GROK_2_VISION_LATEST,
|
|
95
|
+
XaiModels.GROK_BETA,
|
|
96
|
+
],
|
|
97
|
+
errorRules: [
|
|
98
|
+
{
|
|
99
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
100
|
+
/Invalid API key|Authentication|invalid_api_key/i.test(ctx.message),
|
|
101
|
+
errorClass: AuthenticationError,
|
|
102
|
+
message: "Invalid xAI API key. Please check your XAI_API_KEY environment variable. Get one at https://console.x.ai/",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
match: (ctx) => /insufficient_quota|quota exceeded/i.test(ctx.message),
|
|
106
|
+
errorClass: ProviderError,
|
|
107
|
+
message: "xAI account has insufficient quota. Top up at https://console.x.ai/",
|
|
108
|
+
},
|
|
109
|
+
...DEFAULT_ERROR_RULES,
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
providerName: AIProviderName.TOGETHER_AI,
|
|
114
|
+
aliases: ["together-ai", "together"],
|
|
115
|
+
apiKeyEnvVar: "TOGETHER_API_KEY",
|
|
116
|
+
baseURLEnvVar: "TOGETHER_BASE_URL",
|
|
117
|
+
defaultBaseURL: "https://api.together.xyz/v1",
|
|
118
|
+
configOptions: createTogetherAIConfig(),
|
|
119
|
+
modelEnvVar: "TOGETHER_MODEL",
|
|
120
|
+
defaultModel: TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO,
|
|
121
|
+
registryDefaultModel: TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO,
|
|
122
|
+
registryDefaultModelChecksEnvVar: true,
|
|
123
|
+
fallbackModelName: TogetherAIModels.LLAMA_3_1_8B_INSTRUCT_TURBO,
|
|
124
|
+
fallbackModels: [
|
|
125
|
+
TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO,
|
|
126
|
+
TogetherAIModels.LLAMA_3_1_405B_INSTRUCT_TURBO,
|
|
127
|
+
TogetherAIModels.LLAMA_3_1_70B_INSTRUCT_TURBO,
|
|
128
|
+
TogetherAIModels.LLAMA_3_1_8B_INSTRUCT_TURBO,
|
|
129
|
+
TogetherAIModels.MIXTRAL_8X22B_INSTRUCT,
|
|
130
|
+
TogetherAIModels.QWEN_2_5_72B_INSTRUCT_TURBO,
|
|
131
|
+
TogetherAIModels.DEEPSEEK_R1,
|
|
132
|
+
TogetherAIModels.DEEPSEEK_V3,
|
|
133
|
+
],
|
|
134
|
+
errorRules: [
|
|
135
|
+
{
|
|
136
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
137
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
138
|
+
errorClass: AuthenticationError,
|
|
139
|
+
message: "Invalid Together AI API key. Get one at https://api.together.xyz/settings/api-keys",
|
|
140
|
+
},
|
|
141
|
+
...DEFAULT_ERROR_RULES,
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
providerName: AIProviderName.FIREWORKS,
|
|
146
|
+
aliases: ["fireworks"],
|
|
147
|
+
apiKeyEnvVar: "FIREWORKS_API_KEY",
|
|
148
|
+
baseURLEnvVar: "FIREWORKS_BASE_URL",
|
|
149
|
+
defaultBaseURL: "https://api.fireworks.ai/inference/v1",
|
|
150
|
+
configOptions: createFireworksConfig(),
|
|
151
|
+
modelEnvVar: "FIREWORKS_MODEL",
|
|
152
|
+
defaultModel: FireworksModels.DEEPSEEK_V4_PRO,
|
|
153
|
+
registryDefaultModel: FireworksModels.DEEPSEEK_V4_PRO,
|
|
154
|
+
registryDefaultModelChecksEnvVar: true,
|
|
155
|
+
fallbackModelName: FireworksModels.DEEPSEEK_V4_PRO,
|
|
156
|
+
fallbackModels: [
|
|
157
|
+
FireworksModels.DEEPSEEK_V4_PRO,
|
|
158
|
+
FireworksModels.GLM_5P1,
|
|
159
|
+
FireworksModels.GLM_5,
|
|
160
|
+
FireworksModels.KIMI_K2P6,
|
|
161
|
+
FireworksModels.KIMI_K2P5,
|
|
162
|
+
FireworksModels.GPT_OSS_120B,
|
|
163
|
+
],
|
|
164
|
+
errorRules: [
|
|
165
|
+
{
|
|
166
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
167
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
168
|
+
errorClass: AuthenticationError,
|
|
169
|
+
message: "Invalid Fireworks API key. Get one at https://fireworks.ai/account/api-keys",
|
|
170
|
+
},
|
|
171
|
+
...DEFAULT_ERROR_RULES,
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
providerName: AIProviderName.PERPLEXITY,
|
|
176
|
+
aliases: ["perplexity", "pplx"],
|
|
177
|
+
apiKeyEnvVar: "PERPLEXITY_API_KEY",
|
|
178
|
+
baseURLEnvVar: "PERPLEXITY_BASE_URL",
|
|
179
|
+
defaultBaseURL: "https://api.perplexity.ai",
|
|
180
|
+
configOptions: createPerplexityConfig(),
|
|
181
|
+
modelEnvVar: "PERPLEXITY_MODEL",
|
|
182
|
+
defaultModel: PerplexityModels.SONAR,
|
|
183
|
+
registryDefaultModel: PerplexityModels.SONAR,
|
|
184
|
+
registryDefaultModelChecksEnvVar: true,
|
|
185
|
+
// Perplexity's live class does NOT override getFallbackModelName() — it
|
|
186
|
+
// inherits the base class default "gpt-3.5-turbo". Preserved here
|
|
187
|
+
// verbatim, not "fixed" to a Perplexity model — that's a real,
|
|
188
|
+
// pre-existing quirk this plan is not authorized to change.
|
|
189
|
+
fallbackModelName: "gpt-3.5-turbo",
|
|
190
|
+
fallbackModels: [
|
|
191
|
+
PerplexityModels.SONAR,
|
|
192
|
+
PerplexityModels.SONAR_PRO,
|
|
193
|
+
PerplexityModels.SONAR_REASONING,
|
|
194
|
+
PerplexityModels.SONAR_REASONING_PRO,
|
|
195
|
+
PerplexityModels.SONAR_DEEP_RESEARCH,
|
|
196
|
+
],
|
|
197
|
+
errorRules: [
|
|
198
|
+
{
|
|
199
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
200
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
201
|
+
errorClass: AuthenticationError,
|
|
202
|
+
message: "Invalid Perplexity API key. Get one at https://www.perplexity.ai/settings/api",
|
|
203
|
+
},
|
|
204
|
+
...DEFAULT_ERROR_RULES,
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
providerName: AIProviderName.MISTRAL,
|
|
209
|
+
aliases: ["mistral"],
|
|
210
|
+
apiKeyEnvVar: "MISTRAL_API_KEY",
|
|
211
|
+
baseURLEnvVar: "MISTRAL_BASE_URL",
|
|
212
|
+
defaultBaseURL: "https://api.mistral.ai/v1",
|
|
213
|
+
configOptions: createMistralConfig(),
|
|
214
|
+
modelEnvVar: "MISTRAL_MODEL",
|
|
215
|
+
defaultModel: MistralModels.MISTRAL_SMALL_2506,
|
|
216
|
+
// The one documented registry-vs-class default-model quirk (see this
|
|
217
|
+
// plan's "Design reference" section): the registry passes the bare
|
|
218
|
+
// literal MISTRAL_LARGE_LATEST with no env-var check, while
|
|
219
|
+
// MistralProvider.getDefaultModel() checks MISTRAL_MODEL and defaults to
|
|
220
|
+
// MISTRAL_SMALL_2506. Preserved exactly, not reconciled.
|
|
221
|
+
registryDefaultModel: MistralModels.MISTRAL_LARGE_LATEST,
|
|
222
|
+
registryDefaultModelChecksEnvVar: false,
|
|
223
|
+
fallbackModelName: MistralModels.MISTRAL_SMALL_2506,
|
|
224
|
+
fallbackModels: [
|
|
225
|
+
MistralModels.MISTRAL_SMALL_2506,
|
|
226
|
+
MistralModels.MISTRAL_LARGE_LATEST,
|
|
227
|
+
],
|
|
228
|
+
errorRules: [
|
|
229
|
+
{
|
|
230
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
231
|
+
/API_KEY_INVALID|Invalid API key|Unauthorized/i.test(ctx.message),
|
|
232
|
+
errorClass: AuthenticationError,
|
|
233
|
+
message: "Invalid Mistral API key. Please check your MISTRAL_API_KEY environment variable.",
|
|
234
|
+
},
|
|
235
|
+
...DEFAULT_ERROR_RULES,
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
providerName: AIProviderName.CLOUDFLARE,
|
|
240
|
+
aliases: ["cloudflare", "workers-ai", "cf-ai"],
|
|
241
|
+
apiKeyEnvVar: "CLOUDFLARE_API_KEY",
|
|
242
|
+
computedBaseURL: {
|
|
243
|
+
envVar: "CLOUDFLARE_ACCOUNT_ID",
|
|
244
|
+
missingValueMessage: "CLOUDFLARE_ACCOUNT_ID is required (or pass credentials.cloudflare.accountId). Get the account id from https://dash.cloudflare.com/",
|
|
245
|
+
build: buildCloudflareBaseURL,
|
|
246
|
+
},
|
|
247
|
+
configOptions: createCloudflareConfig(),
|
|
248
|
+
modelEnvVar: "CLOUDFLARE_MODEL",
|
|
249
|
+
defaultModel: CloudflareModels.LLAMA_3_3_70B_FAST,
|
|
250
|
+
registryDefaultModel: CloudflareModels.LLAMA_3_3_70B_FAST,
|
|
251
|
+
registryDefaultModelChecksEnvVar: true,
|
|
252
|
+
fallbackModelName: CloudflareModels.LLAMA_3_1_8B_FAST,
|
|
253
|
+
fallbackModels: [
|
|
254
|
+
CloudflareModels.LLAMA_3_3_70B_FAST,
|
|
255
|
+
CloudflareModels.LLAMA_3_1_70B_INSTRUCT,
|
|
256
|
+
CloudflareModels.LLAMA_3_1_8B_FAST,
|
|
257
|
+
CloudflareModels.LLAMA_3_2_11B_VISION,
|
|
258
|
+
CloudflareModels.MISTRAL_7B_INSTRUCT_V0_2,
|
|
259
|
+
CloudflareModels.QWEN_1P5_14B_CHAT_AWQ,
|
|
260
|
+
],
|
|
261
|
+
errorRules: [
|
|
262
|
+
{
|
|
263
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
264
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
265
|
+
errorClass: AuthenticationError,
|
|
266
|
+
message: "Invalid Cloudflare API key. Use a token with Workers AI Read+Write scope. Get one at https://dash.cloudflare.com/profile/api-tokens",
|
|
267
|
+
},
|
|
268
|
+
...DEFAULT_ERROR_RULES,
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
];
|
|
@@ -8,6 +8,7 @@ import type { ValidationSchema } from "./aliases.js";
|
|
|
8
8
|
import type { EnhancedGenerateResult, GenerateResult, TextGenerationOptions } from "./generate.js";
|
|
9
9
|
import type { MultimodalAudioEntry } from "./file.js";
|
|
10
10
|
import type { StreamOptions, StreamResult } from "./stream.js";
|
|
11
|
+
import type { ProviderErrorRule } from "./errors.js";
|
|
11
12
|
import type { ExternalMCPToolInfo } from "./externalMcp.js";
|
|
12
13
|
import type { ClaudeSubscriptionTier, AnthropicAuthMethod, AnthropicAuthConfig, SubscriptionInfo, OAuthToken } from "./subscription.js";
|
|
13
14
|
import type { Tool } from "./tools.js";
|
|
@@ -577,6 +578,104 @@ export type ProviderConfigOptions = {
|
|
|
577
578
|
fallbackEnvVars?: string[];
|
|
578
579
|
optional?: boolean;
|
|
579
580
|
};
|
|
581
|
+
/**
|
|
582
|
+
* Minimal credential shape accepted by resolveOpenAICompatConfig() and
|
|
583
|
+
* ConfiguredOpenAICompatProvider. A structural superset of every real
|
|
584
|
+
* per-provider NeurolinkCredentials["<key>"] slice in this family (groq,
|
|
585
|
+
* xai, together, fireworks, perplexity, mistral, cloudflare) — all fields
|
|
586
|
+
* optional, so passing e.g. NeurolinkCredentials["groq"] (which has no
|
|
587
|
+
* accountId) here is always structurally valid.
|
|
588
|
+
*/
|
|
589
|
+
export type OpenAICompatCredentials = {
|
|
590
|
+
apiKey?: string;
|
|
591
|
+
baseURL?: string;
|
|
592
|
+
accountId?: string;
|
|
593
|
+
};
|
|
594
|
+
/**
|
|
595
|
+
* One row of the config-driven OpenAI-compatible provider catalog
|
|
596
|
+
* (OPENAI_COMPAT_CATALOG, src/lib/providers/openaiCompatCatalog.ts).
|
|
597
|
+
* Replaces a hand-written OpenAIChatCompletionsProvider subclass for
|
|
598
|
+
* providers whose only differences from every sibling are credentials,
|
|
599
|
+
* base URL, model defaults, and error-message classification.
|
|
600
|
+
*/
|
|
601
|
+
export type OpenAICompatCatalogEntry = {
|
|
602
|
+
/** Registry key / nl.generate({provider}) value, e.g. "groq". */
|
|
603
|
+
providerName: AIProviderName;
|
|
604
|
+
/** Registry aliases, e.g. ["together-ai", "together"]. */
|
|
605
|
+
aliases: string[];
|
|
606
|
+
/**
|
|
607
|
+
* Env var holding the API key, e.g. "GROQ_API_KEY".
|
|
608
|
+
*
|
|
609
|
+
* Declarative: the key is actually read through `configOptions.envVarName`,
|
|
610
|
+
* which `validateApiKey` consults. This field exists so an entry states its
|
|
611
|
+
* credential source without a caller having to reach into configOptions,
|
|
612
|
+
* and the catalog suite asserts the two always name the same variable — two
|
|
613
|
+
* fields describing one fact are worth nothing if they can disagree.
|
|
614
|
+
*/
|
|
615
|
+
apiKeyEnvVar: string;
|
|
616
|
+
/**
|
|
617
|
+
* Env var that can override the base URL, e.g. "GROQ_BASE_URL". Omit
|
|
618
|
+
* for entries that use computedBaseURL instead (e.g. Cloudflare).
|
|
619
|
+
*/
|
|
620
|
+
baseURLEnvVar?: string;
|
|
621
|
+
/** Static default base URL. Omit for computedBaseURL entries. */
|
|
622
|
+
defaultBaseURL?: string;
|
|
623
|
+
/**
|
|
624
|
+
* Present only for providers whose base URL is computed from an extra
|
|
625
|
+
* required credential value instead of a static default (Cloudflare's
|
|
626
|
+
* accountId). Deliberately narrow (accountId-shaped) rather than a
|
|
627
|
+
* generic extra-field mechanism — Cloudflare is the only current user.
|
|
628
|
+
*/
|
|
629
|
+
computedBaseURL?: {
|
|
630
|
+
/** Env var fallback for the extra value, e.g. "CLOUDFLARE_ACCOUNT_ID". */
|
|
631
|
+
envVar: string;
|
|
632
|
+
/** Thrown when neither credentials.accountId nor envVar supply a value. */
|
|
633
|
+
missingValueMessage: string;
|
|
634
|
+
/** Builds the base URL from the resolved accountId. */
|
|
635
|
+
build: (accountId: string) => string;
|
|
636
|
+
};
|
|
637
|
+
/** Setup/help metadata, passed to validateApiKey(). Not consumed by
|
|
638
|
+
* classifyProviderError() — that function's ProviderErrorContext has no
|
|
639
|
+
* docsUrl field; any URL a rule's message needs is inlined in the rule
|
|
640
|
+
* itself (see Task 4). */
|
|
641
|
+
configOptions: ProviderConfigOptions;
|
|
642
|
+
/** Env var for the default model, e.g. "GROQ_MODEL". */
|
|
643
|
+
modelEnvVar: string;
|
|
644
|
+
/** Default model when modelEnvVar is unset. */
|
|
645
|
+
defaultModel: string;
|
|
646
|
+
/**
|
|
647
|
+
* The literal passed as ProviderFactory.registerProvider()'s defaultModel
|
|
648
|
+
* argument (resolved before the provider is constructed). Preserves each
|
|
649
|
+
* provider's exact pre-migration registry behavior.
|
|
650
|
+
*/
|
|
651
|
+
registryDefaultModel: string;
|
|
652
|
+
/**
|
|
653
|
+
* True for every provider except Mistral: whether the registry-level
|
|
654
|
+
* default also consults modelEnvVar before falling back to
|
|
655
|
+
* registryDefaultModel. False is a pre-existing, intentionally-preserved
|
|
656
|
+
* quirk unique to Mistral's registration (see plan's Design reference).
|
|
657
|
+
*/
|
|
658
|
+
registryDefaultModelChecksEnvVar: boolean;
|
|
659
|
+
/** Fallback model name (getFallbackModelName()). */
|
|
660
|
+
fallbackModelName: string;
|
|
661
|
+
/** Fallback model list (getFallbackModels()). */
|
|
662
|
+
fallbackModels: string[];
|
|
663
|
+
/**
|
|
664
|
+
* Error-classification rules, consumed by classifyProviderError. Typed
|
|
665
|
+
* as a mutable array — not readonly — because plan 07's
|
|
666
|
+
* `classifyProviderError(error, rules: ProviderErrorRule[], provider, modelName?)`
|
|
667
|
+
* declares `rules` as `ProviderErrorRule[]`; a `readonly` array here
|
|
668
|
+
* would not be assignable to that parameter without a cast, which rule
|
|
669
|
+
* 14 (no double assertions) and general hygiene both rule out. Each
|
|
670
|
+
* entry's array is still constructed as a fresh literal per provider in
|
|
671
|
+
* Task 4, so nothing actually mutates it at runtime.
|
|
672
|
+
*/
|
|
673
|
+
errorRules: ProviderErrorRule[];
|
|
674
|
+
};
|
|
675
|
+
/** The subset of OpenAICompatCatalogEntry that resolveOpenAICompatConfig()
|
|
676
|
+
* needs — lets call sites pass a minimal object without the full catalog
|
|
677
|
+
* entry (e.g. in tests, or a future non-catalog caller). */
|
|
678
|
+
export type OpenAICompatConfigInput = Pick<OpenAICompatCatalogEntry, "providerName" | "apiKeyEnvVar" | "baseURLEnvVar" | "defaultBaseURL" | "computedBaseURL" | "configOptions">;
|
|
580
679
|
/**
|
|
581
680
|
* AI Provider type with flexible parameter support
|
|
582
681
|
*/
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Enhanced with format validation and advanced error classification
|
|
6
6
|
* Extended with Claude subscription OAuth support
|
|
7
7
|
*/
|
|
8
|
-
import type { APIValidationResult, ProviderConfigOptions, AnthropicAuthMethod, ClaudeSubscriptionTier, AnthropicAuthConfig, AnthropicAuthConfigResult } from "../types/index.js";
|
|
8
|
+
import type { APIValidationResult, ProviderConfigOptions, AnthropicAuthMethod, ClaudeSubscriptionTier, AnthropicAuthConfig, AnthropicAuthConfigResult, OpenAICompatConfigInput, OpenAICompatCredentials } from "../types/index.js";
|
|
9
9
|
/**
|
|
10
10
|
* API key format validation patterns (extracted from advanced validation system)
|
|
11
11
|
* Exported for use across the codebase to replace scattered regex patterns
|
|
@@ -369,3 +369,25 @@ export declare function hasSubscriptionFeature(feature: "extended_thinking" | "p
|
|
|
369
369
|
* @returns Human-readable configuration description
|
|
370
370
|
*/
|
|
371
371
|
export declare function describeAnthropicConfig(): string;
|
|
372
|
+
/**
|
|
373
|
+
* Resolves the {apiKey, baseURL} pair for a config-driven OpenAI-compatible
|
|
374
|
+
* catalog entry (see OpenAICompatCatalogEntry in types/providers.ts).
|
|
375
|
+
*
|
|
376
|
+
* Extracted from the identical 6-line precedence block that was copy-pasted
|
|
377
|
+
* across groq.ts, xai.ts, togetherAi.ts, fireworks.ts, perplexity.ts, and
|
|
378
|
+
* mistral.ts, plus Cloudflare's accountId-computed-baseURL variant.
|
|
379
|
+
*
|
|
380
|
+
* Precedence (matches every ported subclass's original behavior exactly):
|
|
381
|
+
* apiKey: credentials.apiKey (trimmed, non-blank) > env var > throw
|
|
382
|
+
* baseURL: credentials.baseURL (trimmed, non-blank)
|
|
383
|
+
* > env var (if entry.baseURLEnvVar is set, trimmed, non-blank)
|
|
384
|
+
* > entry.defaultBaseURL
|
|
385
|
+
* baseURL (computedBaseURL entries, e.g. Cloudflare):
|
|
386
|
+
* credentials.baseURL > computedBaseURL.build(accountId), where
|
|
387
|
+
* accountId = credentials.accountId (trimmed) > env var (trimmed)
|
|
388
|
+
* > throw computedBaseURL.missingValueMessage
|
|
389
|
+
*/
|
|
390
|
+
export declare function resolveOpenAICompatConfig(entry: OpenAICompatConfigInput, credentials?: OpenAICompatCredentials): {
|
|
391
|
+
apiKey: string;
|
|
392
|
+
baseURL: string;
|
|
393
|
+
};
|
|
@@ -1300,3 +1300,63 @@ export function describeAnthropicConfig() {
|
|
|
1300
1300
|
lines.push(`Priority Access: ${config.limits.priorityAccess ? "Yes" : "No"}`);
|
|
1301
1301
|
return lines.join("\n");
|
|
1302
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Resolves the {apiKey, baseURL} pair for a config-driven OpenAI-compatible
|
|
1305
|
+
* catalog entry (see OpenAICompatCatalogEntry in types/providers.ts).
|
|
1306
|
+
*
|
|
1307
|
+
* Extracted from the identical 6-line precedence block that was copy-pasted
|
|
1308
|
+
* across groq.ts, xai.ts, togetherAi.ts, fireworks.ts, perplexity.ts, and
|
|
1309
|
+
* mistral.ts, plus Cloudflare's accountId-computed-baseURL variant.
|
|
1310
|
+
*
|
|
1311
|
+
* Precedence (matches every ported subclass's original behavior exactly):
|
|
1312
|
+
* apiKey: credentials.apiKey (trimmed, non-blank) > env var > throw
|
|
1313
|
+
* baseURL: credentials.baseURL (trimmed, non-blank)
|
|
1314
|
+
* > env var (if entry.baseURLEnvVar is set, trimmed, non-blank)
|
|
1315
|
+
* > entry.defaultBaseURL
|
|
1316
|
+
* baseURL (computedBaseURL entries, e.g. Cloudflare):
|
|
1317
|
+
* credentials.baseURL > computedBaseURL.build(accountId), where
|
|
1318
|
+
* accountId = credentials.accountId (trimmed) > env var (trimmed)
|
|
1319
|
+
* > throw computedBaseURL.missingValueMessage
|
|
1320
|
+
*/
|
|
1321
|
+
export function resolveOpenAICompatConfig(entry, credentials) {
|
|
1322
|
+
const overrideApiKey = credentials?.apiKey?.trim();
|
|
1323
|
+
const apiKey = overrideApiKey && overrideApiKey.length > 0
|
|
1324
|
+
? overrideApiKey
|
|
1325
|
+
: validateApiKey(entry.configOptions);
|
|
1326
|
+
if (entry.computedBaseURL) {
|
|
1327
|
+
const { envVar, missingValueMessage, build } = entry.computedBaseURL;
|
|
1328
|
+
// An explicit base URL is checked first and trimmed the same way the
|
|
1329
|
+
// static branch trims it. It makes the account id irrelevant — there is
|
|
1330
|
+
// nothing left to build — so demanding one anyway would reject a fully
|
|
1331
|
+
// specified override.
|
|
1332
|
+
const overrideComputedBaseURL = credentials?.baseURL?.trim();
|
|
1333
|
+
if (overrideComputedBaseURL && overrideComputedBaseURL.length > 0) {
|
|
1334
|
+
return { apiKey, baseURL: overrideComputedBaseURL };
|
|
1335
|
+
}
|
|
1336
|
+
const extraValue = (credentials?.accountId ??
|
|
1337
|
+
process.env[envVar] ??
|
|
1338
|
+
"").trim();
|
|
1339
|
+
if (!extraValue) {
|
|
1340
|
+
throw new Error(missingValueMessage);
|
|
1341
|
+
}
|
|
1342
|
+
return { apiKey, baseURL: build(extraValue) };
|
|
1343
|
+
}
|
|
1344
|
+
const overrideBaseURL = credentials?.baseURL?.trim();
|
|
1345
|
+
const envBaseURL = entry.baseURLEnvVar
|
|
1346
|
+
? process.env[entry.baseURLEnvVar]?.trim()
|
|
1347
|
+
: undefined;
|
|
1348
|
+
const baseURL = (overrideBaseURL && overrideBaseURL.length > 0
|
|
1349
|
+
? overrideBaseURL
|
|
1350
|
+
: undefined) ??
|
|
1351
|
+
(envBaseURL && envBaseURL.length > 0 ? envBaseURL : undefined) ??
|
|
1352
|
+
entry.defaultBaseURL;
|
|
1353
|
+
if (!baseURL) {
|
|
1354
|
+
// Reachable only for an entry that sets neither defaultBaseURL nor
|
|
1355
|
+
// computedBaseURL. Returning "" instead would hand the SDK an empty base
|
|
1356
|
+
// URL and surface as a confusing request failure far from the cause.
|
|
1357
|
+
throw new Error(`${entry.providerName}: no base URL. Set one in credentials` +
|
|
1358
|
+
(entry.baseURLEnvVar ? `, set ${entry.baseURLEnvVar}` : "") +
|
|
1359
|
+
`, or give the catalog entry a defaultBaseURL.`);
|
|
1360
|
+
}
|
|
1361
|
+
return { apiKey, baseURL };
|
|
1362
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.2.0",
|
|
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": {
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"test:mcp:spans": "npx tsx test/continuous-test-suite-mcp-spans.ts",
|
|
95
95
|
"test:mcp:infra": "npx tsx test/continuous-test-suite-mcp-infra.ts",
|
|
96
96
|
"test:providers-mocked": "npx tsx test/continuous-test-suite-providers-mocked.ts",
|
|
97
|
+
"test:openai-compat-catalog": "npx tsx test/continuous-test-suite-openai-compat-catalog.ts",
|
|
97
98
|
"test:provider-descriptors": "npx tsx test/continuous-test-suite-provider-descriptors.ts",
|
|
98
99
|
"test:provider-structure": "npx tsx test/continuous-test-suite-provider-structure.ts",
|
|
99
100
|
"test:provider-fallback": "npx tsx test/continuous-test-suite-provider-fallback.ts",
|