mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
855446e1ab
## Summary Pressing **Stop** while an assistant message is streaming (CopilotRuntime + `HttpAgent` proxy) crashed the chat with: ``` Cannot send event type 'TEXT_MESSAGE_END': The run has already errored with 'RUN_ERROR'. No further events can be sent. ``` Root cause: `finalizeRunEvents` appended a trailing `TEXT_MESSAGE_END` **after** the `RUN_ERROR` that the aborted agent had already emitted. Fixes #5812. ## Root cause When the upstream agent (e.g. pydantic-ai's `AGUIAdapter`) is aborted mid-stream it emits a live `RUN_ERROR` while a text message is still open — it does **not** close the message first. All runners (`in-memory`, `intelligence`, `sqlite`) stream `finalizeRunEvents`' output *after* everything the agent already emitted, so the appended closer landed past the terminal: | | outgoing event order | |---|---| | **Before** | `… TEXT_MESSAGE_CONTENT → RUN_ERROR → TEXT_MESSAGE_END` ❌ verifier throws | | **After** | `… TEXT_MESSAGE_CONTENT → RUN_ERROR` ✅ terminal closes the message client-side | Per the AG-UI invariant: at most one terminal event per run, and no sub-events after it. I confirmed against the real `@ag-ui/client` `verifyEvents` (the verifier the browser runs) that a terminal arriving with a message still open is valid — the terminal implicitly closes it. ## Fix `finalizeRunEvents` (in `@copilotkit/shared`) now returns early and appends **nothing** when the stream already contains a terminal event (`RUN_FINISHED` or `RUN_ERROR`). The abrupt-end path (no terminal → close open streams + synthesize a terminal, in the correct order) is unchanged. No API/signature change; the in-memory, intelligence, and sqlite runners all inherit the fix. ## Testing RED→GREEN verified — each new/updated assertion was confirmed to fail against the pre-fix code: - **`finalize-events.test.ts`** — terminal-present appends nothing (parametrized over `RUN_FINISHED` and `RUN_ERROR`) + a named #5812 case. - **`in-memory-runner.test.ts`** — end-to-end mid-stream-stop regression: a fake `HttpAgent`-style agent is stopped between `TEXT_MESSAGE_START` and `TEXT_MESSAGE_END`; asserts no events follow `RUN_ERROR` **and** that the collected stream passes `verifyEvents` (before the fix this threw the exact browser error). - **`intelligence-runner.test.ts`** — corrected a pre-existing assertion that had encoded the buggy post-terminal `TEXT_MESSAGE_END`. Green: full `@copilotkit/runtime` suite, `@copilotkit/sqlite-runner`, `@copilotkit/shared`, `check-types`, `oxlint` (0 errors), and build. ## Reviewer notes - The behavior change is a single early-return in `finalize-events.ts`; the `terminalEventMissing` guards simplify away because they're only reachable when no terminal exists. - Diff is +204/−55 across 4 files, the bulk of it tests.