Skip to content

Support Anthropic-format custom URLs (incl. AWS Bedrock) via provider: anthropic #202

Description

@initializ-mk

Context

Forge today supports operator-set custom LLM endpoints via two well-known patterns:

Provider Custom URL knob Wire format Auth
OpenAI-compatible (OpenRouter, vLLM, litellm, self-hosted Kimi/Llama, Together.ai, Anyscale, Bedrock OpenAI mode, …) provider: openai + model.base_url OR OPENAI_BASE_URL OpenAI Chat Completions Authorization: Bearer <OPENAI_API_KEY>
Anthropic provider: anthropic only (api.anthropic.com hardcoded as default) Anthropic Messages x-api-key: <ANTHROPIC_API_KEY>

The forge init wizard's "Custom" option only sets provider: openai. The docs (forge-yaml-schema.md, forge.md skill) similarly guide operators to provider: openai + OPENAI_BASE_URL as the only "custom endpoint" path. There is no symmetric "Anthropic-format custom URL" surface — even though the underlying AnthropicClient already accepts cfg.BaseURL (forge-core/llm/providers/anthropic.go:27).

This issue closes the asymmetry plus adds the most-asked-for concrete target: AWS Bedrock with Anthropic-format requests.

Why "use OpenAI-compatible at Bedrock" isn't the answer

