{"record":{"id":"b6b0c45a858f607e","repo":"can1357/oh-my-pi","slug":"unexpected-event-type-for-final-result","errorCode":null,"errorMessage":"Unexpected event type for final result","messagePattern":"Unexpected event type for final result","errorType":"exception","errorClass":"AIError.ProviderResponseError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/utils/event-stream.ts","lineNumber":179,"sourceCode":"\t\ttry {\n\t\t\treturn await work;\n\t\t} finally {\n\t\t\tthis.#pendingLocalWork--;\n\t\t}\n\t}\n}\n\nexport class AssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {\n\tconstructor() {\n\t\tsuper(\n\t\t\tevent => event.type === \"done\" || event.type === \"error\",\n\t\t\tevent => {\n\t\t\t\tif (event.type === \"done\") {\n\t\t\t\t\treturn event.message;\n\t\t\t\t} else if (event.type === \"error\") {\n\t\t\t\t\treturn event.error;\n\t\t\t\t}\n\t\t\t\tthrow new AIError.ProviderResponseError(\"Unexpected event type for final result\", { kind: \"envelope\" });\n\t\t\t},\n\t\t);\n\t}\n\n\toverride push(event: AssistantMessageEvent): void {\n\t\tif (this.done) return;\n\n\t\tif (event.type === \"error\" && event.error.stopReason === \"error\") {\n\t\t\tAIError.classifyMessage(event.error);\n\t\t}\n\n\t\t// Completion resolves the final result and still emits the terminal event.\n\t\tif (this.isComplete(event)) {\n\t\t\tthis.done = true;\n\t\t\tthis.resultSettled = true;\n\t\t\tthis.resolveFinalResult(this.extractResult(event));\n\t\t}\n","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/utils/event-stream.ts#L161-L197","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: custom transform eats terminal events\nstream.map(e => e.type === \"done\" ? null : e).filter(Boolean);\n// after: preserve terminal events\nstream.map(e => e); // pass through done/error untouched","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isTerminalEvent(e: AssistantMessageEvent): boolean {\n\treturn e.type === \"done\" || e.type === \"error\";\n}","tryCatchPattern":"try {\n\tconst message = await collectStream(stream);\n} catch (err) {\n\tif (err instanceof AIError.ProviderResponseError && /Unexpected event type/.test(err.message)) {\n\t\t// stream pipeline malformed: log event types, retry once without transforms\n\t} else throw err;\n}","preventionTips":["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."],"tags":["streaming","event-stream","protocol-violation"],"backgroundTag":"malformed-stream-events","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}