import { Location } from "@opencode-ai/core/location" import { LocationServiceMap } from "@opencode-ai/core/location-layer" import { FileSystem } from "@opencode-ai/core/filesystem" import { AbsolutePath } from "@opencode-ai/core/schema" import { WorkspaceV2 } from "@opencode-ai/core/workspace" import { Effect, Layer, Schema } from "effect" import { HttpServerRequest } from "effect/unstable/http" import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi" export const LocationQuery = Schema.Struct({ location: Schema.optional( Schema.Struct({ directory: Schema.optional(Schema.String), workspace: Schema.optional(Schema.String), }), ), }).annotate({ identifier: "LocationQuery" }) export const locationQueryOpenApi = OpenApi.annotations({ transform: (operation) => { const parameters = operation.parameters if (!Array.isArray(parameters)) return operation return { ...operation, parameters: parameters.map((parameter) => parameter?.name === "location" && parameter?.in === "query" ? { ...parameter, style: "deepObject", explode: true } : parameter, ), } }, }) export function response(data: Effect.Effect) { return Effect.gen(function* () { const location = yield* Location.Service return { location: new Location.Info({ directory: location.directory, workspaceID: location.workspaceID, project: location.project, }), data: yield* data, } }) } export type LocationServices = Layer.Success> export class LocationMiddleware extends HttpApiMiddleware.Service< LocationMiddleware, { provides: LocationServices } >()("@opencode/HttpApiLocation") {} export const LocationGroup = HttpApiGroup.make("server.location") .add( HttpApiEndpoint.get("location.get", "/api/location", { query: LocationQuery, success: Location.Info, }) .annotateMerge(locationQueryOpenApi) .annotateMerge( OpenApi.annotations({ identifier: "v2.location.get", summary: "Get location", description: "Resolve the requested location or the server default location.", }), ), ) .middleware(LocationMiddleware) function ref(request: HttpServerRequest.HttpServerRequest): Location.Ref { const query = new URL(request.url, "http://localhost").searchParams const workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"] const directory = query.get("location[directory]") || (request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd()) return Location.Ref.make({ directory: AbsolutePath.make(directory), workspaceID: workspaceID ? WorkspaceV2.ID.make(workspaceID) : undefined, }) } function decode(input: string) { try { return decodeURIComponent(input) } catch { return input } } export const layer = Layer.effect( LocationMiddleware, Effect.gen(function* () { const locations = yield* LocationServiceMap return LocationMiddleware.of((effect) => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest return yield* effect.pipe(Effect.provide(locations.get(ref(request)))) }), ) }), )