

























@@ -35,6 +35,33 @@ async function drain(stream: AsyncIterable<unknown>): Promise<void> {
3535}
3636}
373738+function isRecord(value: unknown): value is Record<string, unknown> {
39+return typeof value === "object" && value !== null && !Array.isArray(value);
40+}
41+42+function requireRecord(value: unknown, label: string): Record<string, unknown> {
43+if (!isRecord(value)) {
44+throw new Error(`Expected ${label} to be an object`);
45+}
46+return value;
47+}
48+49+function readRecordField(record: Record<string, unknown>, key: string, label: string) {
50+const value = record[key];
51+if (!isRecord(value)) {
52+throw new Error(`Expected ${label} to be an object`);
53+}
54+return value;
55+}
56+57+function expectNumberField(record: Record<string, unknown>, key: string) {
58+expect(typeof record[key]).toBe("number");
59+}
60+61+function getEvent(events: readonly DiagnosticEventPayload[], index: number) {
62+return requireRecord(events[index], `event ${index}`);
63+}
64+3865describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
3966beforeEach(() => {
4067resetDiagnosticEventsForTest();
@@ -97,26 +124,26 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
97124"model.call.started",
98125"model.call.completed",
99126]);
100-expect(events[0]).toMatchObject({
101-type: "model.call.started",
102-runId: "run-1",
103-callId: "call-1",
104-sessionKey: "session-key",
105-sessionId: "session-id",
106-provider: "openai",
107-model: "gpt-5.4",
108-api: "openai-responses",
109-transport: "http",
110-});
127+const startedEvent = getEvent(events, 0);
128+expect(startedEvent.type).toBe("model.call.started");
129+expect(startedEvent.runId).toBe("run-1");
130+expect(startedEvent.callId).toBe("call-1");
131+expect(startedEvent.sessionKey).toBe("session-key");
132+expect(startedEvent.sessionId).toBe("session-id");
133+expect(startedEvent.provider).toBe("openai");
134+expect(startedEvent.model).toBe("gpt-5.4");
135+expect(startedEvent.api).toBe("openai-responses");
136+expect(startedEvent.transport).toBe("http");
111137expect(events[0]?.trace?.parentSpanId).toBe("00f067aa0ba902b7");
112-expect(events[1]).toMatchObject({
113-type: "model.call.completed",
114-callId: "call-1",
115-durationMs: expect.any(Number),
116-requestPayloadBytes: Buffer.byteLength(JSON.stringify(requestPayload), "utf8"),
117-responseStreamBytes: expect.any(Number),
118-timeToFirstByteMs: expect.any(Number),
119-});
138+const completedEvent = getEvent(events, 1);
139+expect(completedEvent.type).toBe("model.call.completed");
140+expect(completedEvent.callId).toBe("call-1");
141+expectNumberField(completedEvent, "durationMs");
142+expect(completedEvent.requestPayloadBytes).toBe(
143+Buffer.byteLength(JSON.stringify(requestPayload), "utf8"),
144+);
145+expectNumberField(completedEvent, "responseStreamBytes");
146+expectNumberField(completedEvent, "timeToFirstByteMs");
120147expect(JSON.stringify(events)).not.toContain("sk-test-secret-value");
121148});
122149@@ -151,13 +178,14 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
151178await drain(streamResult as unknown as AsyncIterable<unknown>);
152179});
153180154-expect(events[1]).toMatchObject({
155-type: "model.call.completed",
156-callId: "call-payload",
157-requestPayloadBytes: Buffer.byteLength(JSON.stringify(replacementPayload), "utf8"),
158-responseStreamBytes: expect.any(Number),
159-timeToFirstByteMs: expect.any(Number),
160-});
181+const completedEvent = getEvent(events, 1);
182+expect(completedEvent.type).toBe("model.call.completed");
183+expect(completedEvent.callId).toBe("call-payload");
184+expect(completedEvent.requestPayloadBytes).toBe(
185+Buffer.byteLength(JSON.stringify(replacementPayload), "utf8"),
186+);
187+expectNumberField(completedEvent, "responseStreamBytes");
188+expectNumberField(completedEvent, "timeToFirstByteMs");
161189expect(JSON.stringify(events)).not.toContain("sk-original-secret");
162190});
163191@@ -201,13 +229,12 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
201229202230expect(capturedOptions).toHaveLength(1);
203231expect(capturedOptions[0]).not.toBe(callerOptions);
204-expect(capturedOptions[0]).toMatchObject({
205-sessionId: "provider-session",
206-headers: {
207-"X-Custom": "kept",
208-traceparent: expect.stringMatching(/^00-4bf92f3577b34da6a3ce929d0e0e4736-[0-9a-f]{16}-01$/),
209-},
210-});
232+const capturedOption = requireRecord(capturedOptions[0], "captured stream options");
233+expect(capturedOption.sessionId).toBe("provider-session");
234+const headers = readRecordField(capturedOption, "headers", "captured stream headers");
235+expect(headers["X-Custom"]).toBe("kept");
236+expect(typeof headers.traceparent).toBe("string");
237+expect(headers.traceparent).toMatch(/^00-4bf92f3577b34da6a3ce929d0e0e4736-[0-9a-f]{16}-01$/);
211238expect(capturedOptions[0]?.headers).not.toHaveProperty("TraceParent");
212239expect(callerOptions.headers).toEqual({
213240"X-Custom": "kept",
@@ -244,13 +271,13 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
244271});
245272246273expect(events.map((event) => event.type)).toEqual(["model.call.started", "model.call.error"]);
247-expect(events[1]).toMatchObject({
248- type: "model.call.error",
249- callId: "call-err",
250- errorCategory: "TypeError",
251- upstreamRequestIdHash: expect.stringMatching(/^sha256:[a-f0-9]{12}$/),
252- durationMs: expect.any(Number),
253-});
274+const errorEvent = getEvent(events, 1);
275+expect(errorEvent.type).toBe("model.call.error");
276+expect(errorEvent.callId).toBe("call-err");
277+expect(errorEvent.errorCategory).toBe("TypeError");
278+expect(typeof errorEvent.upstreamRequestIdHash).toBe("string");
279+expect(errorEvent.upstreamRequestIdHash).toMatch(/^sha256:[a-f0-9]{12}$/);
280+expectNumberField(errorEvent, "durationMs");
254281expect(JSON.stringify(events[1])).not.toContain(requestId);
255282});
256283@@ -282,19 +309,17 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
282309});
283310284311expect(events.map((event) => event.type)).toEqual(["model.call.started", "model.call.error"]);
285-expect(events[1]).toMatchObject({
286-type: "model.call.error",
287-callId: "call-terminated",
288-errorCategory: "Error",
289-failureKind: "terminated",
290-memory: {
291-rssBytes: expect.any(Number),
292-heapTotalBytes: expect.any(Number),
293-heapUsedBytes: expect.any(Number),
294-externalBytes: expect.any(Number),
295-arrayBuffersBytes: expect.any(Number),
296-},
297-});
312+const errorEvent = getEvent(events, 1);
313+expect(errorEvent.type).toBe("model.call.error");
314+expect(errorEvent.callId).toBe("call-terminated");
315+expect(errorEvent.errorCategory).toBe("Error");
316+expect(errorEvent.failureKind).toBe("terminated");
317+const memory = readRecordField(errorEvent, "memory", "error event memory");
318+expectNumberField(memory, "rssBytes");
319+expectNumberField(memory, "heapTotalBytes");
320+expectNumberField(memory, "heapUsedBytes");
321+expectNumberField(memory, "externalBytes");
322+expectNumberField(memory, "arrayBuffersBytes");
298323});
299324300325it("does not mutate non-configurable provider streams", async () => {
@@ -370,41 +395,33 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
370395"model.call.started",
371396"model.call.completed",
372397]);
373-expect(started).toHaveBeenCalledWith(
374-expect.objectContaining({
375-runId: "run-1",
376-callId: "call-hook",
377-sessionKey: "session-key",
378-sessionId: "session-id",
379-provider: "openai",
380-model: "gpt-5.4",
381-api: "openai-responses",
382-transport: "http",
383-}),
384-expect.objectContaining({
385-runId: "run-1",
386-sessionKey: "session-key",
387-sessionId: "session-id",
388-modelProviderId: "openai",
389-modelId: "gpt-5.4",
390-}),
391-);
392-expect(ended).toHaveBeenCalledWith(
393-expect.objectContaining({
394-runId: "run-1",
395-callId: "call-hook",
396-outcome: "completed",
397-durationMs: expect.any(Number),
398-responseStreamBytes: expect.any(Number),
399-timeToFirstByteMs: expect.any(Number),
400-}),
401-expect.objectContaining({ runId: "run-1" }),
402-);
403-const startedEvent = started.mock.calls[0]?.[0];
404-const startedCtx = started.mock.calls[0]?.[1];
398+const startedEvent = requireRecord(started.mock.calls[0]?.[0], "started hook event");
399+expect(startedEvent.runId).toBe("run-1");
400+expect(startedEvent.callId).toBe("call-hook");
401+expect(startedEvent.sessionKey).toBe("session-key");
402+expect(startedEvent.sessionId).toBe("session-id");
403+expect(startedEvent.provider).toBe("openai");
404+expect(startedEvent.model).toBe("gpt-5.4");
405+expect(startedEvent.api).toBe("openai-responses");
406+expect(startedEvent.transport).toBe("http");
407+const startedCtx = requireRecord(started.mock.calls[0]?.[1], "started hook context");
408+expect(startedCtx.runId).toBe("run-1");
409+expect(startedCtx.sessionKey).toBe("session-key");
410+expect(startedCtx.sessionId).toBe("session-id");
411+expect(startedCtx.modelProviderId).toBe("openai");
412+expect(startedCtx.modelId).toBe("gpt-5.4");
413+const endedEvent = requireRecord(ended.mock.calls[0]?.[0], "ended hook event");
414+expect(endedEvent.runId).toBe("run-1");
415+expect(endedEvent.callId).toBe("call-hook");
416+expect(endedEvent.outcome).toBe("completed");
417+expectNumberField(endedEvent, "durationMs");
418+expectNumberField(endedEvent, "responseStreamBytes");
419+expectNumberField(endedEvent, "timeToFirstByteMs");
420+const endedCtx = requireRecord(ended.mock.calls[0]?.[1], "ended hook context");
421+expect(endedCtx.runId).toBe("run-1");
405422expect(Object.isFrozen(startedEvent)).toBe(true);
406423expect(Object.isFrozen(startedCtx)).toBe(true);
407-expect(Object.isFrozen((startedCtx as { trace?: unknown } | undefined)?.trace)).toBe(true);
424+expect(Object.isFrozen(startedCtx.trace)).toBe(true);
408425expect(JSON.stringify([started.mock.calls, ended.mock.calls])).not.toContain(secretChunk);
409426});
410427@@ -438,11 +455,10 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
438455"model.call.started",
439456"model.call.completed",
440457]);
441-expect(events[1]).toMatchObject({
442-type: "model.call.completed",
443-callId: "call-abandoned",
444-durationMs: expect.any(Number),
445-});
458+const completedEvent = getEvent(events, 1);
459+expect(completedEvent.type).toBe("model.call.completed");
460+expect(completedEvent.callId).toBe("call-abandoned");
461+expectNumberField(completedEvent, "durationMs");
446462expect(events[1]).not.toHaveProperty("errorCategory");
447463});
448464});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。