Bedrock now supports BOTH the OpenAI Chat Completions shape AND the native Anthropic Messages shape. Operators reach for the Anthropic shape when they want full access to Anthropic-specific features that don't round-trip cleanly through OpenAI's schema:

  • Prompt caching (cache_control blocks)
  • Tool use in Anthropic's structured-content format
  • Extended thinking (thinking blocks)
  • Vision with Anthropic's image content blocks
  • System prompts as a separate top-level field (cleaner than OpenAI's role=system message-mix)
  • Token / cost reporting that matches what the Anthropic dashboard / Bedrock console shows (per-tier input tokens incl. cache hits, etc.)

For an agent already targeting Claude models, forcing the OpenAI translation layer is a lossy detour. The user wants provider: anthropic + a custom base URL.

Scope (proposed three-phase rollout)

Phase 1 — Documentation + wizard parity (small; no new code paths in the request shape)

Make provider: anthropic + model.base_url (and the ANTHROPIC_BASE_URL env safety net) a first-class documented pattern. The plumbing is already there; what's missing is discoverability.

  • Update docs/reference/forge-yaml-schema.md to spell out the Anthropic-format custom URL pattern alongside the OpenAI one — current text only mentions OpenAI-compatible endpoints.
  • Update .claude/skills/forge.md LLM provider section.
  • Update forge init wizard's "Custom" phase: after the operator enters a Base URL, ask "OpenAI shape or Anthropic shape?" — default OpenAI for back-compat. The wizard writes provider: openai or provider: anthropic accordingly into the generated forge.yaml.
  • Honor ANTHROPIC_BASE_URL in the env-derived BaseURL fallback path (same way OPENAI_BASE_URL is honored today via runner.go:482 LLMProviderEnvDomains).

Use case Phase 1 unlocks: any Anthropic-compatible endpoint that uses the standard x-api-key header — internal proxies, litellm forwarders, locally-running Anthropic-shape gateways, third-party Anthropic-Messages-API-compatible vendors.

Phase 2 — AWS Bedrock with Anthropic-format (the real motivating ask; needs SigV4)

Bedrock URLs look like https://bedrock-runtime.<region>.amazonaws.com/model/<bedrock-model-id>/invoke and authenticate via AWS Signature Version 4, not x-api-key. Even with model.base_url pointed at Bedrock, today's AnthropicClient would 403 because it stamps x-api-key on every request.

Two design options to evaluate:

Option A: New bedrock provider that wraps the request shape

model:
  provider: bedrock
  format: anthropic   # or "openai"; selects the wire shape
  name: anthropic.claude-sonnet-4-20250514-v1:0
  region: us-east-1
  # AWS creds resolved via the standard chain (env, ~/.aws/credentials, IAM role, …)

Pros: clean separation, room for Bedrock-specific knobs (guardrails ID, trace, throughput type), maps to how Bedrock is conceptually a distinct provider.

Cons: a new provider type to maintain. Duplicates wire shape logic with the existing openai / anthropic clients.

Option B: Generic auth_scheme field on the existing providers

model:
  provider: anthropic
  base_url: "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-sonnet-4-20250514-v1%3A0/invoke"
  auth_scheme: aws_sigv4         # default "x_api_key"; "aws_sigv4" enables SigV4 signing on every request
  aws_region: us-east-1

Pros: reuses the AnthropicClient's wire-shape code; symmetric auth_scheme knob would also unlock OpenAI-format on Bedrock (via provider: openai + auth_scheme: aws_sigv4).

Cons: stretches the provider semantics — operator picks a wire shape and an auth scheme orthogonally, which is the cleaner model long-term but more cognitive surface.

Recommended: Option B, because the symmetric auth_scheme knob also helps OAuth-on-bedrock, mTLS-fronted internal proxies, IAP-fronted endpoints, etc. without needing one provider type per new auth shape. But this is a design call worth landing in the issue before code.

Either way, the aws_sigv4 signing implementation reuses forge-core/auth/providers/aws_sigv4 (already in the repo for inbound auth). SigV4 signing for outbound is the inverse direction but uses the same library primitives.

Phase 3 — Wizard "AWS Bedrock" first-class option (polish)

After Phase 2 lands, add an explicit "AWS Bedrock" choice in the forge init provider picker. The wizard then asks region + format (anthropic / openai) + model ID and writes the resolved config. Operator never has to know the URL template.

Out of scope

  • Native Bedrock InvokeModel API (non-passthrough): the request/response shape differs from both OpenAI and Anthropic. If we add it later it's a third format alongside openai and anthropic. The motivating use case here is passthrough (Anthropic shape on Anthropic-hosted models, OpenAI shape on the Bedrock OpenAI compatibility endpoint).
  • Bedrock guardrails wiring — Bedrock has its own guardrails system, distinct from Forge's. If we want Forge to apply Bedrock guardrail IDs on outbound calls, file a separate issue.
  • Per-region failover / multi-region — operator-side concern; Bedrock client config carries one region per agent.
  • Cross-account role assumption — handled via the AWS credentials chain in the operator's environment (AWS_PROFILE, role-arn env, IRSA on K8s), not Forge config.

Files (rough)

Phase 1 (small)

  • forge-cli/internal/tui/steps/provider_step.go — "Custom" path asks shape (OpenAI / Anthropic); writes provider: openai|anthropic.
  • forge-cli/runtime/runner.go — confirm ANTHROPIC_BASE_URL env safety net is symmetric with OPENAI_BASE_URL (current LLMProviderEnvDomains lists it; verify the runtime client construction path picks it up).
  • docs/reference/forge-yaml-schema.md + docs/getting-started/* + .claude/skills/forge.md — document the Anthropic-format custom URL pattern explicitly.
  • Tests: wizard test exercising the Anthropic-shape custom path; runtime test confirming ANTHROPIC_BASE_URL=... reaches AnthropicClient.baseURL.

Phase 2 (Bedrock SigV4)

  • forge-core/llm/providers/sigv4_transport.go (new) — http.RoundTripper that signs outbound requests using AWS SigV4. Hosted as a transport wrapper so it composes with the existing EgressTransport + otelhttp stack.
  • forge-core/llm/client.goClientConfig.AuthScheme field ("" | "x_api_key" | "bearer" | "aws_sigv4") + AWSRegion field.
  • forge-core/llm/providers/anthropic.go — when AuthScheme == "aws_sigv4", replace the x-api-key header logic with the SigV4 transport wrap.
  • forge-core/llm/providers/openai.go — symmetric change so Bedrock OpenAI compatibility works too.
  • forge-core/types/config.goModelRef gains AuthScheme + AWSRegion.
  • forge-cli/runtime/runner.go — populate the new fields from cfg.Model into ClientConfig.
  • Egress allowlist auto-extension to cover *.bedrock-runtime.amazonaws.com patterns (already covered by LLMProviderDomains + base_url parsing; verify).
  • docs/security/authentication.md — note that SigV4-for-outbound shares the inbound provider's keychain conventions.

Phase 3 (Bedrock wizard)

  • forge-cli/internal/tui/steps/provider_step.go — new top-level "AWS Bedrock" choice with region + format + model-id sub-phases.

Verification

Phase 1

  • forge init with custom URL + Anthropic shape → generated forge.yaml carries provider: anthropic + model.base_url.
  • ANTHROPIC_BASE_URL=https://my-anthropic-proxy.internal forge runAnthropicClient sends requests to my-anthropic-proxy.internal.
  • Run an agent against Anthropic's quickstart compatible API endpoint via an internal proxy → response round-trips.

Phase 2 (Bedrock-specific)

  • ANTHROPIC_API_KEY unset; AWS_REGION=us-east-1 + AWS_PROFILE=foo set; forge.yaml carries provider: anthropic + auth_scheme: aws_sigv4 + base_url: https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-sonnet-4-20250514-v1%3A0/invoke → Forge sends a SigV4-signed Anthropic-shape request that Bedrock accepts.
  • Same flow under provider: openai + auth_scheme: aws_sigv4 pointed at Bedrock's OpenAI compatibility endpoint.
  • Prompt caching headers (anthropic-beta: prompt-caching-2024-07-31) survive the SigV4 sign step.

Phase 3

  • forge init → pick "AWS Bedrock" → choose Anthropic format → enter region + model id → generated forge.yaml is valid + forge run works against real Bedrock.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions