// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The OpenAPI 3.1 document, generated from the registry (spec ยง6.2). // // There is no second description of a procedure anywhere: the name, the kind, the scope, the // permission and both zod schemas come from the manifests, and zod 4 converts a schema to JSON // Schema itself. The committed openapi.json is this function's output, and test/openapi.test.ts // fails when the two differ -- so a changed input schema is a visible API change in review. import type { Registry } from "@conusai-kit/core" import { z } from "zod" export const API_PREFIX = "/api/v1" /** The problem+json model every refusal uses (kit-core's errors.ts). */ const problemSchema = { type: "object", required: ["type", "title", "status", "code"], properties: { type: { type: "string", description: "An app-relative URI reference, /problems/." }, title: { type: "string" }, status: { type: "integer" }, code: { type: "string" }, detail: { type: "string" }, errors: { type: "array", items: { type: "object", required: ["path", "message"], properties: { path: { type: "string" }, message: { type: "string" } }, }, }, retryAfter: { type: "integer", description: "Seconds, for 429 and 503." }, }, } as const const problemResponse = (description: string) => ({ description, content: { "application/problem+json": { schema: { $ref: "#/components/schemas/Problem" } } }, }) const jsonSchemaOf = (schema: z.ZodType, io: "input" | "output"): unknown => z.toJSONSchema(schema, { target: "draft-2020-12", io, unrepresentable: "any", reused: "inline" }) /** * What a procedure with a streamed body (kit-core `.stream(field)`) is on the wire: a `PUT` whose * body is the bytes and whose query parameters are the rest of the input. The schema of the input * still comes from the contract; the body field is the one that is not a parameter. */ function streamedOperation(def: { input: z.ZodType; stream?: string | undefined }) { if (def.stream === undefined) return undefined const input = jsonSchemaOf(def.input, "input") as { properties?: Record required?: string[] } const required = new Set(input.required ?? []) return { parameters: Object.entries(input.properties ?? {}) .filter(([name]) => name !== def.stream) .map(([name, schema]) => ({ name, in: "query", required: required.has(name), schema })), requestBody: { required: true, content: { "application/octet-stream": { schema: { type: "string", format: "binary" } } }, description: "The bytes, sent as they are. Their length must be the one the upload declared.", }, } } export interface OpenApiInfo { title: string version: string description?: string } export function openapiDocument(registry: Registry, info: OpenApiInfo): Record { const paths: Record = {} for (const name of [...registry.procedures.keys()].sort()) { const entry = registry.procedures.get(name) if (!entry) continue const { def, module } = entry const authenticated = def.scope !== "public" const streamed = streamedOperation(def) const downloadInput = def.download ? (jsonSchemaOf(def.input, "input") as { properties?: Record; required?: string[] }) : undefined const downloadRequired = new Set(downloadInput?.required ?? []) const responseContent = def.download ? { "application/octet-stream": { schema: { type: "string", format: "binary" } } } : { "application/json": { schema: jsonSchemaOf(def.output, "output") } } paths[`${API_PREFIX}/${name}`] = { [def.download ? "get" : streamed ? "put" : "post"]: { operationId: name, tags: [module], summary: `${def.kind} procedure on the ${def.scope} scope`, description: [ `Kind: ${def.kind}. Scope: ${def.scope}. Permission: ${def.permission}.`, def.idempotent ? "Takes an Idempotency-Key header." : undefined, def.audit ? `Audit event: ${def.audit}.` : undefined, def.usage ? `Usage meter: ${def.usage}.` : undefined, def.tool ? "Exposed as an MCP tool." : undefined, ] .filter(Boolean) .join(" "), ...(authenticated ? { security: [{ session: [] }, { bearer: [] }, { apiKey: [] }] } : { security: [{}] }), parameters: [ ...(def.idempotent ? [ { name: "Idempotency-Key", in: "header", required: true, schema: { type: "string", minLength: 8, maxLength: 200 }, description: "The first answer for this key is replayed, so a retry is a retry and not a second write.", }, ] : []), { name: "X-CSRF-Token", in: "header", required: false, schema: { type: "string" }, description: "Required when the credential is the session cookie; not used by bearer or API-key callers.", }, ...(streamed?.parameters ?? []), ], ...(!def.download ? { requestBody: streamed?.requestBody ?? { required: true, content: { "application/json": { schema: jsonSchemaOf(def.input, "input") } }, }, } : {}), ...(def.download ? { parameters: [ ...(def.idempotent ? [ { name: "Idempotency-Key", in: "header", required: true, schema: { type: "string", minLength: 8, maxLength: 200 }, description: "The first answer for this key is replayed.", }, ] : []), ...Object.entries(downloadInput?.properties ?? {}).map(([name, schema]) => ({ name, in: "query", required: downloadRequired.has(name), schema, })), ], } : {}), responses: { "200": { description: def.download ? "The file bytes, streamed with the stored filename." : "The procedure's output.", content: responseContent, }, "400": problemResponse("The request could not be read."), "401": problemResponse("No credential, or one that is no longer valid."), "404": problemResponse( "No such procedure in this scope: another tenant's ids answer the same way as a missing permission.", ), "409": problemResponse("A conflict, such as a taken slug or a stale version."), "422": problemResponse("The input does not match the procedure's schema."), "429": problemResponse("Rate limited; Retry-After says when."), }, }, } } return { openapi: "3.1.0", info, servers: [{ url: "/", description: "This deployment." }], paths, components: { schemas: { Problem: problemSchema }, securitySchemes: { session: { type: "apiKey", in: "cookie", name: "conusai_session", description: "The sealed session cookie a browser holds.", }, bearer: { type: "http", scheme: "bearer", bearerFormat: "JWT", description: "An OIDC access token for this app's audience.", }, apiKey: { type: "http", scheme: "bearer", description: "An organization API key, cak__." }, }, }, } } /** The document as it is served and as it is committed: two spaces, one trailing newline. */ export const openapiJson = (registry: Registry, info: OpenApiInfo): string => `${JSON.stringify(openapiDocument(registry, info), null, 2)}\n`