diff --git a/packages/ai/src/protocols/openai-chat.ts b/packages/ai/src/protocols/openai-chat.ts index 923c19e641e..511a04a6135 100644 --- a/packages/ai/src/protocols/openai-chat.ts +++ b/packages/ai/src/protocols/openai-chat.ts @@ -526,6 +526,8 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request: ) continue } + if (message.role === "assistant" && message.content.every((part) => part.type === "text" && part.text.trim() === "")) + continue if (message.role === "tool") { const lowered = yield* lowerToolMessages(message, options) messages.push(...lowered.messages) diff --git a/packages/ai/test/provider/openai-chat.test.ts b/packages/ai/test/provider/openai-chat.test.ts index b546dc12a47..ed750380a78 100644 --- a/packages/ai/test/provider/openai-chat.test.ts +++ b/packages/ai/test/provider/openai-chat.test.ts @@ -85,6 +85,28 @@ describe("OpenAI Chat route", () => { }), ) + it.effect("omits empty and whitespace-only assistant messages", () => + Effect.gen(function* () { + const prepared = yield* compileRequest( + LLM.request({ + model, + messages: [ + Message.user("Before."), + Message.assistant([]), + Message.assistant(""), + Message.assistant(" \n\t "), + Message.assistant("After."), + ], + }), + ) + + expect(prepared.body.messages).toEqual([ + { role: "user", content: "Before." }, + { role: "assistant", content: "After." }, + ]) + }), + ) + it.effect("replays canonical reasoning as OpenAI-compatible reasoning_content", () => Effect.gen(function* () { const prepared = yield* compileRequest(