/** * Perplexity LLM provider. * * Uses Perplexity's OpenAI-compatible API with real-time web search. * Reads config from PERPLEXITY_API_KEY, PERPLEXITY_MODEL. */ import type { ChatCompletionRequest, ChatCompletionResponse, LLMProvider } from '../types.js'; export interface PerplexityConfig { apiKey: string; model?: string; } export class PerplexityProvider implements LLMProvider { private config: PerplexityConfig; constructor(config?: Partial) { this.config = { apiKey: config?.apiKey || process.env.PERPLEXITY_API_KEY || '', model: config?.model || process.env.PERPLEXITY_MODEL || 'sonar', }; } isConfigured(): boolean { return Boolean(this.config.apiKey); } async chatCompletion(req: ChatCompletionRequest): Promise { if (!this.isConfigured()) { throw new Error('Perplexity is not configured (missing PERPLEXITY_API_KEY)'); } const body = { model: req.model || this.config.model, messages: req.messages, temperature: req.temperature, max_tokens: req.maxTokens, top_p: req.topP, }; const response = await fetch('https://api.perplexity.ai/chat/completions', { 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(`Perplexity 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 ?? 0, completionTokens: data.usage?.completion_tokens ?? 0, totalTokens: data.usage?.total_tokens ?? 0, }, }; } }