




















@@ -346,6 +346,110 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
346346expect(JSON.stringify(events)).not.toContain("sk-original-secret");
347347});
348348349+it("counts text deltas without serializing full partial snapshots", async () => {
350+const serializedPartial = vi.fn(() => {
351+throw new Error("partial snapshot should not be serialized for text deltas");
352+});
353+async function* stream() {
354+yield {
355+type: "text_delta",
356+contentIndex: 0,
357+delta: "a",
358+partial: {
359+toJSON: serializedPartial,
360+role: "assistant",
361+content: [{ type: "text", text: "a".repeat(200_000) }],
362+},
363+};
364+yield {
365+type: "text_delta",
366+contentIndex: 0,
367+delta: "bc",
368+partial: {
369+toJSON: serializedPartial,
370+role: "assistant",
371+content: [{ type: "text", text: "abc".repeat(200_000) }],
372+},
373+};
374+}
375+const wrapped = wrapStreamFnWithDiagnosticModelCallEvents(
376+(() => stream()) as unknown as StreamFn,
377+{
378+runId: "run-1",
379+provider: "openai",
380+model: "gpt-5.4",
381+trace: createDiagnosticTraceContext(),
382+nextCallId: () => "call-delta-bytes",
383+},
384+);
385+386+const events = await collectModelCallEvents(async () => {
387+await drain(wrapped({} as never, {} as never, {} as never) as AsyncIterable<unknown>);
388+});
389+390+const completedEvent = getEvent(events, 1);
391+expect(completedEvent.type).toBe("model.call.completed");
392+expect(completedEvent.responseStreamBytes).toBe(
393+Buffer.byteLength(
394+JSON.stringify({ type: "text_delta", contentIndex: 0, delta: "a" }),
395+"utf8",
396+) +
397+Buffer.byteLength(
398+JSON.stringify({ type: "text_delta", contentIndex: 0, delta: "bc" }),
399+"utf8",
400+),
401+);
402+expect(serializedPartial).not.toHaveBeenCalled();
403+});
404+405+it("keeps streams alive when diagnostic byte inspection cannot read a chunk", async () => {
406+const opaqueChunk = new Proxy(
407+{},
408+{
409+get(_target, property) {
410+if (property === "then") {
411+return undefined;
412+}
413+throw new Error("chunk should not be inspected");
414+},
415+},
416+);
417+async function* stream() {
418+yield opaqueChunk;
419+yield { type: "text_delta", delta: "ok" };
420+}
421+const wrapped = wrapStreamFnWithDiagnosticModelCallEvents(
422+(() => stream()) as unknown as StreamFn,
423+{
424+runId: "run-1",
425+provider: "openai",
426+model: "gpt-5.4",
427+trace: createDiagnosticTraceContext(),
428+nextCallId: () => "call-opaque-chunk",
429+},
430+);
431+432+const chunks: unknown[] = [];
433+const events = await collectModelCallEvents(async () => {
434+for await (const chunk of wrapped(
435+{} as never,
436+{} as never,
437+{} as never,
438+) as AsyncIterable<unknown>) {
439+chunks.push(chunk);
440+}
441+});
442+443+expect(chunks).toHaveLength(2);
444+expect(chunks[0]).toBe(opaqueChunk);
445+expect(chunks[1]).toEqual({ type: "text_delta", delta: "ok" });
446+const completedEvent = getEvent(events, 1);
447+expect(completedEvent.type).toBe("model.call.completed");
448+expect(completedEvent.responseStreamBytes).toBe(
449+Buffer.byteLength(JSON.stringify({ type: "text_delta", delta: "ok" }), "utf8"),
450+);
451+});
452+349453it("captures model input, tools, and output only when content capture is enabled", async () => {
350454const assistant = {
351455role: "assistant",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。