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)
- 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).
- Body: add
"anthropic_version": "bedrock-2023-05-31" field; remove the "model" field (Bedrock 400s if it's present).
- Streaming: Bedrock's streaming endpoint is
/model/<id>/invoke-with-response-stream (not -stream). Switch on streaming.
- 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:
- Detect the response Content-Type to choose between SSE (Anthropic native) and event-stream (Bedrock).
- 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).
- 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
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
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:— which is the native Anthropic Messages API shape. Bedrock's API surface for the same conversation is:
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 ofaws_sigv4is a strong enough signal that the operator IS pointing at Bedrock (or something that exposes the same API surface) —providerstill drives the wire shape, but the URL/body shape gets the Bedrock-specific treatment.Anthropic provider (
provider: anthropic + auth_scheme: aws_sigv4)<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)."anthropic_version": "bedrock-2023-05-31"field; remove the"model"field (Bedrock 400s if it's present)./model/<id>/invoke-with-response-stream(not-stream). Switch on streaming.anthropic-version: 2023-06-01header 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:This is the largest single piece of work in the issue.
Implementation surface
forge-core/llm/providers/anthropic.goc.authScheme == "aws_sigv4"; body builder addsanthropic_version+ dropsmodel; streaming URL suffix changesforge-core/llm/providers/anthropic_eventstream.go(new)forge-core/llm/providers/anthropic.goChatStreamchooses parser based on Content-Typeforge-core/llm/providers/openai.godocs/security/authentication.mdauth_scheme: aws_sigv4does on each providerdocs/reference/forge-yaml-schema.mdprovider: anthropic+auth_scheme: aws_sigv4CHANGELOG.mdTest plan
https://bedrock-runtime.us-east-1.amazonaws.com+ model idanthropic.claude-sonnet-4-20250514-v1:0→ request lands at/model/anthropic.claude-sonnet-4-20250514-v1%3A0/invoke.anthropic_version: bedrock-2023-05-31, NOmodelfield.Out of scope
AWS_*env vars only.Related
forge-core/auth/providers/aws_sigv4inbound provider; same hand-rolled posture this Phase 2.5 work continues