Skip to content

[Transformer] add Bedrock transformer#228

Merged
Arshardh merged 6 commits into
wso2:mainfrom
Aakashwije:bedrock-transformer
Jul 15, 2026
Merged

[Transformer] add Bedrock transformer#228
Arshardh merged 6 commits into
wso2:mainfrom
Aakashwije:bedrock-transformer

Conversation

@Aakashwije

Copy link
Copy Markdown
Contributor

Purpose

Add AWS Bedrock as a supported target for OpenAI-compatible LLM proxies. Clients should be able to send OpenAI Chat Completions requests while the gateway communicates with AWS Bedrock using the Converse API.

This also enables AWS Bedrock to participate in the existing multi-provider routing flow.

No linked issue.

Goals

  • Introduce the openai-to-bedrock-transformer policy.
  • Translate OpenAI Chat Completions requests into AWS Bedrock Converse requests.
  • Translate Bedrock responses into OpenAI-compatible responses.
  • Support both non-streaming and streaming responses.
  • Support single-provider and multi-provider proxy configurations.
  • Support system prompts, multimodal input, tool calls, tool results, inference settings, usage information, and error responses.

Approach

The policy implements request, response-header, buffered-response, and streaming-response processing.

The implementation:

  • Rewrites requests to /model/{modelId}/converse or /model/{modelId}/converse-stream.
  • Converts OpenAI messages into Bedrock Converse message and content-block structures.
  • Maps system and developer messages to Bedrock system blocks.
  • Maps OpenAI tool definitions, tool choices, tool calls, and tool results to their Bedrock equivalents.
  • Converts supported base64 image_url content into Bedrock image blocks.
  • Maps OpenAI inference settings to Bedrock inferenceConfig.
  • Converts non-streaming Converse responses into OpenAI ChatCompletion responses.
  • Decodes binary Amazon event-stream frames and emits OpenAI-compatible Server-Sent Events.
  • Uses selected_provider metadata to participate in multi-provider routing.
  • Runs unconditionally when no provider was selected, supporting single-provider proxies.

This change does not affect the UI.

User stories

  • As an OpenAI API client, I can use an AWS Bedrock model without changing my request format.
  • As a gateway administrator, I can expose AWS Bedrock through an OpenAI-compatible endpoint.
  • As a gateway administrator, I can include AWS Bedrock in a multi-provider LLM proxy.
  • As a client, I can receive both non-streaming and streaming responses in OpenAI-compatible formats.
  • As a client, I can use supported tool calling and multimodal inputs with Bedrock models.

Release note

Added an OpenAI-to-AWS-Bedrock transformer policy that converts OpenAI Chat Completions traffic to the AWS Bedrock Converse API, including support for streaming responses, tool calling, multimodal input, and multi-provider routing.

Documentation

Documentation is included in this PR:

  • docs/openai-to-bedrock/v0.9/docs/openai-to-bedrock.md
  • docs/openai-to-bedrock/v0.9/metadata.json
  • Updated docs/README.md policy catalog

Training

N/A — no training-content changes are required for this policy addition.

Certification

N/A — this policy addition does not currently require changes to certification exams.

Marketing

N/A — no marketing-content changes are included in this PR.

Automation tests

  • Unit tests

    • Added tests for policy parameter validation.
    • Added tests for request translation and path rewriting.
    • Added tests for single-provider and multi-provider routing.
    • Added tests for system prompts, tools, images, and tool results.
    • Added tests for non-streaming response and error translation.
    • Added tests for Amazon event-stream decoding and OpenAI SSE generation.
    • Added malformed event-stream and split-frame boundary tests.
    • Statement coverage for the new policy: 71.8%.
    • go test -race ./... passed.
    • go vet ./... passed.
    • Existing OpenAI transformer regression suites passed.
  • Integration tests

    • No new end-to-end integration tests are included in this PR.
    • Request, response, routing, and streaming behavior are covered through unit tests using representative OpenAI, Bedrock Converse, and Amazon event-stream payloads.

Security checks

Samples

