diff --git a/packages/ai/src/protocols/openai-chat.ts b/packages/ai/src/protocols/openai-chat.ts index b3ce1d3f66c9..d257581bca23 100644 --- a/packages/ai/src/protocols/openai-chat.ts +++ b/packages/ai/src/protocols/openai-chat.ts @@ -858,12 +858,7 @@ const mapFinishReason = Effect.fnUntraced(function* (event: OpenAIChatEvent, rea case "tool_calls": return "tool-calls" as const default: - return yield* new AIError({ - reason: new UnknownProviderError({ - message: `Provider finish_reason: ${reason}`, - body: ProviderShared.encodeJson(event), - }), - }) + return "unknown" as const } }) diff --git a/packages/ai/test/provider/openai-chat.test.ts b/packages/ai/test/provider/openai-chat.test.ts index 43d1ded6c6ac..c0c906e618e6 100644 --- a/packages/ai/test/provider/openai-chat.test.ts +++ b/packages/ai/test/provider/openai-chat.test.ts @@ -2072,47 +2072,14 @@ describe("OpenAI Chat route", () => { }), ) - it.effect("rejects unknown finish reasons without finalizing streamed tool calls", () => + it.effect("maps unknown finish reasons to unknown and keeps the raw value", () => Effect.gen(function* () { - const body = sseEvents( - deltaChunk({ - tool_calls: [{ index: 0, id: "call_1", function: { name: "lookup", arguments: '{"query":"weather"' } }], - }), - deltaChunk({}, "future_reason"), - ) - const events = yield* Ref.make>([]) - const error = yield* LLMClient.stream( - LLMRequest.update(request, { - tools: [ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })], - }), - ).pipe( - Stream.tap((event) => Ref.update(events, (current) => [...current, event])), - Stream.runDrain, - Effect.provide(fixedResponse(body)), - Effect.flip, + const response = yield* LLMClient.generate(request).pipe( + Effect.provide(fixedResponse(sseEvents(deltaChunk({ content: "Hello" }), deltaChunk({}, "future_reason")))), ) - expect(error).toMatchObject({ - reason: { _tag: "UnknownProvider" }, - message: "Provider finish_reason: future_reason", - }) - expect(yield* Ref.get(events)).toEqual([ - { type: "step-start", index: 0 }, - { - type: "tool-input-start", - id: "call_1", - name: "lookup", - providerExecuted: undefined, - providerMetadata: undefined, - }, - { - type: "tool-input-delta", - id: "call_1", - name: "lookup", - text: '{"query":"weather"', - input: { query: "weather" }, - }, - ]) + expect(response.text).toBe("Hello") + expect(response.finishReason).toEqual({ normalized: "unknown", raw: "future_reason" }) }), ) diff --git a/packages/ai/test/provider/openai-compatible-chat.test.ts b/packages/ai/test/provider/openai-compatible-chat.test.ts index 2f26ae52af16..368a8d5ed7f4 100644 --- a/packages/ai/test/provider/openai-compatible-chat.test.ts +++ b/packages/ai/test/provider/openai-compatible-chat.test.ts @@ -572,16 +572,8 @@ describe("OpenAI-compatible Chat route", () => { const unknown = yield* LLMClient.generate(request).pipe( Effect.provide(fixedResponse(sseEvents(deltaChunk({}, "future_reason")))), - Effect.flip, ) - expect(unknown).toMatchObject({ - reason: { _tag: "UnknownProvider" }, - message: "Provider finish_reason: future_reason", - }) - expect(decodeJson(unknown.reason.body ?? "")).toMatchObject({ - id: "chatcmpl_fixture", - choices: [{ finish_reason: "future_reason" }], - }) + expect(unknown.finishReason).toEqual({ normalized: "unknown", raw: "future_reason" }) }), )