From a75978815a234c93c1049b78a695f93dc98c14db Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 5 Jul 2026 15:58:43 -0400 Subject: [PATCH] feat(server): add loaded locations debug endpoint --- packages/client/src/effect/api/api.ts | 8 +++++ .../client/src/effect/generated/client.ts | 6 ++++ .../client/src/promise/generated/client.ts | 14 +++++++++ .../client/src/promise/generated/types.ts | 2 ++ packages/client/test/promise.test.ts | 2 ++ packages/protocol/src/api.ts | 3 ++ packages/protocol/src/client.ts | 1 + packages/protocol/src/groups/debug.ts | 15 ++++++++++ packages/sdk/js/src/v2/gen/sdk.gen.ts | 21 ++++++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 29 +++++++++++++++++++ packages/server/src/handlers.ts | 2 ++ packages/server/src/handlers/debug.ts | 14 +++++++++ packages/server/src/routes.ts | 2 +- 13 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 packages/protocol/src/groups/debug.ts create mode 100644 packages/server/src/handlers/debug.ts diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index 3f4c852be1..866747dafa 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -862,6 +862,13 @@ export interface VcsApi { readonly diff: VcsDiffOperation } +export type Endpoint25_0Output = EffectValue> +export type DebugLocationOperation = () => Effect.Effect + +export interface DebugApi { + readonly location: DebugLocationOperation +} + export interface AppApi { readonly health: HealthApi readonly location: LocationApi @@ -888,4 +895,5 @@ export interface AppApi { readonly reference: ReferenceApi readonly projectCopy: ProjectCopyApi readonly vcs: VcsApi + readonly debug: DebugApi } diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index a9a91eb8c6..2a253cf016 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -1043,6 +1043,11 @@ const Endpoint24_1 = (raw: RawClient["server.vcs"]) => (input: Endpoint24_1Input const adaptGroup24 = (raw: RawClient["server.vcs"]) => ({ status: Endpoint24_0(raw), diff: Endpoint24_1(raw) }) +const Endpoint25_0 = (raw: RawClient["server.debug"]) => () => + raw["debug.location"]({}).pipe(Effect.mapError(mapClientError)) + +const adaptGroup25 = (raw: RawClient["server.debug"]) => ({ location: Endpoint25_0(raw) }) + const adaptClient = (raw: RawClient) => ({ health: adaptGroup0(raw["server.health"]), location: adaptGroup1(raw["server.location"]), @@ -1069,6 +1074,7 @@ const adaptClient = (raw: RawClient) => ({ reference: adaptGroup22(raw["server.reference"]), projectCopy: adaptGroup23(raw["server.projectCopy"]), vcs: adaptGroup24(raw["server.vcs"]), + debug: adaptGroup25(raw["server.debug"]), }) export const make = (options?: { readonly baseUrl?: URL | string }) => diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index 79368764c7..f5fe29506e 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -173,6 +173,7 @@ import type { VcsStatusOutput, VcsDiffInput, VcsDiffOutput, + DebugLocationOutput, } from "./types" import { ClientError } from "./client-error" @@ -1448,6 +1449,19 @@ export function make(options: ClientOptions) { requestOptions, ), }, + debug: { + location: (requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/debug/location`, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ), + }, } } diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 151f0ff03f..c4ed970247 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -6003,3 +6003,5 @@ export type VcsDiffOutput = { readonly status?: "added" | "deleted" | "modified" }> } + +export type DebugLocationOutput = ReadonlyArray<{ readonly directory: string; readonly workspaceID?: string }> diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index d445a69c08..2dbd58a858 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -30,7 +30,9 @@ test("exposes every standard HTTP API group", () => { "reference", "projectCopy", "vcs", + "debug", ]) + expect(Object.keys(client.debug)).toEqual(["location"]) expect(Object.keys(client.message)).toEqual(["list"]) expect(Object.keys(client.integration)).toEqual([ "list", diff --git a/packages/protocol/src/api.ts b/packages/protocol/src/api.ts index 04d60888d7..2f91fae332 100644 --- a/packages/protocol/src/api.ts +++ b/packages/protocol/src/api.ts @@ -16,6 +16,7 @@ import type { Definition } from "@opencode-ai/schema/event" import { AgentGroup } from "./groups/agent.js" import { PluginGroup } from "./groups/plugin.js" import { HealthGroup } from "./groups/health.js" +import { DebugGroup } from "./groups/debug.js" import { PtyGroup } from "./groups/pty.js" import { ShellGroup } from "./groups/shell.js" import { makeQuestionGroup } from "./groups/question.js" @@ -81,6 +82,7 @@ type ApiGroups< Event extends HttpApiGroup.Any, > = | typeof HealthGroup + | typeof DebugGroup | LocationGroups | FormGroups | SessionGroups @@ -165,6 +167,7 @@ const makeApiFromGroup = < .add(ReferenceGroup.middleware(locationMiddleware)) .add(ProjectCopyGroup.middleware(locationMiddleware)) .add(VcsGroup.middleware(locationMiddleware)) + .add(DebugGroup) .annotateMerge( OpenApi.annotations({ title: "opencode HttpApi", diff --git a/packages/protocol/src/client.ts b/packages/protocol/src/client.ts index 3976289ed9..3d2993a02b 100644 --- a/packages/protocol/src/client.ts +++ b/packages/protocol/src/client.ts @@ -34,6 +34,7 @@ export const ClientApi: ClientApiShape = makeDefaultApi({ export const groupNames = { "server.health": "health", + "server.debug": "debug", "server.location": "location", "server.agent": "agent", "server.plugin": "plugin", diff --git a/packages/protocol/src/groups/debug.ts b/packages/protocol/src/groups/debug.ts new file mode 100644 index 0000000000..f41b02b864 --- /dev/null +++ b/packages/protocol/src/groups/debug.ts @@ -0,0 +1,15 @@ +import { Location } from "@opencode-ai/schema/location" +import { Schema } from "effect" +import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +export const DebugGroup = HttpApiGroup.make("server.debug").add( + HttpApiEndpoint.get("debug.location", "/api/debug/location", { + success: Schema.Array(Location.Ref), + }).annotateMerge( + OpenApi.annotations({ + identifier: "v2.debug.location", + summary: "List loaded locations", + description: "List locations currently loaded by the server.", + }), + ), +) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index ae8318d78e..59484dddfb 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -276,6 +276,8 @@ import type { V2CredentialRemoveResponses, V2CredentialUpdateErrors, V2CredentialUpdateResponses, + V2DebugLocationErrors, + V2DebugLocationResponses, V2EventChangesErrors, V2EventChangesResponses, V2EventSubscribeErrors, @@ -8036,6 +8038,20 @@ export class Vcs2 extends HeyApiClient { } } +export class Debug extends HeyApiClient { + /** + * List loaded locations + * + * List locations currently loaded by the server. + */ + public location(options?: Options) { + return (options?.client ?? this.client).get({ + url: "/api/debug/location", + ...options, + }) + } +} + export class V2 extends HeyApiClient { private _health?: Health get health(): Health { @@ -8156,6 +8172,11 @@ export class V2 extends HeyApiClient { get vcs(): Vcs2 { return (this._vcs ??= new Vcs2({ client: this.client })) } + + private _debug?: Debug + get debug(): Debug { + return (this._debug ??= new Debug({ client: this.client })) + } } export class OpencodeClient extends HeyApiClient { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 2b6e1d031f..6db6b405b8 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -19054,6 +19054,35 @@ export type V2VcsDiffResponses = { export type V2VcsDiffResponse = V2VcsDiffResponses[keyof V2VcsDiffResponses] +export type V2DebugLocationData = { + body?: never + path?: never + query?: never + url: "/api/debug/location" +} + +export type V2DebugLocationErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestErrorV2 + /** + * UnauthorizedError + */ + 401: UnauthorizedErrorV2 +} + +export type V2DebugLocationError = V2DebugLocationErrors[keyof V2DebugLocationErrors] + +export type V2DebugLocationResponses = { + /** + * Success + */ + 200: Array +} + +export type V2DebugLocationResponse = V2DebugLocationResponses[keyof V2DebugLocationResponses] + export type PtyConnectData = { body?: never path: { diff --git a/packages/server/src/handlers.ts b/packages/server/src/handlers.ts index bfae813372..896cbbc592 100644 --- a/packages/server/src/handlers.ts +++ b/packages/server/src/handlers.ts @@ -13,6 +13,7 @@ import { EventHandler } from "./handlers/event" import { AgentHandler } from "./handlers/agent" import { PluginHandler } from "./handlers/plugin" import { HealthHandler } from "./handlers/health" +import { DebugHandler } from "./handlers/debug" import { PtyHandler } from "./handlers/pty" import { ShellHandler } from "./handlers/shell" import { QuestionHandler } from "./handlers/question" @@ -27,6 +28,7 @@ import { VcsHandler } from "./handlers/vcs" export const handlers = Layer.mergeAll( HealthHandler, + DebugHandler, LocationHandler, AgentHandler, PluginHandler, diff --git a/packages/server/src/handlers/debug.ts b/packages/server/src/handlers/debug.ts new file mode 100644 index 0000000000..f5ffd536e4 --- /dev/null +++ b/packages/server/src/handlers/debug.ts @@ -0,0 +1,14 @@ +import { LocationServiceMap } from "@opencode-ai/core/location-service-map" +import { Effect, Option, RcMap } from "effect" +import { HttpApiBuilder } from "effect/unstable/httpapi" +import { Api } from "../api" + +export const DebugHandler = HttpApiBuilder.group(Api, "server.debug", (handlers) => + handlers.handle( + "debug.location", + Effect.fn(function* () { + const locations = Option.getOrThrow(yield* Effect.serviceOption(LocationServiceMap.Service)) + return Array.from(yield* RcMap.keys(locations.rcMap)) + }), + ), +) diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts index da63120bd8..f5e616392f 100644 --- a/packages/server/src/routes.ts +++ b/packages/server/src/routes.ts @@ -83,7 +83,7 @@ function makeRoutes( : AppNodeBuilder.build(applicationServices, replacements) return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe( - Layer.provide(handlers), + Layer.provide(handlers.pipe(Layer.provide(serviceLayer))), Layer.provide(formLocationLayer), Layer.provide(sessionLocationLayer), Layer.provide(layer),