-
-
Notifications
You must be signed in to change notification settings - Fork 127
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Summary
I'm trying to implement the OpenResponses API specification using oRPC, but I've hit a limitation: the API uses a single endpoint that returns different response types based on a stream field in the request body.
The OpenResponses Pattern
The spec defines a single endpoint POST /responses where:
stream: false(or absent) → Returnsapplication/jsonwithResponseResourcestream: true→ Returnstext/event-streamwith SSE streaming events
OpenAPI Spec (from official spec)
{
"paths": {
"/responses": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateResponseBody"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ResponseResource" }
},
"text/event-stream": {
"schema": {
"oneOf": [
{ "$ref": "#/components/schemas/ResponseCreatedStreamingEvent" },
{ "$ref": "#/components/schemas/ResponseCompletedStreamingEvent" }
]
}
}
}
}
}
}
}
}
}Full spec: https://github.com/openresponses/openresponses/blob/main/public/openapi/openapi.json
The Challenge with oRPC
oRPC's contract model assumes one procedure → one output type:
// This works for separate endpoints
const nonStreaming = oc
.route({ method: 'POST', path: '/responses' })
.input(CreateResponseBody)
.output(ResponseResource)
const streaming = oc
.route({ method: 'POST', path: '/responses/stream' })
.input(CreateResponseBody)
.output(eventIterator(StreamingEvent))But I can't define a single procedure that:
- Has one path (
/responses) - Returns
ResponseResourcewheninput.stream === false - Returns
AsyncIterator<StreamingEvent>wheninput.stream === true
Attempted Workarounds
- Two procedures, same path - oRPC doesn't allow this (path conflict)
- Union output type - Can't union a value type with an iterator
- Custom handler logic - Loses type safety on the output
Proposed Solutions
Option A: Conditional output based on input field
const createResponse = oc
.route({ method: 'POST', path: '/responses' })
.input(CreateResponseBody)
.output(ResponseResource, { when: (input) => !input.stream })
.output(eventIterator(StreamingEvent), { when: (input) => input.stream })Option B: Multiple media type outputs
const createResponse = oc
.route({ method: 'POST', path: '/responses' })
.input(CreateResponseBody)
.output({
'application/json': ResponseResource,
'text/event-stream': eventIterator(StreamingEvent),
})Option C: Output type that can be either value or iterator
const createResponse = oc
.route({ method: 'POST', path: '/responses' })
.input(CreateResponseBody)
.output(z.union([ResponseResource, eventIterator(StreamingEvent)]))Context
This pattern is common in LLM APIs:
- OpenAI uses it for
/chat/completions - Anthropic uses it for
/messages - OpenResponses standardizes it
Being able to implement this pattern with oRPC would enable type-safe implementations of these popular API patterns.
Environment
- oRPC version: 1.4.3
- Target: Cloudflare Workers (fetch adapter)
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request