From a7855aa1695865e903c350aa475f154e9e01eb93 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Tue, 21 Jul 2026 19:02:01 +0800 Subject: [PATCH] feat(server): expose location paths --- packages/protocol/src/api.ts | 3 +++ packages/protocol/src/groups/path.ts | 20 ++++++++++++++++++++ packages/schema/src/index.ts | 1 + packages/schema/src/path.ts | 13 +++++++++++++ packages/server/src/handlers.ts | 2 ++ packages/server/src/handlers/path.ts | 22 ++++++++++++++++++++++ packages/server/src/routes.ts | 5 ++++- 7 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 packages/protocol/src/groups/path.ts create mode 100644 packages/schema/src/path.ts create mode 100644 packages/server/src/handlers/path.ts diff --git a/packages/protocol/src/api.ts b/packages/protocol/src/api.ts index df48ecb3a2..843511fb72 100644 --- a/packages/protocol/src/api.ts +++ b/packages/protocol/src/api.ts @@ -30,6 +30,7 @@ import { CredentialGroup } from "./groups/credential.js" import { ProjectGroup } from "./groups/project.js" import { ProjectCopyGroup } from "./groups/project-copy.js" import { VcsGroup } from "./groups/vcs.js" +import { PathGroup } from "./groups/path.js" type LocationGroups = | HttpApiGroup.AddMiddleware @@ -50,6 +51,7 @@ type LocationGroups = | HttpApiGroup.AddMiddleware | HttpApiGroup.AddMiddleware | HttpApiGroup.AddMiddleware + | HttpApiGroup.AddMiddleware type SessionGroups = | ReturnType> @@ -168,6 +170,7 @@ const makeApiFromGroup = < .add(ReferenceGroup.middleware(locationMiddleware)) .add(ProjectCopyGroup.middleware(locationMiddleware)) .add(VcsGroup.middleware(locationMiddleware)) + .add(PathGroup.middleware(locationMiddleware)) .add(DebugGroup) .annotateMerge( OpenApi.annotations({ diff --git a/packages/protocol/src/groups/path.ts b/packages/protocol/src/groups/path.ts new file mode 100644 index 0000000000..27d5116b03 --- /dev/null +++ b/packages/protocol/src/groups/path.ts @@ -0,0 +1,20 @@ +import { Path } from "@opencode-ai/schema/path" +import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { LocationQuery, locationQueryOpenApi } from "./location.js" + +export const PathGroup = HttpApiGroup.make("server.path") + .add( + HttpApiEndpoint.get("path.get", "/api/path", { + query: LocationQuery, + success: Path.Info, + }) + .annotateMerge(locationQueryOpenApi) + .annotateMerge( + OpenApi.annotations({ + identifier: "v2.path.get", + summary: "Get paths", + description: "Get process and location paths.", + }), + ), + ) + .annotateMerge(OpenApi.annotations({ title: "path", description: "Location and process paths." })) diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 7b09e92426..5dd4f5369d 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -14,6 +14,7 @@ export { Model } from "./model.js" export { Money } from "./money.js" export { Permission } from "./permission.js" export { PermissionSaved } from "./permission-saved.js" +export { Path } from "./path.js" export { Project } from "./project.js" export { ProjectCopy } from "./project-copy.js" export { Provider } from "./provider.js" diff --git a/packages/schema/src/path.ts b/packages/schema/src/path.ts new file mode 100644 index 0000000000..122baf18a0 --- /dev/null +++ b/packages/schema/src/path.ts @@ -0,0 +1,13 @@ +export * as Path from "./path.js" + +import { Schema } from "effect" +import { AbsolutePath } from "./schema.js" + +export const Info = Schema.Struct({ + home: AbsolutePath, + state: AbsolutePath, + config: AbsolutePath, + worktree: AbsolutePath, + directory: AbsolutePath, +}).annotate({ identifier: "Path.Info" }) +export interface Info extends Schema.Schema.Type {} diff --git a/packages/server/src/handlers.ts b/packages/server/src/handlers.ts index 4b0b4acaa2..17ac142862 100644 --- a/packages/server/src/handlers.ts +++ b/packages/server/src/handlers.ts @@ -26,6 +26,7 @@ import { CredentialHandler } from "./handlers/credential" import { ProjectHandler } from "./handlers/project" import { ProjectCopyHandler } from "./handlers/project-copy" import { VcsHandler } from "./handlers/vcs" +import { PathHandler } from "./handlers/path" import { EventFeed } from "./event-feed" export const handlers = Layer.mergeAll( @@ -56,4 +57,5 @@ export const handlers = Layer.mergeAll( ReferenceHandler, ProjectCopyHandler, VcsHandler, + PathHandler, ) diff --git a/packages/server/src/handlers/path.ts b/packages/server/src/handlers/path.ts new file mode 100644 index 0000000000..01e09d58d3 --- /dev/null +++ b/packages/server/src/handlers/path.ts @@ -0,0 +1,22 @@ +import { Location } from "@opencode-ai/core/location" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { Global } from "@opencode-ai/util/global" +import { Effect } from "effect" +import { HttpApiBuilder } from "effect/unstable/httpapi" +import { Api } from "../api" + +export const PathHandler = HttpApiBuilder.group(Api, "server.path", (handlers) => + handlers.handle("path.get", () => + Effect.gen(function* () { + const global = yield* Global.Service + const location = yield* Location.Service + return { + home: AbsolutePath.make(global.home), + state: AbsolutePath.make(global.state), + config: AbsolutePath.make(global.config), + worktree: location.project.directory, + directory: location.directory, + } + }), + ), +) diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts index edffdbd4f8..256217fc19 100644 --- a/packages/server/src/routes.ts +++ b/packages/server/src/routes.ts @@ -60,6 +60,7 @@ const applicationServices = LayerNode.group([ WellKnown.node, PtyEnvironment.node, LocationServiceMap.node, + Global.node, SessionRestart.node, ]) @@ -134,7 +135,9 @@ function makeRoutes( Layer.flatMap((context) => { const services = Layer.succeedContext(context) const requestServices = Layer.merge( - Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service, WellKnown.Service)(context)), + Layer.succeedContext( + Context.pick(Global.Service, PermissionSaved.Service, Project.Service, WellKnown.Service)(context), + ), ServerInfo.layer(serviceURLs, options.app), ) return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(