Преглед изворни кода

fix(ai): preserve Gemini tool finish semantics (#40546)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
opencode-agent[bot] пре 1 недеља
родитељ
комит
ad9a95f6ee
2 измењених фајлова са 29 додато и 0 уклоњено
  1. 1 0
      packages/ai/src/protocols/gemini.ts
  2. 28 0
      packages/ai/test/provider/gemini.test.ts

+ 1 - 0
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 (

+ 28 - 0
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({