@juspay/neurolink 9.67.3 → 9.68.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 +282 -282
- package/dist/lib/providers/openaiChatCompletionsBase.d.ts +31 -1
- package/dist/lib/providers/openaiChatCompletionsBase.js +62 -21
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +3 -0
- package/dist/lib/providers/openaiChatCompletionsClient.js +39 -0
- package/dist/lib/types/openaiCompatible.d.ts +0 -1
- package/dist/providers/openaiChatCompletionsBase.d.ts +31 -1
- package/dist/providers/openaiChatCompletionsBase.js +62 -21
- package/dist/providers/openaiChatCompletionsClient.d.ts +3 -0
- package/dist/providers/openaiChatCompletionsClient.js +39 -0
- package/dist/types/openaiCompatible.d.ts +0 -1
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { AIProviderName } from "../constants/enums.js";
|
|
21
21
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
22
|
-
import type { LanguageModel, OpenAICompatBuildBodyArgs, OpenAICompatStreamLifecycleListeners, Schema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
|
|
22
|
+
import type { LanguageModel, OpenAICompatBuildBodyArgs, OpenAICompatChatRequest, OpenAICompatResponseFormat, OpenAICompatStreamLifecycleListeners, Schema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
|
|
23
23
|
/**
|
|
24
24
|
* Abstract HTTP+SSE provider for OpenAI chat-completions-shaped endpoints.
|
|
25
25
|
*/
|
|
@@ -52,6 +52,24 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
52
52
|
* (e.g. LiteLLM's Gemini 2.5 maxTokens skip).
|
|
53
53
|
*/
|
|
54
54
|
protected adjustBuildBodyOptions(_modelId: string, opts: OpenAICompatBuildBodyArgs["options"]): OpenAICompatBuildBodyArgs["options"];
|
|
55
|
+
/**
|
|
56
|
+
* Hook to adjust the OpenAI `response_format` after it's converted from the
|
|
57
|
+
* V3 responseFormat (non-streaming `doGenerate` path). Default identity.
|
|
58
|
+
* Override for providers that don't support a given format type — e.g.
|
|
59
|
+
* DeepSeek rejects `response_format: { type: "json_schema" }` ("This
|
|
60
|
+
* response_format type is unavailable now"); the `@ai-sdk/openai-compatible`
|
|
61
|
+
* path this replaced declared `supportsStructuredOutputs: false`, which
|
|
62
|
+
* downgraded `json_schema` to `json_object`. Subclasses replicate that here.
|
|
63
|
+
*/
|
|
64
|
+
protected adjustResponseFormat(rf: OpenAICompatResponseFormat | undefined, _modelId: string): OpenAICompatResponseFormat | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Hook to adjust the fully-built wire request body before it is sent, on
|
|
67
|
+
* both the streaming and non-streaming paths. Default identity. Override for
|
|
68
|
+
* provider/model quirks that can't be expressed through buildBody options —
|
|
69
|
+
* e.g. Azure's newer reasoning deployments (o-series, gpt-5+) reject
|
|
70
|
+
* `max_tokens` and require `max_completion_tokens`.
|
|
71
|
+
*/
|
|
72
|
+
protected adjustRequestBody(body: OpenAICompatChatRequest, _modelId: string): OpenAICompatChatRequest;
|
|
55
73
|
/**
|
|
56
74
|
* Hook called once at the start of every `executeStream` invocation.
|
|
57
75
|
* Return lifecycle listeners (onUsage / onFinish) to receive deferred
|
|
@@ -66,6 +84,18 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
66
84
|
* `getDefaultModel()` will never hit this branch anyway.
|
|
67
85
|
*/
|
|
68
86
|
protected shouldAutoDiscoverModel(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Builds the chat-completions request URL for a model. Default is
|
|
89
|
+
* `${baseURL}/chat/completions`. Override for providers with a different
|
|
90
|
+
* routing scheme (e.g. Azure's deployment-based path + api-version query).
|
|
91
|
+
*/
|
|
92
|
+
protected getChatCompletionsURL(_modelId: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Auth headers merged into every request. Default is a Bearer token.
|
|
95
|
+
* Override for providers that authenticate differently (e.g. Azure, which
|
|
96
|
+
* uses an `api-key` header instead of `Authorization: Bearer`).
|
|
97
|
+
*/
|
|
98
|
+
protected getAuthHeaders(): Record<string, string>;
|
|
69
99
|
supportsTools(): boolean;
|
|
70
100
|
/**
|
|
71
101
|
* Returns a minimal V3-shaped model used by BaseProvider's `generate()`
|
|
@@ -28,7 +28,7 @@ import { composeAbortSignals, createTimeoutController, mergeAbortSignals, } from
|
|
|
28
28
|
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
29
29
|
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
30
30
|
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
31
|
-
import { buildAPIError, buildBody, buildToolsForOpenAI, createChunkQueue, createDeferredAnalytics, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
|
|
31
|
+
import { buildAPIError, buildBody, buildToolsForOpenAI, createChunkQueue, createDeferredAnalytics, ensureJsonWordInBody, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
|
|
32
32
|
/**
|
|
33
33
|
* Abstract HTTP+SSE provider for OpenAI chat-completions-shaped endpoints.
|
|
34
34
|
*/
|
|
@@ -64,6 +64,28 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
64
64
|
adjustBuildBodyOptions(_modelId, opts) {
|
|
65
65
|
return opts;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Hook to adjust the OpenAI `response_format` after it's converted from the
|
|
69
|
+
* V3 responseFormat (non-streaming `doGenerate` path). Default identity.
|
|
70
|
+
* Override for providers that don't support a given format type — e.g.
|
|
71
|
+
* DeepSeek rejects `response_format: { type: "json_schema" }` ("This
|
|
72
|
+
* response_format type is unavailable now"); the `@ai-sdk/openai-compatible`
|
|
73
|
+
* path this replaced declared `supportsStructuredOutputs: false`, which
|
|
74
|
+
* downgraded `json_schema` to `json_object`. Subclasses replicate that here.
|
|
75
|
+
*/
|
|
76
|
+
adjustResponseFormat(rf, _modelId) {
|
|
77
|
+
return rf;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Hook to adjust the fully-built wire request body before it is sent, on
|
|
81
|
+
* both the streaming and non-streaming paths. Default identity. Override for
|
|
82
|
+
* provider/model quirks that can't be expressed through buildBody options —
|
|
83
|
+
* e.g. Azure's newer reasoning deployments (o-series, gpt-5+) reject
|
|
84
|
+
* `max_tokens` and require `max_completion_tokens`.
|
|
85
|
+
*/
|
|
86
|
+
adjustRequestBody(body, _modelId) {
|
|
87
|
+
return body;
|
|
88
|
+
}
|
|
67
89
|
/**
|
|
68
90
|
* Hook called once at the start of every `executeStream` invocation.
|
|
69
91
|
* Return lifecycle listeners (onUsage / onFinish) to receive deferred
|
|
@@ -82,6 +104,22 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
82
104
|
shouldAutoDiscoverModel() {
|
|
83
105
|
return true;
|
|
84
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Builds the chat-completions request URL for a model. Default is
|
|
109
|
+
* `${baseURL}/chat/completions`. Override for providers with a different
|
|
110
|
+
* routing scheme (e.g. Azure's deployment-based path + api-version query).
|
|
111
|
+
*/
|
|
112
|
+
getChatCompletionsURL(_modelId) {
|
|
113
|
+
return `${stripTrailingSlash(this.config.baseURL)}/chat/completions`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Auth headers merged into every request. Default is a Bearer token.
|
|
117
|
+
* Override for providers that authenticate differently (e.g. Azure, which
|
|
118
|
+
* uses an `api-key` header instead of `Authorization: Bearer`).
|
|
119
|
+
*/
|
|
120
|
+
getAuthHeaders() {
|
|
121
|
+
return { Authorization: `Bearer ${this.config.apiKey}` };
|
|
122
|
+
}
|
|
85
123
|
// ===========================================================================
|
|
86
124
|
// Public/protected concrete methods (shared by all subclasses)
|
|
87
125
|
// ===========================================================================
|
|
@@ -129,11 +167,13 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
129
167
|
return fallback;
|
|
130
168
|
}
|
|
131
169
|
buildDelegatingModel(modelId) {
|
|
132
|
-
const url =
|
|
170
|
+
const url = this.getChatCompletionsURL(modelId);
|
|
133
171
|
const fetchImpl = createProxyFetch();
|
|
134
|
-
const
|
|
172
|
+
const getAuthHeaders = this.getAuthHeaders.bind(this);
|
|
135
173
|
const providerName = this.providerName;
|
|
136
174
|
const adjustBuildBodyOptions = this.adjustBuildBodyOptions.bind(this);
|
|
175
|
+
const adjustResponseFormat = this.adjustResponseFormat.bind(this);
|
|
176
|
+
const adjustRequestBody = this.adjustRequestBody.bind(this);
|
|
137
177
|
const getTimeoutForOptions = (opts) => this.getTimeout((opts ?? {}));
|
|
138
178
|
return {
|
|
139
179
|
specificationVersion: "v3",
|
|
@@ -141,10 +181,17 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
141
181
|
modelId,
|
|
142
182
|
supportedUrls: {},
|
|
143
183
|
doGenerate: async (options) => {
|
|
144
|
-
const
|
|
145
|
-
const
|
|
184
|
+
const baseMessages = messageBuilderToOpenAI(options.prompt);
|
|
185
|
+
const responseFormat = options.responseFormat
|
|
186
|
+
? adjustResponseFormat(v3ResponseFormatToOpenAI(options.responseFormat), modelId)
|
|
187
|
+
: undefined;
|
|
188
|
+
// ensureJsonWordInBody runs LAST — on the body after adjustRequestBody —
|
|
189
|
+
// so the json_object word guard reflects whatever a subclass left on
|
|
190
|
+
// the wire (it may rewrite response_format/messages), not an
|
|
191
|
+
// intermediate state.
|
|
192
|
+
const body = ensureJsonWordInBody(adjustRequestBody(buildBody({
|
|
146
193
|
modelId,
|
|
147
|
-
messages,
|
|
194
|
+
messages: baseMessages,
|
|
148
195
|
options: adjustBuildBodyOptions(modelId, {
|
|
149
196
|
maxTokens: options.maxOutputTokens,
|
|
150
197
|
temperature: options.temperature,
|
|
@@ -159,12 +206,8 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
159
206
|
? { toolChoice: v3ToolChoiceToOpenAI(options.toolChoice) }
|
|
160
207
|
: {}),
|
|
161
208
|
streaming: false,
|
|
162
|
-
...(
|
|
163
|
-
|
|
164
|
-
responseFormat: v3ResponseFormatToOpenAI(options.responseFormat),
|
|
165
|
-
}
|
|
166
|
-
: {}),
|
|
167
|
-
});
|
|
209
|
+
...(responseFormat ? { responseFormat } : {}),
|
|
210
|
+
}), modelId));
|
|
168
211
|
const timeoutController = createTimeoutController(getTimeoutForOptions(options), providerName, "generate");
|
|
169
212
|
const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
|
|
170
213
|
let res;
|
|
@@ -173,7 +216,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
173
216
|
method: "POST",
|
|
174
217
|
headers: {
|
|
175
218
|
"Content-Type": "application/json",
|
|
176
|
-
|
|
219
|
+
...getAuthHeaders(),
|
|
177
220
|
},
|
|
178
221
|
body: JSON.stringify(body),
|
|
179
222
|
...(composedSignal ? { signal: composedSignal } : {}),
|
|
@@ -282,7 +325,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
282
325
|
timeoutController?.cleanup();
|
|
283
326
|
throw setupErr;
|
|
284
327
|
}
|
|
285
|
-
const url =
|
|
328
|
+
const url = this.getChatCompletionsURL(modelId);
|
|
286
329
|
const fetchImpl = createProxyFetch();
|
|
287
330
|
const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
288
331
|
const emitter = this.neurolink?.getEventEmitter();
|
|
@@ -296,7 +339,6 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
296
339
|
maxSteps,
|
|
297
340
|
modelId,
|
|
298
341
|
url,
|
|
299
|
-
apiKey: this.config.apiKey,
|
|
300
342
|
fetchImpl,
|
|
301
343
|
abortSignal,
|
|
302
344
|
options,
|
|
@@ -424,7 +466,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
424
466
|
return result;
|
|
425
467
|
}
|
|
426
468
|
async runStreamLoop(args) {
|
|
427
|
-
const { maxSteps, modelId, url,
|
|
469
|
+
const { maxSteps, modelId, url, fetchImpl, abortSignal, options, conversation, openAITools, openAIToolChoice, toolsRecord, emitter, toolsUsed, toolExecutionSummaries, pushChunk, resolveUsage, resolveFinish, } = args;
|
|
428
470
|
try {
|
|
429
471
|
let stepFinish = null;
|
|
430
472
|
let stepUsage;
|
|
@@ -432,7 +474,6 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
432
474
|
const stepResult = await this.streamOneStep({
|
|
433
475
|
modelId,
|
|
434
476
|
url,
|
|
435
|
-
apiKey,
|
|
436
477
|
fetchImpl,
|
|
437
478
|
abortSignal,
|
|
438
479
|
options,
|
|
@@ -481,7 +522,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
481
522
|
}
|
|
482
523
|
}
|
|
483
524
|
async streamOneStep(args) {
|
|
484
|
-
const body = buildBody({
|
|
525
|
+
const body = ensureJsonWordInBody(this.adjustRequestBody(buildBody({
|
|
485
526
|
modelId: args.modelId,
|
|
486
527
|
messages: args.conversation,
|
|
487
528
|
options: this.adjustBuildBodyOptions(args.modelId, args.options),
|
|
@@ -490,12 +531,12 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
490
531
|
? { toolChoice: args.openAIToolChoice }
|
|
491
532
|
: {}),
|
|
492
533
|
streaming: true,
|
|
493
|
-
});
|
|
534
|
+
}), args.modelId));
|
|
494
535
|
const res = await args.fetchImpl(args.url, {
|
|
495
536
|
method: "POST",
|
|
496
537
|
headers: {
|
|
497
538
|
"Content-Type": "application/json",
|
|
498
|
-
|
|
539
|
+
...this.getAuthHeaders(),
|
|
499
540
|
},
|
|
500
541
|
body: JSON.stringify(body),
|
|
501
542
|
...(args.abortSignal ? { signal: args.abortSignal } : {}),
|
|
@@ -610,7 +651,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
610
651
|
const t = setTimeout(() => controller.abort(), 5000);
|
|
611
652
|
const response = await proxyFetch(modelsUrl, {
|
|
612
653
|
headers: {
|
|
613
|
-
|
|
654
|
+
...this.getAuthHeaders(),
|
|
614
655
|
"Content-Type": "application/json",
|
|
615
656
|
},
|
|
616
657
|
signal: controller.signal,
|
|
@@ -31,6 +31,9 @@ export declare const v3ResponseFormatToOpenAI: (rf: {
|
|
|
31
31
|
description?: string;
|
|
32
32
|
}) => OpenAICompatResponseFormat | undefined;
|
|
33
33
|
export declare const mapNeuroLinkToolChoice: (choice: unknown) => OpenAICompatToolChoiceWire | undefined;
|
|
34
|
+
export declare const messagesContainJsonWord: (messages: ReadonlyArray<OpenAICompatChatMessage>) => boolean;
|
|
35
|
+
export declare const ensureJsonWordInBody: (body: OpenAICompatChatRequest) => OpenAICompatChatRequest;
|
|
36
|
+
export declare const requiresMaxCompletionTokens: (modelId: string) => boolean;
|
|
34
37
|
export declare const buildBody: (args: OpenAICompatBuildBodyArgs) => OpenAICompatChatRequest;
|
|
35
38
|
export declare const parseSSEStream: (body: ReadableStream<Uint8Array>, onTextDelta: (delta: string) => void) => Promise<OpenAICompatSSEResult>;
|
|
36
39
|
export declare const buildAPIError: (url: string, body: OpenAICompatChatRequest, res: Response) => Promise<Error>;
|
|
@@ -318,6 +318,45 @@ export const mapNeuroLinkToolChoice = (choice) => {
|
|
|
318
318
|
}
|
|
319
319
|
return undefined;
|
|
320
320
|
};
|
|
321
|
+
// OpenAI-compatible endpoints (OpenAI, DeepSeek, …) reject
|
|
322
|
+
// `response_format: { type: "json_object" }` unless the literal word "json"
|
|
323
|
+
// appears somewhere in the messages. The `@ai-sdk/openai-compatible` wrapper
|
|
324
|
+
// this client replaced injected that instruction for us; the native client
|
|
325
|
+
// must do the same or json_object requests 400.
|
|
326
|
+
export const messagesContainJsonWord = (messages) => messages.some((m) => {
|
|
327
|
+
const c = m.content;
|
|
328
|
+
if (typeof c === "string") {
|
|
329
|
+
return /\bjson\b/i.test(c);
|
|
330
|
+
}
|
|
331
|
+
if (Array.isArray(c)) {
|
|
332
|
+
return c.some((part) => typeof part?.text === "string" &&
|
|
333
|
+
/\bjson\b/i.test(part.text));
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
});
|
|
337
|
+
// Prepends a minimal JSON-instruction system message to the FINAL wire body
|
|
338
|
+
// when json_object mode is requested and its messages don't already mention
|
|
339
|
+
// "json". Operates on the post-`adjustRequestBody` body so the guard reflects
|
|
340
|
+
// whatever a subclass left on the wire (response_format/messages it may have
|
|
341
|
+
// rewritten), not an intermediate state. No-op otherwise.
|
|
342
|
+
export const ensureJsonWordInBody = (body) => body.response_format?.type === "json_object" &&
|
|
343
|
+
!messagesContainJsonWord(body.messages)
|
|
344
|
+
? {
|
|
345
|
+
...body,
|
|
346
|
+
messages: [
|
|
347
|
+
{
|
|
348
|
+
role: "system",
|
|
349
|
+
content: "Respond with valid JSON only — no prose, no markdown fencing.",
|
|
350
|
+
},
|
|
351
|
+
...body.messages,
|
|
352
|
+
],
|
|
353
|
+
}
|
|
354
|
+
: body;
|
|
355
|
+
// Reasoning-class OpenAI models (o-series, gpt-5+) reject `max_tokens` and
|
|
356
|
+
// require `max_completion_tokens`. The OpenAI + Azure providers use this to
|
|
357
|
+
// rename the field on the wire body; third-party OpenAI-compatible endpoints
|
|
358
|
+
// keep `max_tokens`, so it is opt-in per provider, never applied by default.
|
|
359
|
+
export const requiresMaxCompletionTokens = (modelId) => /^(o\d|gpt-5)/i.test(modelId.replace(/^.*\//, ""));
|
|
321
360
|
export const buildBody = (args) => {
|
|
322
361
|
const { modelId, messages, options, tools, toolChoice, streaming, responseFormat, } = args;
|
|
323
362
|
const body = {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { AIProviderName } from "../constants/enums.js";
|
|
21
21
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
22
|
-
import type { LanguageModel, OpenAICompatBuildBodyArgs, OpenAICompatStreamLifecycleListeners, Schema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
|
|
22
|
+
import type { LanguageModel, OpenAICompatBuildBodyArgs, OpenAICompatChatRequest, OpenAICompatResponseFormat, OpenAICompatStreamLifecycleListeners, Schema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
|
|
23
23
|
/**
|
|
24
24
|
* Abstract HTTP+SSE provider for OpenAI chat-completions-shaped endpoints.
|
|
25
25
|
*/
|
|
@@ -52,6 +52,24 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
52
52
|
* (e.g. LiteLLM's Gemini 2.5 maxTokens skip).
|
|
53
53
|
*/
|
|
54
54
|
protected adjustBuildBodyOptions(_modelId: string, opts: OpenAICompatBuildBodyArgs["options"]): OpenAICompatBuildBodyArgs["options"];
|
|
55
|
+
/**
|
|
56
|
+
* Hook to adjust the OpenAI `response_format` after it's converted from the
|
|
57
|
+
* V3 responseFormat (non-streaming `doGenerate` path). Default identity.
|
|
58
|
+
* Override for providers that don't support a given format type — e.g.
|
|
59
|
+
* DeepSeek rejects `response_format: { type: "json_schema" }` ("This
|
|
60
|
+
* response_format type is unavailable now"); the `@ai-sdk/openai-compatible`
|
|
61
|
+
* path this replaced declared `supportsStructuredOutputs: false`, which
|
|
62
|
+
* downgraded `json_schema` to `json_object`. Subclasses replicate that here.
|
|
63
|
+
*/
|
|
64
|
+
protected adjustResponseFormat(rf: OpenAICompatResponseFormat | undefined, _modelId: string): OpenAICompatResponseFormat | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Hook to adjust the fully-built wire request body before it is sent, on
|
|
67
|
+
* both the streaming and non-streaming paths. Default identity. Override for
|
|
68
|
+
* provider/model quirks that can't be expressed through buildBody options —
|
|
69
|
+
* e.g. Azure's newer reasoning deployments (o-series, gpt-5+) reject
|
|
70
|
+
* `max_tokens` and require `max_completion_tokens`.
|
|
71
|
+
*/
|
|
72
|
+
protected adjustRequestBody(body: OpenAICompatChatRequest, _modelId: string): OpenAICompatChatRequest;
|
|
55
73
|
/**
|
|
56
74
|
* Hook called once at the start of every `executeStream` invocation.
|
|
57
75
|
* Return lifecycle listeners (onUsage / onFinish) to receive deferred
|
|
@@ -66,6 +84,18 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
66
84
|
* `getDefaultModel()` will never hit this branch anyway.
|
|
67
85
|
*/
|
|
68
86
|
protected shouldAutoDiscoverModel(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Builds the chat-completions request URL for a model. Default is
|
|
89
|
+
* `${baseURL}/chat/completions`. Override for providers with a different
|
|
90
|
+
* routing scheme (e.g. Azure's deployment-based path + api-version query).
|
|
91
|
+
*/
|
|
92
|
+
protected getChatCompletionsURL(_modelId: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Auth headers merged into every request. Default is a Bearer token.
|
|
95
|
+
* Override for providers that authenticate differently (e.g. Azure, which
|
|
96
|
+
* uses an `api-key` header instead of `Authorization: Bearer`).
|
|
97
|
+
*/
|
|
98
|
+
protected getAuthHeaders(): Record<string, string>;
|
|
69
99
|
supportsTools(): boolean;
|
|
70
100
|
/**
|
|
71
101
|
* Returns a minimal V3-shaped model used by BaseProvider's `generate()`
|
|
@@ -28,7 +28,7 @@ import { composeAbortSignals, createTimeoutController, mergeAbortSignals, } from
|
|
|
28
28
|
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
29
29
|
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
30
30
|
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
31
|
-
import { buildAPIError, buildBody, buildToolsForOpenAI, createChunkQueue, createDeferredAnalytics, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
|
|
31
|
+
import { buildAPIError, buildBody, buildToolsForOpenAI, createChunkQueue, createDeferredAnalytics, ensureJsonWordInBody, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
|
|
32
32
|
/**
|
|
33
33
|
* Abstract HTTP+SSE provider for OpenAI chat-completions-shaped endpoints.
|
|
34
34
|
*/
|
|
@@ -64,6 +64,28 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
64
64
|
adjustBuildBodyOptions(_modelId, opts) {
|
|
65
65
|
return opts;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Hook to adjust the OpenAI `response_format` after it's converted from the
|
|
69
|
+
* V3 responseFormat (non-streaming `doGenerate` path). Default identity.
|
|
70
|
+
* Override for providers that don't support a given format type — e.g.
|
|
71
|
+
* DeepSeek rejects `response_format: { type: "json_schema" }` ("This
|
|
72
|
+
* response_format type is unavailable now"); the `@ai-sdk/openai-compatible`
|
|
73
|
+
* path this replaced declared `supportsStructuredOutputs: false`, which
|
|
74
|
+
* downgraded `json_schema` to `json_object`. Subclasses replicate that here.
|
|
75
|
+
*/
|
|
76
|
+
adjustResponseFormat(rf, _modelId) {
|
|
77
|
+
return rf;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Hook to adjust the fully-built wire request body before it is sent, on
|
|
81
|
+
* both the streaming and non-streaming paths. Default identity. Override for
|
|
82
|
+
* provider/model quirks that can't be expressed through buildBody options —
|
|
83
|
+
* e.g. Azure's newer reasoning deployments (o-series, gpt-5+) reject
|
|
84
|
+
* `max_tokens` and require `max_completion_tokens`.
|
|
85
|
+
*/
|
|
86
|
+
adjustRequestBody(body, _modelId) {
|
|
87
|
+
return body;
|
|
88
|
+
}
|
|
67
89
|
/**
|
|
68
90
|
* Hook called once at the start of every `executeStream` invocation.
|
|
69
91
|
* Return lifecycle listeners (onUsage / onFinish) to receive deferred
|
|
@@ -82,6 +104,22 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
82
104
|
shouldAutoDiscoverModel() {
|
|
83
105
|
return true;
|
|
84
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Builds the chat-completions request URL for a model. Default is
|
|
109
|
+
* `${baseURL}/chat/completions`. Override for providers with a different
|
|
110
|
+
* routing scheme (e.g. Azure's deployment-based path + api-version query).
|
|
111
|
+
*/
|
|
112
|
+
getChatCompletionsURL(_modelId) {
|
|
113
|
+
return `${stripTrailingSlash(this.config.baseURL)}/chat/completions`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Auth headers merged into every request. Default is a Bearer token.
|
|
117
|
+
* Override for providers that authenticate differently (e.g. Azure, which
|
|
118
|
+
* uses an `api-key` header instead of `Authorization: Bearer`).
|
|
119
|
+
*/
|
|
120
|
+
getAuthHeaders() {
|
|
121
|
+
return { Authorization: `Bearer ${this.config.apiKey}` };
|
|
122
|
+
}
|
|
85
123
|
// ===========================================================================
|
|
86
124
|
// Public/protected concrete methods (shared by all subclasses)
|
|
87
125
|
// ===========================================================================
|
|
@@ -129,11 +167,13 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
129
167
|
return fallback;
|
|
130
168
|
}
|
|
131
169
|
buildDelegatingModel(modelId) {
|
|
132
|
-
const url =
|
|
170
|
+
const url = this.getChatCompletionsURL(modelId);
|
|
133
171
|
const fetchImpl = createProxyFetch();
|
|
134
|
-
const
|
|
172
|
+
const getAuthHeaders = this.getAuthHeaders.bind(this);
|
|
135
173
|
const providerName = this.providerName;
|
|
136
174
|
const adjustBuildBodyOptions = this.adjustBuildBodyOptions.bind(this);
|
|
175
|
+
const adjustResponseFormat = this.adjustResponseFormat.bind(this);
|
|
176
|
+
const adjustRequestBody = this.adjustRequestBody.bind(this);
|
|
137
177
|
const getTimeoutForOptions = (opts) => this.getTimeout((opts ?? {}));
|
|
138
178
|
return {
|
|
139
179
|
specificationVersion: "v3",
|
|
@@ -141,10 +181,17 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
141
181
|
modelId,
|
|
142
182
|
supportedUrls: {},
|
|
143
183
|
doGenerate: async (options) => {
|
|
144
|
-
const
|
|
145
|
-
const
|
|
184
|
+
const baseMessages = messageBuilderToOpenAI(options.prompt);
|
|
185
|
+
const responseFormat = options.responseFormat
|
|
186
|
+
? adjustResponseFormat(v3ResponseFormatToOpenAI(options.responseFormat), modelId)
|
|
187
|
+
: undefined;
|
|
188
|
+
// ensureJsonWordInBody runs LAST — on the body after adjustRequestBody —
|
|
189
|
+
// so the json_object word guard reflects whatever a subclass left on
|
|
190
|
+
// the wire (it may rewrite response_format/messages), not an
|
|
191
|
+
// intermediate state.
|
|
192
|
+
const body = ensureJsonWordInBody(adjustRequestBody(buildBody({
|
|
146
193
|
modelId,
|
|
147
|
-
messages,
|
|
194
|
+
messages: baseMessages,
|
|
148
195
|
options: adjustBuildBodyOptions(modelId, {
|
|
149
196
|
maxTokens: options.maxOutputTokens,
|
|
150
197
|
temperature: options.temperature,
|
|
@@ -159,12 +206,8 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
159
206
|
? { toolChoice: v3ToolChoiceToOpenAI(options.toolChoice) }
|
|
160
207
|
: {}),
|
|
161
208
|
streaming: false,
|
|
162
|
-
...(
|
|
163
|
-
|
|
164
|
-
responseFormat: v3ResponseFormatToOpenAI(options.responseFormat),
|
|
165
|
-
}
|
|
166
|
-
: {}),
|
|
167
|
-
});
|
|
209
|
+
...(responseFormat ? { responseFormat } : {}),
|
|
210
|
+
}), modelId));
|
|
168
211
|
const timeoutController = createTimeoutController(getTimeoutForOptions(options), providerName, "generate");
|
|
169
212
|
const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
|
|
170
213
|
let res;
|
|
@@ -173,7 +216,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
173
216
|
method: "POST",
|
|
174
217
|
headers: {
|
|
175
218
|
"Content-Type": "application/json",
|
|
176
|
-
|
|
219
|
+
...getAuthHeaders(),
|
|
177
220
|
},
|
|
178
221
|
body: JSON.stringify(body),
|
|
179
222
|
...(composedSignal ? { signal: composedSignal } : {}),
|
|
@@ -282,7 +325,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
282
325
|
timeoutController?.cleanup();
|
|
283
326
|
throw setupErr;
|
|
284
327
|
}
|
|
285
|
-
const url =
|
|
328
|
+
const url = this.getChatCompletionsURL(modelId);
|
|
286
329
|
const fetchImpl = createProxyFetch();
|
|
287
330
|
const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
288
331
|
const emitter = this.neurolink?.getEventEmitter();
|
|
@@ -296,7 +339,6 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
296
339
|
maxSteps,
|
|
297
340
|
modelId,
|
|
298
341
|
url,
|
|
299
|
-
apiKey: this.config.apiKey,
|
|
300
342
|
fetchImpl,
|
|
301
343
|
abortSignal,
|
|
302
344
|
options,
|
|
@@ -424,7 +466,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
424
466
|
return result;
|
|
425
467
|
}
|
|
426
468
|
async runStreamLoop(args) {
|
|
427
|
-
const { maxSteps, modelId, url,
|
|
469
|
+
const { maxSteps, modelId, url, fetchImpl, abortSignal, options, conversation, openAITools, openAIToolChoice, toolsRecord, emitter, toolsUsed, toolExecutionSummaries, pushChunk, resolveUsage, resolveFinish, } = args;
|
|
428
470
|
try {
|
|
429
471
|
let stepFinish = null;
|
|
430
472
|
let stepUsage;
|
|
@@ -432,7 +474,6 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
432
474
|
const stepResult = await this.streamOneStep({
|
|
433
475
|
modelId,
|
|
434
476
|
url,
|
|
435
|
-
apiKey,
|
|
436
477
|
fetchImpl,
|
|
437
478
|
abortSignal,
|
|
438
479
|
options,
|
|
@@ -481,7 +522,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
481
522
|
}
|
|
482
523
|
}
|
|
483
524
|
async streamOneStep(args) {
|
|
484
|
-
const body = buildBody({
|
|
525
|
+
const body = ensureJsonWordInBody(this.adjustRequestBody(buildBody({
|
|
485
526
|
modelId: args.modelId,
|
|
486
527
|
messages: args.conversation,
|
|
487
528
|
options: this.adjustBuildBodyOptions(args.modelId, args.options),
|
|
@@ -490,12 +531,12 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
490
531
|
? { toolChoice: args.openAIToolChoice }
|
|
491
532
|
: {}),
|
|
492
533
|
streaming: true,
|
|
493
|
-
});
|
|
534
|
+
}), args.modelId));
|
|
494
535
|
const res = await args.fetchImpl(args.url, {
|
|
495
536
|
method: "POST",
|
|
496
537
|
headers: {
|
|
497
538
|
"Content-Type": "application/json",
|
|
498
|
-
|
|
539
|
+
...this.getAuthHeaders(),
|
|
499
540
|
},
|
|
500
541
|
body: JSON.stringify(body),
|
|
501
542
|
...(args.abortSignal ? { signal: args.abortSignal } : {}),
|
|
@@ -610,7 +651,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
610
651
|
const t = setTimeout(() => controller.abort(), 5000);
|
|
611
652
|
const response = await proxyFetch(modelsUrl, {
|
|
612
653
|
headers: {
|
|
613
|
-
|
|
654
|
+
...this.getAuthHeaders(),
|
|
614
655
|
"Content-Type": "application/json",
|
|
615
656
|
},
|
|
616
657
|
signal: controller.signal,
|
|
@@ -31,6 +31,9 @@ export declare const v3ResponseFormatToOpenAI: (rf: {
|
|
|
31
31
|
description?: string;
|
|
32
32
|
}) => OpenAICompatResponseFormat | undefined;
|
|
33
33
|
export declare const mapNeuroLinkToolChoice: (choice: unknown) => OpenAICompatToolChoiceWire | undefined;
|
|
34
|
+
export declare const messagesContainJsonWord: (messages: ReadonlyArray<OpenAICompatChatMessage>) => boolean;
|
|
35
|
+
export declare const ensureJsonWordInBody: (body: OpenAICompatChatRequest) => OpenAICompatChatRequest;
|
|
36
|
+
export declare const requiresMaxCompletionTokens: (modelId: string) => boolean;
|
|
34
37
|
export declare const buildBody: (args: OpenAICompatBuildBodyArgs) => OpenAICompatChatRequest;
|
|
35
38
|
export declare const parseSSEStream: (body: ReadableStream<Uint8Array>, onTextDelta: (delta: string) => void) => Promise<OpenAICompatSSEResult>;
|
|
36
39
|
export declare const buildAPIError: (url: string, body: OpenAICompatChatRequest, res: Response) => Promise<Error>;
|
|
@@ -318,6 +318,45 @@ export const mapNeuroLinkToolChoice = (choice) => {
|
|
|
318
318
|
}
|
|
319
319
|
return undefined;
|
|
320
320
|
};
|
|
321
|
+
// OpenAI-compatible endpoints (OpenAI, DeepSeek, …) reject
|
|
322
|
+
// `response_format: { type: "json_object" }` unless the literal word "json"
|
|
323
|
+
// appears somewhere in the messages. The `@ai-sdk/openai-compatible` wrapper
|
|
324
|
+
// this client replaced injected that instruction for us; the native client
|
|
325
|
+
// must do the same or json_object requests 400.
|
|
326
|
+
export const messagesContainJsonWord = (messages) => messages.some((m) => {
|
|
327
|
+
const c = m.content;
|
|
328
|
+
if (typeof c === "string") {
|
|
329
|
+
return /\bjson\b/i.test(c);
|
|
330
|
+
}
|
|
331
|
+
if (Array.isArray(c)) {
|
|
332
|
+
return c.some((part) => typeof part?.text === "string" &&
|
|
333
|
+
/\bjson\b/i.test(part.text));
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
});
|
|
337
|
+
// Prepends a minimal JSON-instruction system message to the FINAL wire body
|
|
338
|
+
// when json_object mode is requested and its messages don't already mention
|
|
339
|
+
// "json". Operates on the post-`adjustRequestBody` body so the guard reflects
|
|
340
|
+
// whatever a subclass left on the wire (response_format/messages it may have
|
|
341
|
+
// rewritten), not an intermediate state. No-op otherwise.
|
|
342
|
+
export const ensureJsonWordInBody = (body) => body.response_format?.type === "json_object" &&
|
|
343
|
+
!messagesContainJsonWord(body.messages)
|
|
344
|
+
? {
|
|
345
|
+
...body,
|
|
346
|
+
messages: [
|
|
347
|
+
{
|
|
348
|
+
role: "system",
|
|
349
|
+
content: "Respond with valid JSON only — no prose, no markdown fencing.",
|
|
350
|
+
},
|
|
351
|
+
...body.messages,
|
|
352
|
+
],
|
|
353
|
+
}
|
|
354
|
+
: body;
|
|
355
|
+
// Reasoning-class OpenAI models (o-series, gpt-5+) reject `max_tokens` and
|
|
356
|
+
// require `max_completion_tokens`. The OpenAI + Azure providers use this to
|
|
357
|
+
// rename the field on the wire body; third-party OpenAI-compatible endpoints
|
|
358
|
+
// keep `max_tokens`, so it is opt-in per provider, never applied by default.
|
|
359
|
+
export const requiresMaxCompletionTokens = (modelId) => /^(o\d|gpt-5)/i.test(modelId.replace(/^.*\//, ""));
|
|
321
360
|
export const buildBody = (args) => {
|
|
322
361
|
const { modelId, messages, options, tools, toolChoice, streaming, responseFormat, } = args;
|
|
323
362
|
const body = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.68.0",
|
|
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": {
|