@opencode-ai/server 0.0.0-dev-17880
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 +7528 -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 +68 -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 +11 -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 +1 -0
- package/dist/handlers/form.js +57 -0
- package/dist/handlers/fs.d.ts +1 -0
- package/dist/handlers/fs.js +19 -0
- package/dist/handlers/generate.d.ts +1 -0
- package/dist/handlers/generate.js +10 -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 +22 -0
- package/dist/handlers/permission.d.ts +2 -0
- package/dist/handlers/permission.js +56 -0
- package/dist/handlers/plugin-readiness.d.ts +4 -0
- package/dist/handlers/plugin-readiness.js +9 -0
- package/dist/handlers/plugin.d.ts +1 -0
- package/dist/handlers/plugin.js +8 -0
- package/dist/handlers/project.d.ts +2 -0
- package/dist/handlers/project.js +10 -0
- package/dist/handlers/provider.d.ts +1 -0
- package/dist/handlers/provider.js +20 -0
- package/dist/handlers/pty.d.ts +3 -0
- package/dist/handlers/pty.js +109 -0
- package/dist/handlers/reference.d.ts +1 -0
- package/dist/handlers/reference.js +5 -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 +275 -0
- package/dist/handlers/shell.d.ts +1 -0
- package/dist/handlers/shell.js +30 -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 +14 -0
- package/dist/handlers/websearch.d.ts +1 -0
- package/dist/handlers/websearch.js +38 -0
- package/dist/handlers/worktree.d.ts +2 -0
- package/dist/handlers/worktree.js +32 -0
- package/dist/handlers.d.ts +2 -0
- package/dist/handlers.js +32 -0
- package/dist/location.d.ts +23 -0
- package/dist/location.js +44 -0
- package/dist/middleware/authorization.d.ts +7 -0
- package/dist/middleware/authorization.js +48 -0
- package/dist/middleware/form-location.d.ts +18 -0
- package/dist/middleware/form-location.js +42 -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 +18 -0
- package/dist/middleware/session-location.js +36 -0
- package/dist/options.d.ts +38 -0
- package/dist/options.js +32 -0
- package/dist/process.d.ts +47 -0
- package/dist/process.js +115 -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 +23 -0
- package/dist/routes.js +112 -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 +57 -0
- package/package.json +45 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MCP } from "@opencode-ai/core/mcp/index";
|
|
2
|
+
import { McpServerNotFoundError } from "@opencode-ai/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-ai/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-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization)>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SessionMessage } from "@opencode-ai/core/session/message";
|
|
2
|
+
import { Session } from "@opencode-ai/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-ai/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-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/database/database").Service>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { V1Migration } from "@opencode-ai/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-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Catalog } from "@opencode-ai/core/catalog";
|
|
2
|
+
import { ServiceUnavailableError } from "@opencode-ai/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
|
+
import { pluginReadiness } from "./plugin-readiness";
|
|
8
|
+
const flushPlugins = pluginReadiness(() => new ServiceUnavailableError({
|
|
9
|
+
message: "Model catalog initialization timed out",
|
|
10
|
+
service: "model.catalog"
|
|
11
|
+
}));
|
|
12
|
+
export const ModelHandler = HttpApiBuilder.group(Api, "server.model", (handlers) => Effect.gen(function* () {
|
|
13
|
+
return handlers.handle("model.list", Effect.fn(function* () {
|
|
14
|
+
yield* flushPlugins;
|
|
15
|
+
const catalog = yield* Catalog.Service;
|
|
16
|
+
return yield* response(catalog.model.available());
|
|
17
|
+
})).handle("model.default", Effect.fn(function* () {
|
|
18
|
+
yield* flushPlugins;
|
|
19
|
+
const catalog = yield* Catalog.Service;
|
|
20
|
+
return yield* response(catalog.model.default());
|
|
21
|
+
}));
|
|
22
|
+
}));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { PermissionSaved } from "@opencode-ai/core/permission/saved";
|
|
2
|
+
export declare const PermissionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.permission">, never, import("../middleware/session-location").SessionLocationMiddleware | (import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware | import("effect/unstable/http/HttpRouter").Request<"Requires", PermissionSaved.Service>)>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Location } from "@opencode-ai/core/location";
|
|
2
|
+
import { Permission } from "@opencode-ai/core/permission";
|
|
3
|
+
import { PermissionSaved } from "@opencode-ai/core/permission/saved";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
6
|
+
import { Api } from "../api";
|
|
7
|
+
import { PermissionNotFoundError, SessionNotFoundError } from "@opencode-ai/protocol/errors";
|
|
8
|
+
import { response } from "../location";
|
|
9
|
+
function missingRequest(id) {
|
|
10
|
+
return new PermissionNotFoundError({ requestID: id, message: `Permission request not found: ${id}` });
|
|
11
|
+
}
|
|
12
|
+
export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission", (handlers) => Effect.gen(function* () {
|
|
13
|
+
const requireOwnedRequest = Effect.fnUntraced(function* (sessionID, requestID) {
|
|
14
|
+
const permission = yield* Permission.Service, request = yield* permission.get(requestID);
|
|
15
|
+
if (!request || request.sessionID !== sessionID)
|
|
16
|
+
return yield* missingRequest(requestID);
|
|
17
|
+
return { permission, request };
|
|
18
|
+
});
|
|
19
|
+
return handlers.handle("permission.request.list", Effect.fn(function* () {
|
|
20
|
+
const permission = yield* Permission.Service;
|
|
21
|
+
return yield* response(permission.list());
|
|
22
|
+
})).handle("session.permission.create", Effect.fn(function* (ctx) {
|
|
23
|
+
return {
|
|
24
|
+
data: yield* (yield* Permission.Service).ask({
|
|
25
|
+
id: ctx.payload.id,
|
|
26
|
+
sessionID: ctx.params.sessionID,
|
|
27
|
+
action: ctx.payload.action,
|
|
28
|
+
resources: ctx.payload.resources,
|
|
29
|
+
save: ctx.payload.save,
|
|
30
|
+
metadata: ctx.payload.metadata,
|
|
31
|
+
source: ctx.payload.source,
|
|
32
|
+
agent: ctx.payload.agent
|
|
33
|
+
}).pipe(Effect.catchTag("Session.NotFoundError", (error) => new SessionNotFoundError({
|
|
34
|
+
sessionID: error.sessionID,
|
|
35
|
+
message: `Session not found: ${error.sessionID}`
|
|
36
|
+
})))
|
|
37
|
+
};
|
|
38
|
+
})).handle("session.permission.list", Effect.fn(function* (ctx) {
|
|
39
|
+
return { data: yield* (yield* Permission.Service).forSession(ctx.params.sessionID) };
|
|
40
|
+
})).handle("session.permission.get", Effect.fn(function* (ctx) {
|
|
41
|
+
return { data: (yield* requireOwnedRequest(ctx.params.sessionID, ctx.params.requestID)).request };
|
|
42
|
+
})).handle("session.permission.reply", Effect.fn(function* (ctx) {
|
|
43
|
+
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)));
|
|
44
|
+
return HttpApiSchema.NoContent.make();
|
|
45
|
+
})).handle("permission.saved.list", Effect.fn(function* (ctx) {
|
|
46
|
+
const location = yield* Location.Service;
|
|
47
|
+
return {
|
|
48
|
+
data: yield* (yield* PermissionSaved.Service).list({
|
|
49
|
+
projectID: ctx.query.projectID ?? location.project.id
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
})).handle("permission.saved.remove", Effect.fn(function* (ctx) {
|
|
53
|
+
yield* (yield* PermissionSaved.Service).remove(ctx.params.id);
|
|
54
|
+
return HttpApiSchema.NoContent.make();
|
|
55
|
+
}));
|
|
56
|
+
}));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor";
|
|
2
|
+
import { ServiceUnavailableError } from "@opencode-ai/protocol/errors";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
export declare function pluginReadiness(error: () => ServiceUnavailableError): Effect.Effect<void, ServiceUnavailableError, PluginSupervisor.Service>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor";
|
|
2
|
+
import { ServiceUnavailableError } from "@opencode-ai/protocol/errors";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
export function pluginReadiness(error) {
|
|
5
|
+
return PluginSupervisor.Service.pipe(Effect.flatMap((plugins) => plugins.flush), Effect.timeoutOrElse({
|
|
6
|
+
duration: "5 seconds",
|
|
7
|
+
orElse: () => Effect.fail(error())
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PluginHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.plugin">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Plugin } from "@opencode-ai/core/plugin";
|
|
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 PluginHandler = HttpApiBuilder.group(Api, "server.plugin", (handlers) => handlers.handle("plugin.list", () => Effect.gen(function* () {
|
|
7
|
+
return yield* response(Plugin.Service.use((plugin) => plugin.list()));
|
|
8
|
+
})));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Project } from "@opencode-ai/core/project";
|
|
2
|
+
export declare const ProjectHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.project">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware | import("effect/unstable/http/HttpRouter").Request<"Requires", Project.Service>>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Location } from "@opencode-ai/core/location";
|
|
2
|
+
import { Project } from "@opencode-ai/core/project";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
5
|
+
import { Api } from "../api";
|
|
6
|
+
export const ProjectHandler = HttpApiBuilder.group(Api, "server.project", (handlers) => handlers.handle("project.list", () => Project.Service.use((project) => project.list())).handle("project.current", () => Location.Service.use((location) => Effect.succeed({
|
|
7
|
+
id: location.project.id,
|
|
8
|
+
directory: location.project.directory,
|
|
9
|
+
canonical: location.project.canonical
|
|
10
|
+
}))));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ProviderHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.provider">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Catalog } from "@opencode-ai/core/catalog";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
4
|
+
import { Api } from "../api";
|
|
5
|
+
import { ProviderNotFoundError } from "@opencode-ai/protocol/errors";
|
|
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-ai/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-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware)>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Pty } from "@opencode-ai/core/pty";
|
|
2
|
+
import { PtyProtocol } from "@opencode-ai/core/pty/protocol";
|
|
3
|
+
import { PtyTicket } from "@opencode-ai/core/pty/ticket";
|
|
4
|
+
import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor-service";
|
|
5
|
+
import { Location } from "@opencode-ai/core/location";
|
|
6
|
+
import { Effect, Queue } from "effect";
|
|
7
|
+
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
8
|
+
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
9
|
+
import { Socket } from "effect/unstable/socket";
|
|
10
|
+
import { Api } from "../api";
|
|
11
|
+
import { CorsConfig, isAllowedRequestOrigin } from "../cors";
|
|
12
|
+
import { ForbiddenError, PtyNotFoundError } from "@opencode-ai/protocol/errors";
|
|
13
|
+
import {
|
|
14
|
+
PTY_CONNECT_TICKET_QUERY,
|
|
15
|
+
PTY_CONNECT_TOKEN_HEADER,
|
|
16
|
+
PTY_CONNECT_TOKEN_HEADER_VALUE
|
|
17
|
+
} from "@opencode-ai/protocol/groups/pty";
|
|
18
|
+
import { response } from "../location";
|
|
19
|
+
import { PtyEnvironment } from "../pty-environment";
|
|
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
|
+
yield* (yield* PluginSupervisor.Service).flush;
|
|
31
|
+
const pty = yield* Pty.Service, location = yield* Location.Service, cwd = ctx.payload.cwd || location.directory;
|
|
32
|
+
return yield* response(pty.create({
|
|
33
|
+
...ctx.payload,
|
|
34
|
+
args: ctx.payload.args ? [...ctx.payload.args] : void 0,
|
|
35
|
+
cwd,
|
|
36
|
+
env: {
|
|
37
|
+
...ctx.payload.env,
|
|
38
|
+
...yield* environment.get({ directory: location.directory, cwd })
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
})).handle("pty.get", Effect.fn(function* (ctx) {
|
|
42
|
+
const pty = yield* Pty.Service;
|
|
43
|
+
return yield* response(pty.get(ctx.params.ptyID).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
44
|
+
ptyID: ctx.params.ptyID,
|
|
45
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
46
|
+
}))));
|
|
47
|
+
})).handle("pty.update", Effect.fn(function* (ctx) {
|
|
48
|
+
const pty = yield* Pty.Service;
|
|
49
|
+
return yield* response(pty.update(ctx.params.ptyID, {
|
|
50
|
+
...ctx.payload,
|
|
51
|
+
size: ctx.payload.size ? { ...ctx.payload.size } : void 0
|
|
52
|
+
}).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
53
|
+
ptyID: ctx.params.ptyID,
|
|
54
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
55
|
+
}))));
|
|
56
|
+
})).handle("pty.remove", Effect.fn(function* (ctx) {
|
|
57
|
+
yield* (yield* Pty.Service).remove(ctx.params.ptyID).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
58
|
+
ptyID: ctx.params.ptyID,
|
|
59
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
60
|
+
})));
|
|
61
|
+
return HttpApiSchema.NoContent.make();
|
|
62
|
+
})).handle("pty.connectToken", Effect.fn(function* (ctx) {
|
|
63
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
64
|
+
if (request.headers[PTY_CONNECT_TOKEN_HEADER] !== PTY_CONNECT_TOKEN_HEADER_VALUE || !isAllowedRequestOrigin(request.headers.origin, request.headers.host, cors))
|
|
65
|
+
return yield* new ForbiddenError({ message: "Invalid PTY connect token request" });
|
|
66
|
+
yield* (yield* Pty.Service).get(ctx.params.ptyID).pipe(Effect.catchTag("Pty.NotFoundError", () => new PtyNotFoundError({
|
|
67
|
+
ptyID: ctx.params.ptyID,
|
|
68
|
+
message: `PTY session not found: ${ctx.params.ptyID}`
|
|
69
|
+
})));
|
|
70
|
+
return yield* response(tickets.issue({ ptyID: ctx.params.ptyID, ...yield* ticketScope }));
|
|
71
|
+
})).handleRaw("pty.connect", Effect.fn("PtyHandler.connect")(function* (ctx) {
|
|
72
|
+
const pty = yield* Pty.Service;
|
|
73
|
+
if (!(yield* pty.get(ctx.params.ptyID).pipe(Effect.as(!0), Effect.catchTag("Pty.NotFoundError", () => Effect.succeed(!1)))))
|
|
74
|
+
return HttpServerResponse.empty({ status: 404 });
|
|
75
|
+
const url = new URL(ctx.request.url, "http://localhost"), ticket = url.searchParams.get(PTY_CONNECT_TICKET_QUERY);
|
|
76
|
+
if (ticket) {
|
|
77
|
+
if (!(isAllowedRequestOrigin(ctx.request.headers.origin, ctx.request.headers.host, cors) ? yield* tickets.consume({ ticket, ptyID: ctx.params.ptyID, ...yield* ticketScope }) : !1))
|
|
78
|
+
return HttpServerResponse.empty({ status: 403 });
|
|
79
|
+
}
|
|
80
|
+
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, {
|
|
81
|
+
cursor,
|
|
82
|
+
onData: (chunk) => Queue.offerUnsafe(outbox, chunk),
|
|
83
|
+
onEnd: () => Queue.offerUnsafe(outbox, new Socket.CloseEvent(1000))
|
|
84
|
+
}).pipe(Effect.catchTags({
|
|
85
|
+
"Pty.NotFoundError": () => closeAccepted(new Socket.CloseEvent(4404, "session not found")).pipe(Effect.as(void 0)),
|
|
86
|
+
"Pty.ExitedError": () => closeAccepted(new Socket.CloseEvent(4404, "session exited")).pipe(Effect.as(void 0))
|
|
87
|
+
}));
|
|
88
|
+
if (!attachment)
|
|
89
|
+
return HttpServerResponse.empty();
|
|
90
|
+
for (const chunk of PtyProtocol.chunks(attachment.replay))
|
|
91
|
+
Queue.offerUnsafe(outbox, chunk);
|
|
92
|
+
Queue.offerUnsafe(outbox, PtyProtocol.metaFrame(attachment.cursor));
|
|
93
|
+
attachment.activate();
|
|
94
|
+
const drain = Effect.gen(function* () {
|
|
95
|
+
while (!0) {
|
|
96
|
+
const item = yield* Queue.take(outbox);
|
|
97
|
+
yield* write(item);
|
|
98
|
+
if (item instanceof Socket.CloseEvent)
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
yield* Effect.race(drain, socket.runRaw((message) => {
|
|
103
|
+
const decoded = PtyProtocol.decodeInput(message);
|
|
104
|
+
if (decoded !== void 0)
|
|
105
|
+
attachment.write(decoded);
|
|
106
|
+
})).pipe(Effect.catchReason("SocketError", "SocketCloseError", () => Effect.void), Effect.ensuring(Effect.sync(() => attachment.detach())), Effect.orDie);
|
|
107
|
+
return HttpServerResponse.empty();
|
|
108
|
+
}));
|
|
109
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ReferenceHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.reference">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Reference } from "@opencode-ai/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,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-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/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-ai/core/session";
|
|
2
|
+
import { SessionNotFoundError, UnknownError } from "@opencode-ai/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-ai/core/session";
|
|
2
|
+
import { SessionNotFoundError, UnknownError } from "@opencode-ai/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-ai/core/session";
|
|
2
|
+
import { SessionTransfer } from "@opencode-ai/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("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization)>;
|