openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

AsyncGenerator of a text/event-stream returns the data only

Open jscarle opened this issue 3 months ago • 4 comments

Description

I have a problem with the AsyncGenerator of a text/event-stream returning the data and not the event. The typescript is typed to the event, but it's the data that comes back.

The endpoint returns as stream like this:

event: created
data: {"conversationId":"conv_d6a3363ed00b45fe952c5e7e46195f88"}
id: 00000000

event: output-item-added
data: {"itemId":"rs_015a1b95e8d45cba0068c5ff508ed08194ae8e1bfd49ae531e","outputIndex":0}
id: 00000002

event: output-item-done
data: {"itemId":"rs_015a1b95e8d45cba0068c5ff508ed08194ae8e1bfd49ae531e","outputIndex":0}
id: 00000003

event: output-item-added
data: {"itemId":"msg_015a1b95e8d45cba0068c5ff52cc908194b709e372eed353df","outputIndex":1}
id: 00000004

event: content-part-added
data: {"contentIndex":0}
id: 00000005

event: output-text-delta-update
data: {"delta":"Bonjour"}
id: 00000006

But doing the following:

const result = await streamConversation();
for await (const item of result.stream) {
}

Typescript types it as:

export type SseItemOfChatResponse = {
  data?: ChatResponse
  eventType?: string | null
  eventId?: string | null
  reconnectionInterval?: string | null
}

But item is actually equal to ChatResponse, thus eventType and eventId are unaccessible.

The relevant OpenAPI parts:

"/conversations/stream": {
  "post": {
    "operationId": "StreamConversation",
    "responses": {
      "200": {
        "description": "OK",
        "content": {
          "text/event-stream": {
            "schema": {
              "$ref": "#/components/schemas/SseItemOfChatResponse"
            }
          }
        }
      }
    }
  }
}
"SseItemOfChatResponse": {
  "type": "object",
  "properties": {
    "data": {
      "$ref": "#/components/schemas/ChatResponse"
    },
    "eventType": {
      "type": [
        "null",
        "string"
      ]
    },
    "eventId": {
      "type": [
        "null",
        "string"
      ]
    },
    "reconnectionInterval": {
      "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$",
      "type": [
        "null",
        "string"
      ]
    }
  }
}

Reproducible example or configuration

No response

OpenAPI specification (optional)

No response

System information (optional)

No response

jscarle avatar Sep 13 '25 23:09 jscarle

@jscarle What would be the fix here?

mrlubos avatar Sep 14 '25 05:09 mrlubos

I don't know, it seems like the parsing of the text/event-stream is off during API generation because it's outputing the data as "ChatResponse" instead of "SseItemOfChatResponse".

jscarle avatar Sep 14 '25 05:09 jscarle

I believe the correct fix here is to yield more than data in the relevant client code.

For example:

+              const baseSseEvent = {
+                data,
+                event: eventName,
+                id: lastEventId,
+              }
+
              onSseEvent?.({
-                data,
-                event: eventName,
-                id: lastEventId,
+              ...baseSseEvent,
                retry: retryDelay,
              });

              if (dataLines.length) {
-                yield data as any;
+                yield baseSseEvent as any;
              }

Theo-Steiner avatar Nov 17 '25 06:11 Theo-Steiner

Yes! Please fix this @mrlubos 🙏🏻

jscarle avatar Nov 17 '25 13:11 jscarle