0122f38e09
publish-github-action / publish (push) Has been cancelled
Unify declared instance HTTP API endpoints under typed middleware routing, including event streaming and PTY WebSocket connect handling.\n\nPreserve PTY connect compatibility by checking missing PTYs before parsing optional cursor and ticket query fields, with regression coverage.
173 lines
6.5 KiB
TypeScript
173 lines
6.5 KiB
TypeScript
import { Pty } from "@/pty"
|
|
import { PtyTicket } from "@/pty/ticket"
|
|
import { PtyID } from "@/pty/schema"
|
|
import { PTY_CONNECT_TICKET_QUERY } from "@/server/shared/pty-ticket"
|
|
import { Schema } from "effect"
|
|
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
|
import { Authorization, PtyConnectAuthorization } from "../middleware/authorization"
|
|
import { InstanceContextMiddleware } from "../middleware/instance-context"
|
|
import {
|
|
WorkspaceRoutingMiddleware,
|
|
WorkspaceRoutingQuery,
|
|
WorkspaceRoutingQueryFields,
|
|
} from "../middleware/workspace-routing"
|
|
import { PtyForbiddenError, PtyNotFoundError } from "../errors"
|
|
import { described } from "./metadata"
|
|
|
|
const root = "/pty"
|
|
export const Params = Schema.Struct({ ptyID: PtyID })
|
|
export const CursorQuery = Schema.Struct({
|
|
...WorkspaceRoutingQueryFields,
|
|
cursor: Schema.optional(Schema.String),
|
|
})
|
|
export const ShellItem = Schema.Struct({
|
|
path: Schema.String,
|
|
name: Schema.String,
|
|
acceptable: Schema.Boolean,
|
|
})
|
|
|
|
export const PtyPaths = {
|
|
shells: `${root}/shells`,
|
|
list: root,
|
|
create: root,
|
|
get: `${root}/:ptyID`,
|
|
update: `${root}/:ptyID`,
|
|
remove: `${root}/:ptyID`,
|
|
connectToken: `${root}/:ptyID/connect-token`,
|
|
connect: `${root}/:ptyID/connect`,
|
|
} as const
|
|
|
|
export const PtyApi = HttpApi.make("pty")
|
|
.add(
|
|
HttpApiGroup.make("pty")
|
|
.add(
|
|
HttpApiEndpoint.get("shells", PtyPaths.shells, {
|
|
query: WorkspaceRoutingQuery,
|
|
success: described(Schema.Array(ShellItem), "List of shells"),
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.shells",
|
|
summary: "List available shells",
|
|
description: "Get a list of available shells on the system.",
|
|
}),
|
|
),
|
|
HttpApiEndpoint.get("list", PtyPaths.list, {
|
|
query: WorkspaceRoutingQuery,
|
|
success: described(Schema.Array(Pty.Info), "List of sessions"),
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.list",
|
|
summary: "List PTY sessions",
|
|
description: "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.",
|
|
}),
|
|
),
|
|
HttpApiEndpoint.post("create", PtyPaths.create, {
|
|
query: WorkspaceRoutingQuery,
|
|
payload: Pty.CreateInput,
|
|
success: described(Pty.Info, "Created session"),
|
|
error: HttpApiError.BadRequest,
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.create",
|
|
summary: "Create PTY session",
|
|
description: "Create a new pseudo-terminal (PTY) session for running shell commands and processes.",
|
|
}),
|
|
),
|
|
HttpApiEndpoint.get("get", PtyPaths.get, {
|
|
params: { ptyID: PtyID },
|
|
query: WorkspaceRoutingQuery,
|
|
success: described(Pty.Info, "Session info"),
|
|
error: PtyNotFoundError,
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.get",
|
|
summary: "Get PTY session",
|
|
description: "Retrieve detailed information about a specific pseudo-terminal (PTY) session.",
|
|
}),
|
|
),
|
|
HttpApiEndpoint.put("update", PtyPaths.update, {
|
|
params: { ptyID: PtyID },
|
|
query: WorkspaceRoutingQuery,
|
|
payload: Pty.UpdateInput,
|
|
success: described(Pty.Info, "Updated session"),
|
|
error: [PtyNotFoundError, HttpApiError.BadRequest],
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.update",
|
|
summary: "Update PTY session",
|
|
description: "Update properties of an existing pseudo-terminal (PTY) session.",
|
|
}),
|
|
),
|
|
HttpApiEndpoint.delete("remove", PtyPaths.remove, {
|
|
params: { ptyID: PtyID },
|
|
query: WorkspaceRoutingQuery,
|
|
success: described(Schema.Boolean, "Session removed"),
|
|
error: PtyNotFoundError,
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.remove",
|
|
summary: "Remove PTY session",
|
|
description: "Remove and terminate a specific pseudo-terminal (PTY) session.",
|
|
}),
|
|
),
|
|
HttpApiEndpoint.post("connectToken", PtyPaths.connectToken, {
|
|
params: { ptyID: PtyID },
|
|
query: WorkspaceRoutingQuery,
|
|
success: described(PtyTicket.ConnectToken, "WebSocket connect token"),
|
|
error: [PtyForbiddenError, PtyNotFoundError],
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.connectToken",
|
|
summary: "Create PTY WebSocket token",
|
|
description: "Create a short-lived ticket for opening a PTY WebSocket connection.",
|
|
}),
|
|
),
|
|
)
|
|
.annotateMerge(OpenApi.annotations({ title: "pty", description: "Experimental HttpApi PTY routes." }))
|
|
.middleware(InstanceContextMiddleware)
|
|
.middleware(WorkspaceRoutingMiddleware)
|
|
.middleware(Authorization),
|
|
)
|
|
.annotateMerge(
|
|
OpenApi.annotations({
|
|
title: "opencode experimental HttpApi",
|
|
version: "0.0.1",
|
|
description: "Experimental HttpApi surface for selected instance routes.",
|
|
}),
|
|
)
|
|
|
|
export const PtyConnectApi = HttpApi.make("pty-connect").add(
|
|
HttpApiGroup.make("pty-connect")
|
|
.add(
|
|
// Decode PTY connection query fields in the raw handler after checking
|
|
// existence, preserving the established empty-404 response ordering.
|
|
HttpApiEndpoint.get("connect", PtyPaths.connect, {
|
|
params: Params,
|
|
success: described(Schema.Boolean, "Connected session"),
|
|
error: [HttpApiError.Forbidden, HttpApiError.NotFound],
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "pty.connect",
|
|
summary: "Connect to PTY session",
|
|
description:
|
|
"Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time.",
|
|
transform: (operation) => ({
|
|
...operation,
|
|
parameters: [
|
|
...(operation.parameters ?? []),
|
|
...["directory", "workspace", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({
|
|
in: "query",
|
|
name,
|
|
schema: { type: "string" },
|
|
})),
|
|
],
|
|
}),
|
|
}),
|
|
),
|
|
)
|
|
.annotateMerge(OpenApi.annotations({ title: "pty", description: "PTY websocket route." }))
|
|
.middleware(InstanceContextMiddleware)
|
|
.middleware(WorkspaceRoutingMiddleware)
|
|
.middleware(PtyConnectAuthorization),
|
|
)
|