{"version":3,"sources":["../../src/providers/openai-compatible.ts","../../src/logging/plugin-logger.ts","../../src/logging/index.ts","../../src/version.ts","../../src/providers/request-budget.ts","../../src/providers/profile-parameters.ts","../../src/providers/system-messages.ts"],"sourcesContent":["/**\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 * 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 * 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"],"mappings":"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","names":[]}