/** * OpenAI direct LLM provider. * * Uses OpenAI REST API with Bearer token authentication. * Reads config from OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL. */ import type { ChatCompletionRequest, ChatCompletionResponse, LLMProvider } from '../types.js'; export interface OpenAIConfig { apiKey: string; baseUrl?: string; model?: string; } export class OpenAIProvider implements LLMProvider { private config: OpenAIConfig; constructor(config?: Partial) { this.config = { apiKey: config?.apiKey || process.env.OPENAI_API_KEY || '', baseUrl: config?.baseUrl || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1', model: config?.model || process.env.OPENAI_MODEL || 'gpt-4o-mini', }; } isConfigured(): boolean { return Boolean(this.config.apiKey); } async chatCompletion(req: ChatCompletionRequest): Promise { if (!this.isConfigured()) { throw new Error('OpenAI is not configured (missing OPENAI_API_KEY)'); } const base = this.config.baseUrl!.replace(/\/+$/, ''); const url = `${base}/chat/completions`; const body = { model: req.model || this.config.model, messages: req.messages, temperature: req.temperature, max_tokens: req.maxTokens, top_p: req.topP, stop: req.stop, response_format: req.responseFormat, }; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.config.apiKey}`, }, body: JSON.stringify(body), }); if (!response.ok) { const text = await response.text(); throw new Error(`OpenAI error ${response.status}: ${text}`); } const data = (await response.json()) as { choices: Array<{ message: { content: string }; finish_reason: string }>; model: string; usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number }; }; return { content: data.choices[0]?.message?.content ?? '', model: data.model, finishReason: (data.choices[0]?.finish_reason as ChatCompletionResponse['finishReason']) ?? null, usage: { promptTokens: data.usage.prompt_tokens, completionTokens: data.usage.completion_tokens, totalTokens: data.usage.total_tokens, }, }; } }