diff --git a/packages/core/src/pty.ts b/packages/core/src/pty.ts index 7765013a5..479ad0932 100644 --- a/packages/core/src/pty.ts +++ b/packages/core/src/pty.ts @@ -278,8 +278,7 @@ export const layer = Layer.effect( const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) { const session = yield* requireSession(id) if (input.title) session.info.title = input.title - if (input.size && session.info.status === "running") - session.process.resize(input.size.cols, input.size.rows) + if (input.size && session.info.status === "running") session.process.resize(input.size.cols, input.size.rows) yield* events.publish(Event.Updated, { info: session.info }) return session.info }) diff --git a/packages/opencode/test/server/httpapi-v2-pty.test.ts b/packages/opencode/test/server/httpapi-v2-pty.test.ts index 0a2aa8bcb..054da2dc6 100644 --- a/packages/opencode/test/server/httpapi-v2-pty.test.ts +++ b/packages/opencode/test/server/httpapi-v2-pty.test.ts @@ -164,7 +164,10 @@ describe("v2 pty HttpApi", () => { expect(yield* takeUntil("ping-v2")).toContain("ping-v2") yield* write(new Socket.CloseEvent(1000, "done")).pipe(Effect.catch(() => Effect.void)) - const removed = yield* HttpClientRequest.delete(`/api/pty/${info.id}`).pipe(directoryHeader(dir), HttpClient.execute) + const removed = yield* HttpClientRequest.delete(`/api/pty/${info.id}`).pipe( + directoryHeader(dir), + HttpClient.execute, + ) expect(removed.status).toBe(204) }), ) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index b01c8fd04..3c52dd211 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -309,6 +309,20 @@ import type { V2ProviderGetResponses, V2ProviderListErrors, V2ProviderListResponses, + V2PtyConnectErrors, + V2PtyConnectResponses, + V2PtyConnectTokenErrors, + V2PtyConnectTokenResponses, + V2PtyCreateErrors, + V2PtyCreateResponses, + V2PtyGetErrors, + V2PtyGetResponses, + V2PtyListErrors, + V2PtyListResponses, + V2PtyRemoveErrors, + V2PtyRemoveResponses, + V2PtyUpdateErrors, + V2PtyUpdateResponses, V2QuestionRequestListErrors, V2QuestionRequestListResponses, V2ReferenceListErrors, @@ -6101,6 +6115,258 @@ export class Event2 extends HeyApiClient { } } +export class Pty2 extends HeyApiClient { + /** + * List PTY sessions + * + * List PTY sessions for a location, including exited sessions retained until removal. + */ + public list( + parameters?: { + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/pty", + ...options, + ...params, + }) + } + + /** + * Create PTY session + * + * Create a pseudo-terminal session for a location. + */ + public create( + parameters?: { + location?: { + directory?: string + workspace?: string + } + command?: string + args?: Array + cwd?: string + title?: string + env?: { + [key: string]: string + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "location" }, + { in: "body", key: "command" }, + { in: "body", key: "args" }, + { in: "body", key: "cwd" }, + { in: "body", key: "title" }, + { in: "body", key: "env" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/api/pty", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Remove PTY session + * + * Terminate and remove one PTY session. + */ + public remove( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete({ + url: "/api/pty/{ptyID}", + ...options, + ...params, + }) + } + + /** + * Get PTY session + * + * Get one PTY session, including its exit code once exited. + */ + public get( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/api/pty/{ptyID}", + ...options, + ...params, + }) + } + + /** + * Update PTY session + * + * Update the title or viewport size of one PTY session. + */ + public update( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } + title?: string + size?: { + rows: number + cols: number + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, + { in: "body", key: "title" }, + { in: "body", key: "size" }, + ], + }, + ], + ) + return (options?.client ?? this.client).put({ + url: "/api/pty/{ptyID}", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Create PTY WebSocket token + * + * Create a short-lived single-use ticket for opening a PTY WebSocket connection. + */ + public connectToken( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/api/pty/{ptyID}/connect-token", + ...options, + ...params, + }) + } + + /** + * Connect to PTY session + * + * Establish a WebSocket connection streaming PTY output and accepting terminal input. + */ + public connect( + parameters: { + ptyID: string + "location[directory]"?: string + "location[workspace]"?: string + cursor?: string + ticket?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location[directory]" }, + { in: "query", key: "location[workspace]" }, + { in: "query", key: "cursor" }, + { in: "query", key: "ticket" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/api/pty/{ptyID}/connect", + ...options, + ...params, + }) + } +} + export class Request2 extends HeyApiClient { /** * List pending question requests @@ -6342,6 +6608,11 @@ export class V2 extends HeyApiClient { return (this._event ??= new Event2({ client: this.client })) } + private _pty?: Pty2 + get pty(): Pty2 { + return (this._pty ??= new Pty2({ client: this.client })) + } + private _question?: Question3 get question(): Question3 { return (this._question ??= new Question3({ client: this.client })) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 334f5d469..fd6286a9d 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -650,6 +650,7 @@ export type Pty = { cwd: string status: "running" | "exited" pid: number + exitCode?: number } export type Todo = { @@ -2759,6 +2760,11 @@ export type ProviderNotFoundError = { message: string } +export type ForbiddenError = { + _tag: "ForbiddenError" + message: string +} + export type ProjectCopyError = { name: "ProjectCopyError" data: { @@ -10702,6 +10708,314 @@ export type V2EventSubscribeResponses = { export type V2EventSubscribeResponse = V2EventSubscribeResponses[keyof V2EventSubscribeResponses] +export type V2PtyListData = { + body?: never + path?: never + query?: { + location?: { + directory?: string + workspace?: string + } + } + url: "/api/pty" +} + +export type V2PtyListErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError +} + +export type V2PtyListError = V2PtyListErrors[keyof V2PtyListErrors] + +export type V2PtyListResponses = { + /** + * Success + */ + 200: { + location: LocationInfo + data: Array + } +} + +export type V2PtyListResponse = V2PtyListResponses[keyof V2PtyListResponses] + +export type V2PtyCreateData = { + body: { + command?: string + args?: Array + cwd?: string + title?: string + env?: { + [key: string]: string + } + } + path?: never + query?: { + location?: { + directory?: string + workspace?: string + } + } + url: "/api/pty" +} + +export type V2PtyCreateErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError +} + +export type V2PtyCreateError = V2PtyCreateErrors[keyof V2PtyCreateErrors] + +export type V2PtyCreateResponses = { + /** + * Success + */ + 200: { + location: LocationInfo + data: Pty + } +} + +export type V2PtyCreateResponse = V2PtyCreateResponses[keyof V2PtyCreateResponses] + +export type V2PtyRemoveData = { + body?: never + path: { + ptyID: string + } + query?: { + location?: { + directory?: string + workspace?: string + } + } + url: "/api/pty/{ptyID}" +} + +export type V2PtyRemoveErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * PtyNotFoundError + */ + 404: PtyNotFoundError +} + +export type V2PtyRemoveError = V2PtyRemoveErrors[keyof V2PtyRemoveErrors] + +export type V2PtyRemoveResponses = { + /** + * + */ + 204: void +} + +export type V2PtyRemoveResponse = V2PtyRemoveResponses[keyof V2PtyRemoveResponses] + +export type V2PtyGetData = { + body?: never + path: { + ptyID: string + } + query?: { + location?: { + directory?: string + workspace?: string + } + } + url: "/api/pty/{ptyID}" +} + +export type V2PtyGetErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * PtyNotFoundError + */ + 404: PtyNotFoundError +} + +export type V2PtyGetError = V2PtyGetErrors[keyof V2PtyGetErrors] + +export type V2PtyGetResponses = { + /** + * Success + */ + 200: { + location: LocationInfo + data: Pty + } +} + +export type V2PtyGetResponse = V2PtyGetResponses[keyof V2PtyGetResponses] + +export type V2PtyUpdateData = { + body: { + title?: string + size?: { + rows: number + cols: number + } + } + path: { + ptyID: string + } + query?: { + location?: { + directory?: string + workspace?: string + } + } + url: "/api/pty/{ptyID}" +} + +export type V2PtyUpdateErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * PtyNotFoundError + */ + 404: PtyNotFoundError +} + +export type V2PtyUpdateError = V2PtyUpdateErrors[keyof V2PtyUpdateErrors] + +export type V2PtyUpdateResponses = { + /** + * Success + */ + 200: { + location: LocationInfo + data: Pty + } +} + +export type V2PtyUpdateResponse = V2PtyUpdateResponses[keyof V2PtyUpdateResponses] + +export type V2PtyConnectTokenData = { + body?: never + path: { + ptyID: string + } + query?: { + location?: { + directory?: string + workspace?: string + } + } + url: "/api/pty/{ptyID}/connect-token" +} + +export type V2PtyConnectTokenErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * ForbiddenError + */ + 403: ForbiddenError + /** + * PtyNotFoundError + */ + 404: PtyNotFoundError +} + +export type V2PtyConnectTokenError = V2PtyConnectTokenErrors[keyof V2PtyConnectTokenErrors] + +export type V2PtyConnectTokenResponses = { + /** + * Success + */ + 200: { + location: LocationInfo + data: { + ticket: string + expires_in: number + } + } +} + +export type V2PtyConnectTokenResponse = V2PtyConnectTokenResponses[keyof V2PtyConnectTokenResponses] + +export type V2PtyConnectData = { + body?: never + path: { + ptyID: string + } + query?: { + "location[directory]"?: string + "location[workspace]"?: string + cursor?: string + ticket?: string + } + url: "/api/pty/{ptyID}/connect" +} + +export type V2PtyConnectErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * ForbiddenError + */ + 403: ForbiddenError + /** + * PtyNotFoundError + */ + 404: PtyNotFoundError +} + +export type V2PtyConnectError = V2PtyConnectErrors[keyof V2PtyConnectErrors] + +export type V2PtyConnectResponses = { + /** + * Success + */ + 200: boolean +} + +export type V2PtyConnectResponse = V2PtyConnectResponses[keyof V2PtyConnectResponses] + export type V2QuestionRequestListData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 0e943e301..449791120 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -12927,6 +12927,722 @@ ] } }, + "/api/pty": { + "get": { + "tags": ["pty"], + "operationId": "v2.pty.list", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "workspace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/LocationInfo" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pty" + } + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "List PTY sessions for a location, including exited sessions retained until removal.", + "summary": "List PTY sessions", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.pty.list({\n ...\n})" + } + ] + }, + "post": { + "tags": ["pty"], + "operationId": "v2.pty.create", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "workspace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/LocationInfo" + }, + "data": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Create a pseudo-terminal session for a location.", + "summary": "Create PTY session", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "type": "string" + }, + "title": { + "type": "string" + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } + }, + "required": true + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.pty.create({\n ...\n})" + } + ] + } + }, + "/api/pty/{ptyID}": { + "get": { + "tags": ["pty"], + "operationId": "v2.pty.get", + "parameters": [ + { + "name": "ptyID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^pty" + }, + "required": true + }, + { + "name": "location", + "in": "query", + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "workspace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/LocationInfo" + }, + "data": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "PtyNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PtyNotFoundError" + } + } + } + } + }, + "description": "Get one PTY session, including its exit code once exited.", + "summary": "Get PTY session", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.pty.get({\n ...\n})" + } + ] + }, + "put": { + "tags": ["pty"], + "operationId": "v2.pty.update", + "parameters": [ + { + "name": "ptyID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^pty" + }, + "required": true + }, + { + "name": "location", + "in": "query", + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "workspace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/LocationInfo" + }, + "data": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "PtyNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PtyNotFoundError" + } + } + } + } + }, + "description": "Update the title or viewport size of one PTY session.", + "summary": "Update PTY session", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "size": { + "type": "object", + "properties": { + "rows": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "cols": { + "type": "integer", + "exclusiveMinimum": 0 + } + }, + "required": ["rows", "cols"], + "additionalProperties": false + } + }, + "additionalProperties": false + } + } + }, + "required": true + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.pty.update({\n ...\n})" + } + ] + }, + "delete": { + "tags": ["pty"], + "operationId": "v2.pty.remove", + "parameters": [ + { + "name": "ptyID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^pty" + }, + "required": true + }, + { + "name": "location", + "in": "query", + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "workspace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "PtyNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PtyNotFoundError" + } + } + } + } + }, + "description": "Terminate and remove one PTY session.", + "summary": "Remove PTY session", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.pty.remove({\n ...\n})" + } + ] + } + }, + "/api/pty/{ptyID}/connect-token": { + "post": { + "tags": ["pty"], + "operationId": "v2.pty.connectToken", + "parameters": [ + { + "name": "ptyID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^pty" + }, + "required": true + }, + { + "name": "location", + "in": "query", + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "workspace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/LocationInfo" + }, + "data": { + "type": "object", + "properties": { + "ticket": { + "type": "string" + }, + "expires_in": { + "type": "integer", + "exclusiveMinimum": 0 + } + }, + "required": ["ticket", "expires_in"], + "additionalProperties": false + } + }, + "required": ["location", "data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, + "404": { + "description": "PtyNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PtyNotFoundError" + } + } + } + } + }, + "description": "Create a short-lived single-use ticket for opening a PTY WebSocket connection.", + "summary": "Create PTY WebSocket token", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.pty.connectToken({\n ...\n})" + } + ] + } + }, + "/api/pty/{ptyID}/connect": { + "get": { + "tags": ["pty"], + "operationId": "v2.pty.connect", + "parameters": [ + { + "name": "ptyID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^pty" + }, + "required": true + }, + { + "in": "query", + "name": "location[directory]", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "location[workspace]", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "ticket", + "schema": { + "type": "string" + } + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, + "404": { + "description": "PtyNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PtyNotFoundError" + } + } + } + } + }, + "description": "Establish a WebSocket connection streaming PTY output and accepting terminal input.", + "summary": "Connect to PTY session", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.pty.connect({\n ...\n})" + } + ] + } + }, "/api/question/request": { "get": { "tags": ["session questions"], @@ -15640,6 +16356,10 @@ "pid": { "type": "integer", "minimum": 0 + }, + "exitCode": { + "type": "integer", + "minimum": 0 } }, "required": ["id", "title", "command", "args", "cwd", "status", "pid"], @@ -22082,6 +22802,20 @@ "required": ["_tag", "providerID", "message"], "additionalProperties": false }, + "ForbiddenError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["ForbiddenError"] + }, + "message": { + "type": "string" + } + }, + "required": ["_tag", "message"], + "additionalProperties": false + }, "ProjectCopyError": { "type": "object", "properties": { @@ -30432,6 +31166,10 @@ "name": "events", "description": "Experimental event stream route." }, + { + "name": "pty", + "description": "Experimental location-scoped PTY routes." + }, { "name": "session questions", "description": "Experimental session question routes." diff --git a/packages/server/src/handlers/pty.ts b/packages/server/src/handlers/pty.ts index 0ab4567a2..b56e5d605 100644 --- a/packages/server/src/handlers/pty.ts +++ b/packages/server/src/handlers/pty.ts @@ -9,11 +9,7 @@ import * as Socket from "effect/unstable/socket/Socket" import { Api } from "../api" import { CorsConfig, isAllowedRequestOrigin } from "../cors" import { ForbiddenError, PtyNotFoundError } from "../errors" -import { - PTY_CONNECT_TICKET_QUERY, - PTY_CONNECT_TOKEN_HEADER, - PTY_CONNECT_TOKEN_HEADER_VALUE, -} from "../groups/pty" +import { PTY_CONNECT_TICKET_QUERY, PTY_CONNECT_TOKEN_HEADER, PTY_CONNECT_TOKEN_HEADER_VALUE } from "../groups/pty" import { response } from "../groups/location" const ticketScope = Effect.gen(function* () { @@ -51,18 +47,16 @@ export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) => Effect.fn(function* (ctx) { const pty = yield* Pty.Service return yield* response( - pty - .get(ctx.params.ptyID) - .pipe( - Effect.catchTag( - "Pty.NotFoundError", - () => - new PtyNotFoundError({ - ptyID: ctx.params.ptyID, - message: `PTY session not found: ${ctx.params.ptyID}`, - }), - ), + pty.get(ctx.params.ptyID).pipe( + Effect.catchTag( + "Pty.NotFoundError", + () => + new PtyNotFoundError({ + ptyID: ctx.params.ptyID, + message: `PTY session not found: ${ctx.params.ptyID}`, + }), ), + ), ) }), ) @@ -93,18 +87,16 @@ export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) => "pty.remove", Effect.fn(function* (ctx) { const pty = yield* Pty.Service - yield* pty - .remove(ctx.params.ptyID) - .pipe( - Effect.catchTag( - "Pty.NotFoundError", - () => - new PtyNotFoundError({ - ptyID: ctx.params.ptyID, - message: `PTY session not found: ${ctx.params.ptyID}`, - }), - ), - ) + yield* pty.remove(ctx.params.ptyID).pipe( + Effect.catchTag( + "Pty.NotFoundError", + () => + new PtyNotFoundError({ + ptyID: ctx.params.ptyID, + message: `PTY session not found: ${ctx.params.ptyID}`, + }), + ), + ) return HttpApiSchema.NoContent.make() }), ) @@ -120,18 +112,16 @@ export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) => ) return yield* new ForbiddenError({ message: "Invalid PTY connect token request" }) const pty = yield* Pty.Service - yield* pty - .get(ctx.params.ptyID) - .pipe( - Effect.catchTag( - "Pty.NotFoundError", - () => - new PtyNotFoundError({ - ptyID: ctx.params.ptyID, - message: `PTY session not found: ${ctx.params.ptyID}`, - }), - ), - ) + yield* pty.get(ctx.params.ptyID).pipe( + Effect.catchTag( + "Pty.NotFoundError", + () => + new PtyNotFoundError({ + ptyID: ctx.params.ptyID, + message: `PTY session not found: ${ctx.params.ptyID}`, + }), + ), + ) return yield* response(tickets.issue({ ptyID: ctx.params.ptyID, ...(yield* ticketScope) })) }), )