feat: WIP add openapi-to-ts-client generator to automate the creation of agentic TS clients

pull/700/head
Travis Fischer 2025-03-23 01:31:50 +08:00
rodzic 23221d3bf4
commit c2ef2271d8
15 zmienionych plików z 7985 dodań i 2 usunięć

35
.vscode/launch.json vendored 100644
Wyświetl plik

@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "tsx",
"type": "node",
"request": "launch",
// Debug current file in VSCode
"program": "${file}",
/*
* Path to tsx binary
* Assuming locally installed
*/
"runtimeExecutable": "tsx",
/*
* Open terminal when debugging starts (Optional)
* Useful to see console.logs
*/
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
// Files to exclude from debugger (e.g. call stack)
"skipFiles": [
// Node.js internal core modules
"<node_internals>/**",
// Ignore all dependencies (optional)
"${workspaceFolder}/node_modules/**"
]
}
]
}

Wyświetl plik

@ -0,0 +1,603 @@
/**
* This file was auto-generated from an OpenAPI spec.
*/
import { aiFunction, AIFunctionsProvider, assert, getEnv } from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'
export namespace firecrawl {
export const apiBaseUrl = 'https://api.firecrawl.dev/v0'
export const ScrapeResponseSchema = z.object({
success: z.boolean().optional(),
/** Warning message to let you know of any issues. */
warning: z
.string()
.nullable()
.describe('Warning message to let you know of any issues.')
.optional(),
data: z
.object({
/** Markdown content of the page if the `markdown` format was specified (default) */
markdown: z
.string()
.nullable()
.describe(
'Markdown content of the page if the `markdown` format was specified (default)'
)
.optional(),
/** HTML version of the content on page if the `html` format was specified */
html: z
.string()
.nullable()
.describe(
'HTML version of the content on page if the `html` format was specified'
)
.optional(),
/** Raw HTML content of the page if the `rawHtml` format was specified */
rawHtml: z
.string()
.nullable()
.describe(
'Raw HTML content of the page if the `rawHtml` format was specified'
)
.optional(),
/** Links on the page if the `links` format was specified */
links: z
.array(z.string().url())
.nullable()
.describe('Links on the page if the `links` format was specified')
.optional(),
/** URL of the screenshot of the page if the `screenshot` or `screenshot@fullSize` format was specified */
screenshot: z
.string()
.nullable()
.describe(
'URL of the screenshot of the page if the `screenshot` or `screenshot@fullSize` format was specified'
)
.optional(),
metadata: z
.object({
title: z.string().optional(),
description: z.string().optional(),
language: z.string().nullable().optional(),
sourceURL: z.string().url().optional(),
'<any other metadata> ': z.string().optional(),
/** The status code of the page */
statusCode: z
.number()
.int()
.describe('The status code of the page')
.optional(),
/** The error message of the page */
error: z
.string()
.nullable()
.describe('The error message of the page')
.optional()
})
.optional()
})
.optional()
})
export type ScrapeResponse = z.infer<typeof ScrapeResponseSchema>
export const CrawlResponseSchema = z.object({
success: z.boolean().optional(),
id: z.string().optional(),
url: z.string().url().optional()
})
export type CrawlResponse = z.infer<typeof CrawlResponseSchema>
export const SearchResponseSchema = z.object({
success: z.boolean().optional(),
data: z.array(z.any()).optional()
})
export type SearchResponse = z.infer<typeof SearchResponseSchema>
export const CrawlStatusResponseObjSchema = z.object({
/** Markdown content of the page if the `markdown` format was specified (default) */
markdown: z
.string()
.nullable()
.describe(
'Markdown content of the page if the `markdown` format was specified (default)'
)
.optional(),
/** HTML version of the content on page if the `html` format was specified */
html: z
.string()
.nullable()
.describe(
'HTML version of the content on page if the `html` format was specified'
)
.optional(),
/** Raw HTML content of the page if the `rawHtml` format was specified */
rawHtml: z
.string()
.nullable()
.describe(
'Raw HTML content of the page if the `rawHtml` format was specified'
)
.optional(),
/** Links on the page if the `links` format was specified */
links: z
.array(z.string().url())
.nullable()
.describe('Links on the page if the `links` format was specified')
.optional(),
/** URL of the screenshot of the page if the `screenshot` or `screenshot@fullSize` format was specified */
screenshot: z
.string()
.nullable()
.describe(
'URL of the screenshot of the page if the `screenshot` or `screenshot@fullSize` format was specified'
)
.optional(),
metadata: z
.object({
title: z.string().optional(),
description: z.string().optional(),
language: z.string().nullable().optional(),
sourceURL: z.string().url().optional(),
'<any other metadata> ': z.string().optional(),
/** The status code of the page */
statusCode: z
.number()
.int()
.describe('The status code of the page')
.optional(),
/** The error message of the page */
error: z
.string()
.nullable()
.describe('The error message of the page')
.optional()
})
.optional()
})
export type CrawlStatusResponseObj = z.infer<
typeof CrawlStatusResponseObjSchema
>
export const ScrapeParamsSchema = z.object({
/** The URL to scrape */
url: z.string().url().describe('The URL to scrape'),
/**
* Specific formats to return.
*
* - markdown: The page in Markdown format.
* - html: The page's HTML, trimmed to include only meaningful content.
* - rawHtml: The page's original HTML.
* - links: The links on the page.
* - screenshot: A screenshot of the top of the page.
* - screenshot@fullPage: A screenshot of the full page. (overridden by screenshot if present)
*/
formats: z
.array(
z.enum([
'markdown',
'html',
'rawHtml',
'links',
'screenshot',
'screenshot@fullPage'
])
)
.describe(
"Specific formats to return.\n\n - markdown: The page in Markdown format.\n - html: The page's HTML, trimmed to include only meaningful content.\n - rawHtml: The page's original HTML.\n - links: The links on the page.\n - screenshot: A screenshot of the top of the page.\n - screenshot@fullPage: A screenshot of the full page. (overridden by screenshot if present)"
)
.default(['markdown']),
/** Headers to send with the request. Can be used to send cookies, user-agent, etc. */
headers: z
.record(z.any())
.describe(
'Headers to send with the request. Can be used to send cookies, user-agent, etc.'
)
.optional(),
/** Only include tags, classes and ids from the page in the final output. Use comma separated values. Example: 'script, .ad, #footer' */
includeTags: z
.array(z.string())
.describe(
"Only include tags, classes and ids from the page in the final output. Use comma separated values. Example: 'script, .ad, #footer'"
)
.optional(),
/** Tags, classes and ids to remove from the page. Use comma separated values. Example: 'script, .ad, #footer' */
excludeTags: z
.array(z.string())
.describe(
"Tags, classes and ids to remove from the page. Use comma separated values. Example: 'script, .ad, #footer'"
)
.optional(),
/** Only return the main content of the page excluding headers, navs, footers, etc. */
onlyMainContent: z
.boolean()
.describe(
'Only return the main content of the page excluding headers, navs, footers, etc.'
)
.default(true),
/** Timeout in milliseconds for the request */
timeout: z
.number()
.int()
.describe('Timeout in milliseconds for the request')
.default(30_000),
/** Wait x amount of milliseconds for the page to load to fetch content */
waitFor: z
.number()
.int()
.describe(
'Wait x amount of milliseconds for the page to load to fetch content'
)
.default(0)
})
export type ScrapeParams = z.infer<typeof ScrapeParamsSchema>
export const CrawlUrlsParamsSchema = z.object({
/** The base URL to start crawling from */
url: z.string().url().describe('The base URL to start crawling from'),
crawlerOptions: z
.object({
/** URL patterns to include */
includes: z
.array(z.string())
.describe('URL patterns to include')
.optional(),
/** URL patterns to exclude */
excludes: z
.array(z.string())
.describe('URL patterns to exclude')
.optional(),
/** Generate alt text for images using LLMs (must have a paid plan) */
generateImgAltText: z
.boolean()
.describe(
'Generate alt text for images using LLMs (must have a paid plan)'
)
.default(false),
/** If true, returns only the URLs as a list on the crawl status. Attention: the return response will be a list of URLs inside the data, not a list of documents. */
returnOnlyUrls: z
.boolean()
.describe(
'If true, returns only the URLs as a list on the crawl status. Attention: the return response will be a list of URLs inside the data, not a list of documents.'
)
.default(false),
/** Maximum depth to crawl relative to the entered URL. A maxDepth of 0 scrapes only the entered URL. A maxDepth of 1 scrapes the entered URL and all pages one level deep. A maxDepth of 2 scrapes the entered URL and all pages up to two levels deep. Higher values follow the same pattern. */
maxDepth: z
.number()
.int()
.describe(
'Maximum depth to crawl relative to the entered URL. A maxDepth of 0 scrapes only the entered URL. A maxDepth of 1 scrapes the entered URL and all pages one level deep. A maxDepth of 2 scrapes the entered URL and all pages up to two levels deep. Higher values follow the same pattern.'
)
.optional(),
/** The crawling mode to use. Fast mode crawls 4x faster websites without sitemap, but may not be as accurate and shouldn't be used in heavy js-rendered websites. */
mode: z
.enum(['default', 'fast'])
.describe(
"The crawling mode to use. Fast mode crawls 4x faster websites without sitemap, but may not be as accurate and shouldn't be used in heavy js-rendered websites."
)
.default('default'),
/** Ignore the website sitemap when crawling */
ignoreSitemap: z
.boolean()
.describe('Ignore the website sitemap when crawling')
.default(false),
/** Maximum number of pages to crawl */
limit: z
.number()
.int()
.describe('Maximum number of pages to crawl')
.default(10_000),
/** Enables the crawler to navigate from a specific URL to previously linked pages. For instance, from 'example.com/product/123' back to 'example.com/product' */
allowBackwardCrawling: z
.boolean()
.describe(
"Enables the crawler to navigate from a specific URL to previously linked pages. For instance, from 'example.com/product/123' back to 'example.com/product'"
)
.default(false),
/** Allows the crawler to follow links to external websites. */
allowExternalContentLinks: z
.boolean()
.describe('Allows the crawler to follow links to external websites.')
.default(false)
})
.optional(),
pageOptions: z
.object({
/** Headers to send with the request. Can be used to send cookies, user-agent, etc. */
headers: z
.record(z.any())
.describe(
'Headers to send with the request. Can be used to send cookies, user-agent, etc.'
)
.optional(),
/** Include the HTML version of the content on page. Will output a html key in the response. */
includeHtml: z
.boolean()
.describe(
'Include the HTML version of the content on page. Will output a html key in the response.'
)
.default(false),
/** Include the raw HTML content of the page. Will output a rawHtml key in the response. */
includeRawHtml: z
.boolean()
.describe(
'Include the raw HTML content of the page. Will output a rawHtml key in the response.'
)
.default(false),
/** Only include tags, classes and ids from the page in the final output. Use comma separated values. Example: 'script, .ad, #footer' */
onlyIncludeTags: z
.array(z.string())
.describe(
"Only include tags, classes and ids from the page in the final output. Use comma separated values. Example: 'script, .ad, #footer'"
)
.optional(),
/** Only return the main content of the page excluding headers, navs, footers, etc. */
onlyMainContent: z
.boolean()
.describe(
'Only return the main content of the page excluding headers, navs, footers, etc.'
)
.default(false),
/** Tags, classes and ids to remove from the page. Use comma separated values. Example: 'script, .ad, #footer' */
removeTags: z
.array(z.string())
.describe(
"Tags, classes and ids to remove from the page. Use comma separated values. Example: 'script, .ad, #footer'"
)
.optional(),
/** Replace all relative paths with absolute paths for images and links */
replaceAllPathsWithAbsolutePaths: z
.boolean()
.describe(
'Replace all relative paths with absolute paths for images and links'
)
.default(false),
/** Include a screenshot of the top of the page that you are scraping. */
screenshot: z
.boolean()
.describe(
'Include a screenshot of the top of the page that you are scraping.'
)
.default(false),
/** Include a full page screenshot of the page that you are scraping. */
fullPageScreenshot: z
.boolean()
.describe(
'Include a full page screenshot of the page that you are scraping.'
)
.default(false),
/** Wait x amount of milliseconds for the page to load to fetch content */
waitFor: z
.number()
.int()
.describe(
'Wait x amount of milliseconds for the page to load to fetch content'
)
.default(0)
})
.optional()
})
export type CrawlUrlsParams = z.infer<typeof CrawlUrlsParamsSchema>
export const CrawlUrlsResponseSchema = CrawlResponseSchema
export type CrawlUrlsResponse = z.infer<typeof CrawlUrlsResponseSchema>
export const SearchGoogleParamsSchema = z.object({
/** The query to search for */
query: z.string().url().describe('The query to search for'),
pageOptions: z
.object({
/** Only return the main content of the page excluding headers, navs, footers, etc. */
onlyMainContent: z
.boolean()
.describe(
'Only return the main content of the page excluding headers, navs, footers, etc.'
)
.default(false),
/** Fetch the content of each page. If false, defaults to a basic fast serp API. */
fetchPageContent: z
.boolean()
.describe(
'Fetch the content of each page. If false, defaults to a basic fast serp API.'
)
.default(true),
/** Include the HTML version of the content on page. Will output a html key in the response. */
includeHtml: z
.boolean()
.describe(
'Include the HTML version of the content on page. Will output a html key in the response.'
)
.default(false),
/** Include the raw HTML content of the page. Will output a rawHtml key in the response. */
includeRawHtml: z
.boolean()
.describe(
'Include the raw HTML content of the page. Will output a rawHtml key in the response.'
)
.default(false)
})
.optional(),
searchOptions: z
.object({
/** Maximum number of results. Max is 20 during beta. */
limit: z
.number()
.int()
.describe('Maximum number of results. Max is 20 during beta.')
.optional()
})
.optional()
})
export type SearchGoogleParams = z.infer<typeof SearchGoogleParamsSchema>
export const SearchGoogleResponseSchema = SearchResponseSchema
export type SearchGoogleResponse = z.infer<typeof SearchGoogleResponseSchema>
export const GetCrawlStatusParamsSchema = z.object({
/** ID of the crawl job */
jobId: z.string().describe('ID of the crawl job')
})
export type GetCrawlStatusParams = z.infer<typeof GetCrawlStatusParamsSchema>
export const GetCrawlStatusResponseSchema = z.object({
/** Status of the job (completed, active, failed, paused) */
status: z
.string()
.describe('Status of the job (completed, active, failed, paused)')
.optional(),
/** Current page number */
current: z.number().int().describe('Current page number').optional(),
/** Total number of pages */
total: z.number().int().describe('Total number of pages').optional(),
/** Data returned from the job (null when it is in progress) */
data: z
.array(CrawlStatusResponseObjSchema)
.describe('Data returned from the job (null when it is in progress)')
.optional(),
/** Partial documents returned as it is being crawled (streaming). **This feature is currently in alpha - expect breaking changes** When a page is ready, it will append to the partial_data array, so there is no need to wait for the entire website to be crawled. When the crawl is done, partial_data will become empty and the result will be available in `data`. There is a max of 50 items in the array response. The oldest item (top of the array) will be removed when the new item is added to the array. */
partial_data: z
.array(CrawlStatusResponseObjSchema)
.describe(
'Partial documents returned as it is being crawled (streaming). **This feature is currently in alpha - expect breaking changes** When a page is ready, it will append to the partial_data array, so there is no need to wait for the entire website to be crawled. When the crawl is done, partial_data will become empty and the result will be available in `data`. There is a max of 50 items in the array response. The oldest item (top of the array) will be removed when the new item is added to the array.'
)
.optional()
})
export type GetCrawlStatusResponse = z.infer<
typeof GetCrawlStatusResponseSchema
>
export const CancelCrawlJobParamsSchema = z.object({
/** ID of the crawl job */
jobId: z.string().describe('ID of the crawl job')
})
export type CancelCrawlJobParams = z.infer<typeof CancelCrawlJobParamsSchema>
export const CancelCrawlJobResponseSchema = z.object({
/** Returns cancelled. */
status: z.string().describe('Returns cancelled.').optional()
})
export type CancelCrawlJobResponse = z.infer<
typeof CancelCrawlJobResponseSchema
>
}
export class FirecrawlClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly apiKey: string
protected readonly apiBaseUrl: string
constructor({
apiKey = getEnv('FIRECRAWL_API_KEY'),
apiBaseUrl = firecrawl.apiBaseUrl,
ky = defaultKy
}: {
apiKey?: string
apiBaseUrl?: string
ky?: KyInstance
} = {}) {
assert(
apiKey,
'FirecrawlClient missing required "apiKey" (defaults to "FIRECRAWL_API_KEY")'
)
super()
this.apiKey = apiKey
this.apiBaseUrl = apiBaseUrl
this.ky = ky.extend({
prefixUrl: apiBaseUrl,
headers: {
Authorization: apiKey
}
})
}
/**
* Scrape a single URL.
*/
@aiFunction({
name: 'scrape',
description: 'Scrape a single URL.',
inputSchema: firecrawl.ScrapeParamsSchema
})
async scrape(
params: firecrawl.ScrapeParams
): Promise<firecrawl.ScrapeResponse> {
return this.ky
.post('/scrape', {
json: params
})
.json<firecrawl.ScrapeResponse>()
}
/**
* Crawl multiple URLs based on options.
*/
@aiFunction({
name: 'crawl_urls',
description: 'Crawl multiple URLs based on options.',
inputSchema: firecrawl.CrawlUrlsParamsSchema
})
async crawlUrls(
params: firecrawl.CrawlUrlsParams
): Promise<firecrawl.CrawlUrlsResponse> {
return this.ky
.post('/crawl', {
json: params
})
.json<firecrawl.CrawlUrlsResponse>()
}
/**
* Search for a keyword in Google, returns top page results with markdown content for each page.
*/
@aiFunction({
name: 'search_google',
description:
'Search for a keyword in Google, returns top page results with markdown content for each page.',
inputSchema: firecrawl.SearchGoogleParamsSchema
})
async searchGoogle(
params: firecrawl.SearchGoogleParams
): Promise<firecrawl.SearchGoogleResponse> {
return this.ky
.post('/search', {
json: params
})
.json<firecrawl.SearchGoogleResponse>()
}
/**
* Get the status of a crawl job.
*/
@aiFunction({
name: 'get_crawl_status',
description: 'Get the status of a crawl job.',
inputSchema: firecrawl.GetCrawlStatusParamsSchema
})
async getCrawlStatus(
params: firecrawl.GetCrawlStatusParams
): Promise<firecrawl.GetCrawlStatusResponse> {
return this.ky
.get(`/crawl/status/${params.jobId}`)
.json<firecrawl.GetCrawlStatusResponse>()
}
/**
* Cancel a crawl job.
*/
@aiFunction({
name: 'cancel_crawl_job',
description: 'Cancel a crawl job.',
inputSchema: firecrawl.CancelCrawlJobParamsSchema
})
async cancelCrawlJob(
params: firecrawl.CancelCrawlJobParams
): Promise<firecrawl.CancelCrawlJobResponse> {
return this.ky
.delete(`/crawl/cancel/${params.jobId}`)
.json<firecrawl.CancelCrawlJobResponse>()
}
}

