@happyvertical/ai 0.96.1 → 0.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +123 -3
  2. package/dist/chunks/{anthropic-CD9Ud7a5.js → anthropic-6ZDF32jo.js} +181 -27
  3. package/dist/chunks/anthropic-6ZDF32jo.js.map +1 -0
  4. package/dist/chunks/{bedrock-Bv1qCquH.js → bedrock-BQi3Z9jR.js} +71 -21
  5. package/dist/chunks/bedrock-BQi3Z9jR.js.map +1 -0
  6. package/dist/chunks/{bifrost-DzT2J0Lq.js → bifrost-DsI2I4ka.js} +9 -6
  7. package/dist/chunks/bifrost-DsI2I4ka.js.map +1 -0
  8. package/dist/chunks/{byteplus-modelark-BIEXaTWw.js → byteplus-modelark-Dq8AgCQD.js} +7 -5
  9. package/dist/chunks/byteplus-modelark-Dq8AgCQD.js.map +1 -0
  10. package/dist/chunks/{claude-cli-Chfnckyj.js → claude-cli-DIFxvG3c.js} +7 -5
  11. package/dist/chunks/claude-cli-DIFxvG3c.js.map +1 -0
  12. package/dist/chunks/continuation-D0NisZA1.js +209 -0
  13. package/dist/chunks/continuation-D0NisZA1.js.map +1 -0
  14. package/dist/chunks/{gateway-admin-04mvbDHC.js → gateway-audio-models-Dn7Wrc6P.js} +53 -3
  15. package/dist/chunks/gateway-audio-models-Dn7Wrc6P.js.map +1 -0
  16. package/dist/chunks/{gemini-BGwrHeGc.js → gemini-BLFwWN90.js} +131 -13
  17. package/dist/chunks/gemini-BLFwWN90.js.map +1 -0
  18. package/dist/chunks/{huggingface-mthYVbLR.js → huggingface-BuRwlABZ.js} +8 -6
  19. package/dist/chunks/huggingface-BuRwlABZ.js.map +1 -0
  20. package/dist/chunks/{litellm-xCO8QlGI.js → litellm-CpfCT6jN.js} +9 -6
  21. package/dist/chunks/litellm-CpfCT6jN.js.map +1 -0
  22. package/dist/chunks/{ollama-Bedt8Agy.js → ollama-CnrskTUg.js} +38 -7
  23. package/dist/chunks/ollama-CnrskTUg.js.map +1 -0
  24. package/dist/chunks/{openai-compat-video-B-xVe9bW.js → openai-compat-video-CJIDdkUO.js} +7 -5
  25. package/dist/chunks/openai-compat-video-CJIDdkUO.js.map +1 -0
  26. package/dist/chunks/{qwen-tts-D-UmiMMy.js → qwen-tts-PzEkWcEb.js} +2 -2
  27. package/dist/chunks/{qwen-tts-D-UmiMMy.js.map → qwen-tts-PzEkWcEb.js.map} +1 -1
  28. package/dist/chunks/rate-limit-BzWfHA0e.js +713 -0
  29. package/dist/chunks/rate-limit-BzWfHA0e.js.map +1 -0
  30. package/dist/chunks/{seevio-BJRiguLc.js → seevio-D2y5Io7C.js} +4 -5
  31. package/dist/chunks/seevio-D2y5Io7C.js.map +1 -0
  32. package/dist/chunks/{src-BUQUZK2M.js → src-CLT6r1W0.js} +36 -23
  33. package/dist/chunks/src-CLT6r1W0.js.map +1 -0
  34. package/dist/chunks/tool-messages-0ND35mYX.js +57 -0
  35. package/dist/chunks/tool-messages-0ND35mYX.js.map +1 -0
  36. package/dist/chunks/{types-BpnNcTU1.js → types-DB5W6_F-.js} +37 -5
  37. package/dist/chunks/types-DB5W6_F-.js.map +1 -0
  38. package/dist/chunks/{typesafe-B0gPZQJg.js → typesafe-CVUqQ8N1.js} +7 -5
  39. package/dist/chunks/typesafe-CVUqQ8N1.js.map +1 -0
  40. package/dist/index.js +4 -4
  41. package/dist/node.js +5 -5
  42. package/dist/shared/continuation.d.ts +51 -0
  43. package/dist/shared/continuation.d.ts.map +1 -0
  44. package/dist/shared/factory.d.ts.map +1 -1
  45. package/dist/shared/providers/anthropic.d.ts +53 -0
  46. package/dist/shared/providers/anthropic.d.ts.map +1 -1
  47. package/dist/shared/providers/bedrock.d.ts +13 -0
  48. package/dist/shared/providers/bedrock.d.ts.map +1 -1
  49. package/dist/shared/providers/bifrost.d.ts.map +1 -1
  50. package/dist/shared/providers/claude-cli.d.ts.map +1 -1
  51. package/dist/shared/providers/gateway-audio-models.d.ts +24 -0
  52. package/dist/shared/providers/gateway-audio-models.d.ts.map +1 -0
  53. package/dist/shared/providers/gemini.d.ts +28 -0
  54. package/dist/shared/providers/gemini.d.ts.map +1 -1
  55. package/dist/shared/providers/huggingface.d.ts.map +1 -1
  56. package/dist/shared/providers/litellm.d.ts.map +1 -1
  57. package/dist/shared/providers/ollama.d.ts.map +1 -1
  58. package/dist/shared/providers/openai.d.ts.map +1 -1
  59. package/dist/shared/providers/seevio.d.ts.map +1 -1
  60. package/dist/shared/providers/typesafe.d.ts.map +1 -1
  61. package/dist/shared/rate-limit.d.ts +19 -1
  62. package/dist/shared/rate-limit.d.ts.map +1 -1
  63. package/dist/shared/safety.d.ts.map +1 -1
  64. package/dist/shared/tool-messages.d.ts +27 -0
  65. package/dist/shared/tool-messages.d.ts.map +1 -0
  66. package/dist/shared/types.d.ts +191 -22
  67. package/dist/shared/types.d.ts.map +1 -1
  68. package/package.json +3 -3
  69. package/dist/chunks/anthropic-CD9Ud7a5.js.map +0 -1
  70. package/dist/chunks/bedrock-Bv1qCquH.js.map +0 -1
  71. package/dist/chunks/bifrost-DzT2J0Lq.js.map +0 -1
  72. package/dist/chunks/byteplus-modelark-BIEXaTWw.js.map +0 -1
  73. package/dist/chunks/claude-cli-Chfnckyj.js.map +0 -1
  74. package/dist/chunks/gateway-admin-04mvbDHC.js.map +0 -1
  75. package/dist/chunks/gemini-BGwrHeGc.js.map +0 -1
  76. package/dist/chunks/huggingface-mthYVbLR.js.map +0 -1
  77. package/dist/chunks/litellm-xCO8QlGI.js.map +0 -1
  78. package/dist/chunks/ollama-Bedt8Agy.js.map +0 -1
  79. package/dist/chunks/openai-compat-video-B-xVe9bW.js.map +0 -1
  80. package/dist/chunks/rate-limit-BVW_1Tey.js +0 -202
  81. package/dist/chunks/rate-limit-BVW_1Tey.js.map +0 -1
  82. package/dist/chunks/safety-BKswBoqy.js +0 -279
  83. package/dist/chunks/safety-BKswBoqy.js.map +0 -1
  84. package/dist/chunks/seevio-BJRiguLc.js.map +0 -1
  85. package/dist/chunks/src-BUQUZK2M.js.map +0 -1
  86. package/dist/chunks/types-BpnNcTU1.js.map +0 -1
  87. package/dist/chunks/typesafe-B0gPZQJg.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-cli-DIFxvG3c.js","names":[],"sources":["../../src/shared/providers/claude-cli.ts"],"sourcesContent":["/**\n * Claude CLI provider implementation\n *\n * Provides a standardized interface for interacting with Claude via the Claude Code CLI,\n * enabling users with Claude Max subscriptions to use their subscription instead of\n * paying for API usage. Supports chat completions and streaming responses.\n * Authentication is handled via existing Claude session or setup-token.\n *\n * **Authentication Priority:**\n * 1. ANTHROPIC_API_KEY environment variable (uses Anthropic SDK, no keychain prompts)\n * 2. Claude CLI with setup-token (for CI/CD, no keychain prompts)\n * 3. Claude CLI with keychain (may prompt for password on macOS)\n */\n\nimport { exec, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nimport type {\n AICapabilities,\n AIInterface,\n AIMessage,\n AIModel,\n AIResponse,\n ChatOptions,\n ClaudeCliOptions,\n CompletionOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n MessageOptions,\n TokenUsage,\n TTSOptions,\n TTSResponse,\n VideoGenerationJob,\n VideoGenerationOptions,\n VideoGenerationResult,\n VideoGenerationStatusResult,\n Voice,\n VoiceCloneOptions,\n VoiceDesignOptions,\n VoiceListOptions,\n} from '../types';\nimport {\n AIError,\n AuthenticationError,\n ContextLengthError,\n extractTextContent,\n RateLimitError,\n} from '../types';\nimport { emitUsage } from './usage';\n\nconst execAsync = promisify(exec);\n\n/**\n * Claude CLI provider implementation that shells out to the Claude Code CLI.\n * Supports chat completions, streaming, and leverages Claude Max subscription.\n * Does not support embeddings (use OpenAI or another provider for embeddings).\n *\n * If ANTHROPIC_API_KEY environment variable is set, automatically falls back to\n * using the Anthropic provider to avoid macOS keychain password prompts.\n */\nexport class ClaudeCliProvider implements AIInterface {\n private options: ClaudeCliOptions;\n private cliPath: string | null = null;\n private anthropicFallback: AIInterface | null = null;\n\n /**\n * Creates a new Claude CLI provider instance\n * @param options - Configuration options for the Claude CLI provider\n */\n constructor(options: ClaudeCliOptions) {\n this.options = {\n defaultModel: 'sonnet',\n ...options,\n };\n }\n\n /**\n * Checks if ANTHROPIC_API_KEY is available and initializes fallback provider if needed\n * @private\n */\n private async initializeFallback(): Promise<void> {\n if (this.anthropicFallback !== null) {\n return; // Already initialized\n }\n\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n return; // No API key, will use CLI\n }\n\n // Lazy load Anthropic provider to avoid circular dependency\n const { AnthropicProvider } = await import('./anthropic.js');\n\n // Map claude-cli model names to Anthropic model IDs\n const modelMap: Record<string, string> = {\n sonnet: 'claude-3-5-sonnet-20241022',\n opus: 'claude-3-opus-20240229',\n haiku: 'claude-3-haiku-20240307',\n };\n\n const defaultModel =\n modelMap[this.options.defaultModel || 'sonnet'] ||\n 'claude-3-5-sonnet-20241022';\n\n this.anthropicFallback = new AnthropicProvider({\n type: 'anthropic',\n apiKey,\n defaultModel,\n onUsage: this.options.onUsage,\n usageTags: this.options.usageTags,\n });\n }\n\n /**\n * Finds the Claude CLI binary in PATH or uses custom cliPath\n * Does NOT execute the CLI during detection to avoid keychain prompts\n * @throws {AIError} When CLI cannot be found\n * @private\n */\n private async findCli(): Promise<string> {\n if (this.cliPath) {\n return this.cliPath;\n }\n\n // If custom path provided, check if it exists (don't execute it)\n if (this.options.cliPath) {\n const fs = await import('node:fs/promises');\n try {\n await fs.access(this.options.cliPath);\n this.cliPath = this.options.cliPath;\n return this.cliPath;\n } catch (_error) {\n throw new AIError(\n `Claude CLI not found at specified path: ${this.options.cliPath}. Set ANTHROPIC_API_KEY environment variable to avoid CLI dependency.`,\n 'CLI_NOT_FOUND',\n 'claude-cli',\n );\n }\n }\n\n // Try common installation locations first\n const commonPaths = [\n `${process.env.HOME}/.claude/local/claude`, // Default installation path\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\n const fs = await import('node:fs/promises');\n for (const path of commonPaths) {\n try {\n await fs.access(path);\n this.cliPath = path;\n return this.cliPath;\n } catch (_error) {\n // Try next path\n }\n }\n\n // Try to find in PATH with which (doesn't execute the binary)\n try {\n const { stdout } = await execAsync('which claude');\n const path = stdout.trim();\n if (path) {\n this.cliPath = path;\n return this.cliPath;\n }\n } catch (_error) {\n // Not found\n }\n\n throw new AIError(\n 'Claude CLI not found. Set ANTHROPIC_API_KEY environment variable to use Anthropic SDK instead, or install Claude Code CLI. Visit https://docs.claude.com/en/docs/claude-code/ for installation instructions.',\n 'CLI_NOT_FOUND',\n 'claude-cli',\n );\n }\n\n /**\n * Normalizes model name to full model ID or short name\n * @private\n */\n private normalizeModel(model?: string): string {\n const m = model || this.options.defaultModel || 'sonnet';\n\n // Map short names to CLI-compatible values\n const modelMap: Record<string, string> = {\n sonnet: 'sonnet',\n opus: 'opus',\n haiku: 'haiku',\n };\n\n return modelMap[m] || m;\n }\n\n /**\n * Execute Claude CLI command and return parsed JSON output\n * @private\n */\n private async executeCommand(\n prompt: string,\n options: {\n model?: string;\n systemPrompt?: string;\n temperature?: number;\n maxTokens?: number;\n } = {},\n ): Promise<any> {\n const cliPath = await this.findCli();\n const model = this.normalizeModel(options.model);\n\n const args = ['--print', '--output-format', 'json', '--model', model];\n\n if (options.systemPrompt) {\n args.push('--system-prompt', options.systemPrompt);\n }\n\n args.push(prompt);\n\n return new Promise((resolve, reject) => {\n let stdout = '';\n let stderr = '';\n\n const child = spawn(cliPath, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n child.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n\n child.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n child.on('error', (error) => {\n reject(\n new AIError(\n `Failed to execute Claude CLI: ${error.message}`,\n 'EXECUTION_ERROR',\n 'claude-cli',\n ),\n );\n });\n\n child.on('close', (code) => {\n if (code !== 0) {\n reject(this.mapCliError(stderr, code || undefined));\n return;\n }\n\n try {\n // Parse JSON output\n const result = JSON.parse(stdout);\n resolve(result);\n } catch (_error) {\n reject(\n new AIError(\n 'Failed to parse CLI output as JSON',\n 'PARSE_ERROR',\n 'claude-cli',\n ),\n );\n }\n });\n });\n }\n\n /**\n * Execute Claude CLI command with streaming output\n * @private\n */\n private async *executeStreamingCommand(\n prompt: string,\n options: {\n model?: string;\n systemPrompt?: string;\n onProgress?: (chunk: string) => void;\n } = {},\n ): AsyncIterable<string> {\n const cliPath = await this.findCli();\n const model = this.normalizeModel(options.model);\n\n const args = [\n '--print',\n '--output-format',\n 'stream-json',\n '--verbose', // Required for stream-json format\n '--model',\n model,\n ];\n\n if (options.systemPrompt) {\n args.push('--system-prompt', options.systemPrompt);\n }\n\n args.push(prompt);\n\n const child = spawn(cliPath, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n\n child.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n // Process streaming JSON chunks\n let buffer = '';\n\n for await (const chunk of child.stdout) {\n buffer += chunk.toString();\n\n // Process complete JSON objects (one per line)\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const parsed = JSON.parse(line);\n // Claude CLI stream-json format outputs complete messages\n // Look for assistant message with content\n if (parsed.type === 'assistant' && parsed.message?.content) {\n for (const contentBlock of parsed.message.content) {\n if (contentBlock.type === 'text' && contentBlock.text) {\n const text = contentBlock.text;\n if (options.onProgress) {\n options.onProgress(text);\n }\n yield text;\n }\n }\n }\n // Also handle result type for final output\n else if (parsed.type === 'result' && parsed.result) {\n const text = parsed.result;\n if (options.onProgress) {\n options.onProgress(text);\n }\n yield text;\n }\n } catch (_error) {\n // Skip malformed JSON lines\n }\n }\n }\n }\n\n // Wait for process to complete\n const exitCode = await new Promise<number>((resolve) => {\n child.on('close', (code) => resolve(code || 0));\n });\n\n if (exitCode !== 0) {\n throw this.mapCliError(stderr, exitCode);\n }\n }\n\n /**\n * Maps messages to a single prompt for CLI\n * @private\n */\n private mapMessagesToPrompt(messages: AIMessage[]): {\n prompt: string;\n systemPrompt?: string;\n } {\n let systemPrompt: string | undefined;\n const conversationParts: string[] = [];\n\n for (const message of messages) {\n const textContent = extractTextContent(message.content);\n if (message.role === 'system') {\n systemPrompt = systemPrompt\n ? `${systemPrompt}\\n\\n${textContent}`\n : textContent;\n } else if (message.role === 'user') {\n conversationParts.push(`User: ${textContent}`);\n } else if (message.role === 'assistant') {\n conversationParts.push(`Assistant: ${textContent}`);\n }\n }\n\n // If only one user message and no conversation, return it directly\n if (\n conversationParts.length === 1 &&\n conversationParts[0].startsWith('User: ')\n ) {\n return {\n prompt: conversationParts[0].substring(6), // Remove \"User: \" prefix\n systemPrompt,\n };\n }\n\n return {\n prompt: conversationParts.join('\\n\\n'),\n systemPrompt,\n };\n }\n\n /**\n * Maps CLI errors to standardized error types\n * @private\n */\n\n private mapCliError(stderr: string, exitCode?: number): AIError {\n const errorText = stderr.toLowerCase();\n\n // Authentication errors\n if (\n errorText.includes('not authenticated') ||\n errorText.includes('login required')\n ) {\n return new AuthenticationError('claude-cli');\n }\n\n // Rate limiting\n if (\n errorText.includes('rate limit') ||\n errorText.includes('too many requests')\n ) {\n return new RateLimitError('claude-cli');\n }\n\n // Context length\n if (\n errorText.includes('context length') ||\n errorText.includes('too long')\n ) {\n return new ContextLengthError('claude-cli');\n }\n\n return new AIError(\n `Claude CLI error (exit code ${exitCode}): ${stderr}`,\n 'CLI_ERROR',\n 'claude-cli',\n );\n }\n\n /**\n * Generate a chat completion using Claude CLI\n * @param messages - Array of conversation messages\n * @param options - Optional configuration for the chat completion\n * @returns Promise resolving to the AI response with content and metadata\n * @throws {AIError} When the CLI execution fails\n *\n * @example\n * ```typescript\n * const response = await provider.chat([\n * { role: 'system', content: 'You are a helpful assistant.' },\n * { role: 'user', content: 'Explain quantum computing' }\n * ], {\n * model: 'sonnet'\n * });\n * ```\n */\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n // Check if ANTHROPIC_API_KEY is available and use fallback if so\n await this.initializeFallback();\n if (this.anthropicFallback) {\n return this.anthropicFallback.chat(messages, options);\n }\n\n const startTime = Date.now();\n let { prompt, systemPrompt } = this.mapMessagesToPrompt(messages);\n\n // Add JSON format instruction if requested\n if (options.responseFormat?.type === 'json_object') {\n const jsonInstruction =\n '\\n\\nIMPORTANT: You must respond with valid JSON only. Do not include any explanatory text outside the JSON object.';\n systemPrompt = systemPrompt\n ? systemPrompt + jsonInstruction\n : jsonInstruction.trim();\n }\n\n const result = await this.executeCommand(prompt, {\n model: options.model,\n systemPrompt,\n temperature: options.temperature,\n maxTokens: options.maxTokens,\n });\n\n const model = options.model || this.options.defaultModel;\n const usage: TokenUsage | undefined = result.usage\n ? {\n promptTokens: result.usage.input_tokens || 0,\n completionTokens: result.usage.output_tokens || 0,\n totalTokens:\n (result.usage.input_tokens || 0) +\n (result.usage.output_tokens || 0),\n }\n : undefined;\n\n emitUsage(\n this.options,\n 'claude-cli',\n 'chat',\n model!,\n usage,\n startTime,\n options.usageTags,\n );\n\n // Parse Claude CLI JSON output\n return {\n content:\n result.result ||\n result.content ||\n result.text ||\n JSON.stringify(result),\n model,\n finishReason: result.is_error ? 'stop' : 'stop',\n usage,\n };\n }\n\n /**\n * Generate a text completion (delegates to chat)\n */\n async complete(\n prompt: string,\n options: CompletionOptions = {},\n ): Promise<AIResponse> {\n // Check if ANTHROPIC_API_KEY is available and use fallback if so\n await this.initializeFallback();\n if (this.anthropicFallback) {\n return this.anthropicFallback.complete(prompt, options);\n }\n\n return this.chat([{ role: 'user', content: prompt }], {\n model: options.model,\n maxTokens: options.maxTokens,\n temperature: options.temperature,\n topP: options.topP,\n n: options.n,\n stop: options.stop,\n stream: options.stream,\n onProgress: options.onProgress,\n usageTags: options.usageTags,\n continueOnLength: options.continueOnLength,\n });\n }\n\n /**\n * Simple message interface for single-turn interactions with optional history\n *\n * @param text - The message text to send\n * @param options - Configuration options including history, model, etc.\n * @returns Promise resolving to the response content string\n *\n * @example\n * ```typescript\n * // Simple usage\n * const response = await provider.message('Hello!');\n *\n * // With history\n * const response = await provider.message('What was my question?', {\n * history: [\n * { role: 'user', content: 'What is 2+2?' },\n * { role: 'assistant', content: '4' }\n * ]\n * });\n * ```\n */\n async message(text: string, options: MessageOptions = {}): Promise<string> {\n // Check if ANTHROPIC_API_KEY is available and use fallback if so\n await this.initializeFallback();\n if (this.anthropicFallback) {\n return this.anthropicFallback.message(text, options);\n }\n\n // Build messages array from history + current message\n const messages: AIMessage[] = [\n ...(options.history || []),\n { role: options.role || 'user', content: text },\n ];\n\n const response = await this.chat(messages, {\n model: options.model,\n maxTokens: options.maxTokens,\n temperature: options.temperature,\n topP: options.topP,\n stop: options.stop,\n stream: options.stream,\n frequencyPenalty: options.frequencyPenalty,\n presencePenalty: options.presencePenalty,\n responseFormat: options.responseFormat,\n seed: options.seed,\n tools: options.tools,\n toolChoice: options.toolChoice,\n onProgress: options.onProgress,\n usageTags: options.usageTags,\n continueOnLength: options.continueOnLength,\n });\n\n return response.content;\n }\n\n /**\n * Embeddings are not supported by Claude CLI or Anthropic\n */\n async embed(\n _text: string | string[],\n _options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n // Note: Even with fallback, Anthropic doesn't support embeddings\n throw new AIError(\n 'Claude CLI does not support embeddings. Use OpenAI or another provider for embeddings.',\n 'NOT_SUPPORTED',\n 'claude-cli',\n );\n }\n\n /**\n * Image embeddings are not supported by Claude CLI\n */\n async embedImage(\n _image: string | Buffer,\n _options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n throw new AIError(\n 'Claude CLI does not support image embeddings. Use OpenAI or Gemini.',\n 'NOT_SUPPORTED',\n 'claude-cli',\n );\n }\n\n /**\n * Image description is not yet implemented for Claude CLI\n * Note: Claude supports vision, but this would require significant implementation\n */\n async describeImage(\n _image: string | Buffer,\n _prompt?: string,\n _options: ImageDescriptionOptions = {},\n ): Promise<string> {\n throw new AIError(\n 'Image description is not yet implemented for Claude CLI. Use OpenAI or Gemini.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n /**\n * Image generation is not supported by Claude\n */\n async generateImage(\n _prompt: string,\n _options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n throw new AIError(\n 'Claude does not support image generation. Use OpenAI or Gemini.',\n 'NOT_SUPPORTED',\n 'claude-cli',\n );\n }\n\n /**\n * Stream chat completion using Claude CLI\n */\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n // Check if ANTHROPIC_API_KEY is available and use fallback if so\n await this.initializeFallback();\n if (this.anthropicFallback) {\n yield* this.anthropicFallback.stream(messages, options);\n return;\n }\n\n const startTime = Date.now();\n let { prompt, systemPrompt } = this.mapMessagesToPrompt(messages);\n\n // Add JSON format instruction if requested\n if (options.responseFormat?.type === 'json_object') {\n const jsonInstruction =\n '\\n\\nIMPORTANT: You must respond with valid JSON only. Do not include any explanatory text outside the JSON object.';\n systemPrompt = systemPrompt\n ? systemPrompt + jsonInstruction\n : jsonInstruction.trim();\n }\n\n yield* this.executeStreamingCommand(prompt, {\n model: options.model,\n systemPrompt,\n onProgress: options.onProgress,\n });\n\n const model = options.model || this.options.defaultModel;\n emitUsage(\n this.options,\n 'claude-cli',\n 'stream',\n model!,\n undefined,\n startTime,\n options.usageTags,\n );\n }\n\n /**\n * Count tokens in text (approximation)\n */\n async countTokens(text: string): Promise<number> {\n // Check if ANTHROPIC_API_KEY is available and use fallback if so\n await this.initializeFallback();\n if (this.anthropicFallback) {\n return this.anthropicFallback.countTokens(text);\n }\n\n // Claude uses a different tokenizer, approximate similar to Anthropic\n return Math.ceil(text.length / 3.5);\n }\n\n /**\n * Get available models (static list of Claude models)\n */\n async getModels(): Promise<AIModel[]> {\n // Check if ANTHROPIC_API_KEY is available and use fallback if so\n await this.initializeFallback();\n if (this.anthropicFallback) {\n return this.anthropicFallback.getModels();\n }\n\n return [\n {\n id: 'sonnet',\n name: 'Claude Sonnet',\n description: 'Balanced Claude model via CLI',\n contextLength: 200000,\n capabilities: ['text', 'chat', 'vision'],\n supportsFunctions: false,\n supportsVision: false,\n },\n {\n id: 'opus',\n name: 'Claude Opus',\n description: 'Most powerful Claude model via CLI',\n contextLength: 200000,\n capabilities: ['text', 'chat'],\n supportsFunctions: false,\n supportsVision: false,\n },\n {\n id: 'haiku',\n name: 'Claude Haiku',\n description: 'Fast Claude model via CLI',\n contextLength: 200000,\n capabilities: ['text', 'chat'],\n supportsFunctions: false,\n supportsVision: false,\n },\n ];\n }\n\n /**\n * Get provider capabilities\n */\n async getCapabilities(): Promise<AICapabilities> {\n // Check if ANTHROPIC_API_KEY is available and use fallback if so\n await this.initializeFallback();\n if (this.anthropicFallback) {\n return this.anthropicFallback.getCapabilities();\n }\n\n return {\n chat: true,\n completion: true,\n embeddings: false, // Claude CLI doesn't support embeddings\n streaming: true,\n functions: false, // Not supported via CLI\n vision: false, // Not supported in current CLI version\n fineTuning: false,\n imageEmbeddings: false,\n imageGeneration: false,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 200000,\n supportedOperations: ['chat', 'completion', 'streaming'],\n };\n }\n\n // ============================================================================\n // Video Generation Methods (Not supported - use gemini, byteplus-modelark,\n // or openai-compat-video provider)\n // ============================================================================\n\n async submitVideoGenerationJob(\n _options: VideoGenerationOptions,\n ): Promise<VideoGenerationJob> {\n throw new AIError(\n 'Video generation is not supported by Claude CLI provider. Use gemini, byteplus-modelark, or openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n async getVideoGenerationJob(\n _handle: VideoGenerationJob,\n ): Promise<VideoGenerationStatusResult> {\n throw new AIError(\n 'Video generation is not supported by Claude CLI provider. Use gemini, byteplus-modelark, or openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n async fetchVideoGenerationResult(\n _handle: VideoGenerationJob,\n ): Promise<VideoGenerationResult> {\n throw new AIError(\n 'Video generation is not supported by Claude CLI provider. Use gemini, byteplus-modelark, or openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n async cancelVideoGenerationJob(_handle: VideoGenerationJob): Promise<void> {\n throw new AIError(\n 'Video generation is not supported by Claude CLI provider. Use gemini, byteplus-modelark, or openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n async validateVideoGenerationAccess(): Promise<boolean> {\n throw new AIError(\n 'Video generation is not supported by Claude CLI provider. Use gemini, byteplus-modelark, or openai-compat-video provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n // ============================================================================\n // TTS Methods (Not supported - use Qwen3-TTS provider)\n // ============================================================================\n\n async synthesizeSpeech(\n _text: string,\n _options?: TTSOptions,\n ): Promise<TTSResponse> {\n throw new AIError(\n 'TTS is not supported by Claude CLI provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n streamSpeech(_text: string, _options?: TTSOptions): AsyncIterable<Buffer> {\n const error = new AIError(\n 'TTS streaming is not supported by Claude CLI provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n return {\n [Symbol.asyncIterator]: () => ({\n next: () => Promise.reject(error),\n }),\n };\n }\n\n async cloneVoice(_options: VoiceCloneOptions): Promise<Voice> {\n throw new AIError(\n 'Voice cloning is not supported by Claude CLI provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n async designVoice(_options: VoiceDesignOptions): Promise<Voice> {\n throw new AIError(\n 'Voice design is not supported by Claude CLI provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n\n async getVoices(_options?: VoiceListOptions): Promise<Voice[]> {\n throw new AIError(\n 'Voice listing is not supported by Claude CLI provider. Use Qwen3-TTS provider.',\n 'NOT_IMPLEMENTED',\n 'claude-cli',\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,209 @@
1
+ import { t as AIError } from "./types-DB5W6_F-.js";
2
+ /** Shortest seam overlap (in characters) that is trimmed as a repeat. */
3
+ var MIN_OVERLAP = 12;
4
+ /** Longest seam overlap (in characters) that is searched for. */
5
+ var MAX_OVERLAP = 400;
6
+ var CONTINUE_PROMPT = "Continue exactly where you stopped. Do not repeat anything you already wrote and do not add an introduction.";
7
+ /**
8
+ * Resolve the per-call option over the client default. Returns undefined when
9
+ * continuation is off. Structured output and tool use never continue because
10
+ * stitching two JSON documents or tool-call turns is not safe.
11
+ */
12
+ function resolveContinuation(providerOptions, options) {
13
+ const setting = options.continueOnLength ?? providerOptions.continueOnLength;
14
+ if (!setting) return void 0;
15
+ if (options.tools?.length || options.responseFormat?.type === "json_object") return;
16
+ const requested = typeof setting === "object" ? setting.maxContinuations ?? 3 : 3;
17
+ if (!Number.isFinite(requested) || requested < 0) throw new AIError("continueOnLength.maxContinuations must be a non-negative finite number", "AI_LIMIT_INVALID");
18
+ const maxContinuations = Math.trunc(requested);
19
+ return maxContinuations > 0 ? { maxContinuations } : void 0;
20
+ }
21
+ /**
22
+ * Continuation replies usually start without a leading space. When the text
23
+ * so far ends a sentence and the addition starts a new one, add the space the
24
+ * model dropped at the seam ("over." + "Next" -> "over. Next"). Only a capital
25
+ * letter or an opening quote/bracket counts as a new sentence, so a word or
26
+ * number split across parts ("exam" + "ple", "1." + "5") is left joined.
27
+ */
28
+ function spaceSeam(accumulated, addition) {
29
+ if (!addition || /\s$/.test(accumulated) || /^\s/.test(addition)) return addition;
30
+ return /[.!?:;]["')\]\u201d\u2019]?$/.test(accumulated) && /^[\p{Lu}"'([\u201c\u2018]/u.test(addition) ? ` ${addition}` : addition;
31
+ }
32
+ /**
33
+ * The text to append after `accumulated`: `next` minus any leading run that
34
+ * repeats the end of `accumulated`. Whitespace already emitted is kept, and a
35
+ * dropped space between sentences is restored.
36
+ */
37
+ function continuationAddition(accumulated, next) {
38
+ const tail = accumulated.trimEnd();
39
+ const head = next.trimStart();
40
+ const max = Math.min(tail.length, head.length, MAX_OVERLAP);
41
+ for (let length = max; length >= MIN_OVERLAP; length--) if (tail.endsWith(head.slice(0, length))) return spaceSeam(accumulated, head.slice(length));
42
+ return spaceSeam(accumulated, next);
43
+ }
44
+ function continuationMessages(messages, accumulated) {
45
+ return [
46
+ ...messages,
47
+ {
48
+ role: "assistant",
49
+ content: accumulated.trimEnd()
50
+ },
51
+ {
52
+ role: "user",
53
+ content: CONTINUE_PROMPT
54
+ }
55
+ ];
56
+ }
57
+ /** Do not start another (billed) part once the caller has aborted. */
58
+ function throwIfAborted(signal) {
59
+ if (signal?.aborted) throw new AIError("AI request aborted by caller", "AI_ABORTED");
60
+ }
61
+ function addUsage(a, b) {
62
+ if (!a) return b;
63
+ if (!b) return a;
64
+ return {
65
+ promptTokens: a.promptTokens + b.promptTokens,
66
+ completionTokens: a.completionTokens + b.completionTokens,
67
+ totalTokens: a.totalTokens + b.totalTokens
68
+ };
69
+ }
70
+ /**
71
+ * Run a chat call and, when the reply stopped on the output limit, keep asking
72
+ * the model to continue and stitch the pieces together.
73
+ */
74
+ async function chatWithContinuation(chat, providerOptions, messages, options = {}) {
75
+ const config = resolveContinuation(providerOptions, options);
76
+ const first = await chat(messages, options);
77
+ const hitLimit = (response) => response.finishReason === "length" && !response.toolCalls?.length;
78
+ if (!config || !hitLimit(first) || !first.content.trim()) return first.finishReason === "length" ? {
79
+ ...first,
80
+ truncated: true
81
+ } : first;
82
+ const { onProgress, ...partOptions } = options;
83
+ let content = first.content;
84
+ let usage = first.usage;
85
+ let last = first;
86
+ let parts = 1;
87
+ while (parts <= config.maxContinuations && hitLimit(last)) {
88
+ throwIfAborted(options.signal);
89
+ const next = await chat(continuationMessages(messages, content), {
90
+ ...partOptions,
91
+ stream: false
92
+ });
93
+ parts++;
94
+ usage = addUsage(usage, next.usage);
95
+ last = next;
96
+ const addition = continuationAddition(content, next.content);
97
+ if (!addition) break;
98
+ content += addition;
99
+ if (onProgress) onProgress(addition);
100
+ }
101
+ return {
102
+ ...last,
103
+ content,
104
+ usage,
105
+ model: last.model ?? first.model,
106
+ parts,
107
+ truncated: hitLimit(last)
108
+ };
109
+ }
110
+ /**
111
+ * Stream a reply across continuations as one continuous stream. A continuation
112
+ * part's first characters are held back briefly so a repeated seam can be
113
+ * trimmed before it reaches the consumer.
114
+ */
115
+ async function* streamWithContinuation(stream, providerOptions, messages, options = {}) {
116
+ const config = resolveContinuation(providerOptions, options);
117
+ if (!config) {
118
+ yield* stream(messages, options);
119
+ return;
120
+ }
121
+ const { onProgress, onFinishReason, ...rest } = options;
122
+ let accumulated = "";
123
+ let reason = "stop";
124
+ for (let part = 0; part <= config.maxContinuations; part++) {
125
+ const finish = {};
126
+ const partOptions = {
127
+ ...rest,
128
+ onFinishReason: (value) => {
129
+ finish.reason = value;
130
+ }
131
+ };
132
+ if (part > 0) throwIfAborted(options.signal);
133
+ const source = stream(part === 0 ? messages : continuationMessages(messages, accumulated), partOptions);
134
+ let pending = "";
135
+ let seam = part > 0;
136
+ const emit = (text) => {
137
+ if (!text) return null;
138
+ accumulated += text;
139
+ onProgress?.(text);
140
+ return text;
141
+ };
142
+ for await (const chunk of source) {
143
+ if (!seam) {
144
+ const out = emit(chunk);
145
+ if (out) yield out;
146
+ continue;
147
+ }
148
+ pending += chunk;
149
+ if (pending.length >= 412) {
150
+ seam = false;
151
+ const out = emit(continuationAddition(accumulated, pending));
152
+ if (out) yield out;
153
+ pending = "";
154
+ }
155
+ }
156
+ if (seam && pending) {
157
+ const out = emit(continuationAddition(accumulated, pending));
158
+ if (out) yield out;
159
+ }
160
+ reason = finish.reason ?? "stop";
161
+ if (reason !== "length" || !accumulated.trim()) break;
162
+ }
163
+ onFinishReason?.(reason);
164
+ }
165
+ /**
166
+ * Wrap a provider so chat, complete, message and stream continue replies that
167
+ * stop on the output limit. Continuation is off unless the call (or the
168
+ * client's `continueOnLength` option) enables it.
169
+ *
170
+ * `client` is what each request goes through, normally the rate-limited
171
+ * provider, so every continuation part is paced and retried on its own and a
172
+ * rate-limit retry on part N never re-requests parts 1..N-1. `adapter` is the
173
+ * unwrapped provider: a continued `complete`/`message` runs the adapter's own
174
+ * method against this proxy so its internal `this.chat` reaches the continuing
175
+ * (and per-part paced) chat instead of being paced as one opaque call.
176
+ */
177
+ function createContinuingAI(client, providerOptions, adapter = client) {
178
+ const wrapped = /* @__PURE__ */ new Map();
179
+ const proxy = new Proxy(client, { get(target, property) {
180
+ const value = Reflect.get(target, property, target);
181
+ if (typeof value !== "function") return value;
182
+ if (!wrapped.has(property)) {
183
+ let fn;
184
+ switch (property) {
185
+ case "chat":
186
+ fn = (messages, options) => chatWithContinuation((m, o) => value.call(target, m, o), providerOptions, messages, options);
187
+ break;
188
+ case "stream":
189
+ fn = (messages, options) => streamWithContinuation((m, o) => value.call(target, m, o), providerOptions, messages, options);
190
+ break;
191
+ case "complete":
192
+ case "message":
193
+ fn = (...args) => {
194
+ if (!resolveContinuation(providerOptions, args[1] ?? {})) return value.apply(target, args);
195
+ return Reflect.get(adapter, property, adapter).apply(proxy, args);
196
+ };
197
+ break;
198
+ default: fn = value.bind(target);
199
+ }
200
+ wrapped.set(property, fn);
201
+ }
202
+ return wrapped.get(property);
203
+ } });
204
+ return proxy;
205
+ }
206
+ //#endregion
207
+ export { resolveContinuation as n, createContinuingAI as t };
208
+
209
+ //# sourceMappingURL=continuation-D0NisZA1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"continuation-D0NisZA1.js","names":[],"sources":["../../src/shared/continuation.ts"],"sourcesContent":["import type {\n AIFinishReason,\n AIInterface,\n AIMessage,\n AIResponse,\n BaseAIOptions,\n ChatOptions,\n ContinueOnLengthOption,\n TokenUsage,\n} from './types';\nimport { AIError } from './types';\n\n/** Default number of extra requests allowed after the first one. */\nexport const DEFAULT_MAX_CONTINUATIONS = 3;\n\n/** Shortest seam overlap (in characters) that is trimmed as a repeat. */\nconst MIN_OVERLAP = 12;\n/** Longest seam overlap (in characters) that is searched for. */\nconst MAX_OVERLAP = 400;\n\nexport const CONTINUE_PROMPT =\n 'Continue exactly where you stopped. Do not repeat anything you already wrote and do not add an introduction.';\n\ninterface ResolvedContinuation {\n maxContinuations: number;\n}\n\n/**\n * Resolve the per-call option over the client default. Returns undefined when\n * continuation is off. Structured output and tool use never continue because\n * stitching two JSON documents or tool-call turns is not safe.\n */\nexport function resolveContinuation(\n providerOptions: BaseAIOptions,\n options: Pick<ChatOptions, 'tools' | 'responseFormat'> & {\n continueOnLength?: ContinueOnLengthOption;\n },\n): ResolvedContinuation | undefined {\n const setting = options.continueOnLength ?? providerOptions.continueOnLength;\n if (!setting) return undefined;\n if (options.tools?.length || options.responseFormat?.type === 'json_object') {\n return undefined;\n }\n const requested =\n typeof setting === 'object'\n ? (setting.maxContinuations ?? DEFAULT_MAX_CONTINUATIONS)\n : DEFAULT_MAX_CONTINUATIONS;\n if (!Number.isFinite(requested) || requested < 0) {\n throw new AIError(\n 'continueOnLength.maxContinuations must be a non-negative finite number',\n 'AI_LIMIT_INVALID',\n );\n }\n const maxContinuations = Math.trunc(requested);\n return maxContinuations > 0 ? { maxContinuations } : undefined;\n}\n\n/**\n * Continuation replies usually start without a leading space. When the text\n * so far ends a sentence and the addition starts a new one, add the space the\n * model dropped at the seam (\"over.\" + \"Next\" -> \"over. Next\"). Only a capital\n * letter or an opening quote/bracket counts as a new sentence, so a word or\n * number split across parts (\"exam\" + \"ple\", \"1.\" + \"5\") is left joined.\n */\nfunction spaceSeam(accumulated: string, addition: string): string {\n if (!addition || /\\s$/.test(accumulated) || /^\\s/.test(addition)) {\n return addition;\n }\n return /[.!?:;][\"')\\]\\u201d\\u2019]?$/.test(accumulated) &&\n /^[\\p{Lu}\"'([\\u201c\\u2018]/u.test(addition)\n ? ` ${addition}`\n : addition;\n}\n\n/**\n * The text to append after `accumulated`: `next` minus any leading run that\n * repeats the end of `accumulated`. Whitespace already emitted is kept, and a\n * dropped space between sentences is restored.\n */\nexport function continuationAddition(\n accumulated: string,\n next: string,\n): string {\n const tail = accumulated.trimEnd();\n const head = next.trimStart();\n const max = Math.min(tail.length, head.length, MAX_OVERLAP);\n for (let length = max; length >= MIN_OVERLAP; length--) {\n if (tail.endsWith(head.slice(0, length))) {\n return spaceSeam(accumulated, head.slice(length));\n }\n }\n return spaceSeam(accumulated, next);\n}\n\nfunction continuationMessages(\n messages: AIMessage[],\n accumulated: string,\n): AIMessage[] {\n return [\n ...messages,\n // Some providers reject an assistant turn that ends in whitespace.\n { role: 'assistant', content: accumulated.trimEnd() },\n { role: 'user', content: CONTINUE_PROMPT },\n ];\n}\n\n/** Do not start another (billed) part once the caller has aborted. */\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new AIError('AI request aborted by caller', 'AI_ABORTED');\n }\n}\n\nfunction addUsage(\n a: TokenUsage | undefined,\n b: TokenUsage | undefined,\n): TokenUsage | undefined {\n if (!a) return b;\n if (!b) return a;\n return {\n promptTokens: a.promptTokens + b.promptTokens,\n completionTokens: a.completionTokens + b.completionTokens,\n totalTokens: a.totalTokens + b.totalTokens,\n };\n}\n\n/**\n * Run a chat call and, when the reply stopped on the output limit, keep asking\n * the model to continue and stitch the pieces together.\n */\nexport async function chatWithContinuation(\n chat: (messages: AIMessage[], options?: ChatOptions) => Promise<AIResponse>,\n providerOptions: BaseAIOptions,\n messages: AIMessage[],\n options: ChatOptions & { continueOnLength?: ContinueOnLengthOption } = {},\n): Promise<AIResponse> {\n const config = resolveContinuation(providerOptions, options);\n const first = await chat(messages, options);\n const hitLimit = (response: AIResponse) =>\n response.finishReason === 'length' && !response.toolCalls?.length;\n\n // A first part with no text (reasoning used the whole budget) has nothing to\n // continue from, and providers reject an empty assistant turn.\n if (!config || !hitLimit(first) || !first.content.trim()) {\n return first.finishReason === 'length'\n ? { ...first, truncated: true }\n : first;\n }\n\n // Parts after the first are deduplicated, so progress is reported once with\n // the stitched text instead of with each raw chunk.\n const { onProgress, ...partOptions } = options;\n let content = first.content;\n let usage = first.usage;\n let last = first;\n let parts = 1;\n\n while (parts <= config.maxContinuations && hitLimit(last)) {\n throwIfAborted(options.signal);\n const next = await chat(continuationMessages(messages, content), {\n ...partOptions,\n stream: false,\n });\n parts++;\n usage = addUsage(usage, next.usage);\n last = next;\n const addition = continuationAddition(content, next.content);\n if (!addition) break; // no new text: stop looping\n content += addition;\n if (onProgress) onProgress(addition);\n }\n\n return {\n ...last,\n content,\n usage,\n model: last.model ?? first.model,\n parts,\n truncated: hitLimit(last),\n };\n}\n\n/**\n * Stream a reply across continuations as one continuous stream. A continuation\n * part's first characters are held back briefly so a repeated seam can be\n * trimmed before it reaches the consumer.\n */\nexport async function* streamWithContinuation(\n stream: (\n messages: AIMessage[],\n options?: ChatOptions,\n ) => AsyncIterable<string>,\n providerOptions: BaseAIOptions,\n messages: AIMessage[],\n options: ChatOptions & { continueOnLength?: ContinueOnLengthOption } = {},\n): AsyncIterable<string> {\n const config = resolveContinuation(providerOptions, options);\n if (!config) {\n yield* stream(messages, options);\n return;\n }\n\n const { onProgress, onFinishReason, ...rest } = options;\n let accumulated = '';\n // Reported once, for the last part: an earlier part's 'length' is not the\n // reply's finish reason.\n let reason: AIFinishReason = 'stop';\n for (let part = 0; part <= config.maxContinuations; part++) {\n const finish: { reason?: AIFinishReason } = {};\n const partOptions: ChatOptions = {\n ...rest,\n onFinishReason: (value) => {\n finish.reason = value;\n },\n };\n if (part > 0) throwIfAborted(options.signal);\n const source = stream(\n part === 0 ? messages : continuationMessages(messages, accumulated),\n partOptions,\n );\n let pending = '';\n let seam = part > 0;\n const emit = (text: string) => {\n if (!text) return null;\n accumulated += text;\n onProgress?.(text);\n return text;\n };\n for await (const chunk of source) {\n if (!seam) {\n const out = emit(chunk);\n if (out) yield out;\n continue;\n }\n pending += chunk;\n if (pending.length >= MAX_OVERLAP + MIN_OVERLAP) {\n seam = false;\n const out = emit(continuationAddition(accumulated, pending));\n if (out) yield out;\n pending = '';\n }\n }\n if (seam && pending) {\n const out = emit(continuationAddition(accumulated, pending));\n if (out) yield out;\n }\n reason = finish.reason ?? 'stop';\n // Stop unless the part hit the limit and there is text to continue from\n // (see chatWithContinuation).\n if (reason !== 'length' || !accumulated.trim()) break;\n }\n onFinishReason?.(reason);\n}\n\n/**\n * Wrap a provider so chat, complete, message and stream continue replies that\n * stop on the output limit. Continuation is off unless the call (or the\n * client's `continueOnLength` option) enables it.\n *\n * `client` is what each request goes through, normally the rate-limited\n * provider, so every continuation part is paced and retried on its own and a\n * rate-limit retry on part N never re-requests parts 1..N-1. `adapter` is the\n * unwrapped provider: a continued `complete`/`message` runs the adapter's own\n * method against this proxy so its internal `this.chat` reaches the continuing\n * (and per-part paced) chat instead of being paced as one opaque call.\n */\nexport function createContinuingAI<T extends AIInterface>(\n client: T,\n providerOptions: BaseAIOptions,\n adapter: AIInterface = client,\n): T {\n const wrapped = new Map<PropertyKey, unknown>();\n const proxy: T = new Proxy(client, {\n get(target, property) {\n const value = Reflect.get(target, property, target);\n if (typeof value !== 'function') return value;\n if (!wrapped.has(property)) {\n let fn: unknown;\n switch (property) {\n case 'chat':\n fn = (messages: AIMessage[], options?: ChatOptions) =>\n chatWithContinuation(\n (m, o) => value.call(target, m, o),\n providerOptions,\n messages,\n options,\n );\n break;\n case 'stream':\n fn = (messages: AIMessage[], options?: ChatOptions) =>\n streamWithContinuation(\n (m, o) => value.call(target, m, o),\n providerOptions,\n messages,\n options,\n );\n break;\n case 'complete':\n case 'message':\n fn = (...args: unknown[]) => {\n const options = (args[1] ?? {}) as Parameters<\n typeof resolveContinuation\n >[1];\n if (!resolveContinuation(providerOptions, options)) {\n return value.apply(target, args);\n }\n // Adapters implement these through this.chat, so run the\n // adapter's method against the proxy to reach the continuing\n // chat().\n const own = Reflect.get(adapter, property, adapter) as (\n ...a: unknown[]\n ) => unknown;\n return own.apply(proxy, args);\n };\n break;\n default:\n fn = value.bind(target);\n }\n wrapped.set(property, fn);\n }\n return wrapped.get(property);\n },\n });\n return proxy;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as AIError } from "./types-BpnNcTU1.js";
1
+ import { t as AIError } from "./types-DB5W6_F-.js";
2
2
  //#region src/shared/providers/gateway-admin.ts
3
3
  function stripTrailingSlash(value) {
4
4
  return value.endsWith("/") ? value.slice(0, -1) : value;
@@ -278,6 +278,56 @@ var LiteLLMAdmin = class {
278
278
  }
279
279
  };
280
280
  //#endregion
281
- export { resolveGatewayAdminBaseUrl as i, LiteLLMAdmin as n, normalizeGatewayBaseUrl as r, BifrostAdmin as t };
281
+ //#region src/shared/providers/gateway-audio-models.ts
282
+ /**
283
+ * Audio model classification shared by the Bifrost and LiteLLM gateway
284
+ * providers.
285
+ *
286
+ * Markers are matched as whole model-ID tokens (split on `/`, `-`, `_`, `.`,
287
+ * `:` and whitespace), never as raw substrings, so an author or model segment
288
+ * that merely contains a marker (e.g. `tts` inside `mattshumer`) is not
289
+ * classified as audio.
290
+ */
291
+ function modelIdTokens(modelId) {
292
+ return modelId.toLowerCase().split(/[/_.:\s-]+/).filter(Boolean);
293
+ }
294
+ function hasTokenSequence(tokens, sequence) {
295
+ for (let i = 0; i + sequence.length <= tokens.length; i++) if (sequence.every((token, offset) => tokens[i + offset] === token)) return true;
296
+ return false;
297
+ }
298
+ var TRANSCRIPTION_TOKENS = /* @__PURE__ */ new Set([
299
+ "transcribe",
300
+ "transcriber",
301
+ "transcription",
302
+ "whisper"
303
+ ]);
304
+ var SPEECH_TOKENS = /* @__PURE__ */ new Set(["tts", "speech"]);
305
+ /**
306
+ * Speech-to-text models (e.g. `whisper-1`, `gpt-4o-transcribe`,
307
+ * `speech-to-text-v1`, `voxtral-mini-4b-realtime`). Listed with the
308
+ * `transcription` capability so gateway consumers can route audio to them;
309
+ * never eligible for chat, vision, embeddings, or image generation.
310
+ */
311
+ function isTranscriptionModel(modelId) {
312
+ const tokens = modelIdTokens(modelId);
313
+ return tokens.some((token) => TRANSCRIPTION_TOKENS.has(token)) || hasTokenSequence(tokens, [
314
+ "speech",
315
+ "to",
316
+ "text"
317
+ ]) || tokens.includes("voxtral") && tokens.includes("realtime");
318
+ }
319
+ /**
320
+ * Text-to-speech models (e.g. `tts-1`, `gpt-4o-mini-tts`). Listed with the
321
+ * `speech` capability; never eligible for chat, vision, embeddings, or image
322
+ * generation.
323
+ */
324
+ function isSpeechModel(modelId) {
325
+ return !isTranscriptionModel(modelId) && modelIdTokens(modelId).some((token) => SPEECH_TOKENS.has(token));
326
+ }
327
+ function isAudioModel(modelId) {
328
+ return isTranscriptionModel(modelId) || isSpeechModel(modelId);
329
+ }
330
+ //#endregion
331
+ export { LiteLLMAdmin as a, BifrostAdmin as i, isSpeechModel as n, normalizeGatewayBaseUrl as o, isTranscriptionModel as r, resolveGatewayAdminBaseUrl as s, isAudioModel as t };
282
332
 
283
- //# sourceMappingURL=gateway-admin-04mvbDHC.js.map
333
+ //# sourceMappingURL=gateway-audio-models-Dn7Wrc6P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-audio-models-Dn7Wrc6P.js","names":[],"sources":["../../src/shared/providers/gateway-admin.ts","../../src/shared/providers/gateway-audio-models.ts"],"sourcesContent":["import type {\n AIAdminBudget,\n AIAdminInterface,\n AIAdminProject,\n AIAdminProviderConfig,\n AIAdminRateLimit,\n AIVirtualKey,\n CreateAIProjectOptions,\n CreateAIVirtualKeyOptions,\n} from '../types';\nimport { AIError } from '../types';\n\ninterface GatewayAdminTransportOptions {\n provider: string;\n baseUrl: string;\n apiKey?: string;\n username?: string;\n password?: string;\n headers?: Record<string, string>;\n timeout?: number;\n}\n\ntype JsonRecord = Record<string, unknown>;\n\nfunction stripTrailingSlash(value: string): string {\n return value.endsWith('/') ? value.slice(0, -1) : value;\n}\n\nexport function normalizeGatewayBaseUrl(baseUrl: string): string {\n return stripTrailingSlash(baseUrl.trim());\n}\n\n/**\n * Derive the gateway root URL for admin APIs from an OpenAI-compatible base URL.\n *\n * Strips known inference path suffixes iteratively so that a baseUrl such as\n * `http://host/openai/v1` collapses to the gateway root rather than to the\n * partial `http://host/openai`.\n */\nexport function deriveGatewayAdminBaseUrl(baseUrl: string): string {\n let normalized = normalizeGatewayBaseUrl(baseUrl);\n const suffixes = ['/pydanticai/v1', '/openai', '/v1'];\n let stripped = true;\n\n while (stripped) {\n stripped = false;\n for (const suffix of suffixes) {\n if (normalized.endsWith(suffix)) {\n normalized = normalized.slice(0, -suffix.length);\n stripped = true;\n break;\n }\n }\n }\n\n return normalized || normalizeGatewayBaseUrl(baseUrl);\n}\n\nexport function resolveGatewayAdminBaseUrl(\n baseUrl: string | undefined,\n adminBaseUrl: string | undefined,\n provider: string,\n): string {\n const configuredBaseUrl = adminBaseUrl || baseUrl;\n if (!configuredBaseUrl?.trim()) {\n throw new AIError(\n `${provider} baseUrl is required for admin operations`,\n 'ADMIN_BASE_URL_REQUIRED',\n provider,\n );\n }\n\n return adminBaseUrl\n ? normalizeGatewayBaseUrl(adminBaseUrl)\n : deriveGatewayAdminBaseUrl(configuredBaseUrl);\n}\n\nexport function slugifyProjectId(name: string, tenantId?: string): string {\n const value = [tenantId, name].filter(Boolean).join('-');\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\n return slug || 'ai-project';\n}\n\nfunction toJsonRecord(value: unknown): JsonRecord {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as JsonRecord)\n : {};\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction encodeBasicCredentials(username: string, password: string): string {\n const value = `${username}:${password}`;\n\n if (typeof btoa === 'function') {\n return btoa(value);\n }\n\n return Buffer.from(value, 'utf8').toString('base64');\n}\n\nfunction removeUndefinedValues<T extends JsonRecord>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) {\n delete value[key];\n }\n }\n\n return value;\n}\n\nfunction createMetadata(\n options: {\n tenantId?: string;\n description?: string;\n metadata?: Record<string, unknown>;\n },\n tenantField = 'tenant_id',\n): Record<string, unknown> | undefined {\n const metadata = {\n ...options.metadata,\n };\n\n if (options.tenantId) {\n metadata[tenantField] = options.tenantId;\n }\n\n if (options.description) {\n metadata.description = options.description;\n }\n\n return Object.keys(metadata).length > 0 ? metadata : undefined;\n}\n\nfunction responseMessage(body: unknown, fallback: string): string {\n const record = toJsonRecord(body);\n const error = toJsonRecord(record.error);\n\n return (\n stringValue(error.message) ||\n stringValue(record.message) ||\n stringValue(record.detail) ||\n fallback\n );\n}\n\nclass GatewayAdminTransport {\n private readonly baseUrl: string;\n private readonly provider: string;\n private readonly apiKey?: string;\n private readonly username?: string;\n private readonly password?: string;\n private readonly headers?: Record<string, string>;\n private readonly timeout?: number;\n\n constructor(options: GatewayAdminTransportOptions) {\n this.provider = options.provider;\n this.baseUrl = normalizeGatewayBaseUrl(options.baseUrl);\n this.apiKey = options.apiKey;\n this.username = options.username;\n this.password = options.password;\n this.headers = options.headers;\n this.timeout = options.timeout;\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Record<string, string | undefined>,\n ): Promise<T> {\n const url = new URL(\n `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`,\n );\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) {\n url.searchParams.set(key, value);\n }\n }\n }\n\n const controller =\n typeof AbortController === 'undefined'\n ? undefined\n : new AbortController();\n const timeoutHandle =\n controller && this.timeout\n ? setTimeout(() => controller.abort(), this.timeout)\n : undefined;\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n Accept: 'application/json',\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...this.headers,\n ...this.createAuthHeaders(),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller?.signal,\n });\n\n const text = await response.text();\n const parsed = text ? this.parseJson(text) : undefined;\n\n if (!response.ok) {\n throw this.mapResponseError(response, parsed);\n }\n\n return parsed as T;\n } catch (error) {\n if (error instanceof AIError) {\n throw error;\n }\n\n throw new AIError(\n `${this.provider} admin request failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n 'ADMIN_REQUEST_FAILED',\n this.provider,\n );\n } finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle);\n }\n }\n }\n\n private createAuthHeaders(): Record<string, string> {\n if (this.username && this.password) {\n return {\n Authorization: `Basic ${encodeBasicCredentials(\n this.username,\n this.password,\n )}`,\n };\n }\n\n return this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {};\n }\n\n private parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch (_error) {\n return { message: text };\n }\n }\n\n private mapResponseError(response: Response, body: unknown): AIError {\n const message = responseMessage(\n body,\n `${this.provider} admin request failed with ${response.status}`,\n );\n\n if (response.status === 401 || response.status === 403) {\n return new AIError(message, 'AUTH_ERROR', this.provider);\n }\n\n if (response.status === 429) {\n return new AIError(message, 'RATE_LIMIT', this.provider, undefined, true);\n }\n\n return new AIError(\n message,\n `ADMIN_HTTP_${response.status}`,\n this.provider,\n undefined,\n response.status >= 500,\n );\n }\n}\n\nfunction mapBifrostBudget(budget?: AIAdminBudget): JsonRecord | undefined {\n if (!budget) return undefined;\n\n return removeUndefinedValues({\n max_limit: budget.maxLimit,\n reset_duration: budget.resetDuration,\n calendar_aligned: budget.calendarAligned,\n });\n}\n\nfunction mapBifrostRateLimit(\n rateLimit?: AIAdminRateLimit,\n): JsonRecord | undefined {\n if (!rateLimit) return undefined;\n\n return removeUndefinedValues({\n token_max_limit: rateLimit.tokenMaxLimit ?? rateLimit.tpmLimit,\n token_reset_duration: rateLimit.tokenResetDuration,\n request_max_limit: rateLimit.requestMaxLimit ?? rateLimit.rpmLimit,\n request_reset_duration: rateLimit.requestResetDuration,\n });\n}\n\nfunction mapBifrostProviderConfigs(\n configs?: AIAdminProviderConfig[],\n): JsonRecord[] | undefined {\n if (!configs) return undefined;\n\n return configs.map((config) =>\n removeUndefinedValues({\n provider: config.provider,\n weight: config.weight,\n allowed_models: config.allowedModels,\n key_ids: config.keyIds,\n }),\n );\n}\n\nfunction mapLiteLLMBudget(\n budget?: AIAdminBudget,\n): Pick<JsonRecord, 'max_budget' | 'budget_duration'> {\n return removeUndefinedValues({\n max_budget: budget?.maxLimit,\n budget_duration: budget?.resetDuration,\n });\n}\n\nfunction mapLiteLLMRateLimit(\n rateLimit?: AIAdminRateLimit,\n): Pick<JsonRecord, 'tpm_limit' | 'rpm_limit'> {\n return removeUndefinedValues({\n tpm_limit: rateLimit?.tpmLimit ?? rateLimit?.tokenMaxLimit,\n rpm_limit: rateLimit?.rpmLimit ?? rateLimit?.requestMaxLimit,\n });\n}\n\nexport class BifrostAdmin implements AIAdminInterface {\n private readonly transport: GatewayAdminTransport;\n\n constructor(options: GatewayAdminTransportOptions) {\n this.transport = new GatewayAdminTransport(options);\n }\n\n async createProject(\n options: CreateAIProjectOptions,\n ): Promise<AIAdminProject> {\n const body = removeUndefinedValues({\n name: options.name,\n customer_id: options.tenantId,\n budget: mapBifrostBudget(options.budget),\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/api/governance/teams',\n body,\n );\n const team = toJsonRecord(response.team ?? response);\n const id = stringValue(team.id) || stringValue(team.team_id);\n\n if (!id) {\n throw new AIError(\n 'Bifrost did not return a project id',\n 'ADMIN_INVALID_RESPONSE',\n 'bifrost',\n );\n }\n\n return {\n id,\n name:\n stringValue(team.name) || stringValue(team.team_alias) || options.name,\n tenantId: stringValue(team.customer_id) || options.tenantId,\n budgetId: stringValue(team.budget_id),\n provider: 'bifrost',\n raw: response,\n };\n }\n\n async createVirtualKey(\n options: CreateAIVirtualKeyOptions,\n ): Promise<AIVirtualKey> {\n const body = removeUndefinedValues({\n name: options.name,\n description: options.description,\n provider_configs: mapBifrostProviderConfigs(options.providerConfigs),\n team_id: options.projectId,\n customer_id: options.projectId ? undefined : options.tenantId,\n budget: mapBifrostBudget(options.budget),\n rate_limit: mapBifrostRateLimit(options.rateLimit),\n key_ids: options.keyIds,\n is_active: options.isActive ?? true,\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/api/governance/virtual-keys',\n body,\n );\n const virtualKey = toJsonRecord(\n response.virtual_key ?? response.virtualKey ?? response,\n );\n\n return {\n id: stringValue(virtualKey.id) || stringValue(virtualKey.key_id),\n name: stringValue(virtualKey.name) || options.name,\n key: stringValue(virtualKey.value) || stringValue(virtualKey.key),\n maskedKey:\n stringValue(virtualKey.key_name) || stringValue(virtualKey.masked_key),\n projectId: stringValue(virtualKey.team_id) || options.projectId,\n tenantId: stringValue(virtualKey.customer_id) || options.tenantId,\n expiresAt:\n stringValue(virtualKey.expires) || stringValue(virtualKey.expires_at),\n provider: 'bifrost',\n raw: response,\n };\n }\n}\n\nexport class LiteLLMAdmin implements AIAdminInterface {\n private readonly transport: GatewayAdminTransport;\n\n constructor(options: GatewayAdminTransportOptions) {\n this.transport = new GatewayAdminTransport(options);\n }\n\n async createProject(\n options: CreateAIProjectOptions,\n ): Promise<AIAdminProject> {\n const projectId =\n options.id || slugifyProjectId(options.name, options.tenantId);\n const body = removeUndefinedValues({\n team_id: projectId,\n team_alias: options.name,\n models: options.models,\n ...mapLiteLLMBudget(options.budget),\n ...mapLiteLLMRateLimit(options.rateLimit),\n metadata: createMetadata(options),\n blocked: options.isBlocked,\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/team/new',\n body,\n );\n const team = toJsonRecord(response.team ?? response);\n\n return {\n id: stringValue(team.team_id) || projectId,\n name:\n stringValue(team.team_alias) || stringValue(team.name) || options.name,\n tenantId: options.tenantId,\n provider: 'litellm',\n raw: response,\n };\n }\n\n async createVirtualKey(\n options: CreateAIVirtualKeyOptions,\n ): Promise<AIVirtualKey> {\n const body = removeUndefinedValues({\n key_alias: options.name,\n team_id: options.projectId,\n user_id: options.userId,\n models: options.models,\n duration: options.duration,\n ...mapLiteLLMBudget(options.budget),\n ...mapLiteLLMRateLimit(options.rateLimit),\n metadata: createMetadata(options),\n aliases: options.aliases,\n config: options.config,\n permissions: options.permissions,\n blocked: options.isActive === undefined ? undefined : !options.isActive,\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/key/generate',\n body,\n );\n\n return {\n id: stringValue(response.token_id) || stringValue(response.key_id),\n name:\n stringValue(response.key_alias) ||\n stringValue(response.key_name) ||\n options.name,\n key: stringValue(response.key),\n maskedKey: stringValue(response.key_name),\n projectId: stringValue(response.team_id) || options.projectId,\n tenantId: options.tenantId,\n expiresAt: stringValue(response.expires),\n provider: 'litellm',\n raw: response,\n };\n }\n}\n","/**\n * Audio model classification shared by the Bifrost and LiteLLM gateway\n * providers.\n *\n * Markers are matched as whole model-ID tokens (split on `/`, `-`, `_`, `.`,\n * `:` and whitespace), never as raw substrings, so an author or model segment\n * that merely contains a marker (e.g. `tts` inside `mattshumer`) is not\n * classified as audio.\n */\n\nfunction modelIdTokens(modelId: string): string[] {\n return modelId\n .toLowerCase()\n .split(/[/_.:\\s-]+/)\n .filter(Boolean);\n}\n\nfunction hasTokenSequence(tokens: string[], sequence: string[]): boolean {\n for (let i = 0; i + sequence.length <= tokens.length; i++) {\n if (sequence.every((token, offset) => tokens[i + offset] === token)) {\n return true;\n }\n }\n return false;\n}\n\nconst TRANSCRIPTION_TOKENS = new Set([\n 'transcribe',\n 'transcriber',\n 'transcription',\n 'whisper',\n]);\n\nconst SPEECH_TOKENS = new Set(['tts', 'speech']);\n\n/**\n * Speech-to-text models (e.g. `whisper-1`, `gpt-4o-transcribe`,\n * `speech-to-text-v1`, `voxtral-mini-4b-realtime`). Listed with the\n * `transcription` capability so gateway consumers can route audio to them;\n * never eligible for chat, vision, embeddings, or image generation.\n */\nexport function isTranscriptionModel(modelId: string): boolean {\n const tokens = modelIdTokens(modelId);\n return (\n tokens.some((token) => TRANSCRIPTION_TOKENS.has(token)) ||\n hasTokenSequence(tokens, ['speech', 'to', 'text']) ||\n // Voxtral realtime checkpoints are streaming speech-to-text only; other\n // Voxtral models are audio-understanding chat models.\n (tokens.includes('voxtral') && tokens.includes('realtime'))\n );\n}\n\n/**\n * Text-to-speech models (e.g. `tts-1`, `gpt-4o-mini-tts`). Listed with the\n * `speech` capability; never eligible for chat, vision, embeddings, or image\n * generation.\n */\nexport function isSpeechModel(modelId: string): boolean {\n return (\n !isTranscriptionModel(modelId) &&\n modelIdTokens(modelId).some((token) => SPEECH_TOKENS.has(token))\n );\n}\n\nexport function isAudioModel(modelId: string): boolean {\n return isTranscriptionModel(modelId) || isSpeechModel(modelId);\n}\n"],"mappings":"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"}