@happyvertical/ai 0.85.4 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +87 -1
  2. package/dist/chunks/{anthropic-DA8sdt12.js → anthropic-DuYUnw8j.js} +20 -4
  3. package/dist/chunks/anthropic-DuYUnw8j.js.map +1 -0
  4. package/dist/chunks/{bedrock-DDiYFUw0.js → bedrock-ikQJAwWB.js} +19 -3
  5. package/dist/chunks/bedrock-ikQJAwWB.js.map +1 -0
  6. package/dist/chunks/{bifrost-fM9FpW_w.js → bifrost-CaYhelVL.js} +5 -4
  7. package/dist/chunks/bifrost-CaYhelVL.js.map +1 -0
  8. package/dist/chunks/byteplus-modelark-B8l8sPXu.js +473 -0
  9. package/dist/chunks/byteplus-modelark-B8l8sPXu.js.map +1 -0
  10. package/dist/chunks/{claude-cli-CnJh5KQT.js → claude-cli-DeTKk9nu.js} +19 -3
  11. package/dist/chunks/claude-cli-DeTKk9nu.js.map +1 -0
  12. package/dist/chunks/{gateway-admin-CiKvHst7.js → gateway-admin-DJXEDpUP.js} +2 -2
  13. package/dist/chunks/{gateway-admin-CiKvHst7.js.map → gateway-admin-DJXEDpUP.js.map} +1 -1
  14. package/dist/chunks/{gemini-Bu7Fke7c.js → gemini-BT8fjXWz.js} +294 -11
  15. package/dist/chunks/gemini-BT8fjXWz.js.map +1 -0
  16. package/dist/chunks/{huggingface-B07_6wHW.js → huggingface-CqzyxeLB.js} +19 -3
  17. package/dist/chunks/huggingface-CqzyxeLB.js.map +1 -0
  18. package/dist/chunks/{litellm-D_Oo9OQ_.js → litellm-viqTuSY8.js} +5 -4
  19. package/dist/chunks/litellm-viqTuSY8.js.map +1 -0
  20. package/dist/chunks/{ollama-D4ksOTO8.js → ollama-CdWqwZ05.js} +19 -3
  21. package/dist/chunks/ollama-CdWqwZ05.js.map +1 -0
  22. package/dist/chunks/{openai--F38QHiJ.js → openai-BgrK3OfW.js} +20 -4
  23. package/dist/chunks/openai-BgrK3OfW.js.map +1 -0
  24. package/dist/chunks/openai-compat-video-D_U7jo0d.js +369 -0
  25. package/dist/chunks/openai-compat-video-D_U7jo0d.js.map +1 -0
  26. package/dist/chunks/{qwen-tts-DRRbOrhA.js → qwen-tts-BXjzY7DX.js} +21 -4
  27. package/dist/chunks/qwen-tts-BXjzY7DX.js.map +1 -0
  28. package/dist/chunks/{rate-limit-BwyPGXFW.js → rate-limit-DgEhanDL.js} +4 -3
  29. package/dist/chunks/rate-limit-DgEhanDL.js.map +1 -0
  30. package/dist/chunks/{safety-CEnoJS6X.js → safety-C415clBm.js} +25 -7
  31. package/dist/chunks/safety-C415clBm.js.map +1 -0
  32. package/dist/chunks/src-CU4qWJ2c.js +740 -0
  33. package/dist/chunks/src-CU4qWJ2c.js.map +1 -0
  34. package/dist/chunks/{types-FHs-KbGL.js → types-CINwUPDo.js} +4 -2
  35. package/dist/chunks/types-CINwUPDo.js.map +1 -0
  36. package/dist/index.js +3 -717
  37. package/dist/node/factory.d.ts +9 -0
  38. package/dist/node/factory.d.ts.map +1 -1
  39. package/dist/node.d.ts +29 -0
  40. package/dist/node.d.ts.map +1 -0
  41. package/dist/node.js +198 -0
  42. package/dist/node.js.map +1 -0
  43. package/dist/shared/factory.d.ts.map +1 -1
  44. package/dist/shared/providers/anthropic.d.ts +6 -1
  45. package/dist/shared/providers/anthropic.d.ts.map +1 -1
  46. package/dist/shared/providers/bedrock.d.ts +6 -1
  47. package/dist/shared/providers/bedrock.d.ts.map +1 -1
  48. package/dist/shared/providers/bifrost.d.ts.map +1 -1
  49. package/dist/shared/providers/byteplus-modelark.d.ts +91 -0
  50. package/dist/shared/providers/byteplus-modelark.d.ts.map +1 -0
  51. package/dist/shared/providers/claude-cli.d.ts +6 -1
  52. package/dist/shared/providers/claude-cli.d.ts.map +1 -1
  53. package/dist/shared/providers/gemini.d.ts +115 -2
  54. package/dist/shared/providers/gemini.d.ts.map +1 -1
  55. package/dist/shared/providers/huggingface.d.ts +6 -1
  56. package/dist/shared/providers/huggingface.d.ts.map +1 -1
  57. package/dist/shared/providers/litellm.d.ts.map +1 -1
  58. package/dist/shared/providers/ollama.d.ts +6 -1
  59. package/dist/shared/providers/ollama.d.ts.map +1 -1
  60. package/dist/shared/providers/openai-compat-video.d.ts +98 -0
  61. package/dist/shared/providers/openai-compat-video.d.ts.map +1 -0
  62. package/dist/shared/providers/openai.d.ts +6 -1
  63. package/dist/shared/providers/openai.d.ts.map +1 -1
  64. package/dist/shared/providers/qwen-tts.d.ts +6 -1
  65. package/dist/shared/providers/qwen-tts.d.ts.map +1 -1
  66. package/dist/shared/rate-limit.d.ts.map +1 -1
  67. package/dist/shared/safety.d.ts.map +1 -1
  68. package/dist/shared/types.d.ts +343 -5
  69. package/dist/shared/types.d.ts.map +1 -1
  70. package/package.json +7 -3
  71. package/dist/chunks/anthropic-DA8sdt12.js.map +0 -1
  72. package/dist/chunks/bedrock-DDiYFUw0.js.map +0 -1
  73. package/dist/chunks/bifrost-fM9FpW_w.js.map +0 -1
  74. package/dist/chunks/claude-cli-CnJh5KQT.js.map +0 -1
  75. package/dist/chunks/gemini-Bu7Fke7c.js.map +0 -1
  76. package/dist/chunks/huggingface-B07_6wHW.js.map +0 -1
  77. package/dist/chunks/litellm-D_Oo9OQ_.js.map +0 -1
  78. package/dist/chunks/ollama-D4ksOTO8.js.map +0 -1
  79. package/dist/chunks/openai--F38QHiJ.js.map +0 -1
  80. package/dist/chunks/qwen-tts-DRRbOrhA.js.map +0 -1
  81. package/dist/chunks/rate-limit-BwyPGXFW.js.map +0 -1
  82. package/dist/chunks/safety-CEnoJS6X.js.map +0 -1
  83. package/dist/chunks/types-FHs-KbGL.js.map +0 -1
  84. package/dist/index.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-BgrK3OfW.js","names":[],"sources":["../../src/shared/providers/openai.ts"],"sourcesContent":["/**\n * OpenAI provider implementation\n *\n * Provides a standardized interface for interacting with OpenAI's GPT models,\n * including chat completions, text completions, embeddings, and streaming responses.\n * Supports all major OpenAI features including function calling, vision models,\n * and custom fine-tuned models.\n */\n\nimport OpenAI from 'openai';\nimport { extractRetryAfterSeconds } from '../rate-limit';\nimport {\n normalizeBaseAIOptions,\n normalizeChatOptions,\n normalizeImageGenerationOptions,\n type PreparedRequestControls,\n prepareRequestControls,\n} from '../safety';\nimport type {\n AICapabilities,\n AIInterface,\n AIMessage,\n AIModel,\n AIResponse,\n BaseAIOptions,\n ChatOptions,\n CompletionOptions,\n ContentPart,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n MessageOptions,\n TokenUsage,\n TTSOptions,\n TTSResponse,\n VideoGenerationJob,\n VideoGenerationOptions,\n VideoGenerationResult,\n VideoGenerationStatusResult,\n Voice,\n VoiceCloneOptions,\n VoiceDesignOptions,\n VoiceListOptions,\n} from '../types';\nimport {\n AIError,\n AuthenticationError,\n ContentFilterError,\n ContextLengthError,\n ModelNotFoundError,\n RateLimitError,\n} from '../types';\nimport { emitUsage } from './usage';\n\n/**\n * Shared profile for OpenAI-compatible providers\n */\nexport interface OpenAICompatibleProfile {\n providerLabel: string;\n providerName: string;\n defaultModel: string;\n capabilities: AICapabilities;\n describeModel(modelId: string): string;\n getContextLength(modelId: string): number;\n getModelCapabilities(modelId: string): string[];\n shouldIncludeModel(modelId: string): boolean;\n supportsFunctions(modelId: string): boolean;\n supportsVision(modelId: string): boolean;\n}\n\nconst OPENAI_PROFILE: OpenAICompatibleProfile = {\n providerLabel: 'OpenAI',\n providerName: 'openai',\n defaultModel: 'gpt-4o',\n capabilities: {\n chat: true,\n completion: true,\n embeddings: true,\n streaming: true,\n functions: true,\n vision: true,\n fineTuning: true,\n imageEmbeddings: true,\n imageGeneration: true,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 128000,\n supportedOperations: [\n 'chat',\n 'completion',\n 'embedding',\n 'streaming',\n 'functions',\n 'vision',\n 'image_embedding',\n 'image_generation',\n ],\n },\n describeModel: (modelId) => `OpenAI model: ${modelId}`,\n getContextLength(modelId) {\n if (modelId.includes('gpt-4o')) return 128000;\n if (modelId.includes('gpt-4.1')) return 128000;\n if (modelId.includes('gpt-4-turbo')) return 128000;\n if (modelId.includes('gpt-4')) return 8192;\n if (modelId.includes('gpt-3.5-turbo')) return 16385;\n if (modelId.includes('text-embedding')) return 8192;\n return 4096;\n },\n getModelCapabilities(modelId) {\n const capabilities = ['text'];\n if (modelId.includes('gpt')) {\n capabilities.push('chat', 'functions');\n }\n if (\n modelId.includes('vision') ||\n modelId === 'gpt-4o' ||\n modelId.includes('gpt-4.1')\n ) {\n capabilities.push('vision');\n }\n if (modelId.includes('embedding')) {\n capabilities.push('embeddings');\n }\n return capabilities;\n },\n shouldIncludeModel(modelId) {\n return modelId.includes('gpt') || modelId.includes('text-embedding');\n },\n supportsFunctions(modelId) {\n return modelId.includes('gpt-4') || modelId.includes('gpt-3.5');\n },\n supportsVision(modelId) {\n return (\n modelId.includes('vision') ||\n modelId === 'gpt-4o' ||\n modelId.includes('gpt-4.1')\n );\n },\n};\n\nexport interface OpenAICompatibleOptions extends BaseAIOptions {\n type?: string;\n apiKey?: string;\n baseUrl?: string;\n organization?: string;\n}\n\n/**\n * OpenAI provider implementation that handles all interactions with OpenAI's API.\n * Supports GPT models, embeddings, function calling, streaming, and vision capabilities.\n */\nexport class OpenAIProvider implements AIInterface {\n private client: OpenAI;\n protected options: OpenAICompatibleOptions;\n protected readonly profile: OpenAICompatibleProfile;\n\n /**\n * Creates a new OpenAI provider instance\n * @param options - Configuration options for the OpenAI provider\n */\n constructor(\n options: OpenAICompatibleOptions,\n profile: OpenAICompatibleProfile = OPENAI_PROFILE,\n ) {\n this.profile = profile;\n this.options = normalizeBaseAIOptions({\n defaultModel: this.profile.defaultModel,\n ...options,\n });\n\n this.client = new OpenAI({\n apiKey: this.options.apiKey,\n baseURL: this.options.baseUrl,\n organization: this.options.organization,\n timeout: this.options.timeout,\n maxRetries: this.options.maxRetries,\n defaultHeaders: this.options.headers,\n });\n }\n\n /**\n * Generate a chat completion using OpenAI's chat models\n * @param messages - Array of conversation messages\n * @param options - Optional configuration for the chat completion\n * @returns Promise resolving to the AI response with content and metadata\n * @throws {AIError} When the API request fails or returns invalid data\n *\n * @example\n * ```typescript\n * const response = await provider.chat([\n * { role: 'system', content: 'You are a helpful assistant.' },\n * { role: 'user', content: 'What is the capital of France?' }\n * ], {\n * model: 'gpt-4o',\n * temperature: 0.7,\n * maxTokens: 150\n * });\n * console.log(response.content); // \"Paris is the capital of France.\"\n * ```\n */\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n const startTime = Date.now();\n let controls: PreparedRequestControls | undefined;\n try {\n const model =\n options.model || this.options.defaultModel || this.profile.defaultModel;\n options = normalizeChatOptions(\n this.options,\n options,\n this.profile.providerName,\n model,\n );\n controls = prepareRequestControls(this.options, options);\n const request = {\n model,\n messages: this.mapMessagesToOpenAI(messages),\n max_tokens: options.maxTokens,\n temperature: options.temperature,\n top_p: options.topP,\n n: options.n,\n stop: options.stop,\n frequency_penalty: options.frequencyPenalty,\n presence_penalty: options.presencePenalty,\n user: options.user,\n tools: options.tools?.map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.function.name,\n description: tool.function.description,\n parameters: tool.function.parameters,\n },\n })),\n tool_choice: this.mapToolChoice(options.toolChoice),\n response_format: options.responseFormat,\n seed: options.seed,\n stream: false,\n } as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming & {\n reasoning?: {\n effort?: string;\n max_tokens?: number;\n include_thoughts?: boolean;\n };\n };\n if (\n (this.profile.providerName === 'bifrost' ||\n this.profile.providerName === 'litellm') &&\n (options.reasoning?.maxTokens || 0) > 0\n ) {\n request.reasoning = {\n effort: options.reasoning?.effort,\n max_tokens: options.reasoning?.maxTokens,\n include_thoughts: options.reasoning?.includeThoughts,\n };\n }\n const response = await this.client.chat.completions.create(request, {\n signal: controls.signal,\n timeout: controls.timeout,\n });\n\n const choice = response.choices[0];\n if (!choice) {\n throw new AIError(\n `No choices returned from ${this.profile.providerLabel}`,\n 'NO_CHOICES',\n this.profile.providerName,\n );\n }\n\n const usage = this.mapUsage(response.usage);\n emitUsage(\n this.options,\n this.profile.providerName,\n 'chat',\n response.model || model,\n usage,\n startTime,\n options.usageTags,\n );\n\n return {\n content: choice.message.content || '',\n usage,\n model: response.model,\n finishReason: this.mapFinishReason(choice.finish_reason),\n toolCalls: choice.message.tool_calls\n ?.filter((call) => call.type === 'function')\n .map((call) => ({\n id: call.id,\n type: 'function' as const,\n function: {\n name: call.function.name,\n arguments: call.function.arguments,\n },\n })),\n };\n } catch (error) {\n if (controls?.didTimeout()) {\n throw new AIError(\n `AI request timed out after ${controls.timeout}ms`,\n 'AI_TIMEOUT',\n this.profile.providerName,\n options.model,\n );\n }\n if (options.signal?.aborted) {\n throw new AIError(\n 'AI request aborted by caller',\n 'AI_ABORTED',\n this.profile.providerName,\n options.model,\n );\n }\n throw this.mapError(error);\n } finally {\n controls?.cleanup();\n }\n }\n\n /**\n * Generate a text completion for a given prompt\n * @param prompt - The text prompt to complete\n * @param options - Optional configuration for the completion\n * @returns Promise resolving to the AI response\n * @throws {AIError} When the API request fails\n *\n * @example\n * ```typescript\n * const response = await provider.complete('The weather today is', {\n * model: 'gpt-4o',\n * maxTokens: 50,\n * temperature: 0.5\n * });\n * ```\n */\n async complete(\n prompt: string,\n options: CompletionOptions = {},\n ): Promise<AIResponse> {\n return this.chat([{ role: 'user', content: prompt }], {\n model: options.model,\n maxTokens: options.maxTokens,\n temperature: options.temperature,\n topP: options.topP,\n n: options.n,\n stop: options.stop,\n stream: options.stream,\n onProgress: options.onProgress,\n signal: options.signal,\n timeout: options.timeout,\n reasoning: options.reasoning,\n usageTags: options.usageTags,\n });\n }\n\n /**\n * Simple message interface for single-turn interactions with optional history\n *\n * @param text - The message text to send\n * @param options - Configuration options including history, model, etc.\n * @returns Promise resolving to the response content string\n *\n * @example\n * ```typescript\n * // Simple usage\n * const response = await provider.message('Hello!');\n *\n * // With options\n * const response = await provider.message('Analyze this', {\n * model: 'gpt-4o',\n * responseFormat: { type: 'json_object' }\n * });\n *\n * // With history\n * const response = await provider.message('What was my question?', {\n * history: [\n * { role: 'user', content: 'What is 2+2?' },\n * { role: 'assistant', content: '4' }\n * ]\n * });\n * ```\n */\n async message(text: string, options: MessageOptions = {}): Promise<string> {\n // Build messages array from history + current message\n const messages: AIMessage[] = [\n ...(options.history || []),\n { role: options.role || 'user', content: text },\n ];\n\n const response = await this.chat(messages, {\n model: options.model,\n maxTokens: options.maxTokens,\n temperature: options.temperature,\n topP: options.topP,\n stop: options.stop,\n stream: options.stream,\n frequencyPenalty: options.frequencyPenalty,\n presencePenalty: options.presencePenalty,\n responseFormat: options.responseFormat,\n seed: options.seed,\n tools: options.tools,\n toolChoice: options.toolChoice,\n onProgress: options.onProgress,\n signal: options.signal,\n timeout: options.timeout,\n reasoning: options.reasoning,\n usageTags: options.usageTags,\n });\n\n return response.content;\n }\n\n /**\n * Generate embeddings for the given text(s)\n * @param text - Single text string or array of texts to embed\n * @param options - Optional configuration for embeddings\n * @returns Promise resolving to embeddings response with vector arrays\n * @throws {AIError} When the API request fails\n *\n * @example\n * ```typescript\n * // Single text embedding\n * const response1 = await provider.embed('Hello world');\n * console.log(response1.embeddings[0]); // Array of numbers\n *\n * // Multiple text embeddings\n * const response2 = await provider.embed(['Text 1', 'Text 2']);\n * console.log(response2.embeddings.length); // 2\n * ```\n */\n async embed(\n text: string | string[],\n options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n const startTime = Date.now();\n try {\n const model = options.model || 'text-embedding-3-small';\n const input = Array.isArray(text) ? text : [text];\n const response = await this.client.embeddings.create({\n model,\n input,\n encoding_format: options.encodingFormat,\n dimensions: options.dimensions,\n user: options.user,\n });\n\n const usage = this.mapUsage(response.usage);\n emitUsage(\n this.options,\n this.profile.providerName,\n 'embed',\n response.model || model,\n usage,\n startTime,\n options.usageTags,\n );\n\n return {\n embeddings: response.data.map((item) => item.embedding),\n usage,\n model: response.model,\n };\n } catch (error) {\n throw this.mapError(error);\n }\n }\n\n /**\n * Convert an image to a base64 data URL\n * @param image - Image as URL, base64 data URL, or Buffer\n * @returns base64 data URL string\n * @private\n */\n private async imageToBase64(\n image: string | Buffer,\n signal?: AbortSignal,\n ): Promise<string> {\n if (Buffer.isBuffer(image)) {\n return `data:image/png;base64,${image.toString('base64')}`;\n }\n if (image.startsWith('data:')) {\n return image; // Already base64 data URL\n }\n // It's a URL - fetch and convert\n const response = await fetch(image, { signal });\n if (!response.ok) {\n throw new AIError(\n `Failed to fetch image: ${response.status} ${response.statusText}`,\n 'IMAGE_FETCH_ERROR',\n this.profile.providerName,\n );\n }\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n const contentType = response.headers.get('content-type') || 'image/png';\n return `data:${contentType};base64,${buffer.toString('base64')}`;\n }\n\n /**\n * Generate a text description of an image\n * @param image - Image as URL, base64 data URL, or Buffer\n * @param prompt - Custom prompt for description (optional)\n * @param options - Optional configuration\n * @returns Promise resolving to the description string\n *\n * @example\n * ```typescript\n * const description = await provider.describeImage('https://example.com/image.jpg');\n * ```\n */\n async describeImage(\n image: string | Buffer,\n prompt?: string,\n options: ImageDescriptionOptions = {},\n ): Promise<string> {\n let controls: PreparedRequestControls | undefined;\n try {\n const defaultPrompt =\n 'Describe this image for a search index. Include objects, mood, lighting, and any visible text.';\n\n controls = prepareRequestControls(this.options, options);\n const imageUrl = await this.imageToBase64(image, controls.signal);\n\n const response = await this.chat(\n [\n {\n role: 'user',\n content: [\n { type: 'text', text: prompt || defaultPrompt },\n {\n type: 'image_url',\n image_url: {\n url: imageUrl,\n detail: options.detail || 'auto',\n },\n },\n ],\n },\n ],\n {\n model:\n options.model ||\n this.options.defaultModel ||\n this.profile.defaultModel,\n maxTokens: options.maxTokens ?? 500,\n signal: controls.signal,\n timeout: controls.timeout,\n reasoning: options.reasoning,\n usageTags: options.usageTags,\n },\n );\n\n return response.content;\n } catch (error) {\n if (controls?.didTimeout()) {\n throw new AIError(\n `AI request timed out after ${controls.timeout}ms`,\n 'AI_TIMEOUT',\n this.profile.providerName,\n options.model,\n );\n }\n if (options.signal?.aborted) {\n throw new AIError(\n 'AI request aborted by caller',\n 'AI_ABORTED',\n this.profile.providerName,\n options.model,\n );\n }\n throw this.mapError(error);\n } finally {\n controls?.cleanup();\n }\n }\n\n /**\n * Generate embeddings for an image using describe-then-embed pattern\n * @param image - Image as URL, base64 data URL, or Buffer\n * @param options - Optional configuration for image embeddings\n * @returns Promise resolving to embeddings response\n *\n * @example\n * ```typescript\n * const embedding = await provider.embedImage('https://example.com/image.jpg');\n * ```\n */\n async embedImage(\n image: string | Buffer,\n options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n try {\n // OpenAI doesn't have native image embeddings - use describe-then-embed\n const description = await this.describeImage(\n image,\n 'Describe this image in detail for semantic embedding. Include all visible objects, colors, textures, composition, text, people, actions, and overall scene context.',\n );\n\n return this.embed(description, {\n model: options.model || 'text-embedding-3-small',\n dimensions: options.dimensions,\n user: options.user,\n });\n } catch (error) {\n throw this.mapError(error);\n }\n }\n\n /**\n * Generate an image from a text prompt using DALL-E\n * @param prompt - Text description of the image to generate\n * @param options - Optional configuration for image generation\n * @returns Promise resolving to generated image(s)\n *\n * @example\n * ```typescript\n * const result = await provider.generateImage('A sunset over mountains');\n * fs.writeFileSync('image.png', result.images[0].data);\n * ```\n */\n async generateImage(\n prompt: string,\n options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n let controls: PreparedRequestControls | undefined;\n try {\n const model = options.model || 'dall-e-3';\n options = normalizeImageGenerationOptions(\n this.options,\n options,\n this.profile.providerName,\n model,\n );\n controls = prepareRequestControls(this.options, options);\n\n const requestParams: OpenAI.Images.ImageGenerateParams = {\n model,\n prompt,\n n: model === 'dall-e-3' ? 1 : options.n,\n size:\n (options.size as OpenAI.Images.ImageGenerateParams['size']) ||\n '1024x1024',\n response_format: options.outputFormat === 'url' ? 'url' : 'b64_json',\n };\n\n if (model === 'dall-e-3') {\n if (options.style) {\n requestParams.style = options.style;\n }\n if (options.quality) {\n requestParams.quality =\n options.quality as OpenAI.Images.ImageGenerateParams['quality'];\n }\n }\n\n const response = await this.client.images.generate(requestParams, {\n signal: controls.signal,\n timeout: controls.timeout,\n });\n\n const images = (response.data || []).map((item) => {\n let data: Buffer | string;\n const mimeType = 'image/png';\n\n if (options.outputFormat === 'url') {\n data = item.url || '';\n } else if (options.outputFormat === 'base64') {\n data = item.b64_json || '';\n } else {\n // Default: buffer\n data = Buffer.from(item.b64_json || '', 'base64');\n }\n\n return {\n data,\n mimeType,\n revisedPrompt: item.revised_prompt,\n };\n });\n\n return {\n images,\n model,\n };\n } catch (error) {\n if (controls?.didTimeout()) {\n throw new AIError(\n `AI request timed out after ${controls.timeout}ms`,\n 'AI_TIMEOUT',\n this.profile.providerName,\n options.model,\n );\n }\n if (options.signal?.aborted) {\n throw new AIError(\n 'AI request aborted by caller',\n 'AI_ABORTED',\n this.profile.providerName,\n options.model,\n );\n }\n throw this.mapError(error);\n } finally {\n controls?.cleanup();\n }\n }\n\n // ============================================================================\n // Video Generation Methods (Not supported - use gemini, byteplus-modelark,\n // or openai-compat-video provider)\n // ============================================================================\n\n async submitVideoGenerationJob(\n _options: VideoGenerationOptions,\n ): Promise<VideoGenerationJob> {\n throw new AIError(\n `Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n async getVideoGenerationJob(\n _handle: VideoGenerationJob,\n ): Promise<VideoGenerationStatusResult> {\n throw new AIError(\n `Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n async fetchVideoGenerationResult(\n _handle: VideoGenerationJob,\n ): Promise<VideoGenerationResult> {\n throw new AIError(\n `Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n async cancelVideoGenerationJob(_handle: VideoGenerationJob): Promise<void> {\n throw new AIError(\n `Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n async validateVideoGenerationAccess(): Promise<boolean> {\n throw new AIError(\n `Video generation is not supported by ${this.profile.providerLabel} provider. Use gemini, byteplus-modelark, or openai-compat-video provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n /**\n * Stream a chat completion response in real-time\n * @param messages - Array of conversation messages\n * @param options - Optional configuration for the chat completion\n * @yields Individual content chunks as they arrive\n * @throws {AIError} When the streaming request fails\n *\n * @example\n * ```typescript\n * for await (const chunk of provider.stream([\n * { role: 'user', content: 'Write a story about AI' }\n * ])) {\n * process.stdout.write(chunk);\n * }\n * ```\n */\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n const startTime = Date.now();\n let controls: PreparedRequestControls | undefined;\n try {\n const model =\n options.model || this.options.defaultModel || this.profile.defaultModel;\n options = normalizeChatOptions(\n this.options,\n options,\n this.profile.providerName,\n model,\n );\n controls = prepareRequestControls(this.options, options);\n const request = {\n model,\n messages: this.mapMessagesToOpenAI(messages),\n max_tokens: options.maxTokens,\n temperature: options.temperature,\n top_p: options.topP,\n stop: options.stop,\n frequency_penalty: options.frequencyPenalty,\n presence_penalty: options.presencePenalty,\n user: options.user,\n stream: true,\n } as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming & {\n reasoning?: {\n effort?: string;\n max_tokens?: number;\n include_thoughts?: boolean;\n };\n };\n if (\n (this.profile.providerName === 'bifrost' ||\n this.profile.providerName === 'litellm') &&\n (options.reasoning?.maxTokens || 0) > 0\n ) {\n request.reasoning = {\n effort: options.reasoning?.effort,\n max_tokens: options.reasoning?.maxTokens,\n include_thoughts: options.reasoning?.includeThoughts,\n };\n }\n const stream = await this.client.chat.completions.create(request, {\n signal: controls.signal,\n timeout: controls.timeout,\n });\n\n for await (const chunk of stream) {\n const content = chunk.choices[0]?.delta?.content;\n if (content) {\n if (options.onProgress) {\n options.onProgress(content);\n }\n yield content;\n }\n }\n\n emitUsage(\n this.options,\n this.profile.providerName,\n 'stream',\n model,\n undefined,\n startTime,\n options.usageTags,\n );\n } catch (error) {\n if (controls?.didTimeout()) {\n throw new AIError(\n `AI request timed out after ${controls.timeout}ms`,\n 'AI_TIMEOUT',\n this.profile.providerName,\n options.model,\n );\n }\n if (options.signal?.aborted) {\n throw new AIError(\n 'AI request aborted by caller',\n 'AI_ABORTED',\n this.profile.providerName,\n options.model,\n );\n }\n throw this.mapError(error);\n } finally {\n controls?.cleanup();\n }\n }\n\n /**\n * Count the number of tokens in the given text\n * @param text - The text to count tokens for\n * @returns Promise resolving to the estimated token count\n *\n * @remarks\n * OpenAI doesn't provide a direct token counting API, so this is an approximation\n * based on the general rule of ~4 characters per token. For precise counting,\n * consider using a dedicated tokenizer library.\n *\n * @example\n * ```typescript\n * const count = await provider.countTokens('Hello, world!');\n * console.log(count); // Approximately 4 tokens\n * ```\n */\n async countTokens(text: string): Promise<number> {\n // OpenAI doesn't provide a direct token counting API\n // This is an approximation based on the general rule of ~4 characters per token\n return Math.ceil(text.length / 4);\n }\n\n /**\n * Get a list of available OpenAI models\n * @returns Promise resolving to an array of model information\n * @throws {AIError} When the API request fails\n *\n * @example\n * ```typescript\n * const models = await provider.getModels();\n * const gptModels = models.filter(m => m.id.includes('gpt'));\n * ```\n */\n async getModels(): Promise<AIModel[]> {\n try {\n const response = await this.client.models.list();\n return response.data\n .filter((model) => this.profile.shouldIncludeModel(model.id))\n .map((model) => ({\n id: model.id,\n name: model.id,\n description: this.profile.describeModel(model.id),\n contextLength: this.profile.getContextLength(model.id),\n capabilities: this.profile.getModelCapabilities(model.id),\n supportsFunctions: this.profile.supportsFunctions(model.id),\n supportsVision: this.profile.supportsVision(model.id),\n }));\n } catch (error) {\n throw this.mapError(error);\n }\n }\n\n /**\n * Get the capabilities supported by this OpenAI provider\n * @returns Promise resolving to provider capabilities\n *\n * @example\n * ```typescript\n * const caps = await provider.getCapabilities();\n * if (caps.functions) {\n * // Provider supports function calling\n * }\n * ```\n */\n async getCapabilities(): Promise<AICapabilities> {\n return { ...this.profile.capabilities };\n }\n\n // ============================================================================\n // TTS Methods (Not supported - use Qwen3-TTS provider)\n // ============================================================================\n\n async synthesizeSpeech(\n _text: string,\n _options?: TTSOptions,\n ): Promise<TTSResponse> {\n throw new AIError(\n `TTS is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n streamSpeech(_text: string, _options?: TTSOptions): AsyncIterable<Buffer> {\n const error = new AIError(\n `TTS streaming is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n return {\n [Symbol.asyncIterator]: () => ({\n next: () => Promise.reject(error),\n }),\n };\n }\n\n async cloneVoice(_options: VoiceCloneOptions): Promise<Voice> {\n throw new AIError(\n `Voice cloning is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n async designVoice(_options: VoiceDesignOptions): Promise<Voice> {\n throw new AIError(\n `Voice design is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n async getVoices(_options?: VoiceListOptions): Promise<Voice[]> {\n throw new AIError(\n `Voice listing is not supported by ${this.profile.providerLabel} provider. Use Qwen3-TTS provider.`,\n 'NOT_IMPLEMENTED',\n this.profile.providerName,\n );\n }\n\n /**\n * Maps internal AI messages to OpenAI's message format\n * @param messages - Array of internal AI messages\n * @returns Array of OpenAI-compatible message parameters\n * @private\n */\n private mapMessagesToOpenAI(\n messages: AIMessage[],\n ): OpenAI.Chat.ChatCompletionMessageParam[] {\n return messages.map((message) => {\n // Handle content that can be string or ContentPart[]\n let content: string | OpenAI.Chat.ChatCompletionContentPart[];\n\n if (typeof message.content === 'string') {\n content = message.content;\n } else {\n // Array of content parts - map to OpenAI format\n content = message.content.map((part: ContentPart) => {\n if (part.type === 'text') {\n return { type: 'text' as const, text: part.text };\n }\n // Image content part\n return {\n type: 'image_url' as const,\n image_url: {\n url: part.image_url.url,\n detail: part.image_url.detail,\n },\n };\n });\n }\n\n // Build message based on role and content\n const baseMessage = {\n role: message.role as OpenAI.Chat.ChatCompletionRole,\n content,\n };\n\n // Add optional fields based on role and availability\n if (\n message.name &&\n (message.role === 'system' ||\n message.role === 'user' ||\n message.role === 'function')\n ) {\n (baseMessage as any).name = message.name;\n }\n\n if (message.tool_calls && message.role === 'assistant') {\n (baseMessage as any).tool_calls = message.tool_calls;\n }\n\n return baseMessage as OpenAI.Chat.ChatCompletionMessageParam;\n });\n }\n\n /**\n * Maps internal tool choice format to OpenAI's tool choice format\n * @param toolChoice - Internal tool choice specification\n * @returns OpenAI-compatible tool choice option or undefined\n * @private\n */\n private mapToolChoice(\n toolChoice?:\n | 'auto'\n | 'none'\n | { type: 'function'; function: { name: string } },\n ): OpenAI.Chat.ChatCompletionToolChoiceOption | undefined {\n if (!toolChoice) return undefined;\n if (typeof toolChoice === 'string') return toolChoice;\n return {\n type: 'function',\n function: { name: toolChoice.function.name },\n };\n }\n\n /**\n * Maps OpenAI usage information to internal token usage format\n * @param usage - OpenAI usage object from API response\n * @returns Internal token usage object or undefined\n * @private\n */\n private mapUsage(\n usage?:\n | OpenAI.CompletionUsage\n | OpenAI.Completions.CompletionUsage\n | OpenAI.Embeddings.CreateEmbeddingResponse.Usage,\n ): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens || 0,\n completionTokens: (usage as any).completion_tokens || 0,\n totalTokens: usage.total_tokens || 0,\n };\n }\n\n /**\n * Maps OpenAI finish reason to internal finish reason format\n * @param reason - OpenAI finish reason from API response\n * @returns Internal finish reason\n * @private\n */\n private mapFinishReason(reason: string | null): AIResponse['finishReason'] {\n switch (reason) {\n case 'stop':\n return 'stop';\n case 'length':\n return 'length';\n case 'tool_calls':\n return 'tool_calls';\n case 'content_filter':\n return 'content_filter';\n default:\n return 'stop';\n }\n }\n\n /**\n * Gets the context length for a given OpenAI model\n * @param modelId - The OpenAI model identifier\n * @returns Maximum context length in tokens\n * @private\n */\n /**\n * Maps OpenAI API errors to internal AI error types\n * @param error - The error object from OpenAI API\n * @returns Appropriate internal AI error instance\n * @private\n */\n\n private mapError(error: unknown): AIError {\n if (error instanceof OpenAI.APIError) {\n switch (error.status) {\n case 401:\n return new AuthenticationError(this.profile.providerName);\n case 429: {\n return new RateLimitError(\n this.profile.providerName,\n extractRetryAfterSeconds(error),\n );\n }\n case 404:\n return new ModelNotFoundError(\n error.message,\n this.profile.providerName,\n );\n case 413:\n return new ContextLengthError(this.profile.providerName);\n default:\n if (error.message.includes('content_filter')) {\n return new ContentFilterError(this.profile.providerName);\n }\n return new AIError(\n error.message,\n 'API_ERROR',\n this.profile.providerName,\n );\n }\n }\n\n if (error instanceof AIError) {\n return error;\n }\n\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error occurred';\n return new AIError(\n errorMessage,\n 'UNKNOWN_ERROR',\n this.profile.providerName,\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,369 @@
1
+ import { r as AuthenticationError, s as RateLimitError, t as AIError } from "./types-CINwUPDo.js";
2
+ import { n as extractRetryAfterSeconds } from "./rate-limit-DgEhanDL.js";
3
+ import { a as normalizeBaseAIOptions } from "./safety-C415clBm.js";
4
+ import { t as emitUsage } from "./usage-C1Y1Nlg4.js";
5
+ import { ValidationError } from "@happyvertical/utils";
6
+ //#region src/shared/providers/openai-compat-video.ts
7
+ /**
8
+ * OpenAI-compatible video-generation provider implementation
9
+ *
10
+ * Thin, raw-HTTP adapter over a `/v1/videos`-shaped REST surface (create /
11
+ * retrieve / cancel / download content) configured by base URL + key. This
12
+ * is the shape LiteLLM normalizes Veo behind, and the shape used by
13
+ * Sora-style gateways. The surface is young and still churning across
14
+ * vendors, so this adapter is deliberately thin: it forwards the fields the
15
+ * OpenAI `videos` API documents (model, prompt, seconds, size, a single
16
+ * image reference) and leaves everything else to the gateway's defaults.
17
+ *
18
+ * Only video-generation methods are implemented; use the `openai` or
19
+ * `litellm` provider types against the same gateway for chat/embeddings.
20
+ *
21
+ * Request bodies are sent as JSON, not the `multipart/form-data` the
22
+ * official `openai` npm package uses for `input_reference` uploads. This is
23
+ * a deliberate choice for this thin adapter: LiteLLM (the primary target
24
+ * gateway) accepts `input_reference.image_url` as a URL or base64 data URL
25
+ * in a JSON body, and staying JSON-only keeps this file a plain HTTP client
26
+ * with no multipart-encoding dependency. A raw OpenAI Sora endpoint that
27
+ * requires actual multipart file uploads is not supported by this adapter.
28
+ */
29
+ function ensureOpenAICompatVideoOptions(options) {
30
+ if (!options.baseUrl?.trim()) throw new ValidationError("openai-compat-video baseUrl is required", {
31
+ provider: "openai-compat-video",
32
+ hint: "Pass baseUrl like https://llm.happyvertical.com/v1 or set OPENAI_COMPAT_VIDEO_BASE_URL"
33
+ });
34
+ return {
35
+ ...options,
36
+ baseUrl: options.baseUrl.trim().replace(/\/+$/, "")
37
+ };
38
+ }
39
+ function parseSize(size) {
40
+ const match = size?.match(/^(\d+)x(\d+)$/);
41
+ if (!match) return {};
42
+ return {
43
+ width: Number(match[1]),
44
+ height: Number(match[2])
45
+ };
46
+ }
47
+ /**
48
+ * Map a gateway's raw status string to our status enum.
49
+ *
50
+ * LiteLLM (the primary target gateway) documents `'processing'` as its
51
+ * mid-render status rather than OpenAI's own `'in_progress'`; both, plus
52
+ * `'pending'`, map to `'running'`. Only an explicit `'failed'` maps to a
53
+ * terminal failure — an unrecognized status is never assumed to be
54
+ * terminal, since misreading a mid-render job as failed would cause a
55
+ * caller to resubmit (and double-bill) a job that was actually still
56
+ * rendering. Unrecognized values map to `'running'` with the raw string
57
+ * preserved via `rawStatus` so callers can still see and log it.
58
+ */
59
+ function mapStatus(status) {
60
+ switch (status) {
61
+ case "queued": return { status: "queued" };
62
+ case "in_progress":
63
+ case "processing":
64
+ case "pending": return { status: "running" };
65
+ case "completed": return { status: "succeeded" };
66
+ case "failed": return { status: "failed" };
67
+ case "cancelled":
68
+ case "canceled": return { status: "cancelled" };
69
+ default: return {
70
+ status: "running",
71
+ rawStatus: status
72
+ };
73
+ }
74
+ }
75
+ /**
76
+ * OpenAI-compatible video-generation provider for `/v1/videos`-shaped
77
+ * gateways (LiteLLM, Sora-shaped providers).
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * import { getAI } from '@happyvertical/ai';
82
+ *
83
+ * const video = await getAI({
84
+ * type: 'openai-compat-video',
85
+ * baseUrl: 'https://llm.happyvertical.com/v1',
86
+ * apiKey: process.env.OPENAI_COMPAT_VIDEO_API_KEY!,
87
+ * });
88
+ *
89
+ * const job = await video.submitVideoGenerationJob({
90
+ * prompt: 'A time-lapse of clouds over a mountain range',
91
+ * durationSeconds: 8,
92
+ * });
93
+ * ```
94
+ */
95
+ var OpenAICompatVideoProvider = class {
96
+ options;
97
+ constructor(options) {
98
+ this.options = normalizeBaseAIOptions(ensureOpenAICompatVideoOptions(options));
99
+ }
100
+ get baseUrl() {
101
+ return this.options.baseUrl;
102
+ }
103
+ async request(path, options = {}) {
104
+ const text = await (await this.rawRequest(path, options)).text();
105
+ return text ? JSON.parse(text) : {};
106
+ }
107
+ async rawRequest(path, options = {}) {
108
+ if (options.signal?.aborted) throw new AIError("openai-compat-video request aborted by caller", "AI_ABORTED", "openai-compat-video");
109
+ const url = new URL(`${this.baseUrl}${path}`);
110
+ for (const [key, value] of Object.entries(options.query ?? {})) url.searchParams.set(key, String(value));
111
+ const controller = new AbortController();
112
+ const timeoutMs = options.timeout ?? this.options.timeout;
113
+ let timedOut = false;
114
+ const timer = timeoutMs ? setTimeout(() => {
115
+ timedOut = true;
116
+ controller.abort();
117
+ }, timeoutMs) : void 0;
118
+ const onExternalAbort = () => controller.abort(options.signal?.reason);
119
+ if (options.signal?.aborted) onExternalAbort();
120
+ else options.signal?.addEventListener("abort", onExternalAbort, { once: true });
121
+ try {
122
+ const headers = { ...this.options.headers || {} };
123
+ if (this.options.apiKey) headers.Authorization = `Bearer ${this.options.apiKey}`;
124
+ if (options.body !== void 0) headers["Content-Type"] = "application/json";
125
+ let response;
126
+ try {
127
+ response = await fetch(url, {
128
+ method: options.method || "GET",
129
+ headers,
130
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
131
+ signal: controller.signal
132
+ });
133
+ } catch (error) {
134
+ if (timedOut) throw new AIError(`openai-compat-video request to ${path} timed out after ${timeoutMs}ms`, "AI_TIMEOUT", "openai-compat-video", void 0, true);
135
+ if (options.signal?.aborted) throw new AIError("openai-compat-video request aborted by caller", "AI_ABORTED", "openai-compat-video");
136
+ throw new AIError(`Network error calling openai-compat-video ${path}: ${error instanceof Error ? error.message : String(error)}`, "NETWORK_ERROR", "openai-compat-video", void 0, true);
137
+ }
138
+ if (!response.ok) {
139
+ if (response.status === 404 && options.allowNotFound) return response;
140
+ const errorText = await response.text();
141
+ if (response.status === 401 || response.status === 403) throw new AuthenticationError("openai-compat-video");
142
+ if (response.status === 429) throw new RateLimitError("openai-compat-video", extractRetryAfterSeconds({ headers: response.headers }));
143
+ throw new AIError(`openai-compat-video request to ${path} failed: ${response.status} ${errorText}`, "OPENAI_COMPAT_VIDEO_REQUEST_FAILED", "openai-compat-video");
144
+ }
145
+ return response;
146
+ } finally {
147
+ if (timer) clearTimeout(timer);
148
+ options.signal?.removeEventListener("abort", onExternalAbort);
149
+ }
150
+ }
151
+ /**
152
+ * Submit an asynchronous video-generation job.
153
+ *
154
+ * `resolution` is forwarded as the gateway's `size` field only when it is
155
+ * already in `WIDTHxHEIGHT` form (e.g. `'1280x720'`); other resolution
156
+ * labels (`'720p'`) are not translated and the gateway's default size
157
+ * applies instead — keeping this adapter thin rather than guessing a
158
+ * pixel size per vendor. `negativePrompt`, `seed`, and `fps` are not part
159
+ * of the documented OpenAI videos schema and are not forwarded.
160
+ */
161
+ async submitVideoGenerationJob(options) {
162
+ const startTime = Date.now();
163
+ const model = options.model || this.options.defaultModel || "sora-2";
164
+ const body = {
165
+ model,
166
+ prompt: options.prompt
167
+ };
168
+ if (options.durationSeconds !== void 0) body.seconds = String(options.durationSeconds);
169
+ if (options.resolution?.includes("x")) body.size = options.resolution;
170
+ const firstReference = options.referenceImages?.[0];
171
+ if (firstReference) body.input_reference = { image_url: typeof firstReference.image === "string" ? firstReference.image : `data:${firstReference.mimeType || "image/png"};base64,${firstReference.image.toString("base64")}` };
172
+ const video = await this.request("/videos", {
173
+ method: "POST",
174
+ body,
175
+ signal: options.signal,
176
+ timeout: options.timeout
177
+ });
178
+ emitUsage(this.options, "openai-compat-video", "submitVideoGenerationJob", video.model || model, void 0, startTime, {
179
+ ...options.durationSeconds !== void 0 ? { durationSeconds: String(options.durationSeconds) } : {},
180
+ ...options.resolution ? { resolution: options.resolution } : {},
181
+ ...options.aspectRatio ? { aspectRatio: options.aspectRatio } : {},
182
+ ...options.usageTags
183
+ });
184
+ return {
185
+ jobId: video.id,
186
+ provider: "openai-compat-video",
187
+ model: video.model || model,
188
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
189
+ };
190
+ }
191
+ /**
192
+ * Poll the status of a video-generation job.
193
+ *
194
+ * The gateway's status response does not include the video bytes, so
195
+ * `result` on a succeeded job carries only metadata (mimeType, duration,
196
+ * dimensions) plus a `url` when the gateway happens to include one
197
+ * directly. Call {@link fetchVideoGenerationResult} to download the bytes.
198
+ */
199
+ async getVideoGenerationJob(handle) {
200
+ const video = await this.request(`/videos/${handle.jobId}`);
201
+ return this.mapVideo(video);
202
+ }
203
+ /**
204
+ * Fetch the result of a completed video-generation job, downloading the
205
+ * rendered bytes from the gateway's content endpoint.
206
+ *
207
+ * @throws {AIError} When the job has not succeeded yet
208
+ */
209
+ async fetchVideoGenerationResult(handle) {
210
+ const video = await this.request(`/videos/${handle.jobId}`);
211
+ const status = this.mapVideo(video);
212
+ if (status.status !== "succeeded") throw new AIError(`openai-compat-video job ${handle.jobId} has not succeeded (status: ${status.status})`, "VIDEO_JOB_NOT_READY", "openai-compat-video", handle.model);
213
+ if (video.url) return status.result;
214
+ const response = await this.rawRequest(`/videos/${handle.jobId}/content`);
215
+ const arrayBuffer = await response.arrayBuffer();
216
+ const { width, height } = parseSize(video.size);
217
+ return {
218
+ data: Buffer.from(arrayBuffer),
219
+ mimeType: response.headers.get("content-type") || "video/mp4",
220
+ durationSeconds: video.seconds ? Number(video.seconds) : void 0,
221
+ width,
222
+ height
223
+ };
224
+ }
225
+ /**
226
+ * Cancel a video-generation job. Cancellation of an in-flight Sora-shaped
227
+ * job is not part of the documented OpenAI `videos` API and is
228
+ * gateway-dependent (e.g. LiteLLM's Veo passthrough may support it); this
229
+ * attempts the conventional `/videos/{id}/cancel` REST call and surfaces
230
+ * an {@link AIError} if the gateway rejects or does not implement it.
231
+ */
232
+ async cancelVideoGenerationJob(handle) {
233
+ await this.rawRequest(`/videos/${handle.jobId}/cancel`, { method: "POST" });
234
+ }
235
+ /**
236
+ * Cheap auth-shaped check for video-generation access.
237
+ *
238
+ * This probes `GET /videos/{sentinel-id}` rather than listing videos:
239
+ * LiteLLM (the primary target gateway) requires a `custom_llm_provider`
240
+ * parameter on its `GET /v1/videos` list route that this generic adapter
241
+ * has no context to supply, so a list-based check would fail with valid
242
+ * credentials on the flagship gateway. A single-resource GET works
243
+ * uniformly instead, classified as:
244
+ * - `401`/`403` → invalid credentials, throws {@link AuthenticationError}
245
+ * - `404` → authenticated successfully, the sentinel id just doesn't
246
+ * exist — this is a **valid** result, not a failure
247
+ * - `2xx` → valid (the sentinel id coincidentally exists)
248
+ *
249
+ * Callers on a hot path must cache the result themselves.
250
+ */
251
+ async validateVideoGenerationAccess() {
252
+ const response = await this.rawRequest(`/videos/have-ai-validate-video-access`, { allowNotFound: true });
253
+ return response.ok || response.status === 404;
254
+ }
255
+ mapVideo(video) {
256
+ const { status, rawStatus } = mapStatus(video.status);
257
+ if (status === "failed") return {
258
+ status: "failed",
259
+ error: video.error?.message || `Video generation failed (status: ${video.status})`
260
+ };
261
+ if (status !== "succeeded") return {
262
+ status,
263
+ progress: video.progress,
264
+ rawStatus
265
+ };
266
+ const { width, height } = parseSize(video.size);
267
+ return {
268
+ status: "succeeded",
269
+ result: {
270
+ url: video.url,
271
+ mimeType: "video/mp4",
272
+ durationSeconds: video.seconds ? Number(video.seconds) : void 0,
273
+ width,
274
+ height
275
+ }
276
+ };
277
+ }
278
+ async getModels() {
279
+ return [{
280
+ id: "sora-2",
281
+ name: "Sora 2",
282
+ description: "OpenAI-compatible Sora 2 video-generation model",
283
+ contextLength: 0,
284
+ capabilities: ["video_generation"],
285
+ supportsFunctions: false,
286
+ supportsVision: true
287
+ }, {
288
+ id: "sora-2-pro",
289
+ name: "Sora 2 Pro",
290
+ description: "OpenAI-compatible Sora 2 Pro video-generation model",
291
+ contextLength: 0,
292
+ capabilities: ["video_generation"],
293
+ supportsFunctions: false,
294
+ supportsVision: true
295
+ }];
296
+ }
297
+ async getCapabilities() {
298
+ return {
299
+ chat: false,
300
+ completion: false,
301
+ embeddings: false,
302
+ streaming: false,
303
+ functions: false,
304
+ vision: false,
305
+ fineTuning: false,
306
+ imageEmbeddings: false,
307
+ imageGeneration: false,
308
+ videoGeneration: true,
309
+ tts: false,
310
+ voiceCloning: false,
311
+ voiceDesign: false,
312
+ maxContextLength: 0,
313
+ supportedOperations: [
314
+ "submitVideoGenerationJob",
315
+ "getVideoGenerationJob",
316
+ "fetchVideoGenerationResult",
317
+ "cancelVideoGenerationJob"
318
+ ]
319
+ };
320
+ }
321
+ async chat(_messages, _options) {
322
+ throw new AIError("Chat is not supported by the openai-compat-video provider. Use the openai or litellm provider for text generation.", "NOT_IMPLEMENTED", "openai-compat-video");
323
+ }
324
+ async complete(_prompt, _options) {
325
+ throw new AIError("Complete is not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
326
+ }
327
+ async message(_text, _options) {
328
+ throw new AIError("Message is not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
329
+ }
330
+ async embed(_text, _options) {
331
+ throw new AIError("Embeddings are not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
332
+ }
333
+ async embedImage(_image, _options) {
334
+ throw new AIError("Image embeddings are not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
335
+ }
336
+ async describeImage(_image, _prompt, _options) {
337
+ throw new AIError("Image description is not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
338
+ }
339
+ async generateImage(_prompt, _options) {
340
+ throw new AIError("Image generation is not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
341
+ }
342
+ stream(_messages, _options) {
343
+ const error = new AIError("Chat streaming is not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
344
+ return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
345
+ }
346
+ async countTokens(_text) {
347
+ throw new AIError("Token counting is not supported by the openai-compat-video provider.", "NOT_IMPLEMENTED", "openai-compat-video");
348
+ }
349
+ async synthesizeSpeech(_text, _options) {
350
+ throw new AIError("TTS is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.", "NOT_IMPLEMENTED", "openai-compat-video");
351
+ }
352
+ streamSpeech(_text, _options) {
353
+ const error = new AIError("TTS streaming is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.", "NOT_IMPLEMENTED", "openai-compat-video");
354
+ return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
355
+ }
356
+ async cloneVoice(_options) {
357
+ throw new AIError("Voice cloning is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.", "NOT_IMPLEMENTED", "openai-compat-video");
358
+ }
359
+ async designVoice(_options) {
360
+ throw new AIError("Voice design is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.", "NOT_IMPLEMENTED", "openai-compat-video");
361
+ }
362
+ async getVoices(_options) {
363
+ throw new AIError("Voice listing is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.", "NOT_IMPLEMENTED", "openai-compat-video");
364
+ }
365
+ };
366
+ //#endregion
367
+ export { OpenAICompatVideoProvider };
368
+
369
+ //# sourceMappingURL=openai-compat-video-D_U7jo0d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compat-video-D_U7jo0d.js","names":[],"sources":["../../src/shared/providers/openai-compat-video.ts"],"sourcesContent":["/**\n * OpenAI-compatible video-generation provider implementation\n *\n * Thin, raw-HTTP adapter over a `/v1/videos`-shaped REST surface (create /\n * retrieve / cancel / download content) configured by base URL + key. This\n * is the shape LiteLLM normalizes Veo behind, and the shape used by\n * Sora-style gateways. The surface is young and still churning across\n * vendors, so this adapter is deliberately thin: it forwards the fields the\n * OpenAI `videos` API documents (model, prompt, seconds, size, a single\n * image reference) and leaves everything else to the gateway's defaults.\n *\n * Only video-generation methods are implemented; use the `openai` or\n * `litellm` provider types against the same gateway for chat/embeddings.\n *\n * Request bodies are sent as JSON, not the `multipart/form-data` the\n * official `openai` npm package uses for `input_reference` uploads. This is\n * a deliberate choice for this thin adapter: LiteLLM (the primary target\n * gateway) accepts `input_reference.image_url` as a URL or base64 data URL\n * in a JSON body, and staying JSON-only keeps this file a plain HTTP client\n * with no multipart-encoding dependency. A raw OpenAI Sora endpoint that\n * requires actual multipart file uploads is not supported by this adapter.\n */\n\nimport { ValidationError } from '@happyvertical/utils';\nimport { extractRetryAfterSeconds } from '../rate-limit';\nimport { normalizeBaseAIOptions } from '../safety';\nimport type {\n AICapabilities,\n AIInterface,\n AIMessage,\n AIModel,\n AIResponse,\n ChatOptions,\n CompletionOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n MessageOptions,\n OpenAICompatVideoOptions,\n TTSOptions,\n TTSResponse,\n VideoGenerationJob,\n VideoGenerationOptions,\n VideoGenerationResult,\n VideoGenerationStatusResult,\n Voice,\n VoiceCloneOptions,\n VoiceDesignOptions,\n VoiceListOptions,\n} from '../types';\nimport { AIError, AuthenticationError, RateLimitError } from '../types';\nimport { emitUsage } from './usage';\n\n/** Raw video-job object shape, following the OpenAI `videos` API. */\ninterface CompatVideoObject {\n id: string;\n status: string;\n model: string;\n progress?: number;\n seconds?: string;\n size?: string;\n error?: { code?: string; message?: string } | null;\n /** Some gateways (LiteLLM's Veo passthrough) include a direct download URL. */\n url?: string;\n}\n\nfunction ensureOpenAICompatVideoOptions(\n options: OpenAICompatVideoOptions,\n): OpenAICompatVideoOptions {\n if (!options.baseUrl?.trim()) {\n throw new ValidationError('openai-compat-video baseUrl is required', {\n provider: 'openai-compat-video',\n hint: 'Pass baseUrl like https://llm.happyvertical.com/v1 or set OPENAI_COMPAT_VIDEO_BASE_URL',\n });\n }\n\n return {\n ...options,\n baseUrl: options.baseUrl.trim().replace(/\\/+$/, ''),\n };\n}\n\nfunction parseSize(size: string | undefined): {\n width?: number;\n height?: number;\n} {\n const match = size?.match(/^(\\d+)x(\\d+)$/);\n if (!match) return {};\n return { width: Number(match[1]), height: Number(match[2]) };\n}\n\n/**\n * Map a gateway's raw status string to our status enum.\n *\n * LiteLLM (the primary target gateway) documents `'processing'` as its\n * mid-render status rather than OpenAI's own `'in_progress'`; both, plus\n * `'pending'`, map to `'running'`. Only an explicit `'failed'` maps to a\n * terminal failure — an unrecognized status is never assumed to be\n * terminal, since misreading a mid-render job as failed would cause a\n * caller to resubmit (and double-bill) a job that was actually still\n * rendering. Unrecognized values map to `'running'` with the raw string\n * preserved via `rawStatus` so callers can still see and log it.\n */\nfunction mapStatus(status: string): {\n status: VideoGenerationStatusResult['status'];\n rawStatus?: string;\n} {\n switch (status) {\n case 'queued':\n return { status: 'queued' };\n case 'in_progress':\n case 'processing':\n case 'pending':\n return { status: 'running' };\n case 'completed':\n return { status: 'succeeded' };\n case 'failed':\n return { status: 'failed' };\n case 'cancelled':\n case 'canceled':\n return { status: 'cancelled' };\n default:\n return { status: 'running', rawStatus: status };\n }\n}\n\n/**\n * OpenAI-compatible video-generation provider for `/v1/videos`-shaped\n * gateways (LiteLLM, Sora-shaped providers).\n *\n * @example\n * ```typescript\n * import { getAI } from '@happyvertical/ai';\n *\n * const video = await getAI({\n * type: 'openai-compat-video',\n * baseUrl: 'https://llm.happyvertical.com/v1',\n * apiKey: process.env.OPENAI_COMPAT_VIDEO_API_KEY!,\n * });\n *\n * const job = await video.submitVideoGenerationJob({\n * prompt: 'A time-lapse of clouds over a mountain range',\n * durationSeconds: 8,\n * });\n * ```\n */\nexport class OpenAICompatVideoProvider implements AIInterface {\n private options: OpenAICompatVideoOptions;\n\n constructor(options: OpenAICompatVideoOptions) {\n this.options = normalizeBaseAIOptions(\n ensureOpenAICompatVideoOptions(options),\n );\n }\n\n private get baseUrl(): string {\n return this.options.baseUrl as string;\n }\n\n private async request<T>(\n path: string,\n options: {\n method?: 'GET' | 'POST';\n body?: unknown;\n query?: Record<string, string | number>;\n signal?: AbortSignal;\n timeout?: number;\n } = {},\n ): Promise<T> {\n const response = await this.rawRequest(path, options);\n const text = await response.text();\n return text ? (JSON.parse(text) as T) : ({} as T);\n }\n\n private async rawRequest(\n path: string,\n options: {\n method?: 'GET' | 'POST';\n body?: unknown;\n query?: Record<string, string | number>;\n signal?: AbortSignal;\n timeout?: number;\n /** When true, a 404 response is returned as-is instead of throwing. */\n allowNotFound?: boolean;\n } = {},\n ): Promise<Response> {\n if (options.signal?.aborted) {\n throw new AIError(\n 'openai-compat-video request aborted by caller',\n 'AI_ABORTED',\n 'openai-compat-video',\n );\n }\n\n const url = new URL(`${this.baseUrl}${path}`);\n for (const [key, value] of Object.entries(options.query ?? {})) {\n url.searchParams.set(key, String(value));\n }\n\n const controller = new AbortController();\n const timeoutMs = options.timeout ?? this.options.timeout;\n let timedOut = false;\n const timer = timeoutMs\n ? setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs)\n : undefined;\n // Mirrors `prepareRequestControls`'s pattern (packages/ai/src/shared/safety.ts):\n // addEventListener on an already-aborted signal never fires, so an\n // already-aborted `signal` must invoke the abort immediately instead of\n // only listening for a future 'abort' event.\n const onExternalAbort = () => controller.abort(options.signal?.reason);\n if (options.signal?.aborted) {\n onExternalAbort();\n } else {\n options.signal?.addEventListener('abort', onExternalAbort, {\n once: true,\n });\n }\n\n try {\n const headers: Record<string, string> = {\n ...(this.options.headers || {}),\n };\n if (this.options.apiKey) {\n headers.Authorization = `Bearer ${this.options.apiKey}`;\n }\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: options.method || 'GET',\n headers,\n body:\n options.body !== undefined\n ? JSON.stringify(options.body)\n : undefined,\n signal: controller.signal,\n });\n } catch (error) {\n // The fetch() call itself failed (network error, DNS failure,\n // timeout/abort) — wrap it into the AIError taxonomy so callers get\n // a consistent, retryable-vs-terminal-distinguishable error rather\n // than a raw TypeError/DOMException.\n if (timedOut) {\n throw new AIError(\n `openai-compat-video request to ${path} timed out after ${timeoutMs}ms`,\n 'AI_TIMEOUT',\n 'openai-compat-video',\n undefined,\n true,\n );\n }\n if (options.signal?.aborted) {\n throw new AIError(\n 'openai-compat-video request aborted by caller',\n 'AI_ABORTED',\n 'openai-compat-video',\n );\n }\n throw new AIError(\n `Network error calling openai-compat-video ${path}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n 'NETWORK_ERROR',\n 'openai-compat-video',\n undefined,\n true,\n );\n }\n\n if (!response.ok) {\n if (response.status === 404 && options.allowNotFound) {\n return response;\n }\n\n const errorText = await response.text();\n\n if (response.status === 401 || response.status === 403) {\n throw new AuthenticationError('openai-compat-video');\n }\n if (response.status === 429) {\n throw new RateLimitError(\n 'openai-compat-video',\n extractRetryAfterSeconds({ headers: response.headers }),\n );\n }\n\n throw new AIError(\n `openai-compat-video request to ${path} failed: ${response.status} ${errorText}`,\n 'OPENAI_COMPAT_VIDEO_REQUEST_FAILED',\n 'openai-compat-video',\n );\n }\n\n return response;\n } finally {\n if (timer) clearTimeout(timer);\n options.signal?.removeEventListener('abort', onExternalAbort);\n }\n }\n\n // ============================================================================\n // Video Generation Methods (Implemented)\n // ============================================================================\n\n /**\n * Submit an asynchronous video-generation job.\n *\n * `resolution` is forwarded as the gateway's `size` field only when it is\n * already in `WIDTHxHEIGHT` form (e.g. `'1280x720'`); other resolution\n * labels (`'720p'`) are not translated and the gateway's default size\n * applies instead — keeping this adapter thin rather than guessing a\n * pixel size per vendor. `negativePrompt`, `seed`, and `fps` are not part\n * of the documented OpenAI videos schema and are not forwarded.\n */\n async submitVideoGenerationJob(\n options: VideoGenerationOptions,\n ): Promise<VideoGenerationJob> {\n const startTime = Date.now();\n const model = options.model || this.options.defaultModel || 'sora-2';\n\n const body: Record<string, unknown> = {\n model,\n prompt: options.prompt,\n };\n if (options.durationSeconds !== undefined) {\n body.seconds = String(options.durationSeconds);\n }\n if (options.resolution?.includes('x')) {\n body.size = options.resolution;\n }\n const firstReference = options.referenceImages?.[0];\n if (firstReference) {\n body.input_reference = {\n image_url:\n typeof firstReference.image === 'string'\n ? firstReference.image\n : `data:${firstReference.mimeType || 'image/png'};base64,${firstReference.image.toString('base64')}`,\n };\n }\n\n const video = await this.request<CompatVideoObject>('/videos', {\n method: 'POST',\n body,\n signal: options.signal,\n timeout: options.timeout,\n });\n\n emitUsage(\n this.options,\n 'openai-compat-video',\n 'submitVideoGenerationJob',\n video.model || model,\n undefined,\n startTime,\n {\n ...(options.durationSeconds !== undefined\n ? { durationSeconds: String(options.durationSeconds) }\n : {}),\n ...(options.resolution ? { resolution: options.resolution } : {}),\n ...(options.aspectRatio ? { aspectRatio: options.aspectRatio } : {}),\n ...options.usageTags,\n },\n );\n\n return {\n jobId: video.id,\n provider: 'openai-compat-video',\n model: video.model || model,\n createdAt: new Date().toISOString(),\n };\n }\n\n /**\n * Poll the status of a video-generation job.\n *\n * The gateway's status response does not include the video bytes, so\n * `result` on a succeeded job carries only metadata (mimeType, duration,\n * dimensions) plus a `url` when the gateway happens to include one\n * directly. Call {@link fetchVideoGenerationResult} to download the bytes.\n */\n async getVideoGenerationJob(\n handle: VideoGenerationJob,\n ): Promise<VideoGenerationStatusResult> {\n const video = await this.request<CompatVideoObject>(\n `/videos/${handle.jobId}`,\n );\n return this.mapVideo(video);\n }\n\n /**\n * Fetch the result of a completed video-generation job, downloading the\n * rendered bytes from the gateway's content endpoint.\n *\n * @throws {AIError} When the job has not succeeded yet\n */\n async fetchVideoGenerationResult(\n handle: VideoGenerationJob,\n ): Promise<VideoGenerationResult> {\n const video = await this.request<CompatVideoObject>(\n `/videos/${handle.jobId}`,\n );\n const status = this.mapVideo(video);\n if (status.status !== 'succeeded') {\n throw new AIError(\n `openai-compat-video job ${handle.jobId} has not succeeded (status: ${status.status})`,\n 'VIDEO_JOB_NOT_READY',\n 'openai-compat-video',\n handle.model,\n );\n }\n\n if (video.url) {\n return status.result as VideoGenerationResult;\n }\n\n const response = await this.rawRequest(`/videos/${handle.jobId}/content`);\n const arrayBuffer = await response.arrayBuffer();\n const { width, height } = parseSize(video.size);\n\n return {\n data: Buffer.from(arrayBuffer),\n mimeType: response.headers.get('content-type') || 'video/mp4',\n durationSeconds: video.seconds ? Number(video.seconds) : undefined,\n width,\n height,\n };\n }\n\n /**\n * Cancel a video-generation job. Cancellation of an in-flight Sora-shaped\n * job is not part of the documented OpenAI `videos` API and is\n * gateway-dependent (e.g. LiteLLM's Veo passthrough may support it); this\n * attempts the conventional `/videos/{id}/cancel` REST call and surfaces\n * an {@link AIError} if the gateway rejects or does not implement it.\n */\n async cancelVideoGenerationJob(handle: VideoGenerationJob): Promise<void> {\n await this.rawRequest(`/videos/${handle.jobId}/cancel`, {\n method: 'POST',\n });\n }\n\n /**\n * Cheap auth-shaped check for video-generation access.\n *\n * This probes `GET /videos/{sentinel-id}` rather than listing videos:\n * LiteLLM (the primary target gateway) requires a `custom_llm_provider`\n * parameter on its `GET /v1/videos` list route that this generic adapter\n * has no context to supply, so a list-based check would fail with valid\n * credentials on the flagship gateway. A single-resource GET works\n * uniformly instead, classified as:\n * - `401`/`403` → invalid credentials, throws {@link AuthenticationError}\n * - `404` → authenticated successfully, the sentinel id just doesn't\n * exist — this is a **valid** result, not a failure\n * - `2xx` → valid (the sentinel id coincidentally exists)\n *\n * Callers on a hot path must cache the result themselves.\n */\n async validateVideoGenerationAccess(): Promise<boolean> {\n const sentinelId = 'have-ai-validate-video-access';\n const response = await this.rawRequest(`/videos/${sentinelId}`, {\n allowNotFound: true,\n });\n return response.ok || response.status === 404;\n }\n\n private mapVideo(video: CompatVideoObject): VideoGenerationStatusResult {\n const { status, rawStatus } = mapStatus(video.status);\n\n if (status === 'failed') {\n return {\n status: 'failed',\n error:\n video.error?.message ||\n `Video generation failed (status: ${video.status})`,\n };\n }\n\n if (status !== 'succeeded') {\n return { status, progress: video.progress, rawStatus };\n }\n\n const { width, height } = parseSize(video.size);\n const result: VideoGenerationResult = {\n url: video.url,\n mimeType: 'video/mp4',\n durationSeconds: video.seconds ? Number(video.seconds) : undefined,\n width,\n height,\n };\n\n return { status: 'succeeded', result };\n }\n\n async getModels(): Promise<AIModel[]> {\n return [\n {\n id: 'sora-2',\n name: 'Sora 2',\n description: 'OpenAI-compatible Sora 2 video-generation model',\n contextLength: 0,\n capabilities: ['video_generation'],\n supportsFunctions: false,\n supportsVision: true,\n },\n {\n id: 'sora-2-pro',\n name: 'Sora 2 Pro',\n description: 'OpenAI-compatible Sora 2 Pro video-generation model',\n contextLength: 0,\n capabilities: ['video_generation'],\n supportsFunctions: false,\n supportsVision: true,\n },\n ];\n }\n\n async getCapabilities(): Promise<AICapabilities> {\n return {\n chat: false,\n completion: false,\n embeddings: false,\n streaming: false,\n functions: false,\n vision: false,\n fineTuning: false,\n imageEmbeddings: false,\n imageGeneration: false,\n videoGeneration: true,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 0,\n supportedOperations: [\n 'submitVideoGenerationJob',\n 'getVideoGenerationJob',\n 'fetchVideoGenerationResult',\n 'cancelVideoGenerationJob',\n ],\n };\n }\n\n // ============================================================================\n // Text Generation / TTS Methods (Not Implemented - Use another provider)\n // ============================================================================\n\n async chat(\n _messages: AIMessage[],\n _options?: ChatOptions,\n ): Promise<AIResponse> {\n throw new AIError(\n 'Chat is not supported by the openai-compat-video provider. Use the openai or litellm provider for text generation.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async complete(\n _prompt: string,\n _options?: CompletionOptions,\n ): Promise<AIResponse> {\n throw new AIError(\n 'Complete is not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async message(_text: string, _options?: MessageOptions): Promise<string> {\n throw new AIError(\n 'Message is not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async embed(\n _text: string | string[],\n _options?: EmbeddingOptions,\n ): Promise<EmbeddingResponse> {\n throw new AIError(\n 'Embeddings are not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async embedImage(\n _image: string | Buffer,\n _options?: ImageEmbeddingOptions,\n ): Promise<EmbeddingResponse> {\n throw new AIError(\n 'Image embeddings are not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async describeImage(\n _image: string | Buffer,\n _prompt?: string,\n _options?: ImageDescriptionOptions,\n ): Promise<string> {\n throw new AIError(\n 'Image description is not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async generateImage(\n _prompt: string,\n _options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n throw new AIError(\n 'Image generation is not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n stream(\n _messages: AIMessage[],\n _options?: ChatOptions,\n ): AsyncIterable<string> {\n const error = new AIError(\n 'Chat streaming is not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n return {\n [Symbol.asyncIterator]: () => ({\n next: () => Promise.reject(error),\n }),\n };\n }\n\n async countTokens(_text: string): Promise<number> {\n throw new AIError(\n 'Token counting is not supported by the openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async synthesizeSpeech(\n _text: string,\n _options?: TTSOptions,\n ): Promise<TTSResponse> {\n throw new AIError(\n 'TTS is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n streamSpeech(_text: string, _options?: TTSOptions): AsyncIterable<Buffer> {\n const error = new AIError(\n 'TTS streaming is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n return {\n [Symbol.asyncIterator]: () => ({\n next: () => Promise.reject(error),\n }),\n };\n }\n\n async cloneVoice(_options: VoiceCloneOptions): Promise<Voice> {\n throw new AIError(\n 'Voice cloning is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async designVoice(_options: VoiceDesignOptions): Promise<Voice> {\n throw new AIError(\n 'Voice design is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n\n async getVoices(_options?: VoiceListOptions): Promise<Voice[]> {\n throw new AIError(\n 'Voice listing is not supported by the openai-compat-video provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'openai-compat-video',\n );\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as AIError } from "./types-FHs-KbGL.js";
1
+ import { t as AIError } from "./types-CINwUPDo.js";
2
2
  //#region src/shared/providers/qwen-tts.ts
3
3
  /**
4
4
  * Rate limiter for controlling request frequency
@@ -269,8 +269,24 @@ var Qwen3TTSProvider = class {
269
269
  async generateImage(_prompt, _options) {
270
270
  throw new AIError("Image generation is not supported by Qwen3-TTS provider.", "NOT_IMPLEMENTED", "qwen3-tts");
271
271
  }
272
- async *stream(_messages, _options) {
273
- throw new AIError("Chat streaming is not supported by Qwen3-TTS provider.", "NOT_IMPLEMENTED", "qwen3-tts");
272
+ async submitVideoGenerationJob(_options) {
273
+ throw new AIError("Video generation is not supported by Qwen3-TTS provider. Use gemini, byteplus-modelark, or openai-compat-video provider.", "NOT_IMPLEMENTED", "qwen3-tts");
274
+ }
275
+ async getVideoGenerationJob(_handle) {
276
+ throw new AIError("Video generation is not supported by Qwen3-TTS provider. Use gemini, byteplus-modelark, or openai-compat-video provider.", "NOT_IMPLEMENTED", "qwen3-tts");
277
+ }
278
+ async fetchVideoGenerationResult(_handle) {
279
+ throw new AIError("Video generation is not supported by Qwen3-TTS provider. Use gemini, byteplus-modelark, or openai-compat-video provider.", "NOT_IMPLEMENTED", "qwen3-tts");
280
+ }
281
+ async cancelVideoGenerationJob(_handle) {
282
+ throw new AIError("Video generation is not supported by Qwen3-TTS provider. Use gemini, byteplus-modelark, or openai-compat-video provider.", "NOT_IMPLEMENTED", "qwen3-tts");
283
+ }
284
+ async validateVideoGenerationAccess() {
285
+ throw new AIError("Video generation is not supported by Qwen3-TTS provider. Use gemini, byteplus-modelark, or openai-compat-video provider.", "NOT_IMPLEMENTED", "qwen3-tts");
286
+ }
287
+ stream(_messages, _options) {
288
+ const error = new AIError("Chat streaming is not supported by Qwen3-TTS provider.", "NOT_IMPLEMENTED", "qwen3-tts");
289
+ return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
274
290
  }
275
291
  async countTokens(_text) {
276
292
  throw new AIError("Token counting is not supported by Qwen3-TTS provider.", "NOT_IMPLEMENTED", "qwen3-tts");
@@ -313,6 +329,7 @@ var Qwen3TTSProvider = class {
313
329
  fineTuning: false,
314
330
  imageEmbeddings: false,
315
331
  imageGeneration: false,
332
+ videoGeneration: false,
316
333
  tts: true,
317
334
  voiceCloning: true,
318
335
  voiceDesign: true,
@@ -330,4 +347,4 @@ var Qwen3TTSProvider = class {
330
347
  //#endregion
331
348
  export { Qwen3TTSProvider };
332
349
 
333
- //# sourceMappingURL=qwen-tts-DRRbOrhA.js.map
350
+ //# sourceMappingURL=qwen-tts-BXjzY7DX.js.map