Packet — P0.9.2 ν Claude API Wrappers
1. Files to create / modify
| Action |
Path |
Purpose |
| CREATE |
src/domains/integrations/claude.ts |
Core library: createCompletion, createCompletionWithTools, retry, logging, error types |
| CREATE |
src/__tests__/domains/integrations/claude.test.ts |
Mock-based test suite covering all acceptance criteria |
| MODIFY |
src/config.ts |
Add ANTHROPIC_API_KEY, COLIBRI_ANTHROPIC_MODEL, COLIBRI_ANTHROPIC_TIMEOUT_MS to Zod schema |
| MODIFY |
src/domains/integrations/index.ts |
Add export * from './claude.js' |
2. Implementation plan
2.1 src/config.ts additions
Add three fields to the existing Zod schema object:
ANTHROPIC_API_KEY: z.string().optional(),
COLIBRI_ANTHROPIC_MODEL: z.string().default('claude-sonnet-4-5'),
COLIBRI_ANTHROPIC_TIMEOUT_MS: z.coerce.number().int().positive().default(30000),
ANTHROPIC_API_KEY is .optional() so the server boots without it. Call-time validation in claude.ts raises AnthropicConfigError when absent.
2.2 src/domains/integrations/claude.ts structure
1. Imports: z (unused directly), config (for defaults), package-level constants
2. Constants:
ANTHROPIC_API_BASE = 'https://api.anthropic.com/v1'
ANTHROPIC_API_VERSION = '2023-06-01'
DEFAULT_MAX_TOKENS = 1024
MAX_RETRIES = 3
BASE_DELAY_MS = 100
3. Error classes: AnthropicConfigError, AnthropicApiError
4. Interfaces: AnthropicTool, CompletionOptions, CompletionResult
5. Internal helpers:
isRetryable(status: number): boolean
buildRequestBody(prompt, tools?, options): object
parseResult(json, startMs): CompletionResult
6. Core retry loop: attemptWithRetry(buildFn, options)
7. Public: createCompletion(prompt, options?)
8. Public: createCompletionWithTools(prompt, tools, options?)
2.3 Retry loop pseudocode
async function attemptWithRetry(request, options) {
const fetchFn = options.fetchFn ?? fetch;
const delayFn = options.delayFn ?? sleep;
const logger = options.logger ?? console.error;
let attempt = 0;
let delayMs = BASE_DELAY_MS;
while (true) {
const startMs = Date.now();
try {
const res = await fetchFn(url, request);
if (res.ok) {
const json = await res.json();
const result = parseResult(json, startMs);
logger(`[claude] model=${result.model} prompt_tokens=${result.promptTokens} completion_tokens=${result.completionTokens} latency_ms=${result.latencyMs}`);
return result;
}
if (isRetryable(res.status)) {
if (attempt >= MAX_RETRIES) {
throw new AnthropicApiError('retries_exhausted', res.status);
}
await delayFn(delayMs);
delayMs *= 2;
attempt++;
continue;
}
// Terminal 4xx
throw new AnthropicApiError(`HTTP ${res.status}`, res.status);
} catch (err) {
if (err instanceof AnthropicApiError || err instanceof AnthropicConfigError) throw err;
// Network / fetch error — non-retryable at this level
throw new AnthropicApiError(`Network error: ${err.message}`);
}
}
}
2.4 Test plan
| Test case |
What it covers |
Success path — createCompletion |
200 response → correct CompletionResult |
Success path — createCompletionWithTools |
tools array included in request body |
| 429 → retry → success on 3rd attempt |
Retry logic; delayFn called twice |
| 429 × 4 → retries exhausted |
AnthropicApiError with retries_exhausted |
| 500 → retry → success |
5xx retryable |
| 400 terminal |
Throws without retry |
Missing ANTHROPIC_API_KEY |
AnthropicConfigError |
| Token counting in log |
logger receives prompt_tokens / completion_tokens |
| Latency in log |
latency_ms present and ≥ 0 |
Empty tools array → no tools key in request |
Degrade path |
3. Sequence diagram
caller
│
├─ createCompletion(prompt, options)
│ │ check ANTHROPIC_API_KEY → throw AnthropicConfigError if absent
│ │ build request body
│ └─ attemptWithRetry(...)
│ │ POST /v1/messages
│ │ 200 → parseResult → log → return CompletionResult
│ │ 429/5xx → wait → retry (max 3)
│ │ 4xx → throw AnthropicApiError
│ └─ network err → throw AnthropicApiError
4. Acceptance criteria mapping
| Criterion |
Implementation |
createCompletion(prompt, options) calls API with configured model |
§2.2 + COLIBRI_ANTHROPIC_MODEL default |
createCompletionWithTools(prompt, tools, options) |
§2.2 tools branch |
API key from ANTHROPIC_API_KEY (validated at startup) |
AnthropicConfigError at call-time; optional in Zod |
| 429 → exponential backoff, max 3 retries |
§2.3 retry loop |
| All calls logged with model, prompt_tokens, completion_tokens, latency_ms |
§2.3 logger call |
| Test (mock): retry logic and logging |
§2.4 test plan |
5. Constraints
- No new runtime dependency. Use global
fetch.
- Library-only — no MCP tool registration.
- Never mock real network — inject
fetchFn.
- Never log or commit
ANTHROPIC_API_KEY.
- Do not touch
src/domains/integrations/mcp-bridge.ts or 007_nu_broker.sql (P0.9.1 territory).