spb/search-box Public
Agentic web research engine — hypotheses, verbatim evidence, contradictions, sourced answers streamed live. Claude Opus 5 + Firecrawl + PostgreSQL.
TypeScript 76.9%
CSS 18.7%
SQL 2.1%
JavaScript 1.8%
Shell 0.5%
1/**2 * Search-box.ai3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: packages/anthropic/src/index.ts6 * Description: Anthropic Messages API adapter — singleton client, model config, streaming helpers.7 */89import Anthropic from "@anthropic-ai/sdk";1011let client: Anthropic | null = null;1213export function getAnthropic(): Anthropic {14 if (!client) {15 if (!process.env.ANTHROPIC_API_KEY) throw new Error("ANTHROPIC_API_KEY is not set");16 client = new Anthropic({ maxRetries: 3 });17 }18 return client;19}2021export interface ModelConfig {22 orchestrator: string;23 researcher: string;24 verifier: string;25 synthesis: string;26}2728const DEFAULT_MODEL = "claude-opus-5";2930export function getModels(): ModelConfig {31 return {32 orchestrator: process.env.CLAUDE_ORCHESTRATOR_MODEL || DEFAULT_MODEL,33 researcher: process.env.CLAUDE_RESEARCHER_MODEL || DEFAULT_MODEL,34 verifier: process.env.CLAUDE_VERIFIER_MODEL || DEFAULT_MODEL,35 synthesis: process.env.CLAUDE_SYNTHESIS_MODEL || DEFAULT_MODEL36 };37}3839/**40 * One orchestrator turn (non-streaming). Uses streaming under the hood via41 * the SDK helper so long turns don't hit HTTP timeouts.42 */43export async function orchestratorTurn(params: {44 model: string;45 system: Anthropic.TextBlockParam[];46 messages: Anthropic.MessageParam[];47 tools: Anthropic.ToolUnion[];48 maxTokens?: number;49}): Promise<Anthropic.Message> {50 const stream = getAnthropic().messages.stream({51 model: params.model,52 max_tokens: params.maxTokens ?? 16000,53 output_config: { effort: "medium" },54 system: params.system,55 messages: params.messages,56 tools: params.tools57 });58 return stream.finalMessage();59}6061/**62 * Streaming synthesis call. Invokes `onDelta` per text token; resolves with63 * the final message once complete.64 */65export async function synthesisStream(params: {66 model: string;67 system: Anthropic.TextBlockParam[];68 messages: Anthropic.MessageParam[];69 maxTokens?: number;70 onDelta: (delta: string) => void | Promise<void>;71}): Promise<Anthropic.Message> {72 const stream = getAnthropic().messages.stream({73 model: params.model,74 max_tokens: params.maxTokens ?? 24000,75 output_config: { effort: "medium" },76 system: params.system,77 messages: params.messages78 });79 stream.on("text", (delta) => {80 void params.onDelta(delta);81 });82 return stream.finalMessage();83}8485export type { Anthropic };86