Skip to content

Commit 11030c6

Browse files
authored
Scope boolean query overrides
1 parent c104098 commit 11030c6

3 files changed

Lines changed: 33 additions & 30 deletions

File tree

packages/opencode/src/server/routes/instance/httpapi/groups/query.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ export const QueryBoolean = Schema.Literals(["true", "false"]).pipe(
66
encode: SchemaGetter.transform((value) => (value ? "true" : "false")),
77
}),
88
)
9+
10+
export const QueryBooleanOpenApi = {
11+
anyOf: [{ type: "boolean" }, { type: "string", enum: ["true", "false"] }],
12+
}

packages/opencode/src/server/routes/instance/httpapi/public.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { OpenApi } from "effect/unstable/httpapi"
22
import { OpenCodeHttpApi } from "./api"
3+
import { QueryBooleanOpenApi } from "./groups/query"
34

45
type OpenApiParameter = {
56
name: string
@@ -54,17 +55,20 @@ type OpenApiResponse = {
5455
// Query schemas describe decoded Effect values, but the generated SDK needs the
5556
// public call shape. These keep SDK callers passing numbers/booleans while the
5657
// server still decodes string query params at runtime.
57-
const QueryBooleanParameters = new Set(["roots", "archived"])
5858
const QueryParameterSchemas: Record<string, OpenApiSchema> = {
5959
"GET /experimental/session start": { type: "number" },
60+
"GET /experimental/session roots": QueryBooleanOpenApi,
61+
"GET /experimental/session archived": QueryBooleanOpenApi,
6062
"GET /find/file limit": { type: "integer", minimum: 1, maximum: 200 },
6163
"GET /experimental/session cursor": { type: "number" },
6264
"GET /experimental/session limit": { type: "number" },
6365
"GET /session start": { type: "number" },
66+
"GET /session roots": QueryBooleanOpenApi,
6467
"GET /session limit": { type: "number" },
6568
"GET /session/{sessionID}/message limit": { type: "integer", minimum: 0, maximum: Number.MAX_SAFE_INTEGER },
6669
"GET /api/session limit": { type: "number" },
6770
"GET /api/session start": { type: "number" },
71+
"GET /api/session roots": QueryBooleanOpenApi,
6872
"GET /api/session/{sessionID}/message limit": { type: "number" },
6973
}
7074

@@ -486,12 +490,6 @@ function normalizeParameter(param: OpenApiParameter, route: string) {
486490
param.schema = override
487491
return
488492
}
489-
if (QueryBooleanParameters.has(param.name)) {
490-
param.schema = {
491-
anyOf: [{ type: "boolean" }, { type: "string", enum: ["true", "false"] }],
492-
}
493-
return
494-
}
495493
}
496494
param.schema = stripOptionalNull(param.schema)
497495
}

