import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { handleResponses } from '../../src/handlers/responses.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); function chatChunk(chunk) { return `data: ${JSON.stringify(chunk)}\n\n`; } function makeFakeRealRes() { const listeners = new Map(); return { chunks: [], writableEnded: false, headersSent: false, writeHead() { this.headersSent = true; return this; }, write(chunk) { this.chunks.push(typeof chunk === 'string' ? chunk : chunk.toString('utf8')); return true; }, end(chunk) { if (chunk) this.write(chunk); if (this.writableEnded) return this; this.writableEnded = true; for (const cb of listeners.get('close') || []) cb(); return this; }, on(event, cb) { if (!listeners.has(event)) listeners.set(event, []); listeners.get(event).push(cb); return this; }, }; } function summarizeData(data) { const summary = { keys: Object.keys(data), }; for (const key of ['output_index', 'content_index', 'summary_index', 'status']) { if (key in data) summary[key] = data[key]; } if (data.item) { summary.item = { type: data.item.type, status: data.item.status, content_length: data.item.content?.length, summary_length: data.item.summary?.length, }; } if (data.response) { summary.response = { id: data.response.id, status: data.response.status, output: data.response.output?.map(item => ({ type: item.type, status: item.status })), }; if (data.response.usage) summary.response.usage = data.response.usage; if (data.response.error) summary.response.error = data.response.error; } if (data.part) summary.part = { type: data.part.type }; if (data.output) summary.output = data.output.map(item => ({ type: item.type, status: item.status })); if (data.usage) summary.usage = data.usage; if ('delta' in data) summary.delta = data.delta; if ('text' in data) summary.text = data.text; return summary; } function parseCapturedSse(chunks) { return chunks .join('') .split('\n\n') .filter(Boolean) .filter(frame => !frame.startsWith(':')) .map(frame => { const lines = frame.split('\n'); const event = lines.find(line => line.startsWith('event: '))?.slice(7); const dataLine = lines.find(line => line.startsWith('data: ')); const data = JSON.parse(dataLine?.slice(6) || '{}'); return { event, type: data.type, sequence_number: data.sequence_number, summary: summarizeData(data), }; }); } function makeCacheHitChatMock(cached) { return async function handleChatCompletionsMock(body) { assert.equal(body.stream, true); return { stream: true, headers: { 'Content-Type': 'text/event-stream' }, status: 200, async handler(res) { const id = 'chatcmpl_cache_hit'; const created = 123; const model = body.model; const usage = { prompt_tokens: 1, completion_tokens: cached.text ? 2 : 0, total_tokens: cached.text ? 3 : 1 }; const send = chunk => res.write(chatChunk(chunk)); send({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: { role: 'assistant', content: '' }, finish_reason: null }] }); if (cached.thinking) { send({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: { reasoning_content: cached.thinking }, finish_reason: null }] }); } if (cached.text) { send({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: { content: cached.text }, finish_reason: null }] }); } send({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], usage }); if (!res.writableEnded) { res.write('data: [DONE]\n\n'); res.end(); } }, }; }; } async function captureSequence(cached, filename) { const result = await handleResponses( { stream: true, model: 'gpt-5.4-xhigh', input: 'hi' }, { handleChatCompletions: makeCacheHitChatMock(cached) }, ); assert.equal(result.status, 200); assert.equal(result.stream, true); const realRes = makeFakeRealRes(); await result.handler(realRes); const sequence = parseCapturedSse(realRes.chunks); await writeFile(join(__dirname, filename), `${JSON.stringify(sequence, null, 2)}\n`, 'utf8'); assert.equal(sequence[0]?.type, 'response.created'); assert.match(sequence[0]?.summary.response?.id || '', /^resp_[a-f0-9]{24}$/); assert.equal(sequence[1]?.type, 'response.in_progress'); assert.match(sequence[1]?.summary.response?.id || '', /^resp_[a-f0-9]{24}$/); assert.equal(sequence.at(-1)?.type, 'response.completed'); assert.match(sequence.at(-1)?.summary.response?.id || '', /^resp_[a-f0-9]{24}$/); assert.deepEqual(sequence.at(-1)?.summary.response?.usage, { input_tokens: 1, output_tokens: cached.text ? 2 : 0, total_tokens: cached.text ? 3 : 1, }); assert.equal(sequence.at(-1)?.summary.response?.status, 'completed'); sequence.forEach((event, index) => { assert.equal(Number.isInteger(event.sequence_number), true); assert.equal(event.sequence_number, index); }); return sequence; } describe('Responses cache HIT stream sequence research', () => { it('captures text-only cache HIT stream events', async () => { await captureSequence({ text: 'cached answer' }, 'responses-cache-hit-seq-A.json'); }); it('captures thinking plus text cache HIT stream events', async () => { await captureSequence({ thinking: 'cached thinking', text: 'cached answer' }, 'responses-cache-hit-seq-B.json'); }); });