SPB Git — the platform hosting itself
1/**2 * ─────────────────────────────────────────────3 * SPB Git — Personal Git Platform4 * ─────────────────────────────────────────────5 * Author : Simon-Pierre Boucher6 * Contact : contact@spboucher.ai7 * File : src/ai/providers/anthropic.mjs8 * Purpose : AI provider — Claude via the official Anthropic SDK (server-side key, server-side refusal fallbacks)9 * License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213import Anthropic from '@anthropic-ai/sdk';1415const DEFAULT_MODEL = 'claude-opus-5-5';1617/**18 * @param {{apiKey: string, model?: string, baseUrl?: string}} cfg19 * @returns {{name: string, model: string, complete: (req: {system: string, prompt: string, maxTokens: number}) => Promise<{text: string, usage: {input: number, output: number}, model: string}>}}20 */21export function createAnthropicProvider(cfg) {22 if (!cfg.apiKey) throw new Error('SPBGIT_AI_API_KEY is required for the anthropic provider');23 const client = new Anthropic({ apiKey: cfg.apiKey, ...(cfg.baseUrl ? { baseURL: cfg.baseUrl } : {}) });24 const model = cfg.model || DEFAULT_MODEL;25 return {26 name: 'anthropic',27 model,28 async complete({ system, prompt, maxTokens }) {29 const response = await client.beta.messages.create({30 model,31 max_tokens: maxTokens,32 // Safety-classifier declines are re-routed server-side by category.33 betas: ['server-side-fallback-2026-07-01'],34 fallbacks: 'default',35 system,36 messages: [{ role: 'user', content: prompt }],37 });38 if (response.stop_reason === 'refusal') {39 const why = response.stop_details?.explanation ?? 'the request was declined by the model safety system';40 throw Object.assign(new Error(`The model declined to answer: ${why}`), { code: 'refusal' });41 }42 const text = response.content.filter((b) => b.type === 'text').map((b) => b.text).join('\n').trim();43 return {44 text,45 truncated: response.stop_reason === 'max_tokens',46 usage: { input: response.usage?.input_tokens ?? 0, output: response.usage?.output_tokens ?? 0 },47 model: response.model ?? model,48 };49 },50 };51}52