chore: merge dev into v2 (#34317)
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com> Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: James Long <longster@gmail.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Ben Guthrie <benjee.012@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com>
This commit is contained in:
co-authored by
Brendan Allan
opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Affan Ali
affanali2k3
Frank
opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
James Long
Aiden Cline
Jay V
Dax Raad
Aarav Sareen
OpeOginni
Luke Parker
Ben Guthrie
Dax
Filip
parent
11bf8d8a42
commit
41283933ff
@@ -1067,6 +1067,40 @@ const scenarios: Scenario[] = [
|
||||
headers: ctx.headers(),
|
||||
}))
|
||||
.status(400, undefined, "none"),
|
||||
http.protected
|
||||
.get("/api/session/{sessionID}/history", "v2.session.history")
|
||||
.seeded((ctx) => ctx.session({ title: "Session history" }))
|
||||
.at((ctx) => ({
|
||||
path: `${route("/api/session/{sessionID}/history", { sessionID: ctx.state.id })}?${new URLSearchParams({
|
||||
after: "0",
|
||||
limit: "2",
|
||||
})}`,
|
||||
headers: ctx.headers(),
|
||||
}))
|
||||
.json(
|
||||
200,
|
||||
(body) => {
|
||||
object(body)
|
||||
array(body.data)
|
||||
check(typeof body.hasMore === "boolean", "Expected a history exhaustion signal")
|
||||
},
|
||||
"none",
|
||||
),
|
||||
http.protected
|
||||
.get("/api/session/{sessionID}/history", "v2.session.history.missing")
|
||||
.at((ctx) => ({
|
||||
path: route("/api/session/{sessionID}/history", { sessionID: "ses_httpapi_missing" }),
|
||||
headers: ctx.headers(),
|
||||
}))
|
||||
.json(404, object, "status"),
|
||||
http.protected
|
||||
.get("/api/session/{sessionID}/history", "v2.session.history.invalid")
|
||||
.seeded((ctx) => ctx.session({ title: "Invalid history sequence" }))
|
||||
.at((ctx) => ({
|
||||
path: `${route("/api/session/{sessionID}/history", { sessionID: ctx.state.id })}?after=-1`,
|
||||
headers: ctx.headers(),
|
||||
}))
|
||||
.json(400, object, "status"),
|
||||
http.protected
|
||||
.get("/api/session/{sessionID}/event", "v2.session.events.missing")
|
||||
.at((ctx) => ({
|
||||
|
||||
@@ -10,6 +10,8 @@ type OpenApiSchema = {
|
||||
readonly enum?: readonly unknown[]
|
||||
readonly properties?: Record<string, OpenApiSchema>
|
||||
readonly required?: readonly string[]
|
||||
readonly contentSchema?: OpenApiSchema
|
||||
readonly contentMediaType?: string
|
||||
}
|
||||
type OpenApiResponse = {
|
||||
readonly description?: string
|
||||
@@ -99,6 +101,21 @@ describe("PublicApi OpenAPI v2 errors", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("names the v2 event union without the SSE string wrapper collision", () => {
|
||||
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
|
||||
|
||||
expect(spec.components.schemas.V2Event1).toBeUndefined()
|
||||
expect(spec.components.schemas.V2Event?.anyOf?.length).toBeGreaterThan(0)
|
||||
expect(spec.components.schemas.V2EventStream).toMatchObject({
|
||||
type: "string",
|
||||
contentMediaType: "application/json",
|
||||
contentSchema: { $ref: "#/components/schemas/V2Event" },
|
||||
})
|
||||
expect(spec.paths["/api/event"]?.get?.responses?.["200"]?.content?.["text/event-stream"]?.schema).toEqual({
|
||||
$ref: "#/components/schemas/V2Event",
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves /api auth responses", () => {
|
||||
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
|
||||
|
||||
|
||||
@@ -27,13 +27,44 @@ const Event = Schema.Struct({
|
||||
data: Schema.Unknown,
|
||||
})
|
||||
|
||||
async function readEvent(reader: ReadableStreamDefaultReader<Uint8Array>) {
|
||||
const value = await reader.read()
|
||||
if (value.done) throw new Error("event stream closed")
|
||||
return Schema.decodeUnknownSync(Event)(JSON.parse(new TextDecoder().decode(value.value).replace(/^data: /, "")))
|
||||
async function* eventStream(body: ReadableStream<Uint8Array>) {
|
||||
const reader = body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ""
|
||||
try {
|
||||
while (true) {
|
||||
const boundary = buffer.match(/(?:\r\n|\r|\n){2}/)
|
||||
if (!boundary || boundary.index === undefined) {
|
||||
const value = await reader.read()
|
||||
if (value.done) return
|
||||
buffer += decoder.decode(value.value, { stream: true })
|
||||
continue
|
||||
}
|
||||
|
||||
const record = buffer.slice(0, boundary.index)
|
||||
buffer = buffer.slice(boundary.index + boundary[0].length)
|
||||
const data = record
|
||||
.split(/\r\n|\r|\n/)
|
||||
.filter((line) => line.startsWith("data:"))
|
||||
.map((line) => line.slice(5).replace(/^ /, ""))
|
||||
if (data.length) yield Schema.decodeUnknownSync(Event)(JSON.parse(data.join("\n")))
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
await reader.cancel()
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function readEventType(reader: ReadableStreamDefaultReader<Uint8Array>, type: string) {
|
||||
async function readEvent(reader: AsyncIterator<typeof Event.Type>) {
|
||||
const value = await reader.next()
|
||||
if (value.done) throw new Error("event stream closed")
|
||||
return value.value
|
||||
}
|
||||
|
||||
async function readEventType(reader: AsyncIterator<typeof Event.Type>, type: string) {
|
||||
for (let index = 0; index < 20; index++) {
|
||||
const event = await readEvent(reader)
|
||||
if (event.type === type) return event
|
||||
@@ -78,7 +109,7 @@ describe("v2 location HttpApi", () => {
|
||||
await using subscriber = await tmpdir({ git: true })
|
||||
await using publisher = await tmpdir({ git: true })
|
||||
const response = await request("/api/event", subscriber.path)
|
||||
const reader = response.body!.getReader()
|
||||
const reader = eventStream(response.body!)
|
||||
const connected = await readEvent(reader)
|
||||
expect(connected.type).toBe("server.connected")
|
||||
expect(connected.location).toBeUndefined()
|
||||
@@ -90,6 +121,6 @@ describe("v2 location HttpApi", () => {
|
||||
location: { directory: publisher.path },
|
||||
data: { sessionID: expect.any(String) },
|
||||
})
|
||||
await reader.cancel()
|
||||
await reader.return(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user