You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two more ModelBackend implementations in core: anthropic (Claude via direct API) and bedrock (AWS Bedrock — Claude, Llama, Mistral, Titan, etc. via AWS). Both validate the interface against shapes that differ meaningfully from OpenAI's, and round out the major-provider coverage for the issue's Acceptance section.
What ships:
AnthropicBackend class implementing ModelBackend (generate, generateStream, native tool calls; no embed — Anthropic doesn't ship an embedding API).
BedrockBackend class implementing ModelBackend (embed, generate, generateStream, with tool calls on the model families that support them).
Config-driven backend resolution for both.
Streaming via each SDK's chunked completion API → AsyncIterable<GenerateChunk>.
AbortSignal propagation into SDK calls.
Per-call accounting written through hdb_model_calls.
Implementation only — no new design surface. The interface validation work happened in Phase 3 (OpenAI); these are mostly translation-layer code per-provider.
Capability shapes
anthropic
capabilities(): ModelCapabilities{return{embed: false,// Anthropic doesn't currently ship an embedding APIgenerate: true,stream: true,tools: true,// first-class tool-call supportadapters: false,};}
bedrock
capabilities(): ModelCapabilities{return{embed: true,// via Titan / Cohere / Voyage models on Bedrockgenerate: true,stream: true,tools: true,// on Claude, Llama 3.x, Mistral, etc.adapters: false,};}
For Bedrock, tool support is per-model — the backend advertises tools: true at the capability level, and individual generate() calls raise a structured error if the specific configured model doesn't support tools. Same applies to embed — only Bedrock embedding models (Titan, Cohere, Voyage on Bedrock) work; calling embed() against a generative-only model raises a structured error.
Configuration
models:
generative:
claude:
backend: anthropicmodel: <your-claude-model> # e.g. claude-opus-4-7apiKey: ${ANTHROPIC_API_KEY}bedrock-claude:
backend: bedrockmodel: <your-bedrock-model-id> # e.g. anthropic.claude-opus-4-v1:0region: us-east-1# AWS credentials resolved via the standard AWS SDK chain (env vars, IAM role, profile, etc.)embedding:
bedrock-titan:
backend: bedrockmodel: amazon.titan-embed-text-v2:0region: us-east-1
Implementation notes
Anthropic backend
SDK: official @anthropic-ai/sdk npm package.
SDK pinning: lock to a specific minor version, bump per the Harper third-party trust model.
messages array translation: Anthropic's shape differs slightly from OpenAI's (system is a top-level param, not a role; tool_use / tool_result are content blocks in messages, not separate fields). Translation handled in the backend's generate().
Streaming: SDK's stream: true yields delta events. Translate to GenerateChunk.
Token-count fields from result.usage (input_tokens, output_tokens) map to TokenUsage.promptTokens / completionTokens.
Prompt caching (Anthropic-specific feature): exposed via the standard messages content blocks with cache_control markers; backend accepts opaque cache hints in opts but doesn't expose a Harper-side cache API in this phase.
signal: AbortSignal passed into the SDK's signal option.
Bedrock backend
SDK: @aws-sdk/client-bedrock-runtime npm package.
Bedrock has multiple model invocation shapes per model family (Claude vs Llama vs Titan etc.). Backend dispatches on model field to the appropriate request shape — encapsulates the per-family logic so callers see only GenerateInput / GenerateOpts.
Bedrock streaming uses the InvokeModelWithResponseStream API. Translate event-stream chunks to GenerateChunk.
AWS auth via standard SDK chain — no API key field in the Harper config (env / IAM / profile do the work).
Token-count fields per-family — Claude via Bedrock reports usage.input_tokens / usage.output_tokens; Llama via Bedrock reports prompt_token_count / generation_token_count. Per-family translation in the backend.
Files
Path
Change
resources/models/backends/anthropic.ts
new — AnthropicBackend class
resources/models/backends/bedrock.ts
new — BedrockBackend class with per-family request dispatch
resources/models/backends/index.ts
extended — register anthropic and bedrock factories
package.json
new deps — @anthropic-ai/sdk, @aws-sdk/client-bedrock-runtime (pinned versions)
test/models/anthropic.test.ts
new — unit + integration tests (live test behind env-gated flag)
test/models/bedrock.test.ts
new — unit + integration tests (live test behind env-gated flag)
Acceptance criteria
AnthropicBackend implements ModelBackend per Phase 1's interface.
BedrockBackend implements ModelBackend per Phase 1's interface; dispatches on model field to the right per-family request shape.
scope.models.generate() produces completions from each provider.
scope.models.generateStream() yields content deltas from each provider with correct stream framing.
Tool calls in 'return' mode work on Anthropic and on Bedrock-Claude (and other tool-capable Bedrock models).
scope.models.embed() produces vectors via Bedrock embedding models (Titan, etc.).
embed: false on the anthropic backend → scope.models.embed() raises a structured "backend doesn't support embed" error (capability negotiation enforced from Phase 1).
AbortSignal from BackendOpts cancels in-flight SDK calls for both providers.
AWS credentials resolved via the standard SDK chain for Bedrock (env / IAM / profile).
SDK versions pinned and documented; bump cadence noted in PR description.
Integration tests pass against real APIs behind env-gated flags (ANTHROPIC_API_KEY, AWS_PROFILE / equivalent).
/v1/chat/completions from Phase 4 works against both backends end-to-end (an OpenAI-SDK client can hit Harper, which routes to Anthropic or Bedrock under the hood; OpenAI-shape translation in the gateway handles the request/response differences).
Per-model fine-tuning or LoRA adapter selection — adapters: false on both.
Anthropic Files / Messages Batch API (long-running operation surface) — ModelCallResult.pending is reserved in the interface but no backend in Add unified model-access API (scope.models) #510 emits it.
Bedrock Knowledge Bases / Agents — those are application-layer features above the model-access API.
Provider-side prompt caching as a first-class Harper feature — Anthropic's prompt caching is consumed via opaque opts passthrough; not a Harper-side cache primitive.
Each backend (anthropic and bedrock) can ship independently in its own PR if convenient — they share Phase 1's foundation but don't depend on each other.
Branch & PR conventions
Branch: feat/models-anthropic-bedrock-backends (single PR with both), or feat/models-anthropic-backend / feat/models-bedrock-backend if split.
Scope
Two more
ModelBackendimplementations in core:anthropic(Claude via direct API) andbedrock(AWS Bedrock — Claude, Llama, Mistral, Titan, etc. via AWS). Both validate the interface against shapes that differ meaningfully from OpenAI's, and round out the major-provider coverage for the issue's Acceptance section.What ships:
AnthropicBackendclass implementingModelBackend(generate,generateStream, native tool calls; noembed— Anthropic doesn't ship an embedding API).BedrockBackendclass implementingModelBackend(embed,generate,generateStream, with tool calls on the model families that support them).AsyncIterable<GenerateChunk>.AbortSignalpropagation into SDK calls.hdb_model_calls.Implementation only — no new design surface. The interface validation work happened in Phase 3 (OpenAI); these are mostly translation-layer code per-provider.
Capability shapes
anthropicbedrockFor Bedrock, tool support is per-model — the backend advertises
tools: trueat the capability level, and individualgenerate()calls raise a structured error if the specific configured model doesn't support tools. Same applies toembed— only Bedrock embedding models (Titan, Cohere, Voyage on Bedrock) work; callingembed()against a generative-only model raises a structured error.Configuration
Implementation notes
Anthropic backend
@anthropic-ai/sdknpm package.messagesarray translation: Anthropic's shape differs slightly from OpenAI's (systemis a top-level param, not a role;tool_use/tool_resultare content blocks in messages, not separate fields). Translation handled in the backend'sgenerate().stream: trueyields delta events. Translate toGenerateChunk.result.usage(input_tokens,output_tokens) map toTokenUsage.promptTokens/completionTokens.messagescontent blocks withcache_controlmarkers; backend accepts opaque cache hints in opts but doesn't expose a Harper-side cache API in this phase.signal: AbortSignalpassed into the SDK'ssignaloption.Bedrock backend
@aws-sdk/client-bedrock-runtimenpm package.modelfield to the appropriate request shape — encapsulates the per-family logic so callers see onlyGenerateInput/GenerateOpts.InvokeModelWithResponseStreamAPI. Translate event-stream chunks toGenerateChunk.usage.input_tokens/usage.output_tokens; Llama via Bedrock reportsprompt_token_count/generation_token_count. Per-family translation in the backend.Files
resources/models/backends/anthropic.tsAnthropicBackendclassresources/models/backends/bedrock.tsBedrockBackendclass with per-family request dispatchresources/models/backends/index.tsanthropicandbedrockfactoriespackage.json@anthropic-ai/sdk,@aws-sdk/client-bedrock-runtime(pinned versions)test/models/anthropic.test.tstest/models/bedrock.test.tsAcceptance criteria
AnthropicBackendimplementsModelBackendper Phase 1's interface.BedrockBackendimplementsModelBackendper Phase 1's interface; dispatches onmodelfield to the right per-family request shape.scope.models.generate()produces completions from each provider.scope.models.generateStream()yields content deltas from each provider with correct stream framing.'return'mode work on Anthropic and on Bedrock-Claude (and other tool-capable Bedrock models).scope.models.embed()produces vectors via Bedrock embedding models (Titan, etc.).embed: falseon theanthropicbackend →scope.models.embed()raises a structured "backend doesn't support embed" error (capability negotiation enforced from Phase 1).backend: 'anthropic'/'bedrock', model, token counts, latency, success.AbortSignalfromBackendOptscancels in-flight SDK calls for both providers.ANTHROPIC_API_KEY,AWS_PROFILE/ equivalent)./v1/chat/completionsfrom Phase 4 works against both backends end-to-end (an OpenAI-SDK client can hit Harper, which routes to Anthropic or Bedrock under the hood; OpenAI-shape translation in the gateway handles the request/response differences).Out of scope
toolMode: 'auto'orchestration — that's Add agent-loop orchestration /toolMode: 'auto'toscope.models#612, independent of these backends.adapters: falseon both.ModelCallResult.pendingis reserved in the interface but no backend in Add unified model-access API (scope.models) #510 emits it.Stacks on
ModelBackendinterface and registry.Each backend (anthropic and bedrock) can ship independently in its own PR if convenient — they share Phase 1's foundation but don't depend on each other.
Branch & PR conventions
feat/models-anthropic-bedrock-backends(single PR with both), orfeat/models-anthropic-backend/feat/models-bedrock-backendif split.main(after Phase 1 and Phase 3 merge).Closes #<self>; references Add unified model-access API (scope.models) #510 viaTracking: #510.Smoke test
Tracking
Part of #510. Sub-issue 6 of 6.
🤖 Generated with Claude Code