feat(api): LLM provider adapters — Anthropic + OpenAI-compatible #337
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!337
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "336-llm-provider-adapters"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the thin non-streaming inference layer (ADR-0029 §1 + §4, #336): an
LlmClientinterface with an Anthropic adapter (official@anthropic-ai/sdk) and an OpenAI-compatible adapter (plainfetch— covers OpenAI, OpenRouter, and local Ollama). It consumes #333's per-user config/key and the #51 tool registry.Rebased onto
mainafter #334 merged, so the diff is just the adapters.What's in it (
apps/api/lib/llm/)client.ts—LlmClientinterface (generate(req): Promise<LlmResult>), normalized types (LlmMessage/LlmToolDef/LlmGenerateRequest/LlmResult/LlmStopReason), a typedLlmError(not_configured/key_required/auth/rate_limited/overloaded/provider_error— carries only kind/message/status, never the key or raw error), andgetLlmClientForUser(userId, db).tools.ts— zod → JSON Schema (z.toJSONSchema,allOf-flattened, mirroring the MCP endpoint) +llmToolDefsFromRegistry()over #51'sallTools().anthropic.ts— translates normalized ↔ Anthropic Messages API (system,tool_use/tool_resultblocks,tools); injectable client/fetch for tests; model-agnostic (nothinking/sampling params in v1). 529/overload detected viastatus === 529 || type === "overloaded_error"(the brief'sOverloadedErrorclass doesn't exist in SDK 0.106).openai-compatible.ts—fetchto${baseUrl}/chat/completions,Authorizationomitted when keyless; maps function-tools +tool_calls/toolrole; injectablefetch.getLlmClientForUserselects the adapter from the #333 config + decrypted key (anthropic → key required; openai_compatible → key optional).Bounded to inference + provider selection — streaming, the agent loop,
conversations/messages, the SSE endpoint, and the chat UI are later tickets that consume this.Dependency
@anthropic-ai/sdk@^0.106.0(latest stable; the requested^0.69.0is superseded). Established package (won't trip package-age); no install lifecycle script, so noonlyBuiltDependenciesentry. OpenAI-compatible usesfetch— noopenaidep. Lockfile updated;pnpm install --frozen-lockfileclean.Verification
baseUrlfetch);openapi:checkup to date (lib-only, no contract change).describePerEngine). Tests inject transport (fake Anthropic client / fakefetch) — no live API calls: request translation, response normalization (text + tool_calls + stop reason), tool-use round, error mapping, keyless Ollama, selection +not_configured/key_required, and an assertion that no tool def carriesuser_id.Closes #336
🤖 Generated with Claude Code
📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.
e95bdbac1132c903d0b5