diff --git a/packages/ai/src/protocols/gemini.ts b/packages/ai/src/protocols/gemini.ts index ecaf814b04f..2e20324c6e2 100644 --- a/packages/ai/src/protocols/gemini.ts +++ b/packages/ai/src/protocols/gemini.ts @@ -444,6 +444,7 @@ const mapUsage = (usage: GeminiUsage | undefined) => { } const mapFinishReason = (finishReason: string | undefined, hasToolCalls: boolean): FinishReason => { + if (finishReason === undefined) return hasToolCalls ? "tool-calls" : "unknown" if (finishReason === "STOP") return hasToolCalls ? "tool-calls" : "stop" if (finishReason === "MAX_TOKENS") return "length" if ( diff --git a/packages/ai/test/provider/gemini.test.ts b/packages/ai/test/provider/gemini.test.ts index 239b53f6cbd..9bc388f495f 100644 --- a/packages/ai/test/provider/gemini.test.ts +++ b/packages/ai/test/provider/gemini.test.ts @@ -601,6 +601,34 @@ describe("Gemini route", () => { }), ) + it.effect("maps tool calls without a finish reason", () => + Effect.gen(function* () { + const response = yield* LLMClient.generate( + LLMRequest.update(request, { + tools: [ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })], + }), + ).pipe( + Effect.provide( + fixedResponse( + sseEvents({ + candidates: [ + { + content: { + role: "model", + parts: [{ functionCall: { name: "lookup", args: { query: "weather" } } }], + }, + }, + ], + usageMetadata: { promptTokenCount: 5, candidatesTokenCount: 1 }, + }), + ), + ), + ) + + expect(response.finishReason).toEqual({ normalized: "tool-calls", raw: undefined }) + }), + ) + it.effect("assigns unique ids to multiple streamed tool calls", () => Effect.gen(function* () { const body = sseEvents({