Skip to content

[Feature]: Support text/event-stream (SSE) responses in the OpenAPI HTTP client #459

Description

@ALagoni97

Summary

The OpenAPI HTTP client cannot consume text/event-stream responses. Streaming endpoints generate a function that throws on the first call.

Notably, the CLI already knows how to consume SSE — the AsyncAPI eventsource protocol does exactly this. This issue is mostly about making that capability reachable from OpenAPI input, not about building it from scratch.

Current behaviour

src/codegen/generators/typescript/channels/protocols/http/client.ts:289 calls await response.json() for every operation. An SSE endpoint responds with text/event-stream and a body that is never valid JSON, so the call rejects immediately. There is no way for a consumer to intercept it — hooks.makeRequest can replace the request but the .json() is emitted inside every generated function.

What already exists

src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts generates a working SSE consumer for AsyncAPI documents:

  • Line 125 — uses fetchEventSource(...) (dependency configurable via additionalProperties.fetchDependency)
  • Line 129 — onmessage: (ev: EventSourceMessage) => {...} with typed unmarshalling of the payload
  • Line 105 — sets Accept: 'text/event-stream'

There is also a server-side counterpart in eventsource/express.ts. So the streaming primitives, the dependency choice, and the typed-message pattern are all settled — they're just wired to AsyncAPI channels rather than OpenAPI operations.

Why this matters

Streaming responses have become normal in HTTP APIs: incremental results, progress and job status, log tails, chat/agent token streams, live updates. Specs describe them with a text/event-stream response, and an OpenAPI generator that ignores that content type simply cannot produce a usable client for those services.

This is the gap most likely to make someone conclude the CLI isn't ready for their API — not because streaming is exotic, but because for the services that use it, the streaming endpoint is usually the endpoint. Closing it turns a whole category of API from "unsupported" into "supported", and we're closer to it than the missing feature suggests, since the hard part is already written and tested for AsyncAPI.

Proposed direction

When an OpenAPI operation declares a text/event-stream response, generate a streaming variant instead of the request/response function:

  • Reuse the eventsource/fetch.ts machinery rather than introducing a second SSE implementation. Ideally the shared parts move somewhere both protocols can import.
  • Decide on the consumer-facing shape. An AsyncIterable reads best for OpenAPI operations:
    for await (const event of client.streamThing({ ... })) { ... }
    A callback form matching the existing eventsource generator would be more consistent with the current codebase. Worth picking one deliberately — the object-parameter rule in code-style.mdc applies to the callback signature if the callback form wins.
  • Each event payload should be unmarshalled into the model derived from the response schema, the same way eventsource/fetch.ts does today, so streaming is as typed as the non-streaming path.
  • Needs an answer for cancellation (AbortSignal) and for stream errors mid-flight, neither of which the current HttpClientResponse wrapper expresses.
  • Also consider application/x-ndjson, which shows up for the same use cases and is cheaper to parse.

Acceptance criteria

  • An operation with a text/event-stream response generates a consumable, typed stream rather than a function that throws.
  • No duplicated SSE implementation — the OpenAPI and AsyncAPI paths share the underlying consumer.
  • Cancellation and mid-stream error handling are expressible by the caller.
  • Non-streaming operations are unaffected.
  • Covered at all three tiers per CLAUDE.md, with a runtime test consuming a real event stream and asserting typed events arrive in order.

Per "Expected Output First", the intended streaming call shape should be hand-written in test/runtime/typescript/ first — the API surface is the actual decision here; the transport is largely solved.

Depends on

#457 (response content-type branching) — SSE detection is the same dispatch point as binary/text handling at client.ts:289. That one should probably land first and this one plugs into it. #458 (multipart request bodies) covers the request-side half of the same gap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions