@juspay/neurolink 9.67.0 → 9.67.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.
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared OpenAI chat-completions wire-format helpers used by providers that
3
+ * talk to an OpenAI-shaped /chat/completions endpoint (openai-compatible,
4
+ * litellm, groq, perplexity, xai, fireworks, togetherAi, cohere, cloudflare,
5
+ * huggingFace, llamaCpp, lmStudio, deepseek, nvidiaNim, and openAI itself).
6
+ *
7
+ * Everything in this module is provider-agnostic: pure functions that convert
8
+ * between NeuroLink-shaped values and the OpenAI wire format, plus the SSE
9
+ * parser + queue primitives a streaming provider needs. Provider classes own
10
+ * their own orchestration (executeStream + runStreamLoop) for now — that
11
+ * extraction is a follow-up PR.
12
+ *
13
+ * Nothing here imports from "ai" or "@ai-sdk/*". The whole point of this
14
+ * module is to be the native replacement for the AI SDK's OpenAI wrapper.
15
+ */
16
+ import type { OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatMessage, OpenAICompatMessageContent, OpenAICompatResponseFormat, OpenAICompatSSEResult, OpenAICompatStreamChunk, OpenAICompatToolChoiceWire, OpenAICompatV3CallToolChoice, OpenAICompatV3CallTools, Tool } from "../types/index.js";
17
+ export declare const stripTrailingSlash: (s: string) => string;
18
+ export declare const safeStringify: (value: unknown) => string;
19
+ export declare const stringifyToolInput: (input: unknown) => string;
20
+ export declare const stringifyToolOutput: (output: unknown) => string;
21
+ export declare const imageDataToURL: (data: unknown) => string | undefined;
22
+ export declare const convertContentForOpenAI: (content: unknown) => string | OpenAICompatMessageContent[];
23
+ export declare const messageBuilderToOpenAI: (messages: ReadonlyArray<OpenAICompatMessage>) => OpenAICompatChatMessage[];
24
+ export declare const buildToolsForOpenAI: (tools: Record<string, Tool>) => OpenAICompatChatTool[] | undefined;
25
+ export declare const v3ToolsToOpenAI: (tools: OpenAICompatV3CallTools | undefined) => OpenAICompatChatTool[] | undefined;
26
+ export declare const v3ToolChoiceToOpenAI: (choice: OpenAICompatV3CallToolChoice) => OpenAICompatToolChoiceWire | undefined;
27
+ export declare const v3ResponseFormatToOpenAI: (rf: {
28
+ type: "text" | "json";
29
+ schema?: Record<string, unknown>;
30
+ name?: string;
31
+ description?: string;
32
+ }) => OpenAICompatResponseFormat | undefined;
33
+ export declare const mapNeuroLinkToolChoice: (choice: unknown) => OpenAICompatToolChoiceWire | undefined;
34
+ export declare const buildBody: (args: OpenAICompatBuildBodyArgs) => OpenAICompatChatRequest;
35
+ export declare const parseSSEStream: (body: ReadableStream<Uint8Array>, onTextDelta: (delta: string) => void) => Promise<OpenAICompatSSEResult>;
36
+ export declare const buildAPIError: (url: string, body: OpenAICompatChatRequest, res: Response) => Promise<Error>;
37
+ export declare const createDeferredAnalytics: () => {
38
+ usagePromise: Promise<{
39
+ promptTokens: number;
40
+ completionTokens: number;
41
+ totalTokens: number;
42
+ }>;
43
+ finishPromise: Promise<string>;
44
+ resolveUsage: (u: {
45
+ promptTokens: number;
46
+ completionTokens: number;
47
+ totalTokens: number;
48
+ }) => void;
49
+ resolveFinish: (reason: string) => void;
50
+ };
51
+ export declare const createChunkQueue: () => {
52
+ pushChunk: (c: OpenAICompatStreamChunk) => void;
53
+ nextChunk: () => Promise<OpenAICompatStreamChunk>;
54
+ };
55
+ export declare const mergeUsage: (a: {
56
+ prompt_tokens?: number;
57
+ completion_tokens?: number;
58
+ total_tokens?: number;
59
+ } | undefined, b: {
60
+ prompt_tokens?: number;
61
+ completion_tokens?: number;
62
+ total_tokens?: number;
63
+ } | undefined) => {
64
+ prompt_tokens?: number;
65
+ completion_tokens?: number;
66
+ total_tokens?: number;
67
+ } | undefined;
@@ -0,0 +1,526 @@
1
+ /**
2
+ * Shared OpenAI chat-completions wire-format helpers used by providers that
3
+ * talk to an OpenAI-shaped /chat/completions endpoint (openai-compatible,
4
+ * litellm, groq, perplexity, xai, fireworks, togetherAi, cohere, cloudflare,
5
+ * huggingFace, llamaCpp, lmStudio, deepseek, nvidiaNim, and openAI itself).
6
+ *
7
+ * Everything in this module is provider-agnostic: pure functions that convert
8
+ * between NeuroLink-shaped values and the OpenAI wire format, plus the SSE
9
+ * parser + queue primitives a streaming provider needs. Provider classes own
10
+ * their own orchestration (executeStream + runStreamLoop) for now — that
11
+ * extraction is a follow-up PR.
12
+ *
13
+ * Nothing here imports from "ai" or "@ai-sdk/*". The whole point of this
14
+ * module is to be the native replacement for the AI SDK's OpenAI wrapper.
15
+ */
16
+ import { createParser } from "eventsource-parser";
17
+ import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
18
+ export const stripTrailingSlash = (s) => s.replace(/\/+$/, "");
19
+ export const safeStringify = (value) => {
20
+ try {
21
+ return JSON.stringify(value ?? "");
22
+ }
23
+ catch {
24
+ return String(value ?? "");
25
+ }
26
+ };
27
+ export const stringifyToolInput = (input) => {
28
+ if (typeof input === "string") {
29
+ return input;
30
+ }
31
+ try {
32
+ return JSON.stringify(input ?? {});
33
+ }
34
+ catch {
35
+ return "{}";
36
+ }
37
+ };
38
+ // V3 tool-result `output` is a tagged union ({type:"text"|"json"|...}).
39
+ // Serialize each variant the way an OpenAI-compatible endpoint expects
40
+ // to read it as the `content` of a `role: "tool"` message.
41
+ export const stringifyToolOutput = (output) => {
42
+ if (output === null || output === undefined) {
43
+ return "";
44
+ }
45
+ if (typeof output === "string") {
46
+ return output;
47
+ }
48
+ if (typeof output !== "object") {
49
+ return String(output);
50
+ }
51
+ const o = output;
52
+ switch (o.type) {
53
+ case "text":
54
+ return typeof o.value === "string" ? o.value : safeStringify(o.value);
55
+ case "json":
56
+ return safeStringify(o.value);
57
+ case "execution-denied":
58
+ return `Tool execution denied${o.reason ? `: ${o.reason}` : ""}`;
59
+ case "error-text":
60
+ return typeof o.value === "string" ? o.value : safeStringify(o.value);
61
+ case "error-json":
62
+ return safeStringify(o.value);
63
+ case "content":
64
+ if (Array.isArray(o.value)) {
65
+ return o.value
66
+ .map((p) => {
67
+ if (p &&
68
+ typeof p === "object" &&
69
+ p.type === "text") {
70
+ return String(p.text ?? "");
71
+ }
72
+ return "";
73
+ })
74
+ .filter((s) => s.length > 0)
75
+ .join("\n");
76
+ }
77
+ return "";
78
+ default:
79
+ return safeStringify(output);
80
+ }
81
+ };
82
+ export const imageDataToURL = (data) => {
83
+ if (typeof data === "string") {
84
+ if (data.startsWith("data:") || /^https?:\/\//i.test(data)) {
85
+ return data;
86
+ }
87
+ return `data:image/png;base64,${data}`;
88
+ }
89
+ if (data instanceof URL) {
90
+ return data.toString();
91
+ }
92
+ if (data instanceof Uint8Array) {
93
+ return `data:image/png;base64,${Buffer.from(data).toString("base64")}`;
94
+ }
95
+ return undefined;
96
+ };
97
+ export const convertContentForOpenAI = (content) => {
98
+ if (typeof content === "string") {
99
+ return content;
100
+ }
101
+ if (!Array.isArray(content)) {
102
+ return safeStringify(content);
103
+ }
104
+ const out = [];
105
+ for (const part of content) {
106
+ if (typeof part === "string") {
107
+ out.push({ type: "text", text: part });
108
+ continue;
109
+ }
110
+ if (!part || typeof part !== "object") {
111
+ continue;
112
+ }
113
+ const p = part;
114
+ if (p.type === "text") {
115
+ out.push({
116
+ type: "text",
117
+ text: part.text ?? "",
118
+ });
119
+ }
120
+ else if (p.type === "image" || p.type === "image_url") {
121
+ const data = part.image ??
122
+ part.data ??
123
+ part.url;
124
+ const url = imageDataToURL(data);
125
+ if (url) {
126
+ out.push({ type: "image_url", image_url: { url } });
127
+ }
128
+ }
129
+ }
130
+ if (out.length === 1 && out[0].type === "text") {
131
+ return out[0].text;
132
+ }
133
+ return out;
134
+ };
135
+ export const messageBuilderToOpenAI = (messages) => {
136
+ const out = [];
137
+ for (const msg of messages) {
138
+ switch (msg.role) {
139
+ case "system":
140
+ out.push({
141
+ role: "system",
142
+ content: typeof msg.content === "string"
143
+ ? msg.content
144
+ : safeStringify(msg.content),
145
+ });
146
+ break;
147
+ case "user":
148
+ out.push({
149
+ role: "user",
150
+ content: convertContentForOpenAI(msg.content),
151
+ });
152
+ break;
153
+ case "assistant": {
154
+ const parts = Array.isArray(msg.content) ? msg.content : [msg.content];
155
+ const text = [];
156
+ const toolCalls = [];
157
+ for (const part of parts) {
158
+ if (part && typeof part === "object") {
159
+ const p = part;
160
+ if (p.type === "text") {
161
+ text.push({
162
+ type: "text",
163
+ text: part.text ?? "",
164
+ });
165
+ }
166
+ else if (p.type === "tool-call") {
167
+ const tc = part;
168
+ toolCalls.push({
169
+ id: tc.toolCallId ?? "",
170
+ type: "function",
171
+ function: {
172
+ name: tc.toolName ?? "",
173
+ arguments: stringifyToolInput(tc.input),
174
+ },
175
+ });
176
+ }
177
+ }
178
+ else if (typeof part === "string") {
179
+ text.push({ type: "text", text: part });
180
+ }
181
+ }
182
+ const flat = text.length === 0
183
+ ? null
184
+ : text.length === 1 && text[0].type === "text"
185
+ ? text[0].text
186
+ : text;
187
+ out.push({
188
+ role: "assistant",
189
+ content: flat,
190
+ ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
191
+ });
192
+ break;
193
+ }
194
+ case "tool": {
195
+ // V3 tool messages carry `{ toolCallId, output }` per content[] entry,
196
+ // not at the top-level. Emit one OpenAI `role: "tool"` message per
197
+ // tool-result part so the model can correlate by tool_call_id.
198
+ if (Array.isArray(msg.content)) {
199
+ for (const part of msg.content) {
200
+ if (!part || typeof part !== "object") {
201
+ continue;
202
+ }
203
+ const p = part;
204
+ if (p.type === "tool-result") {
205
+ out.push({
206
+ role: "tool",
207
+ tool_call_id: p.toolCallId ?? "",
208
+ content: stringifyToolOutput(p.output),
209
+ });
210
+ }
211
+ }
212
+ }
213
+ else if (typeof msg.content === "string") {
214
+ out.push({
215
+ role: "tool",
216
+ tool_call_id: msg.toolCallId ?? "",
217
+ content: msg.content,
218
+ });
219
+ }
220
+ break;
221
+ }
222
+ }
223
+ }
224
+ return out;
225
+ };
226
+ export const buildToolsForOpenAI = (tools) => {
227
+ const entries = Object.entries(tools);
228
+ if (entries.length === 0) {
229
+ return undefined;
230
+ }
231
+ const out = [];
232
+ for (const [name, tool] of entries) {
233
+ const t = tool;
234
+ const rawSchema = t.inputSchema ?? t.parameters;
235
+ // tool.inputSchema may be a Zod schema, an AI SDK jsonSchema() wrapper,
236
+ // or plain JSON Schema — convertZodToJsonSchema normalizes all three.
237
+ // Sending raw Zod internals (with `_def`) gets rejected by most
238
+ // OpenAI-compatible endpoints.
239
+ const parameters = rawSchema
240
+ ? convertZodToJsonSchema(rawSchema)
241
+ : { type: "object", properties: {} };
242
+ out.push({
243
+ type: "function",
244
+ function: {
245
+ name,
246
+ ...(t.description ? { description: t.description } : {}),
247
+ parameters,
248
+ },
249
+ });
250
+ }
251
+ return out;
252
+ };
253
+ // V3 → OpenAI conversion helpers used by the non-streaming `doGenerate`
254
+ // path that BaseProvider's `generate()` still drives via the AI SDK's
255
+ // `generateText`. The streaming path doesn't need these — it consumes
256
+ // NeuroLink-shaped options directly.
257
+ export const v3ToolsToOpenAI = (tools) => {
258
+ if (!tools || tools.length === 0) {
259
+ return undefined;
260
+ }
261
+ const out = [];
262
+ for (const t of tools) {
263
+ if (t.type === "function") {
264
+ out.push({
265
+ type: "function",
266
+ function: {
267
+ name: t.name,
268
+ ...(t.description ? { description: t.description } : {}),
269
+ parameters: t.inputSchema,
270
+ ...(t.strict !== undefined ? { strict: t.strict } : {}),
271
+ },
272
+ });
273
+ }
274
+ // provider-defined V3 tools are silently dropped here — they have no
275
+ // OpenAI chat-completions equivalent.
276
+ }
277
+ return out.length > 0 ? out : undefined;
278
+ };
279
+ export const v3ToolChoiceToOpenAI = (choice) => {
280
+ switch (choice.type) {
281
+ case "auto":
282
+ case "none":
283
+ case "required":
284
+ return choice.type;
285
+ case "tool":
286
+ return { type: "function", function: { name: choice.toolName } };
287
+ }
288
+ };
289
+ export const v3ResponseFormatToOpenAI = (rf) => {
290
+ if (rf.type === "text") {
291
+ return { type: "text" };
292
+ }
293
+ if (!rf.schema) {
294
+ return { type: "json_object" };
295
+ }
296
+ return {
297
+ type: "json_schema",
298
+ json_schema: {
299
+ name: rf.name ?? "response",
300
+ schema: rf.schema,
301
+ ...(rf.description ? { description: rf.description } : {}),
302
+ strict: true,
303
+ },
304
+ };
305
+ };
306
+ export const mapNeuroLinkToolChoice = (choice) => {
307
+ if (!choice) {
308
+ return undefined;
309
+ }
310
+ if (choice === "auto" || choice === "none" || choice === "required") {
311
+ return choice;
312
+ }
313
+ if (typeof choice === "object" && choice !== null) {
314
+ const c = choice;
315
+ if (c.type === "tool" && c.toolName) {
316
+ return { type: "function", function: { name: c.toolName } };
317
+ }
318
+ }
319
+ return undefined;
320
+ };
321
+ export const buildBody = (args) => {
322
+ const { modelId, messages, options, tools, toolChoice, streaming, responseFormat, } = args;
323
+ const body = {
324
+ model: modelId,
325
+ messages,
326
+ ...(streaming ? { stream: true } : {}),
327
+ ...(streaming ? { stream_options: { include_usage: true } } : {}),
328
+ };
329
+ if (options.maxTokens !== undefined && options.maxTokens !== null) {
330
+ body.max_tokens = options.maxTokens;
331
+ }
332
+ if (options.temperature !== undefined && options.temperature !== null) {
333
+ body.temperature = options.temperature;
334
+ }
335
+ if (options.topP !== undefined && options.topP !== null) {
336
+ body.top_p = options.topP;
337
+ }
338
+ if (options.presencePenalty !== undefined &&
339
+ options.presencePenalty !== null) {
340
+ body.presence_penalty = options.presencePenalty;
341
+ }
342
+ if (options.frequencyPenalty !== undefined &&
343
+ options.frequencyPenalty !== null) {
344
+ body.frequency_penalty = options.frequencyPenalty;
345
+ }
346
+ if (options.seed !== undefined && options.seed !== null) {
347
+ body.seed = options.seed;
348
+ }
349
+ if (options.stopSequences && options.stopSequences.length > 0) {
350
+ body.stop = options.stopSequences;
351
+ }
352
+ if (tools) {
353
+ body.tools = tools;
354
+ }
355
+ if (toolChoice !== undefined) {
356
+ body.tool_choice = toolChoice;
357
+ }
358
+ if (responseFormat) {
359
+ body.response_format = responseFormat;
360
+ }
361
+ return body;
362
+ };
363
+ export const parseSSEStream = async (body, onTextDelta) => {
364
+ const result = {
365
+ text: "",
366
+ toolCalls: new Map(),
367
+ finishReason: null,
368
+ usage: undefined,
369
+ };
370
+ const decoder = new TextDecoder();
371
+ let parseErr;
372
+ const handleEvent = (msg) => {
373
+ const data = msg.data;
374
+ if (!data || data === "[DONE]") {
375
+ return;
376
+ }
377
+ let chunk;
378
+ try {
379
+ chunk = JSON.parse(data);
380
+ }
381
+ catch (err) {
382
+ parseErr = err instanceof Error ? err : new Error(String(err));
383
+ return;
384
+ }
385
+ if (chunk.usage) {
386
+ result.usage = chunk.usage;
387
+ }
388
+ const choice = chunk.choices?.[0];
389
+ if (!choice) {
390
+ return;
391
+ }
392
+ const delta = choice.delta;
393
+ if (delta?.content) {
394
+ result.text += delta.content;
395
+ onTextDelta(delta.content);
396
+ }
397
+ if (delta?.tool_calls) {
398
+ for (const tc of delta.tool_calls) {
399
+ let state = result.toolCalls.get(tc.index);
400
+ if (!state) {
401
+ state = {
402
+ id: tc.id ?? `call_${tc.index}_${Date.now()}`,
403
+ name: tc.function?.name ?? "",
404
+ argsBuffered: "",
405
+ };
406
+ result.toolCalls.set(tc.index, state);
407
+ }
408
+ else if (tc.id) {
409
+ state.id = tc.id;
410
+ }
411
+ if (tc.function?.name) {
412
+ state.name = tc.function.name;
413
+ }
414
+ if (tc.function?.arguments) {
415
+ state.argsBuffered += tc.function.arguments;
416
+ }
417
+ }
418
+ }
419
+ if (choice.finish_reason) {
420
+ result.finishReason = choice.finish_reason;
421
+ }
422
+ };
423
+ const parser = createParser({ onEvent: handleEvent });
424
+ const reader = body.getReader();
425
+ try {
426
+ for (;;) {
427
+ const { done, value } = await reader.read();
428
+ if (done) {
429
+ break;
430
+ }
431
+ parser.feed(decoder.decode(value, { stream: true }));
432
+ }
433
+ parser.feed(decoder.decode());
434
+ }
435
+ finally {
436
+ reader.releaseLock();
437
+ }
438
+ if (parseErr) {
439
+ throw parseErr;
440
+ }
441
+ return result;
442
+ };
443
+ export const buildAPIError = async (url, body, res) => {
444
+ let bodyText;
445
+ let parsed;
446
+ try {
447
+ bodyText = await res.text();
448
+ parsed = bodyText
449
+ ? JSON.parse(bodyText)
450
+ : undefined;
451
+ }
452
+ catch {
453
+ parsed = undefined;
454
+ }
455
+ const msg = parsed?.error?.message ??
456
+ `OpenAI-compatible request failed with status ${res.status}`;
457
+ const err = new Error(msg);
458
+ err.statusCode = res.status;
459
+ err.url = url;
460
+ // Redacted summary only — never attach raw prompts, tool definitions, or
461
+ // tool arguments to the thrown error. Anything serialized by upstream
462
+ // logging would leak them otherwise.
463
+ err.requestBody = {
464
+ model: body.model,
465
+ stream: body.stream === true,
466
+ tool_count: body.tools?.length ?? 0,
467
+ };
468
+ if (bodyText !== undefined) {
469
+ err.responseBody = bodyText;
470
+ }
471
+ return err;
472
+ };
473
+ // Deferred-promise pair for `usage` and `finishReason` so the analytics
474
+ // collector resolves with the actual aggregated values after the multi-step
475
+ // loop ends, not the zeros they had at result-construction time.
476
+ export const createDeferredAnalytics = () => {
477
+ let resolveUsage = () => { };
478
+ const usagePromise = new Promise((r) => {
479
+ resolveUsage = r;
480
+ });
481
+ let resolveFinish = () => { };
482
+ const finishPromise = new Promise((r) => {
483
+ resolveFinish = r;
484
+ });
485
+ return { usagePromise, finishPromise, resolveUsage, resolveFinish };
486
+ };
487
+ // Single-producer / single-consumer chunk queue. The streaming loop pushes
488
+ // `{content}` deltas as they arrive from SSE and a final `{done:true}` when
489
+ // it finishes; the consumer's AsyncIterable pulls from `nextChunk()`.
490
+ export const createChunkQueue = () => {
491
+ const chunkQueue = [];
492
+ let pendingResolve;
493
+ const pushChunk = (c) => {
494
+ if (pendingResolve) {
495
+ const r = pendingResolve;
496
+ pendingResolve = undefined;
497
+ r(c);
498
+ }
499
+ else {
500
+ chunkQueue.push(c);
501
+ }
502
+ };
503
+ const nextChunk = () => new Promise((resolve) => {
504
+ if (chunkQueue.length > 0) {
505
+ resolve(chunkQueue.shift());
506
+ }
507
+ else {
508
+ pendingResolve = resolve;
509
+ }
510
+ });
511
+ return { pushChunk, nextChunk };
512
+ };
513
+ export const mergeUsage = (a, b) => {
514
+ if (!a) {
515
+ return b;
516
+ }
517
+ if (!b) {
518
+ return a;
519
+ }
520
+ return {
521
+ prompt_tokens: (a.prompt_tokens ?? 0) + (b.prompt_tokens ?? 0),
522
+ completion_tokens: (a.completion_tokens ?? 0) + (b.completion_tokens ?? 0),
523
+ total_tokens: (a.total_tokens ?? 0) + (b.total_tokens ?? 0),
524
+ };
525
+ };
526
+ //# sourceMappingURL=openaiChatCompletionsClient.js.map
@@ -1,16 +1,17 @@
1
1
  import type { AIProviderName } from "../constants/enums.js";
