/** * Search-box.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: packages/anthropic/src/index.ts * Description: Anthropic Messages API adapter — singleton client, model config, streaming helpers. */ import Anthropic from "@anthropic-ai/sdk"; let client: Anthropic | null = null; export function getAnthropic(): Anthropic { if (!client) { if (!process.env.ANTHROPIC_API_KEY) throw new Error("ANTHROPIC_API_KEY is not set"); client = new Anthropic({ maxRetries: 3 }); } return client; } export interface ModelConfig { orchestrator: string; researcher: string; verifier: string; synthesis: string; } const DEFAULT_MODEL = "claude-opus-5"; export function getModels(): ModelConfig { return { orchestrator: process.env.CLAUDE_ORCHESTRATOR_MODEL || DEFAULT_MODEL, researcher: process.env.CLAUDE_RESEARCHER_MODEL || DEFAULT_MODEL, verifier: process.env.CLAUDE_VERIFIER_MODEL || DEFAULT_MODEL, synthesis: process.env.CLAUDE_SYNTHESIS_MODEL || DEFAULT_MODEL }; } /** * One orchestrator turn (non-streaming). Uses streaming under the hood via * the SDK helper so long turns don't hit HTTP timeouts. */ export async function orchestratorTurn(params: { model: string; system: Anthropic.TextBlockParam[]; messages: Anthropic.MessageParam[]; tools: Anthropic.ToolUnion[]; maxTokens?: number; }): Promise { const stream = getAnthropic().messages.stream({ model: params.model, max_tokens: params.maxTokens ?? 16000, output_config: { effort: "medium" }, system: params.system, messages: params.messages, tools: params.tools }); return stream.finalMessage(); } /** * Streaming synthesis call. Invokes `onDelta` per text token; resolves with * the final message once complete. */ export async function synthesisStream(params: { model: string; system: Anthropic.TextBlockParam[]; messages: Anthropic.MessageParam[]; maxTokens?: number; onDelta: (delta: string) => void | Promise; }): Promise { const stream = getAnthropic().messages.stream({ model: params.model, max_tokens: params.maxTokens ?? 24000, output_config: { effort: "medium" }, system: params.system, messages: params.messages }); stream.on("text", (delta) => { void params.onDelta(delta); }); return stream.finalMessage(); } export type { Anthropic };