Contract — P0.9.2 ν Claude API Wrappers
1. Module identity
| Attribute | Value |
|---|---|
| File | src/domains/integrations/claude.ts |
| Test file | src/__tests__/domains/integrations/claude.test.ts |
| Exported from | src/domains/integrations/index.ts |
| Concept | ν — Integrations (P0.9.2) |
| Layer | Library-only. No MCP tools registered. |
2. Public API surface
2.1 createCompletion
export async function createCompletion(
prompt: string,
options?: CompletionOptions,
): Promise<CompletionResult>
Behaviour:
- Sends a POST to
https://api.anthropic.com/v1/messages. - Model:
options.model ?? config.COLIBRI_ANTHROPIC_MODEL ?? 'claude-sonnet-4-5'. - Max tokens:
options.maxTokens ?? 1024. - Validates
ANTHROPIC_API_KEYis set before making the request; throwsAnthropicConfigErrorif absent. - Logs to stderr:
[claude] model=<m> prompt_tokens=<n> completion_tokens=<n> latency_ms=<n>. - On 429: exponential backoff, max 3 retries (delays: 100 ms → 200 ms → 400 ms).
- On 5xx: same retry policy.
- On 4xx (excluding 429): terminal failure — throws
AnthropicApiError. - On network error: terminal failure — throws
AnthropicApiError. - After 3 failed retries: throws
AnthropicApiErrorwithretries_exhausted.
2.2 createCompletionWithTools
export async function createCompletionWithTools(
prompt: string,
tools: AnthropicTool[],
options?: CompletionOptions,
): Promise<CompletionResult>
Behaviour:
- Same as
createCompletionwith the addition of atoolsarray in the request body. AnthropicTool:{ name: string; description: string; input_schema: Record<string, unknown> }.- When
toolsis empty, degrades to a plaincreateCompletioncall (notoolskey in body). - Same retry and logging contract as
createCompletion.
2.3 CompletionOptions
export interface CompletionOptions {
readonly model?: string;
readonly maxTokens?: number;
readonly systemPrompt?: string;
/** Inject a custom fetch for tests. Defaults to global `fetch`. */
readonly fetchFn?: typeof fetch;
/** Inject a custom logger for tests. Defaults to `console.error`. */
readonly logger?: (...args: unknown[]) => void;
/** Inject a delay function for tests. Defaults to `sleep` (real delay). */
readonly delayFn?: (ms: number) => Promise<void>;
}
2.4 CompletionResult
export interface CompletionResult {
readonly content: string;
readonly model: string;
readonly promptTokens: number;
readonly completionTokens: number;
readonly latencyMs: number;
readonly stopReason: string;
}
2.5 AnthropicTool
export interface AnthropicTool {
readonly name: string;
readonly description: string;
readonly input_schema: Record<string, unknown>;
}
2.6 Error types
/** Thrown when ANTHROPIC_API_KEY is missing at call time. */
export class AnthropicConfigError extends Error {
readonly code = 'ANTHROPIC_CONFIG_ERROR';
}
/** Thrown when the API returns a terminal error or retries are exhausted. */
export class AnthropicApiError extends Error {
readonly code: 'ANTHROPIC_API_ERROR' | 'ANTHROPIC_RETRIES_EXHAUSTED';
readonly status?: number;
}
3. Env schema additions (to src/config.ts)
| Variable | Type | Default | Required |
|---|---|---|---|
ANTHROPIC_API_KEY |
string |
— | Optional in schema; required at call-time |
COLIBRI_ANTHROPIC_MODEL |
string |
'claude-sonnet-4-5' |
No |
COLIBRI_ANTHROPIC_TIMEOUT_MS |
number (coerce) |
30000 |
No |
ANTHROPIC_API_KEY is declared .optional() so the server boots cleanly even when the key is absent — this matches the acceptance criteria (“validated at startup” means validated before use, not at module load).
4. Retry algorithm
attempt = 0
delayMs = 100
maxRetries = 3
loop:
try POST /v1/messages
if success (2xx): return result
if retryable (429, 5xx):
if attempt >= maxRetries: throw AnthropicApiError('retries_exhausted')
await delay(delayMs)
delayMs *= 2
attempt++
continue
else (4xx not 429, network error):
throw AnthropicApiError(status)
5. Logging format
[claude] model=claude-sonnet-4-5 prompt_tokens=42 completion_tokens=128 latency_ms=1234
Logged to stderr (console.error / injected logger). Never to stdout.
6. Migration decision
No migration. Logging is stderr-only per P0.9.3 precedent. The spec says “logged”, not “stored in DB”. A 008_nu_anthropic.sql would be overkill and add coupling.
7. Security invariants
ANTHROPIC_API_KEYis never logged, never echoed in errors, never present in tests.- Tests use
'sk-ant-test-fake-key'dummy value. - No real network calls in tests —
fetchFnis always injected.
8. Out of scope (Phase 0)
- Circuit breaker (donor
resilience.js). - Prompt cache / deduplication.
- Streaming / SSE.
- Extended thinking.
- Token budget management.
- MCP tool registration for Claude API.