2
2
  import { BaseProvider } from "../core/baseProvider.js";
3
- import type { StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
4
- import type { LanguageModel, Schema } from "../types/index.js";
3
+ import type { LanguageModel, Schema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
5
4
  /**
6
- * OpenAI Compatible Provider - BaseProvider Implementation
7
- * Provides access to one of the OpenAI-compatible endpoint (OpenRouter, vLLM, LiteLLM, etc.)
5
+ * OpenAI Compatible Provider direct HTTP, no AI SDK.
6
+ *
7
+ * Talks to any OpenAI chat-completions-shaped endpoint (LiteLLM, vLLM,
8
+ * OpenRouter, etc.). The entire request/stream/tool-loop is inline above;
9
+ * no `streamText`, no `LanguageModelV3`, no `@ai-sdk/openai`.
8
10
  */
9
11
  export declare class OpenAICompatibleProvider extends BaseProvider {
10
- private model?;
11
12
  private config;
13
+ private resolvedModel?;
12
14
  private discoveredModel?;
13
- private customOpenAI;
14
15
  constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: {
15
16
  apiKey?: string;
16
17
  baseURL?: string;
@@ -18,33 +19,59 @@ export declare class OpenAICompatibleProvider extends BaseProvider {
18
19
  protected getProviderName(): AIProviderName;
19
20
  protected getDefaultModel(): string;
20
21
  /**
21
- * Returns the Vercel AI SDK model instance for OpenAI Compatible endpoints
22
- * Handles auto-discovery if no model was specified
22
+ * Abstract from BaseProvider used by the parent's generate() path which
23
+ * still goes through `generateText`. Returns a thin LanguageModelV3-shaped
24
+ * object that delegates to the same HTTP helpers used by executeStream.
25
+ * Stays inside this file so no AI-SDK-named import is needed here.
23
26
  */
24
27
  protected getAISDKModel(): Promise<LanguageModel>;
25
- protected formatProviderError(error: unknown): Error;
28
+ private resolveModelName;
26
29
  /**
27
- * OpenAI Compatible endpoints support tools for compatible models
30
+ * Returns a minimal V3-shaped model. Only used by BaseProvider's
31
+ * `generate()` non-streaming path which still relies on the parent's
32
+ * `generateText`. The streaming path bypasses this entirely.
28
33
  */
34
+ private buildDelegatingModel;
35
+ protected formatProviderError(error: unknown): Error;
29
36
  supportsTools(): boolean;
30
37
  /**
31
- * Provider-specific streaming implementation
32
- * Note: This is only used when tools are disabled
38
+ * Streaming path — drives the OpenAI endpoint directly. No streamText,
39
+ * no AI SDK orchestrator. Tool calls, multi-step loops, telemetry,
40
+ * abort handling all inline.
33
41
  */
34
42
  protected executeStream(options: StreamOptions, _analysisSchema?: ZodUnknownSchema | Schema<unknown>): Promise<StreamResult>;
35
43
  /**
36
- * Get available models from OpenAI Compatible endpoint
44
+ * Multi-step streaming orchestrator. One iteration per model turn:
37
45
  *
38
- * Fetches from the /v1/models endpoint to discover available models.
39
- * This is useful for auto-discovery when no model is specified.
46
+ * 1. POST /chat/completions with stream:true
47
+ * 2. Parse SSE; push text deltas to the consumer queue
48
+ * 3. If the step emitted tool_calls → execute each, append to
49
+ * conversation, loop again
50
+ * 4. Otherwise resolve the deferred analytics promises and exit
51
+ *
52
+ * Bounded by `args.maxSteps`. Any thrown error rejects loopPromise and
53
+ * is surfaced to the consumer via `await loopPromise` in the stream
54
+ * generator.
40
55
  */
41
- getAvailableModels(): Promise<string[]>;
56
+ private runStreamLoop;
42
57
  /**
43
- * Get the first available model for auto-selection
58
+ * One streaming round-trip: POST chat-completions, parse SSE, push text
59
+ * deltas to the consumer queue. Returns the accumulated SSE result so
60
+ * the caller can decide whether to run tools and re-stream.
44
61
  */
45
- getFirstAvailableModel(): Promise<string>;
62
+ private streamOneStep;
46
63
  /**
47
- * Fallback models when discovery fails
64
+ * Execute every tool_call collected from one streaming step:
65
+ *
66
+ * - append an `assistant` turn carrying the tool_calls
67
+ * - resolve each tool from the local registry and run it
68
+ * - emit tool:start/tool:end events
69
+ * - push per-execution summaries
70
+ * - append a `tool` turn per result so the next step can see them
71
+ * - mirror BaseProvider's tool-events + storage hooks
48
72
  */
73
+ private executeToolBatch;
74
+ getAvailableModels(): Promise<string[]>;
75
+ getFirstAvailableModel(): Promise<string>;
49
76
  private getFallbackModels;
50
77
  }