Repository Public MIT Selected
1/**2 * ─────────────────────────────────────────────3 * SPB Git — Personal Git Platform4 * ─────────────────────────────────────────────5 * Author : Simon-Pierre Boucher6 * Contact : contact@spboucher.ai7 * File : src/ai/providers/openai-compatible.mjs8 * Purpose : AI provider — local inference servers speaking the OpenAI chat-completions shape (MLX, llama.cpp, vLLM); raw fetch, no SDK9 * License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213/**14 * @param {{baseUrl: string, apiKey?: string, model: string}} cfg base URL without the `/chat/completions` suffix15 */16export function createOpenAICompatibleProvider(cfg) {17 if (!cfg.baseUrl) throw new Error('SPBGIT_AI_BASE_URL is required for the openai-compatible provider');18 if (!cfg.model) throw new Error('SPBGIT_AI_MODEL is required for the openai-compatible provider');19 const url = `${cfg.baseUrl.replace(/\/+$/, '')}/chat/completions`;20 return {21 name: 'openai-compatible',22 model: cfg.model,23 async complete({ system, prompt, maxTokens }) {24 const controller = new AbortController();25 const timer = setTimeout(() => controller.abort(), 120000);26 try {27 const res = await fetch(url, {28 method: 'POST',29 headers: { 'Content-Type': 'application/json', ...(cfg.apiKey ? { Authorization: `Bearer ${cfg.apiKey}` } : {}) },30 body: JSON.stringify({ model: cfg.model, max_tokens: maxTokens, temperature: 0.2, messages: [{ role: 'system', content: system }, { role: 'user', content: prompt }] }),31 signal: controller.signal,32 });33 if (!res.ok) throw new Error(`inference server returned HTTP ${res.status}`);34 const data = await res.json();35 const choice = data.choices?.[0];36 return {37 text: String(choice?.message?.content ?? '').trim(),38 truncated: choice?.finish_reason === 'length',39 usage: { input: data.usage?.prompt_tokens ?? 0, output: data.usage?.completion_tokens ?? 0 },40 model: data.model ?? cfg.model,41 };42 } finally {43 clearTimeout(timer);44 }45 },46 };47}48