fix(client): accept larger SSE events (#36442)
This commit is contained in:
@@ -1,4 +1,9 @@
|
|||||||
export type ClientErrorReason = "Transport" | "UnexpectedStatus" | "UnsupportedContentType" | "MalformedResponse"
|
export type ClientErrorReason =
|
||||||
|
| "Transport"
|
||||||
|
| "UnexpectedStatus"
|
||||||
|
| "UnsupportedContentType"
|
||||||
|
| "MalformedResponse"
|
||||||
|
| "SseEventTooLarge"
|
||||||
|
|
||||||
export class ClientError extends Error {
|
export class ClientError extends Error {
|
||||||
override readonly name = "ClientError"
|
override readonly name = "ClientError"
|
||||||
|
|||||||
@@ -213,6 +213,8 @@ interface RequestDescriptor {
|
|||||||
readonly binary?: true
|
readonly binary?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxSseEventBytes = 16 * 1024 * 1024
|
||||||
|
|
||||||
export function make(options: ClientOptions) {
|
export function make(options: ClientOptions) {
|
||||||
const fetch = options.fetch ?? globalThis.fetch
|
const fetch = options.fetch ?? globalThis.fetch
|
||||||
|
|
||||||
@@ -289,7 +291,7 @@ export function make(options: ClientOptions) {
|
|||||||
throw new ClientError("Transport", { cause })
|
throw new ClientError("Transport", { cause })
|
||||||
}
|
}
|
||||||
buffer += decoder.decode(next.value, { stream: !next.done })
|
buffer += decoder.decode(next.value, { stream: !next.done })
|
||||||
if (buffer.length > 1_048_576) throw new ClientError("MalformedResponse")
|
if (buffer.length > maxSseEventBytes) throw new ClientError("SseEventTooLarge")
|
||||||
const trailingCarriageReturn = !next.done && buffer.endsWith("\r")
|
const trailingCarriageReturn = !next.done && buffer.endsWith("\r")
|
||||||
if (trailingCarriageReturn) buffer = buffer.slice(0, -1)
|
if (trailingCarriageReturn) buffer = buffer.slice(0, -1)
|
||||||
buffer = buffer.replaceAll("\r\n", "\n").replaceAll("\r", "\n")
|
buffer = buffer.replaceAll("\r\n", "\n").replaceAll("\r", "\n")
|
||||||
|
|||||||
@@ -284,6 +284,43 @@ test("event.subscribe terminates on malformed Promise SSE data", async () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("event.subscribe accepts a fragmented SSE event below the size limit", async () => {
|
||||||
|
const event = { id: "evt_large", type: "test.large", data: { output: "x".repeat(12 * 1024 * 1024) } }
|
||||||
|
const encoded = new TextEncoder().encode(`data: ${JSON.stringify(event)}\n\n`)
|
||||||
|
const client = OpenCode.make({
|
||||||
|
baseUrl: "http://localhost:3000",
|
||||||
|
fetch: async () =>
|
||||||
|
new Response(
|
||||||
|
new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
for (let offset = 0; offset < encoded.length; offset += 64 * 1024) {
|
||||||
|
controller.enqueue(encoded.slice(offset, offset + 64 * 1024))
|
||||||
|
}
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ headers: { "content-type": "text/event-stream" } },
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(client.event.subscribe()[Symbol.asyncIterator]().next()).resolves.toEqual({ done: false, value: event })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("event.subscribe rejects an SSE event above the size limit", async () => {
|
||||||
|
const client = OpenCode.make({
|
||||||
|
baseUrl: "http://localhost:3000",
|
||||||
|
fetch: async () =>
|
||||||
|
new Response(`data: ${JSON.stringify({ output: "x".repeat(16 * 1024 * 1024) })}`, {
|
||||||
|
headers: { "content-type": "text/event-stream" },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(client.event.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
|
||||||
|
name: "ClientError",
|
||||||
|
reason: "SseEventTooLarge",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("session methods use the public HTTP contract", async () => {
|
test("session methods use the public HTTP contract", async () => {
|
||||||
const requests: Array<{ url: string; init?: RequestInit }> = []
|
const requests: Array<{ url: string; init?: RequestInit }> = []
|
||||||
const client = OpenCode.make({
|
const client = OpenCode.make({
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user