@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,18 @@
|
|
|
1
|
+
import { Database } from "@opencode-ai/core/database/database";
|
|
2
|
+
import { Location } from "@opencode-ai/core/location";
|
|
3
|
+
import { LocationServiceMap } from "@opencode-ai/core/location-services";
|
|
4
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors";
|
|
5
|
+
import { Layer } from "effect";
|
|
6
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
7
|
+
declare const FormLocationMiddleware_base: HttpApiMiddleware.ServiceClass<FormLocationMiddleware, "@opencode/HttpApiFormLocation", {
|
|
8
|
+
requires: never;
|
|
9
|
+
provides: Location.Service | import("@opencode-ai/core/agent").Service | import("@opencode-ai/core/aisdk").Service | import("@opencode-ai/core/form").Service | import("@opencode-ai/core/integration").Service | import("@opencode-ai/core/catalog").Service | import("@opencode-ai/core/environment/environment").Service | import("@opencode-ai/core/mcp/index").Service | import("@opencode-ai/core/shell/select").Service | import("@opencode-ai/core/command").Service | import("@opencode-ai/core/config").Service | import("@opencode-ai/core/file-mutation").Service | import("@opencode-ai/core/formatter").Service | import("@opencode-ai/core/filesystem/search").Service | import("@opencode-ai/core/filesystem").Service | import("@opencode-ai/core/model-resolver").Service | import("@opencode-ai/core/generate").Service | import("@opencode-ai/core/image").Service | import("@opencode-ai/core/plugin/supervisor-service").Service | import("@opencode-ai/core/reference").Service | import("@opencode-ai/core/websearch").Service | import("@opencode-ai/core/plugin").Service | import("@opencode-ai/core/skill").Service | import("@opencode-ai/core/tool").Service | import("@opencode-ai/core/location-mutation").Service | import("@opencode-ai/core/permission").Service | import("@opencode-ai/core/tool/read-filesystem").Service | import("@opencode-ai/core/session/instructions").Service | import("@opencode-ai/core/shell").Service | import("@opencode-ai/core/instruction-discovery").Service | import("@opencode-ai/core/session/compaction").Service | import("@opencode-ai/core/snapshot").Service | import("@opencode-ai/core/tool-output").Service | import("@opencode-ai/core/session/runner/model").Service | import("@opencode-ai/core/session/model-transport").Service | import("@opencode-ai/core/pty").Service | import("@opencode-ai/core/instructions/builtins").Service | import("@opencode-ai/core/skill/instructions").Service | import("@opencode-ai/core/reference/instructions").Service | import("@opencode-ai/core/session/instruction-entry").Service | import("@opencode-ai/core/session/generate").Service | import("@opencode-ai/core/tool/mcp").Service | import("@opencode-ai/core/session/title").Service | import("@opencode-ai/core/session/runner/index").Service | import("@opencode-ai/core/vcs").Service | import("@opencode-ai/core/filesystem/location-watcher").Service;
|
|
10
|
+
error: readonly [typeof InvalidRequestError, typeof SessionNotFoundError];
|
|
11
|
+
clientError: never;
|
|
12
|
+
requiredForClient: false;
|
|
13
|
+
security: never;
|
|
14
|
+
}, HttpApiMiddleware.HttpApiMiddleware<Location.Service | import("@opencode-ai/core/agent").Service | import("@opencode-ai/core/aisdk").Service | import("@opencode-ai/core/form").Service | import("@opencode-ai/core/integration").Service | import("@opencode-ai/core/catalog").Service | import("@opencode-ai/core/environment/environment").Service | import("@opencode-ai/core/mcp/index").Service | import("@opencode-ai/core/shell/select").Service | import("@opencode-ai/core/command").Service | import("@opencode-ai/core/config").Service | import("@opencode-ai/core/file-mutation").Service | import("@opencode-ai/core/formatter").Service | import("@opencode-ai/core/filesystem/search").Service | import("@opencode-ai/core/filesystem").Service | import("@opencode-ai/core/model-resolver").Service | import("@opencode-ai/core/generate").Service | import("@opencode-ai/core/image").Service | import("@opencode-ai/core/plugin/supervisor-service").Service | import("@opencode-ai/core/reference").Service | import("@opencode-ai/core/websearch").Service | import("@opencode-ai/core/plugin").Service | import("@opencode-ai/core/skill").Service | import("@opencode-ai/core/tool").Service | import("@opencode-ai/core/location-mutation").Service | import("@opencode-ai/core/permission").Service | import("@opencode-ai/core/tool/read-filesystem").Service | import("@opencode-ai/core/session/instructions").Service | import("@opencode-ai/core/shell").Service | import("@opencode-ai/core/instruction-discovery").Service | import("@opencode-ai/core/session/compaction").Service | import("@opencode-ai/core/snapshot").Service | import("@opencode-ai/core/tool-output").Service | import("@opencode-ai/core/session/runner/model").Service | import("@opencode-ai/core/session/model-transport").Service | import("@opencode-ai/core/pty").Service | import("@opencode-ai/core/instructions/builtins").Service | import("@opencode-ai/core/skill/instructions").Service | import("@opencode-ai/core/reference/instructions").Service | import("@opencode-ai/core/session/instruction-entry").Service | import("@opencode-ai/core/session/generate").Service | import("@opencode-ai/core/tool/mcp").Service | import("@opencode-ai/core/session/title").Service | import("@opencode-ai/core/session/runner/index").Service | import("@opencode-ai/core/vcs").Service | import("@opencode-ai/core/filesystem/location-watcher").Service, readonly [typeof InvalidRequestError, typeof SessionNotFoundError], never>>;
|
|
15
|
+
export declare class FormLocationMiddleware extends FormLocationMiddleware_base {
|
|
16
|
+
}
|
|
17
|
+
export declare const formLocationLayer: Layer.Layer<FormLocationMiddleware, never, Database.Service | LocationServiceMap.Service>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Database } from "@opencode-ai/core/database/database";
|
|
2
|
+
import { Location } from "@opencode-ai/core/location";
|
|
3
|
+
import { LocationServiceMap } from "@opencode-ai/core/location-services";
|
|
4
|
+
import { AbsolutePath } from "@opencode-ai/core/schema";
|
|
5
|
+
import { Session } from "@opencode-ai/core/session";
|
|
6
|
+
import { SessionTable } from "@opencode-ai/core/session/sql";
|
|
7
|
+
import { Workspace } from "@opencode-ai/core/workspace";
|
|
8
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors";
|
|
9
|
+
import { eq } from "drizzle-orm";
|
|
10
|
+
import { Effect, Layer, Schema } from "effect";
|
|
11
|
+
import { HttpRouter, HttpServerRequest } from "effect/unstable/http";
|
|
12
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
13
|
+
import { requestRef } from "../location";
|
|
14
|
+
|
|
15
|
+
export class FormLocationMiddleware extends HttpApiMiddleware.Service()("@opencode/HttpApiFormLocation", {
|
|
16
|
+
error: [InvalidRequestError, SessionNotFoundError]
|
|
17
|
+
}) {
|
|
18
|
+
}
|
|
19
|
+
const decodeSessionID = Schema.decodeUnknownEffect(Session.ID);
|
|
20
|
+
export const formLocationLayer = Layer.effect(FormLocationMiddleware, Effect.gen(function* () {
|
|
21
|
+
const { db } = yield* Database.Service, locations = yield* LocationServiceMap.Service;
|
|
22
|
+
return FormLocationMiddleware.of((effect) => Effect.gen(function* () {
|
|
23
|
+
const route = yield* HttpRouter.RouteContext;
|
|
24
|
+
if (route.params.sessionID === "global") {
|
|
25
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
26
|
+
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))));
|
|
27
|
+
}
|
|
28
|
+
const sessionID = yield* decodeSessionID(route.params.sessionID).pipe(Effect.mapError(() => new InvalidRequestError({
|
|
29
|
+
message: "Invalid session ID",
|
|
30
|
+
field: "sessionID"
|
|
31
|
+
}))), row = yield* db.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id }).from(SessionTable).where(eq(SessionTable.id, sessionID)).get().pipe(Effect.orDie);
|
|
32
|
+
if (!row)
|
|
33
|
+
return yield* new SessionNotFoundError({
|
|
34
|
+
sessionID,
|
|
35
|
+
message: `Session not found: ${sessionID}`
|
|
36
|
+
});
|
|
37
|
+
return yield* effect.pipe(Effect.provide(locations.get(Location.Ref.make({
|
|
38
|
+
directory: AbsolutePath.make(row.directory),
|
|
39
|
+
workspaceID: row.workspaceID ? Workspace.ID.make(row.workspaceID) : void 0
|
|
40
|
+
}))));
|
|
41
|
+
}));
|
|
42
|
+
}));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { SchemaErrorMiddleware } from "@opencode-ai/protocol/middleware/schema-error";
|
|
2
|
+
export { SchemaErrorMiddleware } from "@opencode-ai/protocol/middleware/schema-error";
|
|
3
|
+
export declare const schemaErrorLayer: import("effect/Layer").Layer<SchemaErrorMiddleware, never, never>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
3
|
+
import { InvalidRequestError } from "@opencode-ai/protocol/errors";
|
|
4
|
+
import { SchemaErrorMiddleware } from "@opencode-ai/protocol/middleware/schema-error";
|
|
5
|
+
export { SchemaErrorMiddleware } from "@opencode-ai/protocol/middleware/schema-error";
|
|
6
|
+
const REASON_LIMIT = 1024;
|
|
7
|
+
function truncateReason(reason) {
|
|
8
|
+
if (reason.length <= REASON_LIMIT)
|
|
9
|
+
return reason;
|
|
10
|
+
return reason.slice(0, REASON_LIMIT) + `... (${reason.length - REASON_LIMIT} more chars)`;
|
|
11
|
+
}
|
|
12
|
+
export const schemaErrorLayer = HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrorMiddleware, (error) => {
|
|
13
|
+
const reason = truncateReason(error.cause.message);
|
|
14
|
+
return Effect.logWarning("schema rejection").pipe(Effect.annotateLogs({ kind: error.kind, reason }), Effect.andThen(Effect.fail(new InvalidRequestError({ message: reason, kind: error.kind }))));
|
|
15
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Database } from "@opencode-ai/core/database/database";
|
|
2
|
+
import { LocationServiceMap } from "@opencode-ai/core/location-services";
|
|
3
|
+
import { Location } from "@opencode-ai/core/location";
|
|
4
|
+
import { Layer } from "effect";
|
|
5
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
6
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors";
|
|
7
|
+
declare const SessionLocationMiddleware_base: HttpApiMiddleware.ServiceClass<SessionLocationMiddleware, "@opencode/HttpApiSessionLocation", {
|
|
8
|
+
requires: never;
|
|
9
|
+
provides: Location.Service | import("@opencode-ai/core/agent").Service | import("@opencode-ai/core/aisdk").Service | import("@opencode-ai/core/form").Service | import("@opencode-ai/core/integration").Service | import("@opencode-ai/core/catalog").Service | import("@opencode-ai/core/environment/environment").Service | import("@opencode-ai/core/mcp/index").Service | import("@opencode-ai/core/shell/select").Service | import("@opencode-ai/core/command").Service | import("@opencode-ai/core/config").Service | import("@opencode-ai/core/file-mutation").Service | import("@opencode-ai/core/formatter").Service | import("@opencode-ai/core/filesystem/search").Service | import("@opencode-ai/core/filesystem").Service | import("@opencode-ai/core/model-resolver").Service | import("@opencode-ai/core/generate").Service | import("@opencode-ai/core/image").Service | import("@opencode-ai/core/plugin/supervisor-service").Service | import("@opencode-ai/core/reference").Service | import("@opencode-ai/core/websearch").Service | import("@opencode-ai/core/plugin").Service | import("@opencode-ai/core/skill").Service | import("@opencode-ai/core/tool").Service | import("@opencode-ai/core/location-mutation").Service | import("@opencode-ai/core/permission").Service | import("@opencode-ai/core/tool/read-filesystem").Service | import("@opencode-ai/core/session/instructions").Service | import("@opencode-ai/core/shell").Service | import("@opencode-ai/core/instruction-discovery").Service | import("@opencode-ai/core/session/compaction").Service | import("@opencode-ai/core/snapshot").Service | import("@opencode-ai/core/tool-output").Service | import("@opencode-ai/core/session/runner/model").Service | import("@opencode-ai/core/session/model-transport").Service | import("@opencode-ai/core/pty").Service | import("@opencode-ai/core/instructions/builtins").Service | import("@opencode-ai/core/skill/instructions").Service | import("@opencode-ai/core/reference/instructions").Service | import("@opencode-ai/core/session/instruction-entry").Service | import("@opencode-ai/core/session/generate").Service | import("@opencode-ai/core/tool/mcp").Service | import("@opencode-ai/core/session/title").Service | import("@opencode-ai/core/session/runner/index").Service | import("@opencode-ai/core/vcs").Service | import("@opencode-ai/core/filesystem/location-watcher").Service;
|
|
10
|
+
error: readonly [typeof InvalidRequestError, typeof SessionNotFoundError];
|
|
11
|
+
clientError: never;
|
|
12
|
+
requiredForClient: false;
|
|
13
|
+
security: never;
|
|
14
|
+
}, HttpApiMiddleware.HttpApiMiddleware<Location.Service | import("@opencode-ai/core/agent").Service | import("@opencode-ai/core/aisdk").Service | import("@opencode-ai/core/form").Service | import("@opencode-ai/core/integration").Service | import("@opencode-ai/core/catalog").Service | import("@opencode-ai/core/environment/environment").Service | import("@opencode-ai/core/mcp/index").Service | import("@opencode-ai/core/shell/select").Service | import("@opencode-ai/core/command").Service | import("@opencode-ai/core/config").Service | import("@opencode-ai/core/file-mutation").Service | import("@opencode-ai/core/formatter").Service | import("@opencode-ai/core/filesystem/search").Service | import("@opencode-ai/core/filesystem").Service | import("@opencode-ai/core/model-resolver").Service | import("@opencode-ai/core/generate").Service | import("@opencode-ai/core/image").Service | import("@opencode-ai/core/plugin/supervisor-service").Service | import("@opencode-ai/core/reference").Service | import("@opencode-ai/core/websearch").Service | import("@opencode-ai/core/plugin").Service | import("@opencode-ai/core/skill").Service | import("@opencode-ai/core/tool").Service | import("@opencode-ai/core/location-mutation").Service | import("@opencode-ai/core/permission").Service | import("@opencode-ai/core/tool/read-filesystem").Service | import("@opencode-ai/core/session/instructions").Service | import("@opencode-ai/core/shell").Service | import("@opencode-ai/core/instruction-discovery").Service | import("@opencode-ai/core/session/compaction").Service | import("@opencode-ai/core/snapshot").Service | import("@opencode-ai/core/tool-output").Service | import("@opencode-ai/core/session/runner/model").Service | import("@opencode-ai/core/session/model-transport").Service | import("@opencode-ai/core/pty").Service | import("@opencode-ai/core/instructions/builtins").Service | import("@opencode-ai/core/skill/instructions").Service | import("@opencode-ai/core/reference/instructions").Service | import("@opencode-ai/core/session/instruction-entry").Service | import("@opencode-ai/core/session/generate").Service | import("@opencode-ai/core/tool/mcp").Service | import("@opencode-ai/core/session/title").Service | import("@opencode-ai/core/session/runner/index").Service | import("@opencode-ai/core/vcs").Service | import("@opencode-ai/core/filesystem/location-watcher").Service, readonly [typeof InvalidRequestError, typeof SessionNotFoundError], never>>;
|
|
15
|
+
export declare class SessionLocationMiddleware extends SessionLocationMiddleware_base {
|
|
16
|
+
}
|
|
17
|
+
export declare const sessionLocationLayer: Layer.Layer<SessionLocationMiddleware, never, Database.Service | LocationServiceMap.Service>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Database } from "@opencode-ai/core/database/database";
|
|
2
|
+
import { LocationServiceMap } from "@opencode-ai/core/location-services";
|
|
3
|
+
import { Location } from "@opencode-ai/core/location";
|
|
4
|
+
import { AbsolutePath } from "@opencode-ai/core/schema";
|
|
5
|
+
import { Session } from "@opencode-ai/core/session";
|
|
6
|
+
import { SessionTable } from "@opencode-ai/core/session/sql";
|
|
7
|
+
import { Workspace } from "@opencode-ai/core/workspace";
|
|
8
|
+
import { eq } from "drizzle-orm";
|
|
9
|
+
import { Effect, Layer, Schema } from "effect";
|
|
10
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
11
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
12
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors";
|
|
13
|
+
|
|
14
|
+
export class SessionLocationMiddleware extends HttpApiMiddleware.Service()("@opencode/HttpApiSessionLocation", {
|
|
15
|
+
error: [InvalidRequestError, SessionNotFoundError]
|
|
16
|
+
}) {
|
|
17
|
+
}
|
|
18
|
+
const decodeSessionID = Schema.decodeUnknownEffect(Session.ID);
|
|
19
|
+
export const sessionLocationLayer = Layer.effect(SessionLocationMiddleware, Effect.gen(function* () {
|
|
20
|
+
const { db } = yield* Database.Service, locations = yield* LocationServiceMap.Service;
|
|
21
|
+
return SessionLocationMiddleware.of((effect) => Effect.gen(function* () {
|
|
22
|
+
const route = yield* HttpRouter.RouteContext, sessionID = yield* decodeSessionID(route.params.sessionID).pipe(Effect.mapError(() => new InvalidRequestError({
|
|
23
|
+
message: "Invalid session ID",
|
|
24
|
+
field: "sessionID"
|
|
25
|
+
}))), row = yield* db.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id }).from(SessionTable).where(eq(SessionTable.id, sessionID)).get().pipe(Effect.orDie);
|
|
26
|
+
if (!row)
|
|
27
|
+
return yield* new SessionNotFoundError({
|
|
28
|
+
sessionID,
|
|
29
|
+
message: `Session not found: ${sessionID}`
|
|
30
|
+
});
|
|
31
|
+
return yield* effect.pipe(Effect.provide(locations.get(Location.Ref.make({
|
|
32
|
+
directory: AbsolutePath.make(row.directory),
|
|
33
|
+
workspaceID: row.workspaceID ? Workspace.ID.make(row.workspaceID) : void 0
|
|
34
|
+
}))));
|
|
35
|
+
}));
|
|
36
|
+
}));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
export declare const ServerOptions: Schema.Struct<{
|
|
3
|
+
readonly app: Schema.optional<Schema.Struct<{
|
|
4
|
+
readonly name: Schema.optional<Schema.String>;
|
|
5
|
+
readonly version: Schema.optional<Schema.String>;
|
|
6
|
+
readonly channel: Schema.optional<Schema.String>;
|
|
7
|
+
}>>;
|
|
8
|
+
readonly hostname: Schema.optional<Schema.String>;
|
|
9
|
+
readonly port: Schema.optional<Schema.Int>;
|
|
10
|
+
readonly password: Schema.optional<Schema.String>;
|
|
11
|
+
readonly simulation: Schema.optional<Schema.Boolean>;
|
|
12
|
+
readonly database: Schema.optional<Schema.Struct<{
|
|
13
|
+
readonly path: Schema.optional<Schema.String>;
|
|
14
|
+
}>>;
|
|
15
|
+
readonly events: Schema.optional<Schema.Struct<{
|
|
16
|
+
readonly persist: Schema.optional<Schema.Boolean>;
|
|
17
|
+
}>>;
|
|
18
|
+
readonly models: Schema.optional<Schema.Struct<{
|
|
19
|
+
readonly url: Schema.optional<Schema.String>;
|
|
20
|
+
readonly file: Schema.optional<Schema.String>;
|
|
21
|
+
readonly fetch: Schema.optional<Schema.Boolean>;
|
|
22
|
+
readonly snapshot: Schema.optional<Schema.Boolean>;
|
|
23
|
+
}>>;
|
|
24
|
+
readonly config: Schema.optional<Schema.Struct<{
|
|
25
|
+
readonly directory: Schema.optional<Schema.String>;
|
|
26
|
+
readonly project: Schema.optional<Schema.Boolean>;
|
|
27
|
+
readonly file: Schema.optional<Schema.String>;
|
|
28
|
+
readonly content: Schema.optional<Schema.String>;
|
|
29
|
+
}>>;
|
|
30
|
+
readonly windows: Schema.optional<Schema.Struct<{
|
|
31
|
+
readonly gitbash: Schema.optional<Schema.String>;
|
|
32
|
+
}>>;
|
|
33
|
+
readonly fs: Schema.optional<Schema.Struct<{
|
|
34
|
+
readonly filewatcher: Schema.optional<Schema.Boolean>;
|
|
35
|
+
readonly fff: Schema.optional<Schema.Boolean>;
|
|
36
|
+
}>>;
|
|
37
|
+
}>;
|
|
38
|
+
export type ServerOptions = typeof ServerOptions.Type;
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Database } from "@opencode-ai/core/database/database";
|
|
2
|
+
import { ModelsDev } from "@opencode-ai/core/models-dev";
|
|
3
|
+
import { Schema } from "effect";
|
|
4
|
+
export const ServerOptions = Schema.Struct({
|
|
5
|
+
app: Schema.optional(Schema.Struct({
|
|
6
|
+
name: Schema.optional(Schema.String),
|
|
7
|
+
version: Schema.optional(Schema.String),
|
|
8
|
+
channel: Schema.optional(Schema.String)
|
|
9
|
+
})),
|
|
10
|
+
hostname: Schema.optional(Schema.String),
|
|
11
|
+
port: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(65535))),
|
|
12
|
+
password: Schema.optional(Schema.String),
|
|
13
|
+
simulation: Schema.optional(Schema.Boolean),
|
|
14
|
+
database: Schema.optional(Database.Options),
|
|
15
|
+
events: Schema.optional(Schema.Struct({
|
|
16
|
+
persist: Schema.optional(Schema.Boolean)
|
|
17
|
+
})),
|
|
18
|
+
models: Schema.optional(ModelsDev.Options),
|
|
19
|
+
config: Schema.optional(Schema.Struct({
|
|
20
|
+
directory: Schema.optional(Schema.String),
|
|
21
|
+
project: Schema.optional(Schema.Boolean),
|
|
22
|
+
file: Schema.optional(Schema.String),
|
|
23
|
+
content: Schema.optional(Schema.String)
|
|
24
|
+
})),
|
|
25
|
+
windows: Schema.optional(Schema.Struct({
|
|
26
|
+
gitbash: Schema.optional(Schema.String)
|
|
27
|
+
})),
|
|
28
|
+
fs: Schema.optional(Schema.Struct({
|
|
29
|
+
filewatcher: Schema.optional(Schema.Boolean),
|
|
30
|
+
fff: Schema.optional(Schema.Boolean)
|
|
31
|
+
}))
|
|
32
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export * as ServerProcess from "./process";
|
|
2
|
+
import { Effect, Scope } from "effect";
|
|
3
|
+
import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
4
|
+
export interface Lifecycle<E = never, R = never> {
|
|
5
|
+
readonly onListen: (address: HttpServer.Address, shutdown: Effect.Effect<void>) => Effect.Effect<Effect.Effect<void>, E, R>;
|
|
6
|
+
}
|
|
7
|
+
type App = Effect.Effect<HttpServerResponse.HttpServerResponse, unknown, HttpServerRequest.HttpServerRequest | Scope.Scope>;
|
|
8
|
+
export type Transform = (app: App) => App;
|
|
9
|
+
export declare const start: <E, R>(options: {
|
|
10
|
+
readonly events?: {
|
|
11
|
+
readonly persist?: boolean | undefined;
|
|
12
|
+
} | undefined;
|
|
13
|
+
readonly models?: {
|
|
14
|
+
readonly url?: string | undefined;
|
|
15
|
+
readonly file?: string | undefined;
|
|
16
|
+
readonly snapshot?: boolean | undefined;
|
|
17
|
+
readonly fetch?: boolean | undefined;
|
|
18
|
+
} | undefined;
|
|
19
|
+
readonly config?: {
|
|
20
|
+
readonly directory?: string | undefined;
|
|
21
|
+
readonly project?: boolean | undefined;
|
|
22
|
+
readonly content?: string | undefined;
|
|
23
|
+
readonly file?: string | undefined;
|
|
24
|
+
} | undefined;
|
|
25
|
+
readonly hostname?: string | undefined;
|
|
26
|
+
readonly windows?: {
|
|
27
|
+
readonly gitbash?: string | undefined;
|
|
28
|
+
} | undefined;
|
|
29
|
+
readonly port?: number | undefined;
|
|
30
|
+
readonly app?: {
|
|
31
|
+
readonly version?: string | undefined;
|
|
32
|
+
readonly name?: string | undefined;
|
|
33
|
+
readonly channel?: string | undefined;
|
|
34
|
+
} | undefined;
|
|
35
|
+
readonly fs?: {
|
|
36
|
+
readonly fff?: boolean | undefined;
|
|
37
|
+
readonly filewatcher?: boolean | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
readonly password?: string | undefined;
|
|
40
|
+
readonly simulation?: boolean | undefined;
|
|
41
|
+
readonly database?: {
|
|
42
|
+
readonly path?: string | undefined;
|
|
43
|
+
} | undefined;
|
|
44
|
+
}, lifecycle?: Lifecycle<E, R> | undefined, transform?: Transform | undefined) => Effect.Effect<{
|
|
45
|
+
address: HttpServer.Address;
|
|
46
|
+
shutdown: Effect.Effect<void, never, never>;
|
|
47
|
+
}, Error | E, Scope.Scope | R>;
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export * as ServerProcess from "./process";
|
|
2
|
+
import { NodeHttpServer } from "@effect/platform-node";
|
|
3
|
+
import { SessionRestart } from "@opencode-ai/core/session/execution/restart";
|
|
4
|
+
import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty";
|
|
5
|
+
import { Cause, Context, Effect, Exit, Latch, Layer, Option, Ref, Scope } from "effect";
|
|
6
|
+
import { HttpMiddleware, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
7
|
+
import { createServer } from "node:http";
|
|
8
|
+
import { ServerAuth } from "./auth";
|
|
9
|
+
import { isAllowedCorsOrigin } from "./cors";
|
|
10
|
+
import { authorizedRequest } from "./middleware/authorization";
|
|
11
|
+
import { withoutParentSpan } from "./request-tracing";
|
|
12
|
+
import { createRoutes } from "./routes";
|
|
13
|
+
import { ServerInfo } from "./server-info";
|
|
14
|
+
import { Status } from "./service-status";
|
|
15
|
+
const errorResponseLogger = HttpMiddleware.make((app) => HttpMiddleware.logger(Effect.tap(app, (response) => response.status < 400 ? HttpMiddleware.withLoggerDisabled(Effect.void) : Effect.void)));
|
|
16
|
+
export const start = Effect.fn("ServerProcess.start")(function* (options, lifecycle, transform) {
|
|
17
|
+
const password = options.password;
|
|
18
|
+
if (!password)
|
|
19
|
+
return yield* Effect.fail(Error("Missing server password"));
|
|
20
|
+
const hostname = options.hostname ?? "127.0.0.1", port = Option.fromNullishOr(options.port), shutdown = yield* Latch.make(), status = yield* Status.make(), bound = yield* listen({ hostname, port }), application = yield* Ref.make(Option.none());
|
|
21
|
+
yield* bound.http.serve(dispatch(password, status, application, options.app?.version ?? "unknown").pipe(HttpMiddleware.cors({ allowedOrigins: isAllowedCorsOrigin, maxAge: 86400 })), errorResponseLogger).pipe(withoutParentSpan);
|
|
22
|
+
if (lifecycle)
|
|
23
|
+
yield* lifecycle.onListen(bound.http.address, shutdown.open.pipe(Effect.asVoid)).pipe(Effect.flatMap((cleanup) => Effect.addFinalizer(() => Scope.close(bound.scope, Exit.void).pipe(Effect.andThen(cleanup)))), Effect.uninterruptible);
|
|
24
|
+
const parentScope = yield* Scope.Scope, applicationScope = yield* Scope.fork(parentScope);
|
|
25
|
+
yield* Effect.addFinalizer(() => status.beginStopping.pipe(Effect.andThen(Ref.set(application, Option.none())), Effect.andThen(Effect.sync(() => bound.server.closeAllConnections()))));
|
|
26
|
+
const boot = Effect.gen(function* () {
|
|
27
|
+
const context = yield* Layer.buildWithScope(createRoutes({
|
|
28
|
+
...options,
|
|
29
|
+
password
|
|
30
|
+
}, () => {
|
|
31
|
+
const address = bound.server.address();
|
|
32
|
+
if (address === null || typeof address === "string")
|
|
33
|
+
return [];
|
|
34
|
+
const host = address.family === "IPv6" ? `[${address.address}]` : address.address;
|
|
35
|
+
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname);
|
|
36
|
+
}).pipe(Layer.provide(NodeHttpServer.layerHttpServices)), applicationScope);
|
|
37
|
+
if (lifecycle)
|
|
38
|
+
yield* installRestartContinuity(Context.get(context, SessionRestart.Service)).pipe(Effect.provideService(Scope.Scope, applicationScope));
|
|
39
|
+
const app = Context.get(context, HttpRouter.HttpRouter).asHttpEffect();
|
|
40
|
+
yield* Ref.set(application, Option.some(transform ? transform(app) : app));
|
|
41
|
+
yield* status.ready;
|
|
42
|
+
return { address: bound.http.address, shutdown: shutdown.await };
|
|
43
|
+
}).pipe(Effect.catchCause((cause) => {
|
|
44
|
+
if (!lifecycle || Cause.hasInterruptsOnly(cause))
|
|
45
|
+
return Effect.failCause(cause);
|
|
46
|
+
return status.fail.pipe(Effect.andThen(Scope.close(applicationScope, Exit.failCause(cause)).pipe(Effect.catchCause((cleanupCause) => Effect.logError("failed to clean up background service boot", { cause: cleanupCause })))), Effect.andThen(Effect.logError("background service boot failed", { cause })), Effect.andThen(Effect.never));
|
|
47
|
+
}));
|
|
48
|
+
if (!lifecycle)
|
|
49
|
+
return yield* boot;
|
|
50
|
+
return yield* Effect.raceFirst(boot, shutdown.await.pipe(Effect.andThen(Effect.interrupt)));
|
|
51
|
+
});
|
|
52
|
+
function listen(options) {
|
|
53
|
+
if (Option.isSome(options.port))
|
|
54
|
+
return bind(options.hostname, options.port.value);
|
|
55
|
+
const next = (port) => bind(options.hostname, port).pipe(Effect.catch((error) => port < 65535 && addressInUse(error) ? next(port + 1) : Effect.fail(error)));
|
|
56
|
+
return next(4096);
|
|
57
|
+
}
|
|
58
|
+
function bind(hostname, port) {
|
|
59
|
+
return Effect.gen(function* () {
|
|
60
|
+
const parentScope = yield* Scope.Scope, serverScope = yield* Scope.fork(parentScope), server = createServer();
|
|
61
|
+
return yield* Effect.gen(function* () {
|
|
62
|
+
const http = yield* NodeHttpServer.make(() => server, { port, host: hostname });
|
|
63
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => server.closeAllConnections()));
|
|
64
|
+
return { http, server, scope: serverScope };
|
|
65
|
+
}).pipe(Effect.provideService(Scope.Scope, serverScope), Effect.onError((cause) => Scope.close(serverScope, Exit.failCause(cause))));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function addressInUse(error) {
|
|
69
|
+
if (typeof error !== "object" || error === null || !("cause" in error))
|
|
70
|
+
return !1;
|
|
71
|
+
const cause = error.cause;
|
|
72
|
+
return typeof cause === "object" && cause !== null && "code" in cause && cause.code === "EADDRINUSE";
|
|
73
|
+
}
|
|
74
|
+
function dispatch(password, status, application, version) {
|
|
75
|
+
const auth = ServerAuth.Config.of({ password: Option.some(password), username: "opencode" });
|
|
76
|
+
return Effect.gen(function* () {
|
|
77
|
+
const request = yield* HttpServerRequest.HttpServerRequest, url = new URL(request.url, "http://localhost");
|
|
78
|
+
if (request.method === "GET" && url.pathname === "/api/health") {
|
|
79
|
+
if (!(yield* authorizedRequest(request, auth)))
|
|
80
|
+
return unauthorized();
|
|
81
|
+
return yield* healthResponse(status, version);
|
|
82
|
+
}
|
|
83
|
+
const state = yield* status.current, app = yield* Ref.get(application), ready = state.type === "ready" && Option.isSome(app);
|
|
84
|
+
if ((!ready || !hasPtyConnectTicketURL(url)) && !(yield* authorizedRequest(request, auth)))
|
|
85
|
+
return unauthorized();
|
|
86
|
+
if (ready)
|
|
87
|
+
return yield* app.value;
|
|
88
|
+
return unavailable(state);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function unauthorized() {
|
|
92
|
+
return HttpServerResponse.empty({
|
|
93
|
+
status: 401,
|
|
94
|
+
headers: { "www-authenticate": 'Basic realm="Secure Area"' }
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const healthResponse = Effect.fnUntraced(function* (status, version) {
|
|
98
|
+
const state = yield* status.current;
|
|
99
|
+
return HttpServerResponse.jsonUnsafe({ healthy: !0, version, pid: process.pid }, {
|
|
100
|
+
status: state.type === "ready" ? 200 : state.type === "failed" ? 500 : 503,
|
|
101
|
+
headers: state.type === "starting" || state.type === "stopping" ? { "retry-after": "1" } : void 0
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
function unavailable(status) {
|
|
105
|
+
if (status.type === "failed")
|
|
106
|
+
return HttpServerResponse.jsonUnsafe({
|
|
107
|
+
code: "service_failed",
|
|
108
|
+
message: "The background service could not start.",
|
|
109
|
+
action: "Run `opencode service restart` after checking the service logs."
|
|
110
|
+
}, { status: 503 });
|
|
111
|
+
return HttpServerResponse.jsonUnsafe({ code: status.type === "stopping" ? "service_stopping" : "service_starting" }, { status: 503, headers: { "retry-after": "1" } });
|
|
112
|
+
}
|
|
113
|
+
const installRestartContinuity = Effect.fnUntraced(function* (restart) {
|
|
114
|
+
yield* Effect.forkScoped(restart.resumeSuspendedSessions);
|
|
115
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * as PtyEnvironment from "./pty-environment";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
export interface Interface {
|
|
4
|
+
readonly get: (input: {
|
|
5
|
+
directory: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
}) => Effect.Effect<Record<string, string>>;
|
|
8
|
+
}
|
|
9
|
+
declare const Service_base: Context.ServiceClass<Service, "@opencode/ServerPtyEnvironment", Interface>;
|
|
10
|
+
export declare class Service extends Service_base {
|
|
11
|
+
}
|
|
12
|
+
export declare const layer: Layer.Layer<Service, never, never>;
|
|
13
|
+
export declare const node: import("@opencode-ai/util/effect/layer-node").Node<Service, never, import("@opencode-ai/util/effect/layer-node").Tag<"global">>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * as PtyEnvironment from "./pty-environment";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node";
|
|
4
|
+
|
|
5
|
+
export class Service extends Context.Service()("@opencode/ServerPtyEnvironment") {
|
|
6
|
+
}
|
|
7
|
+
export const layer = Layer.succeed(Service, Service.of({
|
|
8
|
+
get: () => Effect.succeed({})
|
|
9
|
+
})), node = makeGlobalNode({ service: Service, layer, deps: [] });
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Database } from "@opencode-ai/core/database/database";
|
|
2
|
+
import { LayerNode } from "@opencode-ai/util/effect/layer-node";
|
|
3
|
+
import { Bus } from "@opencode-ai/core/bus";
|
|
4
|
+
import { Credential } from "@opencode-ai/core/credential";
|
|
5
|
+
import { PermissionSaved } from "@opencode-ai/core/permission/saved";
|
|
6
|
+
import { PtyTicket } from "@opencode-ai/core/pty/ticket";
|
|
7
|
+
import { Project } from "@opencode-ai/core/project";
|
|
8
|
+
import { Session } from "@opencode-ai/core/session";
|
|
9
|
+
import { SessionTransfer } from "@opencode-ai/core/session/transfer";
|
|
10
|
+
import { Job } from "@opencode-ai/core/job";
|
|
11
|
+
import { Global } from "@opencode-ai/util/global";
|
|
12
|
+
import { LocationServiceMap } from "@opencode-ai/core/location-service-map";
|
|
13
|
+
import { SessionRestart } from "@opencode-ai/core/session/execution/restart";
|
|
14
|
+
import { SdkPlugins } from "@opencode-ai/core/plugin/sdk";
|
|
15
|
+
import { WellKnown } from "@opencode-ai/core/wellknown";
|
|
16
|
+
import { Workspace } from "@opencode-ai/core/workspace";
|
|
17
|
+
import { Worktree } from "@opencode-ai/core/worktree";
|
|
18
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
19
|
+
import { Layer } from "effect";
|
|
20
|
+
import { PtyEnvironment } from "./pty-environment";
|
|
21
|
+
import type { ServerOptions } from "./options";
|
|
22
|
+
export declare function createRoutes(options?: ServerOptions, serviceURLs?: () => ReadonlyArray<string>, overrides?: LayerNode.Replacements): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | Workspace.Service | WellKnown.Service | Job.Service | LocationServiceMap.Service | SdkPlugins.Service | PermissionSaved.Service | Worktree.Service | Session.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
|
23
|
+
export declare function createEmbeddedRoutes(options?: ServerOptions, overrides?: LayerNode.Replacements): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | Workspace.Service | WellKnown.Service | Job.Service | LocationServiceMap.Service | SdkPlugins.Service | PermissionSaved.Service | Worktree.Service | Session.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Database } from "@opencode-ai/core/database/database";
|
|
2
|
+
import { V1Migration } from "@opencode-ai/core/database/v1-migration";
|
|
3
|
+
import { App } from "@opencode-ai/core/app";
|
|
4
|
+
import { LayerNode } from "@opencode-ai/util/effect/layer-node";
|
|
5
|
+
import { httpClient } from "@opencode-ai/util/effect/app-node-platform";
|
|
6
|
+
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder";
|
|
7
|
+
import { Bus } from "@opencode-ai/core/bus";
|
|
8
|
+
import { EventLogger } from "@opencode-ai/core/event-logger";
|
|
9
|
+
import { FileSystemSearch } from "@opencode-ai/core/filesystem/search";
|
|
10
|
+
import { Credential } from "@opencode-ai/core/credential";
|
|
11
|
+
import { Config } from "@opencode-ai/core/config";
|
|
12
|
+
import { PermissionSaved } from "@opencode-ai/core/permission/saved";
|
|
13
|
+
import { PtyTicket } from "@opencode-ai/core/pty/ticket";
|
|
14
|
+
import { Project } from "@opencode-ai/core/project";
|
|
15
|
+
import { Session } from "@opencode-ai/core/session";
|
|
16
|
+
import { SessionTransfer } from "@opencode-ai/core/session/transfer";
|
|
17
|
+
import { ShellSelect } from "@opencode-ai/core/shell/select";
|
|
18
|
+
import { Job } from "@opencode-ai/core/job";
|
|
19
|
+
import { MCP } from "@opencode-ai/core/mcp/index";
|
|
20
|
+
import { Global } from "@opencode-ai/util/global";
|
|
21
|
+
import { InstructionDiscovery } from "@opencode-ai/core/instruction-discovery";
|
|
22
|
+
import { LocationServiceMap } from "@opencode-ai/core/location-service-map";
|
|
23
|
+
import { ModelsDev } from "@opencode-ai/core/models-dev";
|
|
24
|
+
import { SessionRestart } from "@opencode-ai/core/session/execution/restart";
|
|
25
|
+
import { PluginRuntime } from "@opencode-ai/core/plugin/runtime";
|
|
26
|
+
import { SdkPlugins } from "@opencode-ai/core/plugin/sdk";
|
|
27
|
+
import { WellKnown } from "@opencode-ai/core/wellknown";
|
|
28
|
+
import { Workspace } from "@opencode-ai/core/workspace";
|
|
29
|
+
import { Worktree } from "@opencode-ai/core/worktree";
|
|
30
|
+
import { Watcher } from "@opencode-ai/core/filesystem/watcher";
|
|
31
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
32
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
33
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
34
|
+
import { Api } from "./api";
|
|
35
|
+
import { ServerAuth } from "./auth";
|
|
36
|
+
import { handlers } from "./handlers";
|
|
37
|
+
import { authorizationLayer } from "./middleware/authorization";
|
|
38
|
+
import { schemaErrorLayer } from "./middleware/schema-error";
|
|
39
|
+
import { PtyEnvironment } from "./pty-environment";
|
|
40
|
+
import { layer } from "./location";
|
|
41
|
+
import { formLocationLayer } from "./middleware/form-location";
|
|
42
|
+
import { sessionLocationLayer } from "./middleware/session-location";
|
|
43
|
+
import { ServerInfo } from "./server-info";
|
|
44
|
+
const applicationServiceNodes = [
|
|
45
|
+
Global.node,
|
|
46
|
+
Database.node,
|
|
47
|
+
Bus.node,
|
|
48
|
+
EventLogger.node,
|
|
49
|
+
httpClient,
|
|
50
|
+
Job.node,
|
|
51
|
+
Project.node,
|
|
52
|
+
Worktree.node,
|
|
53
|
+
Session.node,
|
|
54
|
+
SessionTransfer.node,
|
|
55
|
+
PluginRuntime.providerNode,
|
|
56
|
+
SdkPlugins.node,
|
|
57
|
+
PermissionSaved.node,
|
|
58
|
+
PtyTicket.node,
|
|
59
|
+
Credential.node,
|
|
60
|
+
WellKnown.node,
|
|
61
|
+
PtyEnvironment.node,
|
|
62
|
+
LocationServiceMap.node,
|
|
63
|
+
SessionRestart.node
|
|
64
|
+
], applicationServices = LayerNode.group(applicationServiceNodes), embeddedApplicationServices = LayerNode.group([...applicationServiceNodes, Workspace.node]);
|
|
65
|
+
export function createRoutes(options = {}, serviceURLs = () => [], overrides = []) {
|
|
66
|
+
return makeRoutes(options.password ? ServerAuth.Config.configLayer({ password: Option.some(options.password) }) : ServerAuth.Config.layer, options, serviceURLs, overrides, !1);
|
|
67
|
+
}
|
|
68
|
+
export function createEmbeddedRoutes(options = {}, overrides = []) {
|
|
69
|
+
return makeRoutes(ServerAuth.Config.configLayer({ password: Option.none() }), options, () => [], overrides, !0);
|
|
70
|
+
}
|
|
71
|
+
function makeRoutes(auth, options, serviceURLs, overrides, embedded) {
|
|
72
|
+
const pluginRuntimeCell = PluginRuntime.makeCell(), replacements = [...[
|
|
73
|
+
[Database.node, Database.configured(options.database)],
|
|
74
|
+
[Bus.node, Bus.configured({ persist: options.events?.persist })],
|
|
75
|
+
[App.node, App.configured(options.app)],
|
|
76
|
+
[ModelsDev.node, ModelsDev.configured(options.models)],
|
|
77
|
+
[Watcher.node, Watcher.configured({ enabled: options.fs?.filewatcher })],
|
|
78
|
+
[FileSystemSearch.node, FileSystemSearch.configured({ fff: options.fs?.fff })],
|
|
79
|
+
[Global.node, Global.layerWith(options.config?.directory ? { config: options.config.directory } : {})],
|
|
80
|
+
[
|
|
81
|
+
Config.node,
|
|
82
|
+
Config.configured({
|
|
83
|
+
project: options.config?.project,
|
|
84
|
+
file: options.config?.file,
|
|
85
|
+
content: options.config?.content
|
|
86
|
+
})
|
|
87
|
+
],
|
|
88
|
+
[InstructionDiscovery.node, InstructionDiscovery.configured({ project: options.config?.project })],
|
|
89
|
+
[ShellSelect.node, ShellSelect.configured({ gitbash: options.windows?.gitbash })],
|
|
90
|
+
[
|
|
91
|
+
MCP.node,
|
|
92
|
+
MCP.configured({
|
|
93
|
+
clientInfo: {
|
|
94
|
+
name: options.app?.name ?? "opencode",
|
|
95
|
+
version: options.app?.version ?? "unknown"
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
],
|
|
99
|
+
[PluginRuntime.node, PluginRuntime.layerWithCell(pluginRuntimeCell)],
|
|
100
|
+
[PluginRuntime.providerNode, PluginRuntime.providerNodeWithCell(pluginRuntimeCell)]
|
|
101
|
+
], ...overrides];
|
|
102
|
+
return (options.simulation ? Layer.unwrap(Effect.gen(function* () {
|
|
103
|
+
const { simulationReplacements } = yield* Effect.promise(() => import("@opencode-ai/simulation/backend")), simulation = yield* simulationReplacements({ version: App.make(options.app).version });
|
|
104
|
+
return AppNodeBuilder.build(embedded ? embeddedApplicationServices : applicationServices, [
|
|
105
|
+
...replacements,
|
|
106
|
+
...simulation
|
|
107
|
+
]);
|
|
108
|
+
})) : AppNodeBuilder.build(embedded ? embeddedApplicationServices : applicationServices, replacements)).pipe(Layer.flatMap((context) => {
|
|
109
|
+
const services = Layer.succeedContext(context), requestServices = Layer.merge(Layer.succeedContext(Context.pick(Database.Service, PermissionSaved.Service, Project.Service, WellKnown.Service)(context)), ServerInfo.layer(serviceURLs, options.app)), api = HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(Layer.provide(handlers.pipe(Layer.provide(services))), Layer.provide(formLocationLayer), Layer.provide(sessionLocationLayer), Layer.provide(layer), Layer.provide(authorizationLayer), Layer.provide(schemaErrorLayer), Layer.provide(auth), HttpRouter.provideRequest(requestServices), Layer.provideMerge(services), Layer.provideMerge(HttpRouter.layer));
|
|
110
|
+
return Layer.merge(api, V1Migration.layer.pipe(Layer.provide(services)));
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Context, Layer } from "effect";
|
|
2
|
+
import type { ServerOptions } from "./options";
|
|
3
|
+
declare const Service_base: Context.ServiceClass<Service, "@opencode-ai/server/ServerInfo", {
|
|
4
|
+
readonly urls: () => ReadonlyArray<string>;
|
|
5
|
+
readonly app: NonNullable<ServerOptions["app"]>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare class Service extends Service_base {
|
|
8
|
+
}
|
|
9
|
+
export declare function layer(urls: () => ReadonlyArray<string>, app?: ServerOptions["app"]): Layer.Layer<Service, never, never>;
|
|
10
|
+
export declare function connectionURLs(value: string, requestedHostname?: string): string[];
|
|
11
|
+
export * as ServerInfo from "./server-info";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Context, Layer } from "effect";
|
|
2
|
+
import { networkInterfaces } from "node:os";
|
|
3
|
+
|
|
4
|
+
export class Service extends Context.Service()("@opencode-ai/server/ServerInfo") {
|
|
5
|
+
}
|
|
6
|
+
export function layer(urls, app = {}) {
|
|
7
|
+
return Layer.succeed(Service, Service.of({ urls, app }));
|
|
8
|
+
}
|
|
9
|
+
export function connectionURLs(value, requestedHostname) {
|
|
10
|
+
const url = new URL(value), hostname = requestedHostname ?? url.hostname, family = hostname === "0.0.0.0" ? "IPv4" : hostname === "::" || hostname === "[::]" ? "IPv6" : void 0;
|
|
11
|
+
if (family === void 0)
|
|
12
|
+
return [value];
|
|
13
|
+
return [
|
|
14
|
+
...new Set(Object.values(networkInterfaces()).flatMap((entries) => entries ?? []).filter((entry) => !entry.internal && entry.family === family).map((entry) => {
|
|
15
|
+
const result = new URL(value);
|
|
16
|
+
result.hostname = family === "IPv6" ? `[${entry.address}]` : entry.address;
|
|
17
|
+
return result.toString().replace(/\/$/, "");
|
|
18
|
+
}))
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export * as ServerInfo from "./server-info";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export * as Status from "./service-status";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
export type State = {
|
|
4
|
+
readonly type: "starting";
|
|
5
|
+
} | {
|
|
6
|
+
readonly type: "ready";
|
|
7
|
+
} | {
|
|
8
|
+
readonly type: "stopping";
|
|
9
|
+
} | {
|
|
10
|
+
readonly type: "failed";
|
|
11
|
+
};
|
|
12
|
+
export interface Interface {
|
|
13
|
+
readonly current: Effect.Effect<State>;
|
|
14
|
+
readonly ready: Effect.Effect<void>;
|
|
15
|
+
readonly fail: Effect.Effect<void>;
|
|
16
|
+
readonly beginStopping: Effect.Effect<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare const make: (options?: {
|
|
19
|
+
readonly initial?: State;
|
|
20
|
+
} | undefined) => Effect.Effect<{
|
|
21
|
+
current: Effect.Effect<State, never, never>;
|
|
22
|
+
ready: Effect.Effect<void, never, never>;
|
|
23
|
+
fail: Effect.Effect<void, never, never>;
|
|
24
|
+
beginStopping: Effect.Effect<void, never, never>;
|
|
25
|
+
}, never, never>;
|