diff --git a/packages/ai/src/protocols/open-responses-continuation.ts b/packages/ai/src/protocols/open-responses-continuation.ts index 38dd3e95a7f..63fa084207e 100644 --- a/packages/ai/src/protocols/open-responses-continuation.ts +++ b/packages/ai/src/protocols/open-responses-continuation.ts @@ -181,7 +181,7 @@ export const driver = (input: DriverInput): WebSocketChannelDriver => { responseID, request, // Completion can re-encrypt reasoning. Callers replay the item already emitted by output_item.done. - output: event.response?.output + output: event.response?.output?.length ? event.response.output.map((item) => item.type === "reasoning" && item.id !== undefined ? (output.find((done) => done.type === item.type && done.id === item.id) ?? item) diff --git a/packages/ai/test/provider/openai-responses.test.ts b/packages/ai/test/provider/openai-responses.test.ts index 9f6b7620f9d..7cefd28f483 100644 --- a/packages/ai/test/provider/openai-responses.test.ts +++ b/packages/ai/test/provider/openai-responses.test.ts @@ -581,52 +581,54 @@ describe("OpenAI Responses route", () => { ) it.effect("continues a streamed tool call with only the new tool output", () => - Effect.gen(function* () { - const firstRequest = { - type: "response.create", - model: "gpt-5.2", - store: false, - input: [{ role: "user", content: [{ type: "input_text", text: "Weather?" }] }], - } - const first = continuationDriver(firstRequest) - const firstCreate = yield* first.create(undefined) - yield* first.observe( - firstCreate, - ProviderShared.encodeJson({ - type: "response.output_item.done", - item: { - type: "function_call", - id: "fc_1", - status: "completed", - call_id: "call_1", - name: "weather", - arguments: '{ "city": "Paris" }', - }, - }), - ) - const saved = checkpoint( + Effect.forEach([undefined, []], (output) => + Effect.gen(function* () { + const firstRequest = { + type: "response.create", + model: "gpt-5.2", + store: false, + input: [{ role: "user", content: [{ type: "input_text", text: "Weather?" }] }], + } + const first = continuationDriver(firstRequest) + const firstCreate = yield* first.create(undefined) yield* first.observe( firstCreate, - ProviderShared.encodeJson({ type: "response.completed", response: { id: "resp_1" } }), - ), - ) - const second = continuationDriver({ - ...firstRequest, - input: [ - ...firstRequest.input, - { type: "function_call", call_id: "call_1", name: "weather", arguments: '{"city":"Paris"}' }, - { type: "function_call_output", call_id: "call_1", output: '{"temperature":22}' }, - ], - }) + ProviderShared.encodeJson({ + type: "response.output_item.done", + item: { + type: "function_call", + id: "fc_1", + status: "completed", + call_id: "call_1", + name: "weather", + arguments: '{ "city": "Paris" }', + }, + }), + ) + const saved = checkpoint( + yield* first.observe( + firstCreate, + ProviderShared.encodeJson({ type: "response.completed", response: { id: "resp_1", output } }), + ), + ) + const second = continuationDriver({ + ...firstRequest, + input: [ + ...firstRequest.input, + { type: "function_call", call_id: "call_1", name: "weather", arguments: '{"city":"Paris"}' }, + { type: "function_call_output", call_id: "call_1", output: '{"temperature":22}' }, + ], + }) - const create = yield* second.create(saved) + const create = yield* second.create(saved) - expect(create.mode).toBe("incremental") - expect(ProviderShared.decodeJson(create.message)).toMatchObject({ - previous_response_id: "resp_1", - input: [{ type: "function_call_output", call_id: "call_1", output: '{"temperature":22}' }], - }) - }), + expect(create.mode).toBe("incremental") + expect(ProviderShared.decodeJson(create.message)).toMatchObject({ + previous_response_id: "resp_1", + input: [{ type: "function_call_output", call_id: "call_1", output: '{"temperature":22}' }], + }) + }), + ), ) it.effect("continues a tool call from authoritative completed response output", () => @@ -680,45 +682,47 @@ describe("OpenAI Responses route", () => { ) it.effect("continues a promoted steer after assistant output with response-only text metadata", () => - Effect.gen(function* () { - const firstInput = [{ role: "user", content: [{ type: "input_text", text: "First" }] }] - const first = continuationDriver({ type: "response.create", model: "gpt-5.2", store: false, input: firstInput }) - const create = yield* first.create(undefined) - yield* first.observe( - create, - ProviderShared.encodeJson({ - type: "response.output_item.done", - item: { - type: "message", - id: "msg_1", - status: "completed", - role: "assistant", - content: [{ type: "output_text", text: "Hello", annotations: [], logprobs: [] }], - }, - }), - ) - const saved = checkpoint( + Effect.forEach([undefined, []], (output) => + Effect.gen(function* () { + const firstInput = [{ role: "user", content: [{ type: "input_text", text: "First" }] }] + const first = continuationDriver({ type: "response.create", model: "gpt-5.2", store: false, input: firstInput }) + const create = yield* first.create(undefined) yield* first.observe( create, - ProviderShared.encodeJson({ type: "response.completed", response: { id: "resp_1" } }), - ), - ) - const steer = { role: "user", content: [{ type: "input_text", text: "Actually, be brief" }] } - const next = continuationDriver({ - type: "response.create", - model: "gpt-5.2", - store: false, - input: [...firstInput, { role: "assistant", content: [{ type: "output_text", text: "Hello" }] }, steer], - }) + ProviderShared.encodeJson({ + type: "response.output_item.done", + item: { + type: "message", + id: "msg_1", + status: "completed", + role: "assistant", + content: [{ type: "output_text", text: "Hello", annotations: [], logprobs: [] }], + }, + }), + ) + const saved = checkpoint( + yield* first.observe( + create, + ProviderShared.encodeJson({ type: "response.completed", response: { id: "resp_1", output } }), + ), + ) + const steer = { role: "user", content: [{ type: "input_text", text: "Actually, be brief" }] } + const next = continuationDriver({ + type: "response.create", + model: "gpt-5.2", + store: false, + input: [...firstInput, { role: "assistant", content: [{ type: "output_text", text: "Hello" }] }, steer], + }) - const continued = yield* next.create(saved) + const continued = yield* next.create(saved) - expect(continued.mode).toBe("incremental") - expect(ProviderShared.decodeJson(continued.message)).toMatchObject({ - previous_response_id: "resp_1", - input: [steer], - }) - }), + expect(continued.mode).toBe("incremental") + expect(ProviderShared.decodeJson(continued.message)).toMatchObject({ + previous_response_id: "resp_1", + input: [steer], + }) + }), + ), ) it.effect("continues streamed reasoning when completion re-encrypts the same item", () =>