diff --git a/packages/ai/src/protocols/gemini.ts b/packages/ai/src/protocols/gemini.ts index 3f22a5787e36..b8c1b4b95cd7 100644 --- a/packages/ai/src/protocols/gemini.ts +++ b/packages/ai/src/protocols/gemini.ts @@ -289,7 +289,9 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request: LLMR if (message.role === "system") { const part = yield* ProviderShared.wrappedSystemUpdate("Gemini", message) const previous = contents.at(-1) - if (previous?.role === "user") + // Gemini rejects a continuation whose function-response turn carries extra + // parts, so an update after a tool result starts its own user turn. + if (previous?.role === "user" && !previous.parts.some((item) => "functionResponse" in item)) contents[contents.length - 1] = { role: "user", parts: [...previous.parts, { text: part.text }] } else contents.push({ role: "user", parts: [{ text: part.text }] }) continue diff --git a/packages/ai/test/provider/gemini.test.ts b/packages/ai/test/provider/gemini.test.ts index 60d536736dc0..c1579af7595d 100644 --- a/packages/ai/test/provider/gemini.test.ts +++ b/packages/ai/test/provider/gemini.test.ts @@ -139,6 +139,48 @@ describe("Gemini route", () => { }), ) + it.effect("keeps system updates separate from function responses", () => + Effect.gen(function* () { + const prepared = yield* compileRequest( + LLM.request({ + model, + messages: [ + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })]), + Message.tool({ id: "call_1", name: "lookup", result: "done", resultType: "text" }), + Message.system("Update."), + Message.system("Later update."), + ], + }), + ) + + expect(prepared.body.contents).toEqual([ + { + role: "model", + parts: [{ functionCall: { id: undefined, name: "lookup", args: { query: "weather" } } }], + }, + { + role: "user", + parts: [ + { + functionResponse: { + id: undefined, + name: "lookup", + response: { name: "lookup", content: "done" }, + }, + }, + ], + }, + { + role: "user", + parts: [ + { text: "\nUpdate.\n" }, + { text: "\nLater update.\n" }, + ], + }, + ]) + }), + ) + it.effect("prepares multimodal user input and tool history", () => Effect.gen(function* () { const prepared = yield* compileRequest(