The policy documentation includes configuration examples for:

  • Attaching openai-to-bedrock-transformer to an additional provider in a multi-provider LLM proxy.
  • Attaching the transformer directly to a single-provider proxy.
  • Configuring Bedrock model or inference-profile IDs.
  • Configuring the fallback maximum-token value.

No credentials or functional API keys are included in the samples.

Related PRs

N/A.

This branch is based on multi-provider-transformers, which introduces the common multi-provider transformer functionality used by this policy.

Migrations (if applicable)

N/A — this is a new policy and does not change existing policy configurations or stored data.

Existing users can opt in by adding openai-to-bedrock-transformer to a single-provider proxy or to an AWS Bedrock entry under additionalProviders.

Test environment

  • Operating system: macOS
  • Go version: Go 1.26.2
  • SDK dependency: github.com/wso2/api-platform/sdk/core v0.2.14
  • JDK: N/A
  • Databases: N/A
  • Browsers: N/A
  • UI testing: N/A

Learning

The implementation was informed by:

  • The existing OpenAI transformer policies in this repository.
  • The AWS Bedrock provider and OpenAI-to-Bedrock development implementation under api-platform-main/gateway.
  • AWS Bedrock Converse request and response structures.
  • Amazon event-stream binary framing.
  • OpenAI Chat Completions response and streaming SSE structures.
  • The existing selected_provider multi-provider routing convention.

@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 478c24cf-6b46-4dae-8fde-0375dd849c92

📥 Commits

Reviewing files that changed from the base of the PR and between 613dc05 and 5d31dda.

📒 Files selected for processing (5)
  • docs/openai-to-bedrock-transformer/v0.9/docs/openai-to-bedrock-transformer.md
  • policies/openai-to-bedrock-transformer/bedrock_test.go
  • policies/openai-to-bedrock-transformer/openaitobedrock.go
  • policies/openai-to-bedrock-transformer/policy-definition.yaml
  • policies/openai-to-bedrock-transformer/request.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • policies/openai-to-bedrock-transformer/request.go

📝 Walkthrough

Summary

Adds the openai-to-bedrock-transformer policy for translating OpenAI Chat Completions requests to AWS Bedrock Converse requests and converting responses back to OpenAI-compatible JSON or SSE.

Highlights

  • Supports streaming and non-streaming responses, including Amazon event-stream decoding.
  • Handles system/developer prompts, multimodal base64 images, tool calls/results, inference settings, usage data, and error responses.
  • Supports model fallback from policy configuration or the request body.
  • Adds single-provider and multi-provider routing through providerId and selected_provider.
  • Includes policy definitions, documentation, catalog metadata, configuration examples, and comprehensive unit tests covering translation, routing, streaming, validation, and malformed frames.

Walkthrough

Adds the OpenAI to Bedrock Transformer policy. It converts Chat Completions requests into Bedrock Converse requests, including messages, tools, images, inference settings, and provider routing. Buffered Converse responses become OpenAI-compatible JSON, while Bedrock event streams become SSE with deltas, tool calls, usage, errors, and completion markers. The change also adds event-stream framing utilities, policy configuration, versioned documentation, metadata, module setup, and comprehensive tests.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant TransformerPolicy
  participant BedrockConverse
  Client->>TransformerPolicy: Send OpenAI Chat Completions request
  TransformerPolicy->>BedrockConverse: Send translated Converse request
  BedrockConverse-->>TransformerPolicy: Return JSON or event-stream response
  TransformerPolicy-->>Client: Return OpenAI JSON or SSE response
Loading

Suggested reviewers: pubudu538, malinthaprasan, tharindu1st, krishanx92

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly references the added Bedrock transformer.
Description check ✅ Passed The description follows the required template and covers the feature, implementation, tests, and rollout context.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Aakashwije Aakashwije changed the title Bedrock transformer [Transformer] add Bedrock transformer Jul 13, 2026
@Aakashwije
Aakashwije force-pushed the bedrock-transformer branch from 5ce7ee2 to bfbd36f Compare July 13, 2026 15:29
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (8)
policies/llm-header-router/llmheaderrouter_test.go (1)

27-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for request processing and idempotent publish behavior.

