diff --git a/packages/core/src/pty.ts b/packages/core/src/pty.ts index 427444d91a..d706199174 100644 --- a/packages/core/src/pty.ts +++ b/packages/core/src/pty.ts @@ -78,6 +78,7 @@ export class ExitedError extends Schema.TaggedErrorClass()("Pty.Exi }) {} export interface Interface { + readonly shells: () => Effect.Effect> readonly list: () => Effect.Effect readonly get: (id: PtyID) => Effect.Effect readonly create: (input: CreateInput) => Effect.Effect @@ -158,6 +159,10 @@ export const layer = (options?: ShellSelect.Options) => Layer.effect( return Array.from(sessions.values()).map((session) => session.info) }) + const shells = Effect.fn("Pty.shells")(function* () { + return yield* Effect.promise(() => ShellSelect.list(options)) + }) + const get = Effect.fn("Pty.get")(function* (id: PtyID) { return (yield* requireSession(id)).info }) @@ -309,7 +314,7 @@ export const layer = (options?: ShellSelect.Options) => Layer.effect( } }) - return Service.of({ list, get, create, update, remove, write, attach }) + return Service.of({ shells, list, get, create, update, remove, write, attach }) }), ) diff --git a/packages/core/test/pty/pty-session.test.ts b/packages/core/test/pty/pty-session.test.ts index d11f36fc0b..45a858cc34 100644 --- a/packages/core/test/pty/pty-session.test.ts +++ b/packages/core/test/pty/pty-session.test.ts @@ -90,6 +90,18 @@ const waitForOutput = (output: Queue.Queue, text: string) => }), ) +describe("Pty.shells", () => { + it.live("lists available shells", () => + Effect.gen(function* () { + const pty = yield* Pty.Service + const shells = yield* pty.shells() + + expect(shells.length).toBeGreaterThan(0) + expect(shells.every((shell) => shell.path && shell.name && typeof shell.acceptable === "boolean")).toBe(true) + }), + ) +}) + describe("pty", () => { it.live("returns typed not found errors for missing sessions", () => Effect.gen(function* () { diff --git a/packages/protocol/src/client.ts b/packages/protocol/src/client.ts index 0669ccea34..f01bccd82f 100644 --- a/packages/protocol/src/client.ts +++ b/packages/protocol/src/client.ts @@ -63,5 +63,5 @@ export const groupNames = { "server.path": "path", } as const -export const promiseOmitEndpoints = new Set(["pty.connect", "pty.connectToken"]) -export const effectOmitEndpoints = new Set(["fs.read", "pty.connect", "pty.connectToken"]) +export const promiseOmitEndpoints = new Set(["pty.connect"]) +export const effectOmitEndpoints = new Set(["fs.read", "pty.connect"]) diff --git a/packages/protocol/src/groups/pty.ts b/packages/protocol/src/groups/pty.ts index a8fd1a3bc9..db2cb5849d 100644 --- a/packages/protocol/src/groups/pty.ts +++ b/packages/protocol/src/groups/pty.ts @@ -19,6 +19,20 @@ export function hasPtyConnectTicketURL(url: URL) { } export const PtyGroup = HttpApiGroup.make("server.pty") + .add( + HttpApiEndpoint.get("pty.shells", "/api/pty/shells", { + query: LocationQuery, + success: Location.response(Schema.Array(Pty.Shell)), + }) + .annotateMerge(locationQueryOpenApi) + .annotateMerge( + OpenApi.annotations({ + identifier: "v2.pty.shells", + summary: "List available shells", + description: "List shells available for interactive terminal sessions.", + }), + ), + ) .add( HttpApiEndpoint.get("pty.list", "/api/pty", { query: LocationQuery, @@ -101,13 +115,14 @@ export const PtyGroup = HttpApiGroup.make("server.pty") HttpApiEndpoint.post("pty.connectToken", "/api/pty/:ptyID/connect-token", { params: { ptyID: Pty.ID }, query: LocationQuery, + headers: Schema.Struct({ [PTY_CONNECT_TOKEN_HEADER]: Schema.optional(Schema.String) }), success: Location.response(PtyTicket.ConnectToken), error: [ForbiddenError, PtyNotFoundError], }) .annotateMerge(locationQueryOpenApi) .annotateMerge( OpenApi.annotations({ - identifier: "v2.pty.connect.token", + identifier: "v2.pty.connectToken", summary: "Create PTY WebSocket token", description: "Create a short-lived single-use ticket for opening a PTY WebSocket connection.", }), diff --git a/packages/schema/src/pty.ts b/packages/schema/src/pty.ts index b2da9093f0..ea67564f3f 100644 --- a/packages/schema/src/pty.ts +++ b/packages/schema/src/pty.ts @@ -56,3 +56,10 @@ export const UpdateInput = Schema.Struct({ ), }) export interface UpdateInput extends Schema.Schema.Type {} + +export const Shell = Schema.Struct({ + path: Schema.String, + name: Schema.String, + acceptable: Schema.Boolean, +}).annotate({ identifier: "Pty.Shell" }) +export interface Shell extends Schema.Schema.Type {} diff --git a/packages/server/src/handlers/pty.ts b/packages/server/src/handlers/pty.ts index 7f8a9ea64e..b8dab11a4d 100644 --- a/packages/server/src/handlers/pty.ts +++ b/packages/server/src/handlers/pty.ts @@ -29,6 +29,13 @@ export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) => const environment = yield* PtyEnvironment.Service return handlers + .handle( + "pty.shells", + Effect.fn(function* () { + const pty = yield* Pty.Service + return yield* response(pty.shells()) + }), + ) .handle( "pty.list", Effect.fn(function* () {