RSS Git Download  Clone
Raw Blame History 2kB 54 lines
import Anthropic from '@anthropic-ai/sdk';
import { zodToJsonSchema } from './schema.mjs';

const RATES = {
    'claude-opus-4-7': { in: 15.00, out: 75.00 },
    'claude-sonnet-4-6': { in: 3.00, out: 15.00 },
    'claude-haiku-4-5': { in: 1.00, out: 5.00 },
};

let client = null;
const getClient = () => {
    if (!client) {
        if (!process.env.ANTHROPIC_API_KEY) {
            throw new Error('ANTHROPIC_API_KEY missing — see secure/.env.example');
        }
        client = new Anthropic();
    }
    return client;
};

export async function callAnthropic({ system, user, schema, schemaName, model = process.env.ANTHROPIC_MODEL || 'claude-opus-4-7', maxTokens = 16_000 }) {
    const response = await getClient().messages.create({
        model,
        max_tokens: maxTokens,
        system,
        tools: [{
            name: schemaName,
            description: `Submit ${schemaName} result.`,
            input_schema: zodToJsonSchema(schema),
        }],
        tool_choice: { type: 'tool', name: schemaName },
        messages: [
            { role: 'user', content: user },
        ],
    });

    const toolUse = response.content.find((c) => c.type === 'tool_use');
    if (!toolUse) {
        throw new Error('Anthropic returned no tool_use block');
    }

    const validated = schema.parse(toolUse.input);

    const rates = RATES[model] || RATES['claude-opus-4-7'];
    const usage = {
        input: response.usage.input_tokens,
        output: response.usage.output_tokens,
        usd: (response.usage.input_tokens * rates.in + response.usage.output_tokens * rates.out) / 1_000_000,
        model,
    };

    return { data: validated, usage };
}