kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
old-agentic
rodzic
354938db29
commit
047af13b15
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
|
@ -19,6 +19,12 @@
|
||||||
|
|
||||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
**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
|
## License
|
||||||
|
|
||||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { fileURLToPath } from 'node:url'
|
||||||
import type { IJsonSchema, OpenAPIV3 } from 'openapi-types'
|
import type { IJsonSchema, OpenAPIV3 } from 'openapi-types'
|
||||||
import { assert } from '@agentic/core'
|
import { assert } from '@agentic/core'
|
||||||
import SwaggerParser from '@apidevtools/swagger-parser'
|
import SwaggerParser from '@apidevtools/swagger-parser'
|
||||||
import camelCase from 'camelcase'
|
|
||||||
import decamelize from 'decamelize'
|
import decamelize from 'decamelize'
|
||||||
import { execa } from 'execa'
|
import { execa } from 'execa'
|
||||||
|
|
||||||
|
@ -15,11 +14,13 @@ import {
|
||||||
dereference,
|
dereference,
|
||||||
dereferenceFull,
|
dereferenceFull,
|
||||||
getAndResolve,
|
getAndResolve,
|
||||||
|
getComponentDisplayName,
|
||||||
getComponentName,
|
getComponentName,
|
||||||
getOperationParamsName,
|
getOperationParamsName,
|
||||||
getOperationResponseName,
|
getOperationResponseName,
|
||||||
jsonSchemaToZod,
|
jsonSchemaToZod,
|
||||||
naiveMergeJSONSchemas,
|
naiveMergeJSONSchemas,
|
||||||
|
pascalCase,
|
||||||
prettify
|
prettify
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ async function main() {
|
||||||
openapiSpecName.toLowerCase() === openapiSpecName,
|
openapiSpecName.toLowerCase() === openapiSpecName,
|
||||||
`OpenAPI spec name "${openapiSpecName}" must be in kebab case`
|
`OpenAPI spec name "${openapiSpecName}" must be in kebab case`
|
||||||
)
|
)
|
||||||
const name = camelCase(openapiSpecName, { pascalCase: true })
|
const name = pascalCase(openapiSpecName)
|
||||||
const nameLowerCase = name.toLowerCase()
|
const nameLowerCase = name.toLowerCase()
|
||||||
const nameSnakeCase = decamelize(name)
|
const nameSnakeCase = decamelize(name)
|
||||||
const nameKebabCase = decamelize(name, { separator: '-' })
|
const nameKebabCase = decamelize(name, { separator: '-' })
|
||||||
|
@ -214,7 +215,7 @@ async function main() {
|
||||||
operationParamsSources[key] = source
|
operationParamsSources[key] = source
|
||||||
}
|
}
|
||||||
} else if (derefed?.anyOf || derefed?.oneOf) {
|
} else if (derefed?.anyOf || derefed?.oneOf) {
|
||||||
const componentName = getComponentName(schema.$ref)
|
const componentName = getComponentDisplayName(schema.$ref)
|
||||||
operationParamsSources[componentName] = source
|
operationParamsSources[componentName] = source
|
||||||
|
|
||||||
// TODO: handle this case
|
// TODO: handle this case
|
||||||
|
@ -368,7 +369,7 @@ async function main() {
|
||||||
|
|
||||||
if (operationParamsJSONSchema.$refs.length) {
|
if (operationParamsJSONSchema.$refs.length) {
|
||||||
const refSchemas = operationParamsJSONSchema.$refs.map(
|
const refSchemas = operationParamsJSONSchema.$refs.map(
|
||||||
(ref) => `${getComponentName(ref)!}Schema`
|
(ref) => `${getComponentDisplayName(ref)!}Schema`
|
||||||
)
|
)
|
||||||
|
|
||||||
operationsParamsSchema = operationsParamsSchema.replace(
|
operationsParamsSchema = operationsParamsSchema.replace(
|
||||||
|
@ -394,7 +395,7 @@ async function main() {
|
||||||
let isDuplicateDefinition = false
|
let isDuplicateDefinition = false
|
||||||
|
|
||||||
if (operationResponseJSONSchema.$ref) {
|
if (operationResponseJSONSchema.$ref) {
|
||||||
const componentName = getComponentName(
|
const componentName = getComponentDisplayName(
|
||||||
operationResponseJSONSchema.$ref
|
operationResponseJSONSchema.$ref
|
||||||
)
|
)
|
||||||
if (componentName === operationResponseName) {
|
if (componentName === operationResponseName) {
|
||||||
|
@ -475,12 +476,13 @@ async function main() {
|
||||||
if (description && !/[!.?]$/.test(description)) {
|
if (description && !/[!.?]$/.test(description)) {
|
||||||
description += '.'
|
description += '.'
|
||||||
}
|
}
|
||||||
|
const isDescriptionMultiline = description?.includes('\n')
|
||||||
|
|
||||||
const aiClientMethod = `
|
const aiClientMethod = `
|
||||||
${description ? `/**\n * ${description}\n */` : ''}
|
${description ? `/**\n * ${description}\n */` : ''}
|
||||||
@aiFunction({
|
@aiFunction({
|
||||||
name: '${operationNameSnakeCase}',
|
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' : ''},
|
inputSchema: ${namespaceName}.${operationParamsName}Schema${hasUnionParams ? ' as any' : ''},
|
||||||
})
|
})
|
||||||
async ${operationName}(${!hasParams ? '_' : ''}params: ${namespaceName}.${operationParamsName}): Promise<${namespaceName}.${operationResponseName}> {
|
async ${operationName}(${!hasParams ? '_' : ''}params: ${namespaceName}.${operationParamsName}): Promise<${namespaceName}.${operationResponseName}> {
|
||||||
|
@ -527,7 +529,7 @@ async function main() {
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const ref of sortedComponents) {
|
for (const ref of sortedComponents) {
|
||||||
const type = getComponentName(ref)
|
const type = getComponentDisplayName(ref)
|
||||||
assert(type, `Invalid ref name ${ref}`)
|
assert(type, `Invalid ref name ${ref}`)
|
||||||
|
|
||||||
const name = `${type}Schema`
|
const name = `${type}Schema`
|
||||||
|
@ -539,17 +541,17 @@ async function main() {
|
||||||
|
|
||||||
processedComponents.add(ref)
|
processedComponents.add(ref)
|
||||||
|
|
||||||
if (type === 'SearchResponse') {
|
|
||||||
console.log(type, dereferenced)
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = jsonSchemaToZod(dereferenced, { name, type })
|
const schema = jsonSchemaToZod(dereferenced, { name, type })
|
||||||
componentSchemas[type] = schema
|
componentSchemas[type] = schema
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
'\ncomponents',
|
'\ncomponents',
|
||||||
Array.from(sortedComponents).map((ref) => getComponentName(ref))
|
JSON.stringify(
|
||||||
|
sortedComponents.map((ref) => getComponentName(ref)),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// console.log(
|
// console.log(
|
||||||
|
@ -585,33 +587,32 @@ import defaultKy, { type KyInstance } from 'ky'
|
||||||
import { z } from 'zod'`.trim()
|
import { z } from 'zod'`.trim()
|
||||||
|
|
||||||
const commentLine = `// ${'-'.repeat(77)}`
|
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(
|
await prettify(
|
||||||
[
|
[
|
||||||
header,
|
outputTypes,
|
||||||
`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 */')
|
|
||||||
|
|
||||||
const output = await prettify(
|
/**
|
||||||
[
|
* Agentic client for ${name}.${spec.info?.description ? `\n * ${spec.info.description}` : ''}
|
||||||
outputTypes,
|
*/
|
||||||
`
|
|
||||||
export class ${clientName} extends AIFunctionsProvider {
|
export class ${clientName} extends AIFunctionsProvider {
|
||||||
protected readonly ky: KyInstance
|
protected readonly ky: KyInstance
|
||||||
${hasGlobalApiKeyInHeader ? 'protected readonly apiKey: string' : ''}
|
${hasGlobalApiKeyInHeader ? 'protected readonly apiKey: string' : ''}
|
||||||
|
@ -651,12 +652,15 @@ export class ${clientName} extends AIFunctionsProvider {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
aiClientMethodsString,
|
aiClientMethodsString,
|
||||||
'}'
|
'}'
|
||||||
].join('\n\n')
|
].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.mkdir(destFolder, { recursive: true })
|
||||||
await fs.writeFile(destFileClient, output)
|
await fs.writeFile(destFileClient, output)
|
||||||
await execa('npx', ['eslint', '--fix', destFileClient])
|
await execa('npx', ['eslint', '--fix', destFileClient])
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type SwaggerParser from '@apidevtools/swagger-parser'
|
import type SwaggerParser from '@apidevtools/swagger-parser'
|
||||||
import type { IJsonSchema, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
|
import type { IJsonSchema, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
|
||||||
import { assert } from '@agentic/core'
|
import { assert } from '@agentic/core'
|
||||||
|
import camelcase from 'camelcase'
|
||||||
import {
|
import {
|
||||||
type JsonSchema,
|
type JsonSchema,
|
||||||
jsonSchemaToZod as jsonSchemaToZodImpl,
|
jsonSchemaToZod as jsonSchemaToZodImpl,
|
||||||
|
@ -21,8 +22,8 @@ export function prettify(source: string): Promise<string> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function titleCase(identifier: string): string {
|
export function pascalCase(identifier: string): string {
|
||||||
return `${identifier.slice(0, 1).toUpperCase()}${identifier.slice(1)}`
|
return camelcase(identifier, { pascalCase: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unTitleCase(identifier: string): string {
|
export function unTitleCase(identifier: string): string {
|
||||||
|
@ -240,10 +241,15 @@ const reservedWords = new Set([
|
||||||
])
|
])
|
||||||
|
|
||||||
export function getComponentName(ref: string) {
|
export function getComponentName(ref: string) {
|
||||||
const name0 = ref.split('/').pop()!
|
const name = ref.split('/').pop()!
|
||||||
assert(name0, `Invalid ref name ${ref}`)
|
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}`)
|
assert(name1, `Invalid ref name ${ref}`)
|
||||||
|
|
||||||
if (reservedWords.has(name1)) {
|
if (reservedWords.has(name1)) {
|
||||||
|
@ -257,7 +263,7 @@ export function getOperationParamsName(
|
||||||
operationName: string,
|
operationName: string,
|
||||||
schemas?: Record<string, string>
|
schemas?: Record<string, string>
|
||||||
) {
|
) {
|
||||||
const name = `${titleCase(operationName)}Params`
|
const name = `${pascalCase(operationName)}Params`
|
||||||
if (!schemas) return name
|
if (!schemas) return name
|
||||||
|
|
||||||
let tempName = name
|
let tempName = name
|
||||||
|
@ -274,7 +280,7 @@ export function getOperationResponseName(
|
||||||
operationName: string,
|
operationName: string,
|
||||||
schemas?: Record<string, string>
|
schemas?: Record<string, string>
|
||||||
) {
|
) {
|
||||||
const name = `${titleCase(operationName)}Response`
|
const name = `${pascalCase(operationName)}Response`
|
||||||
if (!schemas) return name
|
if (!schemas) return name
|
||||||
|
|
||||||
let tempName = name
|
let tempName = name
|
||||||
|
|
Ładowanie…
Reference in New Issue