packages/opencode/test/server/httpapi-query-schema-drift.test.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
import { PtyPaths } from "../../src/server/routes/instance/httpapi/groups/pty"
2727
import { MessagesQuery as V2MessagesQuery } from "../../src/server/routes/instance/httpapi/groups/v2/message"
2828
import { SessionsQuery as V2SessionsQuery } from "../../src/server/routes/instance/httpapi/groups/v2/session"
29-
import { QueryBoolean } from "../../src/server/routes/instance/httpapi/groups/query"
29+
import { QueryBoolean, QueryBooleanOpenApi } from "../../src/server/routes/instance/httpapi/groups/query"
3030
import { resetDatabase } from "../fixture/db"
3131
import { disposeAllInstances, tmpdir } from "../fixture/fixture"
3232
import { it } from "../lib/effect"
@@ -36,6 +36,8 @@ const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
3636
type Method = "get" | "post" | "put" | "delete" | "patch"
3737
type QuerySchema = { readonly fields: Record<string, unknown> }
3838
type OpenApiSchema = {
39+
readonly anyOf?: readonly OpenApiSchema[]
40+
readonly enum?: readonly string[]
3941
readonly maximum?: number
4042
readonly minimum?: number
4143
readonly pattern?: string
@@ -75,6 +77,13 @@ const numericSdkQueryParams = [
7577
{ method: "get", path: "/api/session/:sessionID/message", name: "limit", schema: { type: "number" } },
7678
] satisfies Array<{ method: Method; path: string; name: string; schema: OpenApiSchema }>
7779

80+
const booleanSdkQueryParams = [
81+
{ method: "get", path: ExperimentalPaths.session, name: "roots" },
82+
{ method: "get", path: ExperimentalPaths.session, name: "archived" },
83+
{ method: "get", path: SessionPaths.list, name: "roots" },
84+
{ method: "get", path: "/api/session", name: "roots" },
85+
] satisfies Array<{ method: Method; path: string; name: string }>
86+
7887
const queryParamPatterns = [
7988
{ method: "get", path: SessionPaths.diff, name: "messageID", pattern: "^msg" },
8089
] satisfies Array<{ method: Method; path: string; name: string; pattern: string }>
@@ -174,20 +183,7 @@ describe("httpapi query schema drift", () => {
174183
)
175184

176185
it.effect(
177-
"OpenAPI query parameter patterns come from runtime schemas",
178-
Effect.sync(() => {
179-
const spec = OpenApi.fromApi(PublicApi)
180-
for (const expected of queryParamPatterns) {
181-
expect(
182-
queryParameter(spec.paths[openApiPath(expected.path)]?.[expected.method], expected.name)?.schema,
183-
`${expected.method.toUpperCase()} ${expected.path} ${expected.name}`,
184-
).toEqual({ type: "string", pattern: expected.pattern })
185-
}
186-
}),
187-
)
188-
189-
it.effect(
190-
"OpenAPI workspace query params are declared by runtime query schemas",
186+
"OpenAPI query params are declared by runtime query schemas",
191187
Effect.sync(() => {
192188
const spec = OpenApi.fromApi(PublicApi)
193189
for (const route of openApiDriftRoutes) {
@@ -200,7 +196,7 @@ describe("httpapi query schema drift", () => {
200196
)
201197

202198
it.effect(
203-
"OpenAPI numeric query params preserve generated SDK call shapes",
199+
"OpenAPI query and path schemas preserve compatibility metadata",
204200
Effect.sync(() => {
205201
const spec = OpenApi.fromApi(PublicApi)
206202
for (const expected of numericSdkQueryParams) {
@@ -209,13 +205,18 @@ describe("httpapi query schema drift", () => {
209205
`${expected.method.toUpperCase()} ${expected.path} ${expected.name}`,
210206
).toEqual(expected.schema)
211207
}
212-
}),
213-
)
214-
215-
it.effect(
216-
"OpenAPI path parameter patterns come from runtime schemas",
217-
Effect.sync(() => {
218-
const spec = OpenApi.fromApi(PublicApi)
208+
for (const expected of booleanSdkQueryParams) {
209+
expect(
210+
queryParameter(spec.paths[openApiPath(expected.path)]?.[expected.method], expected.name)?.schema,
211+
`${expected.method.toUpperCase()} ${expected.path} ${expected.name}`,
212+
).toEqual(QueryBooleanOpenApi)
213+
}
214+
for (const expected of queryParamPatterns) {
215+
expect(
216+
queryParameter(spec.paths[openApiPath(expected.path)]?.[expected.method], expected.name)?.schema,
217+
`${expected.method.toUpperCase()} ${expected.path} ${expected.name}`,
218+
).toEqual({ type: "string", pattern: expected.pattern })
219+
}
219220
for (const expected of pathParamPatterns) {
220221
expect(
221222
pathParameter(spec.paths[openApiPath(expected.path)]?.[expected.method], expected.name)?.schema,

0 commit comments

Comments
 (0)