@opencode/server 0.0.0-beta-19275
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +8507 -0
- package/dist/api.js +9 -0
- package/dist/auth.d.ts +17 -0
- package/dist/auth.js +17 -0
- package/dist/cors.d.ts +7 -0
- package/dist/cors.js +36 -0
- package/dist/event-feed.d.ts +32 -0
- package/dist/event-feed.js +60 -0
- package/dist/fetch.d.ts +77 -0
- package/dist/fetch.js +11 -0
- package/dist/handlers/agent.d.ts +1 -0
- package/dist/handlers/agent.js +17 -0
- package/dist/handlers/command.d.ts +1 -0
- package/dist/handlers/command.js +5 -0
- package/dist/handlers/config.d.ts +1 -0
- package/dist/handlers/config.js +4 -0
- package/dist/handlers/credential.d.ts +1 -0
- package/dist/handlers/credential.js +14 -0
- package/dist/handlers/debug.d.ts +1 -0
- package/dist/handlers/debug.js +11 -0
- package/dist/handlers/event.d.ts +2 -0
- package/dist/handlers/event.js +27 -0
- package/dist/handlers/form.d.ts +4 -0
- package/dist/handlers/form.js +61 -0
- package/dist/handlers/fs.d.ts +1 -0
- package/dist/handlers/fs.js +19 -0
- package/dist/handlers/generate.d.ts +3 -0
- package/dist/handlers/generate.js +15 -0
- package/dist/handlers/health.d.ts +2 -0
- package/dist/handlers/health.js +11 -0
- package/dist/handlers/integration.d.ts +2 -0
- package/dist/handlers/integration.js +83 -0
- package/dist/handlers/location.d.ts +1 -0
- package/dist/handlers/location.js +12 -0
- package/dist/handlers/mcp.d.ts +1 -0
- package/dist/handlers/mcp.js +31 -0
- package/dist/handlers/message.d.ts +2 -0
- package/dist/handlers/message.js +42 -0
- package/dist/handlers/migration.d.ts +1 -0
- package/dist/handlers/migration.js +7 -0
- package/dist/handlers/model.d.ts +1 -0
- package/dist/handlers/model.js +14 -0
- package/dist/handlers/permission.d.ts +4 -0
- package/dist/handlers/permission.js +57 -0
- package/dist/handlers/persistent-pty.d.ts +3 -0
- package/dist/handlers/persistent-pty.js +136 -0
- package/dist/handlers/plugin.d.ts +2 -0
- package/dist/handlers/plugin.js +51 -0
- package/dist/handlers/project.d.ts +2 -0
- package/dist/handlers/project.js +14 -0
- package/dist/handlers/provider.d.ts +1 -0
- package/dist/handlers/provider.js +20 -0
- package/dist/handlers/pty-socket.d.ts +2 -0
- package/dist/handlers/pty-socket.js +4 -0
- package/dist/handlers/pty.d.ts +3 -0
- package/dist/handlers/pty.js +108 -0
- package/dist/handlers/reference.d.ts +1 -0
- package/dist/handlers/reference.js +5 -0
- package/dist/handlers/rpc.d.ts +1 -0
- package/dist/handlers/rpc.js +15 -0
- package/dist/handlers/server.d.ts +2 -0
- package/dist/handlers/server.js +7 -0
- package/dist/handlers/session-error.d.ts +5 -0
- package/dist/handlers/session-error.js +13 -0
- package/dist/handlers/session.d.ts +3 -0
- package/dist/handlers/session.js +294 -0
- package/dist/handlers/shell.d.ts +1 -0
- package/dist/handlers/shell.js +28 -0
- package/dist/handlers/skill.d.ts +1 -0
- package/dist/handlers/skill.js +5 -0
- package/dist/handlers/vcs.d.ts +1 -0
- package/dist/handlers/vcs.js +19 -0
- package/dist/handlers/websearch.d.ts +1 -0
- package/dist/handlers/websearch.js +31 -0
- package/dist/handlers/workspace.d.ts +2 -0
- package/dist/handlers/workspace.js +20 -0
- package/dist/handlers/worktree.d.ts +1 -0
- package/dist/handlers/worktree.js +39 -0
- package/dist/handlers.d.ts +2 -0
- package/dist/handlers.js +35 -0
- package/dist/location.d.ts +26 -0
- package/dist/location.js +52 -0
- package/dist/middleware/authorization.d.ts +7 -0
- package/dist/middleware/authorization.js +49 -0
- package/dist/middleware/form-location.d.ts +18 -0
- package/dist/middleware/form-location.js +25 -0
- package/dist/middleware/schema-error.d.ts +3 -0
- package/dist/middleware/schema-error.js +15 -0
- package/dist/middleware/session-location.d.ts +17 -0
- package/dist/middleware/session-location.js +19 -0
- package/dist/options.d.ts +47 -0
- package/dist/options.js +35 -0
- package/dist/process.d.ts +58 -0
- package/dist/process.js +131 -0
- package/dist/pty-environment.d.ts +13 -0
- package/dist/pty-environment.js +9 -0
- package/dist/request-tracing.d.ts +2 -0
- package/dist/request-tracing.js +2 -0
- package/dist/routes.d.ts +29 -0
- package/dist/routes.js +116 -0
- package/dist/server-info.d.ts +11 -0
- package/dist/server-info.js +22 -0
- package/dist/service-status.d.ts +25 -0
- package/dist/service-status.js +11 -0
- package/dist/workerd.d.ts +45 -0
- package/dist/workerd.js +61 -0
- package/package.json +48 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Mcp } from "@opencode/core/mcp/index";
|
|
2
|
+
import { McpServerNotFoundError } from "@opencode/protocol/errors";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
5
|
+
import { Api } from "../api";
|
|
6
|
+
import { response } from "../location";
|
|
7
|
+
const notFound = (effect) => effect.pipe(Effect.mapError((error) => new McpServerNotFoundError({ server: error.server, message: error.message })));
|
|
8
|
+
export const McpHandler = HttpApiBuilder.group(Api, "server.mcp", (handlers) => Effect.gen(function* () {
|
|
9
|
+
return handlers.handle("mcp.list", Effect.fn(function* () {
|
|
10
|
+
const service = yield* Mcp.Service;
|
|
11
|
+
return yield* response(service.servers().pipe(Effect.map((servers) => servers.map((info) => ({ name: info.name, status: info.status, integrationID: info.integrationID })))));
|
|
12
|
+
})).handle("mcp.add", Effect.fn(function* (ctx) {
|
|
13
|
+
yield* (yield* Mcp.Service).add(ctx.params.server, ctx.payload.config);
|
|
14
|
+
return HttpApiSchema.NoContent.make();
|
|
15
|
+
})).handle("mcp.remove", Effect.fn(function* (ctx) {
|
|
16
|
+
const service = yield* Mcp.Service;
|
|
17
|
+
yield* notFound(service.remove(ctx.params.server));
|
|
18
|
+
return HttpApiSchema.NoContent.make();
|
|
19
|
+
})).handle("mcp.connect", Effect.fn(function* (ctx) {
|
|
20
|
+
const service = yield* Mcp.Service;
|
|
21
|
+
yield* notFound(service.connect(ctx.params.server));
|
|
22
|
+
return HttpApiSchema.NoContent.make();
|
|
23
|
+
})).handle("mcp.disconnect", Effect.fn(function* (ctx) {
|
|
24
|
+
const service = yield* Mcp.Service;
|
|
25
|
+
yield* notFound(service.disconnect(ctx.params.server));
|
|
26
|
+
return HttpApiSchema.NoContent.make();
|
|
27
|
+
})).handle("mcp.resource.catalog", Effect.fn(function* () {
|
|
28
|
+
const service = yield* Mcp.Service;
|
|
29
|
+
return yield* response(service.resourceCatalog());
|
|
30
|
+
}));
|
|
31
|
+
}));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Session } from "@opencode/core/session";
|
|
2
|
+
export declare const MessageHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.message">, never, Session.Service | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization)>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SessionMessage } from "@opencode/core/session/message";
|
|
2
|
+
import { Session } from "@opencode/core/session";
|
|
3
|
+
import { Effect, Schema } from "effect";
|
|
4
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
5
|
+
import { Api } from "../api";
|
|
6
|
+
import { InvalidCursorError } from "@opencode/protocol/errors";
|
|
7
|
+
import { failedMessageDecode, missingSession } from "./session-error";
|
|
8
|
+
const DefaultMessagesLimit = 50, Cursor = Schema.Struct({
|
|
9
|
+
id: SessionMessage.ID,
|
|
10
|
+
order: Schema.Union([Schema.Literal("asc"), Schema.Literal("desc")]),
|
|
11
|
+
direction: Schema.Union([Schema.Literal("previous"), Schema.Literal("next")])
|
|
12
|
+
}), decodeCursor = Schema.decodeUnknownSync(Cursor), cursor = {
|
|
13
|
+
encode(message, order, direction) {
|
|
14
|
+
return Buffer.from(JSON.stringify({ id: message.id, order, direction })).toString("base64url");
|
|
15
|
+
},
|
|
16
|
+
decode(input) {
|
|
17
|
+
return decodeCursor(JSON.parse(Buffer.from(input, "base64url").toString("utf8")));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const MessageHandler = HttpApiBuilder.group(Api, "server.message", (handlers) => Effect.gen(function* () {
|
|
21
|
+
const session = yield* Session.Service;
|
|
22
|
+
return handlers.handle("session.messages", Effect.fn(function* (ctx) {
|
|
23
|
+
if (ctx.query.cursor && ctx.query.order !== void 0)
|
|
24
|
+
return yield* new InvalidCursorError({ message: "Cursor cannot be combined with order" });
|
|
25
|
+
const decoded = yield* Effect.try({
|
|
26
|
+
try: () => ctx.query.cursor ? cursor.decode(ctx.query.cursor) : void 0,
|
|
27
|
+
catch: () => new InvalidCursorError({ message: "Invalid cursor" })
|
|
28
|
+
}), order = decoded?.order ?? ctx.query.order ?? "desc", messages = yield* session.messages({
|
|
29
|
+
sessionID: ctx.params.sessionID,
|
|
30
|
+
limit: ctx.query.limit ?? DefaultMessagesLimit,
|
|
31
|
+
order,
|
|
32
|
+
cursor: decoded ? { id: decoded.id, direction: decoded.direction } : void 0
|
|
33
|
+
}).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageDecodeError", failedMessageDecode)), first = messages[0], last = messages.at(-1);
|
|
34
|
+
return {
|
|
35
|
+
data: messages,
|
|
36
|
+
cursor: {
|
|
37
|
+
previous: first ? cursor.encode(first, order, "previous") : void 0,
|
|
38
|
+
next: last ? cursor.encode(last, order, "next") : void 0
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}));
|
|
42
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MigrationHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.migration">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { V1Migration } from "@opencode/core/database/v1-migration";
|
|
2
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { Api } from "../api";
|
|
5
|
+
export const MigrationHandler = HttpApiBuilder.group(Api, "server.migration", (handlers) => handlers.handle("migration.v1.status", Effect.fn(function* () {
|
|
6
|
+
return yield* V1Migration.status();
|
|
7
|
+
})));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ModelHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.model">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Catalog } from "@opencode/core/catalog";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
4
|
+
import { Api } from "../api";
|
|
5
|
+
import { response } from "../location";
|
|
6
|
+
export const ModelHandler = HttpApiBuilder.group(Api, "server.model", (handlers) => Effect.gen(function* () {
|
|
7
|
+
return handlers.handle("model.list", Effect.fn(function* () {
|
|
8
|
+
const catalog = yield* Catalog.Service;
|
|
9
|
+
return yield* response(catalog.model.available());
|
|
10
|
+
})).handle("model.default", Effect.fn(function* () {
|
|
11
|
+
const catalog = yield* Catalog.Service;
|
|
12
|
+
return yield* response(catalog.model.default());
|
|
13
|
+
}));
|
|
14
|
+
}));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
2
|
+
import { PermissionSaved } from "@opencode/core/permission/saved";
|
|
3
|
+
import { Session } from "@opencode/core/session";
|
|
4
|
+
export declare const PermissionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.permission">, never, Session.Service | Instance.Service | import("../middleware/session-location").SessionLocationMiddleware | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware | import("effect/unstable/http/HttpRouter").Request<"Requires", PermissionSaved.Service>)>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
2
|
+
import { Location } from "@opencode/core/location";
|
|
3
|
+
import { Permission } from "@opencode/core/permission";
|
|
4
|
+
import { PermissionSaved } from "@opencode/core/permission/saved";
|
|
5
|
+
import { Session } from "@opencode/core/session";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
8
|
+
import { Api } from "../api";
|
|
9
|
+
import { PermissionNotFoundError } from "@opencode/protocol/errors";
|
|
10
|
+
import { response, sessionInfo } from "../location";
|
|
11
|
+
import { missingSession } from "./session-error";
|
|
12
|
+
function missingRequest(id) {
|
|
13
|
+
return new PermissionNotFoundError({ requestID: id, message: `Permission request not found: ${id}` });
|
|
14
|
+
}
|
|
15
|
+
export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission", (handlers) => Effect.gen(function* () {
|
|
16
|
+
const instances = yield* Instance.Service, sessions = yield* Session.Service, requireOwnedRequest = Effect.fnUntraced(function* (sessionID, requestID) {
|
|
17
|
+
const permission = yield* Permission.Service, request = yield* permission.get(requestID);
|
|
18
|
+
if (!request || request.sessionID !== sessionID)
|
|
19
|
+
return yield* missingRequest(requestID);
|
|
20
|
+
return { permission, request };
|
|
21
|
+
});
|
|
22
|
+
return handlers.handle("permission.request.list", Effect.fn(function* () {
|
|
23
|
+
const permission = yield* Permission.Service;
|
|
24
|
+
return yield* response(permission.list());
|
|
25
|
+
})).handle("session.permission.create", Effect.fn(function* (ctx) {
|
|
26
|
+
return {
|
|
27
|
+
data: yield* (yield* Permission.Service).ask({
|
|
28
|
+
id: ctx.payload.id,
|
|
29
|
+
sessionID: ctx.params.sessionID,
|
|
30
|
+
action: ctx.payload.action,
|
|
31
|
+
resources: ctx.payload.resources,
|
|
32
|
+
save: ctx.payload.save,
|
|
33
|
+
metadata: ctx.payload.metadata,
|
|
34
|
+
source: ctx.payload.source,
|
|
35
|
+
agent: ctx.payload.agent
|
|
36
|
+
}).pipe(Effect.catchTag("Session.NotFoundError", missingSession))
|
|
37
|
+
};
|
|
38
|
+
})).handle("session.permission.list", Effect.fn(function* (ctx) {
|
|
39
|
+
const session = yield* sessionInfo(sessions, ctx.params.sessionID);
|
|
40
|
+
return { data: yield* Permission.Service.use((permission) => permission.forSession(ctx.params.sessionID)).pipe(instances.provide(session)) };
|
|
41
|
+
})).handle("session.permission.get", Effect.fn(function* (ctx) {
|
|
42
|
+
return { data: (yield* requireOwnedRequest(ctx.params.sessionID, ctx.params.requestID)).request };
|
|
43
|
+
})).handle("session.permission.reply", Effect.fn(function* (ctx) {
|
|
44
|
+
yield* (yield* requireOwnedRequest(ctx.params.sessionID, ctx.params.requestID)).permission.reply({ requestID: ctx.params.requestID, reply: ctx.payload.reply, message: ctx.payload.message }).pipe(Effect.catchTag("Permission.NotFoundError", () => missingRequest(ctx.params.requestID)));
|
|
45
|
+
return HttpApiSchema.NoContent.make();
|
|
46
|
+
})).handle("permission.saved.list", Effect.fn(function* (ctx) {
|
|
47
|
+
const location = yield* Location.Service;
|
|
48
|
+
return {
|
|
49
|
+
data: yield* (yield* PermissionSaved.Service).list({
|
|
50
|
+
projectID: ctx.query.projectID ?? location.project.id
|
|
51
|
+
})
|
|
52
|
+
};
|
|
53
|
+
})).handle("permission.saved.remove", Effect.fn(function* (ctx) {
|
|
54
|
+
yield* (yield* PermissionSaved.Service).remove(ctx.params.id);
|
|
55
|
+
return HttpApiSchema.NoContent.make();
|
|
56
|
+
}));
|
|
57
|
+
}));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { PersistentPty } from "@opencode/core/persistent-pty";
|
|
2
|
+
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
3
|
+
export declare const PersistentPtyHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.experimental">, never, PersistentPty.Service | PtyTicket.Service | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization)>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { PersistentPty } from "@opencode/core/persistent-pty";
|
|
2
|
+
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
3
|
+
import { ForbiddenError, PtyNotFoundError, ServiceUnavailableError } from "@opencode/protocol/errors";
|
|
4
|
+
import {
|
|
5
|
+
PTY_CONNECT_TICKET_QUERY,
|
|
6
|
+
PTY_CONNECT_TOKEN_HEADER,
|
|
7
|
+
PTY_CONNECT_TOKEN_HEADER_VALUE
|
|
8
|
+
} from "@opencode/protocol/groups/persistent-pty";
|
|
9
|
+
import { Effect, Queue, Semaphore } from "effect";
|
|
10
|
+
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
11
|
+
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
12
|
+
import { Socket } from "effect/unstable/socket";
|
|
13
|
+
import { Api } from "../api";
|
|
14
|
+
import { CorsConfig, isAllowedRequestOrigin } from "../cors";
|
|
15
|
+
import { runPtySocket } from "./pty-socket";
|
|
16
|
+
export const PersistentPtyHandler = HttpApiBuilder.group(Api, "server.experimental", (handlers) => Effect.gen(function* () {
|
|
17
|
+
const tickets = yield* PtyTicket.Service, cors = yield* CorsConfig, pty = yield* PersistentPty.Service;
|
|
18
|
+
return handlers.handle("persistentPty.read", Effect.fn(function* (ctx) {
|
|
19
|
+
return { data: yield* pty.read(ctx.params.sessionID, ctx.query.lines).pipe(mapUnavailable) };
|
|
20
|
+
})).handle("persistentPty.list", Effect.fn(function* (ctx) {
|
|
21
|
+
return { data: yield* pty.list(ctx.params.sessionID).pipe(mapUnavailable) };
|
|
22
|
+
})).handle("persistentPty.create", Effect.fn(function* (ctx) {
|
|
23
|
+
return {
|
|
24
|
+
data: yield* pty.create(ctx.params.sessionID, {
|
|
25
|
+
command: ctx.payload.command,
|
|
26
|
+
args: ctx.payload.args,
|
|
27
|
+
cwd: ctx.payload.cwd,
|
|
28
|
+
title: ctx.payload.title,
|
|
29
|
+
env: ctx.payload.env,
|
|
30
|
+
cols: ctx.payload.size?.cols,
|
|
31
|
+
rows: ctx.payload.size?.rows
|
|
32
|
+
}).pipe(Effect.catchTag("PersistentPty.UnavailableError", unavailable))
|
|
33
|
+
};
|
|
34
|
+
})).handle("persistentPty.shutdown", Effect.fn(function* () {
|
|
35
|
+
yield* pty.shutdown().pipe(mapUnavailable);
|
|
36
|
+
return HttpApiSchema.NoContent.make();
|
|
37
|
+
})).handle("persistentPty.handoff", Effect.fn(function* () {
|
|
38
|
+
return { handoff: yield* pty.handoff().pipe(mapUnavailable) };
|
|
39
|
+
})).handle("persistentPty.get", Effect.fn(function* (ctx) {
|
|
40
|
+
return { data: yield* pty.get(ctx.params.ptyID).pipe(mapTerminalError) };
|
|
41
|
+
})).handle("persistentPty.update", Effect.fn(function* (ctx) {
|
|
42
|
+
yield* pty.resize(ctx.params.ptyID, ctx.payload.size.cols, ctx.payload.size.rows, ctx.payload.attachmentID).pipe(mapTerminalError);
|
|
43
|
+
return { data: yield* pty.get(ctx.params.ptyID).pipe(mapTerminalError) };
|
|
44
|
+
})).handle("persistentPty.snapshot", Effect.fn(function* (ctx) {
|
|
45
|
+
return { data: yield* pty.snapshot(ctx.params.ptyID).pipe(mapTerminalError) };
|
|
46
|
+
})).handle("persistentPty.remove", Effect.fn(function* (ctx) {
|
|
47
|
+
yield* pty.remove(ctx.params.ptyID).pipe(mapTerminalError);
|
|
48
|
+
return HttpApiSchema.NoContent.make();
|
|
49
|
+
})).handle("persistentPty.connectToken", Effect.fn(function* (ctx) {
|
|
50
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
51
|
+
if (request.headers[PTY_CONNECT_TOKEN_HEADER] !== PTY_CONNECT_TOKEN_HEADER_VALUE || !isAllowedRequestOrigin(request.headers.origin, request.headers.host, cors))
|
|
52
|
+
return yield* new ForbiddenError({ message: "Invalid persistent PTY connect token request" });
|
|
53
|
+
yield* pty.get(ctx.params.ptyID).pipe(mapTerminalError);
|
|
54
|
+
return { data: yield* tickets.issue({ ptyID: ctx.params.ptyID }) };
|
|
55
|
+
})).handleRaw("persistentPty.connect", Effect.fn("PersistentPtyHandler.connect")(function* (ctx) {
|
|
56
|
+
const url = new URL(ctx.request.url, "http://localhost"), ticket = url.searchParams.get(PTY_CONNECT_TICKET_QUERY);
|
|
57
|
+
if (ticket) {
|
|
58
|
+
if (!(isAllowedRequestOrigin(ctx.request.headers.origin, ctx.request.headers.host, cors) ? yield* tickets.consume({ ticket, ptyID: ctx.params.ptyID }) : !1))
|
|
59
|
+
return HttpServerResponse.empty({ status: 403 });
|
|
60
|
+
}
|
|
61
|
+
const cursor = Number(url.searchParams.get("cursor") ?? "0"), role = url.searchParams.get("role") === "observer" ? "observer" : "controller", framedInput = url.searchParams.get("input_protocol") === "1", attachmentID = url.searchParams.get("attachment_id") ?? crypto.randomUUID();
|
|
62
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0)
|
|
63
|
+
return HttpServerResponse.empty({ status: 400 });
|
|
64
|
+
const socket = yield* Effect.orDie(ctx.request.upgrade), write = yield* socket.writer, outbox = yield* Queue.unbounded(), input = yield* Semaphore.make(1);
|
|
65
|
+
let attachment;
|
|
66
|
+
const onOpen = Effect.gen(function* () {
|
|
67
|
+
attachment = yield* pty.attach(ctx.params.ptyID, {
|
|
68
|
+
cursor,
|
|
69
|
+
attachmentID,
|
|
70
|
+
role,
|
|
71
|
+
takeover: url.searchParams.get("takeover") === "true",
|
|
72
|
+
onEvent: (event) => {
|
|
73
|
+
if (event.type === "output")
|
|
74
|
+
Queue.offerUnsafe(outbox, event.data);
|
|
75
|
+
if (event.type === "resized")
|
|
76
|
+
Queue.offerUnsafe(outbox, JSON.stringify({ ...event, checkpoint: Buffer.from(event.checkpoint).toString("base64") }));
|
|
77
|
+
if (event.type !== "output" && event.type !== "resized")
|
|
78
|
+
Queue.offerUnsafe(outbox, JSON.stringify(event));
|
|
79
|
+
},
|
|
80
|
+
onEnd: () => Queue.offerUnsafe(outbox, new Socket.CloseEvent(1000))
|
|
81
|
+
}).pipe(Effect.catchTags({
|
|
82
|
+
"PersistentPty.NotFoundError": () => Effect.succeed(void 0),
|
|
83
|
+
"PersistentPty.UnavailableError": () => Effect.succeed(void 0)
|
|
84
|
+
}));
|
|
85
|
+
if (!attachment) {
|
|
86
|
+
Queue.offerUnsafe(outbox, new Socket.CloseEvent(4404, "terminal unavailable"));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
Queue.offerUnsafe(outbox, JSON.stringify({
|
|
90
|
+
type: "attached",
|
|
91
|
+
attachmentID,
|
|
92
|
+
inputProtocol: framedInput ? 1 : 0,
|
|
93
|
+
info: attachment.info,
|
|
94
|
+
role: attachment.role,
|
|
95
|
+
generation: attachment.generation,
|
|
96
|
+
replay: {
|
|
97
|
+
requestedOffset: attachment.replay.requestedOffset,
|
|
98
|
+
availableOffset: attachment.replay.availableOffset,
|
|
99
|
+
endOffset: attachment.replay.endOffset,
|
|
100
|
+
truncated: attachment.replay.truncated
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
if (attachment.replay.data.length > 0)
|
|
104
|
+
Queue.offerUnsafe(outbox, attachment.replay.data);
|
|
105
|
+
Queue.offerUnsafe(outbox, JSON.stringify({ type: "replay_complete", endOffset: attachment.replay.endOffset }));
|
|
106
|
+
attachment.activate();
|
|
107
|
+
}), drain = Effect.gen(function* () {
|
|
108
|
+
while (!0) {
|
|
109
|
+
const item = yield* Queue.take(outbox);
|
|
110
|
+
yield* write(item);
|
|
111
|
+
if (item instanceof Socket.CloseEvent)
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
yield* runPtySocket(drain, socket.runRaw((message) => input.withPermit(Effect.suspend(() => {
|
|
116
|
+
if (!attachment)
|
|
117
|
+
return Effect.void;
|
|
118
|
+
const data = typeof message === "string" ? Buffer.from(message) : message;
|
|
119
|
+
if (!framedInput)
|
|
120
|
+
return pty.input(ctx.params.ptyID, attachmentID, attachment.info.size.cols, attachment.info.size.rows, data).pipe(Effect.ignore);
|
|
121
|
+
if (data.byteLength < 5)
|
|
122
|
+
return Effect.void;
|
|
123
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength), type = data[0], cols = view.getUint16(1), rows = view.getUint16(3);
|
|
124
|
+
if (type !== 0 && type !== 1 || cols === 0 || rows === 0)
|
|
125
|
+
return Effect.void;
|
|
126
|
+
if (type === 0)
|
|
127
|
+
return pty.control(ctx.params.ptyID, attachmentID, cols, rows).pipe(Effect.ignore);
|
|
128
|
+
return pty.input(ctx.params.ptyID, attachmentID, cols, rows, data.subarray(5)).pipe(Effect.ignore);
|
|
129
|
+
})), { onOpen }), () => attachment?.detach()).pipe(Effect.catchReason("SocketError", "SocketCloseError", () => Effect.void), Effect.orDie);
|
|
130
|
+
return HttpServerResponse.empty();
|
|
131
|
+
}));
|
|
132
|
+
}));
|
|
133
|
+
const mapUnavailable = (effect) => effect.pipe(Effect.catchTag("PersistentPty.UnavailableError", unavailable)), mapTerminalError = (effect) => effect.pipe(Effect.catchTags({
|
|
134
|
+
"PersistentPty.NotFoundError": (error) => new PtyNotFoundError({ ptyID: error.ptyID, message: `PTY session not found: ${error.ptyID}` }),
|
|
135
|
+
"PersistentPty.UnavailableError": unavailable
|
|
136
|
+
})), unavailable = (error) => new ServiceUnavailableError({ message: error.message, service: "opencode-pty" });
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { PluginUpdate } from "@opencode/core/plugin/update";
|
|
2
|
+
export declare const PluginHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.plugin">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware | import("effect/unstable/http/HttpRouter").Request<"Requires", PluginUpdate.Service>>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Plugin } from "@opencode/core/plugin";
|
|
2
|
+
import { PluginUpdate } from "@opencode/core/plugin/update";
|
|
3
|
+
import { InvalidRequestError, ServiceUnavailableError } from "@opencode/protocol/errors";
|
|
4
|
+
import { Cause, Effect, Exit } from "effect";
|
|
5
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
6
|
+
import { Api } from "../api";
|
|
7
|
+
import { response } from "../location";
|
|
8
|
+
export const PluginHandler = HttpApiBuilder.group(Api, "server.plugin", (handlers) => handlers.handle("plugin.list", () => Effect.gen(function* () {
|
|
9
|
+
return yield* response(Plugin.Service.use((plugin) => plugin.list()));
|
|
10
|
+
})).handle("plugin.awaitActivation", () => Plugin.awaitActivation).handle("plugin.check", (ctx) => Effect.gen(function* () {
|
|
11
|
+
const plugins = yield* Plugin.Service;
|
|
12
|
+
yield* plugins.awaitActivation;
|
|
13
|
+
const inventory = yield* plugins.list(), targets = [
|
|
14
|
+
...new Set(inventory.flatMap((plugin) => plugin.source.type === "package" ? [plugin.source.target] : []))
|
|
15
|
+
].filter((target) => ctx.payload.target === void 0 || target === ctx.payload.target);
|
|
16
|
+
if (ctx.payload.target !== void 0 && !targets.length)
|
|
17
|
+
return yield* new InvalidRequestError({
|
|
18
|
+
message: `Plugin package is not in the current server inventory: ${ctx.payload.target}`,
|
|
19
|
+
field: "target"
|
|
20
|
+
});
|
|
21
|
+
const updates = yield* PluginUpdate.Service, outdated = new Map(yield* Effect.forEach(targets, (target) => updates.check(target, { refresh: !0 }).pipe(Effect.map((value) => [target, value])), { concurrency: "unbounded" }));
|
|
22
|
+
return yield* response(Effect.succeed(inventory.map((plugin) => {
|
|
23
|
+
if (plugin.source.type !== "package" || !outdated.has(plugin.source.target))
|
|
24
|
+
return plugin;
|
|
25
|
+
return {
|
|
26
|
+
...plugin,
|
|
27
|
+
source: {
|
|
28
|
+
type: "package",
|
|
29
|
+
target: plugin.source.target,
|
|
30
|
+
...plugin.source.version ? { version: plugin.source.version } : {},
|
|
31
|
+
...outdated.get(plugin.source.target) ? { outdated: !0 } : {},
|
|
32
|
+
...plugin.source.updating ? { updating: !0 } : {}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
})));
|
|
36
|
+
})).handle("plugin.update", (ctx) => Effect.gen(function* () {
|
|
37
|
+
const plugins = yield* Plugin.Service;
|
|
38
|
+
yield* plugins.awaitActivation;
|
|
39
|
+
const inventory = new Set((yield* plugins.list()).flatMap((plugin) => plugin.source.type === "package" ? [plugin.source.target] : [])), unknown = ctx.payload.targets.filter((target) => !inventory.has(target));
|
|
40
|
+
if (unknown.length)
|
|
41
|
+
return yield* new InvalidRequestError({
|
|
42
|
+
message: `Plugin packages are not in the current server inventory: ${unknown.join(", ")}`,
|
|
43
|
+
field: "targets"
|
|
44
|
+
});
|
|
45
|
+
const updates = yield* PluginUpdate.Service, failures = yield* Effect.forEach(ctx.payload.targets, (target) => updates.update(target).pipe(Effect.exit, Effect.map((exit) => Exit.isFailure(exit) ? [`${target}: ${Cause.pretty(exit.cause)}`] : [])), { concurrency: "unbounded" }).pipe(Effect.map((results) => results.flat()));
|
|
46
|
+
if (failures.length)
|
|
47
|
+
return yield* new ServiceUnavailableError({
|
|
48
|
+
message: `Failed to update plugin packages: ${failures.join("; ")}`,
|
|
49
|
+
service: "plugin"
|
|
50
|
+
});
|
|
51
|
+
})));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Project } from "@opencode/core/project";
|
|
2
|
+
export declare const ProjectHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.project">, never, import("effect/unstable/http/HttpRouter").Request<"Requires", Project.Service> | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware)>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Location } from "@opencode/core/location";
|
|
2
|
+
import { Project } from "@opencode/core/project";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
5
|
+
import { Api } from "../api";
|
|
6
|
+
import { ProjectNotFoundError } from "@opencode/protocol/errors";
|
|
7
|
+
export const ProjectHandler = HttpApiBuilder.group(Api, "server.project", (handlers) => handlers.handle("project.list", () => Project.Service.use((project) => project.list())).handle("project.update", (ctx) => Project.Service.use((project) => project.update({ ...ctx.payload, projectID: ctx.params.projectID }).pipe(Effect.mapError(() => new ProjectNotFoundError({
|
|
8
|
+
projectID: ctx.params.projectID,
|
|
9
|
+
message: `Project not found: ${ctx.params.projectID}`
|
|
10
|
+
}))))).handle("project.current", () => Location.Service.use((location) => Effect.succeed({
|
|
11
|
+
id: location.project.id,
|
|
12
|
+
directory: location.project.directory,
|
|
13
|
+
canonical: location.project.canonical
|
|
14
|
+
}))));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ProviderHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.provider">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Catalog } from "@opencode/core/catalog";
|
|
2
|
+
import { ProviderNotFoundError } from "@opencode/protocol/errors";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
5
|
+
import { Api } from "../api";
|
|
6
|
+
import { response } from "../location";
|
|
7
|
+
export const ProviderHandler = HttpApiBuilder.group(Api, "server.provider", (handlers) => Effect.gen(function* () {
|
|
8
|
+
return handlers.handle("provider.list", Effect.fn(function* () {
|
|
9
|
+
const catalog = yield* Catalog.Service;
|
|
10
|
+
return yield* response(catalog.provider.available());
|
|
11
|
+
})).handle("provider.get", Effect.fn(function* (ctx) {
|
|
12
|
+
const provider = yield* (yield* Catalog.Service).provider.get(ctx.params.providerID);
|
|
13
|
+
if (!provider)
|
|
14
|
+
return yield* new ProviderNotFoundError({
|
|
15
|
+
providerID: ctx.params.providerID,
|
|
16
|
+
message: `Provider not found: ${ctx.params.providerID}`
|
|
17
|
+
});
|
|
18
|
+
return yield* response(Effect.succeed(provider));
|
|
19
|
+
}));
|
|
20
|
+
}));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
2
|
+
import { PtyEnvironment } from "../pty-environment";
|
|
3
|
+
export declare const PtyHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.pty">, never, PtyTicket.Service | PtyEnvironment.Service | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware)>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Pty } from "@opencode/core/pty";
|
|
2
|
+
import { PtyProtocol } from "@opencode/core/pty/protocol";
|
|
3
|
+
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
4
|
+
import { Location } from "@opencode/core/location";
|
|
5
|
+
import { Effect, Queue } from "effect";
|
|
6
|
+
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
7
|
+
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
8
|
+
import { Socket } from "effect/unstable/socket";
|
|
9
|
+
import { Api } from "../api";
|
|
10
|
+
import { CorsConfig, isAllowedRequestOrigin } from "../cors";
|
|
11
|
+
import { ForbiddenError, PtyNotFoundError } from "@opencode/protocol/errors";
|
|
12
|
+
import {
|
|
13
|
+
PTY_CONNECT_TICKET_QUERY,
|
|
14
|
+
PTY_CONNECT_TOKEN_HEADER,
|
|
15
|
+
PTY_CONNECT_TOKEN_HEADER_VALUE
|
|
16
|
+
} from "@opencode/protocol/groups/pty";
|
|
17
|
+
import { response } from "../location";
|
|
18
|
+
import { PtyEnvironment } from "../pty-environment";
|
|
19
|
+
import { runPtySocket } from "./pty-socket";
|
|
20
|
+
const ticketScope = Effect.gen(function* () {
|
|
21
|
+
const location = yield* Location.Service;
|
|
22
|
+
return { directory: location.directory, workspaceID: location.workspaceID };
|
|
23
|
+
});
|
|
24
|
+
export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) => Effect.gen(function* () {
|
|
25
|
+
const tickets = yield* PtyTicket.Service, cors = yield* CorsConfig, environment = yield* PtyEnvironment.Service;
|
|
26
|
+
return handlers.handle("pty.list", Effect.fn(function* () {
|
|
27
|
+
const pty = yield* Pty.Service;
|
|
28
|
+
return yield* response(pty.list());
|
|
29
|
+
})).handle("pty.create", Effect.fn(function* (ctx) {
|
|
30
|
+
const pty = yield* Pty.Service, location = yield* Location.Service, cwd = ctx.payload.cwd || location.directory;
|
|
31
|
+
return yield* response(pty.create({
|
|
32
|
+
...ctx.payload,
|
|
33
|
+
args: ctx.payload.args ? [...ctx.payload.args] : void 0,
|
|
34
|
+
cwd,
|
|
35
|
+
env: {
|
|
36
|
+
...ctx.payload.env,
|
|
37
|
+
...yield* environment.get({ directory: location.directory, cwd })
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
})).handle("pty.get", Effect.fn(function* (ctx) {
|
|
41
|
+
const pty = yield* Pty.Service;
|
|
42
|
+
return yield* response(pty.get(ctx.params.ptyID).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
43
|
+
ptyID: ctx.params.ptyID,
|
|
44
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
45
|
+
}))));
|
|
46
|
+
})).handle("pty.update", Effect.fn(function* (ctx) {
|
|
47
|
+
const pty = yield* Pty.Service;
|
|
48
|
+
return yield* response(pty.update(ctx.params.ptyID, {
|
|
49
|
+
...ctx.payload,
|
|
50
|
+
size: ctx.payload.size ? { ...ctx.payload.size } : void 0
|
|
51
|
+
}).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
52
|
+
ptyID: ctx.params.ptyID,
|
|
53
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
54
|
+
}))));
|
|
55
|
+
})).handle("pty.remove", Effect.fn(function* (ctx) {
|
|
56
|
+
yield* (yield* Pty.Service).remove(ctx.params.ptyID).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
57
|
+
ptyID: ctx.params.ptyID,
|
|
58
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
59
|
+
})));
|
|
60
|
+
return HttpApiSchema.NoContent.make();
|
|
61
|
+
})).handle("pty.connectToken", Effect.fn(function* (ctx) {
|
|
62
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
63
|
+
if (request.headers[PTY_CONNECT_TOKEN_HEADER] !== PTY_CONNECT_TOKEN_HEADER_VALUE || !isAllowedRequestOrigin(request.headers.origin, request.headers.host, cors))
|
|
64
|
+
return yield* new ForbiddenError({ message: "Invalid PTY connect token request" });
|
|
65
|
+
yield* (yield* Pty.Service).get(ctx.params.ptyID).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
66
|
+
ptyID: ctx.params.ptyID,
|
|
67
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
68
|
+
})));
|
|
69
|
+
return yield* response(tickets.issue({ ptyID: ctx.params.ptyID, ...yield* ticketScope }));
|
|
70
|
+
})).handleRaw("pty.connect", Effect.fn("PtyHandler.connect")(function* (ctx) {
|
|
71
|
+
const pty = yield* Pty.Service;
|
|
72
|
+
if (!(yield* pty.get(ctx.params.ptyID).pipe(Effect.as(!0), Effect.catchTag("Pty.NotFoundError", () => Effect.succeed(!1)))))
|
|
73
|
+
return HttpServerResponse.empty({ status: 404 });
|
|
74
|
+
const url = new URL(ctx.request.url, "http://localhost"), ticket = url.searchParams.get(PTY_CONNECT_TICKET_QUERY);
|
|
75
|
+
if (ticket) {
|
|
76
|
+
if (!(isAllowedRequestOrigin(ctx.request.headers.origin, ctx.request.headers.host, cors) ? yield* tickets.consume({ ticket, ptyID: ctx.params.ptyID, ...yield* ticketScope }) : !1))
|
|
77
|
+
return HttpServerResponse.empty({ status: 403 });
|
|
78
|
+
}
|
|
79
|
+
const parsedCursor = url.searchParams.get("cursor"), cursorNumber = parsedCursor === null ? void 0 : Number(parsedCursor), cursor = cursorNumber !== void 0 && Number.isSafeInteger(cursorNumber) && cursorNumber >= -1 ? cursorNumber : void 0, socket = yield* Effect.orDie(ctx.request.upgrade), write = yield* socket.writer, closeAccepted = (event) => socket.runRaw(() => Effect.void, { onOpen: write(event).pipe(Effect.catch(() => Effect.void)) }).pipe(Effect.timeout("1 second"), Effect.catchReason("SocketError", "SocketCloseError", () => Effect.void), Effect.catch(() => Effect.void)), outbox = yield* Queue.unbounded(), attachment = yield* pty.attach(ctx.params.ptyID, {
|
|
80
|
+
cursor,
|
|
81
|
+
onData: (chunk) => Queue.offerUnsafe(outbox, chunk),
|
|
82
|
+
onEnd: () => Queue.offerUnsafe(outbox, new Socket.CloseEvent(1000))
|
|
83
|
+
}).pipe(Effect.catchTags({
|
|
84
|
+
"Pty.NotFoundError": () => closeAccepted(new Socket.CloseEvent(4404, "session not found")).pipe(Effect.as(void 0)),
|
|
85
|
+
"Pty.ExitedError": () => closeAccepted(new Socket.CloseEvent(4404, "session exited")).pipe(Effect.as(void 0))
|
|
86
|
+
}));
|
|
87
|
+
if (!attachment)
|
|
88
|
+
return HttpServerResponse.empty();
|
|
89
|
+
for (const chunk of PtyProtocol.chunks(attachment.replay))
|
|
90
|
+
Queue.offerUnsafe(outbox, chunk);
|
|
91
|
+
Queue.offerUnsafe(outbox, PtyProtocol.metaFrame(attachment.cursor));
|
|
92
|
+
attachment.activate();
|
|
93
|
+
const drain = Effect.gen(function* () {
|
|
94
|
+
while (!0) {
|
|
95
|
+
const item = yield* Queue.take(outbox);
|
|
96
|
+
yield* write(item);
|
|
97
|
+
if (item instanceof Socket.CloseEvent)
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
yield* runPtySocket(drain, socket.runRaw((message) => {
|
|
102
|
+
const decoded = PtyProtocol.decodeInput(message);
|
|
103
|
+
if (decoded !== void 0)
|
|
104
|
+
attachment.write(decoded);
|
|
105
|
+
}), attachment.detach).pipe(Effect.catchReason("SocketError", "SocketCloseError", () => Effect.void), Effect.orDie);
|
|
106
|
+
return HttpServerResponse.empty();
|
|
107
|
+
}));
|
|
108
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ReferenceHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.reference">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Reference } from "@opencode/core/reference";
|
|
2
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
3
|
+
import { Api } from "../api";
|
|
4
|
+
import { response } from "../location";
|
|
5
|
+
export const ReferenceHandler = HttpApiBuilder.group(Api, "server.reference", (handlers) => handlers.handle("reference.list", () => response(Reference.Service.use((reference) => reference.list()))));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const RpcHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.rpc">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Rpc } from "@opencode/core/rpc";
|
|
2
|
+
import { Plugin } from "@opencode/core/plugin";
|
|
3
|
+
import { RpcError, RpcInternalError } from "@opencode/protocol/errors";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
6
|
+
import { Api } from "../api";
|
|
7
|
+
export const RpcHandler = HttpApiBuilder.group(Api, "server.rpc", (handlers) => handlers.handle("rpc.call", ({ params, payload }) => Effect.gen(function* () {
|
|
8
|
+
yield* Plugin.awaitActivation;
|
|
9
|
+
const output = yield* (yield* Rpc.Service).call(params.rpcID, params.method, payload.input);
|
|
10
|
+
return output === void 0 ? {} : { output };
|
|
11
|
+
}).pipe(Effect.mapError((error) => error.type === "rpc.invalid_output" || error.type === "rpc.internal" ? new RpcInternalError({ type: error.type, message: error.message }) : new RpcError({
|
|
12
|
+
type: error.type,
|
|
13
|
+
message: error.message,
|
|
14
|
+
...error.data === void 0 ? {} : { data: error.data }
|
|
15
|
+
})), Effect.catchDefect((defect) => Effect.logError("rpc call failed", { rpc: params.rpcID, method: params.method, defect }).pipe(Effect.andThen(Effect.fail(new RpcInternalError({ type: "rpc.internal", message: "RPC call failed" }))))))));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ServerInfo } from "../server-info";
|
|
2
|
+
export declare const ServerHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.server">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("effect/unstable/http/HttpRouter").Request<"Requires", ServerInfo.Service>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
3
|
+
import { Api } from "../api";
|
|
4
|
+
import { ServerInfo } from "../server-info";
|
|
5
|
+
export const ServerHandler = HttpApiBuilder.group(Api, "server.server", (handlers) => handlers.handle("server.get", () => Effect.gen(function* () {
|
|
6
|
+
return { urls: (yield* ServerInfo.Service).urls() };
|
|
7
|
+
})));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Session } from "@opencode/core/session";
|
|
2
|
+
import { SessionNotFoundError, UnknownError } from "@opencode/protocol/errors";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
export declare function missingSession(error: Session.NotFoundError): SessionNotFoundError;
|
|
5
|
+
export declare function failedMessageDecode(error: Session.MessageDecodeError): Effect.Effect<never, UnknownError, never>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Session } from "@opencode/core/session";
|
|
2
|
+
import { SessionNotFoundError, UnknownError } from "@opencode/protocol/errors";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
export function missingSession(error) {
|
|
5
|
+
return new SessionNotFoundError({
|
|
6
|
+
sessionID: error.sessionID,
|
|
7
|
+
message: `Session not found: ${error.sessionID}`
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function failedMessageDecode(error) {
|
|
11
|
+
const ref = `err_${crypto.randomUUID().slice(0, 8)}`;
|
|
12
|
+
return Effect.logError("failed to decode session message").pipe(Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), Effect.andThen(Effect.fail(new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }))));
|
|
13
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Session } from "@opencode/core/session";
|
|
2
|
+
import { SessionTransfer } from "@opencode/core/session/transfer";
|
|
3
|
+
export declare const SessionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session">, never, Session.Service | import("../middleware/session-location").SessionLocationMiddleware | SessionTransfer.Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service> | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization)>;
|