{"version":3,"sources":["../src/tools/parsers.ts","../src/tools/converters.ts","../src/logging/plugin-logger.ts","../src/logging/index.ts","../src/providers/openai-compatible.ts","../src/version.ts","../src/providers/request-budget.ts","../src/providers/profile-parameters.ts","../src/providers/system-messages.ts","../src/system-prompts/builder.ts","../src/builtin-tools.ts","../src/host-rewrite.ts","../src/version.generated.ts"],"sourcesContent":["/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n  const toolCalls: ToolCallRequest[] = [];\n\n  try {\n    const resp = response as Record<string, unknown>;\n\n    // Handle direct tool_calls array (snake_case)\n    let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n    // Handle direct toolCalls array (camelCase - some SDKs use this)\n    if (!toolCallsArray) {\n      toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n    }\n\n    // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n    if (!toolCallsArray) {\n      const choices = resp?.choices as\n        | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n        | undefined;\n      toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n    }\n\n    // Check nested structure from streaming responses: choices[0].delta.toolCalls\n    // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n    if (!toolCallsArray) {\n      const choices = resp?.choices as\n        | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n        | undefined;\n      toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n    }\n\n    if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n      for (const toolCall of toolCallsArray) {\n        const tc = toolCall as {\n          id?: string;\n          type?: string;\n          function?: { name: string; arguments: string };\n        };\n\n        if (tc.type === 'function' && tc.function) {\n          const argsStr = tc.function.arguments || '{}';\n\n          // During streaming, arguments may be incomplete JSON.\n          // Check if the string looks like complete JSON before parsing.\n          // Skip incomplete tool calls - they'll be complete in the final response.\n          const trimmed = argsStr.trim();\n          if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {\n            // Incomplete JSON - skip this tool call for now\n            continue;\n          }\n\n          try {\n            toolCalls.push({\n              name: tc.function.name,\n              arguments: JSON.parse(argsStr),\n              callId: tc.id || undefined,\n            });\n          } catch {\n            // JSON parse failed (e.g., incomplete JSON during streaming)\n            // This is expected during streaming - skip silently\n            continue;\n          }\n        }\n      }\n    }\n  } catch (error) {\n    // Log error for unexpected parsing failures (not JSON parse errors)\n    console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n  }\n\n  return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n  const toolCalls: ToolCallRequest[] = [];\n\n  try {\n    const resp = response as Record<string, unknown>;\n\n    if (!resp?.content || !Array.isArray(resp.content)) {\n      return toolCalls;\n    }\n\n    for (const block of resp.content) {\n      const b = block as { type?: string; id?: string; name?: string; input?: Record<string, unknown> };\n\n      if (b.type === 'tool_use' && b.name) {\n        toolCalls.push({\n          name: b.name,\n          arguments: b.input || {},\n          callId: b.id || undefined,\n        });\n      }\n    }\n  } catch (error) {\n    console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n  }\n\n  return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n  const toolCalls: ToolCallRequest[] = [];\n\n  try {\n    const resp = response as Record<string, unknown>;\n    const candidates = resp?.candidates as\n      | Array<{ content?: { parts?: unknown[] } }>\n      | undefined;\n    const parts = candidates?.[0]?.content?.parts;\n\n    if (!parts || !Array.isArray(parts)) {\n      return toolCalls;\n    }\n\n    for (const part of parts) {\n      const p = part as {\n        functionCall?: { name: string; args?: Record<string, unknown> };\n      };\n\n      if (p.functionCall) {\n        toolCalls.push({\n          name: p.functionCall.name,\n          arguments: p.functionCall.args || {},\n        });\n      }\n    }\n  } catch (error) {\n    console.error('[plugin-utils] Error parsing Google tool calls:', error);\n  }\n\n  return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n  if (!response || typeof response !== 'object') {\n    return null;\n  }\n\n  const resp = response as Record<string, unknown>;\n\n  // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n  if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n    return 'openai';\n  }\n  if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n    return 'openai';\n  }\n\n  const choices = resp.choices as Array<{\n    message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n    delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n  }> | undefined;\n  if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n    return 'openai';\n  }\n  // Check delta for streaming responses (OpenRouter SDK uses this)\n  if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n    return 'openai';\n  }\n\n  // Anthropic format: has content array with tool_use type\n  if (resp.content && Array.isArray(resp.content)) {\n    const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n      (block) => block.type === 'tool_use'\n    );\n    if (hasToolUse) {\n      return 'anthropic';\n    }\n  }\n\n  // Google format: has candidates[0].content.parts with functionCall\n  const candidates = resp.candidates as\n    | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n    | undefined;\n  if (candidates?.[0]?.content?.parts) {\n    const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n    if (hasFunctionCall) {\n      return 'google';\n    }\n  }\n\n  return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n  response: unknown,\n  format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n  let actualFormat: ToolCallFormat | null = format;\n\n  if (format === 'auto') {\n    actualFormat = detectToolCallFormat(response);\n    if (!actualFormat) {\n      return [];\n    }\n  }\n\n  switch (actualFormat) {\n    case 'openai':\n      return parseOpenAIToolCalls(response);\n    case 'anthropic':\n      return parseAnthropicToolCalls(response);\n    case 'google':\n      return parseGoogleToolCalls(response);\n    default:\n      return [];\n  }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n  const format = detectToolCallFormat(response);\n  return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n  UniversalTool,\n  AnthropicToolDefinition,\n  GoogleToolDefinition,\n  OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * //   name: 'search_web',\n * //   description: 'Search the web',\n * //   input_schema: {\n * //     type: 'object',\n * //     properties: { query: { type: 'string' } },\n * //     required: ['query']\n * //   }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n  return {\n    name: tool.function.name,\n    description: tool.function.description ?? '',\n    input_schema: {\n      type: 'object',\n      properties: tool.function.parameters?.properties ?? {},\n      required: tool.function.parameters?.required ?? [],\n    },\n  };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * //   name: 'search_web',\n * //   description: 'Search the web',\n * //   parameters: {\n * //     type: 'object',\n * //     properties: { query: { type: 'string' } },\n * //     required: ['query']\n * //   }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n  return {\n    name: tool.function.name,\n    description: tool.function.description ?? '',\n    parameters: {\n      type: 'object',\n      properties: tool.function.parameters?.properties ?? {},\n      required: tool.function.parameters?.required ?? [],\n    },\n  };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n  return {\n    type: 'function',\n    function: {\n      name: tool.name,\n      description: tool.description ?? '',\n      parameters: {\n        type: 'object',\n        properties: tool.input_schema.properties,\n        required: tool.input_schema.required ?? [],\n      },\n    },\n  };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n  return {\n    type: 'function',\n    function: {\n      name: tool.name,\n      description: tool.description,\n      parameters: {\n        type: 'object',\n        properties: tool.parameters.properties,\n        required: tool.parameters.required,\n      },\n    },\n  };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n  tool: T,\n  maxBytes: number\n): T {\n  if (!tool || !tool.description) {\n    return tool;\n  }\n\n  const warningText = ' [Note: description truncated due to length limit]';\n  const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n  if (maxDescBytes <= 0) {\n    console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n    return tool;\n  }\n\n  const descBytes = Buffer.byteLength(tool.description);\n\n  if (descBytes > maxBytes) {\n    // Truncate description to fit within the byte limit\n    let truncated = tool.description;\n    while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n      truncated = truncated.slice(0, -1);\n    }\n\n    return {\n      ...tool,\n      description: truncated + warningText,\n    };\n  }\n\n  return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n  tool: UniversalTool | OpenAIToolDefinition,\n  target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n  switch (target) {\n    case 'anthropic':\n      return convertToAnthropicFormat(tool);\n    case 'google':\n      return convertToGoogleFormat(tool);\n    case 'openai':\n    default:\n      return tool;\n  }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n  tools: Array<UniversalTool | OpenAIToolDefinition>,\n  target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n  return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\n","/**\n * Plugin Logger Bridge\n *\n * Provides a logger factory for plugins that automatically bridges\n * to Quilltap's core logging when running inside the host application,\n * or falls back to console logging when running standalone.\n *\n * @module @quilltap/plugin-utils/logging/plugin-logger\n */\n\nimport type { PluginLogger, LogContext, LogLevel } from '@quilltap/plugin-types';\n\n/**\n * Extended logger interface with child logger support\n */\nexport interface PluginLoggerWithChild extends PluginLogger {\n  /**\n   * Create a child logger with additional context\n   * @param additionalContext Context to merge with parent context\n   * @returns A new logger with combined context\n   */\n  child(additionalContext: LogContext): PluginLoggerWithChild;\n}\n\n/**\n * Type for the global Quilltap logger bridge\n * Stored on globalThis to work across different npm package copies\n */\ndeclare global {\n   \n  var __quilltap_logger_factory:\n    | ((pluginName: string) => PluginLoggerWithChild)\n    | undefined;\n}\n\n/**\n * Get the core logger factory from global namespace\n *\n * @returns The injected factory or null if not in Quilltap environment\n */\nfunction getCoreLoggerFactory(): ((pluginName: string) => PluginLoggerWithChild) | null {\n  return globalThis.__quilltap_logger_factory ?? null;\n}\n\n/**\n * Inject the core logger factory from Quilltap host\n *\n * This is called by Quilltap core when loading plugins to bridge\n * plugin logging into the host's logging system. Uses globalThis\n * to ensure it works even when plugins have their own copy of\n * plugin-utils in their node_modules.\n *\n * **Internal API - not for plugin use**\n *\n * @param factory A function that creates a child logger for a plugin\n */\nexport function __injectCoreLoggerFactory(\n  factory: (pluginName: string) => PluginLoggerWithChild\n): void {\n  globalThis.__quilltap_logger_factory = factory;\n}\n\n/**\n * Clear the injected core logger factory\n *\n * Useful for testing or when unloading the plugin system.\n *\n * **Internal API - not for plugin use**\n */\nexport function __clearCoreLoggerFactory(): void {\n  globalThis.__quilltap_logger_factory = undefined;\n}\n\n/**\n * Check if a core logger has been injected\n *\n * @returns True if running inside Quilltap with core logging available\n */\nexport function hasCoreLogger(): boolean {\n  return getCoreLoggerFactory() !== null;\n}\n\n/**\n * Create a console logger with child support\n *\n * @param prefix Logger prefix\n * @param minLevel Minimum log level\n * @param baseContext Base context to include in all logs\n */\nfunction createConsoleLoggerWithChild(\n  prefix: string,\n  minLevel: LogLevel = 'debug',\n  baseContext: LogContext = {}\n): PluginLoggerWithChild {\n  const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];\n  const shouldLog = (level: LogLevel): boolean =>\n    levels.indexOf(level) >= levels.indexOf(minLevel);\n\n  const formatContext = (context?: LogContext): string => {\n    const merged = { ...baseContext, ...context };\n    const entries = Object.entries(merged)\n      .filter(([key]) => key !== 'context')\n      .map(([key, value]) => `${key}=${JSON.stringify(value)}`)\n      .join(' ');\n    return entries ? ` ${entries}` : '';\n  };\n\n  const logger: PluginLoggerWithChild = {\n    debug: (message: string, context?: LogContext): void => {\n      if (shouldLog('debug')) {\n        console.debug(`[${prefix}] ${message}${formatContext(context)}`);\n      }\n    },\n\n    info: (message: string, context?: LogContext): void => {\n      if (shouldLog('info')) {\n        console.info(`[${prefix}] ${message}${formatContext(context)}`);\n      }\n    },\n\n    warn: (message: string, context?: LogContext): void => {\n      if (shouldLog('warn')) {\n        console.warn(`[${prefix}] ${message}${formatContext(context)}`);\n      }\n    },\n\n    error: (message: string, context?: LogContext, error?: Error): void => {\n      if (shouldLog('error')) {\n        console.error(\n          `[${prefix}] ${message}${formatContext(context)}`,\n          error ? `\\n${error.stack || error.message}` : ''\n        );\n      }\n    },\n\n    child: (additionalContext: LogContext): PluginLoggerWithChild => {\n      return createConsoleLoggerWithChild(prefix, minLevel, {\n        ...baseContext,\n        ...additionalContext,\n      });\n    },\n  };\n\n  return logger;\n}\n\n/**\n * Create a plugin logger that bridges to Quilltap core logging\n *\n * When running inside Quilltap:\n * - Routes all logs to the core logger\n * - Tags logs with `{ plugin: pluginName, module: 'plugin' }`\n * - Logs appear in Quilltap's combined.log and console\n *\n * When running standalone:\n * - Falls back to console logging with `[pluginName]` prefix\n * - Respects the specified minimum log level\n *\n * @param pluginName - The plugin identifier (e.g., 'qtap-plugin-openai')\n * @param minLevel - Minimum log level when running standalone (default: 'debug')\n * @returns A logger instance\n *\n * @example\n * ```typescript\n * // In your plugin's provider.ts\n * import { createPluginLogger } from '@quilltap/plugin-utils';\n *\n * const logger = createPluginLogger('qtap-plugin-my-provider');\n *\n * export class MyProvider implements LLMProvider {\n *   async sendMessage(params: LLMParams, apiKey: string): Promise<LLMResponse> {\n *     logger.debug('Sending message', { model: params.model });\n *\n *     try {\n *       const response = await this.client.chat({...});\n *       logger.info('Received response', { tokens: response.usage?.total_tokens });\n *       return response;\n *     } catch (error) {\n *       logger.error('Failed to send message', { model: params.model }, error as Error);\n *       throw error;\n *     }\n *   }\n * }\n * ```\n */\nexport function createPluginLogger(\n  pluginName: string,\n  minLevel: LogLevel = 'debug'\n): PluginLoggerWithChild {\n  // Check for core logger factory from global namespace\n  const coreFactory = getCoreLoggerFactory();\n  if (coreFactory) {\n    return coreFactory(pluginName);\n  }\n\n  // Standalone mode: use enhanced console logger\n  return createConsoleLoggerWithChild(pluginName, minLevel);\n}\n\n/**\n * Get the minimum log level from environment\n *\n * Checks for LOG_LEVEL or QUILLTAP_LOG_LEVEL environment variables.\n * Useful for configuring standalone plugin logging.\n *\n * @returns The configured log level, or 'info' as default\n */\nexport function getLogLevelFromEnv(): LogLevel {\n  if (typeof process !== 'undefined' && process.env) {\n    const envLevel = process.env.LOG_LEVEL || process.env.QUILLTAP_LOG_LEVEL;\n    if (envLevel && ['debug', 'info', 'warn', 'error'].includes(envLevel)) {\n      return envLevel as LogLevel;\n    }\n  }\n  return 'info';\n}\n","/**\n * Logging Utilities\n *\n * Exports the plugin logger bridge and related utilities.\n *\n * @module @quilltap/plugin-utils/logging\n */\n\nexport {\n  createPluginLogger,\n  hasCoreLogger,\n  getLogLevelFromEnv,\n  __injectCoreLoggerFactory,\n  __clearCoreLoggerFactory,\n} from './plugin-logger';\n\nexport type { PluginLoggerWithChild } from './plugin-logger';\n\n// Re-export logger types from plugin-types\nexport type { PluginLogger, LogContext, LogLevel } from '@quilltap/plugin-types';\n\n// Re-export logger utilities from plugin-types for convenience\nexport { createConsoleLogger, createNoopLogger } from '@quilltap/plugin-types';\n","/**\n * OpenAI-Compatible Provider Base Class\n *\n * A reusable base class for building LLM providers that use OpenAI-compatible APIs.\n * This includes services like:\n * - Local LLM servers (LM Studio, vLLM, Text Generation Web UI, Ollama with OpenAI compat)\n * - Cloud services with OpenAI-compatible APIs (Gab AI, Together AI, Fireworks, etc.)\n *\n * External plugins can extend this class to create custom providers with minimal code:\n *\n * @example\n * ```typescript\n * import { OpenAICompatibleProvider } from '@quilltap/plugin-utils';\n *\n * export class MyCustomProvider extends OpenAICompatibleProvider {\n *   constructor() {\n *     super({\n *       baseUrl: 'https://api.my-service.com/v1',\n *       providerName: 'MyService',\n *       requireApiKey: true,\n *       attachmentErrorMessage: 'MyService does not support file attachments',\n *     });\n *   }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport OpenAI from 'openai';\nimport type {\n  TextProvider,\n  LLMParams,\n  LLMResponse,\n  StreamChunk,\n  PluginLogger,\n} from '@quilltap/plugin-types';\nimport { createPluginLogger } from '../logging';\nimport { getQuilltapUserAgent } from '../version';\nimport { DEFAULT_REQUEST_TIMEOUT_MS, buildSdkRequestOptions } from './request-budget';\nimport { applyProfileParameters } from './profile-parameters';\nimport { collapseLeadingSystemMessages } from './system-messages';\n\n/** Matches the OpenAI SDK default; retries never apply to a caller-supplied budget. */\nconst DEFAULT_MAX_RETRIES = 2;\n\n/**\n * Configuration options for OpenAI-compatible providers.\n *\n * Use this interface when extending OpenAICompatibleProvider to customize\n * the provider's behavior for your specific service.\n */\nexport interface OpenAICompatibleProviderConfig {\n  /**\n   * Base URL for the API endpoint.\n   * Should include the version path (e.g., 'https://api.example.com/v1')\n   */\n  baseUrl: string;\n\n  /**\n   * Provider name used for logging context.\n   * This appears in log messages to identify which provider generated them.\n   * @default 'OpenAICompatible'\n   */\n  providerName?: string;\n\n  /**\n   * Whether an API key is required for this provider.\n   * If true, requests will fail with an error when no API key is provided.\n   * If false, requests will use 'not-needed' as the API key (for local servers).\n   * @default false\n   */\n  requireApiKey?: boolean;\n\n  /**\n   * Custom error message shown when file attachments are attempted.\n   * @default 'OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)'\n   */\n  attachmentErrorMessage?: string;\n\n  /**\n   * Default wall-clock budget per request, in milliseconds.\n   *\n   * The OpenAI SDK's own default is 600 000 ms, which means a service that\n   * accepts a connection and then goes quiet holds the caller for ten minutes\n   * before the first retry. That is far longer than any Quilltap caller wants\n   * to wait, so the base class lowers it.\n   *\n   * Individual requests can tighten this further via `LLMParams.requestTimeoutMs`.\n   *\n   * @default 300000 (5 minutes)\n   */\n  requestTimeoutMs?: number;\n\n  /**\n   * How many times the SDK may retry a failed or timed-out request.\n   *\n   * Applies only to requests that did *not* specify their own\n   * `LLMParams.requestTimeoutMs` — a caller-supplied budget is a hard ceiling\n   * and never retries.\n   *\n   * @default 2 (the OpenAI SDK default)\n   */\n  maxRetries?: number;\n}\n\n/**\n * Base provider class for OpenAI-compatible APIs.\n *\n * This class implements the full LLMProvider interface using the OpenAI SDK,\n * allowing subclasses to create custom providers with just configuration.\n *\n * Features:\n * - Streaming and non-streaming chat completions\n * - API key validation\n * - Model listing\n * - Configurable API key requirements\n * - Dynamic logging with provider name context\n *\n * @remarks\n * File attachments and image generation are not supported by default,\n * as support varies across OpenAI-compatible implementations.\n */\nexport class OpenAICompatibleProvider implements TextProvider {\n  /** File attachments are not supported by default */\n  readonly supportsFileAttachments = false;\n  /** No MIME types are supported for attachments */\n  readonly supportedMimeTypes: string[] = [];\n  /** Web search is not supported */\n  readonly supportsWebSearch = false;\n\n  /** Base URL for the API endpoint */\n  protected readonly baseUrl: string;\n  /** Provider name for logging */\n  protected readonly providerName: string;\n  /** Whether API key is required */\n  protected readonly requireApiKey: boolean;\n  /** Error message for attachment failures */\n  protected readonly attachmentErrorMessage: string;\n  /** Default per-request wall-clock budget in milliseconds */\n  protected readonly requestTimeoutMs: number;\n  /** Retry count for requests that carry no caller-supplied budget */\n  protected readonly maxRetries: number;\n  /** Logger instance */\n  protected readonly logger: PluginLogger;\n\n  /**\n   * Whether the endpoint tolerates more than one leading `system` message.\n   *\n   * True for every hosted service — and so the default, which keeps the\n   * outbound bytes of OpenRouter, Z.AI and every other subclass exactly as they\n   * were. A subclass that points at a local runtime applying the *model's* chat\n   * template sets this false: several template families reject a system message\n   * anywhere but index 0, and Quilltap's context builder deliberately emits up\n   * to three of them so its cache breakpoints survive. See Bug 82 and\n   * {@link collapseLeadingSystemMessages}.\n   */\n  protected readonly acceptsRepeatedSystemMessages: boolean = true;\n\n  /**\n   * Apply {@link acceptsRepeatedSystemMessages} to a mapped wire-message array.\n   * Called by both body builds so the streaming and non-streaming shapes cannot\n   * drift apart.\n   */\n  protected applySystemMessagePolicy<T extends { role: string; content?: string | null }>(\n    messages: T[]\n  ): T[] {\n    return this.acceptsRepeatedSystemMessages ? messages : collapseLeadingSystemMessages(messages);\n  }\n\n  /**\n   * Creates a new OpenAI-compatible provider instance.\n   *\n   * @param config - Configuration object or base URL string (for backward compatibility)\n   */\n  constructor(config: string | OpenAICompatibleProviderConfig) {\n    // Support both legacy string baseUrl and new config object\n    if (typeof config === 'string') {\n      this.baseUrl = config;\n      this.providerName = 'OpenAICompatible';\n      this.requireApiKey = false;\n      this.attachmentErrorMessage =\n        'OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)';\n      this.requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;\n      this.maxRetries = DEFAULT_MAX_RETRIES;\n    } else {\n      this.baseUrl = config.baseUrl;\n      this.providerName = config.providerName ?? 'OpenAICompatible';\n      this.requireApiKey = config.requireApiKey ?? false;\n      this.attachmentErrorMessage =\n        config.attachmentErrorMessage ??\n        'OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)';\n      this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n      this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n    }\n\n    this.logger = createPluginLogger(`${this.providerName}Provider`);\n  }\n\n  /**\n   * Collects attachment failures for messages with attachments.\n   * Since attachments are not supported, all attachments are marked as failed.\n   *\n   * @param params - LLM parameters containing messages\n   * @returns Object with empty sent array and failed attachments\n   */\n  protected collectAttachmentFailures(\n    params: LLMParams\n  ): { sent: string[]; failed: { id: string; error: string }[] } {\n    const failed: { id: string; error: string }[] = [];\n    for (const msg of params.messages) {\n      if (msg.attachments) {\n        for (const attachment of msg.attachments) {\n          failed.push({\n            id: attachment.id,\n            error: this.attachmentErrorMessage,\n          });\n        }\n      }\n    }\n    return { sent: [], failed };\n  }\n\n  /**\n   * Validates that an API key is provided when required.\n   * @throws Error if API key is required but not provided\n   */\n  protected validateApiKeyRequirement(apiKey: string): void {\n    if (this.requireApiKey && !apiKey) {\n      throw new Error(`${this.providerName} provider requires an API key`);\n    }\n  }\n\n  /**\n   * Gets the effective API key to use for requests.\n   * Returns 'not-needed' for providers that don't require keys.\n   */\n  protected getEffectiveApiKey(apiKey: string): string {\n    return this.requireApiKey ? apiKey : apiKey || 'not-needed';\n  }\n\n  /**\n   * Creates an OpenAI client configured with the provider's base URL,\n   * API key, and Quilltap User-Agent header.\n   *\n   * @param apiKey - API key for authentication\n   * @returns Configured OpenAI client instance\n   */\n  protected createClient(apiKey: string): OpenAI {\n    return new OpenAI({\n      apiKey: this.getEffectiveApiKey(apiKey),\n      baseURL: this.baseUrl,\n      defaultHeaders: { 'User-Agent': getQuilltapUserAgent() },\n      timeout: this.requestTimeoutMs,\n      maxRetries: this.maxRetries,\n    });\n  }\n\n  /**\n   * Builds the per-request options for a single call.\n   *\n   * When the caller supplied `params.requestTimeoutMs` it is a hard ceiling:\n   * the request uses that budget and does not retry, so the total time spent is\n   * what the caller asked for rather than a multiple of it. Otherwise the\n   * client-level defaults apply.\n   *\n   * Subclasses that build their own request bodies should pass the result as\n   * the second argument to `client.chat.completions.create(...)`.\n   *\n   * @param params - LLM parameters for the request\n   * @returns Request options for the OpenAI SDK, or undefined to use defaults\n   */\n  protected buildRequestOptions(\n    params: LLMParams\n  ): { timeout: number; maxRetries: number } | undefined {\n    return buildSdkRequestOptions(params);\n  }\n\n  /**\n   * Profile-parameter keys this provider forwards from\n   * `LLMParams.profileParameters` onto the request body.\n   *\n   * **Empty by default**, so a subclass that declares nothing sends exactly the\n   * bytes it sent before this mechanism existed. Subclasses opt in by\n   * overriding with their own allow-list; `model`, `messages`, `stream`,\n   * `stream_options` and `tools` must never appear in one.\n   */\n  protected readonly profileParamAllowlist: readonly string[] = [];\n\n  /**\n   * Per-key hook for allow-listed profile parameters. The default forwards the\n   * stored value verbatim.\n   *\n   * Override to reshape a value (the editor stores flat strings where some APIs\n   * want objects), to gate a key on the model, or — by writing into `body` and\n   * returning `undefined` — to send it under a different key entirely.\n   */\n  protected normalizeProfileParam(\n    _key: string,\n    value: unknown,\n    _params: LLMParams,\n    _body: Record<string, unknown>\n  ): unknown | undefined {\n    return value;\n  }\n\n  /**\n   * Apply {@link profileParamAllowlist} to a request body under construction.\n   * Called by both body builds so the streaming and non-streaming shapes cannot\n   * drift apart.\n   */\n  protected applyProfileParams(body: Record<string, unknown>, params: LLMParams): void {\n    applyProfileParameters(\n      body,\n      params,\n      this.profileParamAllowlist,\n      (key, value, callParams, target) =>\n        this.normalizeProfileParam(key, value, callParams, target)\n    );\n  }\n\n  /**\n   * Normalize an OpenAI-shaped `tool_calls` array into Quilltap's `ToolCall`\n   * shape, tolerating servers that hand back already-parsed argument objects\n   * where the OpenAI wire format specifies a JSON string.\n   */\n  protected normalizeToolCalls(\n    toolCalls: readonly unknown[] | undefined | null\n  ): { id: string; type: 'function'; function: { name: string; arguments: string } }[] {\n    if (!toolCalls || toolCalls.length === 0) return [];\n    const out: { id: string; type: 'function'; function: { name: string; arguments: string } }[] = [];\n    for (const raw of toolCalls) {\n      const tc = raw as {\n        id?: string;\n        function?: { name?: string; arguments?: unknown };\n      };\n      if (!tc?.function?.name) continue;\n      const args = tc.function.arguments;\n      out.push({\n        id: tc.id ?? '',\n        type: 'function',\n        function: {\n          name: tc.function.name,\n          arguments: typeof args === 'string' ? args : JSON.stringify(args ?? {}),\n        },\n      });\n    }\n    return out;\n  }\n\n  /**\n   * Build the Chat Completions request body — the ONE build both\n   * {@link sendMessage} and {@link streamMessage} call, so the streaming and\n   * non-streaming shapes cannot drift apart. Maps Quilltap messages to the\n   * OpenAI wire format (tool results and assistant tool calls included),\n   * applies {@link applySystemMessagePolicy}, assembles the body literal, and\n   * finishes with {@link applyProfileParams}.\n   *\n   * Subclasses that keep the base class's send/stream loops can override this\n   * to reshape the body in one place.\n   *\n   * @param params - LLM parameters for the request\n   * @param stream - Whether the body is for a streaming request; adds\n   *   `stream: true` and `stream_options: { include_usage: true }`\n   */\n  protected buildRequestBody(params: LLMParams, stream: boolean): Record<string, unknown> {\n    // Map messages to OpenAI Chat Completions format, including tool messages\n    const mappedMessages = params.messages\n      .filter((m) => {\n        // Skip tool messages without toolCallId (backward compat)\n        if (m.role === 'tool' && !m.toolCallId) return false;\n        return true;\n      })\n      .map((m) => {\n        // Tool result messages\n        if (m.role === 'tool' && m.toolCallId) {\n          return {\n            role: 'tool' as const,\n            tool_call_id: m.toolCallId as string,\n            content: m.content,\n          };\n        }\n        // Assistant messages with tool calls\n        if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {\n          return {\n            role: 'assistant' as const,\n            content: m.content || null,\n            tool_calls: m.toolCalls.map((tc: any) => ({\n              id: tc.id,\n              type: tc.type,\n              function: tc.function,\n            })),\n          };\n        }\n        // Standard messages (strip attachments)\n        return {\n          role: m.role as 'system' | 'user' | 'assistant',\n          content: m.content,\n        };\n      });\n\n    // Endpoints whose chat template insists the system message be first and\n    // singular get the leading run folded into one (Bug 82); everyone else is\n    // handed the array untouched.\n    const messages = this.applySystemMessagePolicy(mappedMessages);\n\n    // Built as a Record so the allow-list can write non-standard keys the\n    // OpenAI SDK's own types don't know about (`chat_template_kwargs`, `top_k`,\n    // …). Widening happens once, here, rather than as `as any` at each write.\n    const body: Record<string, unknown> = {\n      model: params.model,\n      messages,\n      temperature: params.temperature ?? 0.7,\n      max_tokens: params.maxTokens ?? 4096,\n      top_p: params.topP ?? 1,\n      stop: params.stop,\n      // Spread here — not appended after the literal — so the streaming keys\n      // keep their historical position between `stop` and `user` and the\n      // serialized body stays byte-identical.\n      ...(stream ? { stream: true, stream_options: { include_usage: true } } : {}),\n      ...(params.cacheKey ? { user: params.cacheKey } : {}),\n      // Tools arrive only once the runtime gate (the profile's \"Allow tool\n      // use\") has passed, so no separate guard is needed here.\n      ...(params.tools && params.tools.length > 0\n        ? { tools: params.tools, tool_choice: params.toolChoice ?? 'auto' }\n        : {}),\n    };\n    this.applyProfileParams(body, params);\n    return body;\n  }\n\n  /**\n   * Normalizes a Chat Completions `usage` block into Quilltap's shape.\n   *\n   * OpenAI-compatible endpoints fold prompt-cache hits into `prompt_tokens`\n   * (reported separately as `prompt_tokens_details.cached_tokens`). Quilltap's\n   * convention is that `promptTokens` / `totalTokens` EXCLUDE cache reads, so\n   * cached input never counts against token budgets; `cacheUsage` still\n   * carries the cache reads for display. Endpoints that report no\n   * `cached_tokens` are unaffected.\n   */\n  protected normalizeUsage(\n    usage: OpenAI.Completions.CompletionUsage | null | undefined\n  ): Pick<LLMResponse, 'usage' | 'cacheUsage'> {\n    const cachedTokens = usage?.prompt_tokens_details?.cached_tokens ?? 0;\n    if (cachedTokens > 0) {\n      this.logger.debug(`${this.providerName} usage: excluding prompt-cache hits from prompt/total`, {\n        context: `${this.providerName}Provider.normalizeUsage`,\n        promptTokens: usage?.prompt_tokens ?? 0,\n        cachedTokens,\n      });\n    }\n    return {\n      usage: {\n        promptTokens: Math.max(0, (usage?.prompt_tokens ?? 0) - cachedTokens),\n        completionTokens: usage?.completion_tokens ?? 0,\n        totalTokens: Math.max(0, (usage?.total_tokens ?? 0) - cachedTokens),\n      },\n      ...(cachedTokens > 0\n        ? { cacheUsage: { cacheReadInputTokens: cachedTokens, cachedTokens } }\n        : {}),\n    };\n  }\n\n  /**\n   * Sends a message and returns the complete response.\n   *\n   * @param params - LLM parameters including messages, model, and settings\n   * @param apiKey - API key for authentication\n   * @returns Complete LLM response with content and usage statistics\n   */\n  async sendMessage(params: LLMParams, apiKey: string): Promise<LLMResponse> {\n    this.validateApiKeyRequirement(apiKey);\n    const attachmentResults = this.collectAttachmentFailures(params);\n\n    const client = this.createClient(apiKey);\n    const body = this.buildRequestBody(params, false);\n\n    try {\n      const response = (await client.chat.completions.create(\n        body as unknown as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming,\n        this.buildRequestOptions(params)\n      )) as OpenAI.Chat.Completions.ChatCompletion;\n\n      const choice = response.choices[0];\n      const toolCalls = this.normalizeToolCalls(choice.message.tool_calls);\n      return {\n        content: choice.message.content ?? '',\n        finishReason: choice.finish_reason,\n        ...this.normalizeUsage(response.usage),\n        raw: response,\n        attachmentResults,\n        ...(toolCalls.length > 0 ? { toolCalls } : {}),\n      };\n    } catch (error) {\n      this.logger.error(\n        `${this.providerName} API error in sendMessage`,\n        { context: `${this.providerName}Provider.sendMessage`, baseUrl: this.baseUrl },\n        error instanceof Error ? error : undefined\n      );\n      throw error;\n    }\n  }\n\n  /**\n   * Sends a message and streams the response.\n   *\n   * @param params - LLM parameters including messages, model, and settings\n   * @param apiKey - API key for authentication\n   * @yields Stream chunks with content and final usage statistics\n   */\n  async *streamMessage(params: LLMParams, apiKey: string): AsyncGenerator<StreamChunk> {\n    this.validateApiKeyRequirement(apiKey);\n    const attachmentResults = this.collectAttachmentFailures(params);\n\n    const client = this.createClient(apiKey);\n    const body = this.buildRequestBody(params, true);\n\n    try {\n      const stream = (await client.chat.completions.create(\n        body as unknown as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming,\n        this.buildRequestOptions(params)\n      )) as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>;\n\n      let chunkCount = 0;\n\n      // Track usage - it may come in a separate final chunk\n      let accumulatedUsage: OpenAI.Completions.CompletionUsage | null = null;\n      // Tool-call fragments arrive index-keyed, with `arguments` split across\n      // deltas as raw string pieces; accumulate per index and assemble at the\n      // end rather than trying to parse mid-stream.\n      const toolCallAccumulator = new Map<number, { id: string; name: string; arguments: string }>();\n      let finishReason: string | null = null;\n\n      for await (const chunk of stream) {\n        chunkCount++;\n        const choice = chunk.choices[0];\n        const content = choice?.delta?.content;\n        const hasUsage = chunk.usage;\n\n        // Track usage when we get it (may come in a separate final chunk)\n        if (hasUsage) {\n          accumulatedUsage = chunk.usage ?? null;\n        }\n\n        if (choice?.delta?.tool_calls) {\n          for (const tcDelta of choice.delta.tool_calls) {\n            const idx = tcDelta.index ?? 0;\n            const existing = toolCallAccumulator.get(idx) ?? { id: '', name: '', arguments: '' };\n            if (tcDelta.id) existing.id = tcDelta.id;\n            if (tcDelta.function?.name) existing.name = tcDelta.function.name;\n            if (tcDelta.function?.arguments) existing.arguments += tcDelta.function.arguments;\n            toolCallAccumulator.set(idx, existing);\n          }\n        }\n\n        if (choice?.finish_reason) finishReason = choice.finish_reason;\n\n        // Yield content chunks\n        if (content) {\n          yield {\n            content,\n            done: false,\n          };\n        }\n      }\n\n      const toolCalls = Array.from(toolCallAccumulator.values())\n        .filter((tc) => tc.name)\n        .map((tc) => ({\n          id: tc.id,\n          type: 'function' as const,\n          function: { name: tc.name, arguments: tc.arguments },\n        }));\n\n      // After stream ends, yield final chunk with accumulated usage\n      yield {\n        content: '',\n        done: true,\n        ...(accumulatedUsage\n          ? {\n              ...this.normalizeUsage(accumulatedUsage),\n              rawProviderUsage: accumulatedUsage as unknown as Record<string, unknown>,\n            }\n          : {}),\n        attachmentResults,\n        // Only synthesized when the stream actually carried tool calls: the\n        // host's tool detection reads `rawResponse`, and an empty one on every\n        // ordinary turn would be noise in the logs for no gain.\n        ...(toolCalls.length > 0\n          ? {\n              toolCalls,\n              rawResponse: {\n                choices: [\n                  {\n                    index: 0,\n                    message: { role: 'assistant', content: '', tool_calls: toolCalls },\n                    finish_reason: finishReason,\n                  },\n                ],\n              },\n            }\n          : {}),\n      };\n    } catch (error) {\n      this.logger.error(\n        `${this.providerName} API error in streamMessage`,\n        { context: `${this.providerName}Provider.streamMessage`, baseUrl: this.baseUrl },\n        error instanceof Error ? error : undefined\n      );\n      throw error;\n    }\n  }\n\n  /**\n   * Validates an API key by attempting to list models.\n   *\n   * @param apiKey - API key to validate\n   * @returns true if the API key is valid, false otherwise\n   */\n  async validateApiKey(apiKey: string): Promise<boolean> {\n    // For providers that require API key, return false if not provided\n    if (this.requireApiKey && !apiKey) {\n      return false;\n    }\n\n    try {\n      const client = this.createClient(apiKey);\n      await client.models.list();\n      return true;\n    } catch (error) {\n      this.logger.error(\n        `${this.providerName} API validation failed`,\n        { context: `${this.providerName}Provider.validateApiKey`, baseUrl: this.baseUrl },\n        error instanceof Error ? error : undefined\n      );\n      return false;\n    }\n  }\n\n  /**\n   * Fetches available models from the API.\n   *\n   * @param apiKey - API key for authentication\n   * @returns Sorted array of model IDs, or empty array on failure\n   */\n  async getAvailableModels(apiKey: string): Promise<string[]> {\n    // For providers that require API key, return empty if not provided\n    if (this.requireApiKey && !apiKey) {\n      this.logger.error(`${this.providerName} provider requires an API key to fetch models`, {\n        context: `${this.providerName}Provider.getAvailableModels`,\n      });\n      return [];\n    }\n\n    try {\n      const client = this.createClient(apiKey);\n      const models = await client.models.list();\n      const modelList = models.data.map((m) => m.id).sort();\n      return modelList;\n    } catch (error) {\n      this.logger.error(\n        `Failed to fetch ${this.providerName} models`,\n        { context: `${this.providerName}Provider.getAvailableModels`, baseUrl: this.baseUrl },\n        error instanceof Error ? error : undefined\n      );\n      return [];\n    }\n  }\n\n}\n","/**\n * Quilltap Version Management\n *\n * Provides access to the Quilltap application version for plugins.\n * Uses the same globalThis injection pattern as the logger factory,\n * allowing the host app to inject its version at startup.\n *\n * @module @quilltap/plugin-utils/version\n */\n\n/**\n * Global key used to store the injected Quilltap version.\n * Must match the key used by the host app's injection code.\n */\nconst GLOBAL_VERSION_KEY = '__quilltap_app_version';\n\n/**\n * Inject the Quilltap application version into the global namespace.\n *\n * This should be called early in plugin initialization (alongside the\n * logger factory injection), before any plugins are loaded. Plugins\n * can then retrieve the version via `getQuilltapVersion()`.\n *\n * @param version - The Quilltap application version string (e.g., '3.3.0')\n *\n * @example\n * ```typescript\n * import { __injectQuilltapVersion } from '@quilltap/plugin-utils';\n * import packageJson from './package.json';\n *\n * __injectQuilltapVersion(packageJson.version);\n * ```\n */\nexport function __injectQuilltapVersion(version: string): void {\n  (globalThis as Record<string, unknown>)[GLOBAL_VERSION_KEY] = version;\n}\n\n/**\n * Clear the injected Quilltap version from the global namespace.\n *\n * Useful for testing or cleanup.\n */\nexport function __clearQuilltapVersion(): void {\n  (globalThis as Record<string, unknown>)[GLOBAL_VERSION_KEY] = undefined;\n}\n\n/**\n * Get the Quilltap application version.\n *\n * Returns the version injected by the host app, or 'unknown' if\n * no version has been injected (e.g., running outside of Quilltap).\n *\n * @returns The Quilltap version string (e.g., '3.3.0') or 'unknown'\n */\nexport function getQuilltapVersion(): string {\n  const version = (globalThis as Record<string, unknown>)[GLOBAL_VERSION_KEY];\n  return typeof version === 'string' ? version : 'unknown';\n}\n\n/**\n * Get the Quilltap User-Agent string for API requests.\n *\n * Returns a string in the format `Quilltap/{version}` suitable for\n * use as a User-Agent header or app identifier in API calls.\n *\n * @returns User-Agent string (e.g., 'Quilltap/3.3.0')\n *\n * @example\n * ```typescript\n * import { getQuilltapUserAgent } from '@quilltap/plugin-utils';\n *\n * const client = new OpenAI({\n *   apiKey,\n *   defaultHeaders: { 'User-Agent': getQuilltapUserAgent() },\n * });\n * ```\n */\nexport function getQuilltapUserAgent(): string {\n  return `Quilltap/${getQuilltapVersion()}`;\n}\n","/**\n * Per-request wall-clock budgets.\n *\n * Every provider SDK ships a generous default request timeout — the OpenAI and\n * Anthropic SDKs both use ten minutes — on the assumption that a slow answer is\n * better than no answer. That assumption is wrong for work Quilltap awaits\n * inline on a user-visible turn: a service that accepts the connection and then\n * goes silent holds the turn for the full ten minutes before the SDK's first\n * retry, with nothing logged, because request logs are only written when a call\n * finishes.\n *\n * `LLMParams.requestTimeoutMs` is the caller's answer to \"how long is this\n * worth waiting for\". These helpers are the single place that decides what that\n * number means, so an SDK-backed provider and a `fetch`-based one apply it the\n * same way.\n *\n * @packageDocumentation\n */\n\nimport type { LLMParams } from '@quilltap/plugin-types';\n\n/**\n * Fallback budget for a request whose caller expressed no preference.\n *\n * Half the SDK default: long enough for a slow non-streaming generation, short\n * enough that a silently-stalled endpoint fails in minutes rather than tens of\n * minutes.\n */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 300_000;\n\n/**\n * The budget for one request, in milliseconds.\n *\n * @param params - LLM parameters, whose `requestTimeoutMs` wins when set\n * @param defaultMs - Budget to use when the caller named none\n * @returns The effective budget in milliseconds\n */\nexport function resolveRequestTimeoutMs(\n  params: Pick<LLMParams, 'requestTimeoutMs'>,\n  defaultMs: number = DEFAULT_REQUEST_TIMEOUT_MS\n): number {\n  const requested = params.requestTimeoutMs;\n  return typeof requested === 'number' && requested > 0 ? requested : defaultMs;\n}\n\n/**\n * Per-request options for an OpenAI- or Anthropic-style SDK call.\n *\n * A caller-supplied budget is a *ceiling*, not a per-attempt allowance, so\n * retries are disabled for it — three attempts at the requested budget would\n * spend three times what the caller agreed to. Returns undefined when the\n * caller named no budget, leaving the client's own defaults in charge.\n *\n * @param params - LLM parameters for the request\n * @returns SDK request options, or undefined to use the client defaults\n */\nexport function buildSdkRequestOptions(\n  params: Pick<LLMParams, 'requestTimeoutMs'>\n): { timeout: number; maxRetries: number } | undefined {\n  const requested = params.requestTimeoutMs;\n  if (typeof requested !== 'number' || requested <= 0) return undefined;\n  return { timeout: requested, maxRetries: 0 };\n}\n\n/**\n * Client-construction options for a provider that builds a fresh SDK client\n * per call, where \"client default\" and \"this request\" are the same thing.\n *\n * Always defined, unlike {@link buildSdkRequestOptions} — a per-call client has\n * no pre-existing defaults worth preserving, so it takes the resolved budget\n * either way and only forgoes retries when the caller set a ceiling.\n *\n * @param params - LLM parameters for the request\n * @param defaultMs - Budget to use when the caller named none\n * @returns `timeout` and `maxRetries` to spread into the SDK constructor\n */\nexport function buildSdkClientOptions(\n  params: Pick<LLMParams, 'requestTimeoutMs'>,\n  defaultMs: number = DEFAULT_REQUEST_TIMEOUT_MS\n): { timeout: number; maxRetries: number } {\n  const capped = typeof params.requestTimeoutMs === 'number' && params.requestTimeoutMs > 0;\n  return {\n    timeout: resolveRequestTimeoutMs(params, defaultMs),\n    maxRetries: capped ? 0 : 2,\n  };\n}\n\n/**\n * An abort signal that fires at the request's budget, for providers that call\n * `fetch` directly rather than through an SDK.\n *\n * Unlike the SDK options above this bounds the *whole* exchange, body included:\n * `fetch` resolves at the response headers, but the signal stays live while the\n * body streams. Pass a generous `defaultMs` on streaming paths, or compose with\n * the caller's own signal, so a long generation is not cut off mid-answer.\n *\n * @param params - LLM parameters for the request\n * @param defaultMs - Budget to use when the caller named none\n * @returns An AbortSignal that aborts once the budget elapses\n */\nexport function buildRequestAbortSignal(\n  params: Pick<LLMParams, 'requestTimeoutMs'>,\n  defaultMs: number = DEFAULT_REQUEST_TIMEOUT_MS\n): AbortSignal {\n  return AbortSignal.timeout(resolveRequestTimeoutMs(params, defaultMs));\n}\n","/**\n * Profile-parameter passthrough\n *\n * A connection profile's `parameters` blob is free-form JSON: the host stores\n * whatever the profile editor (or a hand-edited profile) puts there and hands\n * it back to the provider as `LLMParams.profileParameters` at call time. What\n * reaches the wire is entirely the provider's business — and a provider that\n * never reads the bag drops every setting in silence, which is exactly the\n * defect Bug 71 records.\n *\n * This is the one mechanism every provider uses to forward those keys.\n *\n * **An exported function rather than a base-class method.** Only one plugin in\n * the repository extends `OpenAICompatibleProvider` (DeepSeek); Z.AI and\n * OpenRouter implement `TextProvider` directly, and Ollama speaks its own\n * REST shape. A `protected` method would have reached one of them, so the\n * mechanism is a free function usable by inheritance *and* composition, and\n * the base class simply calls it.\n *\n * @packageDocumentation\n */\n\nimport type { LLMParams } from '@quilltap/plugin-types';\n\n/**\n * Per-key hook applied to an allow-listed profile parameter on its way into\n * the request body.\n *\n * Return the value to send, or `undefined` to omit the key entirely.\n *\n * The `body` is passed so a normalizer can place a value somewhere other than\n * its own top-level key — the OpenAI-compatible plugin folds `reasoning_effort`\n * into `chat_template_kwargs` this way, then returns `undefined` so no flat\n * `reasoning_effort` key is sent. Normalizers that only reshape a value can\n * ignore the argument.\n */\nexport type ProfileParamNormalizer = (\n  key: string,\n  value: unknown,\n  params: LLMParams,\n  body: Record<string, unknown>\n) => unknown | undefined;\n\n/**\n * Copy allow-listed keys from `params.profileParameters` onto a request body.\n *\n * Rules, all of which predate this helper (they were hand-rolled identically in\n * the DeepSeek and Z.AI providers before it existed):\n *\n * - **Allow-list, never spread.** `model`, `messages`, `stream`,\n *   `stream_options` and `tools` must be unreachable from a profile — a\n *   misconfigured bag cannot be allowed to retarget the request.\n * - **`undefined` and `null` skip.** So does the empty string, which is what\n *   the schema-driven profile editor stores for \"omit this and use the model\n *   default\".\n * - **The allow-list is ordered.** Keys are applied in the order given, so a\n *   normalizer that merges into an earlier key (`chat_template_kwargs`) sees\n *   whatever the profile set for it.\n *\n * @param body - Request body under construction; mutated in place\n * @param params - The call's LLM parameters, carrying `profileParameters`\n * @param allowlist - Keys this provider is willing to forward\n * @param normalize - Optional per-key hook (see {@link ProfileParamNormalizer})\n */\nexport function applyProfileParameters(\n  body: Record<string, unknown>,\n  params: LLMParams,\n  allowlist: readonly string[],\n  normalize?: ProfileParamNormalizer\n): void {\n  const profile = params.profileParameters;\n  if (!profile || typeof profile !== 'object') return;\n  const bag = profile as Record<string, unknown>;\n\n  for (const key of allowlist) {\n    const raw = bag[key];\n    if (raw === undefined || raw === null) continue;\n    // Empty string from the schema-driven profile editor means \"omit the\n    // parameter and use the model default.\"\n    if (typeof raw === 'string' && raw === '') continue;\n    const value = normalize ? normalize(key, raw, params, body) : raw;\n    if (value === undefined) continue;\n    body[key] = value;\n  }\n}\n","/**\n * Leading-system-message collapse.\n *\n * Quilltap's context builder deliberately emits the head of a turn as up to\n * three consecutive `system` messages — the cacheable persona prefix, the\n * static identity reinforcement, and the compressed-history summary — so that a\n * cache breakpoint on the first is not invalidated by churn in the others.\n * Hosted providers accept that happily.\n *\n * A local runtime does not answer for itself: it applies the *model's own* chat\n * template, and several families (Qwen most notably, plus Llama- and\n * Gemma-derived templates) `raise_exception` on any system message after index\n * 0. The whole request is then rejected before a token is generated, so the\n * opening greeting — one system message — works and every turn after it dies.\n *\n * The repair belongs here, at request-build time, rather than in the context\n * assembly: the blocks stay separate for every provider that wants them\n * separate, and the providers that cannot take them fold the run on the way\n * out. See Bug 82.\n *\n * @packageDocumentation\n */\n\n/** The shape this helper needs; wire messages carry more, and keep it. */\ninterface RoleAndContent {\n  role: string;\n  content?: string | null;\n}\n\n/**\n * Fold a run of consecutive leading `system` messages into a single one,\n * joining their contents with a blank line.\n *\n * Only the *leading* run is touched — a system message that appears later is\n * left where it is, because moving or merging it would change what the model is\n * told and when. Arrays with fewer than two leading system messages are\n * returned unchanged (the same array reference), so a provider that opts in\n * still sends byte-identical requests everywhere the problem does not arise.\n *\n * @param messages - Wire-format messages, already mapped for the endpoint\n * @returns The messages with their leading system run collapsed\n */\nexport function collapseLeadingSystemMessages<T extends RoleAndContent>(messages: T[]): T[] {\n  let runLength = 0;\n  while (runLength < messages.length && messages[runLength].role === 'system') {\n    runLength++;\n  }\n\n  if (runLength < 2) return messages;\n\n  const run = messages.slice(0, runLength);\n  const merged: T = {\n    ...run[0],\n    content: run\n      .map((m) => m.content ?? '')\n      .filter((c) => c.length > 0)\n      .join('\\n\\n'),\n  };\n\n  return [merged, ...messages.slice(runLength)];\n}\n","/**\n * System Prompt Plugin Builder utilities\n *\n * Provides helper functions for creating and validating system prompt plugins.\n *\n * @module @quilltap/plugin-utils/system-prompts\n */\n\nimport type {\n  SystemPromptData,\n  SystemPromptMetadata,\n  SystemPromptPlugin,\n} from '@quilltap/plugin-types';\n\n// ============================================================================\n// BUILDER OPTIONS\n// ============================================================================\n\n/**\n * Options for creating a system prompt plugin\n */\nexport interface CreateSystemPromptPluginOptions {\n  /** Plugin metadata */\n  metadata: SystemPromptMetadata;\n\n  /**\n   * One or more system prompts.\n   * Each prompt must have a unique name within the plugin.\n   */\n  prompts: SystemPromptData[];\n\n  /**\n   * Optional initialization function.\n   * Called when the plugin is loaded.\n   */\n  initialize?: () => void | Promise<void>;\n}\n\n// ============================================================================\n// BUILDER FUNCTIONS\n// ============================================================================\n\n/**\n * Creates a system prompt plugin.\n *\n * @param options - Plugin configuration options\n * @returns A valid SystemPromptPlugin instance\n *\n * @example\n * ```typescript\n * import { createSystemPromptPlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createSystemPromptPlugin({\n *   metadata: {\n *     pluginId: 'my-prompts',\n *     displayName: 'My Prompt Collection',\n *     description: 'Custom system prompts for various models',\n *   },\n *   prompts: [\n *     {\n *       name: 'CLAUDE_CREATIVE',\n *       content: '# Creative Writing Prompt\\n\\nYou are {{char}}...',\n *       modelHint: 'CLAUDE',\n *       category: 'CREATIVE',\n *     },\n *   ],\n * });\n * ```\n */\nexport function createSystemPromptPlugin(\n  options: CreateSystemPromptPluginOptions\n): SystemPromptPlugin {\n  const { metadata, prompts, initialize } = options;\n\n  // Validate prompts\n  if (prompts.length === 0) {\n    throw new Error('At least one system prompt is required');\n  }\n\n  // Check for duplicate names\n  const names = new Set<string>();\n  for (const prompt of prompts) {\n    if (!prompt.name || prompt.name.trim() === '') {\n      throw new Error('Prompt name is required');\n    }\n    if (!prompt.content || prompt.content.trim() === '') {\n      throw new Error(`Prompt \"${prompt.name}\" requires content`);\n    }\n    if (!prompt.modelHint || prompt.modelHint.trim() === '') {\n      throw new Error(`Prompt \"${prompt.name}\" requires a modelHint`);\n    }\n    if (!prompt.category || prompt.category.trim() === '') {\n      throw new Error(`Prompt \"${prompt.name}\" requires a category`);\n    }\n    if (names.has(prompt.name)) {\n      throw new Error(`Duplicate prompt name: \"${prompt.name}\"`);\n    }\n    names.add(prompt.name);\n  }\n\n  // Create the plugin\n  const plugin: SystemPromptPlugin = {\n    metadata,\n    prompts,\n  };\n\n  // Add initialize function if provided\n  if (initialize) {\n    plugin.initialize = async () => {\n      await initialize();\n    };\n  }\n\n  return plugin;\n}\n\n// ============================================================================\n// VALIDATION UTILITIES\n// ============================================================================\n\n/**\n * Validates a complete system prompt plugin\n *\n * @param plugin - The plugin to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateSystemPromptPlugin(plugin: SystemPromptPlugin): boolean {\n  // Validate metadata\n  if (!plugin.metadata) {\n    throw new Error('Plugin metadata is required');\n  }\n\n  if (!plugin.metadata.pluginId || plugin.metadata.pluginId.trim() === '') {\n    throw new Error('Plugin metadata.pluginId is required');\n  }\n\n  if (!/^[a-z0-9-]+$/.test(plugin.metadata.pluginId)) {\n    throw new Error('Plugin pluginId must be lowercase alphanumeric with hyphens only');\n  }\n\n  if (!plugin.metadata.displayName || plugin.metadata.displayName.trim() === '') {\n    throw new Error('Plugin metadata.displayName is required');\n  }\n\n  // Validate prompts\n  if (!plugin.prompts || !Array.isArray(plugin.prompts) || plugin.prompts.length === 0) {\n    throw new Error('Plugin must have at least one prompt');\n  }\n\n  const names = new Set<string>();\n  for (const prompt of plugin.prompts) {\n    if (!prompt.name || prompt.name.trim() === '') {\n      throw new Error('Prompt name is required');\n    }\n    if (!prompt.content || prompt.content.trim() === '') {\n      throw new Error(`Prompt \"${prompt.name}\" requires content`);\n    }\n    if (!prompt.modelHint || prompt.modelHint.trim() === '') {\n      throw new Error(`Prompt \"${prompt.name}\" requires a modelHint`);\n    }\n    if (!prompt.category || prompt.category.trim() === '') {\n      throw new Error(`Prompt \"${prompt.name}\" requires a category`);\n    }\n    if (names.has(prompt.name)) {\n      throw new Error(`Duplicate prompt name: \"${prompt.name}\"`);\n    }\n    names.add(prompt.name);\n  }\n\n  return true;\n}\n","/**\n * Built-in tool names for Quilltap\n *\n * These are the tool names used by Quilltap's built-in tools.\n * Plugins that provide dynamic tools (like MCP connectors) should use\n * this list for collision detection to avoid shadowing built-in functionality.\n *\n * @module @quilltap/plugin-utils/builtin-tools\n */\n\n/**\n * Names of all built-in Quilltap tools\n *\n * This set contains the function names of tools that are built into Quilltap:\n * - `generate_image` - AI image generation\n * - `search` - Search the Scriptorium (memories, conversations, backgrounds)\n * - `search_web` - Web search (when enabled)\n * - `project_info` - Get project metadata\n * - `request_full_context` - Request full context reload (context compression)\n * - `help_search` - Search Quilltap help documentation\n * - `help_settings` - Read Quilltap instance settings\n */\nexport const BUILTIN_TOOL_NAMES = new Set([\n  'generate_image',\n  'search',\n  'search_web',\n  'project_info',\n  'request_full_context',\n  'help_search',\n  'help_settings',\n  // Document editing tools (Scriptorium Phase 3.3)\n  'doc_read_file',\n  'doc_write_file',\n  'doc_str_replace',\n  'doc_insert_text',\n  'doc_grep',\n  'doc_list_files',\n  'doc_read_frontmatter',\n  'doc_update_frontmatter',\n  'doc_read_heading',\n  'doc_update_heading',\n  // Document file management tools (Scriptorium Phase 3.4)\n  'doc_move_file',\n  'doc_copy_file',\n  'doc_delete_file',\n  'doc_create_folder',\n  'doc_delete_folder',\n]);\n\n/**\n * Get the set of built-in tool names\n *\n * Use this function to get the current list of reserved tool names\n * when implementing collision detection in plugins that provide\n * dynamic tools (e.g., MCP server connectors, external tool bridges).\n *\n * @returns Set of tool names that are reserved by Quilltap's built-in tools\n *\n * @example\n * ```typescript\n * import { getBuiltinToolNames } from '@quilltap/plugin-utils';\n *\n * // When generating tool names from external sources:\n * const builtinNames = getBuiltinToolNames();\n * if (builtinNames.has(proposedToolName)) {\n *   // Rename or prefix the tool to avoid collision\n *   proposedToolName = `external_${proposedToolName}`;\n * }\n * ```\n */\nexport function getBuiltinToolNames(): Set<string> {\n  return new Set(BUILTIN_TOOL_NAMES);\n}\n","/**\n * Host URL Rewriting for Container Environments\n *\n * When Quilltap runs inside Docker — or inside a virtual machine the user has\n * built and manages themselves — `localhost` and `127.0.0.1` resolve to the\n * container/VM's own loopback, not the host machine where services like Ollama\n * or LM Studio are running.\n *\n * This module provides a single function that transparently rewrites\n * localhost URLs to point at the host, so users can configure\n * `http://localhost:11434` and have it Just Work in every environment.\n *\n * Gateway resolution order:\n * 1. `QUILLTAP_HOST_IP` env var (explicit override) → rewrite to that IP.\n *    This is also the only supported route for a self-managed VM, where\n *    Quilltap has no reliable way to detect the host gateway on its own.\n * 2. In Docker: rewrite `localhost` → `host.docker.internal`\n *    (Docker Desktop DNS or --add-host on Linux handles the forwarding)\n * 3. Give up gracefully — return URL unchanged\n *\n * @module @quilltap/plugin-utils/host-rewrite\n */\n\nimport { createPluginLogger } from './logging';\nimport { existsSync, statSync } from 'node:fs';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Hostnames that refer to the local loopback */\nconst LOCALHOST_HOSTS = new Set([\n  'localhost',\n  '127.0.0.1',\n  '[::1]',\n  '::1',\n]);\n\n// ============================================================================\n// Cached Gateway Host\n// ============================================================================\n\nlet cachedGatewayHost: string | null | undefined; // undefined = not yet resolved\n\nconst rewriteLogger = createPluginLogger('host-rewrite');\n\n// ============================================================================\n// Environment Detection (self-contained)\n// ============================================================================\n\n/**\n * Check if running in a Docker container.\n *\n * Detects Docker by checking:\n * 1. DOCKER_CONTAINER environment variable\n * 2. Existence of /.dockerenv file\n * 3. Existence of /app directory (Quilltap Docker convention)\n */\nfunction isDockerEnvironment(): boolean {\n  if (process.env.DOCKER_CONTAINER === 'true') {\n    return true;\n  }\n\n  // Check for Docker-specific markers\n  try {\n    if (existsSync('/.dockerenv')) {\n      return true;\n    }\n    // Check for /app as a directory (Quilltap Docker convention)\n    const appStat = statSync('/app');\n    if (appStat.isDirectory()) {\n      return true;\n    }\n  } catch {\n    // Not in Docker\n  }\n\n  return false;\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Check if running in an environment that needs URL rewriting.\n *\n * Docker is detected automatically. A hand-rolled VM cannot be, so setting\n * `QUILLTAP_HOST_IP` is what opts one in — it both enables rewriting and\n * supplies the gateway address.\n */\nexport function isVMEnvironment(): boolean {\n  return isDockerEnvironment() || !!process.env.QUILLTAP_HOST_IP;\n}\n\n/**\n * Resolve the host gateway address (IP or hostname).\n *\n * Tries multiple strategies in order; caches the result so file reads\n * only happen once per process lifetime.\n */\nexport function resolveHostGateway(): string | null {\n  // Return cached result if already resolved\n  if (cachedGatewayHost !== undefined) {\n    return cachedGatewayHost;\n  }\n\n  // Strategy 1: Explicit env var override\n  const envIP = process.env.QUILLTAP_HOST_IP;\n  if (envIP) {\n    rewriteLogger.info('Host gateway from QUILLTAP_HOST_IP', { host: envIP });\n    cachedGatewayHost = envIP;\n    return cachedGatewayHost;\n  }\n\n  // Strategy 2: Docker — use host.docker.internal directly as a hostname\n  // Docker Desktop (macOS/Windows) provides built-in DNS resolution for\n  // host.docker.internal via its DNS server (127.0.0.11).  Linux Docker\n  // needs --add-host=host.docker.internal:host-gateway (handled by the\n  // start scripts).  Either way, host.docker.internal correctly forwards\n  // to services bound to the host's loopback (127.0.0.1).\n  //\n  // The bridge gateway IP (e.g. 172.17.0.1) is deliberately not used as a\n  // fallback: it is only the Docker bridge interface, and services listening\n  // on the host's localhost are NOT reachable through it.\n  if (isDockerEnvironment()) {\n    rewriteLogger.info('Docker environment detected — using host.docker.internal as gateway hostname');\n    cachedGatewayHost = 'host.docker.internal';\n    return cachedGatewayHost;\n  }\n\n  rewriteLogger.warn('Could not resolve host gateway — localhost URLs will not be rewritten');\n  cachedGatewayHost = null;\n  return cachedGatewayHost;\n}\n\n/**\n * Rewrite a localhost URL to point at the host gateway.\n *\n * No-ops when:\n * - Not running in a container (and QUILLTAP_HOST_IP is unset)\n * - The URL doesn't point to localhost or 127.0.0.1\n * - Gateway resolution fails\n *\n * @param url The URL to potentially rewrite\n * @returns The original URL or a rewritten version with the gateway host\n */\nexport function rewriteLocalhostUrl(url: string): string {\n  // No-op on bare metal\n  if (!isVMEnvironment()) {\n    return url;\n  }\n\n  // Parse the URL to check the hostname\n  let parsed: URL;\n  try {\n    parsed = new URL(url);\n  } catch {\n    // Not a valid URL — return unchanged\n    return url;\n  }\n\n  // Check if hostname is a localhost variant\n  if (!LOCALHOST_HOSTS.has(parsed.hostname)) {\n    return url;\n  }\n\n  // Resolve the gateway host\n  const gatewayHost = resolveHostGateway();\n  if (!gatewayHost) {\n    return url;\n  }\n\n  // Rewrite the hostname\n  parsed.hostname = gatewayHost;\n  const rewritten = parsed.toString();\n\n  rewriteLogger.debug('Rewrote localhost URL', {\n    original: url,\n    rewritten,\n    gatewayHost,\n  });\n\n  return rewritten;\n}\n\n/**\n * Reset the cached gateway host (for testing).\n * @internal\n */\nexport function _resetGatewayCache(): void {\n  cachedGatewayHost = undefined;\n}\n","/**\n * GENERATED FILE — DO NOT EDIT.\n *\n * Written by scripts/generate-version.mjs from the \"version\" field in\n * package.json. Bump the version there; this file follows automatically on\n * `npm run build` / `npm run typecheck`.\n */\n\n/**\n * Version of the plugin-utils package.\n * Can be used at runtime to check compatibility.\n */\nexport const PLUGIN_UTILS_VERSION = '2.7.1';\n"],"mappings":"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","names":[]}