summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-28 11:10:00 -0400
committerGitHub <[email protected]>2026-04-28 11:10:00 -0400
commitea3c6c34811de792ba6870766dd0a36f3a392bc6 (patch)
tree67f9731a871408d86b3fd652286acfccfc054e06 /packages
parent9b68b7195a5006333ac94f5ea65fb20174c879d8 (diff)
downloadopencode-ea3c6c34811de792ba6870766dd0a36f3a392bc6.tar.gz
opencode-ea3c6c34811de792ba6870766dd0a36f3a392bc6.zip
fix(httpapi): document instance query parameters (#24809)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/server/routes/instance/httpapi/pty.ts1
-rw-r--r--packages/opencode/src/server/routes/instance/httpapi/public.ts51
-rw-r--r--packages/opencode/test/server/httpapi-bridge.test.ts37
3 files changed, 88 insertions, 1 deletions
diff --git a/packages/opencode/src/server/routes/instance/httpapi/pty.ts b/packages/opencode/src/server/routes/instance/httpapi/pty.ts
index f1ac09399..217000253 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/pty.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/pty.ts
@@ -118,7 +118,6 @@ export const PtyConnectApi = HttpApi.make("pty-connect").add(
.add(
HttpApiEndpoint.get("connect", PtyPaths.connect, {
params: Params,
- query: CursorQuery,
success: Schema.Boolean,
}).annotateMerge(
OpenApi.annotations({
diff --git a/packages/opencode/src/server/routes/instance/httpapi/public.ts b/packages/opencode/src/server/routes/instance/httpapi/public.ts
index 1a7f675b3..c26d16e91 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/public.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/public.ts
@@ -17,6 +17,56 @@ import { SyncApi } from "./sync"
import { TuiApi } from "./tui"
import { WorkspaceApi } from "./workspace"
+type OpenApiParameter = {
+ name: string
+ in: string
+ required?: boolean
+ schema?: unknown
+}
+
+type OpenApiOperation = {
+ parameters?: OpenApiParameter[]
+}
+
+type OpenApiPathItem = Partial<Record<"get" | "post" | "put" | "delete" | "patch", OpenApiOperation>>
+
+type OpenApiSpec = {
+ paths?: Record<string, OpenApiPathItem>
+}
+
+const InstanceQueryParameters = [
+ {
+ name: "directory",
+ in: "query",
+ required: false,
+ schema: { type: "string" },
+ },
+ {
+ name: "workspace",
+ in: "query",
+ required: false,
+ schema: { type: "string" },
+ },
+] satisfies OpenApiParameter[]
+
+function documentInstanceQueryParameters(input: Record<string, unknown>) {
+ const spec = input as OpenApiSpec
+ for (const [path, item] of Object.entries(spec.paths ?? {})) {
+ if (path.startsWith("/global/") || path.startsWith("/auth/")) continue
+ for (const method of ["get", "post", "put", "delete", "patch"] as const) {
+ const operation = item[method]
+ if (!operation) continue
+ operation.parameters = [
+ ...InstanceQueryParameters,
+ ...(operation.parameters ?? []).filter(
+ (param) => param.in !== "query" || (param.name !== "directory" && param.name !== "workspace"),
+ ),
+ ]
+ }
+ }
+ return input
+}
+
export const PublicApi = HttpApi.make("opencode")
.addHttpApi(ControlApi)
.addHttpApi(GlobalApi)
@@ -41,5 +91,6 @@ export const PublicApi = HttpApi.make("opencode")
title: "opencode",
version: "1.0.0",
description: "opencode api",
+ transform: documentInstanceQueryParameters,
}),
)
diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts
index 150cf53c0..d4d14dbc0 100644
--- a/packages/opencode/test/server/httpapi-bridge.test.ts
+++ b/packages/opencode/test/server/httpapi-bridge.test.ts
@@ -34,6 +34,32 @@ function openApiRouteKeys(spec: { paths: Record<string, Partial<Record<(typeof m
.sort()
}
+function openApiParameters(spec: { paths: Record<string, Partial<Record<(typeof methods)[number], Operation>>> }) {
+ return Object.fromEntries(
+ Object.entries(spec.paths).flatMap(([path, item]) =>
+ methods
+ .filter((method) => item[method])
+ .map((method) => [
+ `${method.toUpperCase()} ${path}`,
+ (item[method]?.parameters ?? [])
+ .map(parameterKey)
+ .filter((param) => param !== undefined)
+ .sort(),
+ ]),
+ ),
+ )
+}
+
+type Operation = {
+ parameters?: unknown[]
+}
+
+function parameterKey(param: unknown) {
+ if (!param || typeof param !== "object" || !("in" in param) || !("name" in param)) return
+ if (typeof param.in !== "string" || typeof param.name !== "string") return
+ return `${param.in}:${param.name}:${"required" in param && param.required === true}`
+}
+
function authorization(username: string, password: string) {
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
}
@@ -63,6 +89,17 @@ describe("HttpApi server", () => {
expect(effectRoutes.filter((route) => !honoRoutes.includes(route))).toEqual([])
})
+ test("matches generated OpenAPI route parameters", async () => {
+ const hono = openApiParameters(await Server.openapi())
+ const effect = openApiParameters(OpenApi.fromApi(PublicApi))
+
+ expect(
+ Object.keys(hono)
+ .filter((route) => JSON.stringify(hono[route]) !== JSON.stringify(effect[route]))
+ .map((route) => ({ route, hono: hono[route], effect: effect[route] })),
+ ).toEqual([])
+ })
+
test("allows requests when auth is disabled", async () => {
await using tmp = await tmpdir({ git: true })
await Bun.write(`${tmp.path}/hello.txt`, "hello")