@happyvertical/ai 0.74.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.
Files changed (77) hide show
  1. package/AGENT.md +33 -0
  2. package/LICENSE +7 -0
  3. package/README.md +384 -0
  4. package/dist/chunks/anthropic-BRwbhwIl.js +463 -0
  5. package/dist/chunks/anthropic-BRwbhwIl.js.map +1 -0
  6. package/dist/chunks/bedrock-Cf1xUerN.js +808 -0
  7. package/dist/chunks/bedrock-Cf1xUerN.js.map +1 -0
  8. package/dist/chunks/bifrost-3mXtQsTj.js +233 -0
  9. package/dist/chunks/bifrost-3mXtQsTj.js.map +1 -0
  10. package/dist/chunks/claude-cli-BrHRfkry.js +603 -0
  11. package/dist/chunks/claude-cli-BrHRfkry.js.map +1 -0
  12. package/dist/chunks/gateway-admin-C4GFPbZF.js +359 -0
  13. package/dist/chunks/gateway-admin-C4GFPbZF.js.map +1 -0
  14. package/dist/chunks/gemini-BfpHXDIQ.js +662 -0
  15. package/dist/chunks/gemini-BfpHXDIQ.js.map +1 -0
  16. package/dist/chunks/huggingface-280qv9iv.js +366 -0
  17. package/dist/chunks/huggingface-280qv9iv.js.map +1 -0
  18. package/dist/chunks/index-BT4thAvS.js +934 -0
  19. package/dist/chunks/index-BT4thAvS.js.map +1 -0
  20. package/dist/chunks/litellm-DhPKa_Jz.js +220 -0
  21. package/dist/chunks/litellm-DhPKa_Jz.js.map +1 -0
  22. package/dist/chunks/ollama-Di1ldur0.js +851 -0
  23. package/dist/chunks/ollama-Di1ldur0.js.map +1 -0
  24. package/dist/chunks/openai-5snI2diE.js +749 -0
  25. package/dist/chunks/openai-5snI2diE.js.map +1 -0
  26. package/dist/chunks/qwen-tts-DgPgdXxG.js +365 -0
  27. package/dist/chunks/qwen-tts-DgPgdXxG.js.map +1 -0
  28. package/dist/chunks/usage-DMWiJ2oB.js +21 -0
  29. package/dist/chunks/usage-DMWiJ2oB.js.map +1 -0
  30. package/dist/cli/claude-context.d.ts +3 -0
  31. package/dist/cli/claude-context.d.ts.map +1 -0
  32. package/dist/cli/claude-context.js +21 -0
  33. package/dist/cli/claude-context.js.map +1 -0
  34. package/dist/index.d.ts +20 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +21 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/node/factory.d.ts +27 -0
  39. package/dist/node/factory.d.ts.map +1 -0
  40. package/dist/shared/client.d.ts +410 -0
  41. package/dist/shared/client.d.ts.map +1 -0
  42. package/dist/shared/factory.d.ts +83 -0
  43. package/dist/shared/factory.d.ts.map +1 -0
  44. package/dist/shared/message.d.ts +71 -0
  45. package/dist/shared/message.d.ts.map +1 -0
  46. package/dist/shared/providers/anthropic.d.ts +82 -0
  47. package/dist/shared/providers/anthropic.d.ts.map +1 -0
  48. package/dist/shared/providers/bedrock.d.ts +49 -0
  49. package/dist/shared/providers/bedrock.d.ts.map +1 -0
  50. package/dist/shared/providers/bifrost.d.ts +25 -0
  51. package/dist/shared/providers/bifrost.d.ts.map +1 -0
  52. package/dist/shared/providers/claude-cli.d.ts +139 -0
  53. package/dist/shared/providers/claude-cli.d.ts.map +1 -0
  54. package/dist/shared/providers/gateway-admin.d.ts +35 -0
  55. package/dist/shared/providers/gateway-admin.d.ts.map +1 -0
  56. package/dist/shared/providers/gemini.d.ts +116 -0
  57. package/dist/shared/providers/gemini.d.ts.map +1 -0
  58. package/dist/shared/providers/huggingface.d.ts +33 -0
  59. package/dist/shared/providers/huggingface.d.ts.map +1 -0
  60. package/dist/shared/providers/litellm.d.ts +25 -0
  61. package/dist/shared/providers/litellm.d.ts.map +1 -0
  62. package/dist/shared/providers/ollama.d.ts +47 -0
  63. package/dist/shared/providers/ollama.d.ts.map +1 -0
  64. package/dist/shared/providers/openai.d.ts +272 -0
  65. package/dist/shared/providers/openai.d.ts.map +1 -0
  66. package/dist/shared/providers/qwen-tts.d.ts +85 -0
  67. package/dist/shared/providers/qwen-tts.d.ts.map +1 -0
  68. package/dist/shared/providers/usage.d.ts +14 -0
  69. package/dist/shared/providers/usage.d.ts.map +1 -0
  70. package/dist/shared/rate-limit.d.ts +13 -0
  71. package/dist/shared/rate-limit.d.ts.map +1 -0
  72. package/dist/shared/thread.d.ts +104 -0
  73. package/dist/shared/thread.d.ts.map +1 -0
  74. package/dist/shared/types.d.ts +1779 -0
  75. package/dist/shared/types.d.ts.map +1 -0
  76. package/metadata.json +35 -0
  77. package/package.json +62 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BT4thAvS.js","sources":["../../src/shared/types.ts","../../src/shared/client.ts","../../src/shared/rate-limit.ts","../../src/shared/factory.ts","../../src/shared/message.ts","../../src/shared/thread.ts","../../src/index.ts"],"sourcesContent":["/**\n * Core types and interfaces for the AI library\n */\n\n/**\n * Thinking level options for Gemini 3 models\n * Controls the amount of internal reasoning the model performs\n */\nexport type GeminiThinkingLevel = 'minimal' | 'low' | 'medium' | 'high';\n\n/**\n * Supported AI provider types\n */\nexport const AI_PROVIDER_TYPES = [\n 'openai',\n 'litellm',\n 'bifrost',\n 'ollama',\n 'gemini',\n 'anthropic',\n 'huggingface',\n 'bedrock',\n 'claude-cli',\n 'qwen3-tts',\n] as const;\n\n/**\n * Supported AI provider type union\n */\nexport type AIProviderType = (typeof AI_PROVIDER_TYPES)[number];\n\n/**\n * Text content part for multimodal messages\n */\nexport interface TextContentPart {\n type: 'text';\n text: string;\n}\n\n/**\n * Image content part for vision-capable models\n */\nexport interface ImageContentPart {\n type: 'image_url';\n image_url: {\n /** Image URL (http/https) or base64 data URL */\n url: string;\n /** Image detail level for processing */\n detail?: 'auto' | 'low' | 'high';\n };\n}\n\n/**\n * Union type for all content parts in multimodal messages\n */\nexport type ContentPart = TextContentPart | ImageContentPart;\n\n/**\n * Extract text content from a message content field.\n *\n * Handles both simple string content and multimodal content arrays,\n * extracting only the text parts and concatenating them.\n *\n * @param content - The message content (string or ContentPart array)\n * @returns The extracted text content\n */\nexport function extractTextContent(content: string | ContentPart[]): string {\n if (typeof content === 'string') {\n return content;\n }\n // Extract text from content parts\n return content\n .filter((part): part is TextContentPart => part.type === 'text')\n .map((part) => part.text)\n .join('\\n');\n}\n\n/**\n * AI message structure for chat interactions\n *\n * Supports both simple string content and multimodal content arrays\n * for vision-capable models.\n *\n * @example Simple text message\n * ```typescript\n * const message: AIMessage = {\n * role: 'user',\n * content: 'Hello, how are you?'\n * };\n * ```\n *\n * @example Multimodal message with image\n * ```typescript\n * const message: AIMessage = {\n * role: 'user',\n * content: [\n * { type: 'text', text: 'What is in this image?' },\n * { type: 'image_url', image_url: { url: 'data:image/png;base64,...' } }\n * ]\n * };\n * ```\n */\nexport interface AIMessage {\n /**\n * Role of the message sender\n */\n role: 'system' | 'user' | 'assistant' | 'function' | 'tool';\n\n /**\n * Content of the message.\n *\n * Can be a simple string for text-only messages, or an array of content parts\n * for multimodal messages (e.g., text + images for vision models).\n */\n content: string | ContentPart[];\n\n /**\n * Optional name for the message sender\n */\n name?: string;\n\n /**\n * Optional tool calls\n */\n tool_calls?: Array<{\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n }>;\n}\n\n/**\n * Options for chat completion requests\n */\nexport interface ChatOptions {\n /**\n * Model to use for completion\n */\n model?: string;\n\n /**\n * Maximum number of tokens to generate\n */\n maxTokens?: number;\n\n /**\n * Sampling temperature (0-2)\n */\n temperature?: number;\n\n /**\n * Top-p sampling parameter\n */\n topP?: number;\n\n /**\n * Number of completions to generate\n */\n n?: number;\n\n /**\n * Sequences that stop generation\n */\n stop?: string | string[];\n\n /**\n * Whether to stream the response\n */\n stream?: boolean;\n\n /**\n * Penalty for frequency of tokens\n */\n frequencyPenalty?: number;\n\n /**\n * Penalty for presence of tokens\n */\n presencePenalty?: number;\n\n /**\n * User identifier for monitoring\n */\n user?: string;\n\n /**\n * Available tools/functions\n */\n tools?: AITool[];\n\n /**\n * Tool choice behavior\n */\n toolChoice?:\n | 'auto'\n | 'none'\n | { type: 'function'; function: { name: string } };\n\n /**\n * Response format specification\n */\n responseFormat?: { type: 'text' | 'json_object' };\n\n /**\n * Random seed for deterministic results\n */\n seed?: number;\n\n /**\n * Callback for streaming responses\n */\n onProgress?: (chunk: string) => void;\n\n /**\n * Thinking level for providers that expose reasoning controls.\n * Gemini 3 models use named levels:\n * - 'minimal': No thinking for most queries (Gemini 3 Flash only)\n * - 'low': Minimizes latency and cost, good for simple tasks\n * - 'medium': Balanced thinking for most tasks (Gemini 3 Flash only)\n * - 'high': Maximizes reasoning depth (default for Gemini 3)\n *\n * Ollama also accepts `false` to explicitly disable visible/internal thinking\n * for models that support it.\n */\n thinkingLevel?: GeminiThinkingLevel | false;\n\n /**\n * Whether to include the model's internal thoughts in the response\n * Only applicable for Gemini 3 models with thinking enabled\n */\n includeThoughts?: boolean;\n\n /**\n * Custom tags to attach to the usage event for this call.\n * Merged over any global `usageTags` from provider options.\n */\n usageTags?: Record<string, string>;\n}\n\n/**\n * Options for text completion requests (non-chat models)\n */\nexport interface CompletionOptions {\n /**\n * Model to use for completion\n */\n model?: string;\n\n /**\n * Maximum number of tokens to generate\n */\n maxTokens?: number;\n\n /**\n * Sampling temperature\n */\n temperature?: number;\n\n /**\n * Top-p sampling parameter\n */\n topP?: number;\n\n /**\n * Number of completions to generate\n */\n n?: number;\n\n /**\n * Sequences that stop generation\n */\n stop?: string | string[];\n\n /**\n * Whether to stream the response\n */\n stream?: boolean;\n\n /**\n * Callback for streaming responses\n */\n onProgress?: (chunk: string) => void;\n\n /**\n * Custom tags to attach to the usage event for this call.\n * Merged over any global `usageTags` from provider options.\n */\n usageTags?: Record<string, string>;\n}\n\n/**\n * Options for embedding generation\n */\nexport interface EmbeddingOptions {\n /**\n * Model to use for embeddings\n */\n model?: string;\n\n /**\n * User identifier for monitoring\n */\n user?: string;\n\n /**\n * Encoding format for embeddings\n */\n encodingFormat?: 'float' | 'base64';\n\n /**\n * Number of dimensions for the embedding\n */\n dimensions?: number;\n\n /**\n * Custom tags to attach to the usage event for this call.\n * Merged over any global `usageTags` from provider options.\n */\n usageTags?: Record<string, string>;\n}\n\n/**\n * Options for image embedding generation\n */\nexport interface ImageEmbeddingOptions {\n /**\n * Model to use for image embeddings\n * - Gemini: 'multimodalembedding@001' or similar\n * - OpenAI: Uses describe-then-embed with text-embedding-3-small\n */\n model?: string;\n\n /**\n * Number of dimensions for the embedding output\n */\n dimensions?: number;\n\n /**\n * User identifier for monitoring\n */\n user?: string;\n}\n\n/**\n * Options for image description generation\n */\nexport interface ImageDescriptionOptions {\n /**\n * Model to use for image description\n * - OpenAI: defaults to 'gpt-4o'\n * - Gemini: defaults to 'gemini-2.5-flash'\n */\n model?: string;\n\n /**\n * Maximum tokens for the description\n */\n maxTokens?: number;\n\n /**\n * Detail level for image processing (OpenAI-specific)\n */\n detail?: 'auto' | 'low' | 'high';\n}\n\n/**\n * Options for image generation\n */\nexport interface ImageGenerationOptions {\n /**\n * Model to use for image generation\n * - OpenAI: 'dall-e-3' (default), 'dall-e-2'\n * - Gemini: 'imagen-3.0-generate-002' (default)\n */\n model?: string;\n\n /**\n * Input image for image-to-image workflows\n * Can be a URL (http/https), base64 data URL, or Buffer\n */\n imageInput?: string | Buffer;\n\n /**\n * Aspect ratio for the generated image\n * e.g., \"16:9\", \"1:1\", \"4:3\", \"3:4\", \"9:16\"\n */\n aspectRatio?: string;\n\n /**\n * Output format for the generated image\n * - 'buffer': Returns raw image bytes (default)\n * - 'base64': Returns base64-encoded string\n * - 'url': Returns temporary URL (provider-dependent, may expire)\n */\n outputFormat?: 'buffer' | 'base64' | 'url';\n\n /**\n * Number of images to generate (provider-dependent)\n * - DALL-E 3: Only 1 supported\n * - Imagen 3: 1-4 supported\n */\n n?: number;\n\n /**\n * Image style (OpenAI DALL-E 3 specific)\n */\n style?: 'vivid' | 'natural';\n\n /**\n * Quality setting\n * - OpenAI: 'standard' | 'hd'\n */\n quality?: string;\n\n /**\n * Size specification (for providers that use fixed sizes)\n * - OpenAI DALL-E 3: '1024x1024' | '1792x1024' | '1024x1792'\n */\n size?: string;\n}\n\n/**\n * Response from image generation\n */\nexport interface ImageGenerationResponse {\n /**\n * Generated image(s) - format depends on outputFormat option\n */\n images: Array<{\n /**\n * Image data - Buffer for 'buffer' format, string for 'base64' or 'url'\n */\n data: Buffer | string;\n /**\n * MIME type of the image (e.g., 'image/png', 'image/jpeg')\n */\n mimeType: string;\n /**\n * Revised prompt (if provider modified the original)\n */\n revisedPrompt?: string;\n }>;\n\n /**\n * Model used for generation\n */\n model?: string;\n}\n\n/**\n * Options for simple message requests (convenience method)\n * This provides a simpler interface than chat() for single-turn interactions\n */\nexport interface MessageOptions {\n /**\n * Model to use for completion\n */\n model?: string;\n\n /**\n * Role of the message sender (default: 'user')\n */\n role?: 'user' | 'assistant' | 'system';\n\n /**\n * Conversation history (previous messages)\n */\n history?: AIMessage[];\n\n /**\n * Maximum number of tokens to generate\n */\n maxTokens?: number;\n\n /**\n * Sampling temperature (0-2)\n */\n temperature?: number;\n\n /**\n * Top-p sampling parameter\n */\n topP?: number;\n\n /**\n * Sequences that stop generation\n */\n stop?: string | string[];\n\n /**\n * Whether to stream the response\n */\n stream?: boolean;\n\n /**\n * Penalty for frequency of tokens\n */\n frequencyPenalty?: number;\n\n /**\n * Penalty for presence of tokens\n */\n presencePenalty?: number;\n\n /**\n * Response format specification\n */\n responseFormat?: { type: 'text' | 'json_object' };\n\n /**\n * Random seed for deterministic results\n */\n seed?: number;\n\n /**\n * Available tools/functions\n */\n tools?: AITool[];\n\n /**\n * Tool choice behavior\n */\n toolChoice?:\n | 'auto'\n | 'none'\n | { type: 'function'; function: { name: string } };\n\n /**\n * Callback for streaming responses\n */\n onProgress?: (chunk: string) => void;\n\n /**\n * Custom tags to attach to the usage event for this call.\n * Merged over any global `usageTags` from provider options.\n */\n usageTags?: Record<string, string>;\n}\n\n/**\n * Tool/function definition for AI models\n */\nexport interface AITool {\n /**\n * Type of tool\n */\n type: 'function';\n\n /**\n * Function definition\n */\n function: {\n /**\n * Function name\n */\n name: string;\n\n /**\n * Function description\n */\n description?: string;\n\n /**\n * JSON schema for function parameters\n */\n parameters?: Record<string, any>;\n };\n}\n\n/**\n * Model information structure\n */\nexport interface AIModel {\n /**\n * Model identifier\n */\n id: string;\n\n /**\n * Human-readable model name\n */\n name: string;\n\n /**\n * Model description\n */\n description?: string;\n\n /**\n * Maximum context length in tokens\n */\n contextLength: number;\n\n /**\n * Supported capabilities\n */\n capabilities: string[];\n\n /**\n * Whether the model supports function calling\n */\n supportsFunctions: boolean;\n\n /**\n * Whether the model supports vision/multimodal input\n */\n supportsVision: boolean;\n\n /**\n * Cost per input token (if available)\n */\n inputCostPer1k?: number;\n\n /**\n * Cost per output token (if available)\n */\n outputCostPer1k?: number;\n}\n\n/**\n * Budget configuration for AI gateway admin operations.\n *\n * Providers translate this to their native field names:\n * - Bifrost: `budget.max_limit` / `budget.reset_duration`\n * - LiteLLM: `max_budget` / `budget_duration`\n */\nexport interface AIAdminBudget {\n /**\n * Maximum spend in USD.\n */\n maxLimit?: number;\n\n /**\n * Reset duration such as `1h`, `1d`, `30d`, or `1M`.\n */\n resetDuration?: string;\n\n /**\n * Bifrost only: reset at calendar boundaries for day/week/month/year periods.\n */\n calendarAligned?: boolean;\n}\n\n/**\n * Rate-limit configuration for AI gateway admin operations.\n */\nexport interface AIAdminRateLimit {\n /**\n * Provider-agnostic token limit.\n *\n * Bifrost maps this to `token_max_limit`; LiteLLM maps it to `tpm_limit`.\n */\n tokenMaxLimit?: number;\n\n /**\n * Bifrost token reset duration such as `1h`.\n */\n tokenResetDuration?: string;\n\n /**\n * Provider-agnostic request limit.\n *\n * Bifrost maps this to `request_max_limit`; LiteLLM maps it to `rpm_limit`.\n */\n requestMaxLimit?: number;\n\n /**\n * Bifrost request reset duration such as `1m`.\n */\n requestResetDuration?: string;\n\n /**\n * LiteLLM tokens-per-minute limit. Overrides `tokenMaxLimit` for LiteLLM.\n */\n tpmLimit?: number;\n\n /**\n * LiteLLM requests-per-minute limit. Overrides `requestMaxLimit` for LiteLLM.\n */\n rpmLimit?: number;\n}\n\n/**\n * Bifrost virtual-key routing configuration.\n */\nexport interface AIAdminProviderConfig {\n /**\n * Provider identifier such as `openai` or `anthropic`.\n */\n provider: string;\n\n /**\n * Routing weight for this provider.\n */\n weight?: number;\n\n /**\n * Models this virtual key may use for the provider.\n */\n allowedModels?: string[];\n\n /**\n * Bifrost provider key IDs that this virtual key may use.\n */\n keyIds?: string[];\n}\n\n/**\n * Options for creating a gateway-scoped project.\n *\n * In Bifrost, projects are implemented as governance teams, optionally attached\n * to a customer via `tenantId`. In LiteLLM, projects are implemented as teams.\n */\nexport interface CreateAIProjectOptions {\n /**\n * Stable project ID. LiteLLM requires one; if omitted, a slug is derived from\n * the tenant and project name. Bifrost generates its own team ID.\n */\n id?: string;\n\n /**\n * Human-readable project name.\n */\n name: string;\n\n /**\n * Tenant/customer identifier to attach the project to where supported.\n */\n tenantId?: string;\n\n /**\n * Human-readable description. Stored in metadata for providers that support it.\n */\n description?: string;\n\n /**\n * Models the project may access.\n */\n models?: string[];\n\n /**\n * Shared project budget.\n */\n budget?: AIAdminBudget;\n\n /**\n * Shared project rate limits.\n */\n rateLimit?: AIAdminRateLimit;\n\n /**\n * Provider-specific metadata.\n */\n metadata?: Record<string, unknown>;\n\n /**\n * Whether the project should be blocked on creation where supported.\n */\n isBlocked?: boolean;\n\n /**\n * Provider-specific request body overrides.\n */\n raw?: Record<string, unknown>;\n}\n\n/**\n * Gateway project descriptor returned by admin providers.\n */\nexport interface AIAdminProject {\n /**\n * Provider project ID.\n */\n id: string;\n\n /**\n * Human-readable project name.\n */\n name: string;\n\n /**\n * Tenant/customer identifier where available.\n */\n tenantId?: string;\n\n /**\n * Provider budget ID where available.\n */\n budgetId?: string;\n\n /**\n * Admin provider that created this project.\n */\n provider: string;\n\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Options for creating a gateway virtual key.\n */\nexport interface CreateAIVirtualKeyOptions {\n /**\n * Human-readable key name or alias.\n */\n name: string;\n\n /**\n * Human-readable key description.\n */\n description?: string;\n\n /**\n * Project/team ID to attach the key to.\n */\n projectId?: string;\n\n /**\n * Tenant/customer ID to attach the key to when no project is supplied, or to\n * record in LiteLLM metadata.\n */\n tenantId?: string;\n\n /**\n * Optional end-user ID associated with the key.\n */\n userId?: string;\n\n /**\n * Models this key may access.\n */\n models?: string[];\n\n /**\n * Bifrost provider routing configuration.\n */\n providerConfigs?: AIAdminProviderConfig[];\n\n /**\n * Key-level budget.\n */\n budget?: AIAdminBudget;\n\n /**\n * Key-level rate limits.\n */\n rateLimit?: AIAdminRateLimit;\n\n /**\n * Key duration such as `30d`, `1h`, or `permanent` where supported.\n */\n duration?: string;\n\n /**\n * Provider-specific metadata.\n */\n metadata?: Record<string, unknown>;\n\n /**\n * Bifrost provider API key IDs this virtual key may use. Use `[\"*\"]` to allow\n * all configured provider keys.\n */\n keyIds?: string[];\n\n /**\n * Whether the key should be active on creation.\n */\n isActive?: boolean;\n\n /**\n * LiteLLM model aliases for this key.\n */\n aliases?: Record<string, string>;\n\n /**\n * LiteLLM key-specific config.\n */\n config?: Record<string, unknown>;\n\n /**\n * LiteLLM key-specific permissions.\n */\n permissions?: Record<string, unknown>;\n\n /**\n * Provider-specific request body overrides.\n */\n raw?: Record<string, unknown>;\n}\n\n/**\n * Gateway virtual key descriptor returned by admin providers.\n */\nexport interface AIVirtualKey {\n /**\n * Provider key ID, when returned separately from the key value.\n */\n id?: string;\n\n /**\n * Human-readable key name or alias.\n */\n name?: string;\n\n /**\n * Newly generated key value. Some provider list/detail responses may only\n * expose a masked value.\n */\n key?: string;\n\n /**\n * Masked key value or key name, when provided.\n */\n maskedKey?: string;\n\n /**\n * Attached project/team ID.\n */\n projectId?: string;\n\n /**\n * Attached tenant/customer ID.\n */\n tenantId?: string;\n\n /**\n * Expiration timestamp where supported.\n */\n expiresAt?: string;\n\n /**\n * Admin provider that created this key.\n */\n provider: string;\n\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Admin operations exposed by gateway providers that support provisioning.\n */\nexport interface AIAdminInterface {\n /**\n * Create a project/team for a tenant.\n */\n createProject(options: CreateAIProjectOptions): Promise<AIAdminProject>;\n\n /**\n * Create a virtual key, optionally attached to a project or tenant.\n */\n createVirtualKey(options: CreateAIVirtualKeyOptions): Promise<AIVirtualKey>;\n}\n\n/**\n * AI provider capabilities\n */\nexport interface AICapabilities {\n /**\n * Whether the provider supports chat completions\n */\n chat: boolean;\n\n /**\n * Whether the provider supports text completions\n */\n completion: boolean;\n\n /**\n * Whether the provider supports embeddings\n */\n embeddings: boolean;\n\n /**\n * Whether the provider supports streaming\n */\n streaming: boolean;\n\n /**\n * Whether the provider supports function calling\n */\n functions: boolean;\n\n /**\n * Whether the provider supports vision/multimodal\n */\n vision: boolean;\n\n /**\n * Whether the provider supports fine-tuning\n */\n fineTuning: boolean;\n\n /**\n * Whether the provider supports image embeddings\n */\n imageEmbeddings: boolean;\n\n /**\n * Whether the provider supports image generation\n */\n imageGeneration: boolean;\n\n /**\n * Whether the provider supports text-to-speech synthesis\n */\n tts: boolean;\n\n /**\n * Whether the provider supports voice cloning from samples\n */\n voiceCloning: boolean;\n\n /**\n * Whether the provider supports voice design via description\n */\n voiceDesign: boolean;\n\n /**\n * Maximum context length supported\n */\n maxContextLength: number;\n\n /**\n * Supported operations\n */\n supportedOperations: string[];\n}\n\n/**\n * Token usage information\n */\nexport interface TokenUsage {\n /**\n * Number of prompt tokens\n */\n promptTokens: number;\n\n /**\n * Number of completion tokens\n */\n completionTokens: number;\n\n /**\n * Total tokens used\n */\n totalTokens: number;\n}\n\n/**\n * Usage event emitted via the `onUsage` callback after each API call.\n * Provides token usage, timing, and context for tracking and analytics.\n *\n * @example\n * ```typescript\n * const ai = await getAI({\n * type: 'openai',\n * apiKey: '...',\n * onUsage: (event) => {\n * console.log(`[${event.provider}/${event.model}] ${event.operation}: ${event.usage?.totalTokens} tokens in ${event.duration}ms`);\n * },\n * });\n * ```\n */\nexport interface UsageEvent {\n /** Provider that handled the request (e.g. 'openai', 'anthropic', 'gemini') */\n provider: string;\n\n /** Model that was used (e.g. 'gpt-4o', 'claude-3-5-sonnet-20241022') */\n model: string;\n\n /** Operation type that generated this usage */\n operation:\n | 'chat'\n | 'complete'\n | 'message'\n | 'embed'\n | 'embedImage'\n | 'describeImage'\n | 'generateImage'\n | 'stream';\n\n /** Token usage breakdown, if available from the provider */\n usage?: TokenUsage;\n\n /** Wall-clock duration of the API call in milliseconds */\n duration: number;\n\n /** Timestamp when the call completed */\n timestamp: Date;\n\n /** Custom tags from global `usageTags` and per-call `usageTags`, merged */\n tags?: Record<string, string>;\n}\n\n/**\n * AI response structure\n */\nexport interface AIResponse {\n /**\n * Generated content\n */\n content: string;\n\n /**\n * Token usage information\n */\n usage?: TokenUsage;\n\n /**\n * Model used for generation\n */\n model?: string;\n\n /**\n * Finish reason\n */\n finishReason?: 'stop' | 'length' | 'tool_calls' | 'content_filter';\n\n /**\n * Tool calls made by the model\n */\n toolCalls?: Array<{\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n }>;\n}\n\n/**\n * Embedding response structure\n */\nexport interface EmbeddingResponse {\n /**\n * Generated embeddings\n */\n embeddings: number[][];\n\n /**\n * Token usage information\n */\n usage?: TokenUsage;\n\n /**\n * Model used for embeddings\n */\n model?: string;\n}\n\n/**\n * Core AI interface that all providers must implement\n */\nexport interface AIInterface {\n /**\n * Optional admin surface for gateway providers that support provisioning.\n */\n admin?: AIAdminInterface;\n\n /**\n * Generate a chat completion from a sequence of messages.\n *\n * @param messages - Conversation messages (system, user, assistant, tool roles)\n * @param options - Chat options including model, temperature, tools, etc.\n * @returns Promise resolving to the model's response with content and usage info\n * @throws {AIError} When the request fails\n * @throws {AuthenticationError} When credentials are invalid\n * @throws {RateLimitError} When the provider's rate limit is exceeded\n */\n chat(messages: AIMessage[], options?: ChatOptions): Promise<AIResponse>;\n\n /**\n * Generate a text completion from a prompt string (non-chat interface).\n *\n * @param prompt - The text prompt to complete\n * @param options - Completion options including model, temperature, etc.\n * @returns Promise resolving to the model's response\n * @throws {AIError} When the request fails\n */\n complete(prompt: string, options?: CompletionOptions): Promise<AIResponse>;\n\n /**\n * Simple message interface for single-turn interactions\n *\n * This is a convenience method that wraps chat() for simpler use cases.\n * It accepts a text string and optional configuration, returning just\n * the response content as a string.\n *\n * Supports conversation history via the `history` option for multi-turn\n * conversations while maintaining a simple API.\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 single-turn usage\n * const response = await ai.message('Hello, how are you?');\n *\n * // With options\n * const response = await ai.message('Analyze this data', {\n * model: 'gpt-4o',\n * responseFormat: { type: 'json_object' },\n * maxTokens: 1000\n * });\n *\n * // With conversation history\n * const response = await ai.message('What did I ask before?', {\n * history: [\n * { role: 'user', content: 'Hello' },\n * { role: 'assistant', content: 'Hi there!' }\n * ]\n * });\n * ```\n */\n message(text: string, options?: MessageOptions): Promise<string>;\n\n /**\n * Generate vector embeddings for one or more text inputs.\n *\n * @param text - A single string or array of strings to embed\n * @param options - Embedding options including model and dimensions\n * @returns Promise resolving to embedding vectors and usage info\n * @throws {AIError} When embeddings are not supported by this provider or request fails\n */\n embed(\n text: string | string[],\n options?: EmbeddingOptions,\n ): Promise<EmbeddingResponse>;\n\n /**\n * Generate embeddings for an image\n *\n * Implementation varies by provider:\n * - Gemini: Uses native multimodal embeddings\n * - OpenAI: Uses describe-then-embed pattern (describeImage → embed)\n * - Others: Throws NOT_IMPLEMENTED\n *\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 * @throws {AIError} When embeddings are not supported or request fails\n *\n * @example\n * ```typescript\n * // From URL\n * const embedding = await ai.embedImage('https://example.com/image.jpg');\n *\n * // From Buffer\n * const buffer = fs.readFileSync('image.png');\n * const embedding = await ai.embedImage(buffer);\n *\n * // With options\n * const embedding = await ai.embedImage(imageUrl, { dimensions: 768 });\n * ```\n */\n embedImage(\n image: string | Buffer,\n options?: ImageEmbeddingOptions,\n ): Promise<EmbeddingResponse>;\n\n /**\n * Generate a text description of an image\n *\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 * @throws {AIError} When vision is not supported or request fails\n *\n * @example\n * ```typescript\n * // Default description for search indexing\n * const description = await ai.describeImage('https://example.com/image.jpg');\n *\n * // Custom prompt\n * const description = await ai.describeImage(imageBuffer, 'What product is shown?');\n *\n * // With options\n * const description = await ai.describeImage(imageUrl, undefined, {\n * model: 'gpt-4o',\n * maxTokens: 500,\n * detail: 'high'\n * });\n * ```\n */\n describeImage(\n image: string | Buffer,\n prompt?: string,\n options?: ImageDescriptionOptions,\n ): Promise<string>;\n\n /**\n * Generate an image from a text prompt\n *\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 * @throws {AIError} When image generation is not supported or request fails\n *\n * @example\n * ```typescript\n * // Basic generation (returns Buffer by default)\n * const result = await ai.generateImage('A sunset over mountains');\n * fs.writeFileSync('image.png', result.images[0].data);\n *\n * // With options\n * const result = await ai.generateImage('A cat wearing a hat', {\n * outputFormat: 'base64',\n * size: '1024x1024',\n * style: 'vivid'\n * });\n * ```\n */\n generateImage(\n prompt: string,\n options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse>;\n\n /**\n * Stream a chat completion, yielding text chunks as they arrive.\n *\n * @param messages - Conversation messages\n * @param options - Chat options including model, temperature, etc.\n * @returns Async iterable of string chunks\n * @throws {AIError} When the request fails\n */\n stream(messages: AIMessage[], options?: ChatOptions): AsyncIterable<string>;\n\n /**\n * Estimate or calculate the token count for a text string.\n *\n * @param text - The text to tokenize\n * @returns Promise resolving to the token count\n */\n countTokens(text: string): Promise<number>;\n\n /**\n * List models available from this provider.\n *\n * @returns Promise resolving to an array of model descriptors\n */\n getModels(): Promise<AIModel[]>;\n\n /**\n * Query the capabilities supported by this provider (chat, embeddings, vision, TTS, etc.).\n *\n * @returns Promise resolving to a capabilities descriptor\n */\n getCapabilities(): Promise<AICapabilities>;\n\n // ============================================================================\n // Text-to-Speech Methods\n // ============================================================================\n\n /**\n * Synthesize speech from text\n *\n * @param text - The text to synthesize into speech\n * @param options - Optional configuration for TTS synthesis\n * @returns Promise resolving to audio data with metadata\n * @throws {AIError} When TTS is not supported or request fails\n *\n * @example\n * ```typescript\n * // Basic synthesis\n * const result = await ai.synthesizeSpeech('Hello, world!');\n * fs.writeFileSync('speech.wav', result.audio);\n *\n * // With options\n * const result = await ai.synthesizeSpeech('News broadcast text', {\n * voice: 'news-anchor-1',\n * speed: 1.1,\n * includeWordTimings: true\n * });\n * console.log(`Duration: ${result.duration}s`);\n * ```\n */\n synthesizeSpeech(text: string, options?: TTSOptions): Promise<TTSResponse>;\n\n /**\n * Stream speech synthesis for real-time playback\n *\n * @param text - The text to synthesize into speech\n * @param options - Optional configuration for TTS synthesis\n * @returns AsyncIterable of audio chunks\n * @throws {AIError} When TTS streaming is not supported or request fails\n *\n * @example\n * ```typescript\n * const chunks: Buffer[] = [];\n * for await (const chunk of ai.streamSpeech('Long text...')) {\n * chunks.push(chunk);\n * // Or stream directly to audio output\n * }\n * ```\n */\n streamSpeech(text: string, options?: TTSOptions): AsyncIterable<Buffer>;\n\n /**\n * Clone a voice from an audio sample\n *\n * Creates a new voice profile from a 3+ second audio sample.\n * The cloned voice can be used in subsequent synthesizeSpeech calls.\n *\n * @param options - Voice cloning configuration including audio sample\n * @returns Promise resolving to the cloned voice profile\n * @throws {AIError} When voice cloning is not supported or request fails\n *\n * @example\n * ```typescript\n * const sample = fs.readFileSync('voice-sample.wav');\n * const voice = await ai.cloneVoice({\n * sampleAudio: sample,\n * name: 'News Anchor Voice',\n * language: 'en-US'\n * });\n *\n * // Use the cloned voice\n * const speech = await ai.synthesizeSpeech('Breaking news...', {\n * voice: voice.id\n * });\n * ```\n */\n cloneVoice(options: VoiceCloneOptions): Promise<Voice>;\n\n /**\n * Design a voice using natural language description\n *\n * Creates a new voice profile from a text description of the desired voice.\n * The designed voice can be used in subsequent synthesizeSpeech calls.\n *\n * @param options - Voice design configuration including description\n * @returns Promise resolving to the designed voice profile\n * @throws {AIError} When voice design is not supported or request fails\n *\n * @example\n * ```typescript\n * const voice = await ai.designVoice({\n * description: 'warm female voice, slight British accent, professional news anchor',\n * language: 'en-US',\n * gender: 'female'\n * });\n *\n * // Use the designed voice\n * const speech = await ai.synthesizeSpeech('Good evening...', {\n * voice: voice.id\n * });\n * ```\n */\n designVoice(options: VoiceDesignOptions): Promise<Voice>;\n\n /**\n * List available voices for TTS synthesis\n *\n * @param options - Optional filters for the voice list\n * @returns Promise resolving to array of available voices\n * @throws {AIError} When TTS is not supported or request fails\n *\n * @example\n * ```typescript\n * // List all voices\n * const voices = await ai.getVoices();\n *\n * // Filter by language\n * const englishVoices = await ai.getVoices({ language: 'en' });\n *\n * // Include cloned voices\n * const allVoices = await ai.getVoices({ includeCloned: true });\n * ```\n */\n getVoices(options?: VoiceListOptions): Promise<Voice[]>;\n}\n\n/**\n * Shared rate-limit configuration for AI providers.\n *\n * The pacing wrapper activates only when one of the pacing fields\n * (`enabled`, `key`, `cooldownMs`, `initialDelayMs`, `maxAttempts`) is set.\n *\n * `qwen3-tts` also uses `requestsPerMinute` and `maxConcurrent` from this\n * object for its local token bucket limiter.\n */\nexport interface AIRateLimitOptions {\n /**\n * Enable shared in-process request pacing for this client.\n */\n enabled?: boolean;\n\n /**\n * Shared budget key used to coordinate pacing across multiple clients.\n * If omitted, a provider-scoped key is derived from the configured credentials.\n */\n key?: string;\n\n /**\n * Minimum delay in milliseconds between successful calls sharing the same key.\n */\n cooldownMs?: number;\n\n /**\n * Fallback delay in milliseconds before retrying a rate-limited call when\n * the provider does not return a `Retry-After` hint.\n */\n initialDelayMs?: number;\n\n /**\n * Maximum attempts for retryable rate-limit failures, including the first call.\n */\n maxAttempts?: number;\n\n /**\n * Qwen3-TTS only: maximum requests per minute for its local token bucket.\n */\n requestsPerMinute?: number;\n\n /**\n * Qwen3-TTS only: maximum concurrent requests allowed by its local limiter.\n */\n maxConcurrent?: number;\n}\n\n/**\n * Base configuration options for all providers\n */\nexport interface BaseAIOptions {\n /**\n * API timeout in milliseconds\n */\n timeout?: number;\n\n /**\n * Maximum number of retries\n */\n maxRetries?: number;\n\n /**\n * Custom headers\n */\n headers?: Record<string, string>;\n\n /**\n * Default model to use\n */\n defaultModel?: string;\n\n /**\n * Callback invoked after each API call with usage details.\n * Use this to track token consumption, costs, and performance across providers.\n *\n * Errors thrown inside this callback are silently caught and will not\n * affect the API call result.\n *\n * @param event - Usage event with provider, model, operation, tokens, and timing\n */\n onUsage?: (event: UsageEvent) => void;\n\n /**\n * Global tags to include in every usage event.\n * Per-call `usageTags` on `ChatOptions` / `EmbeddingOptions` / etc.\n * will be merged on top of these.\n */\n usageTags?: Record<string, string>;\n\n /**\n * Optional shared pacing / retry configuration.\n */\n rateLimit?: AIRateLimitOptions;\n}\n\n/**\n * OpenAI provider options\n */\nexport interface OpenAIOptions extends BaseAIOptions {\n type?: 'openai';\n apiKey?: string;\n baseUrl?: string;\n organization?: string;\n}\n\n/**\n * LiteLLM provider options\n *\n * LiteLLM exposes an OpenAI-compatible API surface and requires a custom\n * base URL such as `https://llm.happyvertical.com/v1`.\n */\nexport interface LiteLLMOptions extends BaseAIOptions {\n type: 'litellm';\n apiKey?: string;\n baseUrl?: string;\n organization?: string;\n adminApiKey?: string;\n adminBaseUrl?: string;\n adminUrl?: string;\n adminHeaders?: Record<string, string>;\n}\n\n/**\n * Bifrost provider options.\n *\n * Bifrost exposes OpenAI-compatible inference through endpoints such as\n * `/openai` and `/v1`, plus governance admin endpoints at `/api/governance/*`.\n */\nexport interface BifrostOptions extends BaseAIOptions {\n type: 'bifrost';\n apiKey?: string;\n baseUrl?: string;\n organization?: string;\n /**\n * Optional virtual key for admin routes. Bifrost OSS admin APIs typically use\n * username/password Basic auth instead; use `adminUser` / `adminPassword`\n * when governance auth is enabled without enterprise bearer-token support.\n */\n adminApiKey?: string;\n /**\n * Admin API root. Alias: `adminUrl`.\n */\n adminBaseUrl?: string;\n /**\n * Admin API root. Kept as a friendly alias for env vars such as\n * `BIFROST_ADMIN_URL`.\n */\n adminUrl?: string;\n /**\n * Bifrost admin username for HTTP Basic auth.\n */\n adminUser?: string;\n /**\n * Bifrost admin username for HTTP Basic auth.\n */\n adminUsername?: string;\n /**\n * Bifrost admin password for HTTP Basic auth.\n */\n adminPassword?: string;\n adminHeaders?: Record<string, string>;\n}\n\n/**\n * Ollama provider options\n *\n * Ollama defaults to the local host at `http://localhost:11434` and can also\n * target remote hosts such as `https://ollama.com/api` when paired with an\n * API key.\n */\nexport interface OllamaOptions extends BaseAIOptions {\n type: 'ollama';\n apiKey?: string;\n baseUrl?: string;\n /**\n * Default keep-alive duration for model requests, for example `5m` or `0`.\n */\n keepAlive?: string | number;\n}\n\n/**\n * Gemini provider options\n */\nexport interface GeminiOptions extends BaseAIOptions {\n type: 'gemini';\n apiKey?: string;\n baseUrl?: string;\n projectId?: string;\n location?: string;\n /**\n * Thinking level for Gemini 3 models (gemini-3-flash-preview, gemini-3-pro)\n * Controls internal reasoning depth:\n * - 'minimal': No thinking for most queries (Gemini 3 Flash only)\n * - 'low': Minimizes latency and cost, good for simple tasks\n * - 'medium': Balanced thinking for most tasks (Gemini 3 Flash only)\n * - 'high': Maximizes reasoning depth (default for Gemini 3)\n *\n * Note: Only works with Gemini 3 models. Gemini 2.5 uses thinkingBudget instead.\n */\n thinkingLevel?: GeminiThinkingLevel;\n}\n\n/**\n * Anthropic provider options\n */\nexport interface AnthropicOptions extends BaseAIOptions {\n type: 'anthropic';\n apiKey?: string;\n baseUrl?: string;\n anthropicVersion?: string;\n}\n\n/**\n * Hugging Face provider options\n */\nexport interface HuggingFaceOptions extends BaseAIOptions {\n type: 'huggingface';\n apiToken?: string;\n endpoint?: string;\n model?: string;\n useCache?: boolean;\n waitForModel?: boolean;\n}\n\n/**\n * AWS Bedrock provider options\n */\nexport interface BedrockOptions extends BaseAIOptions {\n type: 'bedrock';\n region?: string;\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n };\n endpoint?: string;\n}\n\n/**\n * Claude CLI provider options\n * Uses the local Claude Code CLI instead of API keys\n */\nexport interface ClaudeCliOptions extends BaseAIOptions {\n type: 'claude-cli';\n /**\n * Optional custom path to claude binary\n * If not specified, will search in PATH\n */\n cliPath?: string;\n}\n\n/**\n * Qwen3-TTS provider options\n * Uses Qwen3-TTS for text-to-speech synthesis\n *\n * TTS is co-located with ComfyUI for GPU sharing efficiency.\n */\nexport interface Qwen3TTSOptions extends BaseAIOptions {\n type: 'qwen3-tts';\n\n /**\n * TTS service endpoint URL\n * e.g., 'http://localhost:8880' or 'http://qwen-tts:8000'\n */\n endpoint?: string;\n\n /**\n * Default model variant\n * - 'qwen3-tts-1.7b': Higher quality (4.54GB VRAM)\n * - 'qwen3-tts-0.6b': Faster, lower VRAM (2.52GB)\n */\n defaultModel?: 'qwen3-tts-1.7b' | 'qwen3-tts-0.6b';\n\n /**\n * Default voice ID to use for synthesis\n */\n defaultVoice?: string;\n\n /**\n * Default language for synthesis\n */\n defaultLanguage?: string;\n\n /**\n * Rate limiting configuration for the local TTS adapter.\n * Reuses `BaseAIOptions.rateLimit` and reads `requestsPerMinute` / `maxConcurrent`.\n */\n rateLimit?: AIRateLimitOptions;\n}\n\n/**\n * Union type for all provider options\n */\nexport type GetAIOptions =\n | OpenAIOptions\n | LiteLLMOptions\n | BifrostOptions\n | OllamaOptions\n | GeminiOptions\n | AnthropicOptions\n | HuggingFaceOptions\n | BedrockOptions\n | ClaudeCliOptions\n | Qwen3TTSOptions;\n\n/**\n * Base error class for all AI operations.\n * Provider-specific errors are mapped to subclasses for structured error handling.\n *\n * @param message - Human-readable error description\n * @param code - Machine-readable error code (e.g., 'AUTH_ERROR', 'RATE_LIMIT')\n * @param provider - Provider that raised the error (e.g., 'openai', 'anthropic')\n * @param model - Model involved in the error, if applicable\n */\nexport class AIError extends Error {\n constructor(\n message: string,\n public code: string,\n public provider?: string,\n public model?: string,\n public retryable: boolean = false,\n ) {\n super(message);\n this.name = 'AIError';\n }\n}\n\n/**\n * Thrown when API key or credentials are invalid or missing.\n *\n * @param provider - Provider that rejected authentication\n */\nexport class AuthenticationError extends AIError {\n constructor(provider?: string) {\n super('Authentication failed', 'AUTH_ERROR', provider, undefined, false);\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Thrown when the provider's rate limit has been exceeded.\n *\n * @param provider - Provider that enforced the rate limit\n * @param retryAfter - Seconds to wait before retrying, if provided by the API\n */\nexport class RateLimitError extends AIError {\n public retryAfter?: number;\n\n constructor(provider?: string, retryAfter?: number) {\n super(\n `Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ''}`,\n 'RATE_LIMIT',\n provider,\n undefined,\n true,\n );\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Thrown when the requested model does not exist or is not available.\n *\n * @param model - The model identifier that was not found\n * @param provider - Provider that was queried\n */\nexport class ModelNotFoundError extends AIError {\n constructor(model: string, provider?: string) {\n super(\n `Model not found: ${model}`,\n 'MODEL_NOT_FOUND',\n provider,\n model,\n false,\n );\n this.name = 'ModelNotFoundError';\n }\n}\n\n/**\n * Thrown when the input exceeds the model's maximum context window.\n *\n * @param provider - Provider that reported the error\n * @param model - Model whose context limit was exceeded\n */\nexport class ContextLengthError extends AIError {\n constructor(provider?: string, model?: string) {\n super(\n 'Input exceeds maximum context length',\n 'CONTEXT_LENGTH_EXCEEDED',\n provider,\n model,\n false,\n );\n this.name = 'ContextLengthError';\n }\n}\n\n/**\n * Thrown when content is blocked by the provider's safety/content filters.\n *\n * @param provider - Provider that filtered the content\n * @param model - Model that triggered the filter\n */\nexport class ContentFilterError extends AIError {\n constructor(provider?: string, model?: string) {\n super(\n 'Content filtered by safety systems',\n 'CONTENT_FILTERED',\n provider,\n model,\n false,\n );\n this.name = 'ContentFilterError';\n }\n}\n\n// ============================================================================\n// Text-to-Speech (TTS) Types\n// ============================================================================\n\n/**\n * Options for text-to-speech synthesis\n */\nexport interface TTSOptions {\n /**\n * TTS model to use (e.g., 'qwen3-tts-1.7b', 'qwen3-tts-0.6b')\n */\n model?: string;\n\n /**\n * Voice ID or profile reference to use for synthesis\n */\n voice?: string;\n\n /**\n * ISO language code (e.g., 'en-US', 'zh-CN')\n * Supported: Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian\n */\n language?: string;\n\n /**\n * Speech rate multiplier (0.5 - 2.0, default: 1.0)\n */\n speed?: number;\n\n /**\n * Pitch adjustment in semitones (-20 to 20, default: 0)\n */\n pitch?: number;\n\n /**\n * Output audio format\n */\n outputFormat?: 'wav' | 'mp3' | 'ogg';\n\n /**\n * Whether to stream the audio output\n */\n stream?: boolean;\n\n /**\n * Whether to include word-level timing information for lip-sync\n */\n includeWordTimings?: boolean;\n}\n\n/**\n * Options for voice cloning from audio samples\n */\nexport interface VoiceCloneOptions {\n /**\n * Model to use for voice cloning\n */\n model?: string;\n\n /**\n * Audio sample for cloning (3+ seconds recommended)\n * Can be a Buffer or base64-encoded string\n */\n sampleAudio: Buffer | string;\n\n /**\n * MIME type of the sample audio (e.g., 'audio/wav', 'audio/mp3')\n */\n sampleMimeType?: string;\n\n /**\n * Name for the cloned voice profile\n */\n name?: string;\n\n /**\n * Description of the voice\n */\n description?: string;\n\n /**\n * Language of the voice sample\n */\n language?: string;\n}\n\n/**\n * Options for voice design via natural language description\n */\nexport interface VoiceDesignOptions {\n /**\n * Model to use for voice design\n */\n model?: string;\n\n /**\n * Natural language description of the desired voice\n * e.g., \"warm female voice with slight British accent, professional news anchor tone\"\n */\n description: string;\n\n /**\n * Primary language for the voice\n */\n language?: string;\n\n /**\n * Target gender for the voice\n */\n gender?: 'male' | 'female' | 'neutral';\n}\n\n/**\n * Word timing information for lip-sync alignment\n */\nexport interface WordTiming {\n /**\n * The word or phoneme\n */\n word: string;\n\n /**\n * Start time in seconds\n */\n start: number;\n\n /**\n * End time in seconds\n */\n end: number;\n}\n\n/**\n * Response from text-to-speech synthesis\n */\nexport interface TTSResponse {\n /**\n * Generated audio data\n */\n audio: Buffer;\n\n /**\n * MIME type of the audio (e.g., 'audio/wav', 'audio/mp3')\n */\n mimeType: string;\n\n /**\n * Duration of the audio in seconds\n */\n duration: number;\n\n /**\n * Word-level timing information for lip-sync (if requested)\n */\n wordTimings?: WordTiming[];\n\n /**\n * Model used for generation\n */\n model?: string;\n\n /**\n * Sample rate in Hz (e.g., 22050, 44100)\n */\n sampleRate?: number;\n}\n\n/**\n * Voice profile information\n */\nexport interface Voice {\n /**\n * Unique identifier for the voice\n */\n id: string;\n\n /**\n * Human-readable name for the voice\n */\n name: string;\n\n /**\n * Primary language of the voice (ISO code)\n */\n language: string;\n\n /**\n * Gender of the voice\n */\n gender?: 'male' | 'female' | 'neutral';\n\n /**\n * Description of the voice characteristics\n */\n description?: string;\n\n /**\n * Whether this is a cloned voice\n */\n isCloned?: boolean;\n\n /**\n * Whether this was designed via natural language\n */\n isDesigned?: boolean;\n\n /**\n * URL to a sample of this voice (if available)\n */\n sampleUrl?: string;\n\n /**\n * Provider-specific voice data/embedding\n */\n voiceData?: Record<string, any>;\n}\n\n/**\n * Options for listing available voices\n */\nexport interface VoiceListOptions {\n /**\n * Filter by language\n */\n language?: string;\n\n /**\n * Filter by gender\n */\n gender?: 'male' | 'female' | 'neutral';\n\n /**\n * Include cloned voices\n */\n includeCloned?: boolean;\n\n /**\n * Include designed voices\n */\n includeDesigned?: boolean;\n}\n","import { ApiError, ValidationError } from '@happyvertical/utils';\nimport OpenAI from 'openai';\n\nimport type { AIMessageOptions } from './message';\nimport type { AIProviderType, AIRateLimitOptions } from './types';\nimport { AI_PROVIDER_TYPES } from './types';\n\n/**\n * Common options for AI client configuration\n */\nexport interface AIClientOptions {\n /**\n * Type of AI client (e.g., 'openai')\n */\n type?: AIProviderType | string;\n\n /**\n * Response format for AI completions\n */\n responseFormat?: string;\n\n /**\n * API key for authentication\n */\n apiKey?: string;\n\n /**\n * Base URL for API requests\n */\n baseUrl?: string;\n\n /**\n * Admin API key for gateway providers that support provisioning.\n */\n adminApiKey?: string;\n\n /**\n * Admin base URL for gateway providers when it differs from `baseUrl`.\n */\n adminBaseUrl?: string;\n\n /**\n * Alias for adminBaseUrl.\n */\n adminUrl?: string;\n\n /**\n * Admin username for providers that use HTTP Basic auth.\n */\n adminUser?: string;\n\n /**\n * Admin username for providers that use HTTP Basic auth.\n */\n adminUsername?: string;\n\n /**\n * Admin password for providers that use HTTP Basic auth.\n */\n adminPassword?: string;\n\n /**\n * Custom admin headers for gateway providers.\n */\n adminHeaders?: Record<string, string>;\n\n /**\n * Optional shared pacing / retry configuration for getAI().\n */\n rateLimit?: AIRateLimitOptions;\n}\n\n/**\n * Interface defining required methods for AI clients\n */\nexport interface AIClientInterface {\n /**\n * Configuration options for this client\n */\n options: AIClientOptions;\n\n /**\n * Sends a message to the AI and gets a response\n *\n * @param text - Message text\n * @param options - Message options\n * @returns Promise resolving to the AI response\n */\n message(text: string, options: AIMessageOptions): Promise<unknown>;\n\n /**\n * Gets a text completion from the AI\n *\n * @param text - Input text for completion\n * @param options - Completion options\n * @returns Promise resolving to the completion result\n */\n textCompletion(text: string, options: AIMessageOptions): Promise<unknown>;\n}\n\n/**\n * Type guard to check if options are for OpenAI client\n *\n * @param options - Options to check\n * @returns True if options are valid for OpenAI client\n */\nfunction isOpenAIClientOptions(\n options: AIClientOptions,\n): options is OpenAIClientOptions {\n return options.type === 'openai' && 'apiKey' in options && !!options.apiKey;\n}\n\n/**\n * Type guard to check if value is an AI client instance\n *\n * @param value - Value to check\n * @returns True if value is an AI client instance\n */\nfunction isAIClientInstance(value: any): value is AIClient {\n return value instanceof AIClient;\n}\n\n/**\n * Options for AI text completion requests\n */\nexport interface AITextCompletionOptions {\n /**\n * Model identifier to use\n */\n model?: string;\n\n /**\n * Timeout in milliseconds\n */\n timeout?: number;\n\n /**\n * Role of the message sender\n */\n role?: OpenAI.Chat.ChatCompletionRole;\n\n /**\n * Previous messages in the conversation\n */\n history?: OpenAI.Chat.ChatCompletionMessageParam[];\n\n /**\n * Name of the message sender\n */\n name?: string;\n\n /**\n * Penalty for token frequency\n */\n frequencyPenalty?: number;\n\n /**\n * Token bias adjustments\n */\n logitBias?: Record<string, number>;\n\n /**\n * Whether to return log probabilities\n */\n logprobs?: boolean;\n\n /**\n * Number of top log probabilities to return\n */\n topLogprobs?: number;\n\n /**\n * Maximum tokens to generate\n */\n maxTokens?: number;\n\n /**\n * Number of completions to generate\n */\n n?: number;\n\n /**\n * Penalty for token presence\n */\n presencePenalty?: number;\n\n /**\n * Format for the response\n */\n responseFormat?: { type: 'text' | 'json_object' };\n\n /**\n * Random seed for deterministic results\n */\n seed?: number;\n\n /**\n * Sequences that stop generation\n */\n stop?: string | Array<string>;\n\n /**\n * Whether to stream responses\n */\n stream?: boolean;\n\n /**\n * Sampling temperature\n */\n temperature?: number;\n\n /**\n * Top-p sampling parameter\n */\n topProbability?: number;\n\n /**\n * Available tools for the model\n */\n tools?: Array<any>; // todo: figure out generic solution - Array<OpenAI.Chat.ChatCompletionTool>;\n\n /**\n * Tool selection behavior\n */\n toolChoice?:\n | 'none'\n | 'auto'\n | { type: 'function'; function: { name: string } };\n\n /**\n * User identifier\n */\n user?: string;\n\n /**\n * Callback for handling streaming responses\n */\n onProgress?: (partialMessage: string) => void;\n}\n\n/**\n * Base class for AI clients\n * Provides a common interface for different AI service providers\n */\nexport class AIClient {\n /**\n * Configuration options for this client\n */\n public options: AIClientOptions;\n\n /**\n * Creates a new AIClient\n *\n * @param options - Client configuration options\n */\n constructor(options: AIClientOptions) {\n this.options = options;\n }\n\n /**\n * Sends a message to the AI\n * Base implementation returns a placeholder response\n *\n * @param text - Message text\n * @param options - Message options\n * @returns Promise resolving to a placeholder response\n */\n public async message(\n _text: string,\n _options: AITextCompletionOptions = { role: 'user' },\n ) {\n return 'not a real ai message, this is the base class!';\n }\n\n /**\n * Factory method to create appropriate AI client based on options\n *\n * @param options - Client configuration options\n * @returns Promise resolving to an initialized AI client\n * @throws Error if client type is invalid\n */\n public static async create(\n options: AIClientOptions | AIClient,\n ): Promise<AIClient | OpenAIClient> {\n // If an AI client instance is passed, return it directly\n if (isAIClientInstance(options)) {\n return options;\n }\n\n // Cast to options since we know it's not an instance\n const clientOptions = options as AIClientOptions;\n\n if (isOpenAIClientOptions(clientOptions)) {\n return OpenAIClient.create(clientOptions);\n }\n\n // Delegate to modern factory for non-OpenAI providers\n const providedType = (clientOptions as any).type;\n if (providedType && providedType !== 'openai') {\n const { getAI } = await import('./factory.js');\n return (await getAI(clientOptions as any)) as any;\n }\n\n // Provide specific error messages for common issues\n if (providedType === 'openai') {\n throw new ValidationError(\n 'OpenAI API key is required but missing or empty',\n {\n supportedTypes: [...AI_PROVIDER_TYPES],\n providedType,\n hint: 'Set OPENAI_API_KEY environment variable or pass apiKey in options',\n },\n );\n }\n\n throw new ValidationError('Invalid client type specified', {\n supportedTypes: [...AI_PROVIDER_TYPES],\n providedType,\n });\n }\n\n /**\n * Gets a text completion from the AI\n * In base class, delegates to message method\n *\n * @param text - Input text for completion\n * @param options - Completion options\n * @returns Promise resolving to the completion result\n */\n public textCompletion(\n text: string,\n options: AITextCompletionOptions = {\n role: 'user',\n },\n ) {\n return this.message(text, options);\n }\n}\n\n/**\n * Creates an OpenAI client instance\n *\n * @param options - OpenAI configuration options\n * @returns Promise resolving to an OpenAI client\n */\nexport async function getOpenAI(options: {\n apiKey?: string;\n baseUrl?: string;\n}) {\n return new OpenAI({\n apiKey: options.apiKey,\n baseURL: options.baseUrl,\n });\n}\n\n/**\n * Options specific to OpenAI text completion requests\n */\nexport interface OpenAITextCompletionOptions {\n /**\n * Model identifier to use\n */\n model?: string;\n\n /**\n * Timeout in milliseconds\n */\n timeout?: number;\n\n /**\n * Role of the message sender\n */\n role?: OpenAI.Chat.ChatCompletionRole;\n\n /**\n * Previous messages in the conversation\n */\n history?: Array<OpenAI.Chat.ChatCompletionMessageParam>;\n\n /**\n * Name of the message sender\n */\n name?: string;\n\n /**\n * Penalty for token frequency\n */\n frequencyPenalty?: number;\n\n /**\n * Token bias adjustments\n */\n logitBias?: Record<string, number>;\n\n /**\n * Whether to return log probabilities\n */\n logprobs?: boolean;\n\n /**\n * Number of top log probabilities to return\n */\n topLogprobs?: number;\n\n /**\n * Maximum tokens to generate\n */\n maxTokens?: number;\n\n /**\n * Number of completions to generate\n */\n n?: number;\n\n /**\n * Penalty for token presence\n */\n presencePenalty?: number;\n\n /**\n * Format for the response\n */\n responseFormat?: { type: 'text' | 'json_object' };\n\n /**\n * Random seed for deterministic results\n */\n seed?: number;\n\n /**\n * Sequences that stop generation\n */\n stop?: string | Array<string>;\n\n /**\n * Whether to stream responses\n */\n stream?: boolean;\n\n /**\n * Sampling temperature\n */\n temperature?: number;\n\n /**\n * Top-p sampling parameter\n */\n topProbability?: number;\n\n /**\n * Available tools for the model\n */\n tools?: Array<OpenAI.Chat.ChatCompletionTool>;\n\n /**\n * Tool selection behavior\n */\n toolChoice?:\n | 'none'\n | 'auto'\n | { type: 'function'; function: { name: string } };\n\n /**\n * User identifier\n */\n user?: string;\n\n /**\n * Callback for handling streaming responses\n */\n onProgress?: (partialMessage: string) => void;\n}\n\n/**\n * Configuration options specific to OpenAI client\n */\nexport interface OpenAIClientOptions extends AIClientOptions {\n /**\n * OpenAI API key\n */\n apiKey?: string;\n\n /**\n * OpenAI API base URL\n */\n baseUrl?: string;\n}\n\n/**\n * Client implementation for the OpenAI API\n */\nexport class OpenAIClient extends AIClient {\n /**\n * OpenAI client instance\n */\n protected openai!: OpenAI;\n\n /**\n * Configuration options for this client\n */\n public options: OpenAIClientOptions;\n\n /**\n * Creates a new OpenAIClient\n *\n * @param options - OpenAI client configuration options\n */\n constructor(options: OpenAIClientOptions) {\n super(options);\n this.options = options;\n }\n\n /**\n * Sends a message to OpenAI\n *\n * @param text - Message text\n * @param options - Message options\n * @returns Promise resolving to the OpenAI response\n */\n public async message(\n text: string,\n options: AIMessageOptions = { role: 'user' },\n ) {\n const response = await this.textCompletion(text, options);\n return response;\n }\n\n /**\n * Factory method to create and initialize an OpenAIClient\n *\n * @param options - OpenAI client configuration options\n * @returns Promise resolving to an initialized OpenAIClient\n */\n public static async create(\n options: OpenAIClientOptions,\n ): Promise<OpenAIClient> {\n const client = new OpenAIClient(options);\n await client.initialize();\n return client;\n }\n\n /**\n * Initializes the OpenAI client\n */\n protected async initialize() {\n this.openai = new OpenAI({\n apiKey: this.options.apiKey,\n baseURL: this.options.baseUrl,\n });\n }\n\n /**\n * Sends a text completion request to the OpenAI API\n *\n * @param message - The message to send\n * @param options - Configuration options for the completion request\n * @returns Promise resolving to the completion text\n * @throws Error if the OpenAI API response is invalid\n */\n public async textCompletion(\n message: string,\n options: OpenAITextCompletionOptions = {},\n ): Promise<string> {\n const {\n model = 'gpt-4o',\n role = 'user',\n history = [],\n name: _name,\n frequencyPenalty = 0,\n logitBias,\n logprobs = false,\n topLogprobs,\n maxTokens,\n n = 1,\n presencePenalty = 0,\n responseFormat,\n seed,\n stop,\n stream: _stream = false,\n temperature = 1,\n topProbability: topP = 1,\n tools,\n toolChoice,\n user,\n onProgress,\n } = options;\n\n const messages = [\n ...history,\n {\n role: role as OpenAI.Chat.ChatCompletionRole,\n content: message,\n } as OpenAI.Chat.ChatCompletionSystemMessageParam,\n ];\n\n if (onProgress) {\n const stream = await this.openai.chat.completions.create({\n model,\n messages,\n stream: true,\n frequency_penalty: frequencyPenalty,\n logit_bias: logitBias,\n logprobs,\n top_logprobs: topLogprobs,\n max_tokens: maxTokens,\n n,\n presence_penalty: presencePenalty,\n response_format: responseFormat,\n seed,\n stop,\n temperature,\n top_p: topP,\n tools,\n tool_choice: toolChoice,\n user,\n });\n\n let fullContent = '';\n for await (const chunk of stream) {\n const content = chunk.choices[0]?.delta?.content || '';\n fullContent += content;\n onProgress(content);\n }\n\n return fullContent;\n }\n const response = await this.openai.chat.completions.create({\n model,\n messages,\n frequency_penalty: frequencyPenalty,\n logit_bias: logitBias,\n logprobs,\n top_logprobs: topLogprobs,\n max_tokens: maxTokens,\n n,\n presence_penalty: presencePenalty,\n response_format: responseFormat,\n seed,\n stop,\n stream: false,\n temperature,\n top_p: topP,\n tools,\n tool_choice: toolChoice,\n user,\n });\n\n const choice = response.choices[0];\n if (!choice || !choice.message || !choice.message.content) {\n throw new ApiError('Invalid response from OpenAI API: Missing content', {\n model,\n responseId: response.id,\n choices: response.choices?.length || 0,\n hasChoice: !!choice,\n hasMessage: !!choice?.message,\n hasContent: !!choice?.message?.content,\n });\n }\n return choice.message.content;\n }\n}\n\n/**\n * Options for getting an AI client with type information\n */\ntype GetAIClientOptions = AIClientOptions & {\n type?: AIProviderType;\n};\n\n/**\n * Factory function to create and initialize an appropriate AI client\n * Delegates to the modern getAI() factory for all provider types\n *\n * @param options - Client configuration options\n * @returns Promise resolving to an initialized AI client\n * @throws Error if client type is invalid\n */\nexport async function getAIClient(\n options: GetAIClientOptions,\n): Promise<AIClient> {\n // Delegate to modern factory for all providers\n const { getAI } = await import('./factory.js');\n return (await getAI(options as any)) as any;\n}\n","import type { AIClientOptions } from './client';\nimport type { AIInterface, AIRateLimitOptions, GetAIOptions } from './types';\nimport { AIError, RateLimitError } from './types';\n\nconst RATE_LIMITED_METHODS = new Set<keyof AIInterface>([\n 'chat',\n 'complete',\n 'message',\n 'embed',\n 'embedImage',\n 'describeImage',\n 'generateImage',\n 'getModels',\n 'synthesizeSpeech',\n 'cloneVoice',\n 'designVoice',\n 'getVoices',\n]);\n\nconst MAX_BUDGET_COORDINATORS = 128;\nconst BUDGET_COORDINATOR_TTL_MS = 15 * 60 * 1000;\n\ninterface NormalizedRateLimitConfig {\n cooldownMs: number;\n initialDelayMs: number;\n key: string;\n maxAttempts: number;\n}\n\nclass BudgetCoordinator {\n private nextAvailableAt = 0;\n private pendingSchedules = 0;\n private tail: Promise<void> = Promise.resolve();\n private lastUsedAt = Date.now();\n\n touch(): void {\n this.lastUsedAt = Date.now();\n }\n\n schedule<T>(work: () => Promise<T>): Promise<T> {\n this.pendingSchedules += 1;\n this.touch();\n\n const run = this.tail.then(work, work);\n this.tail = run.then(\n () => undefined,\n () => undefined,\n );\n\n return run.finally(() => {\n this.pendingSchedules = Math.max(0, this.pendingSchedules - 1);\n this.touch();\n });\n }\n\n async waitUntilReady(): Promise<void> {\n this.touch();\n const delayMs = this.nextAvailableAt - Date.now();\n if (delayMs > 0) {\n await sleep(delayMs);\n }\n }\n\n delayFor(delayMs: number): void {\n this.touch();\n if (delayMs <= 0) {\n return;\n }\n\n this.nextAvailableAt = Math.max(this.nextAvailableAt, Date.now() + delayMs);\n }\n\n isEvictable(now: number): boolean {\n return this.pendingSchedules === 0 && now >= this.nextAvailableAt;\n }\n\n isExpired(now: number): boolean {\n return (\n this.isEvictable(now) &&\n now - this.lastUsedAt >= BUDGET_COORDINATOR_TTL_MS\n );\n }\n\n getLastUsedAt(): number {\n return this.lastUsedAt;\n }\n}\n\nconst budgetCoordinators = new Map<string, BudgetCoordinator>();\n\nfunction sleep(delayMs: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\nfunction pruneBudgetCoordinators(): void {\n const now = Date.now();\n\n for (const [key, coordinator] of budgetCoordinators.entries()) {\n if (coordinator.isExpired(now)) {\n budgetCoordinators.delete(key);\n }\n }\n\n while (budgetCoordinators.size > MAX_BUDGET_COORDINATORS) {\n const evictableEntries = [...budgetCoordinators.entries()]\n .filter(([, coordinator]) => coordinator.isEvictable(now))\n .sort(\n ([, leftCoordinator], [, rightCoordinator]) =>\n leftCoordinator.getLastUsedAt() - rightCoordinator.getLastUsedAt(),\n );\n\n if (evictableEntries.length === 0) {\n break;\n }\n\n budgetCoordinators.delete(evictableEntries[0][0]);\n }\n}\n\nfunction getBudgetCoordinator(key: string): BudgetCoordinator {\n pruneBudgetCoordinators();\n\n let coordinator = budgetCoordinators.get(key);\n if (!coordinator) {\n coordinator = new BudgetCoordinator();\n budgetCoordinators.set(key, coordinator);\n pruneBudgetCoordinators();\n }\n\n coordinator.touch();\n return coordinator;\n}\n\nfunction hasPacingConfig(rateLimit?: AIRateLimitOptions): boolean {\n if (!rateLimit || rateLimit.enabled === false) {\n return false;\n }\n\n return (\n rateLimit.enabled === true ||\n rateLimit.key !== undefined ||\n rateLimit.cooldownMs !== undefined ||\n rateLimit.initialDelayMs !== undefined ||\n rateLimit.maxAttempts !== undefined\n );\n}\n\nfunction normalizeNonNegativeInteger(\n value: number | undefined,\n fallback: number,\n): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return fallback;\n }\n\n return Math.max(0, Math.trunc(value));\n}\n\nfunction hashKey(value: string): string {\n let hash = 2166136261;\n\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 16777619);\n }\n\n return (hash >>> 0).toString(36);\n}\n\nfunction deriveBudgetKey(options: GetAIOptions | AIClientOptions): string {\n const type = (options as { type?: string }).type;\n const provider = typeof type === 'string' && type ? type : 'openai';\n\n const credentialLikeValues = [\n 'apiKey' in options ? options.apiKey : undefined,\n 'apiToken' in options ? options.apiToken : undefined,\n 'credentials' in options ? options.credentials?.accessKeyId : undefined,\n 'endpoint' in options ? options.endpoint : undefined,\n 'baseUrl' in options ? options.baseUrl : undefined,\n 'cliPath' in options ? options.cliPath : undefined,\n ];\n\n const seed = credentialLikeValues.find(\n (value): value is string => typeof value === 'string' && value.length > 0,\n );\n\n return seed ? `${provider}:${hashKey(seed)}` : `${provider}:default`;\n}\n\nfunction normalizeRateLimitConfig(\n options: GetAIOptions | AIClientOptions,\n): NormalizedRateLimitConfig | null {\n const rateLimit = 'rateLimit' in options ? options.rateLimit : undefined;\n\n if (!hasPacingConfig(rateLimit)) {\n return null;\n }\n\n return {\n cooldownMs: normalizeNonNegativeInteger(rateLimit?.cooldownMs, 0),\n initialDelayMs: normalizeNonNegativeInteger(\n rateLimit?.initialDelayMs,\n 5000,\n ),\n key: rateLimit?.key?.trim() || deriveBudgetKey(options),\n maxAttempts: Math.max(\n 1,\n normalizeNonNegativeInteger(rateLimit?.maxAttempts, 3),\n ),\n };\n}\n\nfunction getRetryDelayMs(\n error: RateLimitError,\n config: NormalizedRateLimitConfig,\n): number {\n const hintedDelayMs =\n typeof error.retryAfter === 'number' && Number.isFinite(error.retryAfter)\n ? Math.max(0, Math.ceil(error.retryAfter * 1000))\n : undefined;\n\n if (hintedDelayMs !== undefined) {\n return Math.max(config.cooldownMs, hintedDelayMs);\n }\n\n return Math.max(config.cooldownMs, config.initialDelayMs);\n}\n\nasync function invokeWithPacing<T>(\n execute: () => Promise<T>,\n coordinator: BudgetCoordinator,\n config: NormalizedRateLimitConfig,\n): Promise<T> {\n let attempt = 1;\n\n while (true) {\n await coordinator.waitUntilReady();\n\n try {\n const result = await execute();\n coordinator.delayFor(config.cooldownMs);\n return result;\n } catch (error) {\n if (error instanceof RateLimitError) {\n coordinator.delayFor(getRetryDelayMs(error, config));\n\n if (error.retryable && attempt < config.maxAttempts) {\n attempt += 1;\n continue;\n }\n }\n\n throw error;\n }\n }\n}\n\nexport function parseRetryAfterSeconds(\n retryAfter: number | string | null | undefined,\n): number | undefined {\n if (typeof retryAfter === 'number' && Number.isFinite(retryAfter)) {\n return Math.max(0, retryAfter);\n }\n\n if (typeof retryAfter !== 'string') {\n return undefined;\n }\n\n const trimmed = retryAfter.trim();\n if (!trimmed) {\n return undefined;\n }\n\n const seconds = Number.parseFloat(trimmed);\n if (!Number.isNaN(seconds)) {\n return Math.max(0, seconds);\n }\n\n const retryAt = Date.parse(trimmed);\n if (Number.isNaN(retryAt)) {\n return undefined;\n }\n\n return Math.max(0, Math.ceil((retryAt - Date.now()) / 1000));\n}\n\nfunction getHeaderValue(\n headers: unknown,\n headerName: string,\n): number | string | undefined {\n if (!headers || typeof headers !== 'object') {\n return undefined;\n }\n\n if ('get' in headers && typeof headers.get === 'function') {\n return headers.get(headerName) ?? headers.get(headerName.toLowerCase());\n }\n\n const objectHeaders = headers as Record<string, number | string | undefined>;\n return objectHeaders[headerName] ?? objectHeaders[headerName.toLowerCase()];\n}\n\nexport function extractRetryAfterSeconds(error: unknown): number | undefined {\n if (!error || typeof error !== 'object') {\n return undefined;\n }\n\n if ('retryAfter' in error) {\n const retryAfter = parseRetryAfterSeconds(\n (error as { retryAfter?: number | string | null }).retryAfter,\n );\n if (retryAfter !== undefined) {\n return retryAfter;\n }\n }\n\n if ('headers' in error) {\n const retryAfter = parseRetryAfterSeconds(\n getHeaderValue((error as { headers?: unknown }).headers, 'retry-after'),\n );\n if (retryAfter !== undefined) {\n return retryAfter;\n }\n }\n\n const message =\n 'message' in error && typeof error.message === 'string'\n ? error.message\n : '';\n\n const retryAfterMatch =\n message.match(/retry after\\s+(\\d+(?:\\.\\d+)?)\\s*s?/i) ??\n message.match(/retryDelay[^\\d]*(\\d+(?:\\.\\d+)?)s/i);\n\n return retryAfterMatch\n ? parseRetryAfterSeconds(retryAfterMatch[1])\n : undefined;\n}\n\nexport function createRateLimitedAI<T extends AIInterface>(\n client: T,\n options: GetAIOptions | AIClientOptions,\n): T {\n const config = normalizeRateLimitConfig(options);\n if (!config) {\n return client;\n }\n\n const wrappedMethods = new Map<PropertyKey, unknown>();\n\n return new Proxy(client, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver);\n\n if (typeof value !== 'function') {\n return value;\n }\n\n if (!RATE_LIMITED_METHODS.has(property as keyof AIInterface)) {\n if (!wrappedMethods.has(property)) {\n wrappedMethods.set(property, value.bind(target));\n }\n\n return wrappedMethods.get(property);\n }\n\n if (!wrappedMethods.has(property)) {\n wrappedMethods.set(property, (...args: unknown[]) => {\n const coordinator = getBudgetCoordinator(config.key);\n return coordinator.schedule(() =>\n invokeWithPacing(\n () => Reflect.apply(value, target, args) as Promise<unknown>,\n coordinator,\n config,\n ),\n );\n });\n }\n\n return wrappedMethods.get(property);\n },\n }) as T;\n}\n\nexport function __resetAIRateLimitStateForTests(): void {\n budgetCoordinators.clear();\n}\n\nexport function __getAIRateLimitStateForTests(): {\n count: number;\n maxBudgetCoordinators: number;\n ttlMs: number;\n} {\n return {\n count: budgetCoordinators.size,\n maxBudgetCoordinators: MAX_BUDGET_COORDINATORS,\n ttlMs: BUDGET_COORDINATOR_TTL_MS,\n };\n}\n\nexport function isRetryableAIError(error: unknown): error is AIError {\n return error instanceof AIError && error.retryable;\n}\n","/**\n * Universal factory functions for creating AI provider instances\n * Works in both browser and Node.js environments\n */\n\nimport { loadEnvConfig, ValidationError } from '@happyvertical/utils';\n\nimport type { AIClientOptions } from './client';\nimport { createRateLimitedAI } from './rate-limit';\nimport type {\n AIInterface,\n AIProviderType,\n AnthropicOptions,\n BedrockOptions,\n BifrostOptions,\n ClaudeCliOptions,\n GeminiOptions,\n GetAIOptions,\n HuggingFaceOptions,\n LiteLLMOptions,\n OllamaOptions,\n OpenAIOptions,\n Qwen3TTSOptions,\n} from './types';\nimport { AI_PROVIDER_TYPES } from './types';\n\n/**\n * Type guards for provider options\n */\n\n/**\n * Checks if the options are for OpenAI provider\n * @param options - The AI provider options to check\n * @returns True if options are for OpenAI provider (including default case)\n */\nfunction isOpenAIOptions(\n options: GetAIOptions | AIClientOptions,\n): options is OpenAIOptions {\n return !options.type || options.type === 'openai';\n}\n\n/**\n * Checks if the options are for LiteLLM provider\n * @param options - The AI provider options to check\n * @returns True if options are for LiteLLM provider\n */\nfunction isLiteLLMOptions(\n options: GetAIOptions | AIClientOptions,\n): options is LiteLLMOptions {\n return options.type === 'litellm';\n}\n\n/**\n * Checks if the options are for Bifrost provider\n * @param options - The AI provider options to check\n * @returns True if options are for Bifrost provider\n */\nfunction isBifrostOptions(\n options: GetAIOptions | AIClientOptions,\n): options is BifrostOptions {\n return options.type === 'bifrost';\n}\n\n/**\n * Checks if the options are for Ollama provider\n * @param options - The AI provider options to check\n * @returns True if options are for Ollama provider\n */\nfunction isOllamaOptions(\n options: GetAIOptions | AIClientOptions,\n): options is OllamaOptions {\n return options.type === 'ollama';\n}\n\n/**\n * Checks if the options are for Google Gemini provider\n * @param options - The AI provider options to check\n * @returns True if options are for Gemini provider\n */\nfunction isGeminiOptions(\n options: GetAIOptions | AIClientOptions,\n): options is GeminiOptions {\n return options.type === 'gemini';\n}\n\n/**\n * Checks if the options are for Anthropic Claude provider\n * @param options - The AI provider options to check\n * @returns True if options are for Anthropic provider\n */\nfunction isAnthropicOptions(\n options: GetAIOptions | AIClientOptions,\n): options is AnthropicOptions {\n return options.type === 'anthropic';\n}\n\n/**\n * Checks if the options are for Hugging Face provider\n * @param options - The AI provider options to check\n * @returns True if options are for Hugging Face provider\n */\nfunction isHuggingFaceOptions(\n options: GetAIOptions | AIClientOptions,\n): options is HuggingFaceOptions {\n return options.type === 'huggingface';\n}\n\n/**\n * Checks if the options are for AWS Bedrock provider\n * @param options - The AI provider options to check\n * @returns True if options are for Bedrock provider\n */\nfunction isBedrockOptions(\n options: GetAIOptions | AIClientOptions,\n): options is BedrockOptions {\n return options.type === 'bedrock';\n}\n\n/**\n * Checks if the options are for Claude CLI provider\n * @param options - The AI provider options to check\n * @returns True if options are for Claude CLI provider\n */\nfunction isClaudeCliOptions(\n options: GetAIOptions | AIClientOptions,\n): options is ClaudeCliOptions {\n return options.type === 'claude-cli';\n}\n\n/**\n * Checks if the options are for Qwen3-TTS provider\n * @param options - The AI provider options to check\n * @returns True if options are for Qwen3-TTS provider\n */\nfunction isQwen3TTSOptions(\n options: GetAIOptions | AIClientOptions,\n): options is Qwen3TTSOptions {\n return options.type === 'qwen3-tts';\n}\n\n/**\n * Creates an AI provider instance based on the provided options.\n * Universal version that works in both browser and Node.js environments.\n *\n * Supports environment variable configuration using the pattern:\n * - HAVE_AI_PROVIDER → provider type (string)\n * - HAVE_AI_MODEL → defaultModel (string)\n * - HAVE_AI_TIMEOUT → timeout (number)\n * - HAVE_AI_MAX_RETRIES → maxRetries (number)\n * - HAVE_AI_API_KEY → apiKey (string) - fallback if provider-specific key not set\n * - HAVE_AI_BASE_URL → baseUrl (string)\n *\n * User-provided options always take precedence over environment variables.\n *\n * Accepts both GetAIOptions (provider-specific options with literal types)\n * and AIClientOptions (legacy interface with generic string type) for\n * backward compatibility with existing code.\n *\n * @param options - Configuration options for the AI provider. Can be GetAIOptions or AIClientOptions.\n * @returns Promise resolving to an AI provider instance that implements the AIInterface\n * @throws {ValidationError} When the provider type is unsupported or invalid\n *\n * @example\n * ```typescript\n * // Create OpenAI client with explicit options\n * const openai = await getAI({\n * type: 'openai',\n * apiKey: process.env.OPENAI_API_KEY!,\n * defaultModel: 'gpt-4o'\n * });\n *\n * // Or use environment variables (HAVE_AI_PROVIDER=openai, HAVE_AI_API_KEY=sk-...)\n * const client = await getAI({});\n *\n * // Create Anthropic client\n * const anthropic = await getAI({\n * type: 'anthropic',\n * apiKey: process.env.ANTHROPIC_API_KEY!,\n * defaultModel: 'claude-3-5-sonnet-20241022'\n * });\n *\n * // Works with AIClientOptions (legacy interface)\n * const clientOptions: AIClientOptions = { type: 'openai', apiKey: '...' };\n * const legacy = await getAI(clientOptions);\n * ```\n */\nexport async function getAI(\n options: GetAIOptions | AIClientOptions = {},\n): Promise<AIInterface> {\n // Load environment variables with user options taking precedence\n options = loadEnvConfig(options as Record<string, any>, {\n packageName: 'ai',\n schema: {\n provider: 'string',\n type: 'string', // Alias for provider\n model: 'string',\n defaultModel: 'string',\n timeout: 'number',\n maxRetries: 'number',\n apiKey: 'string',\n baseUrl: 'string',\n adminApiKey: 'string',\n adminBaseUrl: 'string',\n adminUrl: 'string',\n adminUser: 'string',\n adminUsername: 'string',\n adminPassword: 'string',\n },\n }) as GetAIOptions;\n\n // Normalize 'provider' field to 'type' for consistency\n if ('provider' in options && !options.type) {\n (options as any).type = (options as any).provider;\n }\n\n // Normalize 'model' field to 'defaultModel' for consistency\n if ('model' in options && !options.defaultModel) {\n (options as any).defaultModel = (options as any).model;\n }\n\n let client: AIInterface;\n\n if (isOpenAIOptions(options)) {\n const { OpenAIProvider } = await import('./providers/openai.js');\n client = new OpenAIProvider(options);\n } else if (isLiteLLMOptions(options)) {\n const { LiteLLMProvider } = await import('./providers/litellm.js');\n client = new LiteLLMProvider(options);\n } else if (isBifrostOptions(options)) {\n const { BifrostProvider } = await import('./providers/bifrost.js');\n client = new BifrostProvider(options);\n } else if (isOllamaOptions(options)) {\n const { OllamaProvider } = await import('./providers/ollama.js');\n client = new OllamaProvider(options);\n } else if (isGeminiOptions(options)) {\n const { GeminiProvider } = await import('./providers/gemini.js');\n client = new GeminiProvider(options);\n } else if (isAnthropicOptions(options)) {\n const { AnthropicProvider } = await import('./providers/anthropic.js');\n client = new AnthropicProvider(options);\n } else if (isHuggingFaceOptions(options)) {\n const { HuggingFaceProvider } = await import('./providers/huggingface.js');\n client = new HuggingFaceProvider(options);\n } else if (isBedrockOptions(options)) {\n const { BedrockProvider } = await import('./providers/bedrock.js');\n client = new BedrockProvider(options);\n } else if (isClaudeCliOptions(options)) {\n const { ClaudeCliProvider } = await import('./providers/claude-cli.js');\n client = new ClaudeCliProvider(options);\n } else if (isQwen3TTSOptions(options)) {\n const { Qwen3TTSProvider } = await import('./providers/qwen-tts.js');\n client = new Qwen3TTSProvider(options);\n } else {\n throw new ValidationError('Unsupported AI provider type', {\n supportedTypes: [...AI_PROVIDER_TYPES],\n providedType: (options as any).type,\n });\n }\n\n return createRateLimitedAI(client, options);\n}\n\n/**\n * Browser-compatible auto-detection of AI provider based on available credentials.\n * Does not rely on process.env, making it suitable for browser environments.\n *\n * @param options - Configuration options that may contain provider-specific credentials\n * @returns Promise resolving to an AI provider instance based on detected credentials\n * @throws {ValidationError} When no provider can be detected from the provided options\n *\n * @example\n * ```typescript\n * // Auto-detect OpenAI from apiKey\n * const client1 = await getAIAuto({\n * apiKey: 'sk-...', // Detected as OpenAI\n * defaultModel: 'gpt-4o'\n * });\n *\n * // Auto-detect Hugging Face from apiToken\n * const client2 = await getAIAuto({\n * apiToken: 'hf_...', // Detected as Hugging Face\n * model: 'microsoft/DialoGPT-medium'\n * });\n *\n * // Auto-detect AWS Bedrock from region and credentials\n * const client3 = await getAIAuto({\n * region: 'us-east-1',\n * credentials: {\n * accessKeyId: 'AKIA...',\n * secretAccessKey: 'xxx'\n * }\n * });\n * ```\n */\nexport async function getAIAuto(\n options: Record<string, any>,\n): Promise<AIInterface> {\n const baseUrl = String((options as any).baseUrl || '');\n const hasKeepAliveOption =\n 'keepAlive' in options && (options as any).keepAlive !== undefined;\n\n // Auto-detect provider based on available credentials\n if (\n /((?:localhost|127\\.0\\.0\\.1)(?::11434)?(?:\\/(?:api|v1))?|ollama(?:\\.com)?(?:\\/(?:api|v1))?)\\/?$/i.test(\n baseUrl,\n ) ||\n hasKeepAliveOption\n ) {\n return getAI({ ...options, type: 'ollama' } as OllamaOptions);\n }\n\n if (options.apiKey && !options.type) {\n // Default to OpenAI if apiKey is provided without explicit type\n return getAI({ ...options, type: 'openai' } as OpenAIOptions);\n }\n\n if (options.apiToken) {\n // Hugging Face uses apiToken\n return getAI({ ...options, type: 'huggingface' } as HuggingFaceOptions);\n }\n\n if (options.region && options.credentials) {\n // AWS Bedrock uses region and explicit credentials\n return getAI({ ...options, type: 'bedrock' } as BedrockOptions);\n }\n\n if (options.projectId || options.anthropicVersion) {\n // Try to detect based on provider-specific options\n if (options.anthropicVersion) {\n return getAI({ ...options, type: 'anthropic' } as AnthropicOptions);\n }\n if (options.projectId) {\n return getAI({ ...options, type: 'gemini' } as GeminiOptions);\n }\n }\n\n throw new ValidationError('Could not auto-detect AI provider from options', {\n hint: 'Please specify a \"type\" field in options or provide provider-specific credentials',\n supportedTypes: [...AI_PROVIDER_TYPES] as AIProviderType[],\n providedOptions: Object.keys(options),\n });\n}\n","import type { AIThread } from './thread';\n\n/**\n * Options for creating AI messages\n */\nexport interface AIMessageOptions {\n /**\n * Role of the message sender\n */\n role?: 'user' | 'assistant' | 'system';\n\n /**\n * Format for the AI response\n */\n responseFormat?: { type: 'text' | 'json_object' };\n}\n\n/**\n * Represents a message in an AI conversation\n */\nexport class AIMessage {\n /**\n * Original options used to create this message\n */\n protected options;\n\n /**\n * Name of the message sender\n */\n public name: string;\n\n /**\n * Content of the message\n */\n public content: string;\n\n /**\n * Role of the message sender in the conversation\n */\n public role: 'user' | 'assistant' | 'system';\n\n /**\n * Creates a new AI message\n *\n * @param options - Message configuration\n * @param options.role - Role of the message sender\n * @param options.content - Content of the message\n * @param options.name - Name of the message sender\n */\n constructor(options: {\n role: 'user' | 'assistant' | 'system';\n content: string;\n name: string;\n }) {\n this.options = options;\n this.role = options.role;\n this.content = options.content;\n this.name = options.name;\n }\n\n /**\n * Factory method to create a new AI message\n *\n * @param options - Message configuration\n * @param options.thread - Thread this message belongs to\n * @param options.role - Role of the message sender\n * @param options.content - Content of the message\n * @param options.name - Name of the message sender\n * @returns Promise resolving to a new AIMessage instance\n */\n static async create(options: {\n thread: AIThread;\n role: 'user' | 'assistant' | 'system';\n content: string;\n name: string;\n }) {\n return new AIMessage(options);\n }\n}\n","import type OpenAI from 'openai';\nimport { AIClient, type AIClientOptions } from './client';\nimport { AIMessage } from './message';\n\n/**\n * Options for creating an AI conversation thread\n */\nexport interface AIThreadOptions {\n /**\n * Options for the AI client to use in this thread\n */\n ai: AIClientOptions;\n}\n\n/**\n * Represents a conversation thread with an AI model\n * Manages messages, references, and conversation state\n */\nexport class AIThread {\n /**\n * AI client instance for this thread\n */\n protected ai!: AIClient;\n\n /**\n * Options used to configure this thread\n */\n protected options: AIThreadOptions;\n\n /**\n * Messages in this conversation thread\n */\n private messages: AIMessage[] = [];\n\n /**\n * Reference materials to include in the conversation context\n */\n private references: { [name: string]: string } = {};\n\n /**\n * Creates a new AI thread\n *\n * @param options - Thread configuration options\n */\n constructor(options: AIThreadOptions) {\n this.options = options;\n }\n\n /**\n * Factory method to create and initialize a new AI thread\n *\n * @param options - Thread configuration options\n * @returns Promise resolving to an initialized AIThread\n */\n static async create(options: AIThreadOptions) {\n const thread = new AIThread(options);\n await thread.initialize();\n return thread; // No need to add system message here, do it in addSystem\n }\n\n /**\n * Initializes the AI client for this thread\n */\n public async initialize() {\n this.ai = await AIClient.create(this.options.ai);\n }\n\n /**\n * Adds a system message to the conversation\n *\n * @param prompt - System message content\n * @returns Promise resolving to the created AIMessage\n */\n public async addSystem(prompt: string) {\n const message = await AIMessage.create({\n thread: this,\n role: 'system',\n name: 'system',\n content: prompt,\n });\n\n this.messages.push(message);\n return message;\n }\n\n /**\n * Adds a message to the conversation\n *\n * @param options - Message options\n * @param options.role - Role of the message sender\n * @param options.name - Optional name of the message sender\n * @param options.content - Content of the message\n * @returns Promise resolving to the created AIMessage\n */\n public async add(options: {\n role: 'user' | 'assistant' | 'system';\n name?: string;\n content: string;\n }) {\n const message = await AIMessage.create({\n thread: this,\n role: options.role,\n name: options.name || options.role, // Default name to role if not provided\n content: options.content,\n });\n\n this.messages.push(message);\n return message;\n }\n\n /**\n * Gets all messages in this thread\n *\n * @returns Array of AIMessage objects\n */\n public get(): AIMessage[] {\n return this.messages;\n }\n\n /**\n * Adds a reference to be included in the conversation context\n *\n * @param name - Name of the reference\n * @param body - Content of the reference\n */\n public addReference(name: string, body: string): void {\n this.references[name] = body;\n }\n\n /**\n * Assembles the conversation history for sending to the AI\n * Properly orders system message, references, and conversation messages\n *\n * @returns Array of message parameters formatted for the OpenAI API\n */\n public assembleHistory(): OpenAI.Chat.ChatCompletionMessageParam[] {\n const history: OpenAI.Chat.ChatCompletionMessageParam[] = [];\n\n // Add system message first\n const systemMessage = this.messages.find((m) => m.role === 'system');\n if (systemMessage) {\n history.push({\n role: systemMessage.role,\n content: systemMessage.content,\n });\n }\n\n // Add references as user messages (before other user/assistant messages)\n for (const name in this.references) {\n history.push({\n role: 'user',\n content: `Reference - ${name}:\\n${this.references[name]}`,\n });\n }\n\n // Add other messages\n this.messages\n .filter((m) => m.role !== 'system')\n .forEach((message) => {\n history.push({ role: message.role, content: message.content });\n });\n\n return history;\n }\n\n /**\n * Sends a prompt to the AI and gets a response\n *\n * @param prompt - Prompt message to send\n * @param options - Options for the AI response\n * @param options.responseFormat - Format for the AI to respond with\n * @returns Promise resolving to the AI response\n */\n public async do(\n prompt: string,\n options: {\n responseFormat?: 'html' | 'text' | 'json';\n } = {\n responseFormat: 'text',\n },\n ) {\n const { responseFormat } = options;\n const history = this.assembleHistory();\n\n // Get completion from AI with assembled history\n const response = await this.ai.textCompletion(prompt, {\n history,\n responseFormat: {\n type: responseFormat === 'json' ? 'json_object' : 'text',\n },\n });\n return response;\n }\n}\n","/**\n * @happyvertical/ai - A standardized interface for AI model interactions\n *\n * This package provides a unified interface for interacting with various AI models.\n * Supports multiple providers: OpenAI, LiteLLM, Ollama, Gemini, Anthropic,\n * Hugging Face, AWS Bedrock, Claude CLI, and Qwen3-TTS.\n *\n * Key components:\n * - getAI() - Factory function for creating AI provider instances\n * - AIInterface - Standardized interface for all AI providers\n * - Provider-specific implementations for each supported service\n */\n\n// Legacy exports for backward compatibility\nexport * from './shared/client';\nexport * from './shared/factory';\nexport { AIMessage as AIMessageClass } from './shared/message';\nexport * from './shared/thread';\nexport * from './shared/types';\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"names":["getAI"],"mappings":";;AAaO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA0CO,SAAS,mBAAmB,SAAyC;AAC1E,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO;AAAA,EACT;AAEA,SAAO,QACJ,OAAO,CAAC,SAAkC,KAAK,SAAS,MAAM,EAC9D,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,IAAI;AACd;AA6rDO,MAAM,gBAAgB,MAAM;AAAA,EACjC,YACE,SACO,MACA,UACA,OACA,YAAqB,OAC5B;AACA,UAAM,OAAO;AALN,SAAA,OAAA;AACA,SAAA,WAAA;AACA,SAAA,QAAA;AACA,SAAA,YAAA;AAGP,SAAK,OAAO;AAAA,EACd;AACF;AAOO,MAAM,4BAA4B,QAAQ;AAAA,EAC/C,YAAY,UAAmB;AAC7B,UAAM,yBAAyB,cAAc,UAAU,QAAW,KAAK;AACvE,SAAK,OAAO;AAAA,EACd;AACF;AAQO,MAAM,uBAAuB,QAAQ;AAAA,EACnC;AAAA,EAEP,YAAY,UAAmB,YAAqB;AAClD;AAAA,MACE,sBAAsB,aAAa,iBAAiB,UAAU,MAAM,EAAE;AAAA,MACtE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAQO,MAAM,2BAA2B,QAAQ;AAAA,EAC9C,YAAY,OAAe,UAAmB;AAC5C;AAAA,MACE,oBAAoB,KAAK;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AAAA,EACd;AACF;AAQO,MAAM,2BAA2B,QAAQ;AAAA,EAC9C,YAAY,UAAmB,OAAgB;AAC7C;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AAAA,EACd;AACF;AAQO,MAAM,2BAA2B,QAAQ;AAAA,EAC9C,YAAY,UAAmB,OAAgB;AAC7C;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AAAA,EACd;AACF;ACpwDA,SAAS,sBACP,SACgC;AAChC,SAAO,QAAQ,SAAS,YAAY,YAAY,WAAW,CAAC,CAAC,QAAQ;AACvE;AAQA,SAAS,mBAAmB,OAA+B;AACzD,SAAO,iBAAiB;AAC1B;AA4HO,MAAM,SAAS;AAAA;AAAA;AAAA;AAAA,EAIb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,YAAY,SAA0B;AACpC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,QACX,OACA,WAAoC,EAAE,MAAM,UAC5C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAoB,OAClB,SACkC;AAElC,QAAI,mBAAmB,OAAO,GAAG;AAC/B,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB;AAEtB,QAAI,sBAAsB,aAAa,GAAG;AACxC,aAAO,aAAa,OAAO,aAAa;AAAA,IAC1C;AAGA,UAAM,eAAgB,cAAsB;AAC5C,QAAI,gBAAgB,iBAAiB,UAAU;AAC7C,YAAM,EAAE,OAAAA,OAAA,IAAU,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,OAAA;AACxB,aAAQ,MAAMA,OAAM,aAAoB;AAAA,IAC1C;AAGA,QAAI,iBAAiB,UAAU;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,UACE,gBAAgB,CAAC,GAAG,iBAAiB;AAAA,UACrC;AAAA,UACA,MAAM;AAAA,QAAA;AAAA,MACR;AAAA,IAEJ;AAEA,UAAM,IAAI,gBAAgB,iCAAiC;AAAA,MACzD,gBAAgB,CAAC,GAAG,iBAAiB;AAAA,MACrC;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUO,eACL,MACA,UAAmC;AAAA,IACjC,MAAM;AAAA,EAAA,GAER;AACA,WAAO,KAAK,QAAQ,MAAM,OAAO;AAAA,EACnC;AACF;AAQA,eAAsB,UAAU,SAG7B;AACD,SAAO,IAAI,OAAO;AAAA,IAChB,QAAQ,QAAQ;AAAA,IAChB,SAAS,QAAQ;AAAA,EAAA,CAClB;AACH;AA0IO,MAAM,qBAAqB,SAAS;AAAA;AAAA;AAAA;AAAA,EAI/B;AAAA;AAAA;AAAA;AAAA,EAKH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,YAAY,SAA8B;AACxC,UAAM,OAAO;AACb,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAa,QACX,MACA,UAA4B,EAAE,MAAM,UACpC;AACA,UAAM,WAAW,MAAM,KAAK,eAAe,MAAM,OAAO;AACxD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAoB,OAClB,SACuB;AACvB,UAAM,SAAS,IAAI,aAAa,OAAO;AACvC,UAAM,OAAO,WAAA;AACb,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAgB,aAAa;AAC3B,SAAK,SAAS,IAAI,OAAO;AAAA,MACvB,QAAQ,KAAK,QAAQ;AAAA,MACrB,SAAS,KAAK,QAAQ;AAAA,IAAA,CACvB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,eACX,SACA,UAAuC,IACtB;AACjB,UAAM;AAAA,MACJ,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU,CAAA;AAAA,MACV,MAAM;AAAA,MACN,mBAAmB;AAAA,MACnB;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,IAAI;AAAA,MACJ,kBAAkB;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,UAAU;AAAA,MAClB,cAAc;AAAA,MACd,gBAAgB,OAAO;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,IACE;AAEJ,UAAM,WAAW;AAAA,MACf,GAAG;AAAA,MACH;AAAA,QACE;AAAA,QACA,SAAS;AAAA,MAAA;AAAA,IACX;AAGF,QAAI,YAAY;AACd,YAAM,SAAS,MAAM,KAAK,OAAO,KAAK,YAAY,OAAO;AAAA,QACvD;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,mBAAmB;AAAA,QACnB,YAAY;AAAA,QACZ;AAAA,QACA,cAAc;AAAA,QACd,YAAY;AAAA,QACZ;AAAA,QACA,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA,aAAa;AAAA,QACb;AAAA,MAAA,CACD;AAED,UAAI,cAAc;AAClB,uBAAiB,SAAS,QAAQ;AAChC,cAAM,UAAU,MAAM,QAAQ,CAAC,GAAG,OAAO,WAAW;AACpD,uBAAe;AACf,mBAAW,OAAO;AAAA,MACpB;AAEA,aAAO;AAAA,IACT;AACA,UAAM,WAAW,MAAM,KAAK,OAAO,KAAK,YAAY,OAAO;AAAA,MACzD;AAAA,MACA;AAAA,MACA,mBAAmB;AAAA,MACnB,YAAY;AAAA,MACZ;AAAA,MACA,cAAc;AAAA,MACd,YAAY;AAAA,MACZ;AAAA,MACA,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,aAAa;AAAA,MACb;AAAA,IAAA,CACD;AAED,UAAM,SAAS,SAAS,QAAQ,CAAC;AACjC,QAAI,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,OAAO,QAAQ,SAAS;AACzD,YAAM,IAAI,SAAS,qDAAqD;AAAA,QACtE;AAAA,QACA,YAAY,SAAS;AAAA,QACrB,SAAS,SAAS,SAAS,UAAU;AAAA,QACrC,WAAW,CAAC,CAAC;AAAA,QACb,YAAY,CAAC,CAAC,QAAQ;AAAA,QACtB,YAAY,CAAC,CAAC,QAAQ,SAAS;AAAA,MAAA,CAChC;AAAA,IACH;AACA,WAAO,OAAO,QAAQ;AAAA,EACxB;AACF;AAiBA,eAAsB,YACpB,SACmB;AAEnB,QAAM,EAAE,OAAAA,OAAA,IAAU,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,OAAA;AACxB,SAAQ,MAAMA,OAAM,OAAc;AACpC;ACvqBA,MAAM,2CAA2B,IAAuB;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,MAAM,0BAA0B;AAChC,MAAM,4BAA4B,KAAK,KAAK;AAS5C,MAAM,kBAAkB;AAAA,EACd,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,OAAsB,QAAQ,QAAA;AAAA,EAC9B,aAAa,KAAK,IAAA;AAAA,EAE1B,QAAc;AACZ,SAAK,aAAa,KAAK,IAAA;AAAA,EACzB;AAAA,EAEA,SAAY,MAAoC;AAC9C,SAAK,oBAAoB;AACzB,SAAK,MAAA;AAEL,UAAM,MAAM,KAAK,KAAK,KAAK,MAAM,IAAI;AACrC,SAAK,OAAO,IAAI;AAAA,MACd,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAGR,WAAO,IAAI,QAAQ,MAAM;AACvB,WAAK,mBAAmB,KAAK,IAAI,GAAG,KAAK,mBAAmB,CAAC;AAC7D,WAAK,MAAA;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,iBAAgC;AACpC,SAAK,MAAA;AACL,UAAM,UAAU,KAAK,kBAAkB,KAAK,IAAA;AAC5C,QAAI,UAAU,GAAG;AACf,YAAM,MAAM,OAAO;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,SAAS,SAAuB;AAC9B,SAAK,MAAA;AACL,QAAI,WAAW,GAAG;AAChB;AAAA,IACF;AAEA,SAAK,kBAAkB,KAAK,IAAI,KAAK,iBAAiB,KAAK,IAAA,IAAQ,OAAO;AAAA,EAC5E;AAAA,EAEA,YAAY,KAAsB;AAChC,WAAO,KAAK,qBAAqB,KAAK,OAAO,KAAK;AAAA,EACpD;AAAA,EAEA,UAAU,KAAsB;AAC9B,WACE,KAAK,YAAY,GAAG,KACpB,MAAM,KAAK,cAAc;AAAA,EAE7B;AAAA,EAEA,gBAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AACF;AAEA,MAAM,yCAAyB,IAAA;AAE/B,SAAS,MAAM,SAAgC;AAC7C,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,eAAW,SAAS,OAAO;AAAA,EAC7B,CAAC;AACH;AAEA,SAAS,0BAAgC;AACvC,QAAM,MAAM,KAAK,IAAA;AAEjB,aAAW,CAAC,KAAK,WAAW,KAAK,mBAAmB,WAAW;AAC7D,QAAI,YAAY,UAAU,GAAG,GAAG;AAC9B,yBAAmB,OAAO,GAAG;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO,mBAAmB,OAAO,yBAAyB;AACxD,UAAM,mBAAmB,CAAC,GAAG,mBAAmB,QAAA,CAAS,EACtD,OAAO,CAAC,CAAA,EAAG,WAAW,MAAM,YAAY,YAAY,GAAG,CAAC,EACxD;AAAA,MACC,CAAC,CAAA,EAAG,eAAe,GAAG,GAAG,gBAAgB,MACvC,gBAAgB,cAAA,IAAkB,iBAAiB,cAAA;AAAA,IAAc;AAGvE,QAAI,iBAAiB,WAAW,GAAG;AACjC;AAAA,IACF;AAEA,uBAAmB,OAAO,iBAAiB,CAAC,EAAE,CAAC,CAAC;AAAA,EAClD;AACF;AAEA,SAAS,qBAAqB,KAAgC;AAC5D,0BAAA;AAEA,MAAI,cAAc,mBAAmB,IAAI,GAAG;AAC5C,MAAI,CAAC,aAAa;AAChB,kBAAc,IAAI,kBAAA;AAClB,uBAAmB,IAAI,KAAK,WAAW;AACvC,4BAAA;AAAA,EACF;AAEA,cAAY,MAAA;AACZ,SAAO;AACT;AAEA,SAAS,gBAAgB,WAAyC;AAChE,MAAI,CAAC,aAAa,UAAU,YAAY,OAAO;AAC7C,WAAO;AAAA,EACT;AAEA,SACE,UAAU,YAAY,QACtB,UAAU,QAAQ,UAClB,UAAU,eAAe,UACzB,UAAU,mBAAmB,UAC7B,UAAU,gBAAgB;AAE9B;AAEA,SAAS,4BACP,OACA,UACQ;AACR,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;AACtC;AAEA,SAAS,QAAQ,OAAuB;AACtC,MAAI,OAAO;AAEX,WAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACpD,YAAQ,MAAM,WAAW,KAAK;AAC9B,WAAO,KAAK,KAAK,MAAM,QAAQ;AAAA,EACjC;AAEA,UAAQ,SAAS,GAAG,SAAS,EAAE;AACjC;AAEA,SAAS,gBAAgB,SAAiD;AACxE,QAAM,OAAQ,QAA8B;AAC5C,QAAM,WAAW,OAAO,SAAS,YAAY,OAAO,OAAO;AAE3D,QAAM,uBAAuB;AAAA,IAC3B,YAAY,UAAU,QAAQ,SAAS;AAAA,IACvC,cAAc,UAAU,QAAQ,WAAW;AAAA,IAC3C,iBAAiB,UAAU,QAAQ,aAAa,cAAc;AAAA,IAC9D,cAAc,UAAU,QAAQ,WAAW;AAAA,IAC3C,aAAa,UAAU,QAAQ,UAAU;AAAA,IACzC,aAAa,UAAU,QAAQ,UAAU;AAAA,EAAA;AAG3C,QAAM,OAAO,qBAAqB;AAAA,IAChC,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS;AAAA,EAAA;AAG1E,SAAO,OAAO,GAAG,QAAQ,IAAI,QAAQ,IAAI,CAAC,KAAK,GAAG,QAAQ;AAC5D;AAEA,SAAS,yBACP,SACkC;AAClC,QAAM,YAAY,eAAe,UAAU,QAAQ,YAAY;AAE/D,MAAI,CAAC,gBAAgB,SAAS,GAAG;AAC/B,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,YAAY,4BAA4B,WAAW,YAAY,CAAC;AAAA,IAChE,gBAAgB;AAAA,MACd,WAAW;AAAA,MACX;AAAA,IAAA;AAAA,IAEF,KAAK,WAAW,KAAK,KAAA,KAAU,gBAAgB,OAAO;AAAA,IACtD,aAAa,KAAK;AAAA,MAChB;AAAA,MACA,4BAA4B,WAAW,aAAa,CAAC;AAAA,IAAA;AAAA,EACvD;AAEJ;AAEA,SAAS,gBACP,OACA,QACQ;AACR,QAAM,gBACJ,OAAO,MAAM,eAAe,YAAY,OAAO,SAAS,MAAM,UAAU,IACpE,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,aAAa,GAAI,CAAC,IAC9C;AAEN,MAAI,kBAAkB,QAAW;AAC/B,WAAO,KAAK,IAAI,OAAO,YAAY,aAAa;AAAA,EAClD;AAEA,SAAO,KAAK,IAAI,OAAO,YAAY,OAAO,cAAc;AAC1D;AAEA,eAAe,iBACb,SACA,aACA,QACY;AACZ,MAAI,UAAU;AAEd,SAAO,MAAM;AACX,UAAM,YAAY,eAAA;AAElB,QAAI;AACF,YAAM,SAAS,MAAM,QAAA;AACrB,kBAAY,SAAS,OAAO,UAAU;AACtC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,oBAAY,SAAS,gBAAgB,OAAO,MAAM,CAAC;AAEnD,YAAI,MAAM,aAAa,UAAU,OAAO,aAAa;AACnD,qBAAW;AACX;AAAA,QACF;AAAA,MACF;AAEA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,uBACd,YACoB;AACpB,MAAI,OAAO,eAAe,YAAY,OAAO,SAAS,UAAU,GAAG;AACjE,WAAO,KAAK,IAAI,GAAG,UAAU;AAAA,EAC/B;AAEA,MAAI,OAAO,eAAe,UAAU;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,WAAW,KAAA;AAC3B,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,OAAO,WAAW,OAAO;AACzC,MAAI,CAAC,OAAO,MAAM,OAAO,GAAG;AAC1B,WAAO,KAAK,IAAI,GAAG,OAAO;AAAA,EAC5B;AAEA,QAAM,UAAU,KAAK,MAAM,OAAO;AAClC,MAAI,OAAO,MAAM,OAAO,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,IAAI,GAAG,KAAK,MAAM,UAAU,KAAK,IAAA,KAAS,GAAI,CAAC;AAC7D;AAEA,SAAS,eACP,SACA,YAC6B;AAC7B,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,WAAW,OAAO,QAAQ,QAAQ,YAAY;AACzD,WAAO,QAAQ,IAAI,UAAU,KAAK,QAAQ,IAAI,WAAW,aAAa;AAAA,EACxE;AAEA,QAAM,gBAAgB;AACtB,SAAO,cAAc,UAAU,KAAK,cAAc,WAAW,aAAa;AAC5E;AAEO,SAAS,yBAAyB,OAAoC;AAC3E,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,WAAO;AAAA,EACT;AAEA,MAAI,gBAAgB,OAAO;AACzB,UAAM,aAAa;AAAA,MAChB,MAAkD;AAAA,IAAA;AAErD,QAAI,eAAe,QAAW;AAC5B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI,aAAa,OAAO;AACtB,UAAM,aAAa;AAAA,MACjB,eAAgB,MAAgC,SAAS,aAAa;AAAA,IAAA;AAExE,QAAI,eAAe,QAAW;AAC5B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,UACJ,aAAa,SAAS,OAAO,MAAM,YAAY,WAC3C,MAAM,UACN;AAEN,QAAM,kBACJ,QAAQ,MAAM,qCAAqC,KACnD,QAAQ,MAAM,mCAAmC;AAEnD,SAAO,kBACH,uBAAuB,gBAAgB,CAAC,CAAC,IACzC;AACN;AAEO,SAAS,oBACd,QACA,SACG;AACH,QAAM,SAAS,yBAAyB,OAAO;AAC/C,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,QAAM,qCAAqB,IAAA;AAE3B,SAAO,IAAI,MAAM,QAAQ;AAAA,IACvB,IAAI,QAAQ,UAAU,UAAU;AAC9B,YAAM,QAAQ,QAAQ,IAAI,QAAQ,UAAU,QAAQ;AAEpD,UAAI,OAAO,UAAU,YAAY;AAC/B,eAAO;AAAA,MACT;AAEA,UAAI,CAAC,qBAAqB,IAAI,QAA6B,GAAG;AAC5D,YAAI,CAAC,eAAe,IAAI,QAAQ,GAAG;AACjC,yBAAe,IAAI,UAAU,MAAM,KAAK,MAAM,CAAC;AAAA,QACjD;AAEA,eAAO,eAAe,IAAI,QAAQ;AAAA,MACpC;AAEA,UAAI,CAAC,eAAe,IAAI,QAAQ,GAAG;AACjC,uBAAe,IAAI,UAAU,IAAI,SAAoB;AACnD,gBAAM,cAAc,qBAAqB,OAAO,GAAG;AACnD,iBAAO,YAAY;AAAA,YAAS,MAC1B;AAAA,cACE,MAAM,QAAQ,MAAM,OAAO,QAAQ,IAAI;AAAA,cACvC;AAAA,cACA;AAAA,YAAA;AAAA,UACF;AAAA,QAEJ,CAAC;AAAA,MACH;AAEA,aAAO,eAAe,IAAI,QAAQ;AAAA,IACpC;AAAA,EAAA,CACD;AACH;AC7VA,SAAS,gBACP,SAC0B;AAC1B,SAAO,CAAC,QAAQ,QAAQ,QAAQ,SAAS;AAC3C;AAOA,SAAS,iBACP,SAC2B;AAC3B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,iBACP,SAC2B;AAC3B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,gBACP,SAC0B;AAC1B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,gBACP,SAC0B;AAC1B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,mBACP,SAC6B;AAC7B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,qBACP,SAC+B;AAC/B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,iBACP,SAC2B;AAC3B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,mBACP,SAC6B;AAC7B,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,kBACP,SAC4B;AAC5B,SAAO,QAAQ,SAAS;AAC1B;AAgDA,eAAsB,MACpB,UAA0C,IACpB;AAEtB,YAAU,cAAc,SAAgC;AAAA,IACtD,aAAa;AAAA,IACb,QAAQ;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,aAAa;AAAA,MACb,cAAc;AAAA,MACd,UAAU;AAAA,MACV,WAAW;AAAA,MACX,eAAe;AAAA,MACf,eAAe;AAAA,IAAA;AAAA,EACjB,CACD;AAGD,MAAI,cAAc,WAAW,CAAC,QAAQ,MAAM;AACzC,YAAgB,OAAQ,QAAgB;AAAA,EAC3C;AAGA,MAAI,WAAW,WAAW,CAAC,QAAQ,cAAc;AAC9C,YAAgB,eAAgB,QAAgB;AAAA,EACnD;AAEA,MAAI;AAEJ,MAAI,gBAAgB,OAAO,GAAG;AAC5B,UAAM,EAAE,eAAA,IAAmB,MAAM,OAAO,sBAAuB;AAC/D,aAAS,IAAI,eAAe,OAAO;AAAA,EACrC,WAAW,iBAAiB,OAAO,GAAG;AACpC,UAAM,EAAE,gBAAA,IAAoB,MAAM,OAAO,uBAAwB;AACjE,aAAS,IAAI,gBAAgB,OAAO;AAAA,EACtC,WAAW,iBAAiB,OAAO,GAAG;AACpC,UAAM,EAAE,gBAAA,IAAoB,MAAM,OAAO,uBAAwB;AACjE,aAAS,IAAI,gBAAgB,OAAO;AAAA,EACtC,WAAW,gBAAgB,OAAO,GAAG;AACnC,UAAM,EAAE,eAAA,IAAmB,MAAM,OAAO,sBAAuB;AAC/D,aAAS,IAAI,eAAe,OAAO;AAAA,EACrC,WAAW,gBAAgB,OAAO,GAAG;AACnC,UAAM,EAAE,eAAA,IAAmB,MAAM,OAAO,sBAAuB;AAC/D,aAAS,IAAI,eAAe,OAAO;AAAA,EACrC,WAAW,mBAAmB,OAAO,GAAG;AACtC,UAAM,EAAE,kBAAA,IAAsB,MAAM,OAAO,yBAA0B;AACrE,aAAS,IAAI,kBAAkB,OAAO;AAAA,EACxC,WAAW,qBAAqB,OAAO,GAAG;AACxC,UAAM,EAAE,oBAAA,IAAwB,MAAM,OAAO,2BAA4B;AACzE,aAAS,IAAI,oBAAoB,OAAO;AAAA,EAC1C,WAAW,iBAAiB,OAAO,GAAG;AACpC,UAAM,EAAE,gBAAA,IAAoB,MAAM,OAAO,uBAAwB;AACjE,aAAS,IAAI,gBAAgB,OAAO;AAAA,EACtC,WAAW,mBAAmB,OAAO,GAAG;AACtC,UAAM,EAAE,kBAAA,IAAsB,MAAM,OAAO,0BAA2B;AACtE,aAAS,IAAI,kBAAkB,OAAO;AAAA,EACxC,WAAW,kBAAkB,OAAO,GAAG;AACrC,UAAM,EAAE,iBAAA,IAAqB,MAAM,OAAO,wBAAyB;AACnE,aAAS,IAAI,iBAAiB,OAAO;AAAA,EACvC,OAAO;AACL,UAAM,IAAI,gBAAgB,gCAAgC;AAAA,MACxD,gBAAgB,CAAC,GAAG,iBAAiB;AAAA,MACrC,cAAe,QAAgB;AAAA,IAAA,CAChC;AAAA,EACH;AAEA,SAAO,oBAAoB,QAAQ,OAAO;AAC5C;AAkCA,eAAsB,UACpB,SACsB;AACtB,QAAM,UAAU,OAAQ,QAAgB,WAAW,EAAE;AACrD,QAAM,qBACJ,eAAe,WAAY,QAAgB,cAAc;AAG3D,MACE,kGAAkG;AAAA,IAChG;AAAA,EAAA,KAEF,oBACA;AACA,WAAO,MAAM,EAAE,GAAG,SAAS,MAAM,UAA2B;AAAA,EAC9D;AAEA,MAAI,QAAQ,UAAU,CAAC,QAAQ,MAAM;AAEnC,WAAO,MAAM,EAAE,GAAG,SAAS,MAAM,UAA2B;AAAA,EAC9D;AAEA,MAAI,QAAQ,UAAU;AAEpB,WAAO,MAAM,EAAE,GAAG,SAAS,MAAM,eAAqC;AAAA,EACxE;AAEA,MAAI,QAAQ,UAAU,QAAQ,aAAa;AAEzC,WAAO,MAAM,EAAE,GAAG,SAAS,MAAM,WAA6B;AAAA,EAChE;AAEA,MAAI,QAAQ,aAAa,QAAQ,kBAAkB;AAEjD,QAAI,QAAQ,kBAAkB;AAC5B,aAAO,MAAM,EAAE,GAAG,SAAS,MAAM,aAAiC;AAAA,IACpE;AACA,QAAI,QAAQ,WAAW;AACrB,aAAO,MAAM,EAAE,GAAG,SAAS,MAAM,UAA2B;AAAA,IAC9D;AAAA,EACF;AAEA,QAAM,IAAI,gBAAgB,kDAAkD;AAAA,IAC1E,MAAM;AAAA,IACN,gBAAgB,CAAC,GAAG,iBAAiB;AAAA,IACrC,iBAAiB,OAAO,KAAK,OAAO;AAAA,EAAA,CACrC;AACH;;;;;;ACjUO,MAAM,UAAU;AAAA;AAAA;AAAA;AAAA,EAIX;AAAA;AAAA;AAAA;AAAA,EAKH;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUP,YAAY,SAIT;AACD,SAAK,UAAU;AACf,SAAK,OAAO,QAAQ;AACpB,SAAK,UAAU,QAAQ;AACvB,SAAK,OAAO,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,aAAa,OAAO,SAKjB;AACD,WAAO,IAAI,UAAU,OAAO;AAAA,EAC9B;AACF;AC5DO,MAAM,SAAS;AAAA;AAAA;AAAA;AAAA,EAIV;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKF,WAAwB,CAAA;AAAA;AAAA;AAAA;AAAA,EAKxB,aAAyC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjD,YAAY,SAA0B;AACpC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,OAAO,SAA0B;AAC5C,UAAM,SAAS,IAAI,SAAS,OAAO;AACnC,UAAM,OAAO,WAAA;AACb,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,aAAa;AACxB,SAAK,KAAK,MAAM,SAAS,OAAO,KAAK,QAAQ,EAAE;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,UAAU,QAAgB;AACrC,UAAM,UAAU,MAAM,UAAU,OAAO;AAAA,MACrC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,IAAA,CACV;AAED,SAAK,SAAS,KAAK,OAAO;AAC1B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAa,IAAI,SAId;AACD,UAAM,UAAU,MAAM,UAAU,OAAO;AAAA,MACrC,QAAQ;AAAA,MACR,MAAM,QAAQ;AAAA,MACd,MAAM,QAAQ,QAAQ,QAAQ;AAAA;AAAA,MAC9B,SAAS,QAAQ;AAAA,IAAA,CAClB;AAED,SAAK,SAAS,KAAK,OAAO;AAC1B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,MAAmB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,aAAa,MAAc,MAAoB;AACpD,SAAK,WAAW,IAAI,IAAI;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,kBAA4D;AACjE,UAAM,UAAoD,CAAA;AAG1D,UAAM,gBAAgB,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AACnE,QAAI,eAAe;AACjB,cAAQ,KAAK;AAAA,QACX,MAAM,cAAc;AAAA,QACpB,SAAS,cAAc;AAAA,MAAA,CACxB;AAAA,IACH;AAGA,eAAW,QAAQ,KAAK,YAAY;AAClC,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,SAAS,eAAe,IAAI;AAAA,EAAM,KAAK,WAAW,IAAI,CAAC;AAAA,MAAA,CACxD;AAAA,IACH;AAGA,SAAK,SACF,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,EACjC,QAAQ,CAAC,YAAY;AACpB,cAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,SAAS,QAAQ,SAAS;AAAA,IAC/D,CAAC;AAEH,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,GACX,QACA,UAEI;AAAA,IACF,gBAAgB;AAAA,EAAA,GAElB;AACA,UAAM,EAAE,mBAAmB;AAC3B,UAAM,UAAU,KAAK,gBAAA;AAGrB,UAAM,WAAW,MAAM,KAAK,GAAG,eAAe,QAAQ;AAAA,MACpD;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM,mBAAmB,SAAS,gBAAgB;AAAA,MAAA;AAAA,IACpD,CACD;AACD,WAAO;AAAA,EACT;AACF;AC5KO,MAAM,8BAA8B;"}
@@ -0,0 +1,220 @@
1
+ import { ValidationError } from "@happyvertical/utils";
2
+ import { L as LiteLLMAdmin, r as resolveGatewayAdminBaseUrl } from "./gateway-admin-C4GFPbZF.js";
3
+ import { OpenAIProvider } from "./openai-5snI2diE.js";
4
+ const LITELLM_CAPABILITIES = {
5
+ chat: true,
6
+ completion: true,
7
+ embeddings: true,
8
+ streaming: true,
9
+ functions: true,
10
+ vision: true,
11
+ fineTuning: false,
12
+ imageEmbeddings: true,
13
+ imageGeneration: true,
14
+ tts: false,
15
+ voiceCloning: false,
16
+ voiceDesign: false,
17
+ maxContextLength: 128e3,
18
+ supportedOperations: [
19
+ "chat",
20
+ "completion",
21
+ "embedding",
22
+ "streaming",
23
+ "functions",
24
+ "vision",
25
+ "image_embedding",
26
+ "image_generation"
27
+ ]
28
+ };
29
+ function isEmbeddingModel(modelId) {
30
+ return /embed/i.test(modelId);
31
+ }
32
+ function isImageEmbeddingModel(modelId) {
33
+ return /titan-embed-image|multimodalembedding|image[-_]?embed|clip/i.test(
34
+ modelId
35
+ );
36
+ }
37
+ function isImageGenerationModel(modelId) {
38
+ return /gpt-image|dall-e|imagen|stable[-_ ]diffusion|sdxl|flux|titan-image|image-generator/i.test(
39
+ modelId
40
+ );
41
+ }
42
+ function isFilteredLiteLLMModel(modelId) {
43
+ return /moderation|transcrib|whisper|speech|tts|rerank/i.test(modelId);
44
+ }
45
+ function inferLiteLLMContextLength(modelId) {
46
+ if (isEmbeddingModel(modelId)) return 8192;
47
+ if (/gemini-1\.5|gemini-2\.0|gemini-2\.5|gemini-3/i.test(modelId)) {
48
+ return 1e6;
49
+ }
50
+ if (/claude/i.test(modelId)) return 2e5;
51
+ if (/gpt-4o|gpt-4\.1|o1|o3|o4|llama|mistral|qwen|deepseek/i.test(modelId)) {
52
+ return 128e3;
53
+ }
54
+ return 32768;
55
+ }
56
+ function inferLiteLLMFunctions(modelId) {
57
+ if (isEmbeddingModel(modelId)) return false;
58
+ return /gpt|claude|gemini|command|llama|mistral|qwen|deepseek|o1|o3|o4/i.test(
59
+ modelId
60
+ );
61
+ }
62
+ function inferLiteLLMVision(modelId) {
63
+ return /gpt-4o|gpt-4\.1|vision|claude-3|gemini|pixtral|llava|qwen.*vl|vl-/i.test(
64
+ modelId
65
+ );
66
+ }
67
+ function inferLiteLLMCapabilities(modelId) {
68
+ if (isImageGenerationModel(modelId)) {
69
+ return ["image_generation"];
70
+ }
71
+ if (isImageEmbeddingModel(modelId)) {
72
+ return ["embeddings", "image_embedding"];
73
+ }
74
+ if (isEmbeddingModel(modelId)) {
75
+ return ["embeddings"];
76
+ }
77
+ const capabilities = ["text", "chat"];
78
+ if (inferLiteLLMFunctions(modelId)) {
79
+ capabilities.push("functions");
80
+ }
81
+ if (inferLiteLLMVision(modelId)) {
82
+ capabilities.push("vision");
83
+ }
84
+ return capabilities;
85
+ }
86
+ function normalizeBaseUrl(baseUrl) {
87
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
88
+ }
89
+ function ensureLiteLLMOptions(options) {
90
+ if (!options.baseUrl?.trim()) {
91
+ throw new ValidationError("LiteLLM baseUrl is required", {
92
+ provider: "litellm",
93
+ hint: "Pass baseUrl like https://llm.happyvertical.com/v1 or set HAVE_AI_BASE_URL / LITELLM_BASE_URL"
94
+ });
95
+ }
96
+ return {
97
+ ...options,
98
+ baseUrl: normalizeBaseUrl(options.baseUrl.trim())
99
+ };
100
+ }
101
+ const LITELLM_PROFILE = {
102
+ providerLabel: "LiteLLM",
103
+ providerName: "litellm",
104
+ defaultModel: "gpt-4",
105
+ capabilities: LITELLM_CAPABILITIES,
106
+ describeModel: (modelId) => `LiteLLM model: ${modelId}`,
107
+ getContextLength: inferLiteLLMContextLength,
108
+ getModelCapabilities: inferLiteLLMCapabilities,
109
+ shouldIncludeModel: (modelId) => !isFilteredLiteLLMModel(modelId),
110
+ supportsFunctions: inferLiteLLMFunctions,
111
+ supportsVision: inferLiteLLMVision
112
+ };
113
+ function supportsCapability(model, capability) {
114
+ if (capability === "vision") {
115
+ return model.capabilities.includes("vision") || model.supportsVision === true;
116
+ }
117
+ if (capability === "embeddings") {
118
+ return model.capabilities.includes("embeddings");
119
+ }
120
+ if (capability === "image_generation") {
121
+ return model.capabilities.includes("image_generation");
122
+ }
123
+ return model.capabilities.includes("chat");
124
+ }
125
+ class LiteLLMProvider extends OpenAIProvider {
126
+ admin;
127
+ configuredDefaultModel;
128
+ resolvedModelCache = /* @__PURE__ */ new Map();
129
+ constructor(options) {
130
+ const normalized = ensureLiteLLMOptions(options);
131
+ super(normalized, LITELLM_PROFILE);
132
+ this.admin = new LiteLLMAdmin({
133
+ provider: "litellm",
134
+ baseUrl: resolveGatewayAdminBaseUrl(
135
+ normalized.baseUrl,
136
+ normalized.adminUrl || normalized.adminBaseUrl,
137
+ "litellm"
138
+ ),
139
+ apiKey: normalized.adminApiKey || normalized.apiKey,
140
+ headers: normalized.adminHeaders,
141
+ timeout: normalized.timeout
142
+ });
143
+ this.configuredDefaultModel = normalized.defaultModel;
144
+ }
145
+ async resolveModel(capability, explicitModel) {
146
+ if (explicitModel) {
147
+ return explicitModel;
148
+ }
149
+ if (this.configuredDefaultModel && (capability === "chat" || capability === "vision")) {
150
+ return this.configuredDefaultModel;
151
+ }
152
+ const cached = this.resolvedModelCache.get(capability);
153
+ if (cached) {
154
+ return cached;
155
+ }
156
+ const pending = this.selectModel(capability).catch((error) => {
157
+ this.resolvedModelCache.delete(capability);
158
+ throw error;
159
+ });
160
+ this.resolvedModelCache.set(capability, pending);
161
+ return pending;
162
+ }
163
+ async selectModel(capability) {
164
+ const models = await super.getModels();
165
+ const model = models.find(
166
+ (candidate) => supportsCapability(candidate, capability)
167
+ );
168
+ if (!model) {
169
+ throw new ValidationError(
170
+ `No ${capability} model is available from the LiteLLM gateway`,
171
+ {
172
+ provider: "litellm",
173
+ capability,
174
+ hint: "Pass defaultModel explicitly or ensure /models returns a compatible model for this key"
175
+ }
176
+ );
177
+ }
178
+ return model.id;
179
+ }
180
+ async chat(messages, options = {}) {
181
+ return super.chat(messages, {
182
+ ...options,
183
+ model: await this.resolveModel("chat", options.model)
184
+ });
185
+ }
186
+ async *stream(messages, options = {}) {
187
+ yield* super.stream(messages, {
188
+ ...options,
189
+ model: await this.resolveModel("chat", options.model)
190
+ });
191
+ }
192
+ async describeImage(image, prompt, options = {}) {
193
+ return super.describeImage(image, prompt, {
194
+ ...options,
195
+ model: await this.resolveModel("vision", options.model)
196
+ });
197
+ }
198
+ async embed(text, options = {}) {
199
+ return super.embed(text, {
200
+ ...options,
201
+ model: await this.resolveModel("embeddings", options.model)
202
+ });
203
+ }
204
+ async embedImage(image, options = {}) {
205
+ return super.embedImage(image, {
206
+ ...options,
207
+ model: await this.resolveModel("embeddings", options.model)
208
+ });
209
+ }
210
+ async generateImage(prompt, options = {}) {
211
+ return super.generateImage(prompt, {
212
+ ...options,
213
+ model: await this.resolveModel("image_generation", options.model)
214
+ });
215
+ }
216
+ }
217
+ export {
218
+ LiteLLMProvider
219
+ };
220
+ //# sourceMappingURL=litellm-DhPKa_Jz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"litellm-DhPKa_Jz.js","sources":["../../src/shared/providers/litellm.ts"],"sourcesContent":["import { ValidationError } from '@happyvertical/utils';\n\nimport type {\n AICapabilities,\n AIMessage,\n AIModel,\n AIResponse,\n ChatOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n LiteLLMOptions,\n} from '../types';\nimport { LiteLLMAdmin, resolveGatewayAdminBaseUrl } from './gateway-admin';\nimport { type OpenAICompatibleProfile, OpenAIProvider } from './openai';\n\nconst LITELLM_CAPABILITIES: AICapabilities = {\n chat: true,\n completion: true,\n embeddings: true,\n streaming: true,\n functions: true,\n vision: true,\n fineTuning: false,\n imageEmbeddings: true,\n imageGeneration: true,\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\nfunction isEmbeddingModel(modelId: string): boolean {\n return /embed/i.test(modelId);\n}\n\nfunction isImageEmbeddingModel(modelId: string): boolean {\n return /titan-embed-image|multimodalembedding|image[-_]?embed|clip/i.test(\n modelId,\n );\n}\n\nfunction isImageGenerationModel(modelId: string): boolean {\n return /gpt-image|dall-e|imagen|stable[-_ ]diffusion|sdxl|flux|titan-image|image-generator/i.test(\n modelId,\n );\n}\n\nfunction isFilteredLiteLLMModel(modelId: string): boolean {\n return /moderation|transcrib|whisper|speech|tts|rerank/i.test(modelId);\n}\n\nfunction inferLiteLLMContextLength(modelId: string): number {\n if (isEmbeddingModel(modelId)) return 8192;\n if (/gemini-1\\.5|gemini-2\\.0|gemini-2\\.5|gemini-3/i.test(modelId)) {\n return 1000000;\n }\n if (/claude/i.test(modelId)) return 200000;\n if (/gpt-4o|gpt-4\\.1|o1|o3|o4|llama|mistral|qwen|deepseek/i.test(modelId)) {\n return 128000;\n }\n return 32768;\n}\n\nfunction inferLiteLLMFunctions(modelId: string): boolean {\n if (isEmbeddingModel(modelId)) return false;\n return /gpt|claude|gemini|command|llama|mistral|qwen|deepseek|o1|o3|o4/i.test(\n modelId,\n );\n}\n\nfunction inferLiteLLMVision(modelId: string): boolean {\n return /gpt-4o|gpt-4\\.1|vision|claude-3|gemini|pixtral|llava|qwen.*vl|vl-/i.test(\n modelId,\n );\n}\n\nfunction inferLiteLLMCapabilities(modelId: string): string[] {\n if (isImageGenerationModel(modelId)) {\n return ['image_generation'];\n }\n\n if (isImageEmbeddingModel(modelId)) {\n return ['embeddings', 'image_embedding'];\n }\n\n if (isEmbeddingModel(modelId)) {\n return ['embeddings'];\n }\n\n const capabilities = ['text', 'chat'];\n\n if (inferLiteLLMFunctions(modelId)) {\n capabilities.push('functions');\n }\n\n if (inferLiteLLMVision(modelId)) {\n capabilities.push('vision');\n }\n\n return capabilities;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n}\n\nfunction ensureLiteLLMOptions(options: LiteLLMOptions): LiteLLMOptions {\n if (!options.baseUrl?.trim()) {\n throw new ValidationError('LiteLLM baseUrl is required', {\n provider: 'litellm',\n hint: 'Pass baseUrl like https://llm.happyvertical.com/v1 or set HAVE_AI_BASE_URL / LITELLM_BASE_URL',\n });\n }\n\n return {\n ...options,\n baseUrl: normalizeBaseUrl(options.baseUrl.trim()),\n };\n}\n\nconst LITELLM_PROFILE: OpenAICompatibleProfile = {\n providerLabel: 'LiteLLM',\n providerName: 'litellm',\n defaultModel: 'gpt-4',\n capabilities: LITELLM_CAPABILITIES,\n describeModel: (modelId) => `LiteLLM model: ${modelId}`,\n getContextLength: inferLiteLLMContextLength,\n getModelCapabilities: inferLiteLLMCapabilities,\n shouldIncludeModel: (modelId) => !isFilteredLiteLLMModel(modelId),\n supportsFunctions: inferLiteLLMFunctions,\n supportsVision: inferLiteLLMVision,\n};\n\ntype LiteLLMResolvedCapability =\n | 'chat'\n | 'vision'\n | 'embeddings'\n | 'image_generation';\n\nfunction supportsCapability(\n model: AIModel,\n capability: LiteLLMResolvedCapability,\n): boolean {\n if (capability === 'vision') {\n return (\n model.capabilities.includes('vision') || model.supportsVision === true\n );\n }\n\n if (capability === 'embeddings') {\n return model.capabilities.includes('embeddings');\n }\n\n if (capability === 'image_generation') {\n return model.capabilities.includes('image_generation');\n }\n\n return model.capabilities.includes('chat');\n}\n\n/**\n * LiteLLM provider implementation.\n *\n * LiteLLM exposes an OpenAI-compatible API surface, so this provider reuses the\n * OpenAI transport while customizing provider identity, model discovery, and\n * capability heuristics for gateway-backed deployments.\n */\nexport class LiteLLMProvider extends OpenAIProvider {\n readonly admin: LiteLLMAdmin;\n private readonly configuredDefaultModel?: string;\n private readonly resolvedModelCache = new Map<\n LiteLLMResolvedCapability,\n Promise<string>\n >();\n\n constructor(options: LiteLLMOptions) {\n const normalized = ensureLiteLLMOptions(options);\n super(normalized, LITELLM_PROFILE);\n this.admin = new LiteLLMAdmin({\n provider: 'litellm',\n baseUrl: resolveGatewayAdminBaseUrl(\n normalized.baseUrl,\n normalized.adminUrl || normalized.adminBaseUrl,\n 'litellm',\n ),\n apiKey: normalized.adminApiKey || normalized.apiKey,\n headers: normalized.adminHeaders,\n timeout: normalized.timeout,\n });\n this.configuredDefaultModel = normalized.defaultModel;\n }\n\n private async resolveModel(\n capability: LiteLLMResolvedCapability,\n explicitModel?: string,\n ): Promise<string> {\n if (explicitModel) {\n return explicitModel;\n }\n\n if (\n this.configuredDefaultModel &&\n (capability === 'chat' || capability === 'vision')\n ) {\n return this.configuredDefaultModel;\n }\n\n const cached = this.resolvedModelCache.get(capability);\n if (cached) {\n return cached;\n }\n\n const pending = this.selectModel(capability).catch((error) => {\n this.resolvedModelCache.delete(capability);\n throw error;\n });\n this.resolvedModelCache.set(capability, pending);\n return pending;\n }\n\n private async selectModel(\n capability: LiteLLMResolvedCapability,\n ): Promise<string> {\n const models = await super.getModels();\n const model = models.find((candidate) =>\n supportsCapability(candidate, capability),\n );\n\n if (!model) {\n throw new ValidationError(\n `No ${capability} model is available from the LiteLLM gateway`,\n {\n provider: 'litellm',\n capability,\n hint: 'Pass defaultModel explicitly or ensure /models returns a compatible model for this key',\n },\n );\n }\n\n return model.id;\n }\n\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n return super.chat(messages, {\n ...options,\n model: await this.resolveModel('chat', options.model),\n });\n }\n\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n yield* super.stream(messages, {\n ...options,\n model: await this.resolveModel('chat', options.model),\n });\n }\n\n async describeImage(\n image: string | Buffer,\n prompt?: string,\n options: ImageDescriptionOptions = {},\n ): Promise<string> {\n return super.describeImage(image, prompt, {\n ...options,\n model: await this.resolveModel('vision', options.model),\n });\n }\n\n async embed(\n text: string | string[],\n options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embed(text, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async embedImage(\n image: string | Buffer,\n options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embedImage(image, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async generateImage(\n prompt: string,\n options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n return super.generateImage(prompt, {\n ...options,\n model: await this.resolveModel('image_generation', options.model),\n });\n }\n}\n"],"names":[],"mappings":"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"}