@happyvertical/ai 0.79.0 → 0.80.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/AGENT.md +1 -1
  2. package/dist/chunks/anthropic-DA8sdt12.js +438 -0
  3. package/dist/chunks/anthropic-DA8sdt12.js.map +1 -0
  4. package/dist/chunks/bedrock-DDiYFUw0.js +699 -0
  5. package/dist/chunks/bedrock-DDiYFUw0.js.map +1 -0
  6. package/dist/chunks/bifrost-fM9FpW_w.js +198 -0
  7. package/dist/chunks/bifrost-fM9FpW_w.js.map +1 -0
  8. package/dist/chunks/claude-cli-CnJh5KQT.js +498 -0
  9. package/dist/chunks/claude-cli-CnJh5KQT.js.map +1 -0
  10. package/dist/chunks/gateway-admin-CiKvHst7.js +283 -0
  11. package/dist/chunks/gateway-admin-CiKvHst7.js.map +1 -0
  12. package/dist/chunks/gemini-Bu7Fke7c.js +593 -0
  13. package/dist/chunks/gemini-Bu7Fke7c.js.map +1 -0
  14. package/dist/chunks/huggingface-B07_6wHW.js +295 -0
  15. package/dist/chunks/huggingface-B07_6wHW.js.map +1 -0
  16. package/dist/chunks/litellm-D_Oo9OQ_.js +185 -0
  17. package/dist/chunks/litellm-D_Oo9OQ_.js.map +1 -0
  18. package/dist/chunks/ollama-D4ksOTO8.js +626 -0
  19. package/dist/chunks/ollama-D4ksOTO8.js.map +1 -0
  20. package/dist/chunks/openai--F38QHiJ.js +691 -0
  21. package/dist/chunks/openai--F38QHiJ.js.map +1 -0
  22. package/dist/chunks/qwen-tts-DRRbOrhA.js +333 -0
  23. package/dist/chunks/qwen-tts-DRRbOrhA.js.map +1 -0
  24. package/dist/chunks/rate-limit-BwyPGXFW.js +199 -0
  25. package/dist/chunks/rate-limit-BwyPGXFW.js.map +1 -0
  26. package/dist/chunks/safety-CEnoJS6X.js +260 -0
  27. package/dist/chunks/safety-CEnoJS6X.js.map +1 -0
  28. package/dist/chunks/types-FHs-KbGL.js +117 -0
  29. package/dist/chunks/types-FHs-KbGL.js.map +1 -0
  30. package/dist/chunks/usage-C1Y1Nlg4.js +35 -0
  31. package/dist/chunks/usage-C1Y1Nlg4.js.map +1 -0
  32. package/dist/cli/claude-context.js +17 -17
  33. package/dist/cli/claude-context.js.map +1 -1
  34. package/dist/index.js +717 -23
  35. package/dist/index.js.map +1 -1
  36. package/metadata.json +1 -1
  37. package/package.json +5 -5
  38. package/dist/chunks/anthropic-2z-82zgr.js +0 -530
  39. package/dist/chunks/anthropic-2z-82zgr.js.map +0 -1
  40. package/dist/chunks/bedrock-Dc2eVPUD.js +0 -920
  41. package/dist/chunks/bedrock-Dc2eVPUD.js.map +0 -1
  42. package/dist/chunks/bifrost-CEnCsciy.js +0 -258
  43. package/dist/chunks/bifrost-CEnCsciy.js.map +0 -1
  44. package/dist/chunks/claude-cli-X1ONjE8K.js +0 -603
  45. package/dist/chunks/claude-cli-X1ONjE8K.js.map +0 -1
  46. package/dist/chunks/gateway-admin-BUhBzXZb.js +0 -359
  47. package/dist/chunks/gateway-admin-BUhBzXZb.js.map +0 -1
  48. package/dist/chunks/gemini-CS56gY0D.js +0 -768
  49. package/dist/chunks/gemini-CS56gY0D.js.map +0 -1
  50. package/dist/chunks/huggingface-wQSfO5xA.js +0 -411
  51. package/dist/chunks/huggingface-wQSfO5xA.js.map +0 -1
  52. package/dist/chunks/index-DCXO0nZA.js +0 -1293
  53. package/dist/chunks/index-DCXO0nZA.js.map +0 -1
  54. package/dist/chunks/litellm-BMFTYbWc.js +0 -245
  55. package/dist/chunks/litellm-BMFTYbWc.js.map +0 -1
  56. package/dist/chunks/ollama-DsGDrA-c.js +0 -962
  57. package/dist/chunks/ollama-DsGDrA-c.js.map +0 -1
  58. package/dist/chunks/openai-CJEo69jb.js +0 -882
  59. package/dist/chunks/openai-CJEo69jb.js.map +0 -1
  60. package/dist/chunks/qwen-tts-BLYZ6d9s.js +0 -365
  61. package/dist/chunks/qwen-tts-BLYZ6d9s.js.map +0 -1
  62. package/dist/chunks/usage-DMWiJ2oB.js +0 -21
  63. package/dist/chunks/usage-DMWiJ2oB.js.map +0 -1
