@happyvertical/ai 0.89.6 → 0.89.8

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,1508 @@
1
+ import { a as ContextLengthError, i as ContentFilterError, n as AI_PROVIDER_TYPES, o as ModelNotFoundError, r as AuthenticationError, s as RateLimitError, t as AIError } from "./types-sSecpd_N.js";
2
+ import { n as extractRetryAfterSeconds, t as createRateLimitedAI } from "./rate-limit-C2oPwLGI.js";
3
+ import { a as normalizeBaseAIOptions, c as prepareRequestControls, i as createObservedAI, o as normalizeChatOptions, s as normalizeImageGenerationOptions } from "./safety-ClfpyKEN.js";
4
+ import { t as emitUsage } from "./usage-C1Y1Nlg4.js";
5
+ import { ApiError, ValidationError, loadEnvConfig } from "@happyvertical/utils";
6
+ import OpenAI from "openai";
7
+ //#region \0rolldown/runtime.js
8
+ var __defProp = Object.defineProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ //#endregion
19
+ //#region src/shared/providers/openai.ts
20
+ /**
21
+ * OpenAI provider implementation
22
+ *
23
+ * Provides a standardized interface for interacting with OpenAI's GPT models,
24
+ * including chat completions, text completions, embeddings, and streaming responses.
25
+ * Supports all major OpenAI features including function calling, vision models,
26
+ * and custom fine-tuned models.
27
+ */
28
+ var openai_exports = /* @__PURE__ */ __exportAll({
29
+ OpenAIProvider: () => OpenAIProvider,
30
+ buildTokenLimitRequestFields: () => buildTokenLimitRequestFields,
31
+ usesCompletionTokenLimit: () => usesCompletionTokenLimit
32
+ });
33
+ /**
34
+ * Determines whether a model uses OpenAI's newer reasoning-model request
35
+ * shape: `max_completion_tokens` in place of `max_tokens`, and no
36
+ * `temperature` parameter at all (a non-default `temperature` is rejected
37
+ * with a 400 error).
38
+ *
39
+ * This applies to the `gpt-5` family (`gpt-5`, `gpt-5-mini`, `gpt-5-nano`,
40
+ * and future `gpt-5*` variants) and the `o`-series reasoning models (`o1`,
41
+ * `o3`, `o4`, and their variants such as `o1-mini`, `o3-mini`, `o4-mini`).
42
+ * See OpenAI's reasoning models guide and the Chat Completions API
43
+ * reference for `max_completion_tokens`:
44
+ * https://platform.openai.com/docs/guides/reasoning
45
+ * https://platform.openai.com/docs/api-reference/chat/create
46
+ *
47
+ * The match is a conservative prefix/family check so future `gpt-5*` and
48
+ * `o1`/`o3`/`o4` variants are covered without needing an allowlist update.
49
+ * Models outside these families (`gpt-4.x`, `gpt-3.5`, etc.) are unaffected
50
+ * and keep sending `max_tokens` and `temperature` as before.
51
+ *
52
+ * Gateway providers (`BifrostProvider`, some `LiteLLMProvider` deployments)
53
+ * route with a vendor-prefixed model id, e.g. `openai/gpt-5-mini` — see
54
+ * `BifrostProvider`'s own `defaultModel: 'openai/gpt-4o-mini'` convention in
55
+ * `bifrost.ts`. Only the final path segment is matched against the family
56
+ * checks so those gateway-routed ids are recognized the same as a bare
57
+ * `gpt-5-mini`.
58
+ *
59
+ * Internal request-shaping helper: exported at module level only so it can
60
+ * be unit-tested directly. It is not re-exported from the package entry
61
+ * point (`src/index.ts`) and is not part of the package's public API.
62
+ *
63
+ * @param model - The model identifier (e.g. `gpt-5-mini`, `openai/gpt-5-mini`, `gpt-4.1-mini`)
64
+ * @returns `true` when the model requires `max_completion_tokens` and rejects `temperature`
65
+ */
66
+ function usesCompletionTokenLimit(model) {
67
+ if (!model) return false;
68
+ const normalized = model.toLowerCase();
69
+ const lastSegment = normalized.slice(normalized.lastIndexOf("/") + 1);
70
+ return lastSegment.startsWith("gpt-5") || /^o[134](-|$)/.test(lastSegment);
71
+ }
72
+ /**
73
+ * Builds the output-token-limit and temperature fields for a chat/completion
74
+ * request body, shaped for the given model per {@link usesCompletionTokenLimit}.
75
+ *
76
+ * For models that require it: sends `max_completion_tokens` and omits
77
+ * `max_tokens`, and omits `temperature` entirely (never sends a default
78
+ * value in its place). For all other models: sends `max_tokens` and
79
+ * `temperature` unchanged. Fields whose source value is `undefined` are
80
+ * omitted rather than included as `undefined` keys.
81
+ *
82
+ * Internal request-shaping helper: exported at module level only so it can
83
+ * be unit-tested directly. It is not re-exported from the package entry
84
+ * point (`src/index.ts`) and is not part of the package's public API.
85
+ */
86
+ function buildTokenLimitRequestFields(model, maxTokens, temperature) {
87
+ if (usesCompletionTokenLimit(model)) return maxTokens === void 0 ? {} : { max_completion_tokens: maxTokens };
88
+ return {
89
+ ...maxTokens === void 0 ? {} : { max_tokens: maxTokens },
90
+ ...temperature === void 0 ? {} : { temperature }
91
+ };
92
+ }
93
+ var OPENAI_PROFILE = {
94
+ providerLabel: "OpenAI",
95
+ providerName: "openai",
96
+ defaultModel: "gpt-4o",
97
+ capabilities: {
98
+ chat: true,
99
+ completion: true,
100
+ embeddings: true,
101
+ streaming: true,
102
+ functions: true,
103
+ vision: true,
104
+ fineTuning: true,
105
+ imageEmbeddings: true,
106
+ imageGeneration: true,
107
+ videoGeneration: false,
108
+ tts: false,
109
+ voiceCloning: false,
110
+ voiceDesign: false,
111
+ maxContextLength: 128e3,
112
+ supportedOperations: [
113
+ "chat",
114
+ "completion",
115
+ "embedding",
116
+ "streaming",
117
+ "functions",
118
+ "vision",
119
+ "image_embedding",
120
+ "image_generation"
121
+ ]
122
+ },
123
+ describeModel: (modelId) => `OpenAI model: ${modelId}`,
124
+ getContextLength(modelId) {
125
+ if (modelId.includes("gpt-4o")) return 128e3;
126
+ if (modelId.includes("gpt-4.1")) return 128e3;
127
+ if (modelId.includes("gpt-4-turbo")) return 128e3;
128
+ if (modelId.includes("gpt-4")) return 8192;
129
+ if (modelId.includes("gpt-3.5-turbo")) return 16385;
130
+ if (modelId.includes("text-embedding")) return 8192;
131
+ return 4096;
132
+ },
133
+ getModelCapabilities(modelId) {
134
+ const capabilities = ["text"];
135
+ if (modelId.includes("gpt")) capabilities.push("chat", "functions");
136
+ if (modelId.includes("vision") || modelId === "gpt-4o" || modelId.includes("gpt-4.1")) capabilities.push("vision");
137
+ if (modelId.includes("embedding")) capabilities.push("embeddings");
138
+ return capabilities;
139
+ },
140
+ shouldIncludeModel(modelId) {
141
+ return modelId.includes("gpt") || modelId.includes("text-embedding");
142
+ },
143
+ supportsFunctions(modelId) {
144
+ return modelId.includes("gpt-4") || modelId.includes("gpt-3.5");
145
+ },
146
+ supportsVision(modelId) {
147
+ return modelId.includes("vision") || modelId === "gpt-4o" || modelId.includes("gpt-4.1");
148
+ }
149
+ };
150
+ /**
151
+ * OpenAI provider implementation that handles all interactions with OpenAI's API.
152
+ * Supports GPT models, embeddings, function calling, streaming, and vision capabilities.
153
+ */
154
+ var OpenAIProvider = class {
155
+ client;
156
+ options;
157
+ profile;
158
+ /**
159
+ * Creates a new OpenAI provider instance
160
+ * @param options - Configuration options for the OpenAI provider
161
+ */
162
+ constructor(options, profile = OPENAI_PROFILE) {
163
+ this.profile = profile;
164
+ this.options = normalizeBaseAIOptions({
165
+ defaultModel: this.profile.defaultModel,
166
+ ...options
167
+ });
168
+ this.client = new OpenAI({
169
+ apiKey: this.options.apiKey,
170
+ baseURL: this.options.baseUrl,
171
+ organization: this.options.organization,
172
+ timeout: this.options.timeout,
173
+ maxRetries: this.options.maxRetries,
174
+ defaultHeaders: this.options.headers
175
+ });
176
+ }
177
+ /**
178
+ * Generate a chat completion using OpenAI's chat models
179
+ * @param messages - Array of conversation messages
180
+ * @param options - Optional configuration for the chat completion
181
+ * @returns Promise resolving to the AI response with content and metadata
182
+ * @throws {AIError} When the API request fails or returns invalid data
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const response = await provider.chat([
187
+ * { role: 'system', content: 'You are a helpful assistant.' },
188
+ * { role: 'user', content: 'What is the capital of France?' }
189
+ * ], {
190
+ * model: 'gpt-4o',
191
+ * temperature: 0.7,
192
+ * maxTokens: 150
193
+ * });
194
+ * console.log(response.content); // "Paris is the capital of France."
195
+ * ```
196
+ */
197
+ async chat(messages, options = {}) {
198
+ const startTime = Date.now();
199
+ let controls;
200
+ try {
201
+ const model = options.model || this.options.defaultModel || this.profile.defaultModel;
202
+ options = normalizeChatOptions(this.options, options, this.profile.providerName, model);
203
+ controls = prepareRequestControls(this.options, options);
204
+ const request = {
205
+ model,
206
+ messages: this.mapMessagesToOpenAI(messages),
207
+ ...buildTokenLimitRequestFields(model, options.maxTokens, options.temperature),
208
+ top_p: options.topP,
209
+ n: options.n,
210
+ stop: options.stop,
211
+ frequency_penalty: options.frequencyPenalty,
212
+ presence_penalty: options.presencePenalty,
213
+ user: options.user,
214
+ tools: options.tools?.map((tool) => ({
215
+ type: "function",
216
+ function: {
217
+ name: tool.function.name,
218
+ description: tool.function.description,
219
+ parameters: tool.function.parameters
220
+ }
221
+ })),
222
+ tool_choice: this.mapToolChoice(options.toolChoice),
223
+ response_format: options.responseFormat,
224
+ seed: options.seed,
225
+ stream: false
226
+ };
227
+ if ((this.profile.providerName === "bifrost" || this.profile.providerName === "litellm") && (options.reasoning?.maxTokens || 0) > 0) request.reasoning = {
228
+ effort: options.reasoning?.effort,
229
+ max_tokens: options.reasoning?.maxTokens,
230
+ include_thoughts: options.reasoning?.includeThoughts
231
+ };
232
+ const response = await this.client.chat.completions.create(request, {
233
+ signal: controls.signal,
234
+ timeout: controls.timeout
235
+ });
236
+ const choice = response.choices[0];
237
+ if (!choice) throw new AIError(`No choices returned from ${this.profile.providerLabel}`, "NO_CHOICES", this.profile.providerName);
238
+ const usage = this.mapUsage(response.usage);
239
+ emitUsage(this.options, this.profile.providerName, "chat", response.model || model, usage, startTime, options.usageTags);
240
+ return {
241
+ content: choice.message.content || "",
242
+ usage,
243
+ model: response.model,
244
+ finishReason: this.mapFinishReason(choice.finish_reason),
245
+ toolCalls: choice.message.tool_calls?.filter((call) => call.type === "function").map((call) => ({
246
+ id: call.id,
247
+ type: "function",
248
+ function: {
249
+ name: call.function.name,
250
+ arguments: call.function.arguments
251
+ }
252
+ }))
253
+ };
254
+ } catch (error) {
255
+ if (controls?.didTimeout()) throw new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", this.profile.providerName, options.model);
256
+ if (options.signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED", this.profile.providerName, options.model);
257
+ throw this.mapError(error);
258
+ } finally {
259
+ controls?.cleanup();
260
+ }
261
+ }
262
+ /**
263
+ * Generate a text completion for a given prompt
264
+ * @param prompt - The text prompt to complete
265
+ * @param options - Optional configuration for the completion
266
+ * @returns Promise resolving to the AI response
267
+ * @throws {AIError} When the API request fails
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const response = await provider.complete('The weather today is', {
272
+ * model: 'gpt-4o',
273
+ * maxTokens: 50,
274
+ * temperature: 0.5
275
+ * });
276
+ * ```
277
+ */
278
+ async complete(prompt, options = {}) {
279
+ return this.chat([{
280
+ role: "user",
281
+ content: prompt
282
+ }], {
283
+ model: options.model,
284
+ maxTokens: options.maxTokens,
285
+ temperature: options.temperature,
286
+ topP: options.topP,
287
+ n: options.n,
288
+ stop: options.stop,
289
+ stream: options.stream,
290
+ onProgress: options.onProgress,
291
+ signal: options.signal,
292
+ timeout: options.timeout,
293
+ reasoning: options.reasoning,
294
+ usageTags: options.usageTags
295
+ });
296
+ }
297
+ /**
298
+ * Simple message interface for single-turn interactions with optional history
299
+ *
300
+ * @param text - The message text to send
301
+ * @param options - Configuration options including history, model, etc.
302
+ * @returns Promise resolving to the response content string
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * // Simple usage
307
+ * const response = await provider.message('Hello!');
308
+ *
309
+ * // With options
310
+ * const response = await provider.message('Analyze this', {
311
+ * model: 'gpt-4o',
312
+ * responseFormat: { type: 'json_object' }
313
+ * });
314
+ *
315
+ * // With history
316
+ * const response = await provider.message('What was my question?', {
317
+ * history: [
318
+ * { role: 'user', content: 'What is 2+2?' },
319
+ * { role: 'assistant', content: '4' }
320
+ * ]
321
+ * });
322
+ * ```
323
+ */
324
+ async message(text, options = {}) {
325
+ const messages = [...options.history || [], {
326
+ role: options.role || "user",
327
+ content: text
328
+ }];
329
+ return (await this.chat(messages, {
330
+ model: options.model,
331
+ maxTokens: options.maxTokens,
332
+ temperature: options.temperature,
333
+ topP: options.topP,
334
+ stop: options.stop,
335
+ stream: options.stream,
336
+ frequencyPenalty: options.frequencyPenalty,
337
+ presencePenalty: options.presencePenalty,
338
+ responseFormat: options.responseFormat,
339
+ seed: options.seed,
340
+ tools: options.tools,
341
+ toolChoice: options.toolChoice,
342
+ onProgress: options.onProgress,
343
+ signal: options.signal,
344
+ timeout: options.timeout,
345
+ reasoning: options.reasoning,
346
+ usageTags: options.usageTags
347
+ })).content;
348
+ }
349
+ /**
350
+ * Generate embeddings for the given text(s)
351
+ * @param text - Single text string or array of texts to embed
352
+ * @param options - Optional configuration for embeddings
353
+ * @returns Promise resolving to embeddings response with vector arrays
354
+ * @throws {AIError} When the API request fails
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * // Single text embedding
359
+ * const response1 = await provider.embed('Hello world');
360
+ * console.log(response1.embeddings[0]); // Array of numbers
361
+ *
362
+ * // Multiple text embeddings
363
+ * const response2 = await provider.embed(['Text 1', 'Text 2']);
364
+ * console.log(response2.embeddings.length); // 2
365
+ * ```
366
+ */
367
+ async embed(text, options = {}) {
368
+ const startTime = Date.now();
369
+ try {
370
+ const model = options.model || "text-embedding-3-small";
371
+ const input = Array.isArray(text) ? text : [text];
372
+ const response = await this.client.embeddings.create({
373
+ model,
374
+ input,
375
+ encoding_format: options.encodingFormat,
376
+ dimensions: options.dimensions,
377
+ user: options.user
378
+ });
379
+ const usage = this.mapUsage(response.usage);
380
+ emitUsage(this.options, this.profile.providerName, "embed", response.model || model, usage, startTime, options.usageTags);
381
+ return {
382
+ embeddings: response.data.map((item) => item.embedding),
383
+ usage,
384
+ model: response.model
385
+ };
386
+ } catch (error) {
387
+ throw this.mapError(error);
388
+ }
389
+ }
390
+ /**
391
+ * Convert an image to a base64 data URL
392
+ * @param image - Image as URL, base64 data URL, or Buffer
393
+ * @returns base64 data URL string
394
+ * @private
395
+ */
396
+ async imageToBase64(image, signal) {
397
+ if (Buffer.isBuffer(image)) return `data:image/png;base64,${image.toString("base64")}`;
398
+ if (image.startsWith("data:")) return image;
399
+ const response = await fetch(image, { signal });
400
+ if (!response.ok) throw new AIError(`Failed to fetch image: ${response.status} ${response.statusText}`, "IMAGE_FETCH_ERROR", this.profile.providerName);
401
+ const arrayBuffer = await response.arrayBuffer();
402
+ const buffer = Buffer.from(arrayBuffer);
403
+ return `data:${response.headers.get("content-type") || "image/png"};base64,${buffer.toString("base64")}`;
404
+ }
405
+ /**
406
+ * Generate a text description of an image
407
+ * @param image - Image as URL, base64 data URL, or Buffer
408
+ * @param prompt - Custom prompt for description (optional)
409
+ * @param options - Optional configuration
410
+ * @returns Promise resolving to the description string
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const description = await provider.describeImage('https://example.com/image.jpg');
415
+ * ```
416
+ */
417
+ async describeImage(image, prompt, options = {}) {
418
+ let controls;
419
+ try {
420
+ const defaultPrompt = "Describe this image for a search index. Include objects, mood, lighting, and any visible text.";
421
+ controls = prepareRequestControls(this.options, options);
422
+ const imageUrl = await this.imageToBase64(image, controls.signal);
423
+ return (await this.chat([{
424
+ role: "user",
425
+ content: [{
426
+ type: "text",
427
+ text: prompt || defaultPrompt
428
+ }, {
429
+ type: "image_url",
430
+ image_url: {
431
+ url: imageUrl,
432
+ detail: options.detail || "auto"
433
+ }
434
+ }]
435
+ }], {
436
+ model: options.model || this.options.defaultModel || this.profile.defaultModel,
437
+ maxTokens: options.maxTokens ?? 500,
438
+ signal: controls.signal,
439
+ timeout: controls.timeout,
440
+ reasoning: options.reasoning,
441
+ usageTags: options.usageTags
442
+ })).content;
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
+ * Generate embeddings for an image using describe-then-embed pattern
453
+ * @param image - Image as URL, base64 data URL, or Buffer
454
+ * @param options - Optional configuration for image embeddings
455
+ * @returns Promise resolving to embeddings response
456
+ *
457
+ * @example
458
+ * ```typescript
459
+ * const embedding = await provider.embedImage('https://example.com/image.jpg');
460
+ * ```
461
+ */
462
+ async embedImage(image, options = {}) {
463
+ try {
464
+ 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.");
465
+ return this.embed(description, {
466
+ model: options.model || "text-embedding-3-small",
467
+ dimensions: options.dimensions,
468
+ user: options.user
469
+ });
470
+ } catch (error) {
471
+ throw this.mapError(error);
472
+ }
473
+ }
474
+ /**
475
+ * Generate an image from a text prompt using DALL-E
476
+ * @param prompt - Text description of the image to generate
477
+ * @param options - Optional configuration for image generation
478
+ * @returns Promise resolving to generated image(s)
479
+ *
480
+ * @example
481
+ * ```typescript
482
+ * const result = await provider.generateImage('A sunset over mountains');
483
+ * fs.writeFileSync('image.png', result.images[0].data);
484
+ * ```
485
+ */
486
+ async generateImage(prompt, options = {}) {
487
+ let controls;
488
+ try {
489
+ const model = options.model || "dall-e-3";
490
+ options = normalizeImageGenerationOptions(this.options, options, this.profile.providerName, model);
491
+ controls = prepareRequestControls(this.options, options);
492
+ const requestParams = {
493
+ model,
494
+ prompt,
495
+ n: model === "dall-e-3" ? 1 : options.n,
496
+ size: options.size || "1024x1024",
497
+ response_format: options.outputFormat === "url" ? "url" : "b64_json"
498
+ };
499
+ if (model === "dall-e-3") {
500
+ if (options.style) requestParams.style = options.style;
501
+ if (options.quality) requestParams.quality = options.quality;
502
+ }
503
+ return {
504
+ images: ((await this.client.images.generate(requestParams, {
505
+ signal: controls.signal,
506
+ timeout: controls.timeout
507
+ })).data || []).map((item) => {
508
+ let data;
509
+ const mimeType = "image/png";
510
+ if (options.outputFormat === "url") data = item.url || "";
511
+ else if (options.outputFormat === "base64") data = item.b64_json || "";
512
+ else data = Buffer.from(item.b64_json || "", "base64");
513
+ return {
514
+ data,
515
+ mimeType,
516
+ revisedPrompt: item.revised_prompt
517
+ };
518
+ }),
519
+ model
520
+ };
521
+ } catch (error) {
522
+ if (controls?.didTimeout()) throw new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", this.profile.providerName, options.model);
523
+ if (options.signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED", this.profile.providerName, options.model);
524
+ throw this.mapError(error);
525
+ } finally {
526
+ controls?.cleanup();
527
+ }
528
+ }
529
+ async submitVideoGenerationJob(_options) {
530
+ throw new AIError(`Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
531
+ }
532
+ async getVideoGenerationJob(_handle) {
533
+ throw new AIError(`Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
534
+ }
535
+ async fetchVideoGenerationResult(_handle) {
536
+ throw new AIError(`Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
537
+ }
538
+ async cancelVideoGenerationJob(_handle) {
539
+ throw new AIError(`Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
540
+ }
541
+ async validateVideoGenerationAccess() {
542
+ throw new AIError(`Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
543
+ }
544
+ /**
545
+ * Stream a chat completion response in real-time
546
+ * @param messages - Array of conversation messages
547
+ * @param options - Optional configuration for the chat completion
548
+ * @yields Individual content chunks as they arrive
549
+ * @throws {AIError} When the streaming request fails
550
+ *
551
+ * @example
552
+ * ```typescript
553
+ * for await (const chunk of provider.stream([
554
+ * { role: 'user', content: 'Write a story about AI' }
555
+ * ])) {
556
+ * process.stdout.write(chunk);
557
+ * }
558
+ * ```
559
+ */
560
+ async *stream(messages, options = {}) {
561
+ const startTime = Date.now();
562
+ let controls;
563
+ try {
564
+ const model = options.model || this.options.defaultModel || this.profile.defaultModel;
565
+ options = normalizeChatOptions(this.options, options, this.profile.providerName, model);
566
+ controls = prepareRequestControls(this.options, options);
567
+ const request = {
568
+ model,
569
+ messages: this.mapMessagesToOpenAI(messages),
570
+ ...buildTokenLimitRequestFields(model, options.maxTokens, options.temperature),
571
+ top_p: options.topP,
572
+ stop: options.stop,
573
+ frequency_penalty: options.frequencyPenalty,
574
+ presence_penalty: options.presencePenalty,
575
+ user: options.user,
576
+ stream: true
577
+ };
578
+ if ((this.profile.providerName === "bifrost" || this.profile.providerName === "litellm") && (options.reasoning?.maxTokens || 0) > 0) request.reasoning = {
579
+ effort: options.reasoning?.effort,
580
+ max_tokens: options.reasoning?.maxTokens,
581
+ include_thoughts: options.reasoning?.includeThoughts
582
+ };
583
+ const stream = await this.client.chat.completions.create(request, {
584
+ signal: controls.signal,
585
+ timeout: controls.timeout
586
+ });
587
+ for await (const chunk of stream) {
588
+ const content = chunk.choices[0]?.delta?.content;
589
+ if (content) {
590
+ if (options.onProgress) options.onProgress(content);
591
+ yield content;
592
+ }
593
+ }
594
+ emitUsage(this.options, this.profile.providerName, "stream", model, void 0, startTime, options.usageTags);
595
+ } catch (error) {
596
+ if (controls?.didTimeout()) throw new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", this.profile.providerName, options.model);
597
+ if (options.signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED", this.profile.providerName, options.model);
598
+ throw this.mapError(error);
599
+ } finally {
600
+ controls?.cleanup();
601
+ }
602
+ }
603
+ /**
604
+ * Count the number of tokens in the given text
605
+ * @param text - The text to count tokens for
606
+ * @returns Promise resolving to the estimated token count
607
+ *
608
+ * @remarks
609
+ * OpenAI doesn't provide a direct token counting API, so this is an approximation
610
+ * based on the general rule of ~4 characters per token. For precise counting,
611
+ * consider using a dedicated tokenizer library.
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * const count = await provider.countTokens('Hello, world!');
616
+ * console.log(count); // Approximately 4 tokens
617
+ * ```
618
+ */
619
+ async countTokens(text) {
620
+ return Math.ceil(text.length / 4);
621
+ }
622
+ /**
623
+ * Get a list of available OpenAI models
624
+ * @returns Promise resolving to an array of model information
625
+ * @throws {AIError} When the API request fails
626
+ *
627
+ * @example
628
+ * ```typescript
629
+ * const models = await provider.getModels();
630
+ * const gptModels = models.filter(m => m.id.includes('gpt'));
631
+ * ```
632
+ */
633
+ async getModels() {
634
+ try {
635
+ return (await this.client.models.list()).data.filter((model) => this.profile.shouldIncludeModel(model.id)).map((model) => ({
636
+ id: model.id,
637
+ name: model.id,
638
+ description: this.profile.describeModel(model.id),
639
+ contextLength: this.profile.getContextLength(model.id),
640
+ capabilities: this.profile.getModelCapabilities(model.id),
641
+ supportsFunctions: this.profile.supportsFunctions(model.id),
642
+ supportsVision: this.profile.supportsVision(model.id)
643
+ }));
644
+ } catch (error) {
645
+ throw this.mapError(error);
646
+ }
647
+ }
648
+ /**
649
+ * Get the capabilities supported by this OpenAI provider
650
+ * @returns Promise resolving to provider capabilities
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * const caps = await provider.getCapabilities();
655
+ * if (caps.functions) {
656
+ * // Provider supports function calling
657
+ * }
658
+ * ```
659
+ */
660
+ async getCapabilities() {
661
+ return { ...this.profile.capabilities };
662
+ }
663
+ async synthesizeSpeech(_text, _options) {
664
+ throw new AIError(`TTS is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
665
+ }
666
+ streamSpeech(_text, _options) {
667
+ const error = new AIError(`TTS streaming is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
668
+ return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
669
+ }
670
+ async cloneVoice(_options) {
671
+ throw new AIError(`Voice cloning is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
672
+ }
673
+ async designVoice(_options) {
674
+ throw new AIError(`Voice design is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
675
+ }
676
+ async getVoices(_options) {
677
+ throw new AIError(`Voice listing is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`, "NOT_IMPLEMENTED", this.profile.providerName);
678
+ }
679
+ /**
680
+ * Maps internal AI messages to OpenAI's message format
681
+ * @param messages - Array of internal AI messages
682
+ * @returns Array of OpenAI-compatible message parameters
683
+ * @private
684
+ */
685
+ mapMessagesToOpenAI(messages) {
686
+ return messages.map((message) => {
687
+ let content;
688
+ if (typeof message.content === "string") content = message.content;
689
+ else content = message.content.map((part) => {
690
+ if (part.type === "text") return {
691
+ type: "text",
692
+ text: part.text
693
+ };
694
+ return {
695
+ type: "image_url",
696
+ image_url: {
697
+ url: part.image_url.url,
698
+ detail: part.image_url.detail
699
+ }
700
+ };
701
+ });
702
+ const baseMessage = {
703
+ role: message.role,
704
+ content
705
+ };
706
+ if (message.name && (message.role === "system" || message.role === "user" || message.role === "function")) baseMessage.name = message.name;
707
+ if (message.tool_calls && message.role === "assistant") baseMessage.tool_calls = message.tool_calls;
708
+ return baseMessage;
709
+ });
710
+ }
711
+ /**
712
+ * Maps internal tool choice format to OpenAI's tool choice format
713
+ * @param toolChoice - Internal tool choice specification
714
+ * @returns OpenAI-compatible tool choice option or undefined
715
+ * @private
716
+ */
717
+ mapToolChoice(toolChoice) {
718
+ if (!toolChoice) return void 0;
719
+ if (typeof toolChoice === "string") return toolChoice;
720
+ return {
721
+ type: "function",
722
+ function: { name: toolChoice.function.name }
723
+ };
724
+ }
725
+ /**
726
+ * Maps OpenAI usage information to internal token usage format
727
+ * @param usage - OpenAI usage object from API response
728
+ * @returns Internal token usage object or undefined
729
+ * @private
730
+ */
731
+ mapUsage(usage) {
732
+ if (!usage) return void 0;
733
+ return {
734
+ promptTokens: usage.prompt_tokens || 0,
735
+ completionTokens: usage.completion_tokens || 0,
736
+ totalTokens: usage.total_tokens || 0
737
+ };
738
+ }
739
+ /**
740
+ * Maps OpenAI finish reason to internal finish reason format
741
+ * @param reason - OpenAI finish reason from API response
742
+ * @returns Internal finish reason
743
+ * @private
744
+ */
745
+ mapFinishReason(reason) {
746
+ switch (reason) {
747
+ case "stop": return "stop";
748
+ case "length": return "length";
749
+ case "tool_calls": return "tool_calls";
750
+ case "content_filter": return "content_filter";
751
+ default: return "stop";
752
+ }
753
+ }
754
+ /**
755
+ * Gets the context length for a given OpenAI model
756
+ * @param modelId - The OpenAI model identifier
757
+ * @returns Maximum context length in tokens
758
+ * @private
759
+ */
760
+ /**
761
+ * Maps OpenAI API errors to internal AI error types
762
+ * @param error - The error object from OpenAI API
763
+ * @returns Appropriate internal AI error instance
764
+ * @private
765
+ */
766
+ mapError(error) {
767
+ if (error instanceof OpenAI.APIError) switch (error.status) {
768
+ case 401: return new AuthenticationError(this.profile.providerName);
769
+ case 429: return new RateLimitError(this.profile.providerName, extractRetryAfterSeconds(error));
770
+ case 404: return new ModelNotFoundError(error.message, this.profile.providerName);
771
+ case 413: return new ContextLengthError(this.profile.providerName);
772
+ default:
773
+ if (error.message.includes("content_filter")) return new ContentFilterError(this.profile.providerName);
774
+ return new AIError(error.message, "API_ERROR", this.profile.providerName);
775
+ }
776
+ if (error instanceof AIError) return error;
777
+ return new AIError(error instanceof Error ? error.message : "Unknown error occurred", "UNKNOWN_ERROR", this.profile.providerName);
778
+ }
779
+ };
780
+ //#endregion
781
+ //#region src/shared/client.ts
782
+ /**
783
+ * Type guard to check if options are for OpenAI client
784
+ *
785
+ * @param options - Options to check
786
+ * @returns True if options are valid for OpenAI client
787
+ */
788
+ function isOpenAIClientOptions(options) {
789
+ return options.type === "openai" && "apiKey" in options && !!options.apiKey;
790
+ }
791
+ /**
792
+ * Type guard to check if value is an AI client instance
793
+ *
794
+ * @param value - Value to check
795
+ * @returns True if value is an AI client instance
796
+ */
797
+ function isAIClientInstance(value) {
798
+ return value instanceof AIClient;
799
+ }
800
+ /**
801
+ * Base class for AI clients
802
+ * Provides a common interface for different AI service providers
803
+ */
804
+ var AIClient = class {
805
+ /**
806
+ * Configuration options for this client
807
+ */
808
+ options;
809
+ /**
810
+ * Creates a new AIClient
811
+ *
812
+ * @param options - Client configuration options
813
+ */
814
+ constructor(options) {
815
+ this.options = options;
816
+ }
817
+ /**
818
+ * Sends a message to the AI
819
+ * Base implementation returns a placeholder response
820
+ *
821
+ * @param text - Message text
822
+ * @param options - Message options
823
+ * @returns Promise resolving to a placeholder response
824
+ */
825
+ async message(_text, _options = { role: "user" }) {
826
+ return "not a real ai message, this is the base class!";
827
+ }
828
+ /**
829
+ * Factory method to create appropriate AI client based on options
830
+ *
831
+ * @param options - Client configuration options
832
+ * @returns Promise resolving to an initialized AI client
833
+ * @throws Error if client type is invalid
834
+ */
835
+ static async create(options) {
836
+ if (isAIClientInstance(options)) return options;
837
+ const clientOptions = options;
838
+ if (isOpenAIClientOptions(clientOptions)) return OpenAIClient.create(clientOptions);
839
+ const providedType = clientOptions.type;
840
+ if (providedType && providedType !== "openai") {
841
+ const { getAI } = await Promise.resolve().then(() => factory_exports);
842
+ return await getAI(clientOptions);
843
+ }
844
+ if (providedType === "openai") throw new ValidationError("OpenAI API key is required but missing or empty", {
845
+ supportedTypes: [...AI_PROVIDER_TYPES],
846
+ providedType,
847
+ hint: "Set OPENAI_API_KEY environment variable or pass apiKey in options"
848
+ });
849
+ throw new ValidationError("Invalid client type specified", {
850
+ supportedTypes: [...AI_PROVIDER_TYPES],
851
+ providedType
852
+ });
853
+ }
854
+ /**
855
+ * Gets a text completion from the AI
856
+ * In base class, delegates to message method
857
+ *
858
+ * @param text - Input text for completion
859
+ * @param options - Completion options
860
+ * @returns Promise resolving to the completion result
861
+ */
862
+ textCompletion(text, options = { role: "user" }) {
863
+ return this.message(text, options);
864
+ }
865
+ };
866
+ /**
867
+ * Creates an OpenAI client instance
868
+ *
869
+ * @param options - OpenAI configuration options
870
+ * @returns Promise resolving to an OpenAI client
871
+ */
872
+ async function getOpenAI(options) {
873
+ return new OpenAI({
874
+ apiKey: options.apiKey,
875
+ baseURL: options.baseUrl
876
+ });
877
+ }
878
+ /**
879
+ * Client implementation for the OpenAI API
880
+ */
881
+ var OpenAIClient = class OpenAIClient extends AIClient {
882
+ /**
883
+ * OpenAI client instance
884
+ */
885
+ openai;
886
+ /**
887
+ * Configuration options for this client
888
+ */
889
+ options;
890
+ /**
891
+ * Creates a new OpenAIClient
892
+ *
893
+ * @param options - OpenAI client configuration options
894
+ */
895
+ constructor(options) {
896
+ super(options);
897
+ this.options = options;
898
+ }
899
+ /**
900
+ * Sends a message to OpenAI
901
+ *
902
+ * @param text - Message text
903
+ * @param options - Message options
904
+ * @returns Promise resolving to the OpenAI response
905
+ */
906
+ async message(text, options = { role: "user" }) {
907
+ return await this.textCompletion(text, options);
908
+ }
909
+ /**
910
+ * Factory method to create and initialize an OpenAIClient
911
+ *
912
+ * @param options - OpenAI client configuration options
913
+ * @returns Promise resolving to an initialized OpenAIClient
914
+ */
915
+ static async create(options) {
916
+ const client = new OpenAIClient(options);
917
+ await client.initialize();
918
+ return client;
919
+ }
920
+ /**
921
+ * Initializes the OpenAI client
922
+ */
923
+ async initialize() {
924
+ this.openai = new OpenAI({
925
+ apiKey: this.options.apiKey,
926
+ baseURL: this.options.baseUrl
927
+ });
928
+ }
929
+ /**
930
+ * Sends a text completion request to the OpenAI API
931
+ *
932
+ * @param message - The message to send
933
+ * @param options - Configuration options for the completion request
934
+ * @returns Promise resolving to the completion text
935
+ * @throws Error if the OpenAI API response is invalid
936
+ */
937
+ async textCompletion(message, options = {}) {
938
+ const { model = "gpt-4o", role = "user", history = [], name: _name, frequencyPenalty = 0, logitBias, logprobs = false, topLogprobs, maxTokens, n = 1, presencePenalty = 0, responseFormat, seed, stop, stream: _stream = false, temperature = 1, topProbability: topP = 1, tools, toolChoice, user, onProgress } = options;
939
+ const messages = [...history, {
940
+ role,
941
+ content: message
942
+ }];
943
+ if (onProgress) {
944
+ const stream = await this.openai.chat.completions.create({
945
+ model,
946
+ messages,
947
+ stream: true,
948
+ frequency_penalty: frequencyPenalty,
949
+ logit_bias: logitBias,
950
+ logprobs,
951
+ top_logprobs: topLogprobs,
952
+ ...buildTokenLimitRequestFields(model, maxTokens, temperature),
953
+ n,
954
+ presence_penalty: presencePenalty,
955
+ response_format: responseFormat,
956
+ seed,
957
+ stop,
958
+ top_p: topP,
959
+ tools,
960
+ tool_choice: toolChoice,
961
+ user
962
+ });
963
+ let fullContent = "";
964
+ for await (const chunk of stream) {
965
+ const content = chunk.choices[0]?.delta?.content || "";
966
+ fullContent += content;
967
+ onProgress(content);
968
+ }
969
+ return fullContent;
970
+ }
971
+ const response = await this.openai.chat.completions.create({
972
+ model,
973
+ messages,
974
+ frequency_penalty: frequencyPenalty,
975
+ logit_bias: logitBias,
976
+ logprobs,
977
+ top_logprobs: topLogprobs,
978
+ ...buildTokenLimitRequestFields(model, maxTokens, temperature),
979
+ n,
980
+ presence_penalty: presencePenalty,
981
+ response_format: responseFormat,
982
+ seed,
983
+ stop,
984
+ stream: false,
985
+ top_p: topP,
986
+ tools,
987
+ tool_choice: toolChoice,
988
+ user
989
+ });
990
+ const choice = response.choices[0];
991
+ if (!choice || !choice.message || !choice.message.content) throw new ApiError("Invalid response from OpenAI API: Missing content", {
992
+ model,
993
+ responseId: response.id,
994
+ choices: response.choices?.length || 0,
995
+ hasChoice: !!choice,
996
+ hasMessage: !!choice?.message,
997
+ hasContent: !!choice?.message?.content
998
+ });
999
+ return choice.message.content;
1000
+ }
1001
+ };
1002
+ /**
1003
+ * Factory function to create and initialize an appropriate AI client
1004
+ * Delegates to the modern getAI() factory for all provider types
1005
+ *
1006
+ * @param options - Client configuration options
1007
+ * @returns Promise resolving to an initialized AI client
1008
+ * @throws Error if client type is invalid
1009
+ */
1010
+ async function getAIClient(options) {
1011
+ const { getAI } = await Promise.resolve().then(() => factory_exports);
1012
+ return await getAI(options);
1013
+ }
1014
+ //#endregion
1015
+ //#region src/shared/factory.ts
1016
+ /**
1017
+ * Universal factory functions for creating AI provider instances
1018
+ * Works in both browser and Node.js environments
1019
+ */
1020
+ var factory_exports = /* @__PURE__ */ __exportAll({
1021
+ getAI: () => getAI,
1022
+ getAIAuto: () => getAIAuto
1023
+ });
1024
+ /**
1025
+ * Type guards for provider options
1026
+ */
1027
+ /**
1028
+ * Checks if the options are for OpenAI provider
1029
+ * @param options - The AI provider options to check
1030
+ * @returns True if options are for OpenAI provider (including default case)
1031
+ */
1032
+ function isOpenAIOptions(options) {
1033
+ return !options.type || options.type === "openai";
1034
+ }
1035
+ /**
1036
+ * Checks if the options are for LiteLLM provider
1037
+ * @param options - The AI provider options to check
1038
+ * @returns True if options are for LiteLLM provider
1039
+ */
1040
+ function isLiteLLMOptions(options) {
1041
+ return options.type === "litellm";
1042
+ }
1043
+ /**
1044
+ * Checks if the options are for Bifrost provider
1045
+ * @param options - The AI provider options to check
1046
+ * @returns True if options are for Bifrost provider
1047
+ */
1048
+ function isBifrostOptions(options) {
1049
+ return options.type === "bifrost";
1050
+ }
1051
+ /**
1052
+ * Checks if the options are for Ollama provider
1053
+ * @param options - The AI provider options to check
1054
+ * @returns True if options are for Ollama provider
1055
+ */
1056
+ function isOllamaOptions(options) {
1057
+ return options.type === "ollama";
1058
+ }
1059
+ /**
1060
+ * Checks if the options are for Google Gemini provider
1061
+ * @param options - The AI provider options to check
1062
+ * @returns True if options are for Gemini provider
1063
+ */
1064
+ function isGeminiOptions(options) {
1065
+ return options.type === "gemini";
1066
+ }
1067
+ /**
1068
+ * Checks if the options are for Anthropic Claude provider
1069
+ * @param options - The AI provider options to check
1070
+ * @returns True if options are for Anthropic provider
1071
+ */
1072
+ function isAnthropicOptions(options) {
1073
+ return options.type === "anthropic";
1074
+ }
1075
+ /**
1076
+ * Checks if the options are for Hugging Face provider
1077
+ * @param options - The AI provider options to check
1078
+ * @returns True if options are for Hugging Face provider
1079
+ */
1080
+ function isHuggingFaceOptions(options) {
1081
+ return options.type === "huggingface";
1082
+ }
1083
+ /**
1084
+ * Checks if the options are for AWS Bedrock provider
1085
+ * @param options - The AI provider options to check
1086
+ * @returns True if options are for Bedrock provider
1087
+ */
1088
+ function isBedrockOptions(options) {
1089
+ return options.type === "bedrock";
1090
+ }
1091
+ /**
1092
+ * Checks if the options are for Claude CLI provider
1093
+ * @param options - The AI provider options to check
1094
+ * @returns True if options are for Claude CLI provider
1095
+ */
1096
+ function isClaudeCliOptions(options) {
1097
+ return options.type === "claude-cli";
1098
+ }
1099
+ /**
1100
+ * Checks if the options are for Qwen3-TTS provider
1101
+ * @param options - The AI provider options to check
1102
+ * @returns True if options are for Qwen3-TTS provider
1103
+ */
1104
+ function isQwen3TTSOptions(options) {
1105
+ return options.type === "qwen3-tts";
1106
+ }
1107
+ /**
1108
+ * Checks if the options are for the OpenAI-compatible video-generation provider
1109
+ * @param options - The AI provider options to check
1110
+ * @returns True if options are for the openai-compat-video provider
1111
+ */
1112
+ function isOpenAICompatVideoOptions(options) {
1113
+ return options.type === "openai-compat-video";
1114
+ }
1115
+ /**
1116
+ * Checks if the options are for the BytePlus ModelArk (Seedance) provider
1117
+ * @param options - The AI provider options to check
1118
+ * @returns True if options are for the byteplus-modelark provider
1119
+ */
1120
+ function isByteplusModelArkOptions(options) {
1121
+ return options.type === "byteplus-modelark";
1122
+ }
1123
+ /** Checks if options select Seevio's native Seedance task API. */
1124
+ function isSeevioOptions(options) {
1125
+ return options.type === "seevio";
1126
+ }
1127
+ /**
1128
+ * Creates an AI provider instance based on the provided options.
1129
+ * Universal version that works in both browser and Node.js environments.
1130
+ *
1131
+ * Supports environment variable configuration using the pattern:
1132
+ * - HAVE_AI_PROVIDER → provider type (string)
1133
+ * - HAVE_AI_MODEL → defaultModel (string)
1134
+ * - HAVE_AI_TIMEOUT → timeout (number)
1135
+ * - HAVE_AI_MAX_RETRIES → maxRetries (number)
1136
+ * - HAVE_AI_API_KEY → apiKey (string) - fallback if provider-specific key not set
1137
+ * - HAVE_AI_BASE_URL → baseUrl (string)
1138
+ *
1139
+ * User-provided options always take precedence over environment variables.
1140
+ *
1141
+ * Accepts both GetAIOptions (provider-specific options with literal types)
1142
+ * and AIClientOptions (legacy interface with generic string type) for
1143
+ * backward compatibility with existing code.
1144
+ *
1145
+ * @param options - Configuration options for the AI provider. Can be GetAIOptions or AIClientOptions.
1146
+ * @returns Promise resolving to an AI provider instance that implements the AIInterface
1147
+ * @throws {ValidationError} When the provider type is unsupported or invalid
1148
+ *
1149
+ * @example
1150
+ * ```typescript
1151
+ * // Create OpenAI client with explicit options
1152
+ * const openai = await getAI({
1153
+ * type: 'openai',
1154
+ * apiKey: process.env.OPENAI_API_KEY!,
1155
+ * defaultModel: 'gpt-4o'
1156
+ * });
1157
+ *
1158
+ * // Or use environment variables (HAVE_AI_PROVIDER=openai, HAVE_AI_API_KEY=sk-...)
1159
+ * const client = await getAI({});
1160
+ *
1161
+ * // Create Anthropic client
1162
+ * const anthropic = await getAI({
1163
+ * type: 'anthropic',
1164
+ * apiKey: process.env.ANTHROPIC_API_KEY!,
1165
+ * defaultModel: 'claude-3-5-sonnet-20241022'
1166
+ * });
1167
+ *
1168
+ * // Works with AIClientOptions (legacy interface)
1169
+ * const clientOptions: AIClientOptions = { type: 'openai', apiKey: '...' };
1170
+ * const legacy = await getAI(clientOptions);
1171
+ * ```
1172
+ */
1173
+ async function getAI(options = {}) {
1174
+ options = loadEnvConfig(options, {
1175
+ packageName: "ai",
1176
+ schema: {
1177
+ provider: "string",
1178
+ type: "string",
1179
+ model: "string",
1180
+ defaultModel: "string",
1181
+ timeout: "number",
1182
+ maxRetries: "number",
1183
+ apiKey: "string",
1184
+ baseUrl: "string",
1185
+ adminApiKey: "string",
1186
+ adminBaseUrl: "string",
1187
+ adminUrl: "string",
1188
+ adminUser: "string",
1189
+ adminUsername: "string",
1190
+ adminPassword: "string"
1191
+ }
1192
+ });
1193
+ if ("provider" in options && !options.type) options.type = options.provider;
1194
+ if ("model" in options && !options.defaultModel) options.defaultModel = options.model;
1195
+ options = normalizeBaseAIOptions(options);
1196
+ let client;
1197
+ if (isOpenAIOptions(options)) {
1198
+ const { OpenAIProvider } = await Promise.resolve().then(() => openai_exports);
1199
+ client = new OpenAIProvider(options);
1200
+ } else if (isLiteLLMOptions(options)) {
1201
+ const { LiteLLMProvider } = await import("./litellm-D8iaHaji.js");
1202
+ client = new LiteLLMProvider(options);
1203
+ } else if (isBifrostOptions(options)) {
1204
+ const { BifrostProvider } = await import("./bifrost-s3K7V8oU.js");
1205
+ client = new BifrostProvider(options);
1206
+ } else if (isOllamaOptions(options)) {
1207
+ const { OllamaProvider } = await import("./ollama-BrB6bjnz.js");
1208
+ client = new OllamaProvider(options);
1209
+ } else if (isGeminiOptions(options)) {
1210
+ const { GeminiProvider } = await import("./gemini-GQdugon1.js");
1211
+ client = new GeminiProvider(options);
1212
+ } else if (isAnthropicOptions(options)) {
1213
+ const { AnthropicProvider } = await import("./anthropic-ByuqC5mC.js");
1214
+ client = new AnthropicProvider(options);
1215
+ } else if (isHuggingFaceOptions(options)) {
1216
+ const { HuggingFaceProvider } = await import("./huggingface-DxPspM6Q.js");
1217
+ client = new HuggingFaceProvider(options);
1218
+ } else if (isBedrockOptions(options)) {
1219
+ const { BedrockProvider } = await import("./bedrock-BeVCmkrS.js");
1220
+ client = new BedrockProvider(options);
1221
+ } else if (isClaudeCliOptions(options)) {
1222
+ const { ClaudeCliProvider } = await import("./claude-cli-BH_C62dV.js");
1223
+ client = new ClaudeCliProvider(options);
1224
+ } else if (isQwen3TTSOptions(options)) {
1225
+ const { Qwen3TTSProvider } = await import("./qwen-tts-CAh1upii.js");
1226
+ client = new Qwen3TTSProvider(options);
1227
+ } else if (isOpenAICompatVideoOptions(options)) {
1228
+ const { OpenAICompatVideoProvider } = await import("./openai-compat-video-DNE1Yvp6.js");
1229
+ client = new OpenAICompatVideoProvider(options);
1230
+ } else if (isByteplusModelArkOptions(options)) {
1231
+ const { ByteplusModelArkProvider } = await import("./byteplus-modelark-DaF0U4n1.js");
1232
+ client = new ByteplusModelArkProvider(options);
1233
+ } else if (isSeevioOptions(options)) {
1234
+ const { SeevioProvider } = await import("./seevio-Bbu_hTUF.js");
1235
+ client = new SeevioProvider(options);
1236
+ } else throw new ValidationError("Unsupported AI provider type", {
1237
+ supportedTypes: [...AI_PROVIDER_TYPES],
1238
+ providedType: options.type
1239
+ });
1240
+ return createObservedAI(createRateLimitedAI(client, options), options);
1241
+ }
1242
+ /**
1243
+ * Browser-compatible auto-detection of AI provider based on available credentials.
1244
+ * Does not rely on process.env, making it suitable for browser environments.
1245
+ *
1246
+ * @param options - Configuration options that may contain provider-specific credentials
1247
+ * @returns Promise resolving to an AI provider instance based on detected credentials
1248
+ * @throws {ValidationError} When no provider can be detected from the provided options
1249
+ *
1250
+ * @example
1251
+ * ```typescript
1252
+ * // Auto-detect OpenAI from apiKey
1253
+ * const client1 = await getAIAuto({
1254
+ * apiKey: 'sk-...', // Detected as OpenAI
1255
+ * defaultModel: 'gpt-4o'
1256
+ * });
1257
+ *
1258
+ * // Auto-detect Hugging Face from apiToken
1259
+ * const client2 = await getAIAuto({
1260
+ * apiToken: 'hf_...', // Detected as Hugging Face
1261
+ * model: 'microsoft/DialoGPT-medium'
1262
+ * });
1263
+ *
1264
+ * // Auto-detect AWS Bedrock from region and credentials
1265
+ * const client3 = await getAIAuto({
1266
+ * region: 'us-east-1',
1267
+ * credentials: {
1268
+ * accessKeyId: 'AKIA...',
1269
+ * secretAccessKey: 'xxx'
1270
+ * }
1271
+ * });
1272
+ * ```
1273
+ */
1274
+ async function getAIAuto(options) {
1275
+ const baseUrl = String(options.baseUrl || "");
1276
+ const hasKeepAliveOption = "keepAlive" in options && options.keepAlive !== void 0;
1277
+ if (/((?:localhost|127\.0\.0\.1)(?::11434)?(?:\/(?:api|v1))?|ollama(?:\.com)?(?:\/(?:api|v1))?)\/?$/i.test(baseUrl) || hasKeepAliveOption) return getAI({
1278
+ ...options,
1279
+ type: "ollama"
1280
+ });
1281
+ if (options.apiKey && !options.type) return getAI({
1282
+ ...options,
1283
+ type: "openai"
1284
+ });
1285
+ if (options.apiToken) return getAI({
1286
+ ...options,
1287
+ type: "huggingface"
1288
+ });
1289
+ if (options.region && options.credentials) return getAI({
1290
+ ...options,
1291
+ type: "bedrock"
1292
+ });
1293
+ if (options.projectId || options.anthropicVersion) {
1294
+ if (options.anthropicVersion) return getAI({
1295
+ ...options,
1296
+ type: "anthropic"
1297
+ });
1298
+ if (options.projectId) return getAI({
1299
+ ...options,
1300
+ type: "gemini"
1301
+ });
1302
+ }
1303
+ throw new ValidationError("Could not auto-detect AI provider from options", {
1304
+ hint: "Please specify a \"type\" field in options or provide provider-specific credentials",
1305
+ supportedTypes: [...AI_PROVIDER_TYPES],
1306
+ providedOptions: Object.keys(options)
1307
+ });
1308
+ }
1309
+ //#endregion
1310
+ //#region src/shared/message.ts
1311
+ /**
1312
+ * Represents a message in an AI conversation
1313
+ */
1314
+ var AIMessage = class AIMessage {
1315
+ /**
1316
+ * Original options used to create this message
1317
+ */
1318
+ options;
1319
+ /**
1320
+ * Name of the message sender
1321
+ */
1322
+ name;
1323
+ /**
1324
+ * Content of the message
1325
+ */
1326
+ content;
1327
+ /**
1328
+ * Role of the message sender in the conversation
1329
+ */
1330
+ role;
1331
+ /**
1332
+ * Creates a new AI message
1333
+ *
1334
+ * @param options - Message configuration
1335
+ * @param options.role - Role of the message sender
1336
+ * @param options.content - Content of the message
1337
+ * @param options.name - Name of the message sender
1338
+ */
1339
+ constructor(options) {
1340
+ this.options = options;
1341
+ this.role = options.role;
1342
+ this.content = options.content;
1343
+ this.name = options.name;
1344
+ }
1345
+ /**
1346
+ * Factory method to create a new AI message
1347
+ *
1348
+ * @param options - Message configuration
1349
+ * @param options.thread - Thread this message belongs to
1350
+ * @param options.role - Role of the message sender
1351
+ * @param options.content - Content of the message
1352
+ * @param options.name - Name of the message sender
1353
+ * @returns Promise resolving to a new AIMessage instance
1354
+ */
1355
+ static async create(options) {
1356
+ return new AIMessage(options);
1357
+ }
1358
+ };
1359
+ //#endregion
1360
+ //#region src/shared/thread.ts
1361
+ /**
1362
+ * Represents a conversation thread with an AI model
1363
+ * Manages messages, references, and conversation state
1364
+ */
1365
+ var AIThread = class AIThread {
1366
+ /**
1367
+ * AI client instance for this thread
1368
+ */
1369
+ ai;
1370
+ /**
1371
+ * Options used to configure this thread
1372
+ */
1373
+ options;
1374
+ /**
1375
+ * Messages in this conversation thread
1376
+ */
1377
+ messages = [];
1378
+ /**
1379
+ * Reference materials to include in the conversation context
1380
+ */
1381
+ references = {};
1382
+ /**
1383
+ * Creates a new AI thread
1384
+ *
1385
+ * @param options - Thread configuration options
1386
+ */
1387
+ constructor(options) {
1388
+ this.options = options;
1389
+ }
1390
+ /**
1391
+ * Factory method to create and initialize a new AI thread
1392
+ *
1393
+ * @param options - Thread configuration options
1394
+ * @returns Promise resolving to an initialized AIThread
1395
+ */
1396
+ static async create(options) {
1397
+ const thread = new AIThread(options);
1398
+ await thread.initialize();
1399
+ return thread;
1400
+ }
1401
+ /**
1402
+ * Initializes the AI client for this thread
1403
+ */
1404
+ async initialize() {
1405
+ this.ai = await AIClient.create(this.options.ai);
1406
+ }
1407
+ /**
1408
+ * Adds a system message to the conversation
1409
+ *
1410
+ * @param prompt - System message content
1411
+ * @returns Promise resolving to the created AIMessage
1412
+ */
1413
+ async addSystem(prompt) {
1414
+ const message = await AIMessage.create({
1415
+ thread: this,
1416
+ role: "system",
1417
+ name: "system",
1418
+ content: prompt
1419
+ });
1420
+ this.messages.push(message);
1421
+ return message;
1422
+ }
1423
+ /**
1424
+ * Adds a message to the conversation
1425
+ *
1426
+ * @param options - Message options
1427
+ * @param options.role - Role of the message sender
1428
+ * @param options.name - Optional name of the message sender
1429
+ * @param options.content - Content of the message
1430
+ * @returns Promise resolving to the created AIMessage
1431
+ */
1432
+ async add(options) {
1433
+ const message = await AIMessage.create({
1434
+ thread: this,
1435
+ role: options.role,
1436
+ name: options.name || options.role,
1437
+ content: options.content
1438
+ });
1439
+ this.messages.push(message);
1440
+ return message;
1441
+ }
1442
+ /**
1443
+ * Gets all messages in this thread
1444
+ *
1445
+ * @returns Array of AIMessage objects
1446
+ */
1447
+ get() {
1448
+ return this.messages;
1449
+ }
1450
+ /**
1451
+ * Adds a reference to be included in the conversation context
1452
+ *
1453
+ * @param name - Name of the reference
1454
+ * @param body - Content of the reference
1455
+ */
1456
+ addReference(name, body) {
1457
+ this.references[name] = body;
1458
+ }
1459
+ /**
1460
+ * Assembles the conversation history for sending to the AI
1461
+ * Properly orders system message, references, and conversation messages
1462
+ *
1463
+ * @returns Array of message parameters formatted for the OpenAI API
1464
+ */
1465
+ assembleHistory() {
1466
+ const history = [];
1467
+ const systemMessage = this.messages.find((m) => m.role === "system");
1468
+ if (systemMessage) history.push({
1469
+ role: systemMessage.role,
1470
+ content: systemMessage.content
1471
+ });
1472
+ for (const name in this.references) history.push({
1473
+ role: "user",
1474
+ content: `Reference - ${name}:\n${this.references[name]}`
1475
+ });
1476
+ this.messages.filter((m) => m.role !== "system").forEach((message) => {
1477
+ history.push({
1478
+ role: message.role,
1479
+ content: message.content
1480
+ });
1481
+ });
1482
+ return history;
1483
+ }
1484
+ /**
1485
+ * Sends a prompt to the AI and gets a response
1486
+ *
1487
+ * @param prompt - Prompt message to send
1488
+ * @param options - Options for the AI response
1489
+ * @param options.responseFormat - Format for the AI to respond with
1490
+ * @returns Promise resolving to the AI response
1491
+ */
1492
+ async do(prompt, options = { responseFormat: "text" }) {
1493
+ const { responseFormat } = options;
1494
+ const history = this.assembleHistory();
1495
+ return await this.ai.textCompletion(prompt, {
1496
+ history,
1497
+ responseFormat: { type: responseFormat === "json" ? "json_object" : "text" }
1498
+ });
1499
+ }
1500
+ };
1501
+ //#endregion
1502
+ //#region src/index.ts
1503
+ /** @internal */
1504
+ var PACKAGE_VERSION_INITIALIZED = true;
1505
+ //#endregion
1506
+ export { getAI as a, OpenAIClient as c, OpenAIProvider as d, factory_exports as i, getAIClient as l, AIThread as n, getAIAuto as o, AIMessage as r, AIClient as s, PACKAGE_VERSION_INITIALIZED as t, getOpenAI as u };
1507
+
1508
+ //# sourceMappingURL=src-B3ZLEBD2.js.map