diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index bc727b02857..a8d01fda62e 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -2714,7 +2714,8 @@ export namespace Server { "/event", describeRoute({ summary: "Subscribe to events", - description: "Get events", + description: + "Subscribe to server-sent events. Optionally filter events by session ID to only receive events for a specific session.", operationId: "event.subscribe", responses: { 200: { @@ -2727,8 +2728,15 @@ export namespace Server { }, }, }), + validator( + "query", + z.object({ + session: z.string().optional().meta({ description: "Filter events by session ID" }), + }), + ), async (c) => { - log.info("event connected") + const sessionFilter = c.req.valid("query").session + log.info("event connected", { session: sessionFilter }) return streamSSE(c, async (stream) => { stream.writeSSE({ data: JSON.stringify({ @@ -2737,6 +2745,19 @@ export namespace Server { }), }) const unsub = Bus.subscribeAll(async (event) => { + // Filter by session if specified + if (sessionFilter) { + const eventSessionID = + event.properties?.sessionID || event.properties?.info?.sessionID || event.properties?.part?.sessionID + // Only allow server.* events (connected, heartbeat, etc.) to pass through without sessionID + // All other events without sessionID are filtered out to prevent cross-session leakage + if (!eventSessionID && !event.type.startsWith("server.")) { + return + } + if (eventSessionID && eventSessionID !== sessionFilter) { + return + } + } await stream.writeSSE({ data: JSON.stringify(event), }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 702af632457..cc6a11cce55 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -2810,15 +2810,26 @@ export class Event extends HeyApiClient { /** * Subscribe to events * - * Get events + * Subscribe to server-sent events. Optionally filter events by session ID to only receive events for a specific session. */ 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 f083dc85d6e..f5444159f73 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -4513,6 +4513,10 @@ export type EventSubscribeData = { path?: never query?: { directory?: string + /** + * Filter events by session ID + */ + session?: string } url: "/event" } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 0ace5a84e47..b678a5e3327 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -5285,10 +5285,18 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "session", + "schema": { + "type": "string" + }, + "description": "Filter events by session ID" } ], "summary": "Subscribe to events", - "description": "Get events", + "description": "Subscribe to server-sent events. Optionally filter events by session ID to only receive events for a specific session.", "responses": { "200": { "description": "Event stream",