From d80fe29d4bb400ed2d7a975d6416e0fe12f9fe81 Mon Sep 17 00:00:00 2001 From: robert-j-y <212159665+robert-j-y@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:26:20 -0700 Subject: [PATCH 1/2] fix: send null content for tool-only assistant messages Assistant messages that contain only tool calls (no text) were sending `content: ""` which breaks AWS Bedrock Nova with "The text field in the ContentBlock object is blank." Send `content: null` instead. Fixes SDK-443 Co-authored-by: Cursor --- ...onvert-to-openrouter-chat-messages.test.ts | 97 ++++++++++++++++++- .../convert-to-openrouter-chat-messages.ts | 2 +- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/chat/convert-to-openrouter-chat-messages.test.ts b/src/chat/convert-to-openrouter-chat-messages.test.ts index 862dde04..03ab4ccf 100644 --- a/src/chat/convert-to-openrouter-chat-messages.test.ts +++ b/src/chat/convert-to-openrouter-chat-messages.test.ts @@ -2831,6 +2831,101 @@ describe('tool messages', () => { }); }); +describe('tool-only assistant messages send null content (issue #443)', () => { + it('should send content: null for assistant messages with only tool calls', () => { + const result = convertToOpenRouterChatMessages([ + { + role: 'assistant', + content: [ + { + type: 'tool-call', + toolCallId: 'call-1', + toolName: 'get_weather', + input: { location: 'San Francisco' }, + }, + ], + }, + ]); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + role: 'assistant', + content: null, + tool_calls: [ + { + id: 'call-1', + type: 'function', + function: { + name: 'get_weather', + arguments: expect.any(String), + }, + }, + ], + }); + }); + + it('should send content: null for assistant messages with multiple tool calls and no text', () => { + const result = convertToOpenRouterChatMessages([ + { + role: 'assistant', + content: [ + { + type: 'tool-call', + toolCallId: 'call-1', + toolName: 'get_weather', + input: { location: 'SF' }, + }, + { + type: 'tool-call', + toolCallId: 'call-2', + toolName: 'get_time', + input: {}, + }, + ], + }, + ]); + + expect(result).toHaveLength(1); + expect(result[0]?.content).toBeNull(); + const msg = result[0] as { tool_calls?: unknown[] }; + expect(msg.tool_calls).toHaveLength(2); + }); + + it('should preserve text content when assistant message has both text and tool calls', () => { + const result = convertToOpenRouterChatMessages([ + { + role: 'assistant', + content: [ + { type: 'text', text: 'Let me check the weather.' }, + { + type: 'tool-call', + toolCallId: 'call-1', + toolName: 'get_weather', + input: { location: 'San Francisco' }, + }, + ], + }, + ]); + + expect(result).toHaveLength(1); + expect(result[0]?.content).toBe('Let me check the weather.'); + const msg = result[0] as { tool_calls?: unknown[] }; + expect(msg.tool_calls).toHaveLength(1); + }); + + it('should preserve text content for text-only assistant messages', () => { + const result = convertToOpenRouterChatMessages([ + { + role: 'assistant', + content: [{ type: 'text', text: 'Hello!' }], + }, + ]); + + expect(result).toHaveLength(1); + expect(result[0]?.content).toBe('Hello!'); + }); +}); + describe('deterministic tool call argument serialization', () => { it('should produce identical serialized arguments regardless of key insertion order', () => { // Simulate the same tool call arguments with different key insertion orders, @@ -2877,7 +2972,7 @@ describe('deterministic tool call argument serialization', () => { expect(resultA).toEqual([ { role: 'assistant', - content: '', + content: null, tool_calls: [ { id: 'call-1', diff --git a/src/chat/convert-to-openrouter-chat-messages.ts b/src/chat/convert-to-openrouter-chat-messages.ts index c1f50a59..3d1cbd8a 100644 --- a/src/chat/convert-to-openrouter-chat-messages.ts +++ b/src/chat/convert-to-openrouter-chat-messages.ts @@ -370,7 +370,7 @@ export function convertToOpenRouterChatMessages( messages.push({ role: 'assistant', - content: text, + content: text || null, tool_calls: toolCalls.length > 0 ? toolCalls : undefined, reasoning: effectiveReasoning, reasoning_details: finalReasoningDetails, From ce0d82c141ac5c7d2519a4266f340353a8a05d00 Mon Sep 17 00:00:00 2001 From: robert-j-y <212159665+robert-j-y@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:26:34 -0700 Subject: [PATCH 2/2] chore: add changeset for SDK-443 Co-authored-by: Cursor --- .changeset/tool-only-null-content.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tool-only-null-content.md diff --git a/.changeset/tool-only-null-content.md b/.changeset/tool-only-null-content.md new file mode 100644 index 00000000..764412cd --- /dev/null +++ b/.changeset/tool-only-null-content.md @@ -0,0 +1,5 @@ +--- +'@openrouter/ai-sdk-provider': patch +--- + +Send `content: null` instead of `content: ""` for assistant messages that contain only tool calls. Fixes AWS Bedrock Nova rejecting requests with "The text field in the ContentBlock object is blank."