Skip to content

Phase 2.5: AWS Bedrock URL + body rewriting for native InvokeModel passthrough #205

Description

@initializ-mk

Context

Issue #202 Phase 2 (shipped in PR #204) added model.auth_scheme: aws_sigv4 + the hand-rolled SigV4 outbound transport. The signing mechanism is correct and tested end-to-end.

What's missing is the Bedrock-specific protocol translation required to make the SigV4 path work against Bedrock's native API surface. Today, with provider: anthropic + auth_scheme: aws_sigv4 + base_url: https://bedrock-runtime.<region>.amazonaws.com, Forge issues:

POST <base_url>/v1/messages
{
  "model": "anthropic.claude-sonnet-4-20250514-v1:0",
  "messages": […],
  …
}

— which is the native Anthropic Messages API shape. Bedrock's API surface for the same conversation is:

POST <base_url>/model/anthropic.claude-sonnet-4-20250514-v1%3A0/invoke
{
  "anthropic_version": "bedrock-2023-05-31",
  "messages": […],
  …          ← no "model" field; the model is in the URL
}

Until that translation lands, the Phase 2 path works against Bedrock-compat proxies (litellm, OpenRouter) that accept the standard wire format and forward to Bedrock natively. It does NOT work against the bare Bedrock endpoint.

Scope

Add Bedrock-aware URL + body rewriting gated by auth_scheme: aws_sigv4. The presence of aws_sigv4 is a strong enough signal that the operator IS pointing at Bedrock (or something that exposes the same API surface) — provider still drives the wire shape, but the URL/body shape gets the Bedrock-specific treatment.

Anthropic provider (provider: anthropic + auth_scheme: aws_sigv4)

  1. URL: rewrite <base_url>/v1/messages<base_url>/model/<URL-encoded model id>/invoke. URL-encode any : in the model id (Bedrock model ids contain colons: anthropic.claude-sonnet-4-20250514-v1:0).
  2. Body: add "anthropic_version": "bedrock-2023-05-31" field; remove the "model" field (Bedrock 400s if it's present).
  3. Streaming: Bedrock's streaming endpoint is /model/<id>/invoke-with-response-stream (not -stream). Switch on streaming.
  4. Headers: anthropic-version: 2023-06-01 header is NOT sent (replaced by the body field); other headers (Content-Type, X-Amz-Date, X-Amz-Content-Sha256, Authorization) ride per the existing SigV4 transport.

OpenAI provider (provider: openai + auth_scheme: aws_sigv4)

Bedrock's OpenAI compatibility endpoint speaks the OpenAI Chat Completions shape verbatim at /v1/chat/completions — same URL Forge already uses. No rewriting needed; just confirm in a test that the SigV4 transport composes cleanly against this endpoint.

Streaming chunk format

Bedrock's invoke-with-response-stream returns AWS event-stream framing (application/vnd.amazon.eventstream), NOT plain SSE. Each chunk is a length-prefixed binary frame with headers and a JSON payload. The parser needs to:

  1. Detect the response Content-Type to choose between SSE (Anthropic native) and event-stream (Bedrock).
  2. Implement the event-stream binary framing decoder (~60 LOC: 4-byte total length, 4-byte headers length, 4-byte prelude CRC, headers, payload, message CRC).
  3. Extract the JSON payload from each frame and feed it through the existing Anthropic SSE-event handler.

This is the largest single piece of work in the issue.

Implementation surface

File Change
forge-core/llm/providers/anthropic.go URL builder branches on c.authScheme == "aws_sigv4"; body builder adds anthropic_version + drops model; streaming URL suffix changes
forge-core/llm/providers/anthropic_eventstream.go (new) AWS event-stream framing decoder feeding the existing SSE handler
forge-core/llm/providers/anthropic.go ChatStream chooses parser based on Content-Type
forge-core/llm/providers/openai.go No code change; one new test confirming SigV4 transport + standard URL flows clean against the Bedrock OpenAI compat endpoint shape
docs/security/authentication.md New "AWS Bedrock with native InvokeModel" subsection explaining the protocol differences and what auth_scheme: aws_sigv4 does on each provider
docs/reference/forge-yaml-schema.md Worked Bedrock example with provider: anthropic + auth_scheme: aws_sigv4
CHANGELOG.md Unreleased entry referencing this issue + #202

Test plan

  • Anthropic URL rewrite: https://bedrock-runtime.us-east-1.amazonaws.com + model id anthropic.claude-sonnet-4-20250514-v1:0 → request lands at /model/anthropic.claude-sonnet-4-20250514-v1%3A0/invoke.
  • Anthropic body rewrite: request body has anthropic_version: bedrock-2023-05-31, NO model field.
  • AuthScheme empty (default) → URL + body unchanged from current Anthropic native shape.
  • Event-stream decoder: feed a hand-crafted byte stream with two valid frames → emits two JSON payloads in order; rejects a frame with bad CRC.
  • End-to-end against stub Bedrock server: stub server validates URL pattern, accepts the rewritten body, returns event-stream frames; Forge consumes them cleanly.
  • OpenAI client against stub Bedrock OpenAI-compat server: SigV4 transport composes; standard URL works.
  • Manual smoke: real Bedrock account; configure as documented; round-trip a Claude completion.

Out of scope

  • IRSA / EC2 metadata / web identity credential resolution — tracked separately. Phase 2.5 still reads AWS_* env vars only.
  • Bedrock guardrails wiring — Bedrock's own guardrails system. If we want to apply Bedrock guardrail IDs on outbound, file a separate issue.
  • Cross-region Bedrock failover — model.fallbacks already supports multi-provider failover; cross-region inside one Bedrock provider would need region-list semantics.
  • Bedrock OpenAI compat URL rewriting — confirmed unnecessary; the path matches OpenAI native.

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