@happyvertical/ai 0.94.0 → 0.94.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +2 -2
- package/README.md +37 -0
- package/dist/chunks/{anthropic-ByuqC5mC.js → anthropic-CD9Ud7a5.js} +4 -4
- package/dist/chunks/{anthropic-ByuqC5mC.js.map → anthropic-CD9Ud7a5.js.map} +1 -1
- package/dist/chunks/{bedrock-BeVCmkrS.js → bedrock-Bv1qCquH.js} +3 -3
- package/dist/chunks/{bedrock-BeVCmkrS.js.map → bedrock-Bv1qCquH.js.map} +1 -1
- package/dist/chunks/{bifrost-s3K7V8oU.js → bifrost-D1io0ttr.js} +4 -4
- package/dist/chunks/{bifrost-s3K7V8oU.js.map → bifrost-D1io0ttr.js.map} +1 -1
- package/dist/chunks/{byteplus-modelark-DaF0U4n1.js → byteplus-modelark-BIEXaTWw.js} +4 -4
- package/dist/chunks/{byteplus-modelark-DaF0U4n1.js.map → byteplus-modelark-BIEXaTWw.js.map} +1 -1
- package/dist/chunks/{claude-cli-BH_C62dV.js → claude-cli-Chfnckyj.js} +3 -3
- package/dist/chunks/{claude-cli-BH_C62dV.js.map → claude-cli-Chfnckyj.js.map} +1 -1
- package/dist/chunks/{gateway-admin-B4RSBDmo.js → gateway-admin-04mvbDHC.js} +2 -2
- package/dist/chunks/{gateway-admin-B4RSBDmo.js.map → gateway-admin-04mvbDHC.js.map} +1 -1
- package/dist/chunks/{gemini-GQdugon1.js → gemini-BGwrHeGc.js} +4 -4
- package/dist/chunks/{gemini-GQdugon1.js.map → gemini-BGwrHeGc.js.map} +1 -1
- package/dist/chunks/{huggingface-DxPspM6Q.js → huggingface-mthYVbLR.js} +3 -3
- package/dist/chunks/{huggingface-DxPspM6Q.js.map → huggingface-mthYVbLR.js.map} +1 -1
- package/dist/chunks/{litellm-D8iaHaji.js → litellm-xv_7hqm_.js} +4 -4
- package/dist/chunks/{litellm-D8iaHaji.js.map → litellm-xv_7hqm_.js.map} +1 -1
- package/dist/chunks/{ollama-BrB6bjnz.js → ollama-Bedt8Agy.js} +3 -3
- package/dist/chunks/{ollama-BrB6bjnz.js.map → ollama-Bedt8Agy.js.map} +1 -1
- package/dist/chunks/{openai-compat-video-DNE1Yvp6.js → openai-compat-video-B-xVe9bW.js} +4 -4
- package/dist/chunks/{openai-compat-video-DNE1Yvp6.js.map → openai-compat-video-B-xVe9bW.js.map} +1 -1
- package/dist/chunks/{qwen-tts-CAh1upii.js → qwen-tts-D-UmiMMy.js} +2 -2
- package/dist/chunks/{qwen-tts-CAh1upii.js.map → qwen-tts-D-UmiMMy.js.map} +1 -1
- package/dist/chunks/{rate-limit-C2oPwLGI.js → rate-limit-BVW_1Tey.js} +3 -2
- package/dist/chunks/rate-limit-BVW_1Tey.js.map +1 -0
- package/dist/chunks/{safety-ClfpyKEN.js → safety-BKswBoqy.js} +9 -8
- package/dist/chunks/safety-BKswBoqy.js.map +1 -0
- package/dist/chunks/{seevio-Bbu_hTUF.js → seevio-BJRiguLc.js} +4 -4
- package/dist/chunks/{seevio-Bbu_hTUF.js.map → seevio-BJRiguLc.js.map} +1 -1
- package/dist/chunks/{src-B3ZLEBD2.js → src-DOm4LN_h.js} +23 -16
- package/dist/chunks/{src-B3ZLEBD2.js.map → src-DOm4LN_h.js.map} +1 -1
- package/dist/chunks/{types-sSecpd_N.js → types-BpnNcTU1.js} +3 -2
- package/dist/chunks/types-BpnNcTU1.js.map +1 -0
- package/dist/chunks/typesafe-qpVPDkOR.js +306 -0
- package/dist/chunks/typesafe-qpVPDkOR.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/node/factory.d.ts.map +1 -1
- package/dist/node.js +18 -4
- package/dist/node.js.map +1 -1
- package/dist/shared/factory.d.ts.map +1 -1
- package/dist/shared/providers/typesafe.d.ts +30 -0
- package/dist/shared/providers/typesafe.d.ts.map +1 -0
- package/dist/shared/rate-limit.d.ts.map +1 -1
- package/dist/shared/safety.d.ts.map +1 -1
- package/dist/shared/types.d.ts +87 -3
- package/dist/shared/types.d.ts.map +1 -1
- package/metadata.json +2 -2
- package/package.json +3 -3
- package/dist/chunks/rate-limit-C2oPwLGI.js.map +0 -1
- package/dist/chunks/safety-ClfpyKEN.js.map +0 -1
- package/dist/chunks/types-sSecpd_N.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-BpnNcTU1.js","names":[],"sources":["../../src/shared/types.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 * Cost and output limits applied to every generative request.\n *\n * Provider instances merge partial overrides with the exported safe defaults.\n */\nexport interface AIGenerationLimits {\n /** Maximum text tokens a single request may generate. */\n maxOutputTokens: number;\n\n /** Maximum reasoning/thinking tokens a single request may generate. */\n maxReasoningTokens: number;\n\n /** Maximum images a single image-generation request may produce. */\n maxImagesPerRequest: number;\n\n /** Behavior when a request exceeds one of the configured limits. */\n onExceeded: 'error' | 'clamp';\n}\n\n/** Provider-neutral reasoning controls for a single generation request. */\nexport interface AIReasoningOptions {\n effort?: 'none' | GeminiThinkingLevel;\n maxTokens?: number;\n includeThoughts?: boolean;\n}\n\n/** Request-scoped cancellation, timeout, and reasoning controls. */\nexport interface AIRequestControls {\n /** Caller cancellation signal. Provider timeouts are composed with this signal. */\n signal?: AbortSignal;\n\n /** Request timeout in milliseconds. Overrides the provider default. */\n timeout?: number;\n\n /** Provider-neutral reasoning controls. */\n reasoning?: AIReasoningOptions;\n\n /** Sanitized dimensions attached to lifecycle and usage events. */\n usageTags?: Record<string, string>;\n}\n\n/** Operations reported through {@link AIRequestEvent}. */\nexport type AIRequestOperation =\n | 'decide'\n | 'chat'\n | 'complete'\n | 'message'\n | 'embed'\n | 'embedImage'\n | 'describeImage'\n | 'generateImage'\n | 'stream'\n | 'submitVideoGenerationJob'\n | 'getVideoGenerationJob'\n | 'fetchVideoGenerationResult'\n | 'cancelVideoGenerationJob';\n\n/** Terminal lifecycle state for a provider request. */\nexport type AIRequestStatus =\n | 'succeeded'\n | 'failed'\n | 'timed_out'\n | 'aborted'\n | 'rejected';\n\n/**\n * Prompt-free request lifecycle event.\n *\n * This intentionally excludes request and response content and credentials.\n */\nexport interface AIRequestEvent {\n provider: string;\n model: string;\n operation: AIRequestOperation;\n status: AIRequestStatus;\n attempts: number;\n requestedMaxOutputTokens?: number;\n effectiveMaxOutputTokens?: number;\n duration: number;\n errorCode?: string;\n tags?: Record<string, string>;\n}\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 'openai-compat-video',\n 'byteplus-modelark',\n 'seevio',\n 'typesafe',\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 extends AIRequestControls {\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 * @deprecated Use `reasoning.effort` and `reasoning.maxTokens` instead.\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 * @deprecated Use `reasoning.includeThoughts` instead.\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 extends AIRequestControls {\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 extends AIRequestControls {\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 extends AIRequestControls {\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 extends AIRequestControls {\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 typed, prompt-free decisions.\n *\n * Optional to preserve source compatibility for existing capability literals.\n */\n decisions?: boolean;\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 asynchronous video-generation jobs\n * (submitVideoGenerationJob / getVideoGenerationJob / fetchVideoGenerationResult / cancelVideoGenerationJob)\n */\n videoGeneration: 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/** JSON-compatible data evaluated by a decision provider. */\nexport type DecisionValue =\n | string\n | number\n | boolean\n | null\n | DecisionValue[]\n | { [key: string]: DecisionValue };\n\n/** A boolean predicate, expressed without provider-specific terminology. */\nexport interface PredicateDecisionQuestion {\n type: 'predicate';\n instructions: DecisionValue;\n criteria?: { true?: DecisionValue; false?: DecisionValue };\n}\n\n/** A selection from named, unordered labels. */\nexport interface ChoiceDecisionQuestion {\n type: 'choice';\n instructions: DecisionValue;\n criteria: Record<string, DecisionValue | null>;\n}\n\n/** A position on an ordered rubric. */\nexport interface ScoreDecisionQuestion {\n type: 'score';\n instructions: DecisionValue;\n criteria: DecisionValue[];\n}\n\nexport type DecisionQuestion =\n | PredicateDecisionQuestion\n | ChoiceDecisionQuestion\n | ScoreDecisionQuestion;\n\n/** A typed batch of independent questions evaluated against one state. */\nexport interface DecisionRequest {\n state: DecisionValue;\n questions: Record<string, DecisionQuestion>;\n}\n\n/** Request controls for a decision operation. Generation controls do not apply. */\nexport interface DecisionOptions extends AIRequestControls {\n model?: string;\n}\n\nexport interface PredicateDecisionAnswer {\n type: 'predicate';\n /** Probability that the predicate is true, bounded from zero through one. */\n probability: number;\n}\n\nexport interface ChoiceDecisionAnswer {\n type: 'choice';\n choice: string;\n probabilities: Record<string, number>;\n confidence: number;\n}\n\nexport interface ScoreDecisionAnswer {\n type: 'score';\n score: number;\n probabilities: Record<string, number>;\n confidence: number;\n /** Ordered rubric descriptions corresponding to probability keys 0..n-1. */\n levels: DecisionValue[];\n}\n\nexport type DecisionAnswer =\n | PredicateDecisionAnswer\n | ChoiceDecisionAnswer\n | ScoreDecisionAnswer;\n\nexport interface DecisionResult {\n /** Actual model returned by the provider. */\n model: string;\n usage?: TokenUsage;\n /** Provider/model provenance. Probabilities are not cross-provider calibrated. */\n provenance: { provider: string; model: string };\n answers: Record<string, DecisionAnswer>;\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: AIRequestOperation;\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 * Evaluate provider-neutral, typed questions without using text generation.\n * Optional so third-party implementations remain source compatible.\n */\n decide?(\n request: DecisionRequest,\n options?: DecisionOptions,\n ): Promise<DecisionResult>;\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 // Video Generation Methods (async job abstraction)\n // ============================================================================\n\n /**\n * Submit an asynchronous video-generation job.\n *\n * Returns a JSON-serializable handle rather than the finished video: video\n * generation runs as a long-lived provider-side job, so callers persist the\n * handle and poll {@link AIInterface.getVideoGenerationJob} (optionally\n * across process restarts) until the job completes.\n *\n * @param options - Prompt, reference media, and generation parameters\n * @returns Promise resolving to a serializable job handle\n * @throws {AIError} When video generation is not supported or the request fails\n *\n * @example\n * ```typescript\n * const job = await ai.submitVideoGenerationJob({\n * prompt: 'A drone shot flying over a coastal cliff at sunrise',\n * durationSeconds: 8,\n * resolution: '1080p',\n * });\n * // Persist `job` (it is plain JSON) and resume polling later, even after a restart.\n * ```\n */\n submitVideoGenerationJob(\n options: VideoGenerationOptions,\n ): Promise<VideoGenerationJob>;\n\n /**\n * Check the status of a previously submitted video-generation job.\n *\n * @param handle - The job handle returned by {@link AIInterface.submitVideoGenerationJob}\n * @returns Promise resolving to the current status, and the result once succeeded\n * @throws {AIError} When video generation is not supported or the status check fails\n *\n * @example\n * ```typescript\n * const status = await ai.getVideoGenerationJob(job);\n * if (status.status === 'succeeded') {\n * console.log(status.result?.url);\n * }\n * ```\n */\n getVideoGenerationJob(\n handle: VideoGenerationJob,\n ): Promise<VideoGenerationStatusResult>;\n\n /**\n * Fetch the result of a completed video-generation job.\n *\n * @param handle - The job handle returned by {@link AIInterface.submitVideoGenerationJob}\n * @returns Promise resolving to the generated video's location/bytes and metadata\n * @throws {AIError} When video generation is not supported, the job has not\n * succeeded yet, or the request fails\n *\n * @example\n * ```typescript\n * const result = await ai.fetchVideoGenerationResult(job);\n * if (result.data) fs.writeFileSync('output.mp4', result.data);\n * ```\n */\n fetchVideoGenerationResult(\n handle: VideoGenerationJob,\n ): Promise<VideoGenerationResult>;\n\n /**\n * Cancel an in-flight video-generation job.\n *\n * **Cancellation is best-effort across every provider, not a guarantee.**\n * Consumers should call this on step abort or lease loss so they are not\n * billed for orphaned renders, but must be prepared for it to fail and\n * tolerate that failure rather than treating it as fatal:\n * - Some providers have no cancel endpoint at all for video-generation\n * jobs (e.g. Gemini/Veo) and always throw.\n * - Some providers can only cancel a job that hasn't started rendering\n * yet (e.g. ModelArk/Seedance can cancel a `queued` task but rejects\n * cancellation of a `running` one).\n * - Some gateway-shaped providers may not implement cancellation at all,\n * depending on the backend they proxy to.\n *\n * In every failure case, implementations throw {@link AIError} describing\n * why rather than silently succeeding.\n *\n * @param handle - The job handle returned by {@link AIInterface.submitVideoGenerationJob}\n * @throws {AIError} When video generation is not supported, or cancellation\n * is unsupported/rejected by the provider (best-effort — callers must\n * tolerate this)\n *\n * @example\n * ```typescript\n * controller.signal.addEventListener('abort', () => {\n * ai.cancelVideoGenerationJob(job).catch((error) => {\n * // Best-effort: log and move on, don't treat this as fatal.\n * console.warn('Could not cancel video job', job.jobId, error);\n * });\n * });\n * ```\n */\n cancelVideoGenerationJob(handle: VideoGenerationJob): Promise<void>;\n\n /**\n * Perform a cheap, auth-shaped call to confirm video-generation access is\n * configured correctly (e.g. a list-shaped API call), without submitting a\n * billed generation job.\n *\n * This is intentionally minimal, not free: providers without a dedicated\n * health endpoint reuse a low-cost listing call. Callers on a hot path\n * (e.g. a capability check per task) must cache the result themselves\n * rather than calling this on every iteration.\n *\n * @returns Promise resolving to true when access looks valid\n * @throws {AIError} When video generation is not supported or credentials are invalid\n */\n validateVideoGenerationAccess(): Promise<boolean>;\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` and `byteplus-modelark` also use `requestsPerMinute` and\n * `maxConcurrent` from this object for their local token bucket limiters.\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 * Per-request generation guardrails. Partial overrides are merged with the\n * package defaults; raising a ceiling must therefore be deliberate.\n */\n generationLimits?: Partial<AIGenerationLimits>;\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 * Callback invoked once for every terminal request outcome, including local\n * limit rejection and timeout. Prompt and response content are never emitted.\n */\n onRequest?: (event: AIRequestEvent) => 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 * @deprecated Use request-level `reasoning` controls instead. This alias is\n * normalized through `generationLimits.maxReasoningTokens`.\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 * OpenAI-compatible video-generation provider options.\n *\n * Thin adapter over a `/v1/videos`-shaped REST surface (create / retrieve /\n * cancel / download content), the shape used by LiteLLM's video passthrough\n * and Sora-shaped gateways. Only video-generation methods are implemented;\n * chat, embeddings, and other operations throw `NOT_IMPLEMENTED` — use the\n * `openai` or `litellm` provider types for those against the same gateway.\n */\nexport interface OpenAICompatVideoOptions extends BaseAIOptions {\n type: 'openai-compat-video';\n apiKey?: string;\n /**\n * Base URL for the gateway's OpenAI-compatible API root, e.g.\n * `https://llm.happyvertical.com/v1`. Required (directly or via the\n * `OPENAI_COMPAT_VIDEO_BASE_URL` environment variable).\n */\n baseUrl?: string;\n}\n\n/**\n * BytePlus ModelArk (Seedance) video-generation provider options.\n *\n * Raw-HTTP provider following the `qwen3-tts` precedent: no vendor SDK, just\n * the ModelArk video-generation task API (create task / poll / fetch).\n */\nexport interface ByteplusModelArkOptions extends BaseAIOptions {\n type: 'byteplus-modelark';\n /**\n * ModelArk API key. Falls back to `MODELARK_API_KEY` or the\n * BytePlus-documented `ARK_API_KEY` environment variable.\n */\n apiKey?: string;\n /**\n * ModelArk API root. Defaults to\n * `https://ark.ap-southeast.bytepluses.com/api/v3`.\n */\n baseUrl?: string;\n /**\n * Rate limiting configuration for the local submit-time limiter, shared\n * across provider instances constructed with the same `apiKey` (or the\n * same explicit `rateLimit.key`). Reads `requestsPerMinute` /\n * `maxConcurrent`. Defaults approximate the documented Seedance account\n * limits of QPS 2 / 3 concurrent *submissions* — `maxConcurrent` bounds\n * concurrent submit HTTP requests, not the lifetime of the render tasks\n * they create, which this package has no way to observe.\n */\n rateLimit?: AIRateLimitOptions;\n}\n\n/**\n * Seevio Seedance video-generation provider options.\n *\n * This provider is intentionally distinct from BytePlus ModelArk: Seevio\n * exposes its own asynchronous task API and credit-reservation semantics.\n */\nexport interface SeevioOptions extends BaseAIOptions {\n /** Selects Seevio's native asynchronous video API. */\n type: 'seevio';\n /** API key. Falls back to `SEEVIO_API_KEY` in the Node factory. */\n apiKey?: string;\n /** API root. Defaults to `https://api.seevio.ai`. */\n baseUrl?: string;\n /**\n * Reviewed HTTPS origins that may serve generated videos. Defaults to the\n * official `https://cdn.seevio.ai` origin. Redirect targets are checked\n * against this same allow-list before bytes are downloaded.\n */\n resultUrlOrigins?: string[];\n /** Maximum generated-video download size in bytes. Defaults to 200 MiB. */\n maxResultBytes?: number;\n}\n\n/** TypeSafe System One decision provider. `defaultModel` selects a Jev model. */\nexport interface TypeSafeOptions extends BaseAIOptions {\n type: 'typesafe';\n apiKey?: string;\n /** Defaults to https://api.typesafe.ai/v1. */\n baseUrl?: string;\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 | OpenAICompatVideoOptions\n | ByteplusModelArkOptions\n | SeevioOptions\n | TypeSafeOptions;\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// Video Generation Types\n// ============================================================================\n\n/**\n * Lifecycle status of an asynchronous video-generation job.\n */\nexport type VideoGenerationStatus =\n | 'queued'\n | 'running'\n | 'succeeded'\n | 'failed'\n | 'cancelled';\n\n/**\n * A reference image supplied to guide video generation (image-to-video,\n * first/last frame, subject/style conditioning).\n */\nexport interface VideoGenerationReferenceImage {\n /**\n * Image as a URL, base64 data URL, or raw bytes.\n */\n image: string | Buffer;\n\n /**\n * MIME type of `image`, required when `image` is a Buffer.\n */\n mimeType?: string;\n\n /**\n * Provider-specific reference role, e.g. `'first_frame'`, `'last_frame'`,\n * `'reference_image'`, `'style'`, or `'asset'`. Providers that don't\n * recognize a role fall back to their default reference behavior.\n */\n role?: string;\n}\n\n/** A URL-based image, video, or audio asset used to guide video generation. */\nexport interface VideoGenerationReferenceMedia {\n /** Media category understood by providers that support multimodal references. */\n type: 'image' | 'video' | 'audio';\n /** Public URL the provider can retrieve. */\n url: string;\n /** Optional media MIME type retained for provider-specific validation. */\n mimeType?: string;\n /** Optional playback duration used by providers with media-duration limits. */\n durationSeconds?: number;\n}\n\n/**\n * Options for submitting an asynchronous video-generation job.\n */\nexport interface VideoGenerationOptions extends AIRequestControls {\n /**\n * Model to use for video generation (provider-specific id).\n */\n model?: string;\n\n /**\n * Text description of the desired video. Optional for providers that\n * support pure image-to-video generation from `referenceImages` alone.\n */\n prompt?: string;\n\n /**\n * Explicit statement of what should NOT appear in the generated video.\n * Support varies by provider; unsupported providers ignore this field.\n */\n negativePrompt?: string;\n\n /**\n * Reference image(s) for image-to-video or style/subject conditioning.\n */\n referenceImages?: VideoGenerationReferenceImage[];\n\n /**\n * Provider-neutral URL-based image, video, and audio reference assets.\n * Existing `referenceImages` remains supported for providers that accept\n * bytes or data URLs; providers such as Seevio that retrieve public assets\n * directly require HTTPS URLs here.\n */\n referenceMedia?: VideoGenerationReferenceMedia[];\n\n /**\n * Duration of the generated clip in seconds.\n */\n durationSeconds?: number;\n\n /**\n * Resolution label such as `'720p'` or `'1080p'` (provider-dependent).\n */\n resolution?: string;\n\n /**\n * Aspect ratio such as `'16:9'`, `'9:16'`, or `'1:1'`.\n */\n aspectRatio?: string;\n\n /** Whether a provider that supports native audio should generate it. */\n generateAudio?: boolean;\n\n /** Whether a provider that supports it should return a final-frame URL. */\n returnLastFrame?: boolean;\n\n /**\n * Frames per second for the generated video. Support varies by provider.\n */\n fps?: number;\n\n /**\n * Random seed for deterministic generation where supported.\n */\n seed?: number;\n\n /**\n * Custom tags to attach to the usage event emitted for this job.\n * Merged over any global `usageTags` from provider options.\n */\n usageTags?: Record<string, string>;\n}\n\n/**\n * Serializable handle for an in-flight or completed video-generation job.\n *\n * Consumers persist this (e.g. as JSON in a database checkpoint) so they can\n * resume polling after a process restart. Every field is plain\n * JSON-serializable data — no closures, streams, or provider client\n * instances — so the handle round-trips through `JSON.stringify` /\n * `JSON.parse` unchanged.\n *\n * `jobId` and `provider` are the load-bearing fields every provider needs to\n * resume polling; `raw` may carry provider-specific resume context (e.g.\n * which auth mode a job was submitted under) that is opaque to callers but\n * required for a provider to correctly service the resumed request.\n * Consumers must persist and pass back the **whole** handle verbatim —\n * reconstructing a handle from just `jobId` (dropping `raw`) is not\n * guaranteed to work.\n */\nexport interface VideoGenerationJob {\n /**\n * Provider-assigned job or operation identifier.\n */\n jobId: string;\n\n /**\n * Provider that created the job (e.g. `'gemini'`, `'byteplus-modelark'`).\n */\n provider: string;\n\n /**\n * Model used for the job.\n */\n model: string;\n\n /**\n * ISO-8601 timestamp for when the job was submitted.\n */\n createdAt: string;\n\n /**\n * Provider-specific data needed to resume polling (e.g. an operation\n * resource name shape, or routing hints). Opaque to callers; always\n * JSON-serializable.\n */\n raw?: Record<string, unknown>;\n}\n\n/**\n * Result of a completed video-generation job.\n */\nexport interface VideoGenerationResult {\n /**\n * Temporary or permanent URL to the generated video, when the provider\n * returns a downloadable link instead of inline bytes.\n */\n url?: string;\n\n /**\n * Raw generated video bytes, when the provider returns inline data\n * instead of (or in addition to) a URL.\n */\n data?: Buffer;\n\n /**\n * MIME type of the generated video (e.g. `'video/mp4'`).\n */\n mimeType: string;\n\n /**\n * Duration of the generated video in seconds, where reported.\n */\n durationSeconds?: number;\n\n /**\n * Width of the generated video in pixels, where reported.\n */\n width?: number;\n\n /**\n * Height of the generated video in pixels, where reported.\n */\n height?: number;\n\n /** Time at which a temporary result URL expires, when the provider reports it. */\n expiresAt?: string;\n\n /** Optional URL for a returned final frame, when requested and supported. */\n lastFrameUrl?: string;\n}\n\n/** Provider-neutral credit reservation and settlement metadata for a video job. */\nexport interface VideoGenerationBillingMetadata {\n /** Credits reserved or settled for the task, when the provider reports them. */\n credits?: number;\n /** Normalized reservation lifecycle state. */\n status?: 'reserved' | 'charged' | 'refunded' | 'refund_failed';\n /** An unrecognized provider billing state retained for diagnostics. */\n rawStatus?: string;\n}\n\n/**\n * Status snapshot for a video-generation job, as returned by\n * {@link AIInterface.getVideoGenerationJob}.\n */\nexport interface VideoGenerationStatusResult {\n /**\n * Current lifecycle status of the job.\n */\n status: VideoGenerationStatus;\n\n /**\n * Provider-reported progress percentage (0-100), where available.\n */\n progress?: number;\n\n /**\n * Human-readable error message, populated when `status` is `'failed'`.\n */\n error?: string;\n\n /**\n * The provider's raw, unrecognized status string. Only set when the\n * provider reported a status value this package doesn't have an explicit\n * mapping for. Providers must never invent a terminal state (`'failed'`\n * or `'succeeded'`) for a status they don't understand — an unrecognized\n * value is mapped to `'running'` with the original string preserved here,\n * so a mid-render status a provider adds later (e.g. a new gateway\n * synonym for \"processing\") never gets misread as a failure that would\n * cause a caller to resubmit and double-bill.\n */\n rawStatus?: string;\n\n /**\n * Populated once `status` is `'succeeded'`. For providers whose status\n * endpoint returns the payload inline, this is equivalent to calling\n * {@link AIInterface.fetchVideoGenerationResult} with the same handle.\n * Providers whose result requires a separate download (e.g. large binary\n * content behind its own endpoint) may populate metadata here (mimeType,\n * durationSeconds, width, height) while leaving `url`/`data` unset until\n * `fetchVideoGenerationResult` is called explicitly.\n */\n result?: VideoGenerationResult;\n\n /** Provider-reported credit reservation or settlement metadata. */\n billing?: VideoGenerationBillingMetadata;\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"],"mappings":";;;;AAgGA,IAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;AA0CA,SAAgB,mBAAmB,SAAyC;CAC1E,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,OAAO,QACJ,QAAQ,SAAkC,KAAK,SAAS,MAAM,CAAC,CAC/D,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,IAAI;AACd;;;;;;;;;;AAu/DA,IAAa,UAAb,cAA6B,MAAM;CAGxB;CACA;CACA;CACA;CALT,YACE,SACA,MACA,UACA,OACA,YAA4B,OAC5B;EACA,MAAM,OAAO;EALN,KAAA,OAAA;EACA,KAAA,WAAA;EACA,KAAA,QAAA;EACA,KAAA,YAAA;EAGP,KAAK,OAAO;CACd;AACF;;;;;;AAOA,IAAa,sBAAb,cAAyC,QAAQ;CAC/C,YAAY,UAAmB;EAC7B,MAAM,yBAAyB,cAAc,UAAU,KAAA,GAAW,KAAK;EACvE,KAAK,OAAO;CACd;AACF;;;;;;;AAQA,IAAa,iBAAb,cAAoC,QAAQ;CAC1C;CAEA,YAAY,UAAmB,YAAqB;EAClD,MACE,sBAAsB,aAAa,iBAAiB,WAAW,KAAK,MACpE,cACA,UACA,KAAA,GACA,IACF;EACA,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;;;;;;;AAQA,IAAa,qBAAb,cAAwC,QAAQ;CAC9C,YAAY,OAAe,UAAmB;EAC5C,MACE,oBAAoB,SACpB,mBACA,UACA,OACA,KACF;EACA,KAAK,OAAO;CACd;AACF;;;;;;;AAQA,IAAa,qBAAb,cAAwC,QAAQ;CAC9C,YAAY,UAAmB,OAAgB;EAC7C,MACE,wCACA,2BACA,UACA,OACA,KACF;EACA,KAAK,OAAO;CACd;AACF;;;;;;;AAQA,IAAa,qBAAb,cAAwC,QAAQ;CAC9C,YAAY,UAAmB,OAAgB;EAC7C,MACE,sCACA,oBACA,UACA,OACA,KACF;EACA,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { r as AuthenticationError, s as RateLimitError, t as AIError } from "./types-BpnNcTU1.js";
|
|
2
|
+
import { n as extractRetryAfterSeconds } from "./rate-limit-BVW_1Tey.js";
|
|
3
|
+
import { a as normalizeBaseAIOptions, c as prepareRequestControls } from "./safety-BKswBoqy.js";
|
|
4
|
+
import { t as emitUsage } from "./usage-C1Y1Nlg4.js";
|
|
5
|
+
import { ValidationError } from "@happyvertical/utils";
|
|
6
|
+
//#region src/shared/providers/typesafe.ts
|
|
7
|
+
/**
|
|
8
|
+
* TypeSafe System One typed-decision provider.
|
|
9
|
+
*
|
|
10
|
+
* The public SDK contract deliberately calls TypeSafe's `noul` primitive a
|
|
11
|
+
* `predicate`; vendor terminology stays confined to this wire translation.
|
|
12
|
+
*/
|
|
13
|
+
var PROVIDER = "typesafe";
|
|
14
|
+
var DEFAULT_BASE_URL = "https://api.typesafe.ai/v1";
|
|
15
|
+
var DEFAULT_MODEL = "jev-latest";
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function finite(value, field) {
|
|
20
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new AIError(`TypeSafe response ${field} must be finite`, "INVALID_RESPONSE", PROVIDER);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function probability(value, field) {
|
|
24
|
+
const result = finite(value, field);
|
|
25
|
+
if (result < 0 || result > 1) throw new AIError(`TypeSafe response ${field} must be between 0 and 1`, "INVALID_RESPONSE", PROVIDER);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function tokenCount(value, field) {
|
|
29
|
+
const result = finite(value, field);
|
|
30
|
+
if (!Number.isSafeInteger(result) || result < 0) throw new AIError(`TypeSafe response ${field} must be a non-negative integer`, "INVALID_RESPONSE", PROVIDER);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
function sameDecisionValue(left, right) {
|
|
34
|
+
if (left === right) return true;
|
|
35
|
+
if (Array.isArray(left)) return Array.isArray(right) && left.length === right.length && left.every((value, index) => sameDecisionValue(value, right[index]));
|
|
36
|
+
if (isRecord(left)) return isRecord(right) && Object.keys(left).length === Object.keys(right).length && Object.entries(left).every(([key, value]) => key in right && sameDecisionValue(value, right[key]));
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function validateValue(value, field) {
|
|
40
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
41
|
+
if (typeof value === "number") {
|
|
42
|
+
if (!Number.isFinite(value)) throw new ValidationError(`${field} must contain finite numbers`, { provider: PROVIDER });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
value.forEach((item, index) => {
|
|
47
|
+
validateValue(item, `${field}[${index}]`);
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (isRecord(value)) {
|
|
52
|
+
Object.entries(value).forEach(([key, item]) => {
|
|
53
|
+
validateValue(item, `${field}.${key}`);
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
throw new ValidationError(`${field} must be JSON-compatible`, { provider: PROVIDER });
|
|
58
|
+
}
|
|
59
|
+
function validateRequest(request) {
|
|
60
|
+
validateValue(request.state, "state");
|
|
61
|
+
const entries = Object.entries(request.questions || {});
|
|
62
|
+
if (entries.length === 0) throw new ValidationError("questions must not be empty", { provider: PROVIDER });
|
|
63
|
+
for (const [id, question] of entries) {
|
|
64
|
+
if (!id.trim()) throw new ValidationError("question IDs must not be empty", { provider: PROVIDER });
|
|
65
|
+
validateValue(question.instructions, `questions.${id}.instructions`);
|
|
66
|
+
if (question.type === "predicate") {
|
|
67
|
+
if (question.criteria) for (const [key, value] of Object.entries(question.criteria)) validateValue(value, `questions.${id}.criteria.${key}`);
|
|
68
|
+
} else if (question.type === "choice") {
|
|
69
|
+
const options = Object.keys(question.criteria || {});
|
|
70
|
+
if (options.length === 0 || options.length > 255) throw new ValidationError(`questions.${id}.criteria must contain 1 to 255 options`, { provider: PROVIDER });
|
|
71
|
+
for (const [key, value] of Object.entries(question.criteria)) if (value !== null) validateValue(value, `questions.${id}.criteria.${key}`);
|
|
72
|
+
} else if (question.type === "score") {
|
|
73
|
+
if (question.criteria.length < 2 || question.criteria.length > 10) throw new ValidationError(`questions.${id}.criteria must contain 2 to 10 ordered levels`, { provider: PROVIDER });
|
|
74
|
+
question.criteria.forEach((value, index) => {
|
|
75
|
+
validateValue(value, `questions.${id}.criteria[${index}]`);
|
|
76
|
+
});
|
|
77
|
+
} else throw new ValidationError(`questions.${id}.type is unsupported`, { provider: PROVIDER });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function wireQuestion(question) {
|
|
81
|
+
if (question.type === "predicate") return {
|
|
82
|
+
type: "noul",
|
|
83
|
+
instructions: question.instructions,
|
|
84
|
+
...question.criteria ? { criteria: question.criteria } : {}
|
|
85
|
+
};
|
|
86
|
+
return question;
|
|
87
|
+
}
|
|
88
|
+
function distribution(value, expected, field) {
|
|
89
|
+
if (!isRecord(value) || Object.keys(value).length !== expected.length || expected.some((key) => !(key in value))) throw new AIError(`TypeSafe response ${field} must cover exactly the requested values`, "INVALID_RESPONSE", PROVIDER);
|
|
90
|
+
const result = Object.fromEntries(expected.map((key) => [key, probability(value[key], `${field}.${key}`)]));
|
|
91
|
+
const sum = Object.values(result).reduce((total, item) => total + item, 0);
|
|
92
|
+
if (Math.abs(sum - 1) > 1e-6) throw new AIError(`TypeSafe response ${field} must sum to 1`, "INVALID_RESPONSE", PROVIDER);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
function answer(question, raw, id) {
|
|
96
|
+
if (!isRecord(raw)) throw new AIError(`TypeSafe response answers.${id} must be an object`, "INVALID_RESPONSE", PROVIDER);
|
|
97
|
+
if (question.type === "predicate") {
|
|
98
|
+
if (raw.type !== "noul") throw new AIError(`TypeSafe response answers.${id} type does not match predicate`, "INVALID_RESPONSE", PROVIDER);
|
|
99
|
+
return {
|
|
100
|
+
type: "predicate",
|
|
101
|
+
probability: probability(raw.noul, `answers.${id}.noul`)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (raw.type !== question.type) throw new AIError(`TypeSafe response answers.${id} type does not match request`, "INVALID_RESPONSE", PROVIDER);
|
|
105
|
+
if (question.type === "choice") {
|
|
106
|
+
const choices = Object.keys(question.criteria);
|
|
107
|
+
if (typeof raw.choice !== "string" || !choices.includes(raw.choice)) throw new AIError(`TypeSafe response answers.${id}.choice is not a requested option`, "INVALID_RESPONSE", PROVIDER);
|
|
108
|
+
return {
|
|
109
|
+
type: "choice",
|
|
110
|
+
choice: raw.choice,
|
|
111
|
+
probabilities: distribution(raw.probabilities, choices, `answers.${id}.probabilities`),
|
|
112
|
+
confidence: probability(raw.confidence, `answers.${id}.confidence`)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const levels = question.criteria;
|
|
116
|
+
const levelKeys = levels.map((_, index) => String(index));
|
|
117
|
+
const score = finite(raw.score, `answers.${id}.score`);
|
|
118
|
+
if (score < 0 || score > levels.length - 1) throw new AIError(`TypeSafe response answers.${id}.score is outside the requested rubric`, "INVALID_RESPONSE", PROVIDER);
|
|
119
|
+
const legend = raw.legend;
|
|
120
|
+
if (!isRecord(legend) || levelKeys.some((key, index) => !sameDecisionValue(levels[index], legend[key]))) throw new AIError(`TypeSafe response answers.${id}.legend does not match the requested rubric`, "INVALID_RESPONSE", PROVIDER);
|
|
121
|
+
return {
|
|
122
|
+
type: "score",
|
|
123
|
+
score,
|
|
124
|
+
probabilities: distribution(raw.probabilities, levelKeys, `answers.${id}.probabilities`),
|
|
125
|
+
confidence: probability(raw.confidence, `answers.${id}.confidence`),
|
|
126
|
+
levels
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** A decision-only adapter for TypeSafe's System One endpoint. */
|
|
130
|
+
var TypeSafeProvider = class {
|
|
131
|
+
options;
|
|
132
|
+
constructor(options) {
|
|
133
|
+
this.options = normalizeBaseAIOptions({
|
|
134
|
+
...options,
|
|
135
|
+
baseUrl: (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "")
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async decide(request, options = {}) {
|
|
139
|
+
validateRequest(request);
|
|
140
|
+
const apiKey = this.options.apiKey;
|
|
141
|
+
if (!apiKey) throw new AuthenticationError(PROVIDER);
|
|
142
|
+
const controls = prepareRequestControls(this.options, options);
|
|
143
|
+
const startedAt = Date.now();
|
|
144
|
+
const model = options.model || this.options.defaultModel || DEFAULT_MODEL;
|
|
145
|
+
try {
|
|
146
|
+
const response = await fetch(`${this.options.baseUrl}/systemone`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: {
|
|
149
|
+
Authorization: `Bearer ${apiKey}`,
|
|
150
|
+
"Content-Type": "application/json",
|
|
151
|
+
...this.options.headers
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
state: request.state,
|
|
155
|
+
model,
|
|
156
|
+
questions: Object.fromEntries(Object.entries(request.questions).map(([id, question]) => [id, wireQuestion(question)]))
|
|
157
|
+
}),
|
|
158
|
+
signal: controls.signal
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
if (response.status === 401) throw new AuthenticationError(PROVIDER);
|
|
162
|
+
if (response.status === 429 || response.status === 529) throw new RateLimitError(PROVIDER, extractRetryAfterSeconds(response.headers));
|
|
163
|
+
const text = await response.text();
|
|
164
|
+
throw new AIError(`TypeSafe request failed (${response.status}): ${text.slice(0, 500)}`, "API_ERROR", PROVIDER, model, response.status >= 500);
|
|
165
|
+
}
|
|
166
|
+
let body;
|
|
167
|
+
try {
|
|
168
|
+
body = await response.json();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (error instanceof SyntaxError) throw new AIError("TypeSafe response contained invalid JSON", "INVALID_RESPONSE", PROVIDER, model);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
if (!body || typeof body.model !== "string" || !body.model) throw new AIError("TypeSafe response model is required", "INVALID_RESPONSE", PROVIDER);
|
|
174
|
+
if (!isRecord(body.answers)) throw new AIError("TypeSafe response answers is required", "INVALID_RESPONSE", PROVIDER);
|
|
175
|
+
const responseAnswers = body.answers;
|
|
176
|
+
const ids = Object.keys(request.questions);
|
|
177
|
+
if (Object.keys(responseAnswers).length !== ids.length || ids.some((id) => !(id in responseAnswers))) throw new AIError("TypeSafe response answers must match requested question IDs", "INVALID_RESPONSE", PROVIDER);
|
|
178
|
+
const answers = Object.fromEntries(ids.map((id) => [id, answer(request.questions[id], responseAnswers[id], id)]));
|
|
179
|
+
let usage;
|
|
180
|
+
if (body.usage !== void 0) {
|
|
181
|
+
if (!isRecord(body.usage) || !("input_tokens" in body.usage) || !("output_tokens" in body.usage)) throw new AIError("TypeSafe response usage must include input_tokens and output_tokens", "INVALID_RESPONSE", PROVIDER);
|
|
182
|
+
const input = tokenCount(body.usage.input_tokens, "usage.input_tokens");
|
|
183
|
+
const output = tokenCount(body.usage.output_tokens, "usage.output_tokens");
|
|
184
|
+
usage = {
|
|
185
|
+
promptTokens: input,
|
|
186
|
+
completionTokens: output,
|
|
187
|
+
totalTokens: input + output
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
emitUsage(this.options, PROVIDER, "decide", body.model, usage, startedAt, options.usageTags);
|
|
191
|
+
return {
|
|
192
|
+
model: body.model,
|
|
193
|
+
...usage ? { usage } : {},
|
|
194
|
+
provenance: {
|
|
195
|
+
provider: PROVIDER,
|
|
196
|
+
model: body.model
|
|
197
|
+
},
|
|
198
|
+
answers
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (controls.didTimeout()) throw new AIError(`TypeSafe request timed out after ${controls.timeout}ms`, "REQUEST_TIMEOUT", PROVIDER, model, true);
|
|
202
|
+
if (controls.signal.aborted && !(error instanceof AIError)) throw new AIError("TypeSafe request was aborted", "REQUEST_ABORTED", PROVIDER, model);
|
|
203
|
+
if (!(error instanceof AIError)) throw new AIError(`Network error calling TypeSafe System One: ${error instanceof Error ? error.message : String(error)}`, "NETWORK_ERROR", PROVIDER, model, true);
|
|
204
|
+
throw error;
|
|
205
|
+
} finally {
|
|
206
|
+
controls.cleanup();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async getModels() {
|
|
210
|
+
return [{
|
|
211
|
+
id: this.options.defaultModel || DEFAULT_MODEL,
|
|
212
|
+
name: "TypeSafe Jev",
|
|
213
|
+
description: "TypeSafe System One typed decisions",
|
|
214
|
+
contextLength: 0,
|
|
215
|
+
capabilities: ["decisions"],
|
|
216
|
+
supportsFunctions: false,
|
|
217
|
+
supportsVision: false
|
|
218
|
+
}];
|
|
219
|
+
}
|
|
220
|
+
async getCapabilities() {
|
|
221
|
+
return {
|
|
222
|
+
decisions: true,
|
|
223
|
+
chat: false,
|
|
224
|
+
completion: false,
|
|
225
|
+
embeddings: false,
|
|
226
|
+
streaming: false,
|
|
227
|
+
functions: false,
|
|
228
|
+
vision: false,
|
|
229
|
+
fineTuning: false,
|
|
230
|
+
imageEmbeddings: false,
|
|
231
|
+
imageGeneration: false,
|
|
232
|
+
videoGeneration: false,
|
|
233
|
+
tts: false,
|
|
234
|
+
voiceCloning: false,
|
|
235
|
+
voiceDesign: false,
|
|
236
|
+
maxContextLength: 0,
|
|
237
|
+
supportedOperations: ["decide"]
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
unsupported() {
|
|
241
|
+
throw new AIError("This operation is not supported by the TypeSafe decision-only provider.", "NOT_IMPLEMENTED", PROVIDER);
|
|
242
|
+
}
|
|
243
|
+
async chat(_a, _b) {
|
|
244
|
+
return this.unsupported();
|
|
245
|
+
}
|
|
246
|
+
async complete(_a, _b) {
|
|
247
|
+
return this.unsupported();
|
|
248
|
+
}
|
|
249
|
+
async message(_a, _b) {
|
|
250
|
+
return this.unsupported();
|
|
251
|
+
}
|
|
252
|
+
async embed(_a, _b) {
|
|
253
|
+
return this.unsupported();
|
|
254
|
+
}
|
|
255
|
+
async embedImage(_a, _b) {
|
|
256
|
+
return this.unsupported();
|
|
257
|
+
}
|
|
258
|
+
async describeImage(_a, _b, _c) {
|
|
259
|
+
return this.unsupported();
|
|
260
|
+
}
|
|
261
|
+
async generateImage(_a, _b) {
|
|
262
|
+
return this.unsupported();
|
|
263
|
+
}
|
|
264
|
+
stream(_a, _b) {
|
|
265
|
+
const error = new AIError("Chat streaming is not supported by the TypeSafe decision-only provider.", "NOT_IMPLEMENTED", PROVIDER);
|
|
266
|
+
return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
|
|
267
|
+
}
|
|
268
|
+
async countTokens(_a) {
|
|
269
|
+
return this.unsupported();
|
|
270
|
+
}
|
|
271
|
+
async submitVideoGenerationJob(_a) {
|
|
272
|
+
return this.unsupported();
|
|
273
|
+
}
|
|
274
|
+
async getVideoGenerationJob(_a) {
|
|
275
|
+
return this.unsupported();
|
|
276
|
+
}
|
|
277
|
+
async fetchVideoGenerationResult(_a) {
|
|
278
|
+
return this.unsupported();
|
|
279
|
+
}
|
|
280
|
+
async cancelVideoGenerationJob(_a) {
|
|
281
|
+
return this.unsupported();
|
|
282
|
+
}
|
|
283
|
+
async validateVideoGenerationAccess() {
|
|
284
|
+
return this.unsupported();
|
|
285
|
+
}
|
|
286
|
+
async synthesizeSpeech(_a, _b) {
|
|
287
|
+
return this.unsupported();
|
|
288
|
+
}
|
|
289
|
+
streamSpeech(_a, _b) {
|
|
290
|
+
const error = new AIError("TTS streaming is not supported by the TypeSafe decision-only provider.", "NOT_IMPLEMENTED", PROVIDER);
|
|
291
|
+
return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
|
|
292
|
+
}
|
|
293
|
+
async cloneVoice(_a) {
|
|
294
|
+
return this.unsupported();
|
|
295
|
+
}
|
|
296
|
+
async designVoice(_a) {
|
|
297
|
+
return this.unsupported();
|
|
298
|
+
}
|
|
299
|
+
async getVoices(_a) {
|
|
300
|
+
return this.unsupported();
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
//#endregion
|
|
304
|
+
export { TypeSafeProvider };
|
|
305
|
+
|
|
306
|
+
//# sourceMappingURL=typesafe-qpVPDkOR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typesafe-qpVPDkOR.js","names":[],"sources":["../../src/shared/providers/typesafe.ts"],"sourcesContent":["/**\n * TypeSafe System One typed-decision provider.\n *\n * The public SDK contract deliberately calls TypeSafe's `noul` primitive a\n * `predicate`; vendor terminology stays confined to this wire translation.\n */\n\nimport { ValidationError } from '@happyvertical/utils';\n\nimport { extractRetryAfterSeconds } from '../rate-limit';\nimport { normalizeBaseAIOptions, prepareRequestControls } from '../safety';\nimport type {\n AICapabilities,\n AIInterface,\n AIMessage,\n AIModel,\n AIResponse,\n ChatOptions,\n ChoiceDecisionQuestion,\n CompletionOptions,\n DecisionAnswer,\n DecisionOptions,\n DecisionQuestion,\n DecisionRequest,\n DecisionResult,\n DecisionValue,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n MessageOptions,\n ScoreDecisionQuestion,\n TTSOptions,\n TTSResponse,\n TypeSafeOptions,\n VideoGenerationJob,\n VideoGenerationOptions,\n VideoGenerationResult,\n VideoGenerationStatusResult,\n Voice,\n VoiceCloneOptions,\n VoiceDesignOptions,\n VoiceListOptions,\n} from '../types';\nimport { AIError, AuthenticationError, RateLimitError } from '../types';\nimport { emitUsage } from './usage';\n\nconst PROVIDER = 'typesafe';\nconst DEFAULT_BASE_URL = 'https://api.typesafe.ai/v1';\nconst DEFAULT_MODEL = 'jev-latest';\n\ntype WireQuestion =\n | {\n type: 'noul';\n instructions: DecisionValue;\n criteria?: Record<string, DecisionValue>;\n }\n | {\n type: 'choice';\n instructions: DecisionValue;\n criteria: Record<string, DecisionValue | null>;\n }\n | { type: 'score'; instructions: DecisionValue; criteria: DecisionValue[] };\n\ninterface WireResponse {\n model?: unknown;\n answers?: unknown;\n usage?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction finite(value: unknown, field: string): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new AIError(\n `TypeSafe response ${field} must be finite`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n }\n return value;\n}\n\nfunction probability(value: unknown, field: string): number {\n const result = finite(value, field);\n if (result < 0 || result > 1) {\n throw new AIError(\n `TypeSafe response ${field} must be between 0 and 1`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n }\n return result;\n}\n\nfunction tokenCount(value: unknown, field: string): number {\n const result = finite(value, field);\n if (!Number.isSafeInteger(result) || result < 0) {\n throw new AIError(\n `TypeSafe response ${field} must be a non-negative integer`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n }\n return result;\n}\n\nfunction sameDecisionValue(left: DecisionValue, right: unknown): boolean {\n if (left === right) return true;\n if (Array.isArray(left)) {\n return (\n Array.isArray(right) &&\n left.length === right.length &&\n left.every((value, index) => sameDecisionValue(value, right[index]))\n );\n }\n if (isRecord(left)) {\n return (\n isRecord(right) &&\n Object.keys(left).length === Object.keys(right).length &&\n Object.entries(left).every(\n ([key, value]) => key in right && sameDecisionValue(value, right[key]),\n )\n );\n }\n return false;\n}\n\nfunction validateValue(\n value: unknown,\n field: string,\n): asserts value is DecisionValue {\n if (value === null || typeof value === 'string' || typeof value === 'boolean')\n return;\n if (typeof value === 'number') {\n if (!Number.isFinite(value))\n throw new ValidationError(`${field} must contain finite numbers`, {\n provider: PROVIDER,\n });\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, index) => {\n validateValue(item, `${field}[${index}]`);\n });\n return;\n }\n if (isRecord(value)) {\n Object.entries(value).forEach(([key, item]) => {\n validateValue(item, `${field}.${key}`);\n });\n return;\n }\n throw new ValidationError(`${field} must be JSON-compatible`, {\n provider: PROVIDER,\n });\n}\n\nfunction validateRequest(request: DecisionRequest): void {\n validateValue(request.state, 'state');\n const entries = Object.entries(request.questions || {});\n if (entries.length === 0)\n throw new ValidationError('questions must not be empty', {\n provider: PROVIDER,\n });\n for (const [id, question] of entries) {\n if (!id.trim())\n throw new ValidationError('question IDs must not be empty', {\n provider: PROVIDER,\n });\n validateValue(question.instructions, `questions.${id}.instructions`);\n if (question.type === 'predicate') {\n if (question.criteria) {\n for (const [key, value] of Object.entries(question.criteria))\n validateValue(value, `questions.${id}.criteria.${key}`);\n }\n } else if (question.type === 'choice') {\n const options = Object.keys(question.criteria || {});\n if (options.length === 0 || options.length > 255)\n throw new ValidationError(\n `questions.${id}.criteria must contain 1 to 255 options`,\n { provider: PROVIDER },\n );\n for (const [key, value] of Object.entries(question.criteria))\n if (value !== null)\n validateValue(value, `questions.${id}.criteria.${key}`);\n } else if (question.type === 'score') {\n if (question.criteria.length < 2 || question.criteria.length > 10)\n throw new ValidationError(\n `questions.${id}.criteria must contain 2 to 10 ordered levels`,\n { provider: PROVIDER },\n );\n question.criteria.forEach((value, index) => {\n validateValue(value, `questions.${id}.criteria[${index}]`);\n });\n } else {\n throw new ValidationError(`questions.${id}.type is unsupported`, {\n provider: PROVIDER,\n });\n }\n }\n}\n\nfunction wireQuestion(question: DecisionQuestion): WireQuestion {\n if (question.type === 'predicate')\n return {\n type: 'noul',\n instructions: question.instructions,\n ...(question.criteria ? { criteria: question.criteria } : {}),\n };\n return question;\n}\n\nfunction distribution(\n value: unknown,\n expected: string[],\n field: string,\n): Record<string, number> {\n if (\n !isRecord(value) ||\n Object.keys(value).length !== expected.length ||\n expected.some((key) => !(key in value))\n ) {\n throw new AIError(\n `TypeSafe response ${field} must cover exactly the requested values`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n }\n const result = Object.fromEntries(\n expected.map((key) => [key, probability(value[key], `${field}.${key}`)]),\n );\n const sum = Object.values(result).reduce((total, item) => total + item, 0);\n if (Math.abs(sum - 1) > 1e-6)\n throw new AIError(\n `TypeSafe response ${field} must sum to 1`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n return result;\n}\n\nfunction answer(\n question: DecisionQuestion,\n raw: unknown,\n id: string,\n): DecisionAnswer {\n if (!isRecord(raw))\n throw new AIError(\n `TypeSafe response answers.${id} must be an object`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n if (question.type === 'predicate') {\n if (raw.type !== 'noul')\n throw new AIError(\n `TypeSafe response answers.${id} type does not match predicate`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n return {\n type: 'predicate',\n probability: probability(raw.noul, `answers.${id}.noul`),\n };\n }\n if (raw.type !== question.type)\n throw new AIError(\n `TypeSafe response answers.${id} type does not match request`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n if (question.type === 'choice') {\n const choices = Object.keys(question.criteria);\n if (typeof raw.choice !== 'string' || !choices.includes(raw.choice))\n throw new AIError(\n `TypeSafe response answers.${id}.choice is not a requested option`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n return {\n type: 'choice',\n choice: raw.choice,\n probabilities: distribution(\n raw.probabilities,\n choices,\n `answers.${id}.probabilities`,\n ),\n confidence: probability(raw.confidence, `answers.${id}.confidence`),\n };\n }\n const scoreQuestion = question as ScoreDecisionQuestion;\n const levels = scoreQuestion.criteria;\n const levelKeys = levels.map((_, index) => String(index));\n const score = finite(raw.score, `answers.${id}.score`);\n if (score < 0 || score > levels.length - 1)\n throw new AIError(\n `TypeSafe response answers.${id}.score is outside the requested rubric`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n const legend = raw.legend;\n if (\n !isRecord(legend) ||\n levelKeys.some(\n (key, index) => !sameDecisionValue(levels[index], legend[key]),\n )\n )\n throw new AIError(\n `TypeSafe response answers.${id}.legend does not match the requested rubric`,\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n return {\n type: 'score',\n score,\n probabilities: distribution(\n raw.probabilities,\n levelKeys,\n `answers.${id}.probabilities`,\n ),\n confidence: probability(raw.confidence, `answers.${id}.confidence`),\n levels,\n };\n}\n\n/** A decision-only adapter for TypeSafe's System One endpoint. */\nexport class TypeSafeProvider implements AIInterface {\n private readonly options: TypeSafeOptions;\n\n constructor(options: TypeSafeOptions) {\n this.options = normalizeBaseAIOptions({\n ...options,\n baseUrl: (options.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, ''),\n });\n }\n\n async decide(\n request: DecisionRequest,\n options: DecisionOptions = {},\n ): Promise<DecisionResult> {\n validateRequest(request);\n const apiKey = this.options.apiKey;\n if (!apiKey) throw new AuthenticationError(PROVIDER);\n const controls = prepareRequestControls(this.options, options);\n const startedAt = Date.now();\n const model = options.model || this.options.defaultModel || DEFAULT_MODEL;\n try {\n const response = await fetch(`${this.options.baseUrl}/systemone`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n ...this.options.headers,\n },\n body: JSON.stringify({\n state: request.state,\n model,\n questions: Object.fromEntries(\n Object.entries(request.questions).map(([id, question]) => [\n id,\n wireQuestion(question),\n ]),\n ),\n }),\n signal: controls.signal,\n });\n if (!response.ok) {\n if (response.status === 401) throw new AuthenticationError(PROVIDER);\n if (response.status === 429 || response.status === 529)\n throw new RateLimitError(\n PROVIDER,\n extractRetryAfterSeconds(response.headers),\n );\n const text = await response.text();\n throw new AIError(\n `TypeSafe request failed (${response.status}): ${text.slice(0, 500)}`,\n 'API_ERROR',\n PROVIDER,\n model,\n response.status >= 500,\n );\n }\n let body: WireResponse;\n try {\n body = (await response.json()) as WireResponse;\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new AIError(\n 'TypeSafe response contained invalid JSON',\n 'INVALID_RESPONSE',\n PROVIDER,\n model,\n );\n }\n throw error;\n }\n if (!body || typeof body.model !== 'string' || !body.model)\n throw new AIError(\n 'TypeSafe response model is required',\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n if (!isRecord(body.answers))\n throw new AIError(\n 'TypeSafe response answers is required',\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n const responseAnswers = body.answers;\n const ids = Object.keys(request.questions);\n if (\n Object.keys(responseAnswers).length !== ids.length ||\n ids.some((id) => !(id in responseAnswers))\n )\n throw new AIError(\n 'TypeSafe response answers must match requested question IDs',\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n const answers = Object.fromEntries(\n ids.map((id) => [\n id,\n answer(request.questions[id], responseAnswers[id], id),\n ]),\n );\n let usage:\n | {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n }\n | undefined;\n if (body.usage !== undefined) {\n if (\n !isRecord(body.usage) ||\n !('input_tokens' in body.usage) ||\n !('output_tokens' in body.usage)\n ) {\n throw new AIError(\n 'TypeSafe response usage must include input_tokens and output_tokens',\n 'INVALID_RESPONSE',\n PROVIDER,\n );\n }\n const input = tokenCount(body.usage.input_tokens, 'usage.input_tokens');\n const output = tokenCount(\n body.usage.output_tokens,\n 'usage.output_tokens',\n );\n usage = {\n promptTokens: input,\n completionTokens: output,\n totalTokens: input + output,\n };\n }\n emitUsage(\n this.options,\n PROVIDER,\n 'decide',\n body.model,\n usage,\n startedAt,\n options.usageTags,\n );\n return {\n model: body.model,\n ...(usage ? { usage } : {}),\n provenance: { provider: PROVIDER, model: body.model },\n answers,\n };\n } catch (error) {\n if (controls.didTimeout())\n throw new AIError(\n `TypeSafe request timed out after ${controls.timeout}ms`,\n 'REQUEST_TIMEOUT',\n PROVIDER,\n model,\n true,\n );\n if (controls.signal.aborted && !(error instanceof AIError))\n throw new AIError(\n 'TypeSafe request was aborted',\n 'REQUEST_ABORTED',\n PROVIDER,\n model,\n );\n if (!(error instanceof AIError)) {\n throw new AIError(\n `Network error calling TypeSafe System One: ${\n error instanceof Error ? error.message : String(error)\n }`,\n 'NETWORK_ERROR',\n PROVIDER,\n model,\n true,\n );\n }\n throw error;\n } finally {\n controls.cleanup();\n }\n }\n\n async getModels(): Promise<AIModel[]> {\n return [\n {\n id: this.options.defaultModel || DEFAULT_MODEL,\n name: 'TypeSafe Jev',\n description: 'TypeSafe System One typed decisions',\n contextLength: 0,\n capabilities: ['decisions'],\n supportsFunctions: false,\n supportsVision: false,\n },\n ];\n }\n async getCapabilities(): Promise<AICapabilities> {\n return {\n decisions: true,\n chat: false,\n completion: false,\n embeddings: false,\n streaming: false,\n functions: false,\n vision: false,\n fineTuning: false,\n imageEmbeddings: false,\n imageGeneration: false,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 0,\n supportedOperations: ['decide'],\n };\n }\n private unsupported(): never {\n throw new AIError(\n 'This operation is not supported by the TypeSafe decision-only provider.',\n 'NOT_IMPLEMENTED',\n PROVIDER,\n );\n }\n async chat(_a: AIMessage[], _b?: ChatOptions): Promise<AIResponse> {\n return this.unsupported();\n }\n async complete(_a: string, _b?: CompletionOptions): Promise<AIResponse> {\n return this.unsupported();\n }\n async message(_a: string, _b?: MessageOptions): Promise<string> {\n return this.unsupported();\n }\n async embed(\n _a: string | string[],\n _b?: EmbeddingOptions,\n ): Promise<EmbeddingResponse> {\n return this.unsupported();\n }\n async embedImage(\n _a: string | Buffer,\n _b?: ImageEmbeddingOptions,\n ): Promise<EmbeddingResponse> {\n return this.unsupported();\n }\n async describeImage(\n _a: string | Buffer,\n _b?: string,\n _c?: ImageDescriptionOptions,\n ): Promise<string> {\n return this.unsupported();\n }\n async generateImage(\n _a: string,\n _b?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n return this.unsupported();\n }\n stream(_a: AIMessage[], _b?: ChatOptions): AsyncIterable<string> {\n const error = new AIError(\n 'Chat streaming is not supported by the TypeSafe decision-only provider.',\n 'NOT_IMPLEMENTED',\n PROVIDER,\n );\n return {\n [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }),\n };\n }\n async countTokens(_a: string): Promise<number> {\n return this.unsupported();\n }\n async submitVideoGenerationJob(\n _a: VideoGenerationOptions,\n ): Promise<VideoGenerationJob> {\n return this.unsupported();\n }\n async getVideoGenerationJob(\n _a: VideoGenerationJob,\n ): Promise<VideoGenerationStatusResult> {\n return this.unsupported();\n }\n async fetchVideoGenerationResult(\n _a: VideoGenerationJob,\n ): Promise<VideoGenerationResult> {\n return this.unsupported();\n }\n async cancelVideoGenerationJob(_a: VideoGenerationJob): Promise<void> {\n return this.unsupported();\n }\n async validateVideoGenerationAccess(): Promise<boolean> {\n return this.unsupported();\n }\n async synthesizeSpeech(_a: string, _b?: TTSOptions): Promise<TTSResponse> {\n return this.unsupported();\n }\n streamSpeech(_a: string, _b?: TTSOptions): AsyncIterable<Buffer> {\n const error = new AIError(\n 'TTS streaming is not supported by the TypeSafe decision-only provider.',\n 'NOT_IMPLEMENTED',\n PROVIDER,\n );\n return {\n [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }),\n };\n }\n async cloneVoice(_a: VoiceCloneOptions): Promise<Voice> {\n return this.unsupported();\n }\n async designVoice(_a: VoiceDesignOptions): Promise<Voice> {\n return this.unsupported();\n }\n async getVoices(_a?: VoiceListOptions): Promise<Voice[]> {\n return this.unsupported();\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as getAI, c as OpenAIClient, l as getAIClient, n as AIThread, o as getAIAuto, r as AIMessage, s as AIClient, t as PACKAGE_VERSION_INITIALIZED, u as getOpenAI } from "./chunks/src-
|
|
2
|
-
import { a as ContextLengthError, c as extractTextContent, i as ContentFilterError, n as AI_PROVIDER_TYPES, o as ModelNotFoundError, r as AuthenticationError, s as RateLimitError, t as AIError } from "./chunks/types-
|
|
3
|
-
import { n as DEFAULT_AI_MAX_RETRIES, r as DEFAULT_AI_TIMEOUT_MS, t as DEFAULT_AI_GENERATION_LIMITS } from "./chunks/safety-
|
|
1
|
+
import { a as getAI, c as OpenAIClient, l as getAIClient, n as AIThread, o as getAIAuto, r as AIMessage, s as AIClient, t as PACKAGE_VERSION_INITIALIZED, u as getOpenAI } from "./chunks/src-DOm4LN_h.js";
|
|
2
|
+
import { a as ContextLengthError, c as extractTextContent, i as ContentFilterError, n as AI_PROVIDER_TYPES, o as ModelNotFoundError, r as AuthenticationError, s as RateLimitError, t as AIError } from "./chunks/types-BpnNcTU1.js";
|
|
3
|
+
import { n as DEFAULT_AI_MAX_RETRIES, r as DEFAULT_AI_TIMEOUT_MS, t as DEFAULT_AI_GENERATION_LIMITS } from "./chunks/safety-BKswBoqy.js";
|
|
4
4
|
export { AIClient, AIError, AIMessage as AIMessageClass, AIThread, AI_PROVIDER_TYPES, AuthenticationError, ContentFilterError, ContextLengthError, DEFAULT_AI_GENERATION_LIMITS, DEFAULT_AI_MAX_RETRIES, DEFAULT_AI_TIMEOUT_MS, ModelNotFoundError, OpenAIClient, PACKAGE_VERSION_INITIALIZED, RateLimitError, extractTextContent, getAI, getAIAuto, getAIClient, getOpenAI };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/node/factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EACV,WAAW,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/node/factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EACV,WAAW,EAeZ,MAAM,iBAAiB,CAAC;AAGzB;;GAEG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAC7B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAChC,OAAO,CAAC,WAAW,CAAC,CAyUtB"}
|
package/dist/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as getAI, c as OpenAIClient, l as getAIClient, n as AIThread, r as AIMessage, s as AIClient, t as PACKAGE_VERSION_INITIALIZED, u as getOpenAI } from "./chunks/src-
|
|
2
|
-
import { a as ContextLengthError, c as extractTextContent, i as ContentFilterError, n as AI_PROVIDER_TYPES, o as ModelNotFoundError, r as AuthenticationError, s as RateLimitError, t as AIError } from "./chunks/types-
|
|
3
|
-
import { n as DEFAULT_AI_MAX_RETRIES, r as DEFAULT_AI_TIMEOUT_MS, t as DEFAULT_AI_GENERATION_LIMITS } from "./chunks/safety-
|
|
1
|
+
import { a as getAI, c as OpenAIClient, l as getAIClient, n as AIThread, r as AIMessage, s as AIClient, t as PACKAGE_VERSION_INITIALIZED, u as getOpenAI } from "./chunks/src-DOm4LN_h.js";
|
|
2
|
+
import { a as ContextLengthError, c as extractTextContent, i as ContentFilterError, n as AI_PROVIDER_TYPES, o as ModelNotFoundError, r as AuthenticationError, s as RateLimitError, t as AIError } from "./chunks/types-BpnNcTU1.js";
|
|
3
|
+
import { n as DEFAULT_AI_MAX_RETRIES, r as DEFAULT_AI_TIMEOUT_MS, t as DEFAULT_AI_GENERATION_LIMITS } from "./chunks/safety-BKswBoqy.js";
|
|
4
4
|
import { ValidationError, loadEnvConfig } from "@happyvertical/utils";
|
|
5
5
|
//#region src/node/factory.ts
|
|
6
6
|
/**
|
|
@@ -77,6 +77,11 @@ async function getAIAuto(options = {}) {
|
|
|
77
77
|
apiKey: config.apiKey || process.env.SEEVIO_API_KEY,
|
|
78
78
|
baseUrl: config.baseUrl || process.env.SEEVIO_BASE_URL
|
|
79
79
|
});
|
|
80
|
+
if (config.type === "typesafe") return getAI({
|
|
81
|
+
...config,
|
|
82
|
+
apiKey: config.apiKey || process.env.TYPESAFE_API_KEY,
|
|
83
|
+
baseUrl: config.baseUrl || process.env.TYPESAFE_BASE_URL
|
|
84
|
+
});
|
|
80
85
|
if (config.type === "bifrost") {
|
|
81
86
|
const resolvedAdminUrl = callerAdminUrl || process.env.BIFROST_ADMIN_URL || process.env.BIFROST_ADMIN_BASE_URL;
|
|
82
87
|
const resolvedAdminUser = callerAdminUser || process.env.BIFROST_ADMIN_USER || process.env.BIFROST_ADMIN_USERNAME;
|
|
@@ -95,7 +100,7 @@ async function getAIAuto(options = {}) {
|
|
|
95
100
|
return getAI(config);
|
|
96
101
|
}
|
|
97
102
|
try {
|
|
98
|
-
return await import("./chunks/src-
|
|
103
|
+
return await import("./chunks/src-DOm4LN_h.js").then((n) => n.i).then((m) => m.getAIAuto(config));
|
|
99
104
|
} catch (_error) {}
|
|
100
105
|
const hasLiteLLMSignal = Boolean(process.env.LITELLM_BASE_URL || process.env.LITELLM_API_KEY);
|
|
101
106
|
if (Boolean(process.env.BIFROST_BASE_URL) && !config.type) return getAI({
|
|
@@ -159,6 +164,7 @@ async function getAIAuto(options = {}) {
|
|
|
159
164
|
const hasModelArkSignal = Boolean(process.env.MODELARK_API_KEY || process.env.ARK_API_KEY || process.env.MODELARK_BASE_URL);
|
|
160
165
|
const hasOpenAICompatVideoSignal = Boolean(process.env.OPENAI_COMPAT_VIDEO_BASE_URL);
|
|
161
166
|
const hasSeevioSignal = Boolean(process.env.SEEVIO_API_KEY || process.env.SEEVIO_BASE_URL);
|
|
167
|
+
const hasTypeSafeSignal = Boolean(process.env.TYPESAFE_API_KEY || process.env.TYPESAFE_BASE_URL);
|
|
162
168
|
if (hasModelArkSignal && !config.type) return getAI({
|
|
163
169
|
...config,
|
|
164
170
|
type: "byteplus-modelark",
|
|
@@ -177,6 +183,12 @@ async function getAIAuto(options = {}) {
|
|
|
177
183
|
apiKey: config.apiKey || process.env.SEEVIO_API_KEY,
|
|
178
184
|
baseUrl: config.baseUrl || process.env.SEEVIO_BASE_URL
|
|
179
185
|
});
|
|
186
|
+
if (hasTypeSafeSignal && !config.type) return getAI({
|
|
187
|
+
...config,
|
|
188
|
+
type: "typesafe",
|
|
189
|
+
apiKey: config.apiKey || process.env.TYPESAFE_API_KEY,
|
|
190
|
+
baseUrl: config.baseUrl || process.env.TYPESAFE_BASE_URL
|
|
191
|
+
});
|
|
180
192
|
throw new ValidationError("Could not auto-detect AI provider from options or environment", {
|
|
181
193
|
hint: "Please specify a \"type\" field in options or provide provider-specific credentials/environment variables",
|
|
182
194
|
supportedTypes: [...AI_PROVIDER_TYPES],
|
|
@@ -197,6 +209,8 @@ async function getAIAuto(options = {}) {
|
|
|
197
209
|
"HF_TOKEN",
|
|
198
210
|
"AWS_ACCESS_KEY_ID",
|
|
199
211
|
"AWS_DEFAULT_REGION",
|
|
212
|
+
"TYPESAFE_API_KEY",
|
|
213
|
+
"TYPESAFE_BASE_URL",
|
|
200
214
|
"OPENAI_COMPAT_VIDEO_BASE_URL",
|
|
201
215
|
"OPENAI_COMPAT_VIDEO_API_KEY",
|
|
202
216
|
"MODELARK_API_KEY",
|