@@ -0,0 +1,691 @@
1
+ import { a as ContextLengthError, i as ContentFilterError, o as ModelNotFoundError, r as AuthenticationError, s as RateLimitError, t as AIError } from "./types-FHs-KbGL.js";
2
+ import { n as extractRetryAfterSeconds } from "./rate-limit-BwyPGXFW.js";
3
+ import { a as normalizeBaseAIOptions, c as prepareRequestControls, o as normalizeChatOptions, s as normalizeImageGenerationOptions } from "./safety-CEnoJS6X.js";
4
+ import { t as emitUsage } from "./usage-C1Y1Nlg4.js";
5
+ import OpenAI from "openai";
6
+ //#region src/shared/providers/openai.ts
7
+ /**
8
+ * OpenAI provider implementation
9
+ *
10
+ * Provides a standardized interface for interacting with OpenAI's GPT models,
11
+ * including chat completions, text completions, embeddings, and streaming responses.
12
+ * Supports all major OpenAI features including function calling, vision models,
13
+ * and custom fine-tuned models.
14
+ */
15
+ var OPENAI_PROFILE = {
16
+ providerLabel: "OpenAI",
17
+ providerName: "openai",
18
+ defaultModel: "gpt-4o",
19
+ capabilities: {
20
+ chat: true,
21
+ completion: true,
22
+ embeddings: true,
23
+ streaming: true,
24
+ functions: true,
25
+ vision: true,
26
+ fineTuning: true,
27
+ imageEmbeddings: true,
28
+ imageGeneration: true,
29
+ tts: false,
30
+ voiceCloning: false,
31
+ voiceDesign: false,
32
+ maxContextLength: 128e3,
33
+ supportedOperations: [
34
+ "chat",
35
+ "completion",
36
+ "embedding",
37
+ "streaming",
38
+ "functions",
39
+ "vision",
40
+ "image_embedding",
41
+ "image_generation"
42
+ ]
43
+ },
44
+ describeModel: (modelId) => `OpenAI model: ${modelId}`,
45
+ getContextLength(modelId) {
46
+ if (modelId.includes("gpt-4o")) return 128e3;
47
+ if (modelId.includes("gpt-4.1")) return 128e3;
48
+ if (modelId.includes("gpt-4-turbo")) return 128e3;
49
+ if (modelId.includes("gpt-4")) return 8192;
50
+ if (modelId.includes("gpt-3.5-turbo")) return 16385;
51
+ if (modelId.includes("text-embedding")) return 8192;
52
+ return 4096;
53
+ },
54
+ getModelCapabilities(modelId) {
55
+ const capabilities = ["text"];
56
+ if (modelId.includes("gpt")) capabilities.push("chat", "functions");
57
+ if (modelId.includes("vision") || modelId === "gpt-4o" || modelId.includes("gpt-4.1")) capabilities.push("vision");
58
+ if (modelId.includes("embedding")) capabilities.push("embeddings");
59
+ return capabilities;
60
+ },
61
+ shouldIncludeModel(modelId) {
62
+ return modelId.includes("gpt") || modelId.includes("text-embedding");
63
+ },
64
+ supportsFunctions(modelId) {
65
+ return modelId.includes("gpt-4") || modelId.includes("gpt-3.5");
66
+ },
67
+ supportsVision(modelId) {
68
+ return modelId.includes("vision") || modelId === "gpt-4o" || modelId.includes("gpt-4.1");
69
+ }
70
+ };
71
+ /**
72
+ * OpenAI provider implementation that handles all interactions with OpenAI's API.
73
+ * Supports GPT models, embeddings, function calling, streaming, and vision capabilities.
74
+ */
75
+ var OpenAIProvider = class {
76
+ client;
77
+ options;
78
+ profile;
79
+ /**
80
+ * Creates a new OpenAI provider instance
81
+ * @param options - Configuration options for the OpenAI provider
82
+ */
83
+ constructor(options, profile = OPENAI_PROFILE) {
84
+ this.profile = profile;
85
+ this.options = normalizeBaseAIOptions({
86
+ defaultModel: this.profile.defaultModel,
87
+ ...options
88
+ });
89
+ this.client = new OpenAI({
90
+ apiKey: this.options.apiKey,
91
+ baseURL: this.options.baseUrl,
92
+ organization: this.options.organization,
93
+ timeout: this.options.timeout,
94
+ maxRetries: this.options.maxRetries,
95
+ defaultHeaders: this.options.headers
96
+ });
97
+ }
98
+ /**
99
+ * Generate a chat completion using OpenAI's chat models
100
+ * @param messages - Array of conversation messages
101
+ * @param options - Optional configuration for the chat completion
102
+ * @returns Promise resolving to the AI response with content and metadata
103
+ * @throws {AIError} When the API request fails or returns invalid data
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const response = await provider.chat([
108
+ * { role: 'system', content: 'You are a helpful assistant.' },
109
+ * { role: 'user', content: 'What is the capital of France?' }
110
+ * ], {
111
+ * model: 'gpt-4o',
112
+ * temperature: 0.7,
113
+ * maxTokens: 150
114
+ * });
115
+ * console.log(response.content); // "Paris is the capital of France."
116
+ * ```
117
+ */
118
+ async chat(messages, options = {}) {
119
+ const startTime = Date.now();
120
+ let controls;
121
+ try {
122
+ const model = options.model || this.options.defaultModel || this.profile.defaultModel;
123
+ options = normalizeChatOptions(this.options, options, this.profile.providerName, model);
124
+ controls = prepareRequestControls(this.options, options);
125
+ const request = {
126
+ model,
127
+ messages: this.mapMessagesToOpenAI(messages),
128
+ max_tokens: options.maxTokens,
129
+ temperature: options.temperature,
130
+ top_p: options.topP,
131
+ n: options.n,
132
+ stop: options.stop,
133
+ frequency_penalty: options.frequencyPenalty,
134
+ presence_penalty: options.presencePenalty,
135
+ user: options.user,
136
+ tools: options.tools?.map((tool) => ({
137
+ type: "function",
138
+ function: {
139
+ name: tool.function.name,
140
+ description: tool.function.description,
141
+ parameters: tool.function.parameters
142
+ }
143
+ })),
144
+ tool_choice: this.mapToolChoice(options.toolChoice),
145
+ response_format: options.responseFormat,
146
+ seed: options.seed,
147
+ stream: false
148
+ };
149
+ if ((this.profile.providerName === "bifrost" || this.profile.providerName === "litellm") && (options.reasoning?.maxTokens || 0) > 0) request.reasoning = {
150
+ effort: options.reasoning?.effort,
151
+ max_tokens: options.reasoning?.maxTokens,
152
+ include_thoughts: options.reasoning?.includeThoughts
153
+ };
154
+ const response = await this.client.chat.completions.create(request, {
155
+ signal: controls.signal,
156
+ timeout: controls.timeout
157
+ });
158
+ const choice = response.choices[0];
159
+ if (!choice) throw new AIError(`No choices returned from ${this.profile.providerLabel}`, "NO_CHOICES", this.profile.providerName);
160
+ const usage = this.mapUsage(response.usage);
161
+ emitUsage(this.options, this.profile.providerName, "chat", response.model || model, usage, startTime, options.usageTags);
162
+ return {
163
+ content: choice.message.content || "",
164
+ usage,
165
+ model: response.model,
166
+ finishReason: this.mapFinishReason(choice.finish_reason),
167
+ toolCalls: choice.message.tool_calls?.filter((call) => call.type === "function").map((call) => ({
168
+ id: call.id,
169
+ type: "function",
170
+ function: {
171
+ name: call.function.name,
172
+ arguments: call.function.arguments
173
+ }
174
+ }))
175
+ };
176
+ } catch (error) {
177
+ if (controls?.didTimeout()) throw new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", this.profile.providerName, options.model);
178
+ if (options.signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED", this.profile.providerName, options.model);
179
+ throw this.mapError(error);
180
+ } finally {
181
+ controls?.cleanup();
182
+ }
183
+ }
184
+ /**
185
+ * Generate a text completion for a given prompt
186
+ * @param prompt - The text prompt to complete
187
+ * @param options - Optional configuration for the completion
188
+ * @returns Promise resolving to the AI response
189
+ * @throws {AIError} When the API request fails
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const response = await provider.complete('The weather today is', {
194
+ * model: 'gpt-4o',
195
+ * maxTokens: 50,
196
+ * temperature: 0.5
197
+ * });
198
+ * ```
199
+ */
200
+ async complete(prompt, options = {}) {
201
+ return this.chat([{
202
+ role: "user",
203
+ content: prompt
204
+ }], {
205
+ model: options.model,
206
+ maxTokens: options.maxTokens,
207
+ temperature: options.temperature,
208
+ topP: options.topP,
209
+ n: options.n,
210
+ stop: options.stop,
211
+ stream: options.stream,
212
+ onProgress: options.onProgress,
213
+ signal: options.signal,
214
+ timeout: options.timeout,
215
+ reasoning: options.reasoning,
216
+ usageTags: options.usageTags
217
+ });
218
+ }
219
+ /**
220
+ * Simple message interface for single-turn interactions with optional history
221
+ *
222
+ * @param text - The message text to send
223
+ * @param options - Configuration options including history, model, etc.
224
+ * @returns Promise resolving to the response content string
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * // Simple usage
229
+ * const response = await provider.message('Hello!');
230
+ *
231
+ * // With options
232
+ * const response = await provider.message('Analyze this', {
233
+ * model: 'gpt-4o',
234
+ * responseFormat: { type: 'json_object' }
235
+ * });
236
+ *
237
+ * // With history
238
+ * const response = await provider.message('What was my question?', {
239
+ * history: [
240
+ * { role: 'user', content: 'What is 2+2?' },
241
+ * { role: 'assistant', content: '4' }
242
+ * ]
243
+ * });
244
+ * ```
245
+ */
246
+ async message(text, options = {}) {
247
+ const messages = [...options.history || [], {
248
+ role: options.role || "user",
249
+ content: text
250
+ }];
251
+ return (await this.chat(messages, {
252
+ model: options.model,
253
+ maxTokens: options.maxTokens,
254
+ temperature: options.temperature,
255
+ topP: options.topP,
256
+ stop: options.stop,
257
+ stream: options.stream,
258
+ frequencyPenalty: options.frequencyPenalty,
259
+ presencePenalty: options.presencePenalty,
260
+ responseFormat: options.responseFormat,
261
+ seed: options.seed,
262
+ tools: options.tools,
263
+ toolChoice: options.toolChoice,
264
+ onProgress: options.onProgress,
265
+ signal: options.signal,
266
+ timeout: options.timeout,
267
+ reasoning: options.reasoning,
268
+ usageTags: options.usageTags
269
+ })).content;
270
+ }
271
+ /**
272
+ * Generate embeddings for the given text(s)
273
+ * @param text - Single text string or array of texts to embed
274
+ * @param options - Optional configuration for embeddings
275
+ * @returns Promise resolving to embeddings response with vector arrays
276
+ * @throws {AIError} When the API request fails
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * // Single text embedding
281
+ * const response1 = await provider.embed('Hello world');
282
+ * console.log(response1.embeddings[0]); // Array of numbers
283
+ *
284
+ * // Multiple text embeddings
285
+ * const response2 = await provider.embed(['Text 1', 'Text 2']);
286
+ * console.log(response2.embeddings.length); // 2
287
+ * ```
288
+ */
289
+ async embed(text, options = {}) {
290
+ const startTime = Date.now();
291
+ try {
292
+ const model = options.model || "text-embedding-3-small";
293
+ const input = Array.isArray(text) ? text : [text];
294
+ const response = await this.client.embeddings.create({
295
+ model,
296
+ input,
297
+ encoding_format: options.encodingFormat,
298
+ dimensions: options.dimensions,
299
+ user: options.user
300
+ });
301
+ const usage = this.mapUsage(response.usage);
302
+ emitUsage(this.options, this.profile.providerName, "embed", response.model || model, usage, startTime, options.usageTags);
303
+ return {
304
+ embeddings: response.data.map((item) => item.embedding),
305
+ usage,
306
+ model: response.model
307
+ };
308
+ } catch (error) {
309
+ throw this.mapError(error);
310
+ }
311
+ }
312
+ /**
313
+ * Convert an image to a base64 data URL
314
+ * @param image - Image as URL, base64 data URL, or Buffer
315
+ * @returns base64 data URL string
316
+ * @private
317
+ */
318
+ async imageToBase64(image, signal) {
319
+ if (Buffer.isBuffer(image)) return `data:image/png;base64,${image.toString("base64")}`;
320
+ if (image.startsWith("data:")) return image;
321
+ const response = await fetch(image, { signal });
322
+ if (!response.ok) throw new AIError(`Failed to fetch image: ${response.status} ${response.statusText}`, "IMAGE_FETCH_ERROR", this.profile.providerName);
323
+ const arrayBuffer = await response.arrayBuffer();
324
+ const buffer = Buffer.from(arrayBuffer);
325
+ return `data:${response.headers.get("content-type") || "image/png"};base64,${buffer.toString("base64")}`;
326
+ }
327
+ /**
328
+ * Generate a text description of an image
329
+ * @param image - Image as URL, base64 data URL, or Buffer
330
+ * @param prompt - Custom prompt for description (optional)
331
+ * @param options - Optional configuration
332
+ * @returns Promise resolving to the description string
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * const description = await provider.describeImage('https://example.com/image.jpg');
337
+ * ```
338
+ */
339
+ async describeImage(image, prompt, options = {}) {
340
+ let controls;
341
+ try {
342
+ const defaultPrompt = "Describe this image for a search index. Include objects, mood, lighting, and any visible text.";
343
+ controls = prepareRequestControls(this.options, options);
344
+ const imageUrl = await this.imageToBase64(image, controls.signal);
345
+ return (await this.chat([{
346
+ role: "user",
347
+ content: [{
348
+ type: "text",
349
+ text: prompt || defaultPrompt
350
+ }, {
351
+ type: "image_url",
352
+ image_url: {
353
+ url: imageUrl,
354
+ detail: options.detail || "auto"
355
+ }
356
+ }]
357
+ }], {
358
+ model: options.model || this.options.defaultModel || this.profile.defaultModel,
359
+ maxTokens: options.maxTokens ?? 500,
360
+ signal: controls.signal,
361
+ timeout: controls.timeout,
362
+ reasoning: options.reasoning,
363
+ usageTags: options.usageTags
364
+ })).content;
365
+ } catch (error) {
366
+ if (controls?.didTimeout()) throw new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", this.profile.providerName, options.model);
367
+ if (options.signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED", this.profile.providerName, options.model);
368
+ throw this.mapError(error);
369
+ } finally {
370
+ controls?.cleanup();
371
+ }
372
+ }
373
+ /**
374
+ * Generate embeddings for an image using describe-then-embed pattern
375
+ * @param image - Image as URL, base64 data URL, or Buffer
376
+ * @param options - Optional configuration for image embeddings
377
+ * @returns Promise resolving to embeddings response
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * const embedding = await provider.embedImage('https://example.com/image.jpg');
382
+ * ```
383
+ */
384
+ async embedImage(image, options = {}) {
385
+ try {
386
+ const description = await this.describeImage(image, "Describe this image in detail for semantic embedding. Include all visible objects, colors, textures, composition, text, people, actions, and overall scene context.");
387
+ return this.embed(description, {
388
+ model: options.model || "text-embedding-3-small",
389
+ dimensions: options.dimensions,
390
+ user: options.user
391
+ });
392
+ } catch (error) {
393
+ throw this.mapError(error);
394
+ }
395
+ }
396
+ /**
397
+ * Generate an image from a text prompt using DALL-E
398
+ * @param prompt - Text description of the image to generate
399
+ * @param options - Optional configuration for image generation
400
+ * @returns Promise resolving to generated image(s)
401
+ *
402
+ * @example
403
+ * ```typescript
404
+ * const result = await provider.generateImage('A sunset over mountains');
405
+ * fs.writeFileSync('image.png', result.images[0].data);
406
+ * ```
407
+ */
408
+ async generateImage(prompt, options = {}) {
409
+ let controls;
410
+ try {
411
+ const model = options.model || "dall-e-3";
412
+ options = normalizeImageGenerationOptions(this.options, options, this.profile.providerName, model);
413
+ controls = prepareRequestControls(this.options, options);
414
+ const requestParams = {
415
+ model,
416
+ prompt,
417
+ n: model === "dall-e-3" ? 1 : options.n,
418
+ size: options.size || "1024x1024",
419
+ response_format: options.outputFormat === "url" ? "url" : "b64_json"
420
+ };
421
+ if (model === "dall-e-3") {
422
+ if (options.style) requestParams.style = options.style;
423
+ if (options.quality) requestParams.quality = options.quality;
424
+ }
425
+ return {
426
+ images: ((await this.client.images.generate(requestParams, {
427
+ signal: controls.signal,
428
+ timeout: controls.timeout
429
+ })).data || []).map((item) => {
430
+ let data;
431
+ const mimeType = "image/png";
432
+ if (options.outputFormat === "url") data = item.url || "";
433
+ else if (options.outputFormat === "base64") data = item.b64_json || "";
434
+ else data = Buffer.from(item.b64_json || "", "base64");
435
+ return {
436
+ data,
437
+ mimeType,
438
+ revisedPrompt: item.revised_prompt
439
+ };
440
+ }),
441
+ model
442
+ };
443
+ } catch (error) {
444
+ if (controls?.didTimeout()) throw new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", this.profile.providerName, options.model);
445
+ if (options.signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED", this.profile.providerName, options.model);
446
+ throw this.mapError(error);
447
+ } finally {
448
+ controls?.cleanup();
449
+ }
450
+ }
451
+ /**
452
+ * Stream a chat completion response in real-time
453
+ * @param messages - Array of conversation messages
454
+ * @param options - Optional configuration for the chat completion
455
+ * @yields Individual content chunks as they arrive
456
+ * @throws {AIError} When the streaming request fails
457
+ *
458
+ * @example
459
+ * ```typescript
460
+ * for await (const chunk of provider.stream([
461
+ * { role: 'user', content: 'Write a story about AI' }
462
+ * ])) {
463
+ * process.stdout.write(chunk);
464
+ * }
465
+ * ```
466
+ */
467
+ async *stream(messages, options = {}) {
468
+ const startTime = Date.now();
469
+ let controls;
470
+ try {
471
+ const model = options.model || this.options.defaultModel || this.profile.defaultModel;
472
+ options = normalizeChatOptions(this.options, options, this.profile.providerName, model);
473
+ controls = prepareRequestControls(this.options, options);
474
+ const request = {
475
+ model,
476
+ messages: this.mapMessagesToOpenAI(messages),
477
+ max_tokens: options.maxTokens,
478
+ temperature: options.temperature,
479
+ top_p: options.topP,
480
+ stop: options.stop,
481
+ frequency_penalty: options.frequencyPenalty,
482
+ presence_penalty: options.presencePenalty,
483
+ user: options.user,
484
+ stream: true
485
+ };
486
+ if ((this.profile.providerName === "bifrost" || this.profile.providerName === "litellm") && (options.reasoning?.maxTokens || 0) > 0) request.reasoning = {
487
+ effort: options.reasoning?.effort,
488
+ max_tokens: options.reasoning?.maxTokens,
489
+ include_thoughts: options.reasoning?.includeThoughts
490
+ };
491
+ const stream = await this.client.chat.completions.create(request, {
492
+ signal: controls.signal,
493
+ timeout: controls.timeout
494
+ });
495
+ for await (const chunk of stream) {
496
+ const content = chunk.choices[0]?.delta?.content;
497
+ if (content) {
498
+ if (options.onProgress) options.onProgress(content);
499
+ yield content;
500
+ }
501
+ }
502
+ emitUsage(this.options, this.profile.providerName, "stream", model, void 0, startTime, options.usageTags);
503
+ } catch (error) {
504
+ if (controls?.didTimeout()) throw new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", this.profile.providerName, options.model);
505
+ if (options.signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED", this.profile.providerName, options.model);
506
+ throw this.mapError(error);
507
+ } finally {
508
+ controls?.cleanup();
509
+ }
510
+ }
511
+ /**
512
+ * Count the number of tokens in the given text
513
+ * @param text - The text to count tokens for
514
+ * @returns Promise resolving to the estimated token count
515
+ *
516
+ * @remarks
517
+ * OpenAI doesn't provide a direct token counting API, so this is an approximation
518
+ * based on the general rule of ~4 characters per token. For precise counting,
519
+ * consider using a dedicated tokenizer library.
520
+ *
521
+ * @example
522
+ * ```typescript
523
+ * const count = await provider.countTokens('Hello, world!');
524
+ * console.log(count); // Approximately 4 tokens
525
+ * ```
526
+ */
527
+ async countTokens(text) {
528
+ return Math.ceil(text.length / 4);
529
+ }
530
+ /**
531
+ * Get a list of available OpenAI models
532
+ * @returns Promise resolving to an array of model information
533
+ * @throws {AIError} When the API request fails
534
+ *
535
+ * @example
536
+ * ```typescript
537
+ * const models = await provider.getModels();
538
+ * const gptModels = models.filter(m => m.id.includes('gpt'));
539
+ * ```
540
+ */
541
+ async getModels() {
542
+ try {
543
+ return (await this.client.models.list()).data.filter((model) => this.profile.shouldIncludeModel(model.id)).map((model) => ({
544
+ id: model.id,
545
+ name: model.id,
546
+ description: this.profile.describeModel(model.id),
547
+ contextLength: this.profile.getContextLength(model.id),
548
+ capabilities: this.profile.getModelCapabilities(model.id),
549
+ supportsFunctions: this.profile.supportsFunctions(model.id),
550
+ supportsVision: this.profile.supportsVision(model.id)
551
+ }));
552
+ } catch (error) {
553
+ throw this.mapError(error);
554
+ }
555
+ }
556
+ /**
557
+ * Get the capabilities supported by this OpenAI provider
558
+ * @returns Promise resolving to provider capabilities
559
+ *
560
+ * @example
561
+ * ```typescript
562
+ * const caps = await provider.getCapabilities();
563
+ * if (caps.functions) {
564
+ * // Provider supports function calling
565
+ * }
566
+ * ```
567
+ */
568
+ async getCapabilities() {
569
+ return { ...this.profile.capabilities };
570
+ }
571
+ async synthesizeSpeech(_text, _options) {
572
+ throw new AIError(`TTS is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
573
+ }
574
+ streamSpeech(_text, _options) {
575
+ const error = new AIError(`TTS streaming is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
576
+ return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
577
+ }
578
+ async cloneVoice(_options) {
579
+ throw new AIError(`Voice cloning is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
580
+ }
581
+ async designVoice(_options) {
582
+ throw new AIError(`Voice design is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
583
+ }
584
+ async getVoices(_options) {
585
+ throw new AIError(`Voice listing is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
586
+ }
587
+ /**
588
+ * Maps internal AI messages to OpenAI's message format
589
+ * @param messages - Array of internal AI messages
590
+ * @returns Array of OpenAI-compatible message parameters
591
+ * @private
592
+ */
593
+ mapMessagesToOpenAI(messages) {
594
+ return messages.map((message) => {
595
+ let content;
596
+ if (typeof message.content === "string") content = message.content;
597
+ else content = message.content.map((part) => {
598
+ if (part.type === "text") return {
599
+ type: "text",
600
+ text: part.text
601
+ };
602
+ return {
603
+ type: "image_url",
604
+ image_url: {
605
+ url: part.image_url.url,
606
+ detail: part.image_url.detail
607
+ }
608
+ };
609
+ });
610
+ const baseMessage = {
611
+ role: message.role,
612
+ content
613
+ };
614
+ if (message.name && (message.role === "system" || message.role === "user" || message.role === "function")) baseMessage.name = message.name;
615
+ if (message.tool_calls && message.role === "assistant") baseMessage.tool_calls = message.tool_calls;
616
+ return baseMessage;
617
+ });
618
+ }
619
+ /**
620
+ * Maps internal tool choice format to OpenAI's tool choice format
621
+ * @param toolChoice - Internal tool choice specification
622
+ * @returns OpenAI-compatible tool choice option or undefined
623
+ * @private
624
+ */
625
+ mapToolChoice(toolChoice) {
626
+ if (!toolChoice) return void 0;
627
+ if (typeof toolChoice === "string") return toolChoice;
628
+ return {
629
+ type: "function",
630
+ function: { name: toolChoice.function.name }
631
+ };
632
+ }
633
+ /**
634
+ * Maps OpenAI usage information to internal token usage format
635
+ * @param usage - OpenAI usage object from API response
636
+ * @returns Internal token usage object or undefined
637
+ * @private
638
+ */
639
+ mapUsage(usage) {
640
+ if (!usage) return void 0;
641
+ return {
642
+ promptTokens: usage.prompt_tokens || 0,
643
+ completionTokens: usage.completion_tokens || 0,
644
+ totalTokens: usage.total_tokens || 0
645
+ };
646
+ }
647
+ /**
648
+ * Maps OpenAI finish reason to internal finish reason format
649
+ * @param reason - OpenAI finish reason from API response
650
+ * @returns Internal finish reason
651
+ * @private
652
+ */
653
+ mapFinishReason(reason) {
654
+ switch (reason) {
655
+ case "stop": return "stop";
656
+ case "length": return "length";
657
+ case "tool_calls": return "tool_calls";
658
+ case "content_filter": return "content_filter";
659
+ default: return "stop";
660
+ }
661
+ }
662
+ /**
663
+ * Gets the context length for a given OpenAI model
664
+ * @param modelId - The OpenAI model identifier
665
+ * @returns Maximum context length in tokens
666
+ * @private
667
+ */
668
+ /**
669
+ * Maps OpenAI API errors to internal AI error types
670
+ * @param error - The error object from OpenAI API
671
+ * @returns Appropriate internal AI error instance
672
+ * @private
673
+ */
674
+ mapError(error) {
675
+ if (error instanceof OpenAI.APIError) switch (error.status) {
676
+ case 401: return new AuthenticationError(this.profile.providerName);
677
+ case 429: return new RateLimitError(this.profile.providerName, extractRetryAfterSeconds(error));
678
+ case 404: return new ModelNotFoundError(error.message, this.profile.providerName);
679
+ case 413: return new ContextLengthError(this.profile.providerName);
680
+ default:
681
+ if (error.message.includes("content_filter")) return new ContentFilterError(this.profile.providerName);
682
+ return new AIError(error.message, "API_ERROR", this.profile.providerName);
683
+ }
684
+ if (error instanceof AIError) return error;
685
+ return new AIError(error instanceof Error ? error.message : "Unknown error occurred", "UNKNOWN_ERROR", this.profile.providerName);
686
+ }
687
+ };
688
+ //#endregion
689
+ export { OpenAIProvider };
690
+
691
+ //# sourceMappingURL=openai--F38QHiJ.js.map