The test suite covers parseParams and selectProvider but does not test OnRequestHeaders, OnRequestBody, or publishSelection. The idempotent behavior — where an existing selected_provider is left untouched — is a documented feature (llmheaderrouter.go lines 146-149) and a key part of the cross-policy contract, but has no test coverage. Nil SharedContext early-return paths are also untested.

🧪 Suggested test cases
 func TestSelectProvider(t *testing.T) {
 	p := &RouterPolicy{params: mustParse(t)}
 
 	// Exact match.
 	if got, src := p.selectProvider("anthropic"); got != "anthropic-provider" || src != "header" {
 		t.Errorf("expected anthropic-provider/header, got %s/%s", got, src)
 	}
 	// Case-insensitive match.
 	if got, src := p.selectProvider("GEMINI"); got != "gemini-provider" || src != "header" {
 		t.Errorf("expected gemini-provider/header, got %s/%s", got, src)
 	}
 	// Unknown value falls back to default.
 	if got, src := p.selectProvider("unknown"); got != "openai-provider" || src != "default" {
 		t.Errorf("expected openai-provider/default, got %s/%s", got, src)
 	}
 	// Empty header falls back to default.
 	if got, src := p.selectProvider(""); got != "openai-provider" || src != "default" {
 		t.Errorf("expected openai-provider/default, got %s/%s", got, src)
 	}
 }
+
+func TestOnRequestHeaders_PublishesSelection(t *testing.T) {
+	p := &RouterPolicy{params: mustParse(t)}
+	meta := map[string]interface{}{}
+	reqCtx := &policy.RequestHeaderContext{
+		SharedContext: &policy.SharedContext{Metadata: meta},
+		Metadata:      meta,
+		Headers:       policy.NewHeaders(map[string][]string{"x-provider": {"anthropic"}}),
+	}
+	p.OnRequestHeaders(context.Background(), reqCtx, nil)
+	if got := meta[MetadataKeySelectedProvider]; got != "anthropic-provider" {
+		t.Fatalf("expected anthropic-provider, got %v", got)
+	}
+}
+
+func TestOnRequestBody_IdempotentPublish(t *testing.T) {
+	p := &RouterPolicy{params: mustParse(t)}
+	meta := map[string]interface{}{MetadataKeySelectedProvider: "existing-provider"}
+	reqCtx := &policy.RequestContext{
+		SharedContext: &policy.SharedContext{Metadata: meta},
+		Metadata:      meta,
+		Headers:       policy.NewHeaders(map[string][]string{"x-provider": {"anthropic"}}),
+	}
+	p.OnRequestBody(context.Background(), reqCtx, nil)
+	if got := meta[MetadataKeySelectedProvider]; got != "existing-provider" {
+		t.Fatalf("expected existing-provider to be preserved, got %v", got)
+	}
+}
+
+func TestOnRequestHeaders_NilSharedContext(t *testing.T) {
+	p := &RouterPolicy{params: mustParse(t)}
+	reqCtx := &policy.RequestHeaderContext{} // SharedContext is nil
+	action := p.OnRequestHeaders(context.Background(), reqCtx, nil)
+	if _, ok := action.(policy.UpstreamRequestHeaderModifications); !ok {
+		t.Fatalf("expected UpstreamRequestHeaderModifications, got %T", action)
+	}
+}

