fix: pace captured SSE replay

This commit is contained in:
2026-07-11 13:16:40 +08:00
parent 7d0a40e1ea
commit 29eb91e0c3
3 changed files with 78 additions and 16 deletions
+62 -4
View File
@@ -303,6 +303,7 @@ function startFakeUpstream(port, options = {}) {
? parsed.test_stream_delta_chunks
: 1;
const deltaTextBase = parsed.test_stream_delta_text || "hello";
const outputItemId = "msg_stream";
const lifecyclePayloadExtra = parsed.test_stream_lifecycle_marker
? {
marker: parsed.test_stream_lifecycle_marker,
@@ -329,17 +330,46 @@ function startFakeUpstream(port, options = {}) {
output: lifecyclePayloadExtra,
},
})}\n\n`,
`data: ${JSON.stringify({
type: "response.output_item.added",
output_index: 0,
item: {
id: outputItemId,
type: "message",
status: "in_progress",
role: "assistant",
content: [],
},
})}\n\n`,
]
: [];
const streamChunks = Array.from({ length: deltaChunkCount }, (_, index) => {
const deltaTexts = Array.from({ length: deltaChunkCount }, (_, index) => {
const deltaText = deltaChunkCount === 1 ? deltaTextBase : `${deltaTextBase}-${index + 1}`;
return `data: ${JSON.stringify({ type: "response.output_text.delta", delta: deltaText, response_id: "resp_stream", thread_id: parsed.thread_id || "thread_stream", retry_attempt: reasoningAttempt })}\n\n`;
return deltaText;
});
const streamChunks = deltaTexts.map((deltaText) => {
return `data: ${JSON.stringify({ type: "response.output_text.delta", item_id: outputItemId, output_index: 0, content_index: 0, delta: deltaText, response_id: "resp_stream", thread_id: parsed.thread_id || "thread_stream", retry_attempt: reasoningAttempt })}\n\n`;
});
if (parsed.test_stream_include_lifecycle) {
streamChunks.unshift(...lifecycleChunks);
}
if (parsed.test_stream_include_lifecycle) {
streamChunks.push(
`data: ${JSON.stringify({
type: "response.output_item.done",
output_index: 0,
item: {
id: outputItemId,
type: "message",
status: "completed",
role: "assistant",
content: [{
type: "output_text",
text: deltaTexts.join(""),
annotations: [],
}],
},
})}\n\n`,
`data: ${JSON.stringify({
type: "response.completed",
response: {
@@ -552,7 +582,8 @@ async function readSseUntilClose(url, requestBody) {
}
}
text += decoder.decode();
text += decoder.decode();
const elapsedMs = Date.now() - startedAt;
return {
status: response.status,
headers: response.headers,
@@ -560,6 +591,7 @@ async function readSseUntilClose(url, requestBody) {
closedByError,
readCount,
firstChunkDelayMs,
elapsedMs,
};
}
@@ -1650,6 +1682,11 @@ async function run() {
);
assert(capturedLifecycleStream.status === 200, `/responses 捕获回放状态异常: ${capturedLifecycleStream.status}`);
assert(capturedLifecycleStream.readCount > 1, "/responses 捕获回放不应退化为单块响应");
const observedReplayDurationMs = capturedLifecycleStream.elapsedMs - capturedLifecycleStream.firstChunkDelayMs;
assert(
observedReplayDurationMs >= 250,
`/responses 捕获回放缺少真实传输间隔: ${observedReplayDurationMs}ms`,
);
const capturedEvents = parseSseEvents(capturedLifecycleStream.text);
const firstReplayEvent = capturedEvents.find((event) => event.payload);
assert(
@@ -1680,11 +1717,32 @@ async function run() {
capturedDeltas.every((delta, index) => delta === `captured-chunk-${index + 1}`),
"/responses 捕获回放的长流 delta 顺序不完整",
);
const outputItemAddedIndex = capturedEvents.findIndex(
(event) => event.payload?.type === "response.output_item.added",
);
const outputItemDoneIndex = capturedEvents.findIndex(
(event) => event.payload?.type === "response.output_item.done",
);
const firstDeltaIndex = capturedEvents.findIndex(
(event) => event.payload?.type === "response.output_text.delta",
);
const completedIndex = capturedEvents.findIndex((event) => event.payload?.type === "response.completed");
const lastDeltaIndex = capturedEvents
.map((event) => event.payload?.type)
.lastIndexOf("response.output_text.delta");
assert(completedIndex > lastDeltaIndex, "/responses 捕获回放未在所有 delta 后终止");
assert(
outputItemAddedIndex >= 0 && outputItemAddedIndex < firstDeltaIndex,
"/responses 捕获回放未在 delta 前保留 output_item.added",
);
assert(
outputItemDoneIndex > lastDeltaIndex && completedIndex > outputItemDoneIndex,
"/responses 捕获回放未按 delta、output_item.done、response.completed 顺序终止",
);
const completedOutputItem = capturedEvents[outputItemDoneIndex]?.payload?.item;
assert(
completedOutputItem?.content?.[0]?.text === capturedDeltas.join(""),
"/responses 捕获回放的 output_item.done 与全部 delta 内容不一致",
);
assert(!capturedLifecycleStream.text.includes("captured-lifecycle-marker"), "/responses lifecycle 归一化仍透传了巨大的生命周期原始 payload");
const normalizedLifecycleStream = await readSseUntilClose(