chore: generate

This commit is contained in:
opencode-agent[bot] 2026-07-04 21:18:39 +00:00
commit 1b9b260458
8 changed files with 1121 additions and 4358 deletions

View file

@ -165,9 +165,7 @@ const api = OpenAPI.fromSpec({
spec: await Bun.file("openapi.json").json(), // parsed document (no YAML) spec: await Bun.file("openapi.json").json(), // parsed document (no YAML)
auth: { auth: {
resolve: ({ name, scopes, operation }) => resolve: ({ name, scopes, operation }) =>
name === "BearerAuth" name === "BearerAuth" ? Effect.succeed({ type: "bearer", token }) : Effect.succeed(undefined),
? Effect.succeed({ type: "bearer", token })
: Effect.succeed(undefined),
}, },
}) })

View file

@ -46,9 +46,7 @@ export const invoke = (plan: Plan, input: unknown): Effect.Effect<unknown, unkno
) )
} }
if (json && Option.isNone(decoded)) { if (json && Option.isNone(decoded)) {
return yield* Effect.fail( return yield* Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} returned malformed JSON.`))
toolError(`${plan.operation.method} ${plan.operation.path} returned malformed JSON.`),
)
} }
return parsed return parsed
}) })
@ -208,8 +206,9 @@ const buildUrl = (plan: Plan, input: Readonly<Record<string, unknown>>): string
return toolError(`Missing required path parameter '${field.inputName}'.`) return toolError(`Missing required path parameter '${field.inputName}'.`)
} }
const fieldValue = serializeSimple(field, item, (value) => const fieldValue = serializeSimple(field, item, (value) =>
encodeURIComponent(value).replace(/[!'()*]/g, (character) => encodeURIComponent(value).replace(
`%${character.charCodeAt(0).toString(16).toUpperCase()}`, /[!'()*]/g,
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
), ),
) )
if (fieldValue instanceof ToolError) return fieldValue if (fieldValue instanceof ToolError) return fieldValue
@ -271,10 +270,7 @@ const serializeQuery = (
if (value.some((item) => item === undefined || (item !== null && typeof item === "object"))) { if (value.some((item) => item === undefined || (item !== null && typeof item === "object"))) {
return toolError(`Query parameter '${field.inputName}' contains an unsupported nested value.`) return toolError(`Query parameter '${field.inputName}' contains an unsupported nested value.`)
} }
return value.reduce( return value.reduce((current, item) => HttpClientRequest.appendUrlParam(current, field.name, String(item)), request)
(current, item) => HttpClientRequest.appendUrlParam(current, field.name, String(item)),
request,
)
} }
if (isRecord(value) && field.explode) { if (isRecord(value) && field.explode) {
return Object.entries(value).reduce<HttpClientRequest.HttpClientRequest | ToolError>((current, [name, item]) => { return Object.entries(value).reduce<HttpClientRequest.HttpClientRequest | ToolError>((current, [name, item]) => {
@ -289,11 +285,15 @@ const serializeQuery = (
return rendered instanceof ToolError ? rendered : HttpClientRequest.appendUrlParam(request, field.name, rendered) return rendered instanceof ToolError ? rendered : HttpClientRequest.appendUrlParam(request, field.name, rendered)
} }
const readResponseBody = (response: HttpClientResponse.HttpClientResponse, plan: Plan): Effect.Effect<string, ToolError> => const readResponseBody = (
response: HttpClientResponse.HttpClientResponse,
plan: Plan,
): Effect.Effect<string, ToolError> =>
Effect.gen(function* () { Effect.gen(function* () {
const contentLength = response.headers["content-length"] const contentLength = response.headers["content-length"]
const parsedSize = contentLength === undefined ? undefined : Number.parseInt(contentLength, 10) const parsedSize = contentLength === undefined ? undefined : Number.parseInt(contentLength, 10)
const declaredSize = parsedSize !== undefined && Number.isSafeInteger(parsedSize) && parsedSize >= 0 ? parsedSize : undefined const declaredSize =
parsedSize !== undefined && Number.isSafeInteger(parsedSize) && parsedSize >= 0 ? parsedSize : undefined
if (declaredSize !== undefined && declaredSize > maxResponseBodyBytes) { if (declaredSize !== undefined && declaredSize > maxResponseBodyBytes) {
return yield* Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} response exceeds 50 MiB.`)) return yield* Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} response exceeds 50 MiB.`))
} }
@ -304,7 +304,9 @@ const readResponseBody = (response: HttpClientResponse.HttpClientResponse, plan:
return Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} response exceeds 50 MiB.`)) return Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} response exceeds 50 MiB.`))
} }
if (size + chunk.byteLength > body.byteLength) { if (size + chunk.byteLength > body.byteLength) {
const grown = Buffer.allocUnsafe(Math.min(maxResponseBodyBytes, Math.max(size + chunk.byteLength, body.byteLength * 2))) const grown = Buffer.allocUnsafe(
Math.min(maxResponseBodyBytes, Math.max(size + chunk.byteLength, body.byteLength * 2)),
)
body.copy(grown, 0, 0, size) body.copy(grown, 0, 0, size)
body = grown body = grown
} }

View file

@ -78,7 +78,9 @@ const isBinaryMediaType = (document: Document, mediaType: string, value: unknown
return isRecord(schema) && schema.format === "binary" return isRecord(schema) && schema.format === "binary"
} }
const jsonContent = (content: Record<string, unknown>): { readonly mediaType: string; readonly schema: unknown } | undefined => { const jsonContent = (
content: Record<string, unknown>,
): { readonly mediaType: string; readonly schema: unknown } | undefined => {
const entry = Object.entries(content).find(([mediaType]) => isJsonMediaType(mediaType)) const entry = Object.entries(content).find(([mediaType]) => isJsonMediaType(mediaType))
return entry !== undefined && isRecord(entry[1]) ? { mediaType: entry[0], schema: entry[1].schema } : undefined return entry !== undefined && isRecord(entry[1]) ? { mediaType: entry[0], schema: entry[1].schema } : undefined
} }
@ -344,7 +346,7 @@ export const operationOutput = (
if (outcomes.length === 0) return { ok: true, value: undefined } if (outcomes.length === 0) return { ok: true, value: undefined }
return { return {
ok: true, ok: true,
value: withDefinitions(outcomes.length === 1 ? outcomes[0] ?? {} : { anyOf: outcomes }, definitions), value: withDefinitions(outcomes.length === 1 ? (outcomes[0] ?? {}) : { anyOf: outcomes }, definitions),
} }
} }
@ -380,7 +382,9 @@ export const operationPath = (
namespaces: ReadonlySet<string>, namespaces: ReadonlySet<string>,
): ReadonlyArray<string> => { ): ReadonlyArray<string> => {
const raw = nonEmptyString(operation.operationId) const raw = nonEmptyString(operation.operationId)
const segments = (raw === undefined ? [fallbackOperationId(method, path)] : raw.split(".")).map(sanitizeOperationSegment) const segments = (raw === undefined ? [fallbackOperationId(method, path)] : raw.split(".")).map(
sanitizeOperationSegment,
)
if (isOperationPathAvailable(segments, used, namespaces)) return segments if (isOperationPathAvailable(segments, used, namespaces)) return segments
const conflict = segments.slice(0, -1).findIndex((_, index) => used.has(segments.slice(0, index + 1).join("."))) const conflict = segments.slice(0, -1).findIndex((_, index) => used.has(segments.slice(0, index + 1).join(".")))
if (conflict >= 0 && conflict + 1 < segments.length) { if (conflict >= 0 && conflict + 1 < segments.length) {

View file

@ -93,16 +93,10 @@
}, },
"role": { "role": {
"type": "string", "type": "string",
"enum": [ "enum": ["admin", "member"]
"admin",
"member"
]
} }
}, },
"required": [ "required": ["name", "email"],
"name",
"email"
],
"additionalProperties": false "additionalProperties": false
} }
} }
@ -143,9 +137,7 @@
"type": "integer" "type": "integer"
} }
}, },
"required": [ "required": ["query"],
"query"
],
"additionalProperties": false "additionalProperties": false
} }
}, },
@ -216,17 +208,10 @@
}, },
"role": { "role": {
"type": "string", "type": "string",
"enum": [ "enum": ["admin", "member"]
"admin",
"member"
]
} }
}, },
"required": [ "required": ["id", "name", "email"],
"id",
"name",
"email"
],
"additionalProperties": false "additionalProperties": false
} }
}, },

File diff suppressed because it is too large Load diff

View file

@ -54,7 +54,9 @@ const json = (value: unknown, status = 200) =>
const singleOperation = (operation: Record<string, unknown>, method = "get"): Document => ({ const singleOperation = (operation: Record<string, unknown>, method = "get"): Document => ({
openapi: "3.1.0", openapi: "3.1.0",
paths: { "/test": { [method]: { operationId: "test", responses: { 200: { description: "Success" } }, ...operation } } }, paths: {
"/test": { [method]: { operationId: "test", responses: { 200: { description: "Success" } }, ...operation } },
},
}) })
describe("OpenAPI.fromSpec", () => { describe("OpenAPI.fromSpec", () => {
@ -94,7 +96,12 @@ describe("OpenAPI.fromSpec", () => {
const remove = toolAt(api.tools, "users.remove") const remove = toolAt(api.tools, "users.remove")
expect(api.skipped).toEqual([]) expect(api.skipped).toEqual([])
if (!Tool.isDefinition(get) || !Tool.isDefinition(create) || !Tool.isDefinition(search) || !Tool.isDefinition(remove)) { if (
!Tool.isDefinition(get) ||
!Tool.isDefinition(create) ||
!Tool.isDefinition(search) ||
!Tool.isDefinition(remove)
) {
throw new Error("happy-path fixture did not generate every operation") throw new Error("happy-path fixture did not generate every operation")
} }
expect(inputTypeScript(get)).toBe( expect(inputTypeScript(get)).toBe(
@ -110,7 +117,8 @@ describe("OpenAPI.fromSpec", () => {
const result = await Effect.runPromise( const result = await Effect.runPromise(
CodeMode.make({ tools: { api: api.tools } }) CodeMode.make({ tools: { api: api.tools } })
.execute(` .execute(
`
const user = await tools.api.users.get({ const user = await tools.api.users.get({
userId: "user-1", userId: "user-1",
include: ["profile", "permissions"], include: ["profile", "permissions"],
@ -128,7 +136,8 @@ describe("OpenAPI.fromSpec", () => {
}) })
const removed = await tools.api.users.remove({ userId: "user-1" }) const removed = await tools.api.users.remove({ userId: "user-1" })
return { user, created, summary, removed } return { user, created, summary, removed }
`) `,
)
.pipe(Effect.provide(client.layer)), .pipe(Effect.provide(client.layer)),
) )
@ -482,9 +491,9 @@ describe("OpenAPI.fromSpec", () => {
expect(url.searchParams.get("nullable")).toBe("null") expect(url.searchParams.get("nullable")).toBe("null")
expect(url.searchParams.get("constructor")).toBe("safe") expect(url.searchParams.get("constructor")).toBe("safe")
expect(client.requests[0]!.headers.meta).toBe("a=b,c=d") expect(client.requests[0]!.headers.meta).toBe("a=b,c=d")
await expect( await expect(Effect.runPromise(tool.run({ keys: [undefined] }).pipe(Effect.provide(client.layer)))).rejects.toThrow(
Effect.runPromise(tool.run({ keys: [undefined] }).pipe(Effect.provide(client.layer))), "unsupported nested value",
).rejects.toThrow("unsupported nested value") )
}) })
test("skips unsupported parameter encodings and malformed security", () => { test("skips unsupported parameter encodings and malformed security", () => {
@ -586,7 +595,10 @@ describe("OpenAPI.fromSpec", () => {
test("applies authentication carriers without prototype or collision loss", async () => { test("applies authentication carriers without prototype or collision loss", async () => {
const client = recordingClient(() => json({ ok: true })) const client = recordingClient(() => json({ ok: true }))
const authenticated = (security: ReadonlyArray<Record<string, ReadonlyArray<string>>>, schemes: Record<string, unknown>) => const authenticated = (
security: ReadonlyArray<Record<string, ReadonlyArray<string>>>,
schemes: Record<string, unknown>,
) =>
OpenAPI.fromSpec({ OpenAPI.fromSpec({
baseUrl, baseUrl,
spec: { ...singleOperation({}), security, components: { securitySchemes: schemes } }, spec: { ...singleOperation({}), security, components: { securitySchemes: schemes } },
@ -602,13 +614,10 @@ describe("OpenAPI.fromSpec", () => {
expect(new URL(client.requests[0]!.url).searchParams.get("__proto__")).toBe("secret") expect(new URL(client.requests[0]!.url).searchParams.get("__proto__")).toBe("secret")
const duplicate = toolAt( const duplicate = toolAt(
authenticated( authenticated([{ first: [], second: [] }], {
[{ first: [], second: [] }], first: { type: "apiKey", in: "header", name: "x-key" },
{ second: { type: "apiKey", in: "header", name: "x-key" },
first: { type: "apiKey", in: "header", name: "x-key" }, }).tools,
second: { type: "apiKey", in: "header", name: "x-key" },
},
).tools,
"test", "test",
) )
if (!Tool.isDefinition(duplicate)) throw new Error("duplicate auth tool was not generated") if (!Tool.isDefinition(duplicate)) throw new Error("duplicate auth tool was not generated")
@ -633,8 +642,7 @@ describe("OpenAPI.fromSpec", () => {
}, },
}, },
auth: { auth: {
resolve: ({ name }) => resolve: ({ name }) => Effect.succeed(name === "bearer" ? { type: "bearer", token: "secret" } : undefined),
Effect.succeed(name === "bearer" ? { type: "bearer", token: "secret" } : undefined),
}, },
}) })
const alternativeTool = toolAt(alternative.tools, "test") const alternativeTool = toolAt(alternative.tools, "test")
@ -766,9 +774,7 @@ describe("OpenAPI.fromSpec", () => {
const oversized = recordingClient( const oversized = recordingClient(
() => new Response(null, { headers: { "content-length": String(50 * 1024 * 1024 + 1) } }), () => new Response(null, { headers: { "content-length": String(50 * 1024 * 1024 + 1) } }),
) )
const malformed = recordingClient( const malformed = recordingClient(() => new Response("{", { headers: { "content-type": "application/json" } }))
() => new Response("{", { headers: { "content-type": "application/json" } }),
)
const chunked = recordingClient(() => new Response(new Uint8Array(50 * 1024 * 1024 + 1))) const chunked = recordingClient(() => new Response(new Uint8Array(50 * 1024 * 1024 + 1)))
await expect(Effect.runPromise(tool.run({}).pipe(Effect.provide(oversized.layer)))).rejects.toThrow( await expect(Effect.runPromise(tool.run({}).pipe(Effect.provide(oversized.layer)))).rejects.toThrow(

View file

@ -308,10 +308,7 @@ describe("union schemas render every alternative", () => {
test("allOf renders intersections with parenthesized union members", () => { test("allOf renders intersections with parenthesized union members", () => {
const schema = { const schema = {
allOf: [ allOf: [{ type: "object", properties: { id: { type: "string" } } }, { type: ["string", "null"] }],
{ type: "object", properties: { id: { type: "string" } } },
{ type: ["string", "null"] },
],
} as const } as const
expect(jsonSchemaToTypeScript(schema)).toBe("{ id?: string } & (string | null)") expect(jsonSchemaToTypeScript(schema)).toBe("{ id?: string } & (string | null)")
}) })
@ -321,7 +318,9 @@ describe("union schemas render every alternative", () => {
"unknown", "unknown",
) )
expect( expect(
jsonSchemaToTypeScript({ allOf: [{ type: "string" }, { allOf: [{ $ref: "https://example.com/external.json" }] }] }), jsonSchemaToTypeScript({
allOf: [{ type: "string" }, { allOf: [{ $ref: "https://example.com/external.json" }] }],
}),
).toBe("unknown") ).toBe("unknown")
expect( expect(
jsonSchemaToTypeScript({ jsonSchemaToTypeScript({

View file

@ -14507,6 +14507,7 @@
}, },
"description": "Establish a WebSocket connection streaming PTY output and accepting terminal input.", "description": "Establish a WebSocket connection streaming PTY output and accepting terminal input.",
"summary": "Connect to PTY session", "summary": "Connect to PTY session",
"x-websocket": true,
"x-codeSamples": [ "x-codeSamples": [
{ {
"lang": "js", "lang": "js",