Note: The policy.NewHeaders constructor and exact RequestHeaderContext/RequestContext field names should be verified against the SDK v0.2.14 API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/llm-header-router/llmheaderrouter_test.go` around lines 27 - 107,
Add tests covering RouterPolicy.OnRequestHeaders, OnRequestBody, and
publishSelection, including nil SharedContext early returns and the idempotent
case where an existing selected_provider remains unchanged. Verify and use the
SDK v0.2.14 RequestHeaderContext, RequestContext, and policy.NewHeaders APIs,
while preserving assertions for provider selection and publication behavior.

Source: Learnings

policies/openai-to-azure-openai/openaitoazureopenai_test.go (1)

102-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a test for OnRequestBody with a non-matching provider.

TestShouldRun_RoutingGates verifies shouldRun directly, but no test confirms that OnRequestBody returns a no-op UpstreamRequestModifications{} when the selected provider doesn't match. Adding this would close the loop between the routing gate and the request body handler.

💡 Suggested test
+func TestOnRequestBody_NonMatchingProviderSkips(t *testing.T) {
+	p := &TranslatorPolicy{params: PolicyParams{
+		APIVersion: "2024-02-15-preview",
+		Model:      "gpt-4o",
+		PathSuffix: DefaultPathSuffix,
+		Id:         "azure-openai-provider",
+	}}
+	action := p.OnRequestBody(context.Background(),
+		newReqCtx(`{"messages":[]}`, map[string]interface{}{"selected_provider": "gemini-provider"}), nil)
+
+	mods, ok := action.(policy.UpstreamRequestModifications)
+	if !ok {
+		t.Fatalf("expected UpstreamRequestModifications, got %T", action)
+	}
+	if mods.Path != nil {
+		t.Errorf("expected no path rewrite for non-matching provider, got %v", mods.Path)
+	}
+	if mods.UpstreamName != nil {
+		t.Errorf("expected no upstream name for non-matching provider, got %v", mods.UpstreamName)
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/openai-to-azure-openai/openaitoazureopenai_test.go` around lines 102
- 122, Add a test alongside TestOnRequestBody_RewritesPathAndSetsUpstream that
configures a non-matching provider, invokes OnRequestBody, and verifies it
returns an empty policy.UpstreamRequestModifications with no path or upstream
name set. Reuse the existing request context and policy setup patterns while
preserving the current matching-provider assertions.
policies/openai-to-anthropic/openaitoanthropic.go (1)

402-411: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Non-string assistant content falls back to raw passthrough.

When an assistant message has no tool_calls and non-string content (e.g. a content-parts array), the function returns message["content"] unconverted instead of mapping it into Anthropic content blocks like convertUserContent does. Low-impact edge case, but worth normalizing for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/openai-to-anthropic/openaitoanthropic.go` around lines 402 - 411,
Update convertAssistantContent so assistant messages without tool_calls
normalize non-string content through the same content-block conversion used by
convertUserContent, while preserving the existing direct return for non-empty
extracted text and raw passthrough only where appropriate.
policies/openai-to-anthropic/openaitoanthropic_test.go (1)

1-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the routing gate and remaining translation paths.

This file doesn't test shouldRun/shouldRunResponse (the routing-gate logic flagged separately in openaitoanthropic.go), nor translateErrorResponse, the tool_usetool_calls response mapping, convertImage, or convertToolChoice. A routing-gate test analogous to the Mistral policy's TestShouldRun_RoutingGates would likely have caught the metadata-source concern flagged in openaitoanthropic.go.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/openai-to-anthropic/openaitoanthropic_test.go` around lines 1 - 207,
Add tests in the existing openaitoanthropic test suite covering shouldRun and
shouldRunResponse routing gates, following the analogous Mistral
TestShouldRun_RoutingGates pattern; also cover translateErrorResponse,
tool_use-to-tool_calls response mapping in translateResponse, convertImage, and
convertToolChoice. Reuse the visible policy types and translation helpers,
asserting both successful conversions and relevant routing/error edge cases.
policies/openai-to-mistral/openaitomistral.go (1)

180-193: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

shared parameter in selectedProviderFromMetadata is unused for reads.

The function accepts shared *policy.SharedContext but only nil-checks it — all metadata access goes through the separate metadata parameter. If RequestContext/ResponseContext embed *SharedContext (making .Metadata resolve to SharedContext.Metadata), the shared parameter is redundant. Consider removing it and relying solely on metadata, or if shared is needed for future use, document why.

