diff --git a/packages/core/scripts/openaiResponsesTransformer.test.ts b/packages/core/scripts/openaiResponsesTransformer.test.ts new file mode 100644 index 0000000..6e12cb4 --- /dev/null +++ b/packages/core/scripts/openaiResponsesTransformer.test.ts @@ -0,0 +1,67 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { OpenAIResponsesTransformer } from "../src/transformer/openai.responses.transformer"; + +const readStream = async (stream: ReadableStream) => { + const reader = stream.getReader(); + const decoder = new TextDecoder(); + let output = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + output += decoder.decode(value, { stream: true }); + } + + output += decoder.decode(); + return output; +}; + +const parseSseData = (output: string) => + output + .split(/\r?\n/) + .filter((line) => line.startsWith("data: ")) + .map((line) => line.slice("data: ".length).trim()) + .filter((line) => line && line !== "[DONE]") + .map((line) => JSON.parse(line)); + +describe("OpenAIResponsesTransformer", () => { + it("preserves streaming usage from response.completed", async () => { + const upstream = [ + { + type: "response.completed", + response: { + id: "resp_123", + model: "gpt-5.5", + output: [{ type: "message" }], + usage: { + input_tokens: 12345, + output_tokens: 67, + total_tokens: 12412, + }, + }, + }, + ] + .map((event) => `data: ${JSON.stringify(event)}\n\n`) + .join(""); + + const response = new Response(upstream, { + headers: { "Content-Type": "text/event-stream" }, + }); + + const transformed = + await new OpenAIResponsesTransformer().transformResponseOut(response); + assert.ok(transformed.body); + + const chunks = parseSseData(await readStream(transformed.body)); + const doneChunk = chunks.find( + (chunk) => chunk.choices?.[0]?.finish_reason === "stop" + ); + + assert.deepEqual(doneChunk?.usage, { + prompt_tokens: 12345, + completion_tokens: 67, + total_tokens: 12412, + }); + }); +}); diff --git a/packages/core/src/transformer/openai.responses.transformer.ts b/packages/core/src/transformer/openai.responses.transformer.ts index a0a3857..6be9cd0 100644 --- a/packages/core/src/transformer/openai.responses.transformer.ts +++ b/packages/core/src/transformer/openai.responses.transformer.ts @@ -61,6 +61,11 @@ interface ResponsesStreamEvent { output?: Array<{ type: string; }>; + usage?: { + input_tokens?: number; + output_tokens?: number; + total_tokens?: number; + }; }; arguments?: string; reasoning_summary?: string; // 添加推理摘要支持 @@ -550,6 +555,16 @@ export class OpenAIResponsesTransformer implements Transformer { finish_reason: finishReason, }, ], + usage: data.response?.usage + ? { + prompt_tokens: + data.response.usage.input_tokens || 0, + completion_tokens: + data.response.usage.output_tokens || 0, + total_tokens: + data.response.usage.total_tokens || 0, + } + : undefined, }; controller.enqueue(