From f01638671cd3372c33e0435e8317daa01068fb5b Mon Sep 17 00:00:00 2001 From: Phong Do Date: Tue, 24 Feb 2026 23:03:08 -0800 Subject: [PATCH 1/2] fix(opencode): document session query param for event stream --- packages/opencode/src/server/server.ts | 7 +++++ .../test/server/event-openapi.test.ts | 28 +++++++++++++++++++ packages/sdk/js/src/gen/types.gen.ts | 1 + packages/sdk/js/src/v2/gen/sdk.gen.ts | 13 ++++++++- packages/sdk/js/src/v2/gen/types.gen.ts | 1 + 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/server/event-openapi.test.ts diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 9fba9c1fe1a0..5ad1b70f79c2 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -499,6 +499,13 @@ export namespace Server { }, }, }), + validator( + "query", + z.object({ + directory: z.string().optional(), + session: z.string().optional(), + }), + ), async (c) => { log.info("event connected") c.header("X-Accel-Buffering", "no") diff --git a/packages/opencode/test/server/event-openapi.test.ts b/packages/opencode/test/server/event-openapi.test.ts new file mode 100644 index 000000000000..9319f932af83 --- /dev/null +++ b/packages/opencode/test/server/event-openapi.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "bun:test" +import { Log } from "../../src/util/log" +import { Server } from "../../src/server/server" + +Log.init({ print: false }) + +describe("event endpoint contract", () => { + test("documents directory and session query params", async () => { + const specs = await Server.openapi() + const params = specs.paths["/event"]?.get?.parameters ?? [] + const names = params.flatMap((item) => ("name" in item && typeof item.name === "string" ? [item.name] : [])).sort() + expect(names).toEqual(["directory", "session"]) + }) + + test("returns SSE when session query param is provided", async () => { + const response = await Server.App().request("/event?session=ses_test") + expect(response.status).toBe(200) + expect(response.headers.get("content-type")).toContain("text/event-stream") + await response.body?.cancel() + }) + + test("returns SSE without session query param", async () => { + const response = await Server.App().request("/event") + expect(response.status).toBe(200) + expect(response.headers.get("content-type")).toContain("text/event-stream") + await response.body?.cancel() + }) +}) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 8eefe5bfe985..bc7adc6892fa 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -3886,6 +3886,7 @@ export type EventSubscribeData = { path?: never query?: { directory?: string + session?: string } url: "/event" } diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index b4848e605404..f6e0d46dae4d 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -3219,10 +3219,21 @@ export class Event extends HeyApiClient { public subscribe( parameters?: { directory?: string + session?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "session" }, + ], + }, + ], + ) return (options?.client ?? this.client).sse.get({ url: "/event", ...options, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 4050ef15738c..351534fdf89a 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -5135,6 +5135,7 @@ export type EventSubscribeData = { path?: never query?: { directory?: string + session?: string } url: "/event" } From a7401f0e7f4fd8a6919c8ee22e2af064ff9e186d Mon Sep 17 00:00:00 2001 From: Phong Do Date: Tue, 24 Feb 2026 23:18:16 -0800 Subject: [PATCH 2/2] test(opencode): harden event OpenAPI assertions --- .../test/server/event-openapi.test.ts | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/opencode/test/server/event-openapi.test.ts b/packages/opencode/test/server/event-openapi.test.ts index 9319f932af83..84990c2b3cef 100644 --- a/packages/opencode/test/server/event-openapi.test.ts +++ b/packages/opencode/test/server/event-openapi.test.ts @@ -1,28 +1,47 @@ import { describe, expect, test } from "bun:test" +import path from "path" +import { Instance } from "../../src/project/instance" import { Log } from "../../src/util/log" import { Server } from "../../src/server/server" +const projectRoot = path.join(__dirname, "../..") Log.init({ print: false }) describe("event endpoint contract", () => { test("documents directory and session query params", async () => { const specs = await Server.openapi() const params = specs.paths["/event"]?.get?.parameters ?? [] - const names = params.flatMap((item) => ("name" in item && typeof item.name === "string" ? [item.name] : [])).sort() + const names = params + .flatMap((item) => + "name" in item && typeof item.name === "string" && "in" in item && item.in === "query" ? [item.name] : [], + ) + .sort() expect(names).toEqual(["directory", "session"]) }) test("returns SSE when session query param is provided", async () => { - const response = await Server.App().request("/event?session=ses_test") - expect(response.status).toBe(200) - expect(response.headers.get("content-type")).toContain("text/event-stream") - await response.body?.cancel() + await Instance.provide({ + directory: projectRoot, + fn: async () => { + const response = await Server.App().request( + `/event?directory=${encodeURIComponent(projectRoot)}&session=ses_test`, + ) + expect(response.status).toBe(200) + expect(response.headers.get("content-type")).toContain("text/event-stream") + await response.body?.cancel() + }, + }) }) test("returns SSE without session query param", async () => { - const response = await Server.App().request("/event") - expect(response.status).toBe(200) - expect(response.headers.get("content-type")).toContain("text/event-stream") - await response.body?.cancel() + await Instance.provide({ + directory: projectRoot, + fn: async () => { + const response = await Server.App().request(`/event?directory=${encodeURIComponent(projectRoot)}`) + expect(response.status).toBe(200) + expect(response.headers.get("content-type")).toContain("text/event-stream") + await response.body?.cancel() + }, + }) }) })