♻️ Proposed refactor
-func selectedProviderFromMetadata(shared *policy.SharedContext, metadata map[string]interface{}) string {
-	if shared == nil || metadata == nil {
+func selectedProviderFromMetadata(metadata map[string]interface{}) string {
+	if metadata == nil {
 		return ""
 	}
 	raw, ok := metadata[MetadataKeySelectedProvider]

And update call sites:

 func (p *TranslatorPolicy) shouldRun(reqCtx *policy.RequestContext) bool {
-	return p.shouldRunForSelected(selectedProviderFromMetadata(reqCtx.SharedContext, reqCtx.Metadata))
+	return p.shouldRunForSelected(selectedProviderFromMetadata(reqCtx.Metadata))
 }

 func (p *TranslatorPolicy) shouldRunResponse(respCtx *policy.ResponseContext) bool {
-	return p.shouldRunForSelected(selectedProviderFromMetadata(respCtx.SharedContext, respCtx.Metadata))
+	return p.shouldRunForSelected(selectedProviderFromMetadata(respCtx.Metadata))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/openai-to-mistral/openaitomistral.go` around lines 180 - 193, Remove
the unused shared parameter and its nil check from selectedProviderFromMetadata,
making the function depend solely on metadata while preserving the existing
missing, non-string, and whitespace-trimming behavior. Update every
selectedProviderFromMetadata call site to pass only the metadata argument.
policies/openai-to-mistral/openaitomistral_test.go (2)

38-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test for UpstreamName when Id is configured.

TestOnRequestBody_PinsModelAndStripsUnsupportedFields constructs TranslatorPolicy without Id, so mods.UpstreamName is never asserted. The multi-provider routing path (lines 125-128 in openaitomistral.go) is untested at the modification level.

💚 Suggested test addition
func TestOnRequestBody_SetsUpstreamNameWhenIdConfigured(t *testing.T) {
	p := &TranslatorPolicy{params: PolicyParams{Model: "mistral-large-latest", Id: "mistral-provider"}}
	reqBody := `{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}`
	reqCtx := &policy.RequestContext{
		SharedContext: &policy.SharedContext{Metadata: map[string]interface{}{}},
		Body:          &policy.Body{Present: true, Content: []byte(reqBody)},
	}

	action := p.OnRequestBody(context.Background(), reqCtx, nil)
	mods, ok := action.(policy.UpstreamRequestModifications)
	if !ok {
		t.Fatalf("expected UpstreamRequestModifications, got %T", action)
	}
	if mods.UpstreamName == nil || *mods.UpstreamName != "mistral-provider" {
		t.Fatalf("expected UpstreamName 'mistral-provider', got %v", mods.UpstreamName)
	}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/openai-to-mistral/openaitomistral_test.go` around lines 38 - 82, Add
a focused test alongside TestOnRequestBody_PinsModelAndStripsUnsupportedFields
that configures TranslatorPolicy.params.Id, invokes OnRequestBody, and asserts
the returned policy.UpstreamRequestModifications has a non-nil UpstreamName
matching the configured provider ID. Reuse the existing request context and type
assertion patterns.

155-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for translateErrorResponse.

TestTranslateResponse_JSONShape only covers the success path. Error envelope translation (translateErrorResponse in response.go) — including both flat {"message":"..."} and nested {"error":{"message":"..."}} Mistral shapes, status-to-type mapping, and raw-body fallback — is untested.

💚 Suggested test additions
func TestTranslateResponse_ErrorNestedShape(t *testing.T) {
	mistralErr := `{"error":{"type":"invalid_request_error","message":"bad request","code":"400"}}`
	action := translateResponse([]byte(mistralErr), 400, "mistral-large-latest")
	mods, ok := action.(policy.DownstreamResponseModifications)
	if !ok {
		t.Fatalf("expected DownstreamResponseModifications, got %T", action)
	}
	var out map[string]interface{}
	if err := json.Unmarshal(mods.Body, &out); err != nil {
		t.Fatalf("translated body not JSON: %v", err)
	}
	errObj, _ := out["error"].(map[string]interface{})
	if errObj == nil || errObj["message"] != "bad request" {
		t.Errorf("expected nested error message 'bad request', got %v", out)
	}
}

func TestTranslateResponse_ErrorFlatShape(t *testing.T) {
	mistralErr := `{"message":"unauthorized","type":"authentication_error","code":"401"}`
	action := translateResponse([]byte(mistralErr), 401, "mistral-large-latest")
	mods, ok := action.(policy.DownstreamResponseModifications)
	if !ok {
		t.Fatalf("expected DownstreamResponseModifications, got %T", action)
	}
	var out map[string]interface{}
	if err := json.Unmarshal(mods.Body, &out); err != nil {
		t.Fatalf("translated body not JSON: %v", err)
	}
	errObj, _ := out["error"].(map[string]interface{})
	if errObj == nil || errObj["message"] != "unauthorized" {
		t.Errorf("expected flat error message 'unauthorized', got %v", out)
	}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/openai-to-mistral/openaitomistral_test.go` around lines 155 - 174,
Add tests covering translateErrorResponse through translateResponse for nested
and flat Mistral error envelopes, asserting the result is
DownstreamResponseModifications with the expected nested error message. Also
cover status-to-type mapping and invalid or non-JSON input to verify the
raw-body fallback, reusing existing response translation helpers and test
conventions.
policies/openai-to-bedrock/openaitobedrock.go (1)

335-344: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant nil guard on headers.

policy.Headers.Get() is nil-safe, so the if headers == nil check here is unnecessary.

♻️ Optional simplification
 func headerValue(headers *policy.Headers, name string) string {
-	if headers == nil {
-		return ""
-	}
 	values := headers.Get(name)

Based on learnings, "the Get() and Iterate() methods perform built-in nil checks and return safely (nil/empty) when called on a nil receiver."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policies/openai-to-bedrock/openaitobedrock.go` around lines 335 - 344, Remove
the redundant nil check from headerValue and rely on policy.Headers.Get’s
nil-safe behavior. Preserve the existing empty-string result when Get returns no
values, including for a nil headers receiver.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@policies/openai-to-anthropic/response.go`:
- Around line 221-235: Update stopReasonToFinish to handle Anthropic refusal and
pause_turn stop reasons explicitly, mapping both to the appropriate distinct
OpenAI-compatible finish reason rather than openaiFinishStop. Preserve existing
tool-call, max-token, tool-use, end-turn, stop-sequence, and default mappings.

In `@policies/openai-to-bedrock/openaitobedrock.go`:
- Line 93: Update all call sites of selectedProvider, including the guard in the
request flow, to validate reqCtx.SharedContext before accessing its promoted
Metadata field. Ensure nil SharedContext is handled without panicking, either by
guarding before dereference or by passing the context into selectedProvider and
checking it there.

In `@policies/openai-to-bedrock/response.go`:
- Around line 338-354: Update stopReasonToFinish so known Bedrock stop reasons
are evaluated before applying the hasToolCalls fallback. Preserve mappings for
max_tokens and content_filtered/guardrail_intervened, returning tool_calls from
hasToolCalls only when the stop reason does not have a more specific mapping,
keeping streaming and non-streaming behavior consistent.

In `@policies/openai-to-gemini/openaitogemini.go`:
- Around line 376-397: The content-part loop in the []interface{} branch must
not panic on missing or non-string type values. Update the switch in the content
conversion logic to use a guarded two-value string assertion for m["type"],
skipping the item when the assertion fails, while preserving the existing text
and image_url handling.
- Around line 308-341: Update the message conversion loop and related state so
assistant tool calls record each tool_call_id-to-function-name mapping; in the
"tool" case, use the mapped function name for functionResponse.name and forward
the original tool_call_id in the separate identifier field expected by Gemini.
Preserve existing tool response text handling and support matching results from
preceding assistant turns.

---

Nitpick comments:
In `@policies/llm-header-router/llmheaderrouter_test.go`:
- Around line 27-107: Add tests covering RouterPolicy.OnRequestHeaders,
OnRequestBody, and publishSelection, including nil SharedContext early returns
and the idempotent case where an existing selected_provider remains unchanged.
Verify and use the SDK v0.2.14 RequestHeaderContext, RequestContext, and
policy.NewHeaders APIs, while preserving assertions for provider selection and
publication behavior.

In `@policies/openai-to-anthropic/openaitoanthropic_test.go`:
- Around line 1-207: Add tests in the existing openaitoanthropic test suite
covering shouldRun and shouldRunResponse routing gates, following the analogous
Mistral TestShouldRun_RoutingGates pattern; also cover translateErrorResponse,
tool_use-to-tool_calls response mapping in translateResponse, convertImage, and
convertToolChoice. Reuse the visible policy types and translation helpers,
asserting both successful conversions and relevant routing/error edge cases.

In `@policies/openai-to-anthropic/openaitoanthropic.go`:
- Around line 402-411: Update convertAssistantContent so assistant messages
without tool_calls normalize non-string content through the same content-block
conversion used by convertUserContent, while preserving the existing direct
return for non-empty extracted text and raw passthrough only where appropriate.

In `@policies/openai-to-azure-openai/openaitoazureopenai_test.go`:
- Around line 102-122: Add a test alongside
TestOnRequestBody_RewritesPathAndSetsUpstream that configures a non-matching
provider, invokes OnRequestBody, and verifies it returns an empty
policy.UpstreamRequestModifications with no path or upstream name set. Reuse the
existing request context and policy setup patterns while preserving the current
matching-provider assertions.

In `@policies/openai-to-bedrock/openaitobedrock.go`:
- Around line 335-344: Remove the redundant nil check from headerValue and rely
on policy.Headers.Get’s nil-safe behavior. Preserve the existing empty-string
result when Get returns no values, including for a nil headers receiver.

In `@policies/openai-to-mistral/openaitomistral_test.go`:
- Around line 38-82: Add a focused test alongside
TestOnRequestBody_PinsModelAndStripsUnsupportedFields that configures
TranslatorPolicy.params.Id, invokes OnRequestBody, and asserts the returned
policy.UpstreamRequestModifications has a non-nil UpstreamName matching the
configured provider ID. Reuse the existing request context and type assertion
patterns.
- Around line 155-174: Add tests covering translateErrorResponse through
translateResponse for nested and flat Mistral error envelopes, asserting the
result is DownstreamResponseModifications with the expected nested error
message. Also cover status-to-type mapping and invalid or non-JSON input to
verify the raw-body fallback, reusing existing response translation helpers and
test conventions.

In `@policies/openai-to-mistral/openaitomistral.go`:
- Around line 180-193: Remove the unused shared parameter and its nil check from
selectedProviderFromMetadata, making the function depend solely on metadata
while preserving the existing missing, non-string, and whitespace-trimming
behavior. Update every selectedProviderFromMetadata call site to pass only the
metadata argument.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ff45764-89fb-4d19-8c57-1913d5815377

📥 Commits

Reviewing files that changed from the base of the PR and between c60c63f and 5ce7ee2.

⛔ Files ignored due to path filters (6)
  • policies/llm-header-router/go.sum is excluded by !**/*.sum
  • policies/openai-to-anthropic/go.sum is excluded by !**/*.sum
  • policies/openai-to-azure-openai/go.sum is excluded by !**/*.sum
  • policies/openai-to-bedrock/go.sum is excluded by !**/*.sum
  • policies/openai-to-gemini/go.sum is excluded by !**/*.sum
  • policies/openai-to-mistral/go.sum is excluded by !**/*.sum
📒 Files selected for processing (43)
  • docs/README.md
  • docs/llm-header-router/v0.9/docs/llm-header-router.md
  • docs/llm-header-router/v0.9/metadata.json
  • docs/openai-to-anthropic/v0.9/docs/openai-to-anthropic.md
  • docs/openai-to-anthropic/v0.9/metadata.json
  • docs/openai-to-azure-openai/v0.9/docs/openai-to-azure-openai.md
  • docs/openai-to-azure-openai/v0.9/metadata.json
  • docs/openai-to-bedrock/v0.9/docs/openai-to-bedrock.md
  • docs/openai-to-bedrock/v0.9/metadata.json
  • docs/openai-to-gemini/v0.9/docs/openai-to-gemini.md
  • docs/openai-to-gemini/v0.9/metadata.json
  • docs/openai-to-mistral/v0.9/docs/openai-to-mistral.md
  • docs/openai-to-mistral/v0.9/metadata.json
  • policies/llm-header-router/go.mod
  • policies/llm-header-router/llmheaderrouter.go
  • policies/llm-header-router/llmheaderrouter_test.go
  • policies/llm-header-router/policy-definition.yaml
  • policies/openai-to-anthropic/go.mod
  • policies/openai-to-anthropic/openaitoanthropic.go
  • policies/openai-to-anthropic/openaitoanthropic_test.go
  • policies/openai-to-anthropic/policy-definition.yaml
  • policies/openai-to-anthropic/response.go
  • policies/openai-to-azure-openai/go.mod
  • policies/openai-to-azure-openai/openaitoazureopenai.go
  • policies/openai-to-azure-openai/openaitoazureopenai_test.go
  • policies/openai-to-azure-openai/policy-definition.yaml
  • policies/openai-to-bedrock/bedrock_test.go
  • policies/openai-to-bedrock/eventstream.go
  • policies/openai-to-bedrock/go.mod
  • policies/openai-to-bedrock/openaitobedrock.go
  • policies/openai-to-bedrock/policy-definition.yaml
  • policies/openai-to-bedrock/request.go
  • policies/openai-to-bedrock/response.go
  • policies/openai-to-gemini/go.mod
  • policies/openai-to-gemini/openaitogemini.go
  • policies/openai-to-gemini/openaitogemini_test.go
  • policies/openai-to-gemini/policy-definition.yaml
  • policies/openai-to-gemini/response.go
  • policies/openai-to-mistral/go.mod
  • policies/openai-to-mistral/openaitomistral.go
  • policies/openai-to-mistral/openaitomistral_test.go
  • policies/openai-to-mistral/policy-definition.yaml
  • policies/openai-to-mistral/response.go

Comment thread policies/openai-to-anthropic/response.go Outdated
Comment thread policies/openai-to-bedrock/openaitobedrock.go Outdated
Comment thread policies/openai-to-bedrock-transformer/response.go
Comment thread policies/openai-to-gemini/openaitogemini.go Outdated
Comment thread policies/openai-to-gemini/openaitogemini.go Outdated
@Aakashwije
Aakashwije force-pushed the bedrock-transformer branch from bfbd36f to 3adc0cc Compare July 13, 2026 16:19
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

Comment thread docs/openai-to-bedrock-transformer/v0.9/docs/openai-to-bedrock-transformer.md Outdated
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@policies/openai-to-bedrock-transformer/eventstream.go`:
- Around line 169-186: Update the header parsing loop around valueType in the
event-stream decoder to skip non-string headers rather than breaking. Advance
offset according to each AWS event-stream header type’s defined encoding length,
including fixed-width numeric, boolean, byte, and variable-length byte/string
types, while retaining bounds checks; continue parsing until :event-type is
reached or malformed data is detected.

In `@policies/openai-to-bedrock-transformer/response.go`:
- Around line 169-184: Update eventStreamToSSE so that when
decodeEventStreamFrames fails, it preserves and returns the original data
instead of leaving out empty; retain framed conversion and endOfStream
sseDonePayload behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc72b2c1-ea88-40d7-8b32-f9d7dc289482

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce7ee2 and 0a47c8b.

⛔ Files ignored due to path filters (1)
  • policies/openai-to-bedrock-transformer/go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • docs/README.md
  • docs/openai-to-bedrock-transformer/v0.9/docs/openai-to-bedrock-transformer.md
  • docs/openai-to-bedrock-transformer/v0.9/metadata.json
  • policies/openai-to-bedrock-transformer/bedrock_test.go
  • policies/openai-to-bedrock-transformer/eventstream.go
  • policies/openai-to-bedrock-transformer/go.mod
  • policies/openai-to-bedrock-transformer/openaitobedrock.go
  • policies/openai-to-bedrock-transformer/policy-definition.yaml
  • policies/openai-to-bedrock-transformer/request.go
  • policies/openai-to-bedrock-transformer/response.go

Comment thread policies/openai-to-bedrock-transformer/eventstream.go
Comment thread policies/openai-to-bedrock-transformer/response.go
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@Aakashwije
Aakashwije requested a review from tharindu1st July 14, 2026 14:25
Comment thread policies/openai-to-bedrock-transformer/policy-definition.yaml Outdated
Comment thread policies/openai-to-bedrock-transformer/policy-definition.yaml Outdated
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: github.com/wso2/api-platform/sdk/core
Version: v0.2.14
Allowed range: >=v0.2.4
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@Arshardh
Arshardh merged commit 827064c into wso2:main Jul 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants