From e40157013f6997b47d416e4a18220aab152c35f1 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 18:36:46 -0700 Subject: [PATCH] fix(diagnostics): complete early model stream closes --- CHANGELOG.md | 3 +++ .../run/attempt.model-diagnostic-events.test.ts | 11 +++++++---- .../run/attempt.model-diagnostic-events.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ff87e975a8..cfd9efc2caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,9 @@ Docs: https://docs.openclaw.ai ### Fixes +- Diagnostics/OTEL: treat normal early model stream cleanup as a completed + model call instead of exporting a misleading `StreamAbandoned` error span. + Thanks @vincentkoc. - ACP: wait for the configured runtime backend to become healthy before startup identity reconciliation, avoiding transient acpx warnings during Gateway boot. Fixes #40566. diff --git a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts index a432724c059..0de899bc42d 100644 --- a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts @@ -253,7 +253,7 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => { expect(JSON.stringify([started.mock.calls, ended.mock.calls])).not.toContain(secretChunk); }); - it("emits error events when stream consumption stops early", async () => { + it("emits completed events when stream consumption stops early", async () => { async function* stream() { yield { type: "text", text: "first" }; yield { type: "text", text: "second" }; @@ -279,12 +279,15 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => { } }); - expect(events.map((event) => event.type)).toEqual(["model.call.started", "model.call.error"]); + expect(events.map((event) => event.type)).toEqual([ + "model.call.started", + "model.call.completed", + ]); expect(events[1]).toMatchObject({ - type: "model.call.error", + type: "model.call.completed", callId: "call-abandoned", - errorCategory: "StreamAbandoned", durationMs: expect.any(Number), }); + expect(events[1]).not.toHaveProperty("errorCategory"); }); }); diff --git a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts index 5062ef555cc..cfcc2a56582 100644 --- a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts +++ b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts @@ -252,7 +252,7 @@ async function* observeModelCallIterator( } finally { if (!terminalEmitted) { await safeReturnIterator(iterator); - emitModelCallError(eventBase, startedAt, { errorCategory: "StreamAbandoned" }); + emitModelCallCompleted(eventBase, startedAt); } } }