fix(llm): restore OpenAI reasoning streams (#28552)

This commit is contained in:
Kit Langton
2026-05-20 21:02:59 -04:00
committed by GitHub
parent 93131b6e4c
commit 16fb6dac8d
9 changed files with 172 additions and 15 deletions
@@ -83,6 +83,7 @@ describeRecordedGoldenScenarios([
tags: ["flagship"],
scenarios: [
{ id: "text", temperature: false },
{ id: "reasoning", temperature: false },
{ id: "tool-call", temperature: false },
{ id: "tool-loop", temperature: false },
],
@@ -260,6 +260,32 @@ describe("OpenAI Chat route", () => {
}),
)
it.effect("parses OpenAI-compatible reasoning content deltas", () =>
Effect.gen(function* () {
const body = sseEvents(
{ choices: [{ delta: { reasoning_content: "thinking" } }] },
{ choices: [{ delta: { content: "Hello" } }] },
{ choices: [{ delta: {}, finish_reason: "stop" }] },
)
const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body)))
expect(response.reasoning).toBe("thinking")
expect(response.text).toBe("Hello")
expect(response.events).toMatchObject([
{ type: "step-start", index: 0 },
{ type: "reasoning-start", id: "reasoning-0" },
{ type: "reasoning-delta", id: "reasoning-0", text: "thinking" },
{ type: "text-start", id: "text-0" },
{ type: "text-delta", id: "text-0", text: "Hello" },
{ type: "reasoning-end", id: "reasoning-0" },
{ type: "text-end", id: "text-0" },
{ type: "step-finish", index: 0, reason: "stop" },
{ type: "finish", reason: "stop" },
])
}),
)
it.effect("assembles streamed tool call input", () =>
Effect.gen(function* () {
const body = sseEvents(
@@ -118,6 +118,7 @@ describe("OpenAI Responses route", () => {
it.effect("fails immediately when WebSocket is already closed", () =>
Effect.gen(function* () {
const error = yield* WebSocketExecutor.fromWebSocket(
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- fromWebSocket reads readyState before touching WebSocket methods on this branch.
{ readyState: globalThis.WebSocket.CLOSED } as globalThis.WebSocket,
{ url: "wss://api.openai.test/v1/responses", headers: Headers.empty },
).pipe(Effect.flip)
@@ -352,6 +353,33 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("parses reasoning summary stream fixtures", () =>
Effect.gen(function* () {
const body = sseEvents(
{ type: "response.reasoning_summary_text.delta", item_id: "rs_1", delta: "thinking" },
{ type: "response.output_text.delta", item_id: "msg_1", delta: "Hello" },
{ type: "response.reasoning_summary_text.done", item_id: "rs_1" },
{ type: "response.completed", response: { id: "resp_1" } },
)
const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body)))
expect(response.reasoning).toBe("thinking")
expect(response.text).toBe("Hello")
expect(response.events).toMatchObject([
{ type: "step-start", index: 0 },
{ type: "reasoning-start", id: "rs_1" },
{ type: "reasoning-delta", id: "rs_1", text: "thinking" },
{ type: "text-start", id: "msg_1" },
{ type: "text-delta", id: "msg_1", text: "Hello" },
{ type: "reasoning-end", id: "rs_1" },
{ type: "text-end", id: "msg_1" },
{ type: "step-finish", index: 0, reason: "stop" },
{ type: "finish", reason: "stop" },
])
}),
)
it.effect("assembles streamed function call input", () =>
Effect.gen(function* () {
const body = sseEvents(