Wyświetl plik

@ -0,0 +1,128 @@
/**
* This file was auto-generated from an OpenAPI spec.
*/
import {
aiFunction,
AIFunctionsProvider,
sanitizeSearchParams
} from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'
export namespace petstore {
export const apiBaseUrl = 'http://petstore.swagger.io/v1'
export const PetSchema = z.object({
id: z.number().int(),
name: z.string(),
tag: z.string().optional()
})
export type Pet = z.infer<typeof PetSchema>
export const PetsSchema = z.array(PetSchema).max(100)
export type Pets = z.infer<typeof PetsSchema>
export const ListPetsParamsSchema = z.object({
/** How many items to return at one time (max 100) */
limit: z
.number()
.int()
.lte(100)
.describe('How many items to return at one time (max 100)')
.optional()
})
export type ListPetsParams = z.infer<typeof ListPetsParamsSchema>
export const ListPetsResponseSchema = PetsSchema
export type ListPetsResponse = z.infer<typeof ListPetsResponseSchema>
export const CreatePetsParamsSchema = PetSchema
export type CreatePetsParams = z.infer<typeof CreatePetsParamsSchema>
export type CreatePetsResponse = undefined
export const ShowPetByIdParamsSchema = z.object({
/** The id of the pet to retrieve */
petId: z.string().describe('The id of the pet to retrieve')
})
export type ShowPetByIdParams = z.infer<typeof ShowPetByIdParamsSchema>
export const ShowPetByIdResponseSchema = PetSchema
export type ShowPetByIdResponse = z.infer<typeof ShowPetByIdResponseSchema>
}
export class PetStoreClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly apiBaseUrl: string
constructor({
apiBaseUrl = petstore.apiBaseUrl,
ky = defaultKy
}: {
apiKey?: string
apiBaseUrl?: string
ky?: KyInstance
} = {}) {
super()
this.apiBaseUrl = apiBaseUrl
this.ky = ky.extend({
prefixUrl: apiBaseUrl
})
}
/**
* List all pets.
*/
@aiFunction({
name: 'list_pets',
description: 'List all pets.',
inputSchema: petstore.ListPetsParamsSchema
})
async listPets(
params: petstore.ListPetsParams
): Promise<petstore.ListPetsResponse> {
return this.ky
.get('/pets', {
searchParams: sanitizeSearchParams(params)
})
.json<petstore.ListPetsResponse>()
}
/**
* Create a pet.
*/
@aiFunction({
name: 'create_pets',
description: 'Create a pet.',
inputSchema: petstore.CreatePetsParamsSchema
})
async createPets(
params: petstore.CreatePetsParams
): Promise<petstore.CreatePetsResponse> {
return this.ky
.post('/pets', {
json: params
})
.json<petstore.CreatePetsResponse>()
}
/**
* Info for a specific pet.
*/
@aiFunction({
name: 'show_pet_by_id',
description: 'Info for a specific pet.',
inputSchema: petstore.ShowPetByIdParamsSchema
})
async showPetById(
params: petstore.ShowPetByIdParams
): Promise<petstore.ShowPetByIdResponse> {
return this.ky
.get(`/pets/${params.petId}`)
.json<petstore.ShowPetByIdResponse>()
}
}

