@juspay/neurolink 9.68.13 → 9.68.15
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 +4 -0
- package/dist/browser/neurolink.min.js +345 -345
- package/dist/lib/providers/groq.d.ts +10 -19
- package/dist/lib/providers/groq.js +29 -126
- package/dist/lib/providers/openAI.d.ts +29 -57
- package/dist/lib/providers/openAI.js +184 -579
- package/dist/lib/utils/providerSetupMessages.js +1 -1
- package/dist/providers/groq.d.ts +10 -19
- package/dist/providers/groq.js +29 -126
- package/dist/providers/openAI.d.ts +29 -57
- package/dist/providers/openAI.js +184 -579
- package/dist/utils/providerSetupMessages.js +1 -1
- package/package.json +1 -1
|
@@ -1,234 +1,84 @@
|
|
|
1
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
1
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
3
|
-
import { AIProviderName } from "../constants/enums.js";
|
|
4
|
-
import { BaseProvider } from "../core/baseProvider.js";
|
|
5
|
-
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
6
|
-
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
2
|
+
import { AIProviderName as AIProviderNameEnum } from "../constants/enums.js";
|
|
7
3
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
8
4
|
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
9
5
|
import { logger } from "../utils/logger.js";
|
|
10
|
-
import { buildNoOutputSentinel, detectPostStreamNoOutput, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
|
|
11
6
|
import { calculateCost } from "../utils/pricing.js";
|
|
12
7
|
import { createOpenAIConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
13
|
-
import { isZodSchema } from "../utils/schemaConversion.js";
|
|
14
|
-
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
15
|
-
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
16
|
-
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
17
8
|
import { MAX_IMAGE_BYTES, readBoundedBuffer } from "../utils/sizeGuard.js";
|
|
18
9
|
import { assertSafeUrl } from "../utils/ssrfGuard.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
10
|
+
import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
|
|
11
|
+
import { stripTrailingSlash } from "./openaiChatCompletionsClient.js";
|
|
12
|
+
import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
13
|
+
const OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
23
14
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
15
|
+
* Resolve the effective OpenAI base URL from optional credential / env
|
|
16
|
+
* overrides, falling back to the official API host.
|
|
17
|
+
*
|
|
18
|
+
* - Blank or whitespace-only overrides are treated as unset, so a bare
|
|
19
|
+
* `OPENAI_BASE_URL=` cannot silently override the default with "".
|
|
20
|
+
* - The official OpenAI REST API lives under `/v1`. Setup guidance has long
|
|
21
|
+
* shown `OPENAI_BASE_URL="https://api.openai.com"` (no path); consumed
|
|
22
|
+
* verbatim that builds `https://api.openai.com/chat/completions` and 404s.
|
|
23
|
+
* When the canonical host is supplied without a path, append `/v1`. Custom
|
|
24
|
+
* proxy hosts (LiteLLM, gateways, …) are left exactly as provided.
|
|
26
25
|
*/
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
const resolveOpenAIBaseURL = (credentialBaseURL, envBaseURL) => {
|
|
27
|
+
const resolved = [credentialBaseURL, envBaseURL]
|
|
28
|
+
.map((v) => v?.trim())
|
|
29
|
+
.find((v) => !!v && v.length > 0) ?? OPENAI_DEFAULT_BASE_URL;
|
|
30
|
+
try {
|
|
31
|
+
const url = new URL(resolved);
|
|
32
|
+
const hasPath = url.pathname && url.pathname !== "/";
|
|
33
|
+
if (url.hostname === "api.openai.com" && !hasPath) {
|
|
34
|
+
url.pathname = "/v1";
|
|
35
|
+
return stripTrailingSlash(url.toString());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Not a parseable absolute URL — return the override verbatim.
|
|
40
|
+
}
|
|
41
|
+
return resolved;
|
|
37
42
|
};
|
|
43
|
+
const getOpenAIApiKey = () => validateApiKey(createOpenAIConfig());
|
|
44
|
+
const getOpenAIModel = () => getProviderModel("OPENAI_MODEL", "gpt-4o");
|
|
38
45
|
const streamTracer = trace.getTracer("neurolink.provider.openai");
|
|
39
46
|
/**
|
|
40
|
-
* OpenAI Provider
|
|
41
|
-
*
|
|
47
|
+
* OpenAI Provider — direct HTTP, no AI SDK.
|
|
48
|
+
*
|
|
49
|
+
* OpenAI chat completions at api.openai.com/v1. All request / stream /
|
|
50
|
+
* tool-loop orchestration lives in `OpenAIChatCompletionsProvider`; this
|
|
51
|
+
* class adds:
|
|
52
|
+
* - OTel span wrap with cost (`onStreamStart`)
|
|
53
|
+
* - Native `/v1/embeddings` (`embed` / `embedMany`)
|
|
54
|
+
* - Image generation via `/v1/images/generations` (`executeImageGeneration`)
|
|
55
|
+
* - OpenAI-specific error mapping (`formatProviderError`)
|
|
56
|
+
*
|
|
57
|
+
* @see https://platform.openai.com/docs/api-reference
|
|
42
58
|
*/
|
|
43
|
-
export class OpenAIProvider extends
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
...(credentials?.baseURL ? { baseURL: credentials.baseURL } : {}),
|
|
53
|
-
fetch: createProxyFetch(),
|
|
54
|
-
});
|
|
55
|
-
// Initialize model
|
|
56
|
-
this.model = openai(this.modelName);
|
|
57
|
-
logger.debug("OpenAIProvider constructor called", {
|
|
59
|
+
export class OpenAIProvider extends OpenAIChatCompletionsProvider {
|
|
60
|
+
constructor(modelName, sdk, _region, credentials) {
|
|
61
|
+
const overrideApiKey = credentials?.apiKey?.trim();
|
|
62
|
+
const apiKey = overrideApiKey && overrideApiKey.length > 0
|
|
63
|
+
? overrideApiKey
|
|
64
|
+
: getOpenAIApiKey();
|
|
65
|
+
const baseURL = resolveOpenAIBaseURL(credentials?.baseURL, process.env.OPENAI_BASE_URL);
|
|
66
|
+
super(AIProviderNameEnum.OPENAI, modelName, sdk, { baseURL, apiKey });
|
|
67
|
+
logger.debug("OpenAIProvider initialized", {
|
|
58
68
|
model: this.modelName,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
className: this.constructor.name,
|
|
69
|
+
providerName: this.providerName,
|
|
70
|
+
baseURL: this.config.baseURL,
|
|
62
71
|
});
|
|
63
72
|
}
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
/**
|
|
68
|
-
* Check if this provider supports tool/function calling
|
|
69
|
-
*/
|
|
70
|
-
supportsTools() {
|
|
71
|
-
return true; // Re-enable tools now that we understand the issue
|
|
72
|
-
}
|
|
73
|
+
// ===========================================================================
|
|
74
|
+
// Abstract hook implementations
|
|
75
|
+
// ===========================================================================
|
|
73
76
|
getProviderName() {
|
|
74
|
-
return
|
|
77
|
+
return AIProviderNameEnum.OPENAI;
|
|
75
78
|
}
|
|
76
79
|
getDefaultModel() {
|
|
77
80
|
return getOpenAIModel();
|
|
78
81
|
}
|
|
79
|
-
/**
|
|
80
|
-
* Get the default embedding model for OpenAI
|
|
81
|
-
* @returns The default OpenAI embedding model name
|
|
82
|
-
*/
|
|
83
|
-
getDefaultEmbeddingModel() {
|
|
84
|
-
return process.env.OPENAI_EMBEDDING_MODEL || "text-embedding-3-small";
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Returns the Vercel AI SDK model instance for OpenAI
|
|
88
|
-
*/
|
|
89
|
-
getAISDKModel() {
|
|
90
|
-
return this.model;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* OpenAI-specific tool validation and filtering
|
|
94
|
-
* Filters out tools that might cause streaming issues
|
|
95
|
-
*/
|
|
96
|
-
validateAndFilterToolsForOpenAI(tools) {
|
|
97
|
-
const validTools = {};
|
|
98
|
-
for (const [name, tool] of Object.entries(tools)) {
|
|
99
|
-
try {
|
|
100
|
-
// Basic validation - ensure tool has required structure
|
|
101
|
-
if (tool && typeof tool === "object") {
|
|
102
|
-
// Check if tool has description (required by OpenAI)
|
|
103
|
-
if (tool.description && typeof tool.description === "string") {
|
|
104
|
-
// Keep the original tool structure - AI SDK will handle Zod schema conversion internally
|
|
105
|
-
const processedTool = { ...tool };
|
|
106
|
-
// Validate that Zod schemas are properly structured for AI SDK processing
|
|
107
|
-
const toolSchema = getToolSchema(tool);
|
|
108
|
-
if (toolSchema && isZodSchema(toolSchema)) {
|
|
109
|
-
logger.debug(`OpenAI: Tool ${name} has Zod schema - AI SDK will handle conversion`);
|
|
110
|
-
// Basic validation that the Zod schema has the required structure
|
|
111
|
-
this.validateZodSchema(name, toolSchema);
|
|
112
|
-
}
|
|
113
|
-
// Include the tool with original Zod schema for AI SDK processing
|
|
114
|
-
if (this.isValidToolStructure(processedTool)) {
|
|
115
|
-
validTools[name] = processedTool;
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
logger.warn(`OpenAI: Filtering out tool with invalid structure: ${name}`, {
|
|
119
|
-
parametersType: typeof getToolSchema(processedTool),
|
|
120
|
-
hasDescription: !!processedTool.description,
|
|
121
|
-
hasExecute: !!processedTool.execute,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
logger.warn(`OpenAI: Filtering out tool without description: ${name}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
logger.warn(`OpenAI: Filtering out invalid tool: ${name}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
logger.warn(`OpenAI: Error validating tool ${name}:`, error);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return validTools;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Validate Zod schema structure
|
|
141
|
-
*/
|
|
142
|
-
validateZodSchema(toolName, schema) {
|
|
143
|
-
try {
|
|
144
|
-
const zodSchema = schema;
|
|
145
|
-
if (zodSchema._def && zodSchema._def.typeName) {
|
|
146
|
-
logger.debug(`OpenAI: Zod schema for ${toolName} appears valid`, {
|
|
147
|
-
typeName: zodSchema._def.typeName,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
logger.warn(`OpenAI: Zod schema for ${toolName} missing typeName - may cause issues`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch (zodValidationError) {
|
|
155
|
-
logger.warn(`OpenAI: Zod schema validation failed for ${toolName}:`, zodValidationError);
|
|
156
|
-
// Continue anyway - let AI SDK handle it
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Validate tool structure for OpenAI compatibility
|
|
161
|
-
* More lenient validation to avoid filtering out valid tools
|
|
162
|
-
*/
|
|
163
|
-
/** Shared helper: mark a stream span as ERROR, record the exception, and end it. */
|
|
164
|
-
endStreamSpanWithError(span, error) {
|
|
165
|
-
span.setStatus({
|
|
166
|
-
code: SpanStatusCode.ERROR,
|
|
167
|
-
message: error instanceof Error ? error.message : String(error),
|
|
168
|
-
});
|
|
169
|
-
if (error instanceof Error) {
|
|
170
|
-
span.recordException(error);
|
|
171
|
-
}
|
|
172
|
-
span.end();
|
|
173
|
-
}
|
|
174
|
-
isValidToolStructure(tool) {
|
|
175
|
-
if (!tool || typeof tool !== "object") {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
const toolObj = tool;
|
|
179
|
-
// Ensure tool has description and execute function
|
|
180
|
-
if (!toolObj.description || typeof toolObj.description !== "string") {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
if (!toolObj.execute || typeof toolObj.execute !== "function") {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
// AI SDK v6 uses inputSchema; v4 used parameters — check both
|
|
187
|
-
const schema = "inputSchema" in toolObj
|
|
188
|
-
? toolObj.inputSchema
|
|
189
|
-
: "parameters" in toolObj
|
|
190
|
-
? toolObj.parameters
|
|
191
|
-
: undefined;
|
|
192
|
-
return this.isValidToolParameters(schema);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Validate tool parameters for OpenAI compatibility
|
|
196
|
-
* Ensures the tool has either valid Zod schema or valid JSON schema
|
|
197
|
-
*/
|
|
198
|
-
isValidToolParameters(parameters) {
|
|
199
|
-
if (!parameters) {
|
|
200
|
-
// For OpenAI, tools without parameters need an empty object schema
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
// Check if it's a Zod schema - these are valid
|
|
204
|
-
if (isZodSchema(parameters)) {
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
// Check if it's a JSON schema
|
|
208
|
-
if (typeof parameters !== "object" || parameters === null) {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
const params = parameters;
|
|
212
|
-
// If it's a JSON schema, it should have type "object" for OpenAI
|
|
213
|
-
if (params.type && params.type !== "object") {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
// OpenAI requires schemas to have properties field, even if empty
|
|
217
|
-
// If there's no properties field, the schema is incomplete
|
|
218
|
-
if (params.type === "object" && !params.properties) {
|
|
219
|
-
logger.warn(`Tool parameter schema missing properties field:`, params);
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
// If properties exist, they should be an object
|
|
223
|
-
if (params.properties && typeof params.properties !== "object") {
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
// If required exists, it should be an array
|
|
227
|
-
if (params.required && !Array.isArray(params.required)) {
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
82
|
formatProviderError(error) {
|
|
233
83
|
if (error instanceof TimeoutError) {
|
|
234
84
|
return new NetworkError(error.message, this.providerName);
|
|
@@ -262,7 +112,9 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
262
112
|
? message
|
|
263
113
|
: "Invalid OpenAI API key. Please check your OPENAI_API_KEY environment variable.", this.providerName);
|
|
264
114
|
}
|
|
265
|
-
if (message.includes("rate limit") ||
|
|
115
|
+
if (message.includes("rate limit") ||
|
|
116
|
+
errorType === "rate_limit_error" ||
|
|
117
|
+
statusCode === 429) {
|
|
266
118
|
return new RateLimitError("OpenAI rate limit exceeded. Please try again later.", this.providerName);
|
|
267
119
|
}
|
|
268
120
|
if (message.includes("model_not_found")) {
|
|
@@ -271,377 +123,87 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
271
123
|
// Generic provider error
|
|
272
124
|
return new ProviderError(`OpenAI error: ${message}`, this.providerName);
|
|
273
125
|
}
|
|
126
|
+
// ===========================================================================
|
|
127
|
+
// Optional hook overrides
|
|
128
|
+
// ===========================================================================
|
|
274
129
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
130
|
+
* Wrap the stream in an OTel span to capture provider-level latency,
|
|
131
|
+
* token usage, finish reason, and cost. Mirrors the pre-migration
|
|
132
|
+
* `streamText`-span behaviour.
|
|
278
133
|
*/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
let tools = this.validateAndFilterToolsForOpenAI(allTools);
|
|
293
|
-
// OpenAI max tools limit - configurable via environment variable
|
|
294
|
-
const MAX_TOOLS = parseInt(process.env.OPENAI_MAX_TOOLS || "150", 10);
|
|
295
|
-
if (Object.keys(tools).length > MAX_TOOLS) {
|
|
296
|
-
logger.warn(`OpenAI: Too many tools (${Object.keys(tools).length}), limiting to ${MAX_TOOLS} tools`);
|
|
297
|
-
const toolEntries = Object.entries(tools);
|
|
298
|
-
tools = Object.fromEntries(toolEntries.slice(0, MAX_TOOLS));
|
|
299
|
-
}
|
|
300
|
-
// Count tools with Zod schemas for debugging
|
|
301
|
-
const zodToolsCount = Object.values(allTools).filter((tool) => {
|
|
302
|
-
if (!tool || typeof tool !== "object") {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
const schema = getToolSchema(tool);
|
|
306
|
-
return schema !== null && schema !== undefined && isZodSchema(schema);
|
|
307
|
-
}).length;
|
|
308
|
-
logger.info("OpenAI streaming tools", {
|
|
309
|
-
shouldUseTools,
|
|
310
|
-
allToolsCount: Object.keys(allTools).length,
|
|
311
|
-
filteredToolsCount: Object.keys(tools).length,
|
|
312
|
-
zodToolsCount,
|
|
313
|
-
toolNames: Object.keys(tools),
|
|
314
|
-
filteredOutTools: Object.keys(allTools).filter((name) => !tools[name]),
|
|
315
|
-
});
|
|
316
|
-
// Build message array from options with multimodal support
|
|
317
|
-
// Using protected helper from BaseProvider to eliminate code duplication
|
|
318
|
-
const messages = await this.buildMessagesForStream(options);
|
|
319
|
-
let resolvedToolChoice = resolveToolChoice(options, tools, shouldUseTools);
|
|
320
|
-
// Guard: if toolChoice names a specific tool that was filtered out, fall back to "auto"
|
|
321
|
-
if (resolvedToolChoice !== null &&
|
|
322
|
-
typeof resolvedToolChoice === "object" &&
|
|
323
|
-
"toolName" in resolvedToolChoice &&
|
|
324
|
-
typeof resolvedToolChoice.toolName === "string" &&
|
|
325
|
-
!tools[resolvedToolChoice.toolName]) {
|
|
326
|
-
logger.warn(`OpenAI: toolChoice references tool "${resolvedToolChoice.toolName}" which was removed during filtering; falling back to "auto"`);
|
|
327
|
-
resolvedToolChoice = "auto";
|
|
328
|
-
}
|
|
329
|
-
// Debug the actual request being sent to OpenAI
|
|
330
|
-
logger.debug(`OpenAI: streamText request parameters:`, {
|
|
331
|
-
modelName: this.modelName,
|
|
332
|
-
messagesCount: messages.length,
|
|
333
|
-
temperature: options.temperature,
|
|
334
|
-
maxTokens: options.maxTokens,
|
|
335
|
-
toolsCount: Object.keys(tools).length,
|
|
336
|
-
toolChoice: resolvedToolChoice,
|
|
337
|
-
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
338
|
-
firstToolExample: Object.keys(tools).length > 0
|
|
339
|
-
? {
|
|
340
|
-
name: Object.keys(tools)[0],
|
|
341
|
-
description: tools[Object.keys(tools)[0]]?.description,
|
|
342
|
-
parametersType: typeof getToolSchema(tools[Object.keys(tools)[0]]),
|
|
343
|
-
}
|
|
344
|
-
: "no-tools",
|
|
345
|
-
});
|
|
346
|
-
const model = await this.getAISDKModelWithMiddleware(options); // This is where network connection happens!
|
|
347
|
-
// Wrap streamText in an OTel span to capture provider-level latency and token usage
|
|
348
|
-
const streamSpan = streamTracer.startSpan("neurolink.provider.streamText", {
|
|
349
|
-
kind: SpanKind.CLIENT,
|
|
350
|
-
attributes: {
|
|
351
|
-
"gen_ai.system": "openai",
|
|
352
|
-
"gen_ai.request.model": getModelId(model) || this.modelName || "unknown",
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
// Reviewer follow-up: capture upstream provider errors via onError
|
|
356
|
-
// so the post-stream NoOutput detect can propagate the *real* cause
|
|
357
|
-
// into the sentinel's providerError / modelResponseRaw.
|
|
358
|
-
let capturedProviderError;
|
|
359
|
-
let result;
|
|
360
|
-
try {
|
|
361
|
-
result = streamText({
|
|
362
|
-
model,
|
|
363
|
-
messages: messages,
|
|
364
|
-
temperature: options.temperature,
|
|
365
|
-
maxOutputTokens: options.maxTokens, // No default limit - unlimited unless specified
|
|
366
|
-
maxRetries: 0, // NL11: Disable AI SDK's invisible internal retries; we handle retries with OTel instrumentation
|
|
367
|
-
tools,
|
|
368
|
-
stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
|
|
369
|
-
toolChoice: resolvedToolChoice,
|
|
370
|
-
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
371
|
-
experimental_repairToolCall: this.getToolCallRepairFn(options),
|
|
372
|
-
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
373
|
-
onError: (event) => {
|
|
374
|
-
capturedProviderError = event.error;
|
|
375
|
-
logger.error("OpenAI: Stream error", {
|
|
376
|
-
error: event.error instanceof Error
|
|
377
|
-
? event.error.message
|
|
378
|
-
: String(event.error),
|
|
379
|
-
});
|
|
380
|
-
},
|
|
381
|
-
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
382
|
-
logger.info("Tool execution completed", {
|
|
383
|
-
toolResults,
|
|
384
|
-
toolCalls,
|
|
385
|
-
});
|
|
386
|
-
// Emit tool:end for each completed tool result so Pipeline B
|
|
387
|
-
// captures telemetry for AI-SDK-driven tool calls (gap S2).
|
|
388
|
-
emitToolEndFromStepFinish(this.neurolink?.getEventEmitter(), toolResults);
|
|
389
|
-
// Handle tool execution storage
|
|
390
|
-
this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
|
|
391
|
-
logger.warn("[OpenAIProvider] Failed to store tool executions", {
|
|
392
|
-
provider: this.providerName,
|
|
393
|
-
error: error instanceof Error ? error.message : String(error),
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
},
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
catch (streamError) {
|
|
400
|
-
this.endStreamSpanWithError(streamSpan, streamError);
|
|
401
|
-
throw streamError;
|
|
134
|
+
onStreamStart(modelId) {
|
|
135
|
+
const span = streamTracer.startSpan("neurolink.provider.streamText", {
|
|
136
|
+
kind: SpanKind.CLIENT,
|
|
137
|
+
attributes: {
|
|
138
|
+
"gen_ai.system": "openai",
|
|
139
|
+
"gen_ai.request.model": modelId,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
let spanEnded = false;
|
|
143
|
+
const endSpan = () => {
|
|
144
|
+
if (!spanEnded) {
|
|
145
|
+
spanEnded = true;
|
|
146
|
+
span.end();
|
|
402
147
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
.
|
|
407
|
-
|
|
408
|
-
streamSpan.setAttribute("gen_ai.usage.output_tokens", usage.outputTokens || 0);
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
onUsage: (usage) => {
|
|
151
|
+
span.setAttribute("gen_ai.usage.input_tokens", usage.promptTokens);
|
|
152
|
+
span.setAttribute("gen_ai.usage.output_tokens", usage.completionTokens);
|
|
409
153
|
const cost = calculateCost(this.providerName, this.modelName, {
|
|
410
|
-
input: usage.
|
|
411
|
-
output: usage.
|
|
412
|
-
total:
|
|
154
|
+
input: usage.promptTokens,
|
|
155
|
+
output: usage.completionTokens,
|
|
156
|
+
total: usage.totalTokens,
|
|
413
157
|
});
|
|
414
158
|
if (cost && cost > 0) {
|
|
415
|
-
|
|
159
|
+
span.setAttribute("neurolink.cost", cost);
|
|
416
160
|
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// Finish reason may not be available if the stream is aborted
|
|
427
|
-
});
|
|
428
|
-
Promise.resolve(result.text)
|
|
429
|
-
.then(() => {
|
|
430
|
-
streamSpan.end();
|
|
431
|
-
})
|
|
432
|
-
.catch((err) => {
|
|
433
|
-
this.endStreamSpanWithError(streamSpan, err);
|
|
434
|
-
});
|
|
435
|
-
timeoutController?.cleanup();
|
|
436
|
-
// Debug the actual result structure
|
|
437
|
-
logger.debug(`OpenAI: streamText result structure:`, {
|
|
438
|
-
resultKeys: Object.keys(result),
|
|
439
|
-
hasTextStream: !!result.textStream,
|
|
440
|
-
hasToolCalls: !!result.toolCalls,
|
|
441
|
-
hasToolResults: !!result.toolResults,
|
|
442
|
-
resultType: typeof result,
|
|
443
|
-
});
|
|
444
|
-
const transformedStream = this.createOpenAITransformedStream(result, shouldUseTools, tools, () => capturedProviderError);
|
|
445
|
-
// Create analytics promise that resolves after stream completion
|
|
446
|
-
const analyticsPromise = streamAnalyticsCollector.createAnalytics(this.providerName, this.modelName, result, Date.now() - startTime, {
|
|
447
|
-
requestId: `openai-stream-${Date.now()}`,
|
|
448
|
-
streamingMode: true,
|
|
449
|
-
});
|
|
450
|
-
return {
|
|
451
|
-
stream: transformedStream,
|
|
452
|
-
provider: this.providerName,
|
|
453
|
-
model: this.modelName,
|
|
454
|
-
analytics: analyticsPromise,
|
|
455
|
-
metadata: {
|
|
456
|
-
startTime,
|
|
457
|
-
streamId: `openai-${Date.now()}`,
|
|
458
|
-
},
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
catch (error) {
|
|
462
|
-
timeoutController?.cleanup();
|
|
463
|
-
throw this.handleProviderError(error);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
async *createOpenAITransformedStream(result, shouldUseTools, tools, getCapturedProviderError) {
|
|
467
|
-
try {
|
|
468
|
-
logger.debug(`OpenAI: Starting stream transformation`, {
|
|
469
|
-
hasTextStream: !!result.textStream,
|
|
470
|
-
hasFullStream: !!result.fullStream,
|
|
471
|
-
resultKeys: Object.keys(result),
|
|
472
|
-
toolsEnabled: shouldUseTools,
|
|
473
|
-
toolsCount: Object.keys(tools).length,
|
|
474
|
-
});
|
|
475
|
-
let chunkCount = 0;
|
|
476
|
-
let contentYielded = 0;
|
|
477
|
-
const streamToUse = result.fullStream || result.textStream;
|
|
478
|
-
if (!streamToUse) {
|
|
479
|
-
logger.error("OpenAI: No stream available in result", {
|
|
480
|
-
resultKeys: Object.keys(result),
|
|
481
|
-
});
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
logger.debug(`OpenAI: Stream source selected:`, {
|
|
485
|
-
usingFullStream: !!result.fullStream,
|
|
486
|
-
usingTextStream: !!result.textStream && !result.fullStream,
|
|
487
|
-
streamSourceType: result.fullStream ? "fullStream" : "textStream",
|
|
488
|
-
});
|
|
489
|
-
for await (const chunk of streamToUse) {
|
|
490
|
-
chunkCount++;
|
|
491
|
-
logger.debug(`OpenAI: Processing chunk ${chunkCount}:`, {
|
|
492
|
-
chunkType: typeof chunk,
|
|
493
|
-
chunkValue: typeof chunk === "string"
|
|
494
|
-
? chunk.substring(0, 50)
|
|
495
|
-
: "not-string",
|
|
496
|
-
chunkKeys: chunk && typeof chunk === "object"
|
|
497
|
-
? Object.keys(chunk)
|
|
498
|
-
: "not-object",
|
|
499
|
-
hasText: chunk && typeof chunk === "object" && "text" in chunk,
|
|
500
|
-
hasTextDelta: chunk && typeof chunk === "object" && "textDelta" in chunk,
|
|
501
|
-
hasType: chunk && typeof chunk === "object" && "type" in chunk,
|
|
502
|
-
chunkTypeValue: chunk && typeof chunk === "object" && "type" in chunk
|
|
503
|
-
? chunk.type
|
|
504
|
-
: "no-type",
|
|
505
|
-
});
|
|
506
|
-
const contentToYield = this.extractOpenAIChunkContent(chunk);
|
|
507
|
-
if (contentToYield) {
|
|
508
|
-
contentYielded++;
|
|
509
|
-
logger.debug(`OpenAI: Yielding content ${contentYielded}:`, {
|
|
510
|
-
content: contentToYield.substring(0, 50),
|
|
511
|
-
length: contentToYield.length,
|
|
161
|
+
},
|
|
162
|
+
onFinish: (reason, capturedError) => {
|
|
163
|
+
span.setAttribute("gen_ai.response.finish_reason", reason || "unknown");
|
|
164
|
+
if (reason === "error") {
|
|
165
|
+
span.setStatus({
|
|
166
|
+
code: SpanStatusCode.ERROR,
|
|
167
|
+
message: capturedError instanceof Error
|
|
168
|
+
? capturedError.message
|
|
169
|
+
: String(capturedError ?? "stream error"),
|
|
512
170
|
});
|
|
513
|
-
yield { content: contentToYield };
|
|
514
171
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
contentYielded,
|
|
519
|
-
success: contentYielded > 0,
|
|
520
|
-
});
|
|
521
|
-
if (contentYielded === 0) {
|
|
522
|
-
logger.warn(`OpenAI: No content was yielded from stream despite processing ${chunkCount} chunks`);
|
|
523
|
-
// Curator P3-6 (round-2 fix): when no content was yielded, the
|
|
524
|
-
// production trigger sets NoOutputGeneratedError on
|
|
525
|
-
// result.finishReason rejection (NOT on the textStream itself).
|
|
526
|
-
// Surface that rejection here so the enriched sentinel actually
|
|
527
|
-
// fires for real-world no-output streams.
|
|
528
|
-
const detected = await detectPostStreamNoOutput(result, getCapturedProviderError?.());
|
|
529
|
-
if (detected) {
|
|
530
|
-
logger.warn("OpenAI: Stream produced no output (NoOutputGeneratedError) — caught from finishReason rejection");
|
|
531
|
-
stampNoOutputSpan(detected.sentinel);
|
|
532
|
-
yield detected.sentinel;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
catch (streamError) {
|
|
537
|
-
if (NoOutputGeneratedError.isInstance(streamError)) {
|
|
538
|
-
logger.warn("OpenAI: Stream produced no output (NoOutputGeneratedError) — caught from textStream");
|
|
539
|
-
// Defensive: AI SDK *can* throw this from textStream in some
|
|
540
|
-
// failure modes (catastrophic transform errors). Keep this path
|
|
541
|
-
// for completeness; the production trigger goes through the
|
|
542
|
-
// post-loop detect above.
|
|
543
|
-
const sentinel = await buildNoOutputSentinel(streamError, result, getCapturedProviderError?.());
|
|
544
|
-
stampNoOutputSpan(sentinel);
|
|
545
|
-
yield sentinel;
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
logger.error(`OpenAI: Stream transformation error:`, streamError);
|
|
549
|
-
throw streamError;
|
|
550
|
-
}
|
|
172
|
+
endSpan();
|
|
173
|
+
},
|
|
174
|
+
};
|
|
551
175
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if ("type" in chunk && chunk.type === "error") {
|
|
561
|
-
const errorChunk = chunk;
|
|
562
|
-
logger.error(`OpenAI: Error chunk received:`, {
|
|
563
|
-
errorType: errorChunk.type,
|
|
564
|
-
errorDetails: errorChunk.error,
|
|
565
|
-
fullChunk: JSON.stringify(chunk),
|
|
566
|
-
});
|
|
567
|
-
const errorMessage = errorChunk.error &&
|
|
568
|
-
typeof errorChunk.error === "object" &&
|
|
569
|
-
"message" in errorChunk.error
|
|
570
|
-
? String(errorChunk.error.message)
|
|
571
|
-
: "OpenAI API error when tools are enabled";
|
|
572
|
-
throw new Error(`OpenAI streaming error with tools: ${errorMessage}. Try disabling tools with --disableTools`);
|
|
573
|
-
}
|
|
574
|
-
if ("type" in chunk &&
|
|
575
|
-
chunk.type === "text-delta" &&
|
|
576
|
-
"textDelta" in chunk) {
|
|
577
|
-
const textDelta = chunk.textDelta;
|
|
578
|
-
logger.debug(`OpenAI: Found text-delta:`, { textDelta });
|
|
579
|
-
return textDelta;
|
|
580
|
-
}
|
|
581
|
-
if ("text" in chunk) {
|
|
582
|
-
const text = chunk.text;
|
|
583
|
-
logger.debug(`OpenAI: Found direct text:`, { text });
|
|
584
|
-
return text;
|
|
585
|
-
}
|
|
586
|
-
if (process.env.NEUROLINK_DEBUG === "true") {
|
|
587
|
-
logger.debug(`OpenAI: Unhandled object chunk:`, {
|
|
588
|
-
chunkKeys: Object.keys(chunk),
|
|
589
|
-
chunkType: "type" in chunk
|
|
590
|
-
? String(chunk.type)
|
|
591
|
-
: "no-type",
|
|
592
|
-
fullChunk: JSON.stringify(chunk).substring(0, 500),
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
return null;
|
|
596
|
-
}
|
|
597
|
-
if (typeof chunk === "string") {
|
|
598
|
-
logger.debug(`OpenAI: Found string chunk:`, {
|
|
599
|
-
content: chunk,
|
|
600
|
-
});
|
|
601
|
-
return chunk;
|
|
602
|
-
}
|
|
603
|
-
logger.warn(`OpenAI: Unhandled chunk type:`, {
|
|
604
|
-
type: typeof chunk,
|
|
605
|
-
value: String(chunk).substring(0, 100),
|
|
606
|
-
});
|
|
607
|
-
return null;
|
|
176
|
+
// ===========================================================================
|
|
177
|
+
// Embeddings — native POST /v1/embeddings
|
|
178
|
+
// ===========================================================================
|
|
179
|
+
/**
|
|
180
|
+
* Default embedding model, overridable via OPENAI_EMBEDDING_MODEL env var.
|
|
181
|
+
*/
|
|
182
|
+
getDefaultEmbeddingModel() {
|
|
183
|
+
return process.env.OPENAI_EMBEDDING_MODEL || "text-embedding-3-small";
|
|
608
184
|
}
|
|
609
185
|
/**
|
|
610
|
-
* Generate
|
|
186
|
+
* Generate an embedding for a single text input via native /v1/embeddings.
|
|
187
|
+
*
|
|
611
188
|
* @param text - The text to embed
|
|
612
189
|
* @param modelName - The embedding model to use (default: text-embedding-3-small)
|
|
613
190
|
* @returns Promise resolving to the embedding vector
|
|
614
191
|
*/
|
|
615
192
|
async embed(text, modelName) {
|
|
616
|
-
const embeddingModelName = modelName ||
|
|
193
|
+
const embeddingModelName = modelName || this.getDefaultEmbeddingModel();
|
|
617
194
|
logger.debug("Generating embedding", {
|
|
618
195
|
provider: this.providerName,
|
|
619
196
|
model: embeddingModelName,
|
|
620
197
|
textLength: text.length,
|
|
621
198
|
});
|
|
622
199
|
try {
|
|
623
|
-
|
|
624
|
-
// Create the OpenAI provider, preferring per-instance credentials over env vars
|
|
625
|
-
const openai = createOpenAI({
|
|
626
|
-
apiKey: this.credentials?.apiKey ?? getOpenAIApiKey(),
|
|
627
|
-
...(this.credentials?.baseURL
|
|
628
|
-
? { baseURL: this.credentials.baseURL }
|
|
629
|
-
: {}),
|
|
630
|
-
fetch: createProxyFetch(),
|
|
631
|
-
});
|
|
632
|
-
// Get the text embedding model
|
|
633
|
-
const embeddingModel = openai.textEmbeddingModel(embeddingModelName);
|
|
634
|
-
// Generate the embedding
|
|
635
|
-
const result = await embed({
|
|
636
|
-
model: embeddingModel,
|
|
637
|
-
value: text,
|
|
638
|
-
});
|
|
200
|
+
const [embedding] = await this.callEmbeddings(embeddingModelName, [text], "embed");
|
|
639
201
|
logger.debug("Embedding generated successfully", {
|
|
640
202
|
provider: this.providerName,
|
|
641
203
|
model: embeddingModelName,
|
|
642
|
-
embeddingDimension:
|
|
204
|
+
embeddingDimension: embedding.length,
|
|
643
205
|
});
|
|
644
|
-
return
|
|
206
|
+
return embedding;
|
|
645
207
|
}
|
|
646
208
|
catch (error) {
|
|
647
209
|
logger.error("Embedding generation failed", {
|
|
@@ -653,39 +215,28 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
653
215
|
}
|
|
654
216
|
}
|
|
655
217
|
/**
|
|
656
|
-
* Generate embeddings for multiple texts in a single batch
|
|
218
|
+
* Generate embeddings for multiple texts in a single batch via native /v1/embeddings.
|
|
219
|
+
*
|
|
657
220
|
* @param texts - The texts to embed
|
|
658
221
|
* @param modelName - The embedding model to use (default: text-embedding-3-small)
|
|
659
222
|
* @returns Promise resolving to an array of embedding vectors
|
|
660
223
|
*/
|
|
661
224
|
async embedMany(texts, modelName) {
|
|
662
|
-
const embeddingModelName = modelName ||
|
|
225
|
+
const embeddingModelName = modelName || this.getDefaultEmbeddingModel();
|
|
663
226
|
logger.debug("Generating batch embeddings", {
|
|
664
227
|
provider: this.providerName,
|
|
665
228
|
model: embeddingModelName,
|
|
666
229
|
count: texts.length,
|
|
667
230
|
});
|
|
668
231
|
try {
|
|
669
|
-
|
|
670
|
-
const openai = createOpenAI({
|
|
671
|
-
apiKey: this.credentials?.apiKey ?? getOpenAIApiKey(),
|
|
672
|
-
...(this.credentials?.baseURL
|
|
673
|
-
? { baseURL: this.credentials.baseURL }
|
|
674
|
-
: {}),
|
|
675
|
-
fetch: createProxyFetch(),
|
|
676
|
-
});
|
|
677
|
-
const embeddingModel = openai.textEmbeddingModel(embeddingModelName);
|
|
678
|
-
const result = await embedMany({
|
|
679
|
-
model: embeddingModel,
|
|
680
|
-
values: texts,
|
|
681
|
-
});
|
|
232
|
+
const embeddings = await this.callEmbeddings(embeddingModelName, texts, "embedMany");
|
|
682
233
|
logger.debug("Batch embeddings generated successfully", {
|
|
683
234
|
provider: this.providerName,
|
|
684
235
|
model: embeddingModelName,
|
|
685
|
-
count:
|
|
686
|
-
embeddingDimension:
|
|
236
|
+
count: embeddings.length,
|
|
237
|
+
embeddingDimension: embeddings[0]?.length,
|
|
687
238
|
});
|
|
688
|
-
return
|
|
239
|
+
return embeddings;
|
|
689
240
|
}
|
|
690
241
|
catch (error) {
|
|
691
242
|
logger.error("Batch embedding generation failed", {
|
|
@@ -696,6 +247,65 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
696
247
|
throw this.handleProviderError(error);
|
|
697
248
|
}
|
|
698
249
|
}
|
|
250
|
+
async callEmbeddings(modelName, input, operation) {
|
|
251
|
+
const url = `${stripTrailingSlash(this.config.baseURL)}/embeddings`;
|
|
252
|
+
const fetchImpl = createProxyFetch();
|
|
253
|
+
const timeoutController = createTimeoutController(30_000, this.providerName, "generate");
|
|
254
|
+
try {
|
|
255
|
+
const res = await fetchImpl(url, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: {
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
260
|
+
},
|
|
261
|
+
body: JSON.stringify({
|
|
262
|
+
model: modelName,
|
|
263
|
+
input: input.length === 1 ? input[0] : input,
|
|
264
|
+
}),
|
|
265
|
+
...(timeoutController?.controller.signal
|
|
266
|
+
? { signal: timeoutController.controller.signal }
|
|
267
|
+
: {}),
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
const bodyText = await res.text().catch(() => "");
|
|
271
|
+
let message = `OpenAI ${operation} failed with status ${res.status}`;
|
|
272
|
+
let errorType;
|
|
273
|
+
if (bodyText) {
|
|
274
|
+
try {
|
|
275
|
+
const parsed = JSON.parse(bodyText);
|
|
276
|
+
if (parsed.error?.message) {
|
|
277
|
+
message = parsed.error.message;
|
|
278
|
+
}
|
|
279
|
+
errorType = parsed.error?.type;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// Non-JSON error body — keep the status-based message.
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Throw a raw, annotated error so the public embed/embedMany catch
|
|
286
|
+
// formats it exactly once via handleProviderError(), preserving
|
|
287
|
+
// status-based classification (401 -> auth, 429 -> rate limit, …).
|
|
288
|
+
throw Object.assign(new Error(message), {
|
|
289
|
+
status: res.status,
|
|
290
|
+
type: errorType,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const json = (await res.json());
|
|
294
|
+
const embeddings = (json.data ?? [])
|
|
295
|
+
.map((row) => row.embedding)
|
|
296
|
+
.filter((e) => Array.isArray(e));
|
|
297
|
+
if (embeddings.length === 0) {
|
|
298
|
+
throw new ProviderError(`OpenAI ${operation} returned no embeddings`, this.providerName);
|
|
299
|
+
}
|
|
300
|
+
return embeddings;
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
timeoutController?.cleanup();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// ===========================================================================
|
|
307
|
+
// Image generation — native POST /v1/images/generations
|
|
308
|
+
// ===========================================================================
|
|
699
309
|
/**
|
|
700
310
|
* Image generation via the OpenAI Images API (`/v1/images/generations`).
|
|
701
311
|
*
|
|
@@ -717,10 +327,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
717
327
|
throw new Error("OpenAI image generation requires a prompt (input.text or prompt)");
|
|
718
328
|
}
|
|
719
329
|
const model = options.model ?? this.modelName;
|
|
720
|
-
const
|
|
721
|
-
const baseURL = (this.credentials?.baseURL ??
|
|
722
|
-
process.env.OPENAI_BASE_URL ??
|
|
723
|
-
"https://api.openai.com/v1").replace(/\/$/, "");
|
|
330
|
+
const baseURL = stripTrailingSlash(this.config.baseURL ?? OPENAI_DEFAULT_BASE_URL);
|
|
724
331
|
// Image-gen extras live on `options` but are not part of the strict
|
|
725
332
|
// TextGenerationOptions shape — cast to a permissive type to read them.
|
|
726
333
|
const extras = options;
|
|
@@ -778,7 +385,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
778
385
|
response = await proxyFetch(`${baseURL}/images/generations`, {
|
|
779
386
|
method: "POST",
|
|
780
387
|
headers: {
|
|
781
|
-
Authorization: `Bearer ${apiKey}`,
|
|
388
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
782
389
|
"Content-Type": "application/json",
|
|
783
390
|
},
|
|
784
391
|
body: JSON.stringify(body),
|
|
@@ -876,6 +483,4 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
876
483
|
return "1024x1024";
|
|
877
484
|
}
|
|
878
485
|
}
|
|
879
|
-
// Export for factory registration
|
|
880
|
-
export default OpenAIProvider;
|
|
881
486
|
//# sourceMappingURL=openAI.js.map
|