fix(ai): map unknown OpenAI Chat finish reasons to unknown (#58608)

This commit is contained in:
Aiden Cline 2026-10-23 23:30:32 -05:00 • committed by GitHub
parent 7d59b05429
commit dbd99a940b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 7 additions and 53 deletions

View file

@ -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
}
})

View file

@ -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<ReadonlyArray<LLMEvent>>([])
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" })
}),
)

View file

@ -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" })
}),
)