Wyświetl plik

@ -0,0 +1,936 @@
{
"openapi": "3.0.0",
"info": {
"title": "Firecrawl API",
"version": "1.0.0",
"description": "API for interacting with Firecrawl services to perform web scraping and crawling tasks.",
"contact": {
"name": "Firecrawl Support",
"url": "https://firecrawl.dev/support",
"email": "support@firecrawl.dev"
}
},
"servers": [
{
"url": "https://api.firecrawl.dev/v0"
}
],
"paths": {
"/scrape": {
"post": {
"summary": "Scrape a single URL",
"operationId": "scrape",
"tags": ["Scraping"],
"security": [
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "The URL to scrape"
},
"formats": {
"type": "array",
"items": {
"type": "string",
"enum": [
"markdown",
"html",
"rawHtml",
"links",
"screenshot",
"screenshot@fullPage"
]
},
"description": "Specific formats to return.\n\n - markdown: The page in Markdown format.\n - html: The page's HTML, trimmed to include only meaningful content.\n - rawHtml: The page's original HTML.\n - links: The links on the page.\n - screenshot: A screenshot of the top of the page.\n - screenshot@fullPage: A screenshot of the full page. (overridden by screenshot if present)",
"default": ["markdown"]
},
"headers": {
"type": "object",
"description": "Headers to send with the request. Can be used to send cookies, user-agent, etc."
},
"includeTags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Only include tags, classes and ids from the page in the final output. Use comma separated values. Example: 'script, .ad, #footer'"
},
"excludeTags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags, classes and ids to remove from the page. Use comma separated values. Example: 'script, .ad, #footer'"
},
"onlyMainContent": {
"type": "boolean",
"description": "Only return the main content of the page excluding headers, navs, footers, etc.",
"default": true
},
"timeout": {
"type": "integer",
"description": "Timeout in milliseconds for the request",
"default": 30000
},
"waitFor": {
"type": "integer",
"description": "Wait x amount of milliseconds for the page to load to fetch content",
"default": 0
}
},
"required": ["url"]
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScrapeResponse"
}
}
}
},
"402": {
"description": "Payment required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Payment required to access this resource."
}
}
}
}
}
},
"429": {
"description": "Too many requests",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Request rate limit exceeded. Please wait and try again later."
}
}
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "An unexpected error occurred on the server."
}
}
}
}
}
}
}
}
},
"/crawl": {
"post": {
"summary": "Crawl multiple URLs based on options",
"operationId": "crawlUrls",
"tags": ["Crawling"],
"security": [
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "The base URL to start crawling from"
},
"crawlerOptions": {
"type": "object",
"properties": {
"includes": {
"type": "array",
"items": {
"type": "string"
},
"description": "URL patterns to include"
},
"excludes": {
"type": "array",
"items": {
"type": "string"
},
"description": "URL patterns to exclude"
},
"generateImgAltText": {
"type": "boolean",
"description": "Generate alt text for images using LLMs (must have a paid plan)",
"default": false
},
"returnOnlyUrls": {
"type": "boolean",
"description": "If true, returns only the URLs as a list on the crawl status. Attention: the return response will be a list of URLs inside the data, not a list of documents.",
"default": false
},
"maxDepth": {
"type": "integer",
"description": "Maximum depth to crawl relative to the entered URL. A maxDepth of 0 scrapes only the entered URL. A maxDepth of 1 scrapes the entered URL and all pages one level deep. A maxDepth of 2 scrapes the entered URL and all pages up to two levels deep. Higher values follow the same pattern."
},
"mode": {
"type": "string",
"enum": ["default", "fast"],
"description": "The crawling mode to use. Fast mode crawls 4x faster websites without sitemap, but may not be as accurate and shouldn't be used in heavy js-rendered websites.",
"default": "default"
},
"ignoreSitemap": {
"type": "boolean",
"description": "Ignore the website sitemap when crawling",
"default": false
},
"limit": {
"type": "integer",
"description": "Maximum number of pages to crawl",
"default": 10000
},
"allowBackwardCrawling": {
"type": "boolean",
"description": "Enables the crawler to navigate from a specific URL to previously linked pages. For instance, from 'example.com/product/123' back to 'example.com/product'",
"default": false
},
"allowExternalContentLinks": {
"type": "boolean",
"description": "Allows the crawler to follow links to external websites.",
"default": false
}
}
},
"pageOptions": {
"type": "object",
"properties": {
"headers": {
"type": "object",
"description": "Headers to send with the request. Can be used to send cookies, user-agent, etc."
},
"includeHtml": {
"type": "boolean",
"description": "Include the HTML version of the content on page. Will output a html key in the response.",
"default": false
},
"includeRawHtml": {
"type": "boolean",
"description": "Include the raw HTML content of the page. Will output a rawHtml key in the response.",
"default": false
},
"onlyIncludeTags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Only include tags, classes and ids from the page in the final output. Use comma separated values. Example: 'script, .ad, #footer'"
},
"onlyMainContent": {
"type": "boolean",
"description": "Only return the main content of the page excluding headers, navs, footers, etc.",
"default": false
},
"removeTags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags, classes and ids to remove from the page. Use comma separated values. Example: 'script, .ad, #footer'"
},
"replaceAllPathsWithAbsolutePaths": {
"type": "boolean",
"description": "Replace all relative paths with absolute paths for images and links",
"default": false
},
"screenshot": {
"type": "boolean",
"description": "Include a screenshot of the top of the page that you are scraping.",
"default": false
},
"fullPageScreenshot": {
"type": "boolean",
"description": "Include a full page screenshot of the page that you are scraping.",
"default": false
},
"waitFor": {
"type": "integer",
"description": "Wait x amount of milliseconds for the page to load to fetch content",
"default": 0
}
}
}
},
"required": ["url"]
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CrawlResponse"
}
}
}
},
"402": {
"description": "Payment required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Payment required to access this resource."
}
}
}
}
}
},
"429": {
"description": "Too many requests",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Request rate limit exceeded. Please wait and try again later."
}
}
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "An unexpected error occurred on the server."
}
}
}
}
}
}
}
}
},
"/search": {
"post": {
"summary": "Search for a keyword in Google, returns top page results with markdown content for each page",
"operationId": "searchGoogle",
"tags": ["Search"],
"security": [
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"format": "uri",
"description": "The query to search for"
},
"pageOptions": {
"type": "object",
"properties": {
"onlyMainContent": {
"type": "boolean",
"description": "Only return the main content of the page excluding headers, navs, footers, etc.",
"default": false
},
"fetchPageContent": {
"type": "boolean",
"description": "Fetch the content of each page. If false, defaults to a basic fast serp API.",
"default": true
},
"includeHtml": {
"type": "boolean",
"description": "Include the HTML version of the content on page. Will output a html key in the response.",
"default": false
},
"includeRawHtml": {
"type": "boolean",
"description": "Include the raw HTML content of the page. Will output a rawHtml key in the response.",
"default": false
}
}
},
"searchOptions": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of results. Max is 20 during beta."
}
}
}
},
"required": ["query"]
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SearchResponse"
}
}
}
},
"402": {
"description": "Payment required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Payment required to access this resource."
}
}
}
}
}
},
"429": {
"description": "Too many requests",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Request rate limit exceeded. Please wait and try again later."
}
}
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "An unexpected error occurred on the server."
}
}
}
}
}
}
}
}
},
"/crawl/status/{jobId}": {
"get": {
"tags": ["Crawl"],
"summary": "Get the status of a crawl job",
"operationId": "getCrawlStatus",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"name": "jobId",
"in": "path",
"description": "ID of the crawl job",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Status of the job (completed, active, failed, paused)"
},
"current": {
"type": "integer",
"description": "Current page number"
},
"total": {
"type": "integer",
"description": "Total number of pages"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CrawlStatusResponseObj"
},
"description": "Data returned from the job (null when it is in progress)"
},
"partial_data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CrawlStatusResponseObj"
},
"description": "Partial documents returned as it is being crawled (streaming). **This feature is currently in alpha - expect breaking changes** When a page is ready, it will append to the partial_data array, so there is no need to wait for the entire website to be crawled. When the crawl is done, partial_data will become empty and the result will be available in `data`. There is a max of 50 items in the array response. The oldest item (top of the array) will be removed when the new item is added to the array."
}
}
}
}
}
},
"402": {
"description": "Payment required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Payment required to access this resource."
}
}
}
}
}
},
"429": {
"description": "Too many requests",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Request rate limit exceeded. Please wait and try again later."
}
}
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "An unexpected error occurred on the server."
}
}
}
}
}
}
}
}
},
"/crawl/cancel/{jobId}": {
"delete": {
"tags": ["Crawl"],
"summary": "Cancel a crawl job",
"operationId": "cancelCrawlJob",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"name": "jobId",
"in": "path",
"description": "ID of the crawl job",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Returns cancelled."
}
}
}
}
}
},
"402": {
"description": "Payment required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Payment required to access this resource."
}
}
}
}
}
},
"429": {
"description": "Too many requests",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Request rate limit exceeded. Please wait and try again later."
}
}
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "An unexpected error occurred on the server."
}
}
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer"
}
},
"schemas": {
"ScrapeResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"warning": {
"type": "string",
"nullable": true,
"description": "Warning message to let you know of any issues."
},
"data": {
"type": "object",
"properties": {
"markdown": {
"type": "string",
"nullable": true,
"description": "Markdown content of the page if the `markdown` format was specified (default)"
},
"html": {
"type": "string",
"nullable": true,
"description": "HTML version of the content on page if the `html` format was specified"
},
"rawHtml": {
"type": "string",
"nullable": true,
"description": "Raw HTML content of the page if the `rawHtml` format was specified"
},
"links": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"nullable": true,
"description": "Links on the page if the `links` format was specified"
},
"screenshot": {
"type": "string",
"nullable": true,
"description": "URL of the screenshot of the page if the `screenshot` or `screenshot@fullSize` format was specified"
},
"metadata": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"language": {
"type": "string",
"nullable": true
},
"sourceURL": {
"type": "string",
"format": "uri"
},
"<any other metadata> ": {
"type": "string"
},
"statusCode": {
"type": "integer",
"description": "The status code of the page"
},
"error": {
"type": "string",
"nullable": true,
"description": "The error message of the page"
}
}
}
}
}
}
},
"CrawlStatusResponseObj": {
"type": "object",
"properties": {
"markdown": {
"type": "string",
"nullable": true,
"description": "Markdown content of the page if the `markdown` format was specified (default)"
},
"html": {
"type": "string",
"nullable": true,
"description": "HTML version of the content on page if the `html` format was specified"
},
"rawHtml": {
"type": "string",
"nullable": true,
"description": "Raw HTML content of the page if the `rawHtml` format was specified"
},
"links": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"nullable": true,
"description": "Links on the page if the `links` format was specified"
},
"screenshot": {
"type": "string",
"nullable": true,
"description": "URL of the screenshot of the page if the `screenshot` or `screenshot@fullSize` format was specified"
},
"metadata": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"language": {
"type": "string",
"nullable": true
},
"sourceURL": {
"type": "string",
"format": "uri"
},
"<any other metadata> ": {
"type": "string"
},
"statusCode": {
"type": "integer",
"description": "The status code of the page"
},
"error": {
"type": "string",
"nullable": true,
"description": "The error message of the page"
}
}
}
}
},
"SearchResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"data": {
"type": "array",
"items": {
"markdown": {
"type": "string",
"nullable": true,
"description": "Markdown content of the page if the `markdown` format was specified (default)"
},
"html": {
"type": "string",
"nullable": true,
"description": "HTML version of the content on page if the `html` format was specified"
},
"rawHtml": {
"type": "string",
"nullable": true,
"description": "Raw HTML content of the page if the `rawHtml` format was specified"
},
"links": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"nullable": true,
"description": "Links on the page if the `links` format was specified"
},
"screenshot": {
"type": "string",
"nullable": true,
"description": "URL of the screenshot of the page if the `screenshot` or `screenshot@fullSize` format was specified"
},
"metadata": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"language": {
"type": "string",
"nullable": true
},
"sourceURL": {
"type": "string",
"format": "uri"
},
"<any other metadata> ": {
"type": "string"
},
"statusCode": {
"type": "integer",
"description": "The status code of the page"
},
"error": {
"type": "string",
"nullable": true,
"description": "The error message of the page"
}
}
}
}
}
}
},
"CrawlResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
}
}
}
}
},
"security": [
{
"bearerAuth": []
}
]
}

