From 84643f41927475e1967281e6fb59735ca737f66f Mon Sep 17 00:00:00 2001 From: Ryan Vogel Date: Wed, 18 Feb 2026 19:04:18 -0500 Subject: [PATCH] feat: move session listing to experimental routes with enhanced query parameters --- .../src/server/routes/experimental.ts | 60 +++++++++++++++++++ packages/opencode/src/server/routes/global.ts | 60 ------------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index 3c28331bd5..8d156c03d8 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -6,6 +6,7 @@ import { Worktree } from "../../worktree" import { Instance } from "../../project/instance" import { Project } from "../../project/project" import { MCP } from "../../mcp" +import { Session } from "../../session" import { zodToJsonSchema } from "zod-to-json-schema" import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -184,6 +185,65 @@ export const ExperimentalRoutes = lazy(() => return c.json(true) }, ) + .get( + "/session", + describeRoute({ + summary: "List sessions", + description: + "Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", + operationId: "experimental.session.list", + responses: { + 200: { + description: "List of sessions", + content: { + "application/json": { + schema: resolver(Session.GlobalInfo.array()), + }, + }, + }, + }, + }), + validator( + "query", + z.object({ + directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), + roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }), + start: z.coerce + .number() + .optional() + .meta({ description: "Filter sessions updated on or after this timestamp (milliseconds since epoch)" }), + cursor: z.coerce + .number() + .optional() + .meta({ description: "Return sessions updated before this timestamp (milliseconds since epoch)" }), + search: z.string().optional().meta({ description: "Filter sessions by title (case-insensitive)" }), + limit: z.coerce.number().optional().meta({ description: "Maximum number of sessions to return" }), + archived: z.coerce.boolean().optional().meta({ description: "Include archived sessions (default false)" }), + }), + ), + async (c) => { + const query = c.req.valid("query") + const limit = query.limit ?? 100 + const sessions: Session.GlobalInfo[] = [] + for await (const session of Session.listGlobal({ + directory: query.directory, + roots: query.roots, + start: query.start, + cursor: query.cursor, + search: query.search, + limit: limit + 1, + archived: query.archived, + })) { + sessions.push(session) + } + const hasMore = sessions.length > limit + const list = hasMore ? sessions.slice(0, limit) : sessions + if (hasMore && list.length > 0) { + c.header("x-next-cursor", String(list[list.length - 1].time.updated)) + } + return c.json(list) + }, + ) .get( "/resource", describeRoute({ diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index f7b965ef85..4d019f6a7e 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -9,7 +9,6 @@ import { Installation } from "@/installation" import { Log } from "../../util/log" import { lazy } from "../../util/lazy" import { Config } from "../../config/config" -import { Session } from "../../session" import { errors } from "../error" const log = Log.create({ service: "server" }) @@ -108,65 +107,6 @@ export const GlobalRoutes = lazy(() => }) }, ) - .get( - "/session", - describeRoute({ - summary: "List sessions", - description: - "Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", - operationId: "global.session.list", - responses: { - 200: { - description: "List of sessions", - content: { - "application/json": { - schema: resolver(Session.GlobalInfo.array()), - }, - }, - }, - }, - }), - validator( - "query", - z.object({ - directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), - roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }), - start: z.coerce - .number() - .optional() - .meta({ description: "Filter sessions updated on or after this timestamp (milliseconds since epoch)" }), - cursor: z.coerce - .number() - .optional() - .meta({ description: "Return sessions updated before this timestamp (milliseconds since epoch)" }), - search: z.string().optional().meta({ description: "Filter sessions by title (case-insensitive)" }), - limit: z.coerce.number().optional().meta({ description: "Maximum number of sessions to return" }), - archived: z.coerce.boolean().optional().meta({ description: "Include archived sessions (default false)" }), - }), - ), - async (c) => { - const query = c.req.valid("query") - const limit = query.limit ?? 100 - const sessions: Session.GlobalInfo[] = [] - for await (const session of Session.listGlobal({ - directory: query.directory, - roots: query.roots, - start: query.start, - cursor: query.cursor, - search: query.search, - limit: limit + 1, - archived: query.archived, - })) { - sessions.push(session) - } - const hasMore = sessions.length > limit - const list = hasMore ? sessions.slice(0, limit) : sessions - if (hasMore && list.length > 0) { - c.header("x-next-cursor", String(list[list.length - 1].time.updated)) - } - return c.json(list) - }, - ) .get( "/config", describeRoute({