can1357/oh-my-pi · error · AIError.ProviderResponseError
Unexpected event type for final result
Error message
Unexpected event type for final result
What it means
AIError.ProviderResponseError thrown when an event-stream result collector receives a final event that is neither "done" nor "error". The stream contract guarantees the last event is one of those two types; anything else means the event pipeline produced a malformed or truncated sequence, so the library refuses to fabricate a result.
Source
Thrown at packages/ai/src/utils/event-stream.ts:179
try {
return await work;
} finally {
this.#pendingLocalWork--;
}
}
}
export class AssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
constructor() {
super(
event => event.type === "done" || event.type === "error",
event => {
if (event.type === "done") {
return event.message;
} else if (event.type === "error") {
return event.error;
}
throw new AIError.ProviderResponseError("Unexpected event type for final result", { kind: "envelope" });
},
);
}
override push(event: AssistantMessageEvent): void {
if (this.done) return;
if (event.type === "error" && event.error.stopReason === "error") {
AIError.classifyMessage(event.error);
}
// Completion resolves the final result and still emits the terminal event.
if (this.isComplete(event)) {
this.done = true;
this.resultSettled = true;
this.resolveFinalResult(this.extractResult(event));
}
View on GitHub (pinned to 9690622007)
Solutions
- Check whether any custom code wraps or transforms the event stream and drop/replace events — restore pass-through of done/error.
- Upgrade the library if the provider added a new terminal event type that this version does not map.
- Log the offending event type at the stream boundary to identify which adapter emitted it.
- Remove or fix SSE proxies that rewrite event ordering.
Example fix
// before: custom transform eats terminal events stream.map(e => e.type === "done" ? null : e).filter(Boolean); // after: preserve terminal events stream.map(e => e); // pass through done/error untouched
Defensive patterns
Strategy: try-catch
Type guard
function isTerminalEvent(e: AssistantMessageEvent): boolean {
return e.type === "done" || e.type === "error";
} Try / catch
try {
const message = await collectStream(stream);
} catch (err) {
if (err instanceof AIError.ProviderResponseError && /Unexpected event type/.test(err.message)) {
// stream pipeline malformed: log event types, retry once without transforms
} else throw err;
} Prevention
- Never filter/map terminal events out of the AssistantMessageEvent stream.
- Test custom stream transforms against the full event-type union.
- Keep the library current when providers add new event types.
When it happens
Trigger: The AssistantMessageEvent stream closes with a part/other event type as the terminal event — caused by a provider adapter emitting non-standard terminal events, a bug in a custom stream transform, or stream truncation that reordered/dropped the done/error event.
Common situations: Custom middleware or user code transforming the event stream and swallowing the done/error event; a new provider event type not yet mapped by the library version in use; intercepting/proxying provider SSE and mangling the event order.
Related errors
- Auth broker stream did not start with snapshot
- received ${event.type} before message_start
- Request was aborted
- No response body for V2 compaction streaming
- V2 compaction stream closed before response.completed
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/b6b0c45a858f607e.
Report an issue: GitHub.