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

  1. Check whether any custom code wraps or transforms the event stream and drop/replace events — restore pass-through of done/error.
  2. Upgrade the library if the provider added a new terminal event type that this version does not map.
  3. Log the offending event type at the stream boundary to identify which adapter emitted it.
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/b6b0c45a858f607e. Report an issue: GitHub.