pull/700/head
Travis Fischer 2025-03-23 04:24:45 +08:00
rodzic f353dd55b3
commit fdedede6a1
5 zmienionych plików z 234717 dodań i 47 usunięć

Wyświetl plik

@ -19,6 +19,12 @@
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
## TODO
- [ ] Convert HTML in descriptions to markdown.
- [ ] Properly format multiline function comments.
- [ ] Debug stripe schema issue.
## License
MIT © [Travis Fischer](https://x.com/transitive_bs)

Wyświetl plik

@ -6,7 +6,6 @@ import { fileURLToPath } from 'node:url'
import type { IJsonSchema, OpenAPIV3 } from 'openapi-types'
import { assert } from '@agentic/core'
import SwaggerParser from '@apidevtools/swagger-parser'
import camelCase from 'camelcase'
import decamelize from 'decamelize'
import { execa } from 'execa'
@ -15,11 +14,13 @@ import {
dereference,
dereferenceFull,
getAndResolve,
getComponentDisplayName,
getComponentName,
getOperationParamsName,
getOperationResponseName,
jsonSchemaToZod,
naiveMergeJSONSchemas,
pascalCase,
prettify
} from './utils'
@ -62,7 +63,7 @@ async function main() {
openapiSpecName.toLowerCase() === openapiSpecName,
`OpenAPI spec name "${openapiSpecName}" must be in kebab case`
)
const name = camelCase(openapiSpecName, { pascalCase: true })
const name = pascalCase(openapiSpecName)
const nameLowerCase = name.toLowerCase()
const nameSnakeCase = decamelize(name)
const nameKebabCase = decamelize(name, { separator: '-' })
@ -214,7 +215,7 @@ async function main() {
operationParamsSources[key] = source
}
} else if (derefed?.anyOf || derefed?.oneOf) {
const componentName = getComponentName(schema.$ref)
const componentName = getComponentDisplayName(schema.$ref)
operationParamsSources[componentName] = source
// TODO: handle this case
@ -368,7 +369,7 @@ async function main() {
if (operationParamsJSONSchema.$refs.length) {
const refSchemas = operationParamsJSONSchema.$refs.map(
(ref) => `${getComponentName(ref)!}Schema`
(ref) => `${getComponentDisplayName(ref)!}Schema`
)
operationsParamsSchema = operationsParamsSchema.replace(
@ -394,7 +395,7 @@ async function main() {
let isDuplicateDefinition = false
if (operationResponseJSONSchema.$ref) {
const componentName = getComponentName(
const componentName = getComponentDisplayName(
operationResponseJSONSchema.$ref
)
if (componentName === operationResponseName) {
@ -475,12 +476,13 @@ async function main() {
if (description && !/[!.?]$/.test(description)) {
description += '.'
}
const isDescriptionMultiline = description?.includes('\n')
const aiClientMethod = `
${description ? `/**\n * ${description}\n */` : ''}
@aiFunction({
name: '${operationNameSnakeCase}',
${description ? `description: '${description}',` : ''}${hasUnionParams ? '\n// TODO: Improve handling of union params' : ''}
${description ? `description: ${isDescriptionMultiline ? `\`${description.replaceAll('`', '\\`')}\`` : `'${description}'`},` : ''}${hasUnionParams ? '\n// TODO: Improve handling of union params' : ''}
inputSchema: ${namespaceName}.${operationParamsName}Schema${hasUnionParams ? ' as any' : ''},
})
async ${operationName}(${!hasParams ? '_' : ''}params: ${namespaceName}.${operationParamsName}): Promise<${namespaceName}.${operationResponseName}> {
@ -527,7 +529,7 @@ async function main() {
)
for (const ref of sortedComponents) {
const type = getComponentName(ref)
const type = getComponentDisplayName(ref)
assert(type, `Invalid ref name ${ref}`)
const name = `${type}Schema`
@ -539,17 +541,17 @@ async function main() {
processedComponents.add(ref)
if (type === 'SearchResponse') {
console.log(type, dereferenced)
}
const schema = jsonSchemaToZod(dereferenced, { name, type })
componentSchemas[type] = schema
}
console.log(
'\ncomponents',
Array.from(sortedComponents).map((ref) => getComponentName(ref))
JSON.stringify(
sortedComponents.map((ref) => getComponentName(ref)),
null,
2
)
)
// console.log(
@ -585,33 +587,32 @@ import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'`.trim()
const commentLine = `// ${'-'.repeat(77)}`
const outputTypes = (
const outputTypes = [
header,
`export namespace ${namespaceName} {`,
apiBaseUrl ? `export const apiBaseUrl = '${apiBaseUrl}'` : undefined,
Object.values(componentSchemas).length
? `${commentLine}\n// Component schemas\n${commentLine}`
: undefined,
...Object.values(componentSchemas),
Object.values(operationSchemas).length
? `${commentLine}\n// Operation schemas\n${commentLine}`
: undefined,
...Object.values(operationSchemas),
'}'
]
.filter(Boolean)
.join('\n\n')
const output = (
await prettify(
[
header,
`export namespace ${namespaceName} {`,
apiBaseUrl ? `export const apiBaseUrl = '${apiBaseUrl}'` : undefined,
Object.values(componentSchemas).length
? `${commentLine}\n// Component schemas\n${commentLine}`
: undefined,
...Object.values(componentSchemas),
Object.values(operationSchemas).length
? `${commentLine}\n// Operation schemas\n${commentLine}`
: undefined,
...Object.values(operationSchemas),
'}'
]
.filter(Boolean)
.join('\n\n')
)
)
.replaceAll(/z\s*\.object\({}\)\s*\.merge\(([^)]*)\)/gm, '$1')
.replaceAll(/\/\*\*(\S.*)\*\//g, '/** $1 */')
outputTypes,
`
const output = await prettify(
[
outputTypes,
`
/**
* Agentic client for ${name}.${spec.info?.description ? `\n * ${spec.info.description}` : ''}
*/
export class ${clientName} extends AIFunctionsProvider {
protected readonly ky: KyInstance
${hasGlobalApiKeyInHeader ? 'protected readonly apiKey: string' : ''}
@ -651,12 +652,15 @@ export class ${clientName} extends AIFunctionsProvider {
})
}
`,
aiClientMethodsString,
'}'
].join('\n\n')
aiClientMethodsString,
'}'
].join('\n\n')
)
)
.replaceAll(/z\s*\.object\({}\)\s*\.merge\(([^)]*)\)/gm, '$1')
.replaceAll(/\/\*\*(\S.*)\*\//g, '/** $1 */')
// console.log(output)
console.log(output)
await fs.mkdir(destFolder, { recursive: true })
await fs.writeFile(destFileClient, output)
await execa('npx', ['eslint', '--fix', destFileClient])

Wyświetl plik

@ -1,6 +1,7 @@
import type SwaggerParser from '@apidevtools/swagger-parser'
import type { IJsonSchema, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
import { assert } from '@agentic/core'
import camelcase from 'camelcase'
import {
type JsonSchema,
jsonSchemaToZod as jsonSchemaToZodImpl,
@ -21,8 +22,8 @@ export function prettify(source: string): Promise<string> {
})
}
export function titleCase(identifier: string): string {
return `${identifier.slice(0, 1).toUpperCase()}${identifier.slice(1)}`
export function pascalCase(identifier: string): string {
return camelcase(identifier, { pascalCase: true })
}
export function unTitleCase(identifier: string): string {
@ -240,10 +241,15 @@ const reservedWords = new Set([
])
export function getComponentName(ref: string) {
const name0 = ref.split('/').pop()!
assert(name0, `Invalid ref name ${ref}`)
const name = ref.split('/').pop()!
assert(name, `Invalid ref name ${ref}`)
const name1 = titleCase(name0)
return name
}
export function getComponentDisplayName(ref: string) {
const name0 = getComponentName(ref)
const name1 = pascalCase(name0)
assert(name1, `Invalid ref name ${ref}`)
if (reservedWords.has(name1)) {
@ -257,7 +263,7 @@ export function getOperationParamsName(
operationName: string,
schemas?: Record<string, string>
) {
const name = `${titleCase(operationName)}Params`
const name = `${pascalCase(operationName)}Params`
if (!schemas) return name
let tempName = name
@ -274,7 +280,7 @@ export function getOperationResponseName(
operationName: string,
schemas?: Record<string, string>
) {
const name = `${titleCase(operationName)}Response`
const name = `${pascalCase(operationName)}Response`
if (!schemas) return name
let tempName = name