Wyświetl plik

@ -0,0 +1,177 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"license": {
"name": "MIT"
}
},
"servers": [
{
"url": "http://petstore.swagger.io/v1"
}
],
"paths": {
"/pets": {
"get": {
"summary": "List all pets",
"operationId": "listPets",
"tags": ["pets"],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "How many items to return at one time (max 100)",
"required": false,
"schema": {
"type": "integer",
"maximum": 100,
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "A paged array of pets",
"headers": {
"x-next": {
"description": "A link to the next page of responses",
"schema": {
"type": "string"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pets"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"summary": "Create a pet",
"operationId": "createPets",
"tags": ["pets"],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Null response"
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/pets/{petId}": {
"get": {
"summary": "Info for a specific pet",
"operationId": "showPetById",
"tags": ["pets"],
"parameters": [
{
"name": "petId",
"in": "path",
"required": true,
"description": "The id of the pet to retrieve",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Expected response to a valid request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"Pets": {
"type": "array",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Pet"
}
},
"Error": {
"type": "object",
"required": ["code", "message"],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
}
}
}

Wyświetl plik

@ -0,0 +1,261 @@
{
"openapi": "3.1.0",
"info": {
"title": "Tic Tac Toe",
"description": "This API allows writing down marks on a Tic Tac Toe board\nand requesting the state of the board or of individual squares.\n",
"version": "1.0.0"
},
"tags": [
{
"name": "Gameplay"
}
],
"paths": {
"/board": {
"get": {
"summary": "Get the whole board",
"description": "Retrieves the current state of the board and the winner.",
"tags": ["Gameplay"],
"operationId": "get-board",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/status"
}
}
}
}
},
"security": [
{
"defaultApiKey": []
},
{
"app2AppOauth": ["board:read"]
}
]
}
},
"/board/{row}/{column}": {
"parameters": [
{
"$ref": "#/components/parameters/rowParam"
},
{
"$ref": "#/components/parameters/columnParam"
}
],
"get": {
"summary": "Get a single board square",
"description": "Retrieves the requested square.",
"tags": ["Gameplay"],
"operationId": "get-square",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mark"
}
}
}
},
"400": {
"description": "The provided parameters are incorrect",
"content": {
"text/html": {
"schema": {
"$ref": "#/components/schemas/errorMessage"
},
"example": "Illegal coordinates"
}
}
}
},
"security": [
{
"bearerHttpAuthentication": []
},
{
"user2AppOauth": ["board:read"]
}
]
},
"put": {
"summary": "Set a single board square",
"description": "Places a mark on the board and retrieves the whole board and the winner (if any).",
"tags": ["Gameplay"],
"operationId": "put-square",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mark"
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/status"
}
}
}
},
"400": {
"description": "The provided parameters are incorrect",
"content": {
"text/html": {
"schema": {
"$ref": "#/components/schemas/errorMessage"
},
"examples": {
"illegalCoordinates": {
"value": "Illegal coordinates."
},
"notEmpty": {
"value": "Square is not empty."
},
"invalidMark": {
"value": "Invalid Mark (X or O)."
}
}
}
}
}
},
"security": [
{
"bearerHttpAuthentication": []
},
{
"user2AppOauth": ["board:write"]
}
]
}
}
},
"components": {
"parameters": {
"rowParam": {
"description": "Board row (vertical coordinate)",
"name": "row",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/coordinate"
}
},
"columnParam": {
"description": "Board column (horizontal coordinate)",
"name": "column",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/coordinate"
}
}
},
"schemas": {
"errorMessage": {
"type": "string",
"maxLength": 256,
"description": "A text message describing an error"
},
"coordinate": {
"type": "integer",
"minimum": 1,
"maximum": 3,
"example": 1
},
"mark": {
"type": "string",
"enum": [".", "X", "O"],
"description": "Possible values for a board square. `.` means empty square.",
"example": "."
},
"board": {
"type": "array",
"maxItems": 3,
"minItems": 3,
"items": {
"type": "array",
"maxItems": 3,
"minItems": 3,
"items": {
"$ref": "#/components/schemas/mark"
}
}
},
"winner": {
"type": "string",
"enum": [".", "X", "O"],
"description": "Winner of the game. `.` means nobody has won yet.",
"example": "."
},
"status": {
"type": "object",
"properties": {
"winner": {
"$ref": "#/components/schemas/winner"
},
"board": {
"$ref": "#/components/schemas/board"
}
}
}
},
"securitySchemes": {
"defaultApiKey": {
"description": "API key provided in console",
"type": "apiKey",
"name": "api-key",
"in": "header"
},
"basicHttpAuthentication": {
"description": "Basic HTTP Authentication",
"type": "http",
"scheme": "Basic"
},
"bearerHttpAuthentication": {
"description": "Bearer token using a JWT",
"type": "http",
"scheme": "Bearer",
"bearerFormat": "JWT"
},
"app2AppOauth": {
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "https://learn.openapis.org/oauth/2.0/token",
"scopes": {
"board:read": "Read the board"
}
}
}
},
"user2AppOauth": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://learn.openapis.org/oauth/2.0/auth",
"tokenUrl": "https://learn.openapis.org/oauth/2.0/token",
"scopes": {
"board:read": "Read the board",
"board:write": "Write to the board"
}
}
}
}
}
}
}

Wyświetl plik

