diff --git a/lib/request/response-handler.ts b/lib/request/response-handler.ts index a36f89ae..8899f270 100644 --- a/lib/request/response-handler.ts +++ b/lib/request/response-handler.ts @@ -349,6 +349,34 @@ function notifyResponseId( } } +function truncateDiagnosticText(value: unknown, maxLength = 400): string | undefined { + if (typeof value !== "string") return undefined; + const trimmed = value.trim(); + if (trimmed.length === 0) return undefined; + return trimmed.length > maxLength ? `${trimmed.slice(0, maxLength)}...` : trimmed; +} + +function logStreamDiagnostics(finalResponse: unknown): void { + if (!LOGGING_ENABLED || !isRecord(finalResponse)) { + return; + } + + const responseId = extractResponseId(finalResponse); + const phase = getStringField(finalResponse, "phase"); + const commentaryText = truncateDiagnosticText(finalResponse.commentary_text); + const finalAnswerText = truncateDiagnosticText(finalResponse.final_answer_text); + const reasoningSummaryText = truncateDiagnosticText(finalResponse.reasoning_summary_text); + if (phase || commentaryText || finalAnswerText || reasoningSummaryText) { + logRequest("stream-diagnostics", { + ...(responseId ? { responseId } : {}), + ...(phase ? { phase } : {}), + ...(commentaryText ? { commentaryText } : {}), + ...(finalAnswerText ? { finalAnswerText } : {}), + ...(reasoningSummaryText ? { reasoningSummaryText } : {}), + }); + } +} + function maybeCaptureResponseEvent( state: ParsedResponseState, data: SSEEventData, @@ -539,6 +567,8 @@ export async function convertSseToJson( }); } + logStreamDiagnostics(finalResponse); + // Return as plain JSON (not SSE) const jsonHeaders = new Headers(headers); jsonHeaders.set('content-type', 'application/json; charset=utf-8'); diff --git a/test/request-transformer.test.ts b/test/request-transformer.test.ts index bb360838..91dcdb18 100644 --- a/test/request-transformer.test.ts +++ b/test/request-transformer.test.ts @@ -2067,7 +2067,6 @@ describe('Request Transformer Module', () => { 'Removed 1 plan-mode-only tool definition(s) because collaboration mode is default', ); }); - it('removes tool_search tools when the selected model lacks search capability', async () => { const body: RequestBody = { model: 'gpt-5-nano', @@ -2151,7 +2150,6 @@ describe('Request Transformer Module', () => { }, ]); }); - it('filters unsupported tools from nested namespaces without dropping supported descendants', async () => { const body: RequestBody = { model: 'gpt-5-nano', diff --git a/test/response-handler-logging.test.ts b/test/response-handler-logging.test.ts index a295b754..70cfab80 100644 --- a/test/response-handler-logging.test.ts +++ b/test/response-handler-logging.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; const logRequestMock = vi.fn(); @@ -14,6 +14,10 @@ vi.mock("../lib/logger.js", () => ({ })); describe("response handler logging branch", () => { + beforeEach(() => { + logRequestMock.mockClear(); + }); + it("logs full stream content when logging is enabled", async () => { const { convertSseToJson } = await import("../lib/request/response-handler.js"); const response = new Response( @@ -29,4 +33,33 @@ describe("response handler logging branch", () => { expect.objectContaining({ fullContent: expect.stringContaining("response.done") }), ); }); + + it("logs parsed phase and reasoning summary diagnostics when semantic SSE fields are present", async () => { + const { convertSseToJson } = await import("../lib/request/response-handler.js"); + const response = new Response( + [ + 'data: {"type":"response.created","response":{"id":"resp_diag_123","object":"response"}}', + 'data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg_123","type":"message","role":"assistant","phase":"commentary"}}', + 'data: {"type":"response.output_text.done","output_index":0,"content_index":0,"text":"Thinking...","phase":"commentary"}', + 'data: {"type":"response.output_item.done","output_index":0,"item":{"id":"msg_123","type":"message","role":"assistant","phase":"final_answer"}}', + 'data: {"type":"response.output_text.done","output_index":0,"content_index":1,"text":"Done.","phase":"final_answer"}', + 'data: {"type":"response.reasoning_summary_text.done","output_index":1,"summary_index":0,"text":"Need more context."}', + 'data: {"type":"response.done","response":{"id":"resp_diag_123","object":"response"}}', + "", + ].join("\n"), + ); + + const result = await convertSseToJson(response, new Headers()); + expect(result.status).toBe(200); + expect(logRequestMock).toHaveBeenCalledWith( + "stream-diagnostics", + expect.objectContaining({ + responseId: "resp_diag_123", + phase: "final_answer", + commentaryText: "Thinking...", + finalAnswerText: "Done.", + reasoningSummaryText: "Need more context.", + }), + ); + }); });