@juspay/neurolink 9.68.21 → 9.69.1
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 +276 -276
- package/dist/lib/providers/deepseek.d.ts +6 -6
- package/dist/lib/providers/deepseek.js +6 -6
- package/dist/lib/providers/nvidiaNim.d.ts +20 -4
- package/dist/lib/providers/nvidiaNim.js +45 -4
- package/dist/lib/providers/openaiChatCompletionsBase.d.ts +12 -0
- package/dist/lib/providers/openaiChatCompletionsBase.js +83 -7
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +1 -1
- package/dist/lib/providers/openaiChatCompletionsClient.js +19 -1
- package/dist/lib/types/openaiCompatible.d.ts +15 -0
- package/dist/lib/types/stream.d.ts +1 -0
- package/dist/providers/deepseek.d.ts +6 -6
- package/dist/providers/deepseek.js +6 -6
- package/dist/providers/nvidiaNim.d.ts +20 -4
- package/dist/providers/nvidiaNim.js +45 -4
- package/dist/providers/openaiChatCompletionsBase.d.ts +12 -0
- package/dist/providers/openaiChatCompletionsBase.js +83 -7
- package/dist/providers/openaiChatCompletionsClient.d.ts +1 -1
- package/dist/providers/openaiChatCompletionsClient.js +19 -1
- package/dist/types/openaiCompatible.d.ts +15 -0
- package/dist/types/stream.d.ts +1 -0
- package/package.json +2 -1
|
@@ -86,6 +86,17 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
86
86
|
adjustRequestBody(body, _modelId) {
|
|
87
87
|
return body;
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Hook called when a request fails with HTTP 400. Return a modified body to
|
|
91
|
+
* retry ONCE with it; return undefined (default) to propagate the error
|
|
92
|
+
* unchanged. Applies on both the non-streaming doGenerate path and the
|
|
93
|
+
* streaming path. NVIDIA NIM uses this to strip `chat_template` /
|
|
94
|
+
* `chat_template_kwargs.reasoning_budget` when a model server rejects them,
|
|
95
|
+
* restoring the pre-migration fetch-level retry behavior.
|
|
96
|
+
*/
|
|
97
|
+
adjustBodyAfter400(_body, _error) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
89
100
|
/**
|
|
90
101
|
* Hook called once at the start of every `executeStream` invocation.
|
|
91
102
|
* Return lifecycle listeners (onUsage / onFinish) to receive deferred
|
|
@@ -209,6 +220,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
209
220
|
const adjustBuildBodyOptions = this.adjustBuildBodyOptions.bind(this);
|
|
210
221
|
const adjustResponseFormat = this.adjustResponseFormat.bind(this);
|
|
211
222
|
const adjustRequestBody = this.adjustRequestBody.bind(this);
|
|
223
|
+
const adjustBodyAfter400 = this.adjustBodyAfter400.bind(this);
|
|
212
224
|
const getTimeoutForOptions = (opts) => this.getTimeout((opts ?? {}));
|
|
213
225
|
return {
|
|
214
226
|
specificationVersion: "v3",
|
|
@@ -256,19 +268,52 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
256
268
|
body: JSON.stringify(body),
|
|
257
269
|
...(composedSignal ? { signal: composedSignal } : {}),
|
|
258
270
|
});
|
|
271
|
+
if (!res.ok) {
|
|
272
|
+
const apiErr = await buildAPIError(url, body, res);
|
|
273
|
+
// One-shot 400 retry: a subclass may strip a rejected field and
|
|
274
|
+
// return a modified body (e.g. NIM's chat_template /
|
|
275
|
+
// reasoning_budget). The retry runs under the SAME timeout
|
|
276
|
+
// controller as the first attempt, so the configured timeout caps
|
|
277
|
+
// the overall call — matching the streaming path, which reuses
|
|
278
|
+
// its composed signal for the retry.
|
|
279
|
+
const retryBody = res.status === 400
|
|
280
|
+
? adjustBodyAfter400(body, apiErr)
|
|
281
|
+
: undefined;
|
|
282
|
+
if (!retryBody) {
|
|
283
|
+
throw apiErr;
|
|
284
|
+
}
|
|
285
|
+
res = await fetchImpl(url, {
|
|
286
|
+
method: "POST",
|
|
287
|
+
headers: {
|
|
288
|
+
"Content-Type": "application/json",
|
|
289
|
+
...getAuthHeaders(),
|
|
290
|
+
},
|
|
291
|
+
body: JSON.stringify(retryBody),
|
|
292
|
+
...(composedSignal ? { signal: composedSignal } : {}),
|
|
293
|
+
});
|
|
294
|
+
if (!res.ok) {
|
|
295
|
+
throw await buildAPIError(url, retryBody, res);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
259
298
|
}
|
|
260
299
|
finally {
|
|
261
300
|
timeoutController?.cleanup();
|
|
262
301
|
}
|
|
263
|
-
if (!res.ok) {
|
|
264
|
-
throw await buildAPIError(url, body, res);
|
|
265
|
-
}
|
|
266
302
|
const json = (await res.json());
|
|
267
303
|
const choice = json.choices?.[0];
|
|
268
304
|
const text = (typeof choice?.message?.content === "string"
|
|
269
305
|
? choice.message.content
|
|
270
306
|
: "") ?? "";
|
|
271
307
|
const content = [];
|
|
308
|
+
// Reasoner-model output (DeepSeek `reasoning_content`, gateway
|
|
309
|
+
// `reasoning`) becomes a V3 reasoning part ahead of the text part —
|
|
310
|
+
// GenerationHandler joins reasoning parts into `result.reasoning`.
|
|
311
|
+
// `||` so an empty-string reasoning_content falls through to a
|
|
312
|
+
// non-empty `reasoning` field instead of shadowing it.
|
|
313
|
+
const reasoningText = choice?.message?.reasoning_content || choice?.message?.reasoning;
|
|
314
|
+
if (typeof reasoningText === "string" && reasoningText.length > 0) {
|
|
315
|
+
content.push({ type: "reasoning", text: reasoningText });
|
|
316
|
+
}
|
|
272
317
|
if (text.length > 0) {
|
|
273
318
|
content.push({ type: "text", text });
|
|
274
319
|
}
|
|
@@ -300,8 +345,15 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
300
345
|
},
|
|
301
346
|
outputTokens: {
|
|
302
347
|
total: json.usage?.completion_tokens,
|
|
303
|
-
|
|
304
|
-
|
|
348
|
+
// Clamped at 0 — some gateways report inconsistent usage where
|
|
349
|
+
// reasoning_tokens exceeds completion_tokens.
|
|
350
|
+
text: json.usage?.completion_tokens !== undefined &&
|
|
351
|
+
json.usage?.completion_tokens_details?.reasoning_tokens !==
|
|
352
|
+
undefined
|
|
353
|
+
? Math.max(0, json.usage.completion_tokens -
|
|
354
|
+
json.usage.completion_tokens_details.reasoning_tokens)
|
|
355
|
+
: json.usage?.completion_tokens,
|
|
356
|
+
reasoning: json.usage?.completion_tokens_details?.reasoning_tokens,
|
|
305
357
|
},
|
|
306
358
|
},
|
|
307
359
|
warnings: [],
|
|
@@ -567,7 +619,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
567
619
|
: {}),
|
|
568
620
|
streaming: true,
|
|
569
621
|
}), args.modelId));
|
|
570
|
-
|
|
622
|
+
let res = await args.fetchImpl(args.url, {
|
|
571
623
|
method: "POST",
|
|
572
624
|
headers: {
|
|
573
625
|
"Content-Type": "application/json",
|
|
@@ -577,13 +629,37 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
577
629
|
...(args.abortSignal ? { signal: args.abortSignal } : {}),
|
|
578
630
|
});
|
|
579
631
|
if (!res.ok) {
|
|
580
|
-
|
|
632
|
+
const apiErr = await buildAPIError(args.url, body, res);
|
|
633
|
+
// One-shot 400 retry — same hook as the doGenerate path (e.g. NIM
|
|
634
|
+
// strips chat_template/reasoning_budget when a model rejects them).
|
|
635
|
+
const retryBody = res.status === 400
|
|
636
|
+
? this.adjustBodyAfter400(body, apiErr)
|
|
637
|
+
: undefined;
|
|
638
|
+
if (!retryBody) {
|
|
639
|
+
throw apiErr;
|
|
640
|
+
}
|
|
641
|
+
res = await args.fetchImpl(args.url, {
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers: {
|
|
644
|
+
"Content-Type": "application/json",
|
|
645
|
+
...this.getAuthHeaders(),
|
|
646
|
+
},
|
|
647
|
+
body: JSON.stringify(retryBody),
|
|
648
|
+
...(args.abortSignal ? { signal: args.abortSignal } : {}),
|
|
649
|
+
});
|
|
650
|
+
if (!res.ok) {
|
|
651
|
+
throw await buildAPIError(args.url, retryBody, res);
|
|
652
|
+
}
|
|
581
653
|
}
|
|
582
654
|
if (!res.body) {
|
|
583
655
|
throw new Error(`${this.providerName}: stream response had no body`);
|
|
584
656
|
}
|
|
585
657
|
return parseSSEStream(res.body, (delta) => {
|
|
586
658
|
args.pushChunk({ content: delta });
|
|
659
|
+
}, (reasoningDelta) => {
|
|
660
|
+
// Reasoning rides alongside an empty `content` so plain-text
|
|
661
|
+
// consumers (which only read chunk.content) are unaffected.
|
|
662
|
+
args.pushChunk({ content: "", reasoning: reasoningDelta });
|
|
587
663
|
});
|
|
588
664
|
}
|
|
589
665
|
async executeToolBatch(args) {
|
|
@@ -35,7 +35,7 @@ export declare const messagesContainJsonWord: (messages: ReadonlyArray<OpenAICom
|
|
|
35
35
|
export declare const ensureJsonWordInBody: (body: OpenAICompatChatRequest) => OpenAICompatChatRequest;
|
|
36
36
|
export declare const requiresMaxCompletionTokens: (modelId: string) => boolean;
|
|
37
37
|
export declare const buildBody: (args: OpenAICompatBuildBodyArgs) => OpenAICompatChatRequest;
|
|
38
|
-
export declare const parseSSEStream: (body: ReadableStream<Uint8Array>, onTextDelta: (delta: string) => void) => Promise<OpenAICompatSSEResult>;
|
|
38
|
+
export declare const parseSSEStream: (body: ReadableStream<Uint8Array>, onTextDelta: (delta: string) => void, onReasoningDelta?: (delta: string) => void) => Promise<OpenAICompatSSEResult>;
|
|
39
39
|
export declare const buildAPIError: (url: string, body: OpenAICompatChatRequest, res: Response) => Promise<Error>;
|
|
40
40
|
export declare const createDeferredAnalytics: () => {
|
|
41
41
|
usagePromise: Promise<{
|
|
@@ -397,11 +397,19 @@ export const buildBody = (args) => {
|
|
|
397
397
|
if (responseFormat) {
|
|
398
398
|
body.response_format = responseFormat;
|
|
399
399
|
}
|
|
400
|
+
// Provider-specific extras attached via options.extraBody (the explicit
|
|
401
|
+
// channel from adjustBuildBodyOptions) land verbatim on the wire body.
|
|
402
|
+
// Spread last — a provider that puts a core field here is intentionally
|
|
403
|
+
// overriding it.
|
|
404
|
+
if (options.extraBody) {
|
|
405
|
+
Object.assign(body, options.extraBody);
|
|
406
|
+
}
|
|
400
407
|
return body;
|
|
401
408
|
};
|
|
402
|
-
export const parseSSEStream = async (body, onTextDelta) => {
|
|
409
|
+
export const parseSSEStream = async (body, onTextDelta, onReasoningDelta) => {
|
|
403
410
|
const result = {
|
|
404
411
|
text: "",
|
|
412
|
+
reasoning: "",
|
|
405
413
|
toolCalls: new Map(),
|
|
406
414
|
finishReason: null,
|
|
407
415
|
usage: undefined,
|
|
@@ -433,6 +441,16 @@ export const parseSSEStream = async (body, onTextDelta) => {
|
|
|
433
441
|
result.text += delta.content;
|
|
434
442
|
onTextDelta(delta.content);
|
|
435
443
|
}
|
|
444
|
+
// Reasoner-model deltas: DeepSeek/NIM emit `reasoning_content`, some
|
|
445
|
+
// gateways emit `reasoning`. The AI SDK's openai-compatible wrapper
|
|
446
|
+
// surfaced these automatically; the native client must do the same.
|
|
447
|
+
// `||` (not `??`) so an empty-string reasoning_content falls through to
|
|
448
|
+
// a non-empty `reasoning` field instead of shadowing it.
|
|
449
|
+
const reasoningDelta = delta?.reasoning_content || delta?.reasoning;
|
|
450
|
+
if (reasoningDelta) {
|
|
451
|
+
result.reasoning += reasoningDelta;
|
|
452
|
+
onReasoningDelta?.(reasoningDelta);
|
|
453
|
+
}
|
|
436
454
|
if (delta?.tool_calls) {
|
|
437
455
|
for (const tc of delta.tool_calls) {
|
|
438
456
|
let state = result.toolCalls.get(tc.index);
|
|
@@ -104,6 +104,8 @@ export type OpenAICompatChatChoiceMessage = {
|
|
|
104
104
|
content?: string | null;
|
|
105
105
|
tool_calls?: OpenAICompatToolCallWire[];
|
|
106
106
|
refusal?: string | null;
|
|
107
|
+
reasoning_content?: string | null;
|
|
108
|
+
reasoning?: string | null;
|
|
107
109
|
};
|
|
108
110
|
export type OpenAICompatChatChoice = {
|
|
109
111
|
index: number;
|
|
@@ -132,6 +134,8 @@ export type OpenAICompatStreamDelta = {
|
|
|
132
134
|
};
|
|
133
135
|
}>;
|
|
134
136
|
refusal?: string | null;
|
|
137
|
+
reasoning_content?: string | null;
|
|
138
|
+
reasoning?: string | null;
|
|
135
139
|
};
|
|
136
140
|
export type OpenAICompatStreamChunkChoice = {
|
|
137
141
|
index: number;
|
|
@@ -186,6 +190,8 @@ export type OpenAICompatMessage = {
|
|
|
186
190
|
};
|
|
187
191
|
export type OpenAICompatSSEResult = {
|
|
188
192
|
text: string;
|
|
193
|
+
/** Accumulated reasoner-model output (`reasoning_content` / `reasoning` deltas). */
|
|
194
|
+
reasoning: string;
|
|
189
195
|
toolCalls: Map<number, {
|
|
190
196
|
id: string;
|
|
191
197
|
name: string;
|
|
@@ -196,6 +202,7 @@ export type OpenAICompatSSEResult = {
|
|
|
196
202
|
};
|
|
197
203
|
export type OpenAICompatStreamChunk = {
|
|
198
204
|
content: string;
|
|
205
|
+
reasoning?: string;
|
|
199
206
|
} | {
|
|
200
207
|
done: true;
|
|
201
208
|
};
|
|
@@ -241,6 +248,14 @@ export type OpenAICompatBuildBodyArgs = {
|
|
|
241
248
|
frequencyPenalty?: number | null;
|
|
242
249
|
seed?: number | null;
|
|
243
250
|
stopSequences?: string[];
|
|
251
|
+
/**
|
|
252
|
+
* Provider-specific extra wire fields, spread verbatim onto the final
|
|
253
|
+
* request body by `buildBody` (after the standard fields). This is the
|
|
254
|
+
* explicit channel for non-OpenAI knobs (e.g. NVIDIA NIM's `top_k` /
|
|
255
|
+
* `chat_template_kwargs`) — loose unknown keys returned from
|
|
256
|
+
* `adjustBuildBodyOptions` are intentionally NOT forwarded.
|
|
257
|
+
*/
|
|
258
|
+
extraBody?: Record<string, unknown>;
|
|
244
259
|
};
|
|
245
260
|
tools?: OpenAICompatChatTool[];
|
|
246
261
|
toolChoice?: OpenAICompatToolChoiceWire;
|
package/dist/types/stream.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.69.1",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applications with 21+ providers: OpenAI, Anthropic, Google AI Studio, Google Vertex, AWS Bedrock, Azure OpenAI, Mistral, LiteLLM, SageMaker, Hugging Face, Ollama, OpenAI-compatible, OpenRouter, DeepSeek, NVIDIA NIM, LM Studio, llama.cpp, plus voice (OpenAI TTS, ElevenLabs, Deepgram, Azure Speech).",
|
|
6
6
|
"author": {
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
"test:image-gen": "npx tsx test/continuous-test-suite-image-gen-extras.ts",
|
|
103
103
|
"test:ssrf": "npx tsx test/continuous-test-suite-ssrf.ts",
|
|
104
104
|
"test:log-sanitize": "npx tsx test/continuous-test-suite-log-sanitize.ts",
|
|
105
|
+
"test:sse-client": "npx tsx test/continuous-test-suite-sse-client.ts",
|
|
105
106
|
"test:stream-span": "npx tsx test/continuous-test-suite-stream-span.ts",
|
|
106
107
|
"test:credentials": "npx tsx test/continuous-test-suite-credentials.ts",
|
|
107
108
|
"test:dynamic": "npx tsx test/continuous-test-suite-dynamic.ts",
|