feat(plugin): support plugin-provided tools (#34619)

This commit is contained in:
Kit Langton 2026-06-30 13:18:56 -04:00 committed by GitHub
commit 194b0615e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 1633 additions and 959 deletions

View file

@ -244,6 +244,16 @@ export function emitEffectImported(
}
}
export function emitEffectShape(
contract: Contract,
options: { readonly module: string; readonly api: string },
): Output {
return {
operations: operations(contract.groups),
files: [{ path: "api.ts", content: renderEffectShape(contract.groups, options) }],
}
}
export function emitPromise(
contract: Contract,
options?: {
@ -275,6 +285,75 @@ export function emitPromise(
}
}
function renderEffectShape(groups: ReadonlyArray<Group>, options: { readonly module: string; readonly api: string }) {
const endpointTypes = groups.map((group, groupIndex) => {
const rawGroup = group.endpoints[0]?.topLevel ? "RawClient" : `RawClient[${JSON.stringify(group.sourceIdentifier)}]`
const endpoints = group.endpoints.map((endpoint, endpointIndex) => {
const prefix = `Endpoint${groupIndex}_${endpointIndex}`
const request =
endpoint.operation.inputMode === "none"
? ""
: `type ${prefix}Request = Parameters<${rawGroup}[${JSON.stringify(endpoint.endpoint.name)}]>[0]`
const input = endpoint.input
.map(
(field) =>
`readonly ${JSON.stringify(field.name)}${field.optional ? "?" : ""}: ${prefix}Request[${JSON.stringify(field.source)}][${JSON.stringify(field.name)}]`,
)
.join("; ")
const inputType = endpoint.operation.inputMode === "none" ? "" : `export type ${prefix}Input = { ${input} }`
const rawOutput = `EffectValue<ReturnType<${rawGroup}[${JSON.stringify(endpoint.endpoint.name)}]>>`
const outputType = isStreamSchema(endpoint.successes[0])
? `export type ${prefix}Output = StreamValue<${rawOutput}>`
: `export type ${prefix}Output = ${endpoint.unwrapData ? `(${rawOutput})["data"]` : rawOutput}`
return [
request,
endpoint.operation.inputMode === "none" ? "" : inputType,
outputType,
`export type ${groupShapeTypeName(group, endpoint)}<E = never> = (${endpoint.operation.inputMode === "none" ? "" : `input${endpoint.operation.inputMode === "optional" ? "?" : ""}: ${prefix}Input`}) => ${endpoint.operation.success === "stream" ? `Stream.Stream<${prefix}Output, E>` : `Effect.Effect<${prefix}Output, E>`}`,
]
.filter(Boolean)
.join("\n")
})
const methods = group.endpoints
.map(
(endpoint) => `readonly ${JSON.stringify(endpoint.operation.name)}: ${groupShapeTypeName(group, endpoint)}<E>`,
)
.join("\n")
return `${endpoints.join("\n\n")}\n\nexport interface ${groupShapeName(group)}<E = never> {\n${methods}\n}`
})
const clientFields = groups.flatMap((group) =>
group.endpoints[0]?.topLevel
? group.endpoints.map(
(endpoint) =>
`readonly ${JSON.stringify(endpoint.operation.name)}: ${groupShapeTypeName(group, endpoint)}<E>`,
)
: [`readonly ${JSON.stringify(group.identifier)}: ${groupShapeName(group)}<E>`],
)
return `// Generated by @opencode-ai/httpapi-codegen. Do not edit.
import type { Effect, Stream } from "effect"
import type { HttpApiClient } from "effect/unstable/httpapi"
import type { ${options.api} } from ${JSON.stringify(options.module)}
type RawClient = HttpApiClient.ForApi<typeof ${options.api}>
type EffectValue<A> = A extends Effect.Effect<infer Success, any, any> ? Success : never
type StreamValue<A> = A extends Stream.Stream<infer Success, any, any> ? Success : never
${endpointTypes.join("\n\n")}
export interface AppApi<E = never> {
${clientFields.join("\n")}
}
`
}
function groupShapeName(group: Group) {
return `${identifierPart(group.identifier)}Api`
}
function groupShapeTypeName(group: Group, endpoint: Endpoint) {
return `${identifierPart(group.identifier)}${identifierPart(endpoint.operation.name)}Operation`
}
function assertPromiseEndpoint(endpoint: Endpoint) {
const name = `${endpoint.group}.${endpoint.endpoint.name}`
const payload = endpoint.payloads[0]