Skip to content

fix(ai-openai): decode nested Responses API error stream events - #7262

Open
danieljvdm wants to merge 1 commit into
Effect-TS:mainfrom
danieljvdm:fix/openai-responses-error-event
Open

fix(ai-openai): decode nested Responses API error stream events#7262
danieljvdm wants to merge 1 commit into
Effect-TS:mainfrom
danieljvdm:fix/openai-responses-error-event

Conversation

@danieljvdm

@danieljvdm danieljvdm commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

What

The OpenAI Responses API documents the error stream event with code, message, and param at the top level, and OpenAiSchema.ResponseErrorEvent matches exactly that:

Schema.Struct({
  type: Schema.Literal("error"),
  code: Schema.NullOr(Schema.String),
  message: Schema.String,
  param: Schema.NullOr(Schema.String),
  sequence_number: Schema.Int,
  status: Schema.optionalKey(Schema.Int)
})

But mid-stream errors are emitted with the standard error envelope nested under error. Here is the data payload of a real event: error frame captured from POST /v1/responses (stream: true) when the account is out of credits:

{
  "type": "error",
  "error": {
    "type": "insufficient_quota",
    "code": "credit_balance_exhausted",
    "message": "You have no credits remaining. Add credits to continue using the API…",
    "param": null
  },
  "sequence_number": 2
}

code, message, and param are absent at the top level here, so the event fails to decode. Because ResponseStreamEvent is decoded with Sse.decodeDataSchema, that single failing event aborts the entire stream with an opaque schema error instead of surfacing the real problem:

OpenAiClient.createResponseStream: Invalid output: Missing key
  at ["data"]["code"]
Expected UnknownResponseStreamEvent
  at ["data"]

The UnknownResponseStreamEvent fallback deliberately excludes known event types (including "error"), so a nested-shape error event can't fall through it either — the caller just sees the message above instead of "no credits". The error event is the one event that most needs to surface, so hard-failing on it is especially unfortunate.

Fix

ResponseErrorEvent now accepts both wire shapes and normalizes them to the documented shape via Schema.decodeTo:

  • flat/spec payloads decode unchanged;
  • nested payloads have code / message / param lifted from the error envelope;
  • the decoded Type is unchanged — only the accepted Encoded shape widens — so there is no downstream type change (the union member's .Type stays { type, code, message, param, sequence_number, status? }).

This keeps the existing strictness for other events (the "does not silently decode malformed known events as unknown" test still holds); it only teaches the error event about OpenAI's actual second shape.

Test

Adds a case to packages/ai/openai/test/OpenAiSchema.test.ts that decodes ResponseStreamEvent for both the flat and the nested error payloads, asserting the nested one normalizes to the documented shape.

Note

I validated the schema and transform in isolation — both shapes, and as a union member alongside a known sibling and the unknown fallback — typechecking clean under strict + exactOptionalPropertyTypes. I was not able to run the full monorepo build locally, so please let CI confirm formatting / lint.

The Responses API documents the `error` stream event with `code`,
`message`, and `param` at the top level, but mid-stream errors (for
example quota exhaustion) instead emit the standard error envelope nested
under `error`. The strict schema only matched the flat shape, so a nested
error event failed to decode and aborted the whole stream with an opaque
schema error instead of surfacing the real message.

`ResponseErrorEvent` now accepts both wire shapes and normalizes them to
the documented shape, so the error's `code` and `message` always surface
and the decoded type is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 34d5e57

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
@effect/ai-openai Patch
effect Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node Patch
@effect/platform-node-shared Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/vitest Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@effect-slopcop effect-slopcop Bot added bug Something isn't working 4.0 labels Aug 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 6.96 KB 6.96 KB 0.00 KB (0.00%)
batching.ts 9.76 KB 9.76 KB 0.00 KB (0.00%)
brand.ts 6.55 KB 6.55 KB 0.00 KB (0.00%)
cache.ts 10.67 KB 10.67 KB 0.00 KB (0.00%)
config.ts 21.10 KB 21.10 KB 0.00 KB (0.00%)
differ.ts 20.04 KB 20.04 KB 0.00 KB (0.00%)
http-client.ts 21.64 KB 21.64 KB 0.00 KB (0.00%)
logger.ts 10.91 KB 10.91 KB 0.00 KB (0.00%)
metric.ts 8.89 KB 8.89 KB 0.00 KB (0.00%)
optic.ts 6.71 KB 6.71 KB 0.00 KB (0.00%)
pubsub.ts 14.94 KB 14.94 KB 0.00 KB (0.00%)
queue.ts 11.61 KB 11.61 KB 0.00 KB (0.00%)
schedule.ts 10.77 KB 10.77 KB 0.00 KB (0.00%)
schema-class.ts 19.66 KB 19.66 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 29.61 KB 29.61 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.85 KB 25.85 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.53 KB 13.53 KB 0.00 KB (0.00%)
schema-string.ts 11.03 KB 11.03 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.33 KB 15.33 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 21.78 KB 21.78 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.21 KB 24.21 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.00 KB 19.00 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 18.82 KB 18.82 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.69 KB 18.69 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.85 KB 22.85 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.33 KB 19.33 KB 0.00 KB (0.00%)
schema.ts 18.91 KB 18.91 KB 0.00 KB (0.00%)
stm.ts 12.69 KB 12.69 KB 0.00 KB (0.00%)
stream.ts 9.71 KB 9.71 KB 0.00 KB (0.00%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant