fix(httpapi): align request body openapi shape (#24811)

This commit is contained in:
Kit Langton
2026-04-28 12:55:37 -04:00
committed by GitHub
parent 2c2fc3499b
commit c00058ed7a
2 changed files with 97 additions and 3 deletions

View File

@@ -26,14 +26,32 @@ type OpenApiParameter = {
type OpenApiOperation = {
parameters?: OpenApiParameter[]
requestBody?: {
required?: boolean
content?: Record<string, { schema?: OpenApiSchema }>
}
}
type OpenApiPathItem = Partial<Record<"get" | "post" | "put" | "delete" | "patch", OpenApiOperation>>
type OpenApiSpec = {
components?: {
schemas?: Record<string, OpenApiSchema>
}
paths?: Record<string, OpenApiPathItem>
}
type OpenApiSchema = {
$ref?: string
additionalProperties?: OpenApiSchema | boolean
allOf?: OpenApiSchema[]
anyOf?: OpenApiSchema[]
items?: OpenApiSchema
oneOf?: OpenApiSchema[]
properties?: Record<string, OpenApiSchema>
type?: string
}
const InstanceQueryParameters = [
{
name: "directory",
@@ -49,13 +67,28 @@ const InstanceQueryParameters = [
},
] satisfies OpenApiParameter[]
function documentInstanceQueryParameters(input: Record<string, unknown>) {
const LegacyBodyRefParameters = new Set(["Auth", "Config", "Part", "WorktreeRemoveInput", "WorktreeResetInput"])
function matchLegacyOpenApi(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
const isInstanceRoute = !path.startsWith("/global/") && !path.startsWith("/auth/")
for (const method of ["get", "post", "put", "delete", "patch"] as const) {
const operation = item[method]
if (!operation) continue
if (operation.requestBody) {
delete operation.requestBody.required
for (const media of Object.values(operation.requestBody.content ?? {})) {
const ref = media.schema?.$ref?.replace("#/components/schemas/", "")
if (ref && LegacyBodyRefParameters.has(ref)) continue
if (ref && spec.components?.schemas?.[ref]) {
media.schema = normalizeRequestSchema(structuredClone(spec.components.schemas[ref]))
continue
}
if (media.schema) media.schema = normalizeRequestSchema(media.schema)
}
}
if (!isInstanceRoute) continue
operation.parameters = [
...InstanceQueryParameters,
...(operation.parameters ?? []).filter(
@@ -67,6 +100,29 @@ function documentInstanceQueryParameters(input: Record<string, unknown>) {
return input
}
function normalizeRequestSchema(schema: OpenApiSchema): OpenApiSchema {
const options = schema.anyOf ?? schema.oneOf
if (options) {
const withoutNull = options.filter((item) => item.type !== "null")
const finite = withoutNull.find((item) => item.type === "number")
if (finite && withoutNull.every((item) => item.type === "number" || item.type === "string")) return finite
if (withoutNull.length === 1) return normalizeRequestSchema(withoutNull[0])
if (schema.anyOf) schema.anyOf = withoutNull.map(normalizeRequestSchema)
if (schema.oneOf) schema.oneOf = withoutNull.map(normalizeRequestSchema)
}
if (schema.allOf) schema.allOf = schema.allOf.map(normalizeRequestSchema)
if (schema.items) schema.items = normalizeRequestSchema(schema.items)
if (schema.properties) {
for (const [key, value] of Object.entries(schema.properties)) {
schema.properties[key] = normalizeRequestSchema(value)
}
}
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
schema.additionalProperties = normalizeRequestSchema(schema.additionalProperties)
}
return schema
}
export const PublicApi = HttpApi.make("opencode")
.addHttpApi(ControlApi)
.addHttpApi(GlobalApi)
@@ -91,6 +147,6 @@ export const PublicApi = HttpApi.make("opencode")
title: "opencode",
version: "1.0.0",
description: "opencode api",
transform: documentInstanceQueryParameters,
transform: matchLegacyOpenApi,
}),
)