@ -0,0 +1,51 @@
{
"name": "@agentic/openapi-to-ts-client",
"version": "0.1.0",
"description": "TODO",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
},
"type": "module",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit",
"test:unit": "vitest run"
},
"dependencies": {
"@agentic/core": "workspace:*",
"@apidevtools/swagger-parser": "^10.1.1",
"camelcase": "^8.0.0",
"decamelize": "^6.0.0",
"execa": "^9.5.2",
"json-schema-to-zod": "^2.6.0",
"openapi-types": "^12.1.3",
"zod": "catalog:"
},
"devDependencies": {
"@agentic/tsconfig": "workspace:*",
"ky": "catalog:"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>TODO.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# Agentic
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
## License
MIT © [Travis Fischer](https://x.com/transitive_bs)

Wyświetl plik

@ -0,0 +1,614 @@
/* eslint-disable no-template-curly-in-string */
import * as fs from 'node:fs/promises'
import path from 'node:path'
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'
import { convertParametersToJSONSchema } from './openapi-parameters-to-json-schema'
import {
dereference,
dereferenceFull,
getAndResolve,
getComponentName,
getOperationParamsName,
getOperationResponseName,
jsonSchemaToZod,
prettify
} from './utils'
const dirname = path.dirname(fileURLToPath(import.meta.url))
const jsonContentType = 'application/json'
const multipartFormData = 'multipart/form-data'
const httpMethods = [
'get',
'post',
'put',
'delete',
'options',
'head',
'patch',
'trace'
] as const
async function main() {
const pathToOpenApiSpec = process.argv[2]
assert(pathToOpenApiSpec, 'Missing path to OpenAPI spec')
const parser = new SwaggerParser()
const spec = (await parser.bundle(pathToOpenApiSpec)) as OpenAPIV3.Document
// | OpenAPIV3_1.Document
if (
// TODO: make this less brittle
spec.openapi !== '3.0.0' &&
spec.openapi !== '3.1.0' &&
spec.openapi !== '3.1.1'
) {
throw new Error(`Unexpected OpenAPI version "${spec.openapi}"`)
}
const openapiSpecName = path.basename(pathToOpenApiSpec, '.json')
assert(
openapiSpecName.toLowerCase() === openapiSpecName,
`OpenAPI spec name "${openapiSpecName}" must be in kebab case`
)
const name = camelCase(openapiSpecName, { pascalCase: true })
const nameLowerCase = name.toLowerCase()
const nameSnakeCase = decamelize(name)
const nameKebabCase = decamelize(name, { separator: '-' })
const nameUpperCase = nameSnakeCase.toUpperCase()
const clientName = `${name}Client`
const namespaceName = nameLowerCase
// const destFolder = path.join('packages', nameKebabCase)
// const destFolderSrc = path.join(destFolder, 'src')
const destFolder = path.join(dirname, '..', 'fixtures', 'generated')
const destFileClient = path.join(destFolder, `${nameKebabCase}-client.ts`)
const apiBaseUrl = spec.servers?.[0]?.url
const securitySchemes = spec.components?.securitySchemes
const resolvedSecuritySchemes: Record<
string,
OpenAPIV3.SecuritySchemeObject
> = {}
const apiKeyHeaderNames: string[] = []
if (securitySchemes) {
for (const [securitySchemaName, maybeSecuritySchema] of Object.entries(
securitySchemes
)) {
const securitySchema = dereferenceFull(maybeSecuritySchema, parser.$refs)
if (!securitySchema) continue
resolvedSecuritySchemes[securitySchemaName] = securitySchema
switch (securitySchema.type) {
case 'apiKey':
apiKeyHeaderNames.push(securitySchemaName)
break
case 'http':
if (securitySchema.scheme === 'bearer') {
apiKeyHeaderNames.push(securitySchemaName)
}
break
case 'oauth2':
apiKeyHeaderNames.push(securitySchemaName)
break
default:
console.log(
'unsupported security schema',
securitySchemaName,
securitySchema
)
}
}
}
const hasGlobalApiKeyInHeader = apiKeyHeaderNames.length === 1
const componentsToProcess = new Set<string>()
const requestBodyJSONSchemaPaths = [
'requestBody',
'content',
jsonContentType,
'schema'
]
const requestBodyFormDataJSONSchemaPaths = [
'requestBody',
'content',
multipartFormData,
'schema'
]
const operationResponsePaths = [
['responses', '200', 'content', jsonContentType, 'schema'],
['responses', '201', 'content', jsonContentType, 'schema']
// ['responses', 'default', 'content', jsonContentType, 'schema']
]
const operationRequestPaths = [
requestBodyJSONSchemaPaths,
requestBodyFormDataJSONSchemaPaths
]
const operationIds = new Set<string>()
const operationSchemas: Record<string, string> = {}
const componentSchemas: Record<string, string> = {}
const aiClientMethods: string[] = []
for (const path in spec.paths) {
const pathItem = spec.paths[path]
assert(pathItem)
// console.log(JSON.stringify(pathItem, null, 2))
for (const method of httpMethods) {
const operation = pathItem[method]
if (!operation) {
continue
}
if (method === 'trace' || method === 'options') {
continue
}
const operationName =
// TODO: better camelCase fallback
operation.operationId || `${method}${path.replaceAll(/\W+/g, '_')}`
assert(
operationName,
`Invalid operation name ${operationName} for path "${method} ${path}"`
)
assert(
!operationIds.has(operationName),
`Duplicate operation name "${operationName}"`
)
operationIds.add(operationName)
const operationNameSnakeCase = decamelize(operationName)
// if (path !== '/crawl/status/{jobId}') continue
// if (path !== '/pets' || method !== 'post') continue
// console.log(method, path, operationName)
const operationParamsJSONSchema = {
type: 'object',
properties: {} as Record<string, any>,
required: [] as string[],
$refs: [] as string[]
}
const operationResponseJSONSchemas: Record<string, IJsonSchema> = {}
const operationParamsSources: Record<string, string> = {}
// eslint-disable-next-line unicorn/consistent-function-scoping
function addJSONSchemaParams(schema: IJsonSchema, source: string) {
dereferenceFull(schema, parser.$refs, componentsToProcess)
if (schema.$ref) {
operationParamsJSONSchema.$refs.push(schema.$ref)
const derefed = dereference(schema, parser.$refs, componentsToProcess)
if (derefed?.properties) {
for (const key of Object.keys(derefed.properties)) {
assert(
!operationParamsSources[key],
`Duplicate params key ${key} for operation ${operationName} from ${operationParamsSources[key]} and ${source}`
)
operationParamsSources[key] = source
}
}
} else {
assert(schema.type === 'object')
if (schema.properties) {
operationParamsJSONSchema.properties = {
...operationParamsJSONSchema.properties,
...schema.properties
}
for (const key of Object.keys(schema.properties)) {
assert(
!operationParamsSources[key],
`Duplicate params key ${key} for operation ${operationName} from ${operationParamsSources[key]} and ${source}`
)
operationParamsSources[key] = source
}
}
if (schema.required) {
operationParamsJSONSchema.required = [
...operationParamsJSONSchema.required,
...schema.required
]
}
}
}
// eslint-disable-next-line unicorn/consistent-function-scoping
function addJSONSchemaResponse(schema: IJsonSchema, status: string) {
dereferenceFull(schema, parser.$refs, componentsToProcess)
operationResponseJSONSchemas[status] = schema
}
for (const schemaPath of operationRequestPaths) {
const res = getAndResolve(
operation,
schemaPath,
parser.$refs,
componentsToProcess
)
if (res) {
addJSONSchemaParams(
res,
schemaPath[2] === jsonContentType ? 'body' : 'formData'
)
break
}
}
for (const schemaPath of operationResponsePaths) {
const res = getAndResolve(
operation,
schemaPath,
parser.$refs,
componentsToProcess
)
if (res) {
const status = schemaPath[1]!
assert(
status,
`Invalid status ${status} for operation ${operationName}`
)
addJSONSchemaResponse(res, status)
break
}
}
if (operation.parameters) {
const params = convertParametersToJSONSchema(operation.parameters)
if (params.body) {
addJSONSchemaParams(params.body, 'body')
}
if (params.formData) {
addJSONSchemaParams(params.formData, 'formData')
}
if (params.headers) {
addJSONSchemaParams(params.headers, 'formData')
}
if (params.path) {
addJSONSchemaParams(params.path, 'path')
}
if (params.query) {
addJSONSchemaParams(params.query, 'query')
}
}
const operationParamsName = getOperationParamsName(
operationName,
componentSchemas
)
const operationResponseName = getOperationResponseName(operationName)
{
// Merge all operations params into one schema declaration
let operationsParamsSchema = jsonSchemaToZod(
operationParamsJSONSchema,
{ name: `${operationParamsName}Schema`, type: operationParamsName }
)
if (operationParamsJSONSchema.$refs.length) {
const refSchemas = operationParamsJSONSchema.$refs.map(
(ref) => `${getComponentName(ref)!}Schema`
)
operationsParamsSchema = operationsParamsSchema.replace(
// eslint-disable-next-line security/detect-non-literal-regexp
new RegExp(`(${operationParamsName}Schema = .*)$`, 'm'),
// eslint-disable-next-line unicorn/no-array-reduce
refSchemas.reduce(
(acc, refSchema) => `${acc}.merge(${refSchema})`,
'$1'
)
)
}
assert(
!componentSchemas[operationParamsName],
`Operation params name "${operationParamsName}" conflicts with component name`
)
operationSchemas[operationParamsName] = operationsParamsSchema
}
const operationResponseJSONSchema = operationResponseJSONSchemas['200']
if (operationResponseJSONSchema) {
let isDuplicateDefinition = false
if (operationResponseJSONSchema.$ref) {
const componentName = getComponentName(
operationResponseJSONSchema.$ref
)
if (componentName === operationResponseName) {
isDuplicateDefinition = true
}
}
if (!isDuplicateDefinition) {
const operationResponseSchema = jsonSchemaToZod(
operationResponseJSONSchema,
{
name: `${operationResponseName}Schema`,
type: operationResponseName
}
)
assert(
!componentSchemas[operationResponseName],
`Operation response name "${operationResponseName}" conflicts with component name`
)
operationSchemas[operationResponseName] = operationResponseSchema
}
} else {
assert(
!componentSchemas[operationResponseName],
`Operation response name "${operationResponseName}" conflicts with component name`
)
operationSchemas[operationResponseName] =
`export type ${operationResponseName} = undefined\n`
}
// console.log(
// JSON.stringify(
// {
// operationName,
// operationParamsSources,
// operationParamsJSONSchema
// },
// null,
// 2
// )
// )
const queryParams = Object.keys(operationParamsSources).filter(
(key) => operationParamsSources[key] === 'query'
)
const hasQueryParams = queryParams.length > 0
const bodyParams = Object.keys(operationParamsSources).filter(
(key) => operationParamsSources[key] === 'body'
)
const hasBodyParams = bodyParams.length > 0
const formDataParams = Object.keys(operationParamsSources).filter(
(key) => operationParamsSources[key] === 'formData'
)
const hasFormDataParams = formDataParams.length > 0
const pathParams = Object.keys(operationParamsSources).filter(
(key) => operationParamsSources[key] === 'path'
)
const hasPathParams = pathParams.length > 0
const headersParams = Object.keys(operationParamsSources).filter(
(key) => operationParamsSources[key] === 'headers'
)
const hasHeadersParams = headersParams.length > 0
const onlyHasOneParamsSource =
new Set(Object.values(operationParamsSources)).size === 1
const onlyHasPathParams = hasPathParams && onlyHasOneParamsSource
const pathTemplate = hasPathParams
? '`' + path.replaceAll(/{([^}]+)}/g, '${params["$1"]}') + '`'
: `'${path}'`
let description = operation.description || operation.summary
if (description && !/[!.?]$/.test(description)) {
description += '.'
}
const aiClientMethod = `
${description ? `/**\n * ${description}\n */` : ''}
@aiFunction({
name: '${operationNameSnakeCase}',
${description ? `description: '${description}',` : ''}
inputSchema: ${namespaceName}.${operationParamsName}Schema,
})
async ${operationName}(params: ${namespaceName}.${operationParamsName}): Promise<${namespaceName}.${operationResponseName}> {
return this.ky.${method}(${pathTemplate}${
onlyHasPathParams
? ''
: `, {
${hasQueryParams ? (onlyHasOneParamsSource ? `searchParams: sanitizeSearchParams(params),` : `searchParams: sanitizeSearchParams(pick(params, '${queryParams.join("', '")}')),`) : ''}
${hasBodyParams ? (onlyHasOneParamsSource ? `json: params,` : `json: pick(params, '${bodyParams.join("', '")}'),`) : ''}
${hasFormDataParams ? (onlyHasOneParamsSource ? `form: params,` : `form: pick(params, '${formDataParams.join("', '")}'),`) : ''}
${hasHeadersParams ? (onlyHasOneParamsSource ? `headers: params,` : `headers: pick(params, '${headersParams.join("', '")}'),`) : ''}
}`
}).json<${namespaceName}.${operationResponseName}>()
}
`
aiClientMethods.push(aiClientMethod)
}
}
const processedComponents = new Set<string>()
const componentToRefs: Record<
string,
{ dereferenced: any; refs: Set<string> }
> = {}
for (const ref of componentsToProcess) {
const component = parser.$refs.get(ref)
assert(component)
const resolved = new Set<string>()
const dereferenced = dereference(component, parser.$refs)
dereference(component, parser.$refs, resolved, 0, Number.POSITIVE_INFINITY)
assert(dereferenced)
for (const ref of resolved) {
assert(componentsToProcess.has(ref))
}
componentToRefs[ref] = { dereferenced, refs: resolved }
}
const sortedComponents = Object.keys(componentToRefs).sort(
(a, b) => componentToRefs[a]!.refs.size - componentToRefs[b]!.refs.size
)
for (const ref of sortedComponents) {
const type = getComponentName(ref)
assert(type, `Invalid ref name ${ref}`)
const name = `${type}Schema`
const { dereferenced, refs } = componentToRefs[ref]!
if (processedComponents.has(ref)) {
continue
}
for (const r of refs) {
if (processedComponents.has(r)) {
continue
}
processedComponents.add(r)
}
processedComponents.add(ref)
const schema = jsonSchemaToZod(dereferenced, { name, type })
componentSchemas[type] = schema
// console.log(ref, name, dereferenced)
}
// console.log(
// '\ncomponents',
// Array.from(componentsToProcess)
// .map((ref) => getComponentName(ref))
// .sort()
// )
// console.log(
// '\nmodels',
// Object.fromEntries(
// await Promise.all(
// Object.entries(schemaInput).map(async ([key, value]) => {
// return [key, await prettify(value)]
// })
// )
// )
// )
const aiClientMethodsString = aiClientMethods.join('\n\n')
const header = `
/**
* This file was auto-generated from an OpenAPI spec.
*/
import {
AIFunctionsProvider,
${aiClientMethods.length > 0 ? 'aiFunction,' : ''}
${hasGlobalApiKeyInHeader ? 'assert,' : ''}
${hasGlobalApiKeyInHeader ? 'getEnv,' : ''}
${aiClientMethodsString.includes('pick(') ? 'pick,' : ''}
${aiClientMethodsString.includes('sanitizeSearchParams(') ? 'sanitizeSearchParams,' : ''}
} from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'`.trim()
const outputTypes = (
await prettify(
[
header,
`export namespace ${namespaceName} {`,
apiBaseUrl ? `export const apiBaseUrl = '${apiBaseUrl}'` : undefined,
...Object.values(componentSchemas),
...Object.values(operationSchemas),
'}'
]
.filter(Boolean)
.join('\n\n')
)
)
.replaceAll(/z\.object\({}\)\.merge\(([^)]*)\)/g, '$1')
.replaceAll(/\/\*\*(\S.*)\*\//g, '/** $1 */')
const output = await prettify(
[
outputTypes,
`
export class ${clientName} extends AIFunctionsProvider {
protected readonly ky: KyInstance
${hasGlobalApiKeyInHeader ? 'protected readonly apiKey: string' : ''}
protected readonly apiBaseUrl: string
constructor({
${hasGlobalApiKeyInHeader ? `apiKey = getEnv('${nameUpperCase}_API_KEY'),` : ''}
apiBaseUrl${apiBaseUrl ? ` = ${namespaceName}.apiBaseUrl` : ''},
ky = defaultKy
}: {
apiKey?: string
apiBaseUrl?: string
ky?: KyInstance
} = {}) {
${
hasGlobalApiKeyInHeader
? `assert(
apiKey,
'${clientName} missing required "apiKey" (defaults to "${nameUpperCase}_API_KEY")'
)`
: ''
}
super()
${hasGlobalApiKeyInHeader ? `this.apiKey = apiKey` : ''}
this.apiBaseUrl = apiBaseUrl
this.ky = ky.extend({
prefixUrl: apiBaseUrl,
${
hasGlobalApiKeyInHeader
? `headers: {
${apiKeyHeaderNames.map((name) => `'${(resolvedSecuritySchemes[name] as any).name || 'Authorization'}': ${(resolvedSecuritySchemes[name] as any).schema?.toLowerCase() === 'bearer' ? '`Bearer ${apiKey}`' : 'apiKey'}`).join(',\n')}
},`
: ''
}
})
}
`,
aiClientMethodsString,
'}'
].join('\n\n')
)
// console.log(output)
await fs.mkdir(destFolder, { recursive: true })
await fs.writeFile(destFileClient, output)
await execa('npx', ['eslint', '--fix', destFileClient])
}
await main()

Wyświetl plik

@ -0,0 +1,221 @@
/**
* This file is forked from: https://github.com/kogosoftwarellc/open-api/tree/main/packages/openapi-jsonschema-parameters
*
* The original code is licensed under the MIT license.
*/
import { type IJsonSchema, type OpenAPI } from 'openapi-types'
export interface OpenAPIParametersAsJSONSchema {
body?: IJsonSchema
formData?: IJsonSchema
headers?: IJsonSchema
path?: IJsonSchema
query?: IJsonSchema
cookie?: IJsonSchema
}
const VALIDATION_KEYWORDS = new Set([
'additionalItems',
'default',
'example',
'description',
'enum',
'examples',
'exclusiveMaximum',
'exclusiveMinimum',
'format',
'items',
'maxItems',
'maxLength',
'maximum',
'minItems',
'minLength',
'minimum',
'multipleOf',
'pattern',
'title',
'type',
'uniqueItems'
])
const SUBSCHEMA_KEYWORDS = [
'additionalItems',
'items',
'contains',
'additionalProperties',
'propertyNames',
'not'
]
const SUBSCHEMA_ARRAY_KEYWORDS = ['items', 'allOf', 'anyOf', 'oneOf']
const SUBSCHEMA_OBJECT_KEYWORDS = [
'definitions',
'properties',
'patternProperties',
'dependencies'
]
export function convertParametersToJSONSchema(
parameters: OpenAPI.Parameters
): OpenAPIParametersAsJSONSchema {
const parametersSchema: OpenAPIParametersAsJSONSchema = {}
const bodySchema = getBodySchema(parameters)
const formDataSchema = getSchema(parameters, 'formData')
const headerSchema = getSchema(parameters, 'header')
const pathSchema = getSchema(parameters, 'path')
const querySchema = getSchema(parameters, 'query')
const cookieSchema = getSchema(parameters, 'cookie')
if (bodySchema) {
parametersSchema.body = bodySchema
}
if (formDataSchema) {
parametersSchema.formData = formDataSchema
}
if (headerSchema) {
parametersSchema.headers = headerSchema
}
if (pathSchema) {
parametersSchema.path = pathSchema
}
if (querySchema) {
parametersSchema.query = querySchema
}
if (cookieSchema) {
parametersSchema.cookie = cookieSchema
}
return parametersSchema
}
function copyValidationKeywords(src: any) {
const dst: any = {}
for (let i = 0, keys = Object.keys(src), len = keys.length; i < len; i++) {
const keyword = keys[i]
if (!keyword) continue
if (VALIDATION_KEYWORDS.has(keyword) || keyword.slice(0, 2) === 'x-') {
dst[keyword] = src[keyword]
}
}
return dst
}
function handleNullable(schema: IJsonSchema) {
return { anyOf: [schema, { type: 'null' }] }
}
function handleNullableSchema(schema: any) {
if (typeof schema !== 'object' || schema === null) {
return schema
}
const newSchema = { ...schema }
for (const keyword of SUBSCHEMA_KEYWORDS) {
if (
typeof schema[keyword] === 'object' &&
schema[keyword] !== null &&
!Array.isArray(schema[keyword])
) {
newSchema[keyword] = handleNullableSchema(schema[keyword])
}
}
for (const keyword of SUBSCHEMA_ARRAY_KEYWORDS) {
if (Array.isArray(schema[keyword])) {
newSchema[keyword] = schema[keyword].map(handleNullableSchema)
}
}
for (const keyword of SUBSCHEMA_OBJECT_KEYWORDS) {
if (typeof schema[keyword] === 'object' && schema[keyword] !== null) {
newSchema[keyword] = { ...schema[keyword] }
for (const prop of Object.keys(schema[keyword])) {
newSchema[keyword][prop] = handleNullableSchema(schema[keyword][prop])
}
}
}
delete newSchema.$ref
if (schema.nullable) {
delete newSchema.nullable
return handleNullable(newSchema)
}
return newSchema
}
function getBodySchema(parameters: any[]) {
let bodySchema = parameters.find((param) => {
return param.in === 'body' && param.schema
})
if (bodySchema) {
bodySchema = bodySchema.schema
}
return bodySchema
}
function getSchema(parameters: any[], type: string) {
const params = parameters.filter(byIn(type))
let schema: any
if (params.length) {
schema = { type: 'object', properties: {} }
for (const param of params) {
let paramSchema = copyValidationKeywords(param)
if ('schema' in param) {
paramSchema = {
...paramSchema,
...handleNullableSchema(param.schema)
}
if ('examples' in param) {
paramSchema.examples = getExamples(param.examples)
}
schema.properties[param.name] = paramSchema
} else {
if ('examples' in paramSchema) {
paramSchema.examples = getExamples(paramSchema.examples)
}
schema.properties[param.name] = param.nullable
? handleNullable(paramSchema)
: paramSchema
}
}
schema.required = getRequiredParams(params)
}
return schema
}
function getRequiredParams(parameters: any[]) {
return parameters.filter(byRequired).map(toName)
}
function getExamples(exampleSchema: any) {
return Object.keys(exampleSchema).map((k) => exampleSchema[k].value)
}
function byIn(str: string) {
return (param: any) => param.in === str && param.type !== 'file'
}
function byRequired(param: any) {
return !!param.required
}
function toName(param: any) {
return param.name
}

Wyświetl plik

@ -0,0 +1,253 @@
import type SwaggerParser from '@apidevtools/swagger-parser'
import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
import { assert } from '@agentic/core'
import {
type JsonSchema,
jsonSchemaToZod as jsonSchemaToZodImpl
} from 'json-schema-to-zod'
import * as prettier from 'prettier'
export function prettify(source: string): Promise<string> {
return prettier.format(source, {
parser: 'typescript',
semi: false,
singleQuote: true,
jsxSingleQuote: true,
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'always',
trailingComma: 'none'
})
}
export function titleCase(identifier: string): string {
return `${identifier.slice(0, 1).toUpperCase()}${identifier.slice(1)}`
}
export function unTitleCase(identifier: string): string {
return `${identifier.slice(0, 1).toLowerCase()}${identifier.slice(1)}`
}
export function getAndResolve<T extends object = object>(
obj: any,
keys: string[],
refs: SwaggerParser.$Refs,
resolved?: Set<string>,
depth = 0,
maxDepth = 0
): T | null {
if (obj === undefined) return null
if (typeof obj !== 'object') return null
if (!keys.length) {
return dereference(obj, refs, resolved, depth, maxDepth) as T
}
if (obj.$ref) {
const derefed = refs.get(obj.$ref)
resolved?.add(obj.$ref)
if (!derefed) {
return null
}
obj = derefed
}
const key = keys[0]!
const value = obj[key]
keys = keys.slice(1)
if (value === undefined) {
return null
}
return getAndResolve<T>(value, keys, refs, resolved, depth, maxDepth)
}
export function dereferenceFull<T extends object = object>(
obj: T,
refs: SwaggerParser.$Refs,
resolved?: Set<string>
): Exclude<T, OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject> {
return dereference(obj, refs, resolved, 0, Number.POSITIVE_INFINITY) as any
}
export function dereference<T extends object = object>(
obj: T,
refs: SwaggerParser.$Refs,
resolved?: Set<string>,
depth = 0,
maxDepth = 1
): T {
if (!obj) return obj
if (depth >= maxDepth) {
return obj
}
if (Array.isArray(obj)) {
return obj.map((item) =>
dereference(item, refs, resolved, depth + 1, maxDepth)
) as T
} else if (typeof obj === 'object') {
if ('$ref' in obj) {
const ref = obj.$ref as string
const derefed = refs.get(ref)
if (!derefed) {
return obj
}
resolved?.add(ref)
derefed.title = ref.split('/').pop()!
return dereference(derefed, refs, resolved, depth + 1, maxDepth)
} else {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
dereference(value, refs, resolved, depth + 1, maxDepth)
])
) as T
}
} else {
return obj
}
}
export function jsonSchemaToZod(
schema: JsonSchema,
{
name,
type
}: {
name?: string
type?: string
} = {}
): string {
return jsonSchemaToZodImpl(schema, {
name,
module: 'esm',
withJsdocs: true,
type: type ?? true,
noImport: true,
parserOverride: (schema, _refs) => {
if ('$ref' in schema) {
const ref = schema.$ref as string
if (!ref) return
const name = getComponentName(ref)
if (!name) return
return `${name}Schema`
}
}
})
}
const reservedWords = new Set([
'Error',
'Class',
'Object',
'interface',
'type',
'default',
'switch',
'for',
'break',
'case',
'if',
'else',
'while',
'do',
'for',
'return',
'continue',
'function',
'console',
'window',
'global',
'import',
'export',
'namespace',
'using',
'require',
'module',
'process',
'async',
'await',
'const',
'let',
'var',
'void',
'undefined',
'abstract',
'extends',
'implements',
'private',
'protected',
'public',
'Infinity',
'Nan',
'Math',
'Date',
'RegExp',
'JSON',
'Buffer',
'Promise',
'Symbol',
'Map',
'Set',
'WeakMap',
'WeakSet',
'Array',
'Object',
'Boolean',
'Number',
'String',
'Function',
'Symbol'
])
export function getComponentName(ref: string) {
const name0 = ref.split('/').pop()!
assert(name0, `Invalid ref name ${ref}`)
const name1 = titleCase(name0)
assert(name1, `Invalid ref name ${ref}`)
if (reservedWords.has(name1)) {
return `${name1}Type`
}
return name1
}
export function getOperationParamsName(
operationName: string,
schemas?: Record<string, string>
) {
const name = `${titleCase(operationName)}Params`
if (!schemas) return name
let tempName = name
let index = 2
while (schemas[tempName]) {
tempName = `${name}${index}`
++index
}
return tempName
}
export function getOperationResponseName(
operationName: string,
schemas?: Record<string, string>
) {
const name = `${titleCase(operationName)}Response`
if (!schemas) return name
let tempName = name
let index = 2
while (schemas[tempName]) {
tempName = `${name}${index}`
++index
}
return tempName
}

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@agentic/tsconfig/base.json",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -160,7 +160,7 @@ importers:
version: 5.8.2
vitest:
specifier: 3.0.9
version: 3.0.9(@types/node@22.13.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)
version: 3.0.9(@types/debug@4.1.12)(@types/node@22.13.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)
zod:
specifier: 'catalog:'
version: 3.24.2
@ -814,6 +814,40 @@ importers:
specifier: workspace:*
version: link:../tsconfig
packages/openapi-to-ts-client:
dependencies:
'@agentic/core':
specifier: workspace:*
version: link:../core
'@apidevtools/swagger-parser':
specifier: ^10.1.1
version: 10.1.1(openapi-types@12.1.3)
camelcase:
specifier: ^8.0.0
version: 8.0.0
decamelize:
specifier: ^6.0.0
version: 6.0.0
execa:
specifier: ^9.5.2
version: 9.5.2
json-schema-to-zod:
specifier: ^2.6.0
version: 2.6.0
openapi-types:
specifier: ^12.1.3
version: 12.1.3
zod:
specifier: 'catalog:'
version: 3.24.2
devDependencies:
'@agentic/tsconfig':
specifier: workspace:*
version: link:../tsconfig
ky:
specifier: 'catalog:'
version: 1.7.5
packages/people-data-labs:
dependencies:
'@agentic/core':
@ -1374,6 +1408,22 @@ packages:
resolution: {integrity: sha512-IQD9wkVReKAhsEAbDjh/0KrBGTEXelqZLpOBRDaIRvlzZ9sjmUP+gKbpvzyJnei2JHQiE8JAgj7YcNloINbGBw==}
engines: {node: '>= 10'}
'@apidevtools/json-schema-ref-parser@11.7.2':
resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
engines: {node: '>= 16'}
'@apidevtools/openapi-schemas@2.1.0':
resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==}
engines: {node: '>=10'}
'@apidevtools/swagger-methods@3.0.2':
resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
'@apidevtools/swagger-parser@10.1.1':
resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==}
peerDependencies:
openapi-types: '>=7'
'@aws-crypto/crc32@3.0.0':
resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==}
@ -1935,6 +1985,9 @@ packages:
'@js-sdsl/ordered-map@4.4.2':
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
'@jsdevtools/ono@7.1.3':
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
'@langchain/core@0.3.42':
resolution: {integrity: sha512-pT/jC5lqWK3YGDq8dQwgKoa6anqAhMtG1x5JbnrOj9NdaLeBbCKBDQ+/Ykzk3nZ8o+0UMsaXNZo7IVL83VVjHg==}
engines: {node: '>=18'}
@ -2972,6 +3025,9 @@ packages:
'@rushstack/eslint-patch@1.10.4':
resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==}
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
@ -2979,6 +3035,10 @@ packages:
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
engines: {node: '>=18'}
'@sindresorhus/merge-streams@4.0.0':
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'}
'@smithy/abort-controller@4.0.1':
resolution: {integrity: sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==}
engines: {node: '>=18.0.0'}
@ -3202,6 +3262,9 @@ packages:
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/diff-match-patch@1.0.36':
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
@ -3227,6 +3290,9 @@ packages:
'@types/memcached@2.2.10':
resolution: {integrity: sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==}
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
'@types/mysql@2.15.26':
resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==}
@ -3441,6 +3507,14 @@ packages:
zod:
optional: true
ajv-draft-04@1.0.0:
resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==}
peerDependencies:
ajv: ^8.5.0
peerDependenciesMeta:
ajv:
optional: true
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
@ -3653,6 +3727,9 @@ packages:
resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==}
engines: {node: '>= 0.4'}
call-me-maybe@1.0.2:
resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@ -3661,6 +3738,10 @@ packages:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
camelcase@8.0.0:
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
engines: {node: '>=16'}
caniuse-lite@1.0.30001678:
resolution: {integrity: sha512-RR+4U/05gNtps58PEBDZcPWTgEO2MBeoPZ96aQcjmfkBWRIDfN451fW2qyDA9/+HohLLIL5GqiMwA+IB1pWarw==}
@ -3854,6 +3935,10 @@ packages:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decamelize@6.0.0:
resolution: {integrity: sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
decimal.js@10.5.0:
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
@ -4275,6 +4360,10 @@ packages:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
execa@9.5.2:
resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==}
engines: {node: ^18.19.0 || >=20.5.0}
expect-type@1.2.0:
resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==}
engines: {node: '>=12.0.0'}
@ -4344,6 +4433,10 @@ packages:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
figures@6.1.0:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@ -4490,6 +4583,10 @@ packages:
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
engines: {node: '>=16'}
get-stream@9.0.1:
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
engines: {node: '>=18'}
get-symbol-description@1.0.2:
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
engines: {node: '>= 0.4'}
@ -4621,6 +4718,10 @@ packages:
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
engines: {node: '>=16.17.0'}
human-signals@8.0.0:
resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==}
engines: {node: '>=18.18.0'}
humanize-ms@1.2.1:
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
@ -4805,6 +4906,10 @@ packages:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
is-stream@4.0.1:
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
engines: {node: '>=18'}
is-string@1.0.7:
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
engines: {node: '>= 0.4'}
@ -4817,6 +4922,10 @@ packages:
resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
engines: {node: '>= 0.4'}
is-unicode-supported@2.1.0:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
@ -5031,6 +5140,7 @@ packages:
libsql@0.4.7:
resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==}
cpu: [x64, arm64, wasm32]
os: [darwin, linux, win32]
lilconfig@3.1.3:
@ -5281,6 +5391,10 @@ packages:
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
npm-run-path@6.0.0:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
nypm@0.6.0:
resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==}
engines: {node: ^14.16.0 || >=16.10.0}
@ -5456,6 +5570,10 @@ packages:
resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==}
engines: {node: '>=18'}
parse-ms@4.0.0:
resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
engines: {node: '>=18'}
parseley@0.12.1:
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
@ -5622,6 +5740,10 @@ packages:
engines: {node: '>=14'}
hasBin: true
pretty-ms@9.2.0:
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
engines: {node: '>=18'}
process-warning@4.0.1:
resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
@ -6046,6 +6168,10 @@ packages:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
strip-final-newline@4.0.0:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
strip-indent@3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
@ -6342,6 +6468,10 @@ packages:
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
engines: {node: '>=18'}
unicorn-magic@0.3.0:
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
engines: {node: '>=18'}
universal-github-app-jwt@2.2.0:
resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==}
@ -6608,6 +6738,10 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
yoctocolors@2.1.1:
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
engines: {node: '>=18'}
zod-to-json-schema@3.24.3:
resolution: {integrity: sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==}
peerDependencies:
@ -6679,6 +6813,27 @@ snapshots:
'@anush008/tokenizers-linux-x64-gnu': 0.0.0
'@anush008/tokenizers-win32-x64-msvc': 0.0.0
'@apidevtools/json-schema-ref-parser@11.7.2':
dependencies:
'@jsdevtools/ono': 7.1.3
'@types/json-schema': 7.0.15
js-yaml: 4.1.0
'@apidevtools/openapi-schemas@2.1.0': {}
'@apidevtools/swagger-methods@3.0.2': {}
'@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)':
dependencies:
'@apidevtools/json-schema-ref-parser': 11.7.2
'@apidevtools/openapi-schemas': 2.1.0
'@apidevtools/swagger-methods': 3.0.2
'@jsdevtools/ono': 7.1.3
ajv: 8.17.1
ajv-draft-04: 1.0.0(ajv@8.17.1)
call-me-maybe: 1.0.2
openapi-types: 12.1.3
'@aws-crypto/crc32@3.0.0':
dependencies:
'@aws-crypto/util': 3.0.0
@ -7482,6 +7637,8 @@ snapshots:
'@js-sdsl/ordered-map@4.4.2': {}
'@jsdevtools/ono@7.1.3': {}
'@langchain/core@0.3.42(openai@4.87.3(encoding@0.1.13)(ws@8.18.0)(zod@3.24.2))':
dependencies:
'@cfworker/json-schema': 4.1.1
@ -8797,6 +8954,8 @@ snapshots:
'@rushstack/eslint-patch@1.10.4': {}
'@sec-ant/readable-stream@0.4.1': {}
'@selderee/plugin-htmlparser2@0.11.0':
dependencies:
domhandler: 5.0.3
@ -8804,6 +8963,8 @@ snapshots:
'@sindresorhus/merge-streams@2.3.0': {}
'@sindresorhus/merge-streams@4.0.0': {}
'@smithy/abort-controller@4.0.1':
dependencies:
'@smithy/types': 4.1.0
@ -9142,6 +9303,11 @@ snapshots:
dependencies:
'@types/node': 22.13.10
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
optional: true
'@types/diff-match-patch@1.0.36': {}
'@types/estree@1.0.6': {}
@ -9162,6 +9328,9 @@ snapshots:
dependencies:
'@types/node': 22.13.10
'@types/ms@2.1.0':
optional: true
'@types/mysql@2.15.26':
dependencies:
'@types/node': 22.13.10
@ -9436,6 +9605,10 @@ snapshots:
react: 18.3.1
zod: 3.24.2
ajv-draft-04@1.0.0(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
ajv-formats@3.0.1(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
@ -9697,10 +9870,14 @@ snapshots:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.2.7
call-me-maybe@1.0.2: {}
callsites@3.1.0: {}
camelcase@6.3.0: {}
camelcase@8.0.0: {}
caniuse-lite@1.0.30001678: {}
chai@5.2.0:
@ -9883,6 +10060,8 @@ snapshots:
decamelize@1.2.0: {}
decamelize@6.0.0: {}
decimal.js@10.5.0: {}
decircular@0.1.1: {}
@ -10472,6 +10651,21 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
execa@9.5.2:
dependencies:
'@sindresorhus/merge-streams': 4.0.0
cross-spawn: 7.0.6
figures: 6.1.0
get-stream: 9.0.1
human-signals: 8.0.0
is-plain-obj: 4.1.0
is-stream: 4.0.1
npm-run-path: 6.0.0
pretty-ms: 9.2.0
signal-exit: 4.1.0
strip-final-newline: 4.0.0
yoctocolors: 2.1.1
expect-type@1.2.0: {}
express@4.21.2:
@ -10570,6 +10764,10 @@ snapshots:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
figures@6.1.0:
dependencies:
is-unicode-supported: 2.1.0
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
@ -10740,6 +10938,11 @@ snapshots:
get-stream@8.0.1: {}
get-stream@9.0.1:
dependencies:
'@sec-ant/readable-stream': 0.4.1
is-stream: 4.0.1
get-symbol-description@1.0.2:
dependencies:
call-bind: 1.0.7
@ -10902,6 +11105,8 @@ snapshots:
human-signals@5.0.0: {}
human-signals@8.0.0: {}
humanize-ms@1.2.1:
dependencies:
ms: 2.1.3
@ -11060,6 +11265,8 @@ snapshots:
is-stream@3.0.0: {}
is-stream@4.0.1: {}
is-string@1.0.7:
dependencies:
has-tostringtag: 1.0.2
@ -11072,6 +11279,8 @@ snapshots:
dependencies:
which-typed-array: 1.1.15
is-unicode-supported@2.1.0: {}
is-weakmap@2.0.2: {}
is-weakref@1.0.2:
@ -11477,6 +11686,11 @@ snapshots:
dependencies:
path-key: 4.0.0
npm-run-path@6.0.0:
dependencies:
path-key: 4.0.0
unicorn-magic: 0.3.0
nypm@0.6.0:
dependencies:
citty: 0.1.6
@ -11667,6 +11881,8 @@ snapshots:
index-to-position: 0.1.2
type-fest: 4.35.0
parse-ms@4.0.0: {}
parseley@0.12.1:
dependencies:
leac: 0.6.0
@ -11806,6 +12022,10 @@ snapshots:
prettier@3.5.3: {}
pretty-ms@9.2.0:
dependencies:
parse-ms: 4.0.0
process-warning@4.0.1: {}
process@0.11.10: {}
@ -12317,6 +12537,8 @@ snapshots:
strip-final-newline@3.0.0: {}
strip-final-newline@4.0.0: {}
strip-indent@3.0.0:
dependencies:
min-indent: 1.0.1
@ -12602,6 +12824,8 @@ snapshots:
unicorn-magic@0.1.0: {}
unicorn-magic@0.3.0: {}
universal-github-app-jwt@2.2.0: {}
universal-user-agent@7.0.2: {}
@ -12670,7 +12894,7 @@ snapshots:
tsx: 4.19.3
yaml: 2.7.0
vitest@3.0.9(@types/node@22.13.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0):
vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0):
dependencies:
'@vitest/expect': 3.0.9
'@vitest/mocker': 3.0.9(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))
@ -12693,6 +12917,7 @@ snapshots:
vite-node: 3.0.9(@types/node@22.13.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 22.13.10
transitivePeerDependencies:
- jiti
@ -12860,6 +13085,8 @@ snapshots:
yocto-queue@0.1.0: {}
yoctocolors@2.1.1: {}
zod-to-json-schema@3.24.3(zod@3.24.2):
dependencies:
zod: 3.24.2