Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/opencode/src/provider/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export namespace ProviderTransform {
): ModelMessage[] {
// Anthropic rejects messages with empty content - filter out empty string messages
// and remove empty text/reasoning parts from array content
if (model.api.npm === "@ai-sdk/anthropic") {
if (model.api.npm === "@ai-sdk/anthropic" || model.api.npm === "@ai-sdk/amazon-bedrock") {
msgs = msgs
.map((msg) => {
if (typeof msg.content === "string") {
Expand Down
149 changes: 149 additions & 0 deletions packages/opencode/test/provider/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,155 @@ describe("ProviderTransform.message - anthropic empty content filtering", () =>
})
})

describe("ProviderTransform.message - bedrock empty content filtering", () => {
const bedrockModel = {
id: "amazon-bedrock/anthropic.claude-sonnet-4",
providerID: "amazon-bedrock",
api: {
id: "anthropic.claude-sonnet-4",
url: "https://bedrock-runtime.us-east-1.amazonaws.com",
npm: "@ai-sdk/amazon-bedrock",
},
name: "Claude Sonnet 4 (Bedrock)",
capabilities: {
temperature: true,
reasoning: false,
attachment: true,
toolcall: true,
input: { text: true, audio: false, image: true, video: false, pdf: true },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: {
input: 0.003,
output: 0.015,
cache: { read: 0.0003, write: 0.00375 },
},
limit: {
context: 200000,
output: 8192,
},
status: "active",
options: {},
headers: {},
} as any

test("filters out messages with empty string content", () => {
const msgs = [
{ role: "user", content: "Hello" },
{ role: "assistant", content: "" },
{ role: "user", content: "World" },
] as any[]

const result = ProviderTransform.message(msgs, bedrockModel, {})

expect(result).toHaveLength(2)
expect(result[0].content).toBe("Hello")
expect(result[1].content).toBe("World")
})

test("filters out empty text parts from array content", () => {
const msgs = [
{
role: "assistant",
content: [
{ type: "text", text: "" },
{ type: "text", text: "Hello" },
{ type: "text", text: "" },
],
},
] as any[]

const result = ProviderTransform.message(msgs, bedrockModel, {})

expect(result).toHaveLength(1)
expect(result[0].content).toHaveLength(1)
expect(result[0].content[0]).toEqual({ type: "text", text: "Hello" })
})

test("filters out empty reasoning parts from array content", () => {
const msgs = [
{
role: "assistant",
content: [
{ type: "reasoning", text: "" },
{ type: "text", text: "Answer" },
{ type: "reasoning", text: "" },
],
},
] as any[]

const result = ProviderTransform.message(msgs, bedrockModel, {})

expect(result).toHaveLength(1)
expect(result[0].content).toHaveLength(1)
expect(result[0].content[0]).toEqual({ type: "text", text: "Answer" })
})

test("removes entire message when all parts are empty", () => {
const msgs = [
{ role: "user", content: "Hello" },
{
role: "assistant",
content: [
{ type: "text", text: "" },
{ type: "reasoning", text: "" },
],
},
{ role: "user", content: "World" },
] as any[]

const result = ProviderTransform.message(msgs, bedrockModel, {})

expect(result).toHaveLength(2)
expect(result[0].content).toBe("Hello")
expect(result[1].content).toBe("World")
})

test("keeps non-text/reasoning parts even if text parts are empty", () => {
const msgs = [
{
role: "assistant",
content: [
{ type: "text", text: "" },
{ type: "tool-call", toolCallId: "123", toolName: "bash", input: { command: "ls" } },
],
},
] as any[]

const result = ProviderTransform.message(msgs, bedrockModel, {})

expect(result).toHaveLength(1)
expect(result[0].content).toHaveLength(1)
expect(result[0].content[0]).toEqual({
type: "tool-call",
toolCallId: "123",
toolName: "bash",
input: { command: "ls" },
})
})

test("keeps messages with valid text alongside empty parts", () => {
const msgs = [
{
role: "assistant",
content: [
{ type: "reasoning", text: "Thinking..." },
{ type: "text", text: "" },
{ type: "text", text: "Result" },
],
},
] as any[]

const result = ProviderTransform.message(msgs, bedrockModel, {})

expect(result).toHaveLength(1)
expect(result[0].content).toHaveLength(2)
expect(result[0].content[0]).toEqual({ type: "reasoning", text: "Thinking..." })
expect(result[0].content[1]).toEqual({ type: "text", text: "Result" })
})
})

describe("ProviderTransform.message - strip openai metadata when store=false", () => {
const openaiModel = {
id: "openai/gpt-5",
Expand Down
Loading