/** * OpenAI-Compatible LLM Provider Base Class * * Abstract base class for LLM providers that use OpenAI-compatible REST APIs. * Provides shared implementation for: * - Message conversion (library format → OpenAI format) * - Tool definition conversion * - SSE stream parsing * - Tool call delta accumulation * - Token counting (approximation) * * Extended by: OllamaProvider, OpenAIProvider, GeminiProvider * * @example * ```typescript * class MyProvider extends OpenAICompatibleProvider { * readonly name = 'my-provider'; * protected getAuthHeaders() { return { 'Authorization': 'Bearer xxx' }; } * protected getEndpointPath() { return '/v1/chat/completions'; } * // ... other abstract methods * } * ``` */ import type { LLMProvider, Message, StreamChunk, ChatOptions, ToolDefinition } from './types.js'; import { ProviderError } from '../errors.js'; /** * OpenAI-compatible message format */ export interface OpenAIMessage { role: 'system' | 'user' | 'assistant' | 'tool'; content: string | null; tool_calls?: OpenAIToolCall[]; tool_call_id?: string; } /** * OpenAI-compatible tool call format */ export interface OpenAIToolCall { id: string; type: 'function'; function: { name: string; arguments: string; }; } /** * OpenAI-compatible tool definition format */ export interface OpenAITool { type: 'function'; function: { name: string; description: string; parameters: Record; }; } /** * OpenAI streaming response chunk format */ export interface OpenAIStreamChunk { id: string; object: string; created: number; model: string; choices: Array<{ index: number; delta: { role?: string; content?: string | null; tool_calls?: Array<{ index: number; id?: string; type?: string; function?: { name?: string; arguments?: string; }; }>; }; finish_reason: string | null; }>; usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number; /** OpenAI prompt caching: details about cached tokens */ prompt_tokens_details?: { cached_tokens?: number; audio_tokens?: number; }; }; } /** * Base configuration for OpenAI-compatible providers */ export interface OpenAICompatibleConfig { /** Base URL for the API */ baseUrl: string; /** Default model to use */ model: string; /** Default max tokens (default: 4096) */ maxTokens?: number; /** Request timeout in milliseconds (default: 120000) */ timeout?: number; /** * Optional token estimator function (e.g., tiktoken). * When provided, debug payload reports token counts instead of char-based estimates. * Fallback: Math.ceil(text.length / 4) */ estimateTokens?: (text: string) => number; } /** * Abstract base class for OpenAI-compatible LLM providers * * Provides shared implementation for providers that use the OpenAI * chat completions API format (OpenAI, Ollama, Azure OpenAI, Gemini). */ export declare abstract class OpenAICompatibleProvider implements LLMProvider { /** * Provider name (e.g., 'openai', 'ollama', 'gemini') */ abstract readonly name: string; protected readonly baseUrl: string; protected defaultModel: string; protected readonly defaultMaxTokens: number; protected readonly timeout: number; protected readonly estimateTokensFn: (text: string) => number; constructor(config: OpenAICompatibleConfig); getModel(): string; setModel(modelId: string): void; /** * Get authentication headers for API requests * @returns Headers object with auth credentials */ protected abstract getAuthHeaders(): Record; /** * Get the API endpoint path (e.g., '/v1/chat/completions') * @returns API endpoint path */ protected abstract getEndpointPath(): string; /** * Build provider-specific request body extensions * @param options Chat options * @returns Additional body fields for the request */ protected abstract buildProviderSpecificBody(options?: ChatOptions): Record; /** * Map HTTP error to ProviderError with provider-specific messages * @param status HTTP status code * @param body Response body * @param model Model name * @returns ProviderError with appropriate message */ protected abstract mapHttpError(status: number, body: string, model: string): ProviderError; /** * Map connection errors with provider-specific messages * @param error Original error * @returns ProviderError with appropriate message */ protected abstract mapConnectionError(error: Error): ProviderError; /** * Extract cache statistics from response headers. * Override in subclasses for providers that return cache stats in headers (e.g., Fireworks). * @param headers Response headers * @returns Partial LLMUsage with cache stats */ protected extractCacheStatsFromHeaders(_headers: Headers): { cacheReadTokens?: number; }; /** * Stream chat completion from the provider * * @param messages - Conversation messages * @param options - Chat options (thinking is ignored for non-Claude providers) */ chat(messages: Message[], options?: ChatOptions): AsyncIterable; /** * Convert library messages to OpenAI format */ protected convertMessages(messages: Message[]): OpenAIMessage[]; /** * Ensure every assistant tool_call has a matching tool response message. * * OpenAI strictly requires that each tool_call_id in an assistant message * is followed by a tool-role response before the next non-tool message. * This can break after ToolLoopError (partial results) or context * compaction (message reordering). We add synthetic error responses * for any orphaned tool_calls. */ protected repairOpenAIToolPairing(messages: OpenAIMessage[]): OpenAIMessage[]; /** * Map library role to OpenAI role */ protected mapRole(role: string): 'system' | 'user' | 'assistant' | 'tool'; /** * Convert tool definitions to OpenAI format */ protected convertTools(tools: ToolDefinition[]): OpenAITool[]; /** * Process a stream chunk into StreamChunk events */ protected processStreamChunk(chunk: OpenAIStreamChunk, toolCalls: Map): StreamChunk[]; /** * Count tokens in messages using tiktoken (cl100k_base encoding) */ countTokens(messages: Message[]): Promise; }