@opencode/server 0.0.0-reserved → 2.0.1
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 +8394 -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 +43 -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 +60 -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 +289 -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 +39 -6
- package/README.md +0 -5
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ServerAuth } from "../auth";
|
|
2
|
+
import { UnauthorizedError } from "@opencode/protocol/errors";
|
|
3
|
+
import { Authorization } from "@opencode/protocol/middleware/authorization";
|
|
4
|
+
export { Authorization } from "@opencode/protocol/middleware/authorization";
|
|
5
|
+
import { hasPtyConnectTicketURL } from "@opencode/protocol/groups/pty";
|
|
6
|
+
import { hasPersistentPtyConnectTicketURL } from "@opencode/protocol/groups/persistent-pty";
|
|
7
|
+
import { Effect, Encoding, Layer, Redacted } from "effect";
|
|
8
|
+
import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
9
|
+
const AUTH_TOKEN_QUERY = "auth_token", WWW_AUTHENTICATE = 'Basic realm="Secure Area"';
|
|
10
|
+
function emptyCredential() {
|
|
11
|
+
return { username: "", password: Redacted.make("") };
|
|
12
|
+
}
|
|
13
|
+
function decodeCredential(input) {
|
|
14
|
+
return Effect.fromResult(Encoding.decodeBase64String(input)).pipe(Effect.match({
|
|
15
|
+
onFailure: emptyCredential,
|
|
16
|
+
onSuccess: (header) => {
|
|
17
|
+
const separator = header.indexOf(":");
|
|
18
|
+
if (separator === -1)
|
|
19
|
+
return emptyCredential();
|
|
20
|
+
return { username: header.slice(0, separator), password: Redacted.make(header.slice(separator + 1)) };
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
function credentialFromRequest(request) {
|
|
25
|
+
const token = new URL(request.url, "http://localhost").searchParams.get(AUTH_TOKEN_QUERY);
|
|
26
|
+
if (token)
|
|
27
|
+
return decodeCredential(token);
|
|
28
|
+
const match = /^Basic\s+(.+)$/i.exec(request.headers.authorization ?? "");
|
|
29
|
+
if (match)
|
|
30
|
+
return decodeCredential(match[1]);
|
|
31
|
+
return Effect.succeed(emptyCredential());
|
|
32
|
+
}
|
|
33
|
+
export function authorizedRequest(request, config) {
|
|
34
|
+
return credentialFromRequest(request).pipe(Effect.map((credential) => ServerAuth.authorized(credential, config)));
|
|
35
|
+
}
|
|
36
|
+
export const authorizationLayer = Layer.effect(Authorization, Effect.gen(function* () {
|
|
37
|
+
const config = yield* ServerAuth.Config;
|
|
38
|
+
if (!ServerAuth.required(config))
|
|
39
|
+
return Authorization.of((effect) => effect);
|
|
40
|
+
return Authorization.of((effect) => Effect.gen(function* () {
|
|
41
|
+
const request = yield* HttpServerRequest.HttpServerRequest, url = new URL(request.url, "http://localhost");
|
|
42
|
+
if (hasPtyConnectTicketURL(url) || hasPersistentPtyConnectTicketURL(url))
|
|
43
|
+
return yield* effect;
|
|
44
|
+
if (yield* authorizedRequest(request, config))
|
|
45
|
+
return yield* effect;
|
|
46
|
+
yield* HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)));
|
|
47
|
+
return yield* new UnauthorizedError({ message: "Authentication required" });
|
|
48
|
+
}));
|
|
49
|
+
}));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
2
|
+
import { LocationServiceMap } from "@opencode/core/location-services";
|
|
3
|
+
import { Session } from "@opencode/core/session";
|
|
4
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode/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: Instance.Services;
|
|
10
|
+
error: readonly [typeof InvalidRequestError, typeof SessionNotFoundError];
|
|
11
|
+
clientError: never;
|
|
12
|
+
requiredForClient: false;
|
|
13
|
+
security: never;
|
|
14
|
+
}, HttpApiMiddleware.HttpApiMiddleware<Instance.Services, readonly [typeof InvalidRequestError, typeof SessionNotFoundError], never>>;
|
|
15
|
+
export declare class FormLocationMiddleware extends FormLocationMiddleware_base {
|
|
16
|
+
}
|
|
17
|
+
export declare const formLocationLayer: Layer.Layer<FormLocationMiddleware, never, LocationServiceMap.Service | Session.Service | Instance.Service>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
2
|
+
import { LocationServiceMap } from "@opencode/core/location-services";
|
|
3
|
+
import { Session } from "@opencode/core/session";
|
|
4
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors";
|
|
5
|
+
import { Effect, Layer } from "effect";
|
|
6
|
+
import { HttpRouter, HttpServerRequest } from "effect/unstable/http";
|
|
7
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
8
|
+
import { requestRef, sessionInfo } from "../location";
|
|
9
|
+
|
|
10
|
+
export class FormLocationMiddleware extends HttpApiMiddleware.Service()("@opencode/HttpApiFormLocation", {
|
|
11
|
+
error: [InvalidRequestError, SessionNotFoundError]
|
|
12
|
+
}) {
|
|
13
|
+
}
|
|
14
|
+
export const formLocationLayer = Layer.effect(FormLocationMiddleware, Effect.gen(function* () {
|
|
15
|
+
const sessions = yield* Session.Service, instances = yield* Instance.Service, locations = yield* LocationServiceMap.Service;
|
|
16
|
+
return FormLocationMiddleware.of((effect) => Effect.gen(function* () {
|
|
17
|
+
const route = yield* HttpRouter.RouteContext;
|
|
18
|
+
if (route.params.sessionID === "global") {
|
|
19
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
20
|
+
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))));
|
|
21
|
+
}
|
|
22
|
+
const session = yield* sessionInfo(sessions, route.params.sessionID);
|
|
23
|
+
return yield* effect.pipe(instances.provide(session));
|
|
24
|
+
}));
|
|
25
|
+
}));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { SchemaErrorMiddleware } from "@opencode/protocol/middleware/schema-error";
|
|
2
|
+
export { SchemaErrorMiddleware } from "@opencode/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/protocol/errors";
|
|
4
|
+
import { SchemaErrorMiddleware } from "@opencode/protocol/middleware/schema-error";
|
|
5
|
+
export { SchemaErrorMiddleware } from "@opencode/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,17 @@
|
|
|
1
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
2
|
+
import { Session } from "@opencode/core/session";
|
|
3
|
+
import { Layer } from "effect";
|
|
4
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
5
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors";
|
|
6
|
+
declare const SessionLocationMiddleware_base: HttpApiMiddleware.ServiceClass<SessionLocationMiddleware, "@opencode/HttpApiSessionLocation", {
|
|
7
|
+
requires: never;
|
|
8
|
+
provides: Instance.Services;
|
|
9
|
+
error: readonly [typeof InvalidRequestError, typeof SessionNotFoundError];
|
|
10
|
+
clientError: never;
|
|
11
|
+
requiredForClient: false;
|
|
12
|
+
security: never;
|
|
13
|
+
}, HttpApiMiddleware.HttpApiMiddleware<Instance.Services, readonly [typeof InvalidRequestError, typeof SessionNotFoundError], never>>;
|
|
14
|
+
export declare class SessionLocationMiddleware extends SessionLocationMiddleware_base {
|
|
15
|
+
}
|
|
16
|
+
export declare const sessionLocationLayer: Layer.Layer<SessionLocationMiddleware, never, Session.Service | Instance.Service>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
2
|
+
import { Session } from "@opencode/core/session";
|
|
3
|
+
import { Effect, Layer } from "effect";
|
|
4
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
5
|
+
import { HttpApiMiddleware } from "effect/unstable/httpapi";
|
|
6
|
+
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors";
|
|
7
|
+
import { sessionInfo } from "../location";
|
|
8
|
+
|
|
9
|
+
export class SessionLocationMiddleware extends HttpApiMiddleware.Service()("@opencode/HttpApiSessionLocation", {
|
|
10
|
+
error: [InvalidRequestError, SessionNotFoundError]
|
|
11
|
+
}) {
|
|
12
|
+
}
|
|
13
|
+
export const sessionLocationLayer = Layer.effect(SessionLocationMiddleware, Effect.gen(function* () {
|
|
14
|
+
const sessions = yield* Session.Service, instances = yield* Instance.Service;
|
|
15
|
+
return SessionLocationMiddleware.of((effect) => Effect.gen(function* () {
|
|
16
|
+
const route = yield* HttpRouter.RouteContext, session = yield* sessionInfo(sessions, route.params.sessionID);
|
|
17
|
+
return yield* effect.pipe(instances.provide(session));
|
|
18
|
+
}));
|
|
19
|
+
}));
|
|
@@ -0,0 +1,47 @@
|
|
|
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 cors: Schema.optional<Schema.$Array<Schema.String>>;
|
|
12
|
+
readonly simulation: Schema.optional<Schema.Boolean>;
|
|
13
|
+
readonly database: Schema.optional<Schema.Struct<{
|
|
14
|
+
readonly path: Schema.optional<Schema.String>;
|
|
15
|
+
}>>;
|
|
16
|
+
readonly pty: Schema.optional<Schema.Struct<{
|
|
17
|
+
readonly handoff: Schema.optional<Schema.Struct<{
|
|
18
|
+
readonly directory: Schema.String;
|
|
19
|
+
readonly instanceID: Schema.String;
|
|
20
|
+
readonly ticket: Schema.String;
|
|
21
|
+
readonly expiresAt: Schema.Number;
|
|
22
|
+
}>>;
|
|
23
|
+
}>>;
|
|
24
|
+
readonly events: Schema.optional<Schema.Struct<{
|
|
25
|
+
readonly persist: Schema.optional<Schema.Boolean>;
|
|
26
|
+
}>>;
|
|
27
|
+
readonly models: Schema.optional<Schema.Struct<{
|
|
28
|
+
readonly url: Schema.optional<Schema.String>;
|
|
29
|
+
readonly file: Schema.optional<Schema.String>;
|
|
30
|
+
readonly fetch: Schema.optional<Schema.Boolean>;
|
|
31
|
+
readonly snapshot: Schema.optional<Schema.Boolean>;
|
|
32
|
+
}>>;
|
|
33
|
+
readonly config: Schema.optional<Schema.Struct<{
|
|
34
|
+
readonly directory: Schema.optional<Schema.String>;
|
|
35
|
+
readonly project: Schema.optional<Schema.Boolean>;
|
|
36
|
+
readonly file: Schema.optional<Schema.String>;
|
|
37
|
+
readonly content: Schema.optional<Schema.String>;
|
|
38
|
+
}>>;
|
|
39
|
+
readonly windows: Schema.optional<Schema.Struct<{
|
|
40
|
+
readonly gitbash: Schema.optional<Schema.String>;
|
|
41
|
+
}>>;
|
|
42
|
+
readonly fs: Schema.optional<Schema.Struct<{
|
|
43
|
+
readonly filewatcher: Schema.optional<Schema.Boolean>;
|
|
44
|
+
readonly fff: Schema.optional<Schema.Boolean>;
|
|
45
|
+
}>>;
|
|
46
|
+
}>;
|
|
47
|
+
export type ServerOptions = typeof ServerOptions.Type;
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Database } from "@opencode/core/database/database";
|
|
2
|
+
import { ModelsDev } from "@opencode/core/models-dev";
|
|
3
|
+
import { PersistentPty } from "@opencode/core/persistent-pty";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
export const ServerOptions = Schema.Struct({
|
|
6
|
+
app: Schema.optional(Schema.Struct({
|
|
7
|
+
name: Schema.optional(Schema.String),
|
|
8
|
+
version: Schema.optional(Schema.String),
|
|
9
|
+
channel: Schema.optional(Schema.String)
|
|
10
|
+
})),
|
|
11
|
+
hostname: Schema.optional(Schema.String),
|
|
12
|
+
port: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(65535))),
|
|
13
|
+
password: Schema.optional(Schema.String),
|
|
14
|
+
cors: Schema.optional(Schema.Array(Schema.String)),
|
|
15
|
+
simulation: Schema.optional(Schema.Boolean),
|
|
16
|
+
database: Schema.optional(Database.Options),
|
|
17
|
+
pty: Schema.optional(PersistentPty.Options),
|
|
18
|
+
events: Schema.optional(Schema.Struct({
|
|
19
|
+
persist: Schema.optional(Schema.Boolean)
|
|
20
|
+
})),
|
|
21
|
+
models: Schema.optional(ModelsDev.Options),
|
|
22
|
+
config: Schema.optional(Schema.Struct({
|
|
23
|
+
directory: Schema.optional(Schema.String),
|
|
24
|
+
project: Schema.optional(Schema.Boolean),
|
|
25
|
+
file: Schema.optional(Schema.String),
|
|
26
|
+
content: Schema.optional(Schema.String)
|
|
27
|
+
})),
|
|
28
|
+
windows: Schema.optional(Schema.Struct({
|
|
29
|
+
gitbash: Schema.optional(Schema.String)
|
|
30
|
+
})),
|
|
31
|
+
fs: Schema.optional(Schema.Struct({
|
|
32
|
+
filewatcher: Schema.optional(Schema.Boolean),
|
|
33
|
+
fff: Schema.optional(Schema.Boolean)
|
|
34
|
+
}))
|
|
35
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
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 models?: {
|
|
11
|
+
readonly url?: string | undefined;
|
|
12
|
+
readonly file?: string | undefined;
|
|
13
|
+
readonly snapshot?: boolean | undefined;
|
|
14
|
+
readonly fetch?: boolean | undefined;
|
|
15
|
+
} | undefined;
|
|
16
|
+
readonly events?: {
|
|
17
|
+
readonly persist?: boolean | undefined;
|
|
18
|
+
} | undefined;
|
|
19
|
+
readonly pty?: {
|
|
20
|
+
readonly handoff?: {
|
|
21
|
+
readonly directory: string;
|
|
22
|
+
readonly instanceID: string;
|
|
23
|
+
readonly ticket: string;
|
|
24
|
+
readonly expiresAt: number;
|
|
25
|
+
} | undefined;
|
|
26
|
+
} | undefined;
|
|
27
|
+
readonly config?: {
|
|
28
|
+
readonly directory?: string | undefined;
|
|
29
|
+
readonly project?: boolean | undefined;
|
|
30
|
+
readonly content?: string | undefined;
|
|
31
|
+
readonly file?: string | undefined;
|
|
32
|
+
} | undefined;
|
|
33
|
+
readonly hostname?: string | undefined;
|
|
34
|
+
readonly cors?: readonly string[] | undefined;
|
|
35
|
+
readonly windows?: {
|
|
36
|
+
readonly gitbash?: string | undefined;
|
|
37
|
+
} | undefined;
|
|
38
|
+
readonly port?: number | undefined;
|
|
39
|
+
readonly password?: string | undefined;
|
|
40
|
+
readonly app?: {
|
|
41
|
+
readonly version?: string | undefined;
|
|
42
|
+
readonly name?: string | undefined;
|
|
43
|
+
readonly channel?: string | undefined;
|
|
44
|
+
} | undefined;
|
|
45
|
+
readonly fs?: {
|
|
46
|
+
readonly fff?: boolean | undefined;
|
|
47
|
+
readonly filewatcher?: boolean | undefined;
|
|
48
|
+
} | undefined;
|
|
49
|
+
readonly simulation?: boolean | undefined;
|
|
50
|
+
readonly database?: {
|
|
51
|
+
readonly path?: string | undefined;
|
|
52
|
+
} | undefined;
|
|
53
|
+
}, lifecycle?: Lifecycle<E, R> | undefined, transform?: Transform | undefined) => Effect.Effect<{
|
|
54
|
+
address: HttpServer.Address;
|
|
55
|
+
shutdown: Effect.Effect<void, never, never>;
|
|
56
|
+
updateAvailable: (version: string) => Effect.Effect<void, never, never>;
|
|
57
|
+
updated: (version: string) => Effect.Effect<void, never, never>;
|
|
58
|
+
}, Error | E, Scope.Scope | R>;
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export * as ServerProcess from "./process";
|
|
2
|
+
import { NodeHttpServer } from "@effect/platform-node";
|
|
3
|
+
import { Bus } from "@opencode/core/bus";
|
|
4
|
+
import { SessionRestart } from "@opencode/core/session/execution/restart";
|
|
5
|
+
import { InstallationEvent } from "@opencode/schema/installation-event";
|
|
6
|
+
import { hasPtyConnectTicketURL } from "@opencode/protocol/groups/pty";
|
|
7
|
+
import { hasPersistentPtyConnectTicketURL } from "@opencode/protocol/groups/persistent-pty";
|
|
8
|
+
import { Cause, Context, Effect, Exit, Latch, Layer, Option, Ref, Scope } from "effect";
|
|
9
|
+
import {
|
|
10
|
+
HttpMiddleware,
|
|
11
|
+
HttpPlatform,
|
|
12
|
+
HttpRouter,
|
|
13
|
+
HttpServer,
|
|
14
|
+
HttpServerRequest,
|
|
15
|
+
HttpServerResponse
|
|
16
|
+
} from "effect/unstable/http";
|
|
17
|
+
import { createServer } from "node:http";
|
|
18
|
+
import { ServerAuth } from "./auth";
|
|
19
|
+
import { isAllowedCorsOrigin } from "./cors";
|
|
20
|
+
import { authorizedRequest } from "./middleware/authorization";
|
|
21
|
+
import { withoutParentSpan } from "./request-tracing";
|
|
22
|
+
import { createRoutes } from "./routes";
|
|
23
|
+
import { ServerInfo } from "./server-info";
|
|
24
|
+
import { Status } from "./service-status";
|
|
25
|
+
const errorResponseLogger = HttpMiddleware.make((app) => HttpMiddleware.logger(Effect.tap(app, (response) => response.status < 400 ? HttpMiddleware.withLoggerDisabled(Effect.void) : Effect.void)));
|
|
26
|
+
export const start = Effect.fn("ServerProcess.start")(function* (options, lifecycle, transform) {
|
|
27
|
+
const password = options.password;
|
|
28
|
+
if (!password)
|
|
29
|
+
return yield* Effect.fail(Error("Missing server password"));
|
|
30
|
+
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());
|
|
31
|
+
yield* bound.http.serve(dispatch(password, status, application, options.app?.version ?? "unknown").pipe(HttpMiddleware.cors({ allowedOrigins: (origin) => isAllowedCorsOrigin(origin, options), maxAge: 86400 })), errorResponseLogger).pipe(withoutParentSpan);
|
|
32
|
+
if (lifecycle)
|
|
33
|
+
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);
|
|
34
|
+
const parentScope = yield* Scope.Scope, applicationScope = yield* Scope.fork(parentScope);
|
|
35
|
+
yield* Effect.addFinalizer(() => status.beginStopping.pipe(Effect.andThen(Ref.set(application, Option.none())), Effect.andThen(Effect.sync(() => bound.server.closeAllConnections()))));
|
|
36
|
+
const boot = Effect.gen(function* () {
|
|
37
|
+
const context = yield* Layer.buildWithScope(createRoutes({
|
|
38
|
+
...options,
|
|
39
|
+
password
|
|
40
|
+
}, () => {
|
|
41
|
+
const address = bound.server.address();
|
|
42
|
+
if (address === null || typeof address === "string")
|
|
43
|
+
return [];
|
|
44
|
+
const host = address.family === "IPv6" ? `[${address.address}]` : address.address;
|
|
45
|
+
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname);
|
|
46
|
+
}).pipe(Layer.provideMerge(NodeHttpServer.layerHttpServices)), applicationScope);
|
|
47
|
+
if (lifecycle)
|
|
48
|
+
yield* installRestartContinuity(Context.get(context, SessionRestart.Service)).pipe(Effect.provideService(Scope.Scope, applicationScope));
|
|
49
|
+
const app = Context.get(context, HttpRouter.HttpRouter).asHttpEffect().pipe(HttpMiddleware.compression(), Effect.provideService(HttpPlatform.HttpPlatform, Context.get(context, HttpPlatform.HttpPlatform)));
|
|
50
|
+
yield* Ref.set(application, Option.some(transform ? transform(app) : app));
|
|
51
|
+
yield* status.ready;
|
|
52
|
+
const bus = Context.get(context, Bus.Service);
|
|
53
|
+
return {
|
|
54
|
+
address: bound.http.address,
|
|
55
|
+
shutdown: shutdown.await,
|
|
56
|
+
updateAvailable: (version) => bus.publish(InstallationEvent.UpdateAvailable, { version }).pipe(Effect.asVoid),
|
|
57
|
+
updated: (version) => bus.publish(InstallationEvent.Updated, { version }).pipe(Effect.asVoid)
|
|
58
|
+
};
|
|
59
|
+
}).pipe(Effect.catchCause((cause) => {
|
|
60
|
+
if (!lifecycle || Cause.hasInterruptsOnly(cause))
|
|
61
|
+
return Effect.failCause(cause);
|
|
62
|
+
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));
|
|
63
|
+
}));
|
|
64
|
+
if (!lifecycle)
|
|
65
|
+
return yield* boot;
|
|
66
|
+
return yield* Effect.raceFirst(boot, shutdown.await.pipe(Effect.andThen(Effect.interrupt)));
|
|
67
|
+
});
|
|
68
|
+
function listen(options) {
|
|
69
|
+
if (Option.isSome(options.port))
|
|
70
|
+
return bind(options.hostname, options.port.value);
|
|
71
|
+
const next = (port) => bind(options.hostname, port).pipe(Effect.catch((error) => port < 65535 && addressInUse(error) ? next(port + 1) : Effect.fail(error)));
|
|
72
|
+
return next(4096);
|
|
73
|
+
}
|
|
74
|
+
function bind(hostname, port) {
|
|
75
|
+
return Effect.gen(function* () {
|
|
76
|
+
const parentScope = yield* Scope.Scope, serverScope = yield* Scope.fork(parentScope), server = createServer();
|
|
77
|
+
return yield* Effect.gen(function* () {
|
|
78
|
+
const http = yield* NodeHttpServer.make(() => server, { port, host: hostname });
|
|
79
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => server.closeAllConnections()));
|
|
80
|
+
return { http, server, scope: serverScope };
|
|
81
|
+
}).pipe(Effect.provideService(Scope.Scope, serverScope), Effect.onError((cause) => Scope.close(serverScope, Exit.failCause(cause))));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function addressInUse(error) {
|
|
85
|
+
if (typeof error !== "object" || error === null || !("cause" in error))
|
|
86
|
+
return !1;
|
|
87
|
+
const cause = error.cause;
|
|
88
|
+
return typeof cause === "object" && cause !== null && "code" in cause && cause.code === "EADDRINUSE";
|
|
89
|
+
}
|
|
90
|
+
function dispatch(password, status, application, version) {
|
|
91
|
+
const auth = ServerAuth.Config.of({ password: Option.some(password), username: "opencode" });
|
|
92
|
+
return Effect.gen(function* () {
|
|
93
|
+
const request = yield* HttpServerRequest.HttpServerRequest, url = new URL(request.url, "http://localhost");
|
|
94
|
+
if (request.method === "GET" && url.pathname === "/api/health") {
|
|
95
|
+
if (!(yield* authorizedRequest(request, auth)))
|
|
96
|
+
return unauthorized();
|
|
97
|
+
return yield* healthResponse(status, version);
|
|
98
|
+
}
|
|
99
|
+
const state = yield* status.current, app = yield* Ref.get(application), ready = state.type === "ready" && Option.isSome(app);
|
|
100
|
+
if ((!ready || !hasPtyConnectTicketURL(url) && !hasPersistentPtyConnectTicketURL(url)) && !(yield* authorizedRequest(request, auth)))
|
|
101
|
+
return unauthorized();
|
|
102
|
+
if (ready)
|
|
103
|
+
return yield* app.value;
|
|
104
|
+
return unavailable(state);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function unauthorized() {
|
|
108
|
+
return HttpServerResponse.empty({
|
|
109
|
+
status: 401,
|
|
110
|
+
headers: { "www-authenticate": 'Basic realm="Secure Area"' }
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const healthResponse = Effect.fnUntraced(function* (status, version) {
|
|
114
|
+
const state = yield* status.current;
|
|
115
|
+
return HttpServerResponse.jsonUnsafe({ healthy: !0, version, pid: process.pid }, {
|
|
116
|
+
status: state.type === "ready" ? 200 : state.type === "failed" ? 500 : 503,
|
|
117
|
+
headers: state.type === "starting" || state.type === "stopping" ? { "retry-after": "1" } : void 0
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
function unavailable(status) {
|
|
121
|
+
if (status.type === "failed")
|
|
122
|
+
return HttpServerResponse.jsonUnsafe({
|
|
123
|
+
code: "service_failed",
|
|
124
|
+
message: "The background service could not start.",
|
|
125
|
+
action: "Run `opencode service restart` after checking the service logs."
|
|
126
|
+
}, { status: 503 });
|
|
127
|
+
return HttpServerResponse.jsonUnsafe({ code: status.type === "stopping" ? "service_stopping" : "service_starting" }, { status: 503, headers: { "retry-after": "1" } });
|
|
128
|
+
}
|
|
129
|
+
const installRestartContinuity = Effect.fnUntraced(function* (restart) {
|
|
130
|
+
yield* Effect.forkScoped(restart.resumeSuspendedSessions);
|
|
131
|
+
});
|
|
@@ -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/util/effect/layer-node").Provider<Service, never, import("@opencode/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/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,29 @@
|
|
|
1
|
+
import { Database } from "@opencode/core/database/database";
|
|
2
|
+
import { LayerNode } from "@opencode/util/effect/layer-node";
|
|
3
|
+
import { Node } from "@opencode/util/effect/app-node";
|
|
4
|
+
import { Bus } from "@opencode/core/bus";
|
|
5
|
+
import { Credential } from "@opencode/core/credential";
|
|
6
|
+
import { PermissionSaved } from "@opencode/core/permission/saved";
|
|
7
|
+
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
8
|
+
import { PersistentPty } from "@opencode/core/persistent-pty";
|
|
9
|
+
import { Project } from "@opencode/core/project";
|
|
10
|
+
import { Session } from "@opencode/core/session";
|
|
11
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
12
|
+
import { SessionTransfer } from "@opencode/core/session/transfer";
|
|
13
|
+
import { Job } from "@opencode/core/job";
|
|
14
|
+
import { Global } from "@opencode/util/global";
|
|
15
|
+
import { LocationServiceMap } from "@opencode/core/location-service-map";
|
|
16
|
+
import { LocationActivity } from "@opencode/core/location-activity";
|
|
17
|
+
import { SessionRestart } from "@opencode/core/session/execution/restart";
|
|
18
|
+
import { PluginUpdate } from "@opencode/core/plugin/update";
|
|
19
|
+
import { SdkPlugins } from "@opencode/core/plugin/sdk";
|
|
20
|
+
import { WellKnown } from "@opencode/core/wellknown";
|
|
21
|
+
import { Workspace } from "@opencode/core/workspace";
|
|
22
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
23
|
+
import { Layer } from "effect";
|
|
24
|
+
import { PtyEnvironment } from "./pty-environment";
|
|
25
|
+
import type { ServerOptions } from "./options";
|
|
26
|
+
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 | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
|
27
|
+
type InstanceNode = (replacements: () => LayerNode.Replacements) => LayerNode.Provider<Instance.Service, never, typeof Node.tags.values.global>;
|
|
28
|
+
export declare function createEmbeddedRoutes(options?: ServerOptions, overrides?: LayerNode.Replacements, instances?: InstanceNode): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
|
29
|
+
export {};
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Database } from "@opencode/core/database/database";
|
|
2
|
+
import { V1Migration } from "@opencode/core/database/v1-migration";
|
|
3
|
+
import { App } from "@opencode/core/app";
|
|
4
|
+
import { LayerNode } from "@opencode/util/effect/layer-node";
|
|
5
|
+
import { Node } from "@opencode/util/effect/app-node";
|
|
6
|
+
import { httpClient } from "@opencode/util/effect/app-node-platform";
|
|
7
|
+
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder";
|
|
8
|
+
import { Bus } from "@opencode/core/bus";
|
|
9
|
+
import { EventLogger } from "@opencode/core/event-logger";
|
|
10
|
+
import { FileSystemSearch } from "@opencode/core/filesystem/search";
|
|
11
|
+
import { Credential } from "@opencode/core/credential";
|
|
12
|
+
import { Config } from "@opencode/core/config";
|
|
13
|
+
import { PermissionSaved } from "@opencode/core/permission/saved";
|
|
14
|
+
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
15
|
+
import { PersistentPty } from "@opencode/core/persistent-pty";
|
|
16
|
+
import { Project } from "@opencode/core/project";
|
|
17
|
+
import { Session } from "@opencode/core/session";
|
|
18
|
+
import { Instance } from "@opencode/core/instance/service";
|
|
19
|
+
import { SessionTransfer } from "@opencode/core/session/transfer";
|
|
20
|
+
import { ShellSelect } from "@opencode/core/shell/select";
|
|
21
|
+
import { Job } from "@opencode/core/job";
|
|
22
|
+
import { Mcp } from "@opencode/core/mcp/index";
|
|
23
|
+
import { Global } from "@opencode/util/global";
|
|
24
|
+
import { InstructionDiscovery } from "@opencode/core/instruction-discovery";
|
|
25
|
+
import { LocationServiceMap } from "@opencode/core/location-service-map";
|
|
26
|
+
import { LocationActivity } from "@opencode/core/location-activity";
|
|
27
|
+
import { ModelsDev } from "@opencode/core/models-dev";
|
|
28
|
+
import { SessionRestart } from "@opencode/core/session/execution/restart";
|
|
29
|
+
import { PluginUpdate } from "@opencode/core/plugin/update";
|
|
30
|
+
import { SdkPlugins } from "@opencode/core/plugin/sdk";
|
|
31
|
+
import { WellKnown } from "@opencode/core/wellknown";
|
|
32
|
+
import { Workspace } from "@opencode/core/workspace";
|
|
33
|
+
import { Watcher } from "@opencode/core/filesystem/watcher";
|
|
34
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
35
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
36
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
37
|
+
import { Api } from "./api";
|
|
38
|
+
import { ServerAuth } from "./auth";
|
|
39
|
+
import { CorsConfig } from "./cors";
|
|
40
|
+
import { handlers } from "./handlers";
|
|
41
|
+
import { authorizationLayer } from "./middleware/authorization";
|
|
42
|
+
import { schemaErrorLayer } from "./middleware/schema-error";
|
|
43
|
+
import { PtyEnvironment } from "./pty-environment";
|
|
44
|
+
import { layer } from "./location";
|
|
45
|
+
import { formLocationLayer } from "./middleware/form-location";
|
|
46
|
+
import { sessionLocationLayer } from "./middleware/session-location";
|
|
47
|
+
import { ServerInfo } from "./server-info";
|
|
48
|
+
const applicationServiceNodes = [
|
|
49
|
+
Global.node,
|
|
50
|
+
Database.node,
|
|
51
|
+
Bus.node,
|
|
52
|
+
EventLogger.node,
|
|
53
|
+
httpClient,
|
|
54
|
+
Job.node,
|
|
55
|
+
Project.node,
|
|
56
|
+
Session.node,
|
|
57
|
+
Instance.node,
|
|
58
|
+
SessionTransfer.node,
|
|
59
|
+
SdkPlugins.node,
|
|
60
|
+
PluginUpdate.node,
|
|
61
|
+
PermissionSaved.node,
|
|
62
|
+
PtyTicket.node,
|
|
63
|
+
PersistentPty.node,
|
|
64
|
+
Credential.node,
|
|
65
|
+
WellKnown.node,
|
|
66
|
+
PtyEnvironment.node,
|
|
67
|
+
LocationServiceMap.node,
|
|
68
|
+
LocationActivity.node,
|
|
69
|
+
SessionRestart.node,
|
|
70
|
+
Workspace.node
|
|
71
|
+
], applicationServices = LayerNode.group(applicationServiceNodes);
|
|
72
|
+
export function createRoutes(options = {}, serviceURLs = () => [], overrides = []) {
|
|
73
|
+
return makeRoutes(options.password ? ServerAuth.Config.configLayer({ password: Option.some(options.password) }) : ServerAuth.Config.layer, options, serviceURLs, overrides);
|
|
74
|
+
}
|
|
75
|
+
export function createEmbeddedRoutes(options = {}, overrides = [], instances) {
|
|
76
|
+
return makeRoutes(ServerAuth.Config.configLayer({ password: Option.none() }), options, () => [], overrides, instances);
|
|
77
|
+
}
|
|
78
|
+
function makeRoutes(auth, options, serviceURLs, overrides, instances) {
|
|
79
|
+
const standard = [
|
|
80
|
+
Database.node.replace(Database.configured(options.database)),
|
|
81
|
+
PersistentPty.node.replace(PersistentPty.configured(options.pty)),
|
|
82
|
+
Bus.node.replace(Bus.configured({ persist: options.events?.persist })),
|
|
83
|
+
App.node.replace(App.configured(options.app)),
|
|
84
|
+
ModelsDev.node.replace(ModelsDev.configured(options.models)),
|
|
85
|
+
Watcher.node.replace(Watcher.configured({ enabled: options.fs?.filewatcher })),
|
|
86
|
+
FileSystemSearch.node.replace(FileSystemSearch.configured({ fff: options.fs?.fff })),
|
|
87
|
+
Global.node.replace(Global.layerWith(options.config?.directory ? { config: options.config.directory } : {})),
|
|
88
|
+
Config.node.replace(Config.configured({
|
|
89
|
+
project: options.config?.project,
|
|
90
|
+
file: options.config?.file,
|
|
91
|
+
content: options.config?.content
|
|
92
|
+
})),
|
|
93
|
+
InstructionDiscovery.node.replace(InstructionDiscovery.configured({ project: options.config?.project })),
|
|
94
|
+
ShellSelect.node.replace(ShellSelect.configured({ gitbash: options.windows?.gitbash })),
|
|
95
|
+
Mcp.node.replace(Mcp.configured({
|
|
96
|
+
clientInfo: {
|
|
97
|
+
name: options.app?.name ?? "opencode",
|
|
98
|
+
version: options.app?.version ?? "unknown"
|
|
99
|
+
}
|
|
100
|
+
}))
|
|
101
|
+
], build = (overrides) => {
|
|
102
|
+
const replacements = [
|
|
103
|
+
...standard,
|
|
104
|
+
...instances ? [Instance.node.replace(instances(() => replacements))] : [],
|
|
105
|
+
...overrides
|
|
106
|
+
];
|
|
107
|
+
return AppNodeBuilder.build(applicationServices, replacements);
|
|
108
|
+
};
|
|
109
|
+
return (options.simulation ? Layer.unwrap(Effect.gen(function* () {
|
|
110
|
+
const { simulationReplacements } = yield* Effect.promise(() => import("@opencode/simulation/backend")), simulation = yield* simulationReplacements({ version: App.make(options.app).version });
|
|
111
|
+
return build([...overrides, ...simulation]);
|
|
112
|
+
})) : build(overrides)).pipe(Layer.flatMap((context) => {
|
|
113
|
+
const services = Layer.succeedContext(context), requestServices = Layer.merge(Layer.succeedContext(Context.pick(Database.Service, PermissionSaved.Service, PluginUpdate.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(Layer.succeed(CorsConfig, options)))), 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));
|
|
114
|
+
return Layer.merge(api, V1Migration.layer.pipe(Layer.provide(services)));
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
@@ -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/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";
|