kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
Merge pull request #700 from transitive-bullshit/feature/upkeep-0
Major feature updates (MCP, JSON Schema support, arxiv, duckduckgo, openapi-to-ts)pull/704/head
commit
764a52690a
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"privatePackages": false,
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": ["@fisch0920/eslint-config/node"],
|
||||
"ignorePatterns": ["out"]
|
||||
"ignorePatterns": ["out", "packages/openapi-to-ts/fixtures/generated"]
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
out
|
||||
packages/openapi-to-ts/fixtures/generated
|
||||
|
|
|
@ -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/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -56,11 +56,13 @@
|
|||
"group": "Tools",
|
||||
"pages": [
|
||||
"tools/apollo",
|
||||
"tools/arxiv",
|
||||
"tools/bing",
|
||||
"tools/calculator",
|
||||
"tools/clearbit",
|
||||
"tools/dexa",
|
||||
"tools/diffbot",
|
||||
"tools/duck-duck-go",
|
||||
"tools/e2b",
|
||||
"tools/exa",
|
||||
"tools/firecrawl",
|
||||
|
@ -70,6 +72,7 @@
|
|||
"tools/jina",
|
||||
"tools/leadmagic",
|
||||
"tools/midjourney",
|
||||
"tools/mcp",
|
||||
"tools/novu",
|
||||
"tools/people-data-labs",
|
||||
"tools/perigon",
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: ArXiv
|
||||
description: Search for research articles.
|
||||
---
|
||||
|
||||
- package: `@agentic/arxiv`
|
||||
- exports: `class ArXivClient`, `namespace arxiv`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/arxiv/src/arxiv-client.ts)
|
||||
- [arxiv api docs](https://info.arxiv.org/help/api/index.html)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/arxiv
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/arxiv
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/arxiv
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { ArXivClient } from '@agentic/arxiv'
|
||||
|
||||
// No API is required to use the ArXiv API
|
||||
const arxiv = new ArXivClient()
|
||||
const results = await arxiv.search({
|
||||
query: 'machine learning',
|
||||
maxResults: 10
|
||||
})
|
||||
```
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
title: DuckDuckGo
|
||||
description: Search for research articles.
|
||||
---
|
||||
|
||||
- package: `@agentic/duck-duck-go`
|
||||
- exports: `class DuckDuckGoClient`, `namespace duckduckgo`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/duck-duck-go/src/duck-duck-go-client.ts)
|
||||
- [Duck Duck Go api docs](https://api.duckduckgo.com)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/duck-duck-go
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/duck-duck-go
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/duck-duck-go
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { DuckDuckGoClient } from '@agentic/duck-duck-go'
|
||||
|
||||
// No API is required to use the DuckDuckGo API
|
||||
const duckDuckGo = new DuckDuckGoClient()
|
||||
const results = await duckDuckGo.search({
|
||||
query: 'latest news about AI'
|
||||
})
|
||||
```
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
title: MCP Tools
|
||||
description: Agentic adapter for accessing tools defined by Model Context Protocol (MCP) servers.
|
||||
---
|
||||
|
||||
- package: `@agentic/mcp`
|
||||
- exports: `createMcpTools`, `class McpTools`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/mcp/src/mcp-tools.ts)
|
||||
- [MCP docs](https://modelcontextprotocol.io)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/mcp
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/mcp
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/mcp
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import 'dotenv/config'
|
||||
|
||||
import { createAISDKTools } from '@agentic/ai-sdk'
|
||||
import { createMcpTools } from '@agentic/mcp'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { generateText } from 'ai'
|
||||
|
||||
async function main() {
|
||||
// Create an MCP tools provider, which will start a local MCP server process
|
||||
// and use the stdio transport to communicate with it.
|
||||
const mcpTools = await createMcpTools({
|
||||
name: 'agentic-mcp-filesystem',
|
||||
serverProcess: {
|
||||
command: 'npx',
|
||||
args: [
|
||||
'-y',
|
||||
// This example uses a built-in example MCP server from Anthropic, which
|
||||
// provides a set of tools to access the local filesystem.
|
||||
'@modelcontextprotocol/server-filesystem',
|
||||
// Allow the MCP server to access the current working directory.
|
||||
process.cwd()
|
||||
// Feel free to add additional directories the tool should have access to.
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4o-mini'),
|
||||
tools: createAISDKTools(mcpTools),
|
||||
toolChoice: 'required',
|
||||
temperature: 0,
|
||||
system: 'You are a helpful assistant. Be as concise as possible.',
|
||||
prompt: 'What files are in the current directory?'
|
||||
})
|
||||
|
||||
console.log(result.toolResults[0])
|
||||
}
|
||||
|
||||
await main()
|
||||
```
|
||||
|
||||
### createMcpTools
|
||||
|
||||
`createMcpTools` creates a new `McpTools` instance by starting or connecting to an MCP server.
|
||||
|
||||
You must provide either an existing `transport`, an existing `serverUrl`, or a
|
||||
`serverProcess` to spawn.
|
||||
|
||||
All tools within the `McpTools` instance will be namespaced under the given `name`.
|
||||
|
||||
### JSON Schema
|
||||
|
||||
Note that `McpTools` uses JSON Schemas for toll input parameters, whereas most built-in tools use Zod schemas. This is important because some AI frameworks don't support JSON Schemas.
|
||||
|
||||
Currently, Mastra, Dexter, and xsAI don't support JSON Schema input parameters, so they won't work with `McpTools`.
|
|
@ -0,0 +1,44 @@
|
|||
import 'dotenv/config'
|
||||
|
||||
import { createAISDKTools } from '@agentic/ai-sdk'
|
||||
import { createMcpTools } from '@agentic/mcp'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { generateText } from 'ai'
|
||||
import { gracefulExit } from 'exit-hook'
|
||||
|
||||
async function main() {
|
||||
// Create an MCP tools provider, which will start a local MCP server process
|
||||
// and use the stdio transport to communicate with it.
|
||||
const mcpTools = await createMcpTools({
|
||||
name: 'agentic-mcp-filesystem',
|
||||
serverProcess: {
|
||||
command: 'npx',
|
||||
args: [
|
||||
'-y',
|
||||
'@modelcontextprotocol/server-filesystem',
|
||||
// Allow the MCP server to access the current working directory.
|
||||
process.cwd()
|
||||
// Feel free to add additional directories the tool should have access to.
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4o-mini'),
|
||||
tools: createAISDKTools(mcpTools),
|
||||
toolChoice: 'required',
|
||||
temperature: 0,
|
||||
system: 'You are a helpful assistant. Be as concise as possible.',
|
||||
prompt: 'What files are in the current directory?'
|
||||
})
|
||||
|
||||
console.log(result.toolResults[0]?.result || JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
gracefulExit(0)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
gracefulExit(1)
|
||||
}
|
|
@ -9,9 +9,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@agentic/ai-sdk": "workspace:*",
|
||||
"@agentic/mcp": "workspace:*",
|
||||
"@agentic/weather": "workspace:*",
|
||||
"@ai-sdk/openai": "catalog:",
|
||||
"ai": "catalog:",
|
||||
"exit-hook": "catalog:",
|
||||
"openai": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "turbo build",
|
||||
"dev": "turbo dev --concurrency 50 --continue",
|
||||
"dev": "turbo dev --continue",
|
||||
"docs": "cd docs && npx mintlify dev",
|
||||
"clean": "turbo clean",
|
||||
"fix": "run-s fix:*",
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { type AIFunctionLike, AIFunctionSet } from '@agentic/core'
|
||||
import { tool } from 'ai'
|
||||
import {
|
||||
type AIFunctionLike,
|
||||
AIFunctionSet,
|
||||
asAgenticSchema,
|
||||
isZodSchema
|
||||
} from '@agentic/core'
|
||||
import { jsonSchema, tool } from 'ai'
|
||||
|
||||
/**
|
||||
* Converts a set of Agentic stdlib AI functions to an object compatible with
|
||||
|
@ -13,8 +18,10 @@ export function createAISDKTools(...aiFunctionLikeTools: AIFunctionLike[]) {
|
|||
fn.spec.name,
|
||||
tool({
|
||||
description: fn.spec.description,
|
||||
parameters: fn.inputSchema,
|
||||
execute: fn.impl
|
||||
parameters: isZodSchema(fn.inputSchema)
|
||||
? fn.inputSchema
|
||||
: jsonSchema(asAgenticSchema(fn.inputSchema).jsonSchema),
|
||||
execute: fn.execute
|
||||
})
|
||||
])
|
||||
)
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "@agentic/arxiv",
|
||||
"version": "0.1.0",
|
||||
"description": "Agentic SDK for Arxiv.",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"fast-xml-parser": "catalog:",
|
||||
"ky": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentic/tsconfig": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -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>AI agent stdlib that works with any LLM and TypeScript AI SDK.</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)
|
|
@ -0,0 +1,243 @@
|
|||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
pruneEmpty,
|
||||
sanitizeSearchParams
|
||||
} from '@agentic/core'
|
||||
import { XMLParser } from 'fast-xml-parser'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { castArray, getProp } from './utils'
|
||||
|
||||
export namespace arxiv {
|
||||
export const API_BASE_URL = 'https://export.arxiv.org/api'
|
||||
|
||||
export const SortType = {
|
||||
RELEVANCE: 'relevance',
|
||||
LAST_UPDATED_DATE: 'lastUpdatedDate',
|
||||
SUBMITTED_DATE: 'submittedDate'
|
||||
} as const
|
||||
|
||||
export const SortOrder = {
|
||||
ASCENDING: 'ascending',
|
||||
DESCENDING: 'descending'
|
||||
} as const
|
||||
|
||||
export const FilterType = {
|
||||
ALL: 'all',
|
||||
TITLE: 'title',
|
||||
AUTHOR: 'author',
|
||||
ABSTRACT: 'abstract',
|
||||
COMMENT: 'comment',
|
||||
JOURNAL_REFERENCE: 'journal_reference',
|
||||
SUBJECT_CATEGORY: 'subject_category',
|
||||
REPORT_NUMBER: 'report_number'
|
||||
} as const
|
||||
|
||||
export type ValueOf<T extends NonNullable<unknown>> = T[keyof T]
|
||||
export const FilterTypeMapping: Record<ValueOf<typeof FilterType>, string> = {
|
||||
all: 'all',
|
||||
title: 'ti',
|
||||
author: 'au',
|
||||
abstract: 'abs',
|
||||
comment: 'co',
|
||||
journal_reference: 'jr',
|
||||
subject_category: 'cat',
|
||||
report_number: 'rn'
|
||||
}
|
||||
|
||||
export const Separators = {
|
||||
AND: '+AND+',
|
||||
OR: '+OR+',
|
||||
ANDNOT: '+ANDNOT+'
|
||||
} as const
|
||||
|
||||
export interface ArXivResponse {
|
||||
totalResults: number
|
||||
startIndex: number
|
||||
itemsPerPage: number
|
||||
entries: {
|
||||
id: string
|
||||
title: string
|
||||
summary: string
|
||||
published: string
|
||||
updated: string
|
||||
authors: { name: string; affiliation: string[] }[]
|
||||
doi: string
|
||||
comment: string
|
||||
journalReference: string
|
||||
primaryCategory: string
|
||||
categories: string[]
|
||||
links: string[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export const extractId = (value: string) =>
|
||||
value
|
||||
.replace('https://arxiv.org/abs/', '')
|
||||
.replace('https://arxiv.org/pdf/', '')
|
||||
.replace(/v\d$/, '')
|
||||
|
||||
const EntrySchema = z.object({
|
||||
field: z.nativeEnum(FilterType).default(FilterType.ALL),
|
||||
value: z.string().min(1)
|
||||
})
|
||||
|
||||
export const SearchParamsSchema = z
|
||||
.object({
|
||||
ids: z.array(z.string().min(1)).optional(),
|
||||
searchQuery: z
|
||||
.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
include: z
|
||||
.array(EntrySchema)
|
||||
.nonempty()
|
||||
.describe('Filters to include results.'),
|
||||
exclude: z
|
||||
.array(EntrySchema)
|
||||
.optional()
|
||||
.describe('Filters to exclude results.')
|
||||
})
|
||||
])
|
||||
.optional(),
|
||||
start: z.number().int().min(0).default(0),
|
||||
maxResults: z.number().int().min(1).max(100).default(5)
|
||||
})
|
||||
.describe('Sorting by date is not supported.')
|
||||
export type SearchParams = z.infer<typeof SearchParamsSchema>
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight wrapper around ArXiv for academic / scholarly research articles.
|
||||
*
|
||||
* @see https://arxiv.org
|
||||
*/
|
||||
export class ArXivClient extends AIFunctionsProvider {
|
||||
protected readonly ky: KyInstance
|
||||
protected readonly apiBaseUrl: string
|
||||
|
||||
constructor({
|
||||
apiBaseUrl = arxiv.API_BASE_URL,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
apiKey?: string
|
||||
apiBaseUrl?: string
|
||||
ky?: KyInstance
|
||||
}) {
|
||||
super()
|
||||
|
||||
this.apiBaseUrl = apiBaseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: this.apiBaseUrl
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for research articles published on arXiv.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'arxiv_search',
|
||||
description: 'Searches for research articles published on arXiv.',
|
||||
inputSchema: arxiv.SearchParamsSchema
|
||||
})
|
||||
async search(queryOrOpts: string | arxiv.SearchParams) {
|
||||
const opts =
|
||||
typeof queryOrOpts === 'string'
|
||||
? ({ searchQuery: queryOrOpts } as arxiv.SearchParams)
|
||||
: queryOrOpts
|
||||
|
||||
if (!opts.ids?.length && !opts.searchQuery) {
|
||||
throw new Error(
|
||||
`The 'searchQuery' property must be non-empty if the 'ids' property is not provided.`
|
||||
)
|
||||
}
|
||||
|
||||
const searchParams = sanitizeSearchParams({
|
||||
start: opts.start,
|
||||
max_results: opts.maxResults,
|
||||
id_list: opts.ids?.map(arxiv.extractId),
|
||||
search_query: opts.searchQuery
|
||||
? typeof opts.searchQuery === 'string'
|
||||
? opts.searchQuery
|
||||
: [
|
||||
opts.searchQuery.include
|
||||
.map(
|
||||
(tag) => `${arxiv.FilterTypeMapping[tag.field]}:${tag.value}`
|
||||
)
|
||||
.join(arxiv.Separators.AND),
|
||||
(opts.searchQuery.exclude ?? [])
|
||||
.map(
|
||||
(tag) => `${arxiv.FilterTypeMapping[tag.field]}:${tag.value}`
|
||||
)
|
||||
.join(arxiv.Separators.ANDNOT)
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(arxiv.Separators.ANDNOT)
|
||||
: undefined,
|
||||
sortBy: arxiv.SortType.RELEVANCE,
|
||||
sortOrder: arxiv.SortOrder.DESCENDING
|
||||
})
|
||||
|
||||
const responseText = await this.ky.get('query', { searchParams }).text()
|
||||
|
||||
const parser = new XMLParser({
|
||||
allowBooleanAttributes: true,
|
||||
alwaysCreateTextNode: false,
|
||||
attributeNamePrefix: '@_',
|
||||
attributesGroupName: false,
|
||||
cdataPropName: '#cdata',
|
||||
ignoreAttributes: true,
|
||||
numberParseOptions: { hex: false, leadingZeros: true },
|
||||
parseAttributeValue: false,
|
||||
parseTagValue: true,
|
||||
preserveOrder: false,
|
||||
removeNSPrefix: true,
|
||||
textNodeName: '#text',
|
||||
trimValues: true,
|
||||
ignoreDeclaration: true
|
||||
})
|
||||
|
||||
const parsedData = parser.parse(responseText)
|
||||
|
||||
let entries: Record<string, any>[] = getProp(
|
||||
parsedData,
|
||||
['feed', 'entry'],
|
||||
[]
|
||||
)
|
||||
entries = castArray(entries)
|
||||
|
||||
return {
|
||||
totalResults: Math.max(
|
||||
getProp(parsedData, ['feed', 'totalResults'], 0),
|
||||
entries.length
|
||||
),
|
||||
startIndex: getProp(parsedData, ['feed', 'startIndex'], 0),
|
||||
itemsPerPage: getProp(parsedData, ['feed', 'itemsPerPage'], 0),
|
||||
entries: entries.map((entry) =>
|
||||
pruneEmpty({
|
||||
id: arxiv.extractId(entry.id),
|
||||
url: entry.id,
|
||||
title: entry.title,
|
||||
summary: entry.summary,
|
||||
published: entry.published,
|
||||
updated: entry.updated,
|
||||
authors: castArray(entry.author)
|
||||
.filter(Boolean)
|
||||
.map((author: any) => ({
|
||||
name: author.name,
|
||||
affiliation: castArray(author.affiliation ?? [])
|
||||
})),
|
||||
doi: entry.doi,
|
||||
comment: entry.comment,
|
||||
journalReference: entry.journal_ref,
|
||||
primaryCategory: entry.primary_category,
|
||||
categories: castArray(entry.category).filter(Boolean),
|
||||
links: castArray(entry.link).filter(Boolean)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './arxiv-client'
|
|
@ -0,0 +1,30 @@
|
|||
export function hasProp<T>(
|
||||
target: T | undefined,
|
||||
key: keyof T
|
||||
): key is keyof T {
|
||||
return Boolean(target) && Object.prototype.hasOwnProperty.call(target, key)
|
||||
}
|
||||
|
||||
export function getProp(
|
||||
target: unknown,
|
||||
paths: readonly (keyof any)[],
|
||||
defaultValue: any = undefined
|
||||
) {
|
||||
let value: any = target
|
||||
if (!value) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (const key of paths) {
|
||||
if (!hasProp(value, key)) {
|
||||
return defaultValue
|
||||
}
|
||||
value = value[key]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function castArray<T>(arr: T) {
|
||||
const result = Array.isArray(arr) ? arr : [arr]
|
||||
return result as T extends unknown[] ? T : [T]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@agentic/tsconfig/base.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -2,28 +2,29 @@ import { describe, expect, test } from 'vitest'
|
|||
import { z } from 'zod'
|
||||
|
||||
import { createAIFunction } from './create-ai-function'
|
||||
import { type Msg } from './message'
|
||||
|
||||
const fullName = createAIFunction(
|
||||
{
|
||||
name: 'fullName',
|
||||
description: 'Returns the full name of a person.',
|
||||
inputSchema: z.object({
|
||||
first: z.string(),
|
||||
last: z.string()
|
||||
})
|
||||
},
|
||||
async ({ first, last }) => {
|
||||
// TODO: Add tests for passing JSON schema directly.
|
||||
|
||||
const fullNameAIFunction = createAIFunction({
|
||||
name: 'fullName',
|
||||
description: 'Returns the full name of a person.',
|
||||
inputSchema: z.object({
|
||||
first: z.string(),
|
||||
last: z.string()
|
||||
}),
|
||||
execute: ({ first, last }) => {
|
||||
return `${first} ${last}`
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('createAIFunction()', () => {
|
||||
test('exposes OpenAI function calling spec', () => {
|
||||
expect(fullName.spec.name).toEqual('fullName')
|
||||
expect(fullName.spec.description).toEqual(
|
||||
expect(fullNameAIFunction.spec.name).toEqual('fullName')
|
||||
expect(fullNameAIFunction.spec.description).toEqual(
|
||||
'Returns the full name of a person.'
|
||||
)
|
||||
expect(fullName.spec.parameters).toEqual({
|
||||
expect(fullNameAIFunction.spec.parameters).toEqual({
|
||||
properties: {
|
||||
first: { type: 'string' },
|
||||
last: { type: 'string' }
|
||||
|
@ -34,9 +35,22 @@ describe('createAIFunction()', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('executes the function', async () => {
|
||||
expect(await fullName('{"first": "John", "last": "Doe"}')).toEqual(
|
||||
'John Doe'
|
||||
)
|
||||
test('executes the function with JSON string', async () => {
|
||||
expect(
|
||||
await fullNameAIFunction('{"first": "John", "last": "Doe"}')
|
||||
).toEqual('John Doe')
|
||||
})
|
||||
|
||||
test('executes the function with OpenAI Message', async () => {
|
||||
const message: Msg.FuncCall = {
|
||||
role: 'assistant',
|
||||
content: null,
|
||||
function_call: {
|
||||
name: 'fullName',
|
||||
arguments: '{"first": "Jane", "last": "Smith"}'
|
||||
}
|
||||
}
|
||||
|
||||
expect(await fullNameAIFunction(message)).toEqual('Jane Smith')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,9 +1,37 @@
|
|||
import type { z } from 'zod'
|
||||
|
||||
import type * as types from './types'
|
||||
import { parseStructuredOutput } from './parse-structured-output'
|
||||
import { asAgenticSchema } from './schema'
|
||||
import { assert } from './utils'
|
||||
import { zodToJsonSchema } from './zod-to-json-schema'
|
||||
|
||||
export type CreateAIFunctionArgs<
|
||||
InputSchema extends types.AIFunctionInputSchema
|
||||
> = {
|
||||
/** Name of the function. */
|
||||
name: string
|
||||
|
||||
/** Description of the function. */
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Zod schema or AgenticSchema for the function parameters.
|
||||
*
|
||||
* You can use a JSON Schema for more dynamic tool sources such as MCP by
|
||||
* using the `createJsonSchema` utility function.
|
||||
*/
|
||||
inputSchema: InputSchema
|
||||
|
||||
/**
|
||||
* Whether to enable strict structured output generation based on the given
|
||||
* input schema. (this is a feature of the OpenAI API)
|
||||
*
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
strict?: boolean
|
||||
}
|
||||
|
||||
export type AIFunctionImplementation<
|
||||
InputSchema extends types.AIFunctionInputSchema,
|
||||
Output
|
||||
> = (params: types.inferInput<InputSchema>) => types.MaybePromise<Output>
|
||||
|
||||
/**
|
||||
* Create a function meant to be used with OpenAI tool or function calling.
|
||||
|
@ -14,79 +42,103 @@ import { zodToJsonSchema } from './zod-to-json-schema'
|
|||
* The `spec` property of the returned function is the spec for adding the
|
||||
* function to the OpenAI API `functions` property.
|
||||
*/
|
||||
export function createAIFunction<InputSchema extends z.ZodObject<any>, Output>(
|
||||
spec: {
|
||||
/** Name of the function. */
|
||||
name: string
|
||||
/** Description of the function. */
|
||||
description?: string
|
||||
/** Zod schema for the function parameters. */
|
||||
inputSchema: InputSchema
|
||||
/**
|
||||
* Whether or not to enable structured output generation based on the given
|
||||
* zod schema.
|
||||
*/
|
||||
strict?: boolean
|
||||
export function createAIFunction<
|
||||
InputSchema extends types.AIFunctionInputSchema,
|
||||
Output
|
||||
>(
|
||||
args: CreateAIFunctionArgs<InputSchema>,
|
||||
/** Underlying function implementation. */
|
||||
execute: AIFunctionImplementation<InputSchema, Output>
|
||||
): types.AIFunction<InputSchema, Output>
|
||||
export function createAIFunction<
|
||||
InputSchema extends types.AIFunctionInputSchema,
|
||||
Output
|
||||
>(
|
||||
args: CreateAIFunctionArgs<InputSchema> & {
|
||||
/** Underlying function implementation. */
|
||||
execute: AIFunctionImplementation<InputSchema, Output>
|
||||
}
|
||||
): types.AIFunction<InputSchema, Output>
|
||||
export function createAIFunction<
|
||||
InputSchema extends types.AIFunctionInputSchema,
|
||||
Output
|
||||
>(
|
||||
{
|
||||
name,
|
||||
description = '',
|
||||
inputSchema,
|
||||
strict = true,
|
||||
execute
|
||||
}: CreateAIFunctionArgs<InputSchema> & {
|
||||
/** Underlying function implementation. */
|
||||
execute?: AIFunctionImplementation<InputSchema, Output>
|
||||
},
|
||||
/** Implementation of the function to call with the parsed arguments. */
|
||||
implementation: (params: z.infer<InputSchema>) => types.MaybePromise<Output>
|
||||
/** Underlying function implementation. */
|
||||
executeArg?: AIFunctionImplementation<InputSchema, Output>
|
||||
): types.AIFunction<InputSchema, Output> {
|
||||
assert(spec.name, 'createAIFunction missing required "spec.name"')
|
||||
assert(name, 'createAIFunction missing required "name"')
|
||||
assert(inputSchema, 'createAIFunction missing required "inputSchema"')
|
||||
assert(
|
||||
spec.inputSchema,
|
||||
'createAIFunction missing required "spec.inputSchema"'
|
||||
execute || executeArg,
|
||||
'createAIFunction missing required "execute" function implementation'
|
||||
)
|
||||
assert(implementation, 'createAIFunction missing required "implementation"')
|
||||
assert(
|
||||
typeof implementation === 'function',
|
||||
'createAIFunction "implementation" must be a function'
|
||||
!(execute && executeArg),
|
||||
'createAIFunction: cannot provide both "execute" and a second function argument. there should only be one function implementation.'
|
||||
)
|
||||
execute ??= executeArg
|
||||
assert(
|
||||
execute,
|
||||
'createAIFunction missing required "execute" function implementation'
|
||||
)
|
||||
assert(
|
||||
typeof execute === 'function',
|
||||
'createAIFunction "execute" must be a function'
|
||||
)
|
||||
|
||||
const inputAgenticSchema = asAgenticSchema(inputSchema, { strict })
|
||||
|
||||
/** Parse the arguments string, optionally reading from a message. */
|
||||
const parseInput = (input: string | types.Msg) => {
|
||||
const parseInput = (
|
||||
input: string | types.Msg
|
||||
): types.inferInput<InputSchema> => {
|
||||
if (typeof input === 'string') {
|
||||
return parseStructuredOutput(input, spec.inputSchema)
|
||||
return inputAgenticSchema.parse(input)
|
||||
} else {
|
||||
const args = input.function_call?.arguments
|
||||
assert(
|
||||
args,
|
||||
`Missing required function_call.arguments for function ${spec.name}`
|
||||
`Missing required function_call.arguments for function ${name}`
|
||||
)
|
||||
return parseStructuredOutput(args, spec.inputSchema)
|
||||
return inputAgenticSchema.parse(args)
|
||||
}
|
||||
}
|
||||
|
||||
// Call the implementation function with the parsed arguments.
|
||||
// Call the underlying function implementation with the parsed arguments.
|
||||
const aiFunction: types.AIFunction<InputSchema, Output> = (
|
||||
input: string | types.Msg
|
||||
) => {
|
||||
const parsedInput = parseInput(input)
|
||||
|
||||
return implementation(parsedInput)
|
||||
return execute(parsedInput)
|
||||
}
|
||||
|
||||
// Override the default function name with the intended name.
|
||||
Object.defineProperty(aiFunction, 'name', {
|
||||
value: spec.name,
|
||||
value: name,
|
||||
writable: false
|
||||
})
|
||||
|
||||
const strict = !!spec.strict
|
||||
|
||||
aiFunction.inputSchema = spec.inputSchema
|
||||
aiFunction.inputSchema = inputSchema
|
||||
aiFunction.parseInput = parseInput
|
||||
aiFunction.execute = execute
|
||||
aiFunction.spec = {
|
||||
name: spec.name,
|
||||
description: spec.description?.trim() ?? '',
|
||||
parameters: zodToJsonSchema(spec.inputSchema, { strict }),
|
||||
name,
|
||||
description,
|
||||
parameters: inputAgenticSchema.jsonSchema,
|
||||
type: 'function',
|
||||
strict
|
||||
}
|
||||
aiFunction.impl = (
|
||||
params: z.infer<InputSchema>
|
||||
): types.MaybePromise<Output> => {
|
||||
return implementation(params)
|
||||
}
|
||||
|
||||
return aiFunction
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import type { z } from 'zod'
|
||||
|
||||
import type * as types from './types'
|
||||
import { AIFunctionSet } from './ai-function-set'
|
||||
import { createAIFunction } from './create-ai-function'
|
||||
|
@ -8,7 +6,7 @@ import { assert } from './utils'
|
|||
export interface PrivateAIFunctionMetadata {
|
||||
name: string
|
||||
description: string
|
||||
inputSchema: z.AnyZodObject
|
||||
inputSchema: types.AIFunctionInputSchema
|
||||
methodName: string
|
||||
strict?: boolean
|
||||
}
|
||||
|
@ -35,8 +33,15 @@ if (typeof Symbol === 'function' && Symbol.metadata) {
|
|||
}
|
||||
|
||||
export abstract class AIFunctionsProvider {
|
||||
private _functions?: AIFunctionSet
|
||||
protected _functions?: AIFunctionSet
|
||||
|
||||
/**
|
||||
* An `AIFunctionSet` containing all of the AI-compatible functions exposed
|
||||
* by this class.
|
||||
*
|
||||
* This property is useful for manipulating AI functions across multiple
|
||||
* sources, picking specific functions, ommitting certain functions, etc.
|
||||
*/
|
||||
get functions(): AIFunctionSet {
|
||||
if (!this._functions) {
|
||||
const metadata = this.constructor[Symbol.metadata]
|
||||
|
@ -46,7 +51,6 @@ export abstract class AIFunctionsProvider {
|
|||
)
|
||||
const invocables =
|
||||
(metadata?.invocables as PrivateAIFunctionMetadata[]) ?? []
|
||||
// console.log({ metadata, invocables })
|
||||
|
||||
const aiFunctions = invocables.map((invocable) => {
|
||||
const impl = (this as any)[invocable.methodName]
|
||||
|
@ -64,7 +68,7 @@ export abstract class AIFunctionsProvider {
|
|||
|
||||
export function aiFunction<
|
||||
This extends AIFunctionsProvider,
|
||||
InputSchema extends z.SomeZodObject,
|
||||
InputSchema extends types.AIFunctionInputSchema,
|
||||
OptionalArgs extends Array<undefined>,
|
||||
Return extends types.MaybePromise<any>
|
||||
>({
|
||||
|
@ -81,14 +85,14 @@ export function aiFunction<
|
|||
return (
|
||||
_targetMethod: (
|
||||
this: This,
|
||||
input: z.infer<InputSchema>,
|
||||
input: types.inferInput<InputSchema>,
|
||||
...optionalArgs: OptionalArgs
|
||||
) => Return,
|
||||
context: ClassMethodDecoratorContext<
|
||||
This,
|
||||
(
|
||||
this: This,
|
||||
input: z.infer<InputSchema>,
|
||||
input: types.inferInput<InputSchema>,
|
||||
...optionalArgs: OptionalArgs
|
||||
) => Return
|
||||
>
|
||||
|
@ -108,6 +112,23 @@ export function aiFunction<
|
|||
|
||||
context.addInitializer(function () {
|
||||
;(this as any)[methodName] = (this as any)[methodName].bind(this)
|
||||
// ;(this as any)[methodName].aiFunction = this.functions.get(
|
||||
// name ?? methodName
|
||||
// )
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// declare module './fns' {
|
||||
// // Define a type for methods decorated with @aiFunction
|
||||
// type AIFunctionMethod<
|
||||
// T extends z.ZodObject<any> = z.ZodObject<any>,
|
||||
// R = any
|
||||
// > = ((...args: any[]) => R) & {
|
||||
// aiFunction?: AIFunction<T, R>
|
||||
// }
|
||||
|
||||
// interface AIFunctionsProvider {
|
||||
// [methodName: string]: AIFunctionMethod<any, any>
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { expect, test } from 'vitest'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { asAgenticSchema, createJsonSchema, isZodSchema } from './schema'
|
||||
|
||||
test('isZodSchema', () => {
|
||||
expect(isZodSchema(z.object({}))).toBe(true)
|
||||
expect(isZodSchema({})).toBe(false)
|
||||
})
|
||||
|
||||
test('asAgenticSchema', () => {
|
||||
expect(asAgenticSchema(z.object({})).jsonSchema).toEqual({
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: false
|
||||
})
|
||||
expect(asAgenticSchema(createJsonSchema({})).jsonSchema).toEqual({})
|
||||
})
|
|
@ -10,7 +10,14 @@ import { zodToJsonSchema } from './zod-to-json-schema'
|
|||
*/
|
||||
export const schemaSymbol = Symbol('agentic.schema')
|
||||
|
||||
export type Schema<TData = unknown> = {
|
||||
/**
|
||||
* Structured schema used across Agentic, which wraps either a Zod schema or a
|
||||
* JSON Schema.
|
||||
*
|
||||
* JSON Schema support is important to support more dynamic tool sources such as
|
||||
* MCP.
|
||||
*/
|
||||
export type AgenticSchema<TData = unknown> = {
|
||||
/**
|
||||
* The JSON Schema.
|
||||
*/
|
||||
|
@ -39,9 +46,14 @@ export type Schema<TData = unknown> = {
|
|||
* Schema type for inference.
|
||||
*/
|
||||
_type: TData
|
||||
|
||||
/**
|
||||
* Source Zod schema if this object was created from a Zod schema.
|
||||
*/
|
||||
_source?: any
|
||||
}
|
||||
|
||||
export function isSchema(value: unknown): value is Schema {
|
||||
export function isAgenticSchema(value: unknown): value is AgenticSchema {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
|
@ -54,37 +66,51 @@ export function isSchema(value: unknown): value is Schema {
|
|||
|
||||
export function isZodSchema(value: unknown): value is z.ZodType {
|
||||
return (
|
||||
!!value &&
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
'_type' in value &&
|
||||
'_output' in value &&
|
||||
'_input' in value &&
|
||||
'_def' in value &&
|
||||
'parse' in value &&
|
||||
'safeParse' in value
|
||||
'~standard' in value &&
|
||||
(value['~standard'] as any)?.vendor === 'zod'
|
||||
)
|
||||
}
|
||||
|
||||
export function asSchema<TData>(
|
||||
schema: z.Schema<TData> | Schema<TData>,
|
||||
export function asAgenticSchema<TData>(
|
||||
schema: z.Schema<TData> | AgenticSchema<TData>,
|
||||
opts: { strict?: boolean } = {}
|
||||
): Schema<TData> {
|
||||
return isSchema(schema) ? schema : createSchemaFromZodSchema(schema, opts)
|
||||
): AgenticSchema<TData> {
|
||||
return isAgenticSchema(schema)
|
||||
? schema
|
||||
: createAgenticSchemaFromZodSchema(schema, opts)
|
||||
}
|
||||
|
||||
export function asZodOrJsonSchema<TData>(
|
||||
schema: z.Schema<TData> | AgenticSchema<TData>
|
||||
): z.Schema<TData> | types.JSONSchema {
|
||||
return isZodSchema(schema) ? schema : schema.jsonSchema
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a schema from a JSON Schema.
|
||||
* Create an AgenticSchema from a JSON Schema.
|
||||
*
|
||||
* All `AIFunction` input schemas accept either a Zod schema or a custom JSON
|
||||
* Schema. Use this function to wrap JSON schemas for use with `AIFunction`.
|
||||
*
|
||||
* Note that JSON Schemas are not validated by default, so you have to pass
|
||||
* in an optional `parse` function (using `ajv`, for instance) if you'd like to
|
||||
* validate them at runtime.
|
||||
*/
|
||||
export function createSchema<TData = unknown>(
|
||||
export function createJsonSchema<TData = unknown>(
|
||||
jsonSchema: types.JSONSchema,
|
||||
{
|
||||
parse = (value) => value as TData,
|
||||
safeParse
|
||||
safeParse,
|
||||
source
|
||||
}: {
|
||||
parse?: types.ParseFn<TData>
|
||||
safeParse?: types.SafeParseFn<TData>
|
||||
source?: any
|
||||
} = {}
|
||||
): Schema<TData> {
|
||||
): AgenticSchema<TData> {
|
||||
safeParse ??= (value: unknown) => {
|
||||
try {
|
||||
const result = parse(value)
|
||||
|
@ -99,18 +125,20 @@ export function createSchema<TData = unknown>(
|
|||
_type: undefined as TData,
|
||||
jsonSchema,
|
||||
parse,
|
||||
safeParse
|
||||
safeParse,
|
||||
_source: source
|
||||
}
|
||||
}
|
||||
|
||||
export function createSchemaFromZodSchema<TData>(
|
||||
export function createAgenticSchemaFromZodSchema<TData>(
|
||||
zodSchema: z.Schema<TData>,
|
||||
opts: { strict?: boolean } = {}
|
||||
): Schema<TData> {
|
||||
return createSchema(zodToJsonSchema(zodSchema, opts), {
|
||||
): AgenticSchema<TData> {
|
||||
return createJsonSchema(zodToJsonSchema(zodSchema, opts), {
|
||||
parse: (value) => {
|
||||
return parseStructuredOutput(value, zodSchema)
|
||||
}
|
||||
},
|
||||
source: zodSchema
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,10 @@ import type { z } from 'zod'
|
|||
import type { AIFunctionSet } from './ai-function-set'
|
||||
import type { AIFunctionsProvider } from './fns'
|
||||
import type { Msg } from './message'
|
||||
import type { AgenticSchema } from './schema'
|
||||
|
||||
export type { Msg } from './message'
|
||||
export type { Schema } from './schema'
|
||||
export type { AgenticSchema } from './schema'
|
||||
export type { KyInstance } from 'ky'
|
||||
export type { ThrottledFunction } from 'p-throttle'
|
||||
export type { SetOptional, SetRequired, Simplify } from 'type-fest'
|
||||
|
@ -19,7 +20,6 @@ export type DeepNullable<T> = T extends object
|
|||
|
||||
export type MaybePromise<T> = T | Promise<T>
|
||||
|
||||
// TODO: use a more specific type
|
||||
export type JSONSchema = Record<string, unknown>
|
||||
|
||||
export type RelaxedJsonifiable = Jsonifiable | Record<string, unknown>
|
||||
|
@ -52,6 +52,20 @@ export interface AIToolSpec {
|
|||
function: AIFunctionSpec
|
||||
}
|
||||
|
||||
/**
|
||||
* A Zod object schema or a custom schema created from a JSON schema via
|
||||
* `createSchema()`.
|
||||
*/
|
||||
export type AIFunctionInputSchema = z.ZodObject<any> | AgenticSchema<any>
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export type inferInput<InputSchema extends AIFunctionInputSchema> =
|
||||
InputSchema extends AgenticSchema<any>
|
||||
? InputSchema['_type']
|
||||
: InputSchema extends z.ZodTypeAny
|
||||
? z.infer<InputSchema>
|
||||
: never
|
||||
|
||||
/** The implementation of the function, with arg parsing and validation. */
|
||||
export type AIFunctionImpl<Return> = Omit<
|
||||
(input: string | Msg) => MaybePromise<Return>,
|
||||
|
@ -65,27 +79,32 @@ export type AIFunctionImpl<Return> = Omit<
|
|||
* via the `.functions` property
|
||||
* - `AIFunction` - Individual functions
|
||||
*/
|
||||
export type AIFunctionLike = AIFunctionsProvider | AIFunction | AIFunctionSet
|
||||
export type AIFunctionLike =
|
||||
| AIFunctionsProvider
|
||||
| AIFunction<AIFunctionInputSchema>
|
||||
| AIFunctionSet
|
||||
|
||||
/**
|
||||
* A function meant to be used with LLM function calling.
|
||||
*/
|
||||
export interface AIFunction<
|
||||
InputSchema extends z.ZodObject<any> = z.ZodObject<any>,
|
||||
// TODO
|
||||
// InputSchema extends AIFunctionInputSchema = z.ZodObject<any>,
|
||||
InputSchema extends AIFunctionInputSchema = AIFunctionInputSchema,
|
||||
Output = any
|
||||
> {
|
||||
/**
|
||||
* Invokes the underlying AI function `impl` but first validates the input
|
||||
* Invokes the underlying AI function `execute` but first validates the input
|
||||
* against this function's `inputSchema`. This method is callable and is
|
||||
* meant to be passed the raw LLM JSON string or an OpenAI-compatible Message.
|
||||
*/
|
||||
(input: string | Msg): MaybePromise<Output>
|
||||
|
||||
/** The Zod schema for the input object. */
|
||||
/** The schema for the input object (zod or custom schema). */
|
||||
inputSchema: InputSchema
|
||||
|
||||
/** Parse the function arguments from a message. */
|
||||
parseInput(input: string | Msg): z.infer<InputSchema>
|
||||
parseInput(input: string | Msg): inferInput<InputSchema>
|
||||
|
||||
/** The JSON schema function spec for the OpenAI API `functions` property. */
|
||||
spec: AIFunctionSpec
|
||||
|
@ -94,7 +113,7 @@ export interface AIFunction<
|
|||
* The underlying function implementation without any arg parsing or validation.
|
||||
*/
|
||||
// TODO: this `any` shouldn't be necessary, but it is for `createAIFunction` results to be assignable to `AIFunctionLike`
|
||||
impl: (params: z.infer<InputSchema> | any) => MaybePromise<Output>
|
||||
execute: (params: inferInput<InputSchema> | any) => MaybePromise<Output>
|
||||
}
|
||||
|
||||
export type SafeParseResult<TData> =
|
||||
|
|
|
@ -232,9 +232,18 @@ export function sanitizeSearchParams(
|
|||
|
||||
/**
|
||||
* Stringifies a JSON value in a way that's optimized for use with LLM prompts.
|
||||
*
|
||||
* Replacement for `JSON.stringify` when working with LLMs.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* stringifyForModel({ a: 1, b: 2 }) // '{"a":1,"b":2}'
|
||||
* ```
|
||||
*/
|
||||
export function stringifyForModel(
|
||||
jsonObject?: types.RelaxedJsonifiable
|
||||
jsonObject?: types.RelaxedJsonifiable,
|
||||
replacer: (number | string)[] | null = null,
|
||||
space: string | number = 0
|
||||
): string {
|
||||
if (jsonObject === undefined) {
|
||||
return ''
|
||||
|
@ -244,13 +253,13 @@ export function stringifyForModel(
|
|||
return jsonObject
|
||||
}
|
||||
|
||||
return JSON.stringify(jsonObject, null, 0)
|
||||
return JSON.stringify(jsonObject, replacer, space)
|
||||
}
|
||||
|
||||
const dedenter = dedent.withOptions({ escapeSpecialCharacters: true })
|
||||
|
||||
/**
|
||||
* Clean a string by removing extra newlines and indentation.
|
||||
* Cleans a string by removing extra newlines and indentation.
|
||||
*
|
||||
* @see: https://github.com/dmnd/dedent
|
||||
*/
|
||||
|
@ -264,7 +273,7 @@ export function isAIFunction(obj: any): obj is types.AIFunction {
|
|||
if (!obj.inputSchema) return false
|
||||
if (!obj.parseInput) return false
|
||||
if (!obj.spec) return false
|
||||
if (!obj.impl) return false
|
||||
if (!obj.execute) return false
|
||||
if (!obj.spec.name || typeof obj.spec.name !== 'string') return false
|
||||
|
||||
return true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type AIFunctionLike, AIFunctionSet } from '@agentic/core'
|
||||
import { type AIFunctionLike, AIFunctionSet, isZodSchema } from '@agentic/core'
|
||||
import { createAIFunction } from '@dexaai/dexter'
|
||||
|
||||
/**
|
||||
|
@ -10,14 +10,20 @@ export function createDexterFunctions(
|
|||
) {
|
||||
const fns = new AIFunctionSet(aiFunctionLikeTools)
|
||||
|
||||
return fns.map((fn) =>
|
||||
createAIFunction(
|
||||
return fns.map((fn) => {
|
||||
if (!isZodSchema(fn.inputSchema)) {
|
||||
throw new Error(
|
||||
`Dexter tools only support Zod schemas: ${fn.spec.name} tool uses a custom JSON Schema, which is currently not supported.`
|
||||
)
|
||||
}
|
||||
|
||||
return createAIFunction(
|
||||
{
|
||||
name: fn.spec.name,
|
||||
description: fn.spec.description,
|
||||
argsSchema: fn.inputSchema
|
||||
},
|
||||
fn.impl
|
||||
fn.execute
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "@agentic/duck-duck-go",
|
||||
"version": "7.5.3",
|
||||
"description": "Agentic SDK for DuckDuckGo.",
|
||||
"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",
|
||||
"module": "./dist/index.js",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"duck-duck-scrape": "^2.2.7",
|
||||
"string-strip-html": "^13.4.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentic/tsconfig": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -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>AI agent stdlib that works with any LLM and TypeScript AI SDK.</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)
|
|
@ -0,0 +1,78 @@
|
|||
import { aiFunction, AIFunctionsProvider } from '@agentic/core'
|
||||
import { SafeSearchType, search, type SearchOptions } from 'duck-duck-scrape'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { paginate } from './paginate'
|
||||
|
||||
export namespace duckduckgo {
|
||||
export interface DuckDuckGoSearchToolOptions {
|
||||
search?: SearchOptions
|
||||
maxResults: number
|
||||
}
|
||||
|
||||
export interface DuckDuckGoSearchToolRunOptions {
|
||||
search?: SearchOptions
|
||||
}
|
||||
}
|
||||
/**
|
||||
* DuckDuckGo search client.
|
||||
*
|
||||
* @see https://duckduckgo.com
|
||||
*/
|
||||
export class DuckDuckGoClient extends AIFunctionsProvider {
|
||||
/**
|
||||
* Searches the web using DuckDuckGo for a given query.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'duck_duck_go_search',
|
||||
description: 'Searches the web using DuckDuckGo for a given query.',
|
||||
inputSchema: z.object({
|
||||
query: z.string({ description: 'Search query' }).min(1).max(128),
|
||||
maxResults: z.number().min(1).max(100).optional()
|
||||
})
|
||||
})
|
||||
async search(
|
||||
queryOrOptions:
|
||||
| string
|
||||
| { query: string; maxResults?: number; search?: SearchOptions }
|
||||
) {
|
||||
const options =
|
||||
typeof queryOrOptions === 'string'
|
||||
? { query: queryOrOptions }
|
||||
: queryOrOptions
|
||||
|
||||
const results = await paginate({
|
||||
size: options.maxResults ?? 10,
|
||||
handler: async ({ cursor = 0 }) => {
|
||||
const { results: data, noResults: done } = await search(
|
||||
options.query,
|
||||
{
|
||||
safeSearch: SafeSearchType.MODERATE,
|
||||
...options.search,
|
||||
offset: cursor
|
||||
},
|
||||
{
|
||||
uri_modifier: (rawUrl: string) => {
|
||||
const url = new URL(rawUrl)
|
||||
url.searchParams.delete('ss_mkt')
|
||||
return url.toString()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
data,
|
||||
nextCursor: done ? undefined : cursor + data.length
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { stripHtml } = await import('string-strip-html')
|
||||
|
||||
return results.map((result) => ({
|
||||
url: result.url,
|
||||
title: stripHtml(result.title).result,
|
||||
description: stripHtml(result.description).result
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './duck-duck-go-client'
|
|
@ -0,0 +1,34 @@
|
|||
export interface PaginateInput<T, C> {
|
||||
size: number
|
||||
handler: (data: {
|
||||
cursor?: C
|
||||
limit: number
|
||||
}) => Promise<{ data: T[]; nextCursor?: C }>
|
||||
}
|
||||
|
||||
export async function paginate<T, C = number>(
|
||||
input: PaginateInput<T, C>
|
||||
): Promise<T[]> {
|
||||
const acc: T[] = []
|
||||
let cursor: C | undefined
|
||||
|
||||
while (acc.length < input.size) {
|
||||
const { data, nextCursor } = await input.handler({
|
||||
cursor,
|
||||
limit: input.size - acc.length
|
||||
})
|
||||
acc.push(...data)
|
||||
|
||||
if (nextCursor === undefined || data.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
cursor = nextCursor
|
||||
}
|
||||
|
||||
if (acc.length > input.size) {
|
||||
acc.length = input.size
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@agentic/tsconfig/base.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -1,5 +1,10 @@
|
|||
import type { Genkit } from 'genkit'
|
||||
import { type AIFunctionLike, AIFunctionSet } from '@agentic/core'
|
||||
import {
|
||||
type AIFunctionLike,
|
||||
AIFunctionSet,
|
||||
asZodOrJsonSchema,
|
||||
isZodSchema
|
||||
} from '@agentic/core'
|
||||
import { z } from 'zod'
|
||||
|
||||
/**
|
||||
|
@ -12,15 +17,19 @@ export function createGenkitTools(
|
|||
) {
|
||||
const fns = new AIFunctionSet(aiFunctionLikeTools)
|
||||
|
||||
return fns.map((fn) =>
|
||||
genkit.defineTool(
|
||||
return fns.map((fn) => {
|
||||
const inputSchemaKey = isZodSchema(fn.inputSchema)
|
||||
? ('inputSchema' as const)
|
||||
: ('inputJsonSchema' as const)
|
||||
|
||||
return genkit.defineTool(
|
||||
{
|
||||
name: fn.spec.name,
|
||||
description: fn.spec.description,
|
||||
inputSchema: fn.inputSchema,
|
||||
[inputSchemaKey]: asZodOrJsonSchema(fn.inputSchema),
|
||||
outputSchema: z.any()
|
||||
},
|
||||
fn.impl
|
||||
fn.execute
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
type AIFunctionLike,
|
||||
AIFunctionSet,
|
||||
asZodOrJsonSchema,
|
||||
stringifyForModel
|
||||
} from '@agentic/core'
|
||||
import { DynamicStructuredTool } from '@langchain/core/tools'
|
||||
|
@ -17,9 +18,9 @@ export function createLangChainTools(...aiFunctionLikeTools: AIFunctionLike[]) {
|
|||
new DynamicStructuredTool({
|
||||
name: fn.spec.name,
|
||||
description: fn.spec.description,
|
||||
schema: fn.inputSchema,
|
||||
schema: asZodOrJsonSchema(fn.inputSchema),
|
||||
func: async (input) => {
|
||||
const result = await Promise.resolve(fn.impl(input))
|
||||
const result = await Promise.resolve(fn.execute(input))
|
||||
// LangChain tools require the output to be a string
|
||||
return stringifyForModel(result)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { type AIFunctionLike, AIFunctionSet } from '@agentic/core'
|
||||
import {
|
||||
type AIFunctionLike,
|
||||
AIFunctionSet,
|
||||
asZodOrJsonSchema
|
||||
} from '@agentic/core'
|
||||
import { FunctionTool } from 'llamaindex'
|
||||
|
||||
/**
|
||||
|
@ -11,10 +15,11 @@ export function createLlamaIndexTools(
|
|||
const fns = new AIFunctionSet(aiFunctionLikeTools)
|
||||
|
||||
return fns.map((fn) =>
|
||||
FunctionTool.from(fn.impl, {
|
||||
FunctionTool.from(fn.execute, {
|
||||
name: fn.spec.name,
|
||||
description: fn.spec.description,
|
||||
parameters: fn.spec.parameters as any
|
||||
// TODO: Investigate types here
|
||||
parameters: asZodOrJsonSchema(fn.inputSchema) as any
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type AIFunctionLike, AIFunctionSet } from '@agentic/core'
|
||||
import { type AIFunctionLike, AIFunctionSet, isZodSchema } from '@agentic/core'
|
||||
import { createTool } from '@mastra/core/tools'
|
||||
|
||||
/**
|
||||
|
@ -9,14 +9,22 @@ export function createMastraTools(...aiFunctionLikeTools: AIFunctionLike[]) {
|
|||
const fns = new AIFunctionSet(aiFunctionLikeTools)
|
||||
|
||||
return Object.fromEntries(
|
||||
fns.map((fn) => [
|
||||
fn.spec.name,
|
||||
createTool({
|
||||
id: fn.spec.name,
|
||||
description: fn.spec.description,
|
||||
inputSchema: fn.inputSchema,
|
||||
execute: (ctx) => fn.impl(ctx.context)
|
||||
})
|
||||
])
|
||||
fns.map((fn) => {
|
||||
if (!isZodSchema(fn.inputSchema)) {
|
||||
throw new Error(
|
||||
`Mastra tools only support Zod schemas: ${fn.spec.name} tool uses a custom JSON Schema, which is currently not supported.`
|
||||
)
|
||||
}
|
||||
|
||||
return [
|
||||
fn.spec.name,
|
||||
createTool({
|
||||
id: fn.spec.name,
|
||||
description: fn.spec.description,
|
||||
inputSchema: fn.inputSchema,
|
||||
execute: (ctx) => fn.execute(ctx.context)
|
||||
})
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "@agentic/mcp",
|
||||
"version": "0.1.0",
|
||||
"description": "Agentic SDK wrapping an MCP client.",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"@modelcontextprotocol/sdk": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentic/tsconfig": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -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>AI agent stdlib that works with any LLM and TypeScript AI SDK.</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)
|
|
@ -0,0 +1,2 @@
|
|||
export * from './mcp-tools'
|
||||
export type * from './types'
|
|
@ -0,0 +1,216 @@
|
|||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
||||
import type {
|
||||
CallToolResult,
|
||||
ListToolsResult
|
||||
} from '@modelcontextprotocol/sdk/types.js'
|
||||
import {
|
||||
AIFunctionSet,
|
||||
AIFunctionsProvider,
|
||||
assert,
|
||||
createAIFunction,
|
||||
createJsonSchema
|
||||
} from '@agentic/core'
|
||||
import { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import { type z } from 'zod'
|
||||
|
||||
import type { McpClientOptions, McpToolsFilter } from './types'
|
||||
import { paginate } from './paginate'
|
||||
|
||||
/**
|
||||
* Agentic tools provider wrapping an MCP client.
|
||||
*
|
||||
* You likely want to use `createMcpTools` to create an instance of `McpTools`
|
||||
* which enables exposing MCP server tools to the agentic ecosystem.
|
||||
*
|
||||
* @see https://modelcontextprotocol.io
|
||||
*/
|
||||
export class McpTools extends AIFunctionsProvider {
|
||||
readonly name: string
|
||||
readonly client: McpClient
|
||||
readonly rawToolResponses: boolean
|
||||
|
||||
protected _toolsMap: Map<string, ListToolsResult['tools'][number]> | undefined
|
||||
protected readonly _toolsFilter: McpToolsFilter | undefined
|
||||
|
||||
protected constructor({
|
||||
name,
|
||||
client,
|
||||
toolsFilter,
|
||||
rawToolResponses = false
|
||||
}: {
|
||||
client: McpClient
|
||||
} & McpClientOptions) {
|
||||
super()
|
||||
|
||||
this.name = name
|
||||
this.client = client
|
||||
this.rawToolResponses = rawToolResponses
|
||||
|
||||
this._toolsFilter = toolsFilter
|
||||
}
|
||||
|
||||
override get functions(): AIFunctionSet {
|
||||
assert(this._functions)
|
||||
return this._functions
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the McpTools instance by fetching all available tools from the MCP client.
|
||||
* This method must be called before using this class' tools.
|
||||
* It is called automatically when using `McpTools.from()`.
|
||||
*/
|
||||
protected async _init() {
|
||||
const capabilties = this.client.getServerCapabilities()
|
||||
const initPromises: Promise<any>[] = []
|
||||
|
||||
if (capabilties?.tools) {
|
||||
initPromises.push(this._initTools())
|
||||
}
|
||||
|
||||
// TODO: handle prompts, resources, etc.
|
||||
await Promise.all(initPromises)
|
||||
}
|
||||
|
||||
protected async _initTools() {
|
||||
const tools = await paginate({
|
||||
size: Infinity,
|
||||
handler: async ({ cursor }: { cursor?: string }) => {
|
||||
const { tools, nextCursor } = await this.client.listTools({ cursor })
|
||||
return { data: tools, nextCursor } as const
|
||||
}
|
||||
})
|
||||
|
||||
const enabledTools = this._toolsFilter
|
||||
? tools.filter((tool) => this._toolsFilter!(tool.name))
|
||||
: tools
|
||||
|
||||
this._toolsMap = new Map(enabledTools.map((tool) => [tool.name, tool]))
|
||||
this._updateFunctions()
|
||||
}
|
||||
|
||||
protected _updateFunctions() {
|
||||
assert(this._toolsMap)
|
||||
|
||||
this._functions = new AIFunctionSet(
|
||||
Array.from(this._toolsMap.entries()).map(([_name, tool]) => {
|
||||
return createAIFunction(
|
||||
{
|
||||
name: `${this.name}_${tool.name}`,
|
||||
description: tool.description,
|
||||
inputSchema: createJsonSchema(tool.inputSchema),
|
||||
strict: true
|
||||
},
|
||||
async (args) => {
|
||||
const result = await this.client.callTool({
|
||||
name: tool.name,
|
||||
arguments: args
|
||||
})
|
||||
|
||||
if (this.rawToolResponses) {
|
||||
return result
|
||||
}
|
||||
|
||||
return processToolCallResult(result as CallToolResult)
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async callTool(name: string, args: z.infer<z.ZodObject<any>>) {
|
||||
const tool =
|
||||
this._toolsMap?.get(name) ?? this._toolsMap?.get(`${this.name}_${name}`)
|
||||
assert(tool, `Tool ${name} not found`)
|
||||
|
||||
const result = await this.client.callTool({ name, arguments: args })
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new McpTools instance from an existing, fully initialized
|
||||
* MCP client.
|
||||
*
|
||||
* You probably want to use `createMcpTool` instead, which makes initializing
|
||||
* the MCP client and connecting to its transport easier.
|
||||
*
|
||||
* All tools within the `McpTools` instance will be namespaced under the given
|
||||
* `name`.
|
||||
*/
|
||||
static async fromMcpClient(params: { client: McpClient } & McpClientOptions) {
|
||||
const mcpTools = new McpTools(params)
|
||||
await mcpTools._init()
|
||||
return mcpTools
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new McpTools instance by connecting to an MCP server. You must
|
||||
* provide either an existing `transport`, an existing `serverUrl`, or a
|
||||
* `serverProcess` to spawn.
|
||||
*
|
||||
* All tools within the `McpTools` instance will be namespaced under the given
|
||||
* `name`.
|
||||
*/
|
||||
export async function createMcpTools(
|
||||
params: McpClientOptions
|
||||
): Promise<McpTools> {
|
||||
const transport = await createMcpTransport(params)
|
||||
const client = new McpClient(
|
||||
{ name: params.name, version: params.version || '1.0.0' },
|
||||
{ capabilities: {} }
|
||||
)
|
||||
await client.connect(transport)
|
||||
|
||||
return McpTools.fromMcpClient({ client, ...params })
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MCP transport from either an existing `transport`, an existing
|
||||
* `serverUrl`, or a `serverProcess` to spawn.
|
||||
*/
|
||||
export async function createMcpTransport(
|
||||
params: McpClientOptions
|
||||
): Promise<Transport> {
|
||||
if (params.transport) return params.transport
|
||||
|
||||
if (params.serverUrl) {
|
||||
const { SSEClientTransport } = await import(
|
||||
'@modelcontextprotocol/sdk/client/sse.js'
|
||||
)
|
||||
return new SSEClientTransport(new URL(params.serverUrl))
|
||||
}
|
||||
|
||||
if (params.serverProcess) {
|
||||
const { StdioClientTransport } = await import(
|
||||
'@modelcontextprotocol/sdk/client/stdio.js'
|
||||
)
|
||||
return new StdioClientTransport(params.serverProcess)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Unable to create a server connection with supplied options. Must provide transport, stdio, or sseUrl.'
|
||||
)
|
||||
}
|
||||
|
||||
function toText(c: CallToolResult['content']) {
|
||||
return c.map((p) => p.text || '').join('')
|
||||
}
|
||||
|
||||
function processToolCallResult(result: CallToolResult) {
|
||||
if (result.isError) return { error: toText(result.content) }
|
||||
|
||||
if (result.content.every((c) => !!c.text)) {
|
||||
const text = toText(result.content)
|
||||
if (text.trim().startsWith('{') || text.trim().startsWith('[')) {
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return text
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
if (result.content.length === 1) return result.content[0]
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
export interface PaginateInput<T, C> {
|
||||
size: number
|
||||
handler: (data: {
|
||||
cursor?: C
|
||||
limit: number
|
||||
}) => Promise<{ data: T[]; nextCursor?: C }>
|
||||
}
|
||||
|
||||
export async function paginate<T, C = number>(
|
||||
input: PaginateInput<T, C>
|
||||
): Promise<T[]> {
|
||||
const acc: T[] = []
|
||||
let cursor: C | undefined
|
||||
|
||||
while (acc.length < input.size) {
|
||||
const { data, nextCursor } = await input.handler({
|
||||
cursor,
|
||||
limit: input.size - acc.length
|
||||
})
|
||||
acc.push(...data)
|
||||
|
||||
if (nextCursor === undefined || data.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
cursor = nextCursor
|
||||
}
|
||||
|
||||
if (acc.length > input.size) {
|
||||
acc.length = input.size
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import type { StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js'
|
||||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
||||
|
||||
export type McpToolsFilter = (toolName: string) => boolean
|
||||
|
||||
export interface McpClientOptions {
|
||||
/**
|
||||
* Provide a name for this client which will be its namespace for all tools and prompts.
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* Provide a version number for this client (defaults to 1.0.0).
|
||||
*/
|
||||
version?: string
|
||||
|
||||
/**
|
||||
* If you already have an MCP transport you'd like to use, pass it here to connect to the server.
|
||||
*/
|
||||
transport?: Transport
|
||||
|
||||
/**
|
||||
* Start a local server process using the stdio MCP transport.
|
||||
*/
|
||||
serverProcess?: StdioServerParameters
|
||||
|
||||
/**
|
||||
* Connect to a remote server process using the SSE MCP transport.
|
||||
*/
|
||||
serverUrl?: string
|
||||
|
||||
/**
|
||||
* Return tool responses in raw MCP form instead of processing them for Genkit compatibility.
|
||||
*/
|
||||
rawToolResponses?: boolean
|
||||
|
||||
/**
|
||||
* An optional filter function to determine which tools should be enabled.
|
||||
*
|
||||
* By default, all tools available on the MCP server will be enabled, but you
|
||||
* can use this to filter a subset of those tools.
|
||||
*/
|
||||
toolsFilter?: McpToolsFilter
|
||||
}
|
||||
|
||||
// TODO
|
||||
// export interface McpServerOptions {
|
||||
// /** The name you want to give your server for MCP inspection. */
|
||||
// name: string
|
||||
//
|
||||
// /** The version you want the server to advertise to clients. Defaults to 1.0.0. */
|
||||
// version?: string
|
||||
// }
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@agentic/tsconfig/base.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import { cli } from 'cleye'
|
||||
import { gracefulExit } from 'exit-hook'
|
||||
|
||||
import { generateTSFromOpenAPI } from '../src'
|
||||
|
||||
async function main() {
|
||||
const args = cli(
|
||||
{
|
||||
name: 'openapi-to-ts',
|
||||
parameters: ['<openapi file path>'],
|
||||
flags: {
|
||||
debug: {
|
||||
type: Boolean,
|
||||
description: 'Enables verbose debug logging',
|
||||
alias: 'v',
|
||||
default: false
|
||||
},
|
||||
outputDir: {
|
||||
type: String,
|
||||
description: 'Path to the output directory (defaults to cwd)',
|
||||
alias: 'o'
|
||||
},
|
||||
dryRun: {
|
||||
type: Boolean,
|
||||
description: 'Disables all side effects',
|
||||
default: false
|
||||
},
|
||||
noPrettier: {
|
||||
type: Boolean,
|
||||
description: 'Disables prettier formatting',
|
||||
default: false
|
||||
},
|
||||
noEslint: {
|
||||
type: Boolean,
|
||||
description: 'Disables eslint formatting',
|
||||
default: false
|
||||
}
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
process.argv
|
||||
)
|
||||
|
||||
const openapiFilePath = args._[2]!
|
||||
|
||||
if (!openapiFilePath) {
|
||||
console.error('Missing required argument: <openapi file path>\n')
|
||||
args.showHelp()
|
||||
gracefulExit(1)
|
||||
return
|
||||
}
|
||||
|
||||
const output = await generateTSFromOpenAPI({
|
||||
openapiFilePath,
|
||||
outputDir: args.flags.outputDir || process.cwd(),
|
||||
dryRun: args.flags.dryRun,
|
||||
prettier: !args.flags.noPrettier,
|
||||
eslint: !args.flags.noEslint
|
||||
})
|
||||
|
||||
if (args.flags.dryRun) {
|
||||
console.log(output)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
gracefulExit(0)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
gracefulExit(1)
|
||||
}
|
|
@ -0,0 +1,619 @@
|
|||
/* eslint-disable unicorn/no-unreadable-iife */
|
||||
/* eslint-disable unicorn/no-array-reduce */
|
||||
|
||||
/**
|
||||
* 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'
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Component schemas
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Operation schemas
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
* Agentic Firecrawl client.
|
||||
*
|
||||
* API for interacting with Firecrawl services to perform web scraping and crawling tasks.
|
||||
*/
|
||||
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>()
|
||||
}
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,142 @@
|
|||
/* eslint-disable unicorn/no-unreadable-iife */
|
||||
/* eslint-disable unicorn/no-array-reduce */
|
||||
|
||||
/**
|
||||
* 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'
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Component schemas
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Operation schemas
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
/**
|
||||
* Agentic PetStore client.
|
||||
*/
|
||||
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>()
|
||||
}
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Swagger Petstore",
|
||||
"description": "A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification",
|
||||
"termsOfService": "http://swagger.io/terms/",
|
||||
"contact": {
|
||||
"name": "Swagger API Team",
|
||||
"email": "apiteam@swagger.io",
|
||||
"url": "http://swagger.io"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://petstore.swagger.io/api"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/pets": {
|
||||
"get": {
|
||||
"description": "Returns all pets from the system that the user has access to\nNam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.\n\nSed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.",
|
||||
"operationId": "findPets",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tags",
|
||||
"in": "query",
|
||||
"description": "tags to filter by",
|
||||
"required": false,
|
||||
"style": "form",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "maximum number of results to return",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "pet response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "unexpected error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new pet in the store. Duplicates are allowed",
|
||||
"operationId": "addPet",
|
||||
"requestBody": {
|
||||
"description": "Pet to add to the store",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NewPet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "pet response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "unexpected error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pets/{id}": {
|
||||
"get": {
|
||||
"description": "Returns a user based on a single ID, if the user does not have access to the pet",
|
||||
"operationId": "find pet by id",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"description": "ID of pet to fetch",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "pet response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "unexpected error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "deletes a single pet based on the ID supplied",
|
||||
"operationId": "deletePet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"description": "ID of pet to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "pet deleted"
|
||||
},
|
||||
"default": {
|
||||
"description": "unexpected error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Pet": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NewPet"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"NewPet": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"required": ["code", "message"],
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,392 @@
|
|||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Support for different security types",
|
||||
"description": "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securitySchemeObject"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://httpbin.org"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"name": "API Key"
|
||||
},
|
||||
{
|
||||
"name": "HTTP"
|
||||
},
|
||||
{
|
||||
"name": "OAuth 2"
|
||||
},
|
||||
{
|
||||
"name": "OpenID Connect"
|
||||
},
|
||||
{
|
||||
"name": "Other"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/anything/apiKey": {
|
||||
"get": {
|
||||
"summary": "Query parameter",
|
||||
"description": "`apiKey` auth will be supplied within an `apiKey` query parameter.",
|
||||
"tags": ["API Key"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_query": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Cookie",
|
||||
"description": "`apiKey` auth will be supplied within an `api_key` cookie.",
|
||||
"tags": ["API Key"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_cookie": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Header",
|
||||
"description": "`apiKey` auth will be supplied within an `X-API-KEY` header.",
|
||||
"tags": ["API Key"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_header": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/basic": {
|
||||
"post": {
|
||||
"summary": "Basic",
|
||||
"description": "Authentication credentials will be supplied within a `Basic` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample",
|
||||
"tags": ["HTTP"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"basic": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/bearer": {
|
||||
"post": {
|
||||
"summary": "Bearer",
|
||||
"description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample",
|
||||
"tags": ["HTTP"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Bearer (`jwt` format)",
|
||||
"description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header, but its data should be controlled as a JWT.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample\n\n> ℹ️\n> We currently do not support any special handling for this so they're handled as a standard `Bearer` authentication token.",
|
||||
"tags": ["HTTP"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer_jwt": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/oauth2": {
|
||||
"post": {
|
||||
"summary": "General support (all flow types)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"summary": "General support (authorizationCode flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_authorizationCode": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "General support (clientCredentials flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_clientCredentials": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "General support (implicit flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_implicit": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "General support (password flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_password": ["write:things"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/openIdConnect": {
|
||||
"post": {
|
||||
"summary": "General support",
|
||||
"description": "🚧 This is not supported.",
|
||||
"tags": ["OpenID Connect"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"openIdConnect": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/no-auth": {
|
||||
"post": {
|
||||
"summary": "No auth requirements",
|
||||
"description": "This operation does not have any authentication requirements.",
|
||||
"tags": ["Other"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/anything/optional-auth": {
|
||||
"get": {
|
||||
"summary": "Optional auth",
|
||||
"description": "The `apiKey` query parameter auth on this operation is optional.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object",
|
||||
"tags": ["Other"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_query": []
|
||||
},
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/status/401": {
|
||||
"post": {
|
||||
"summary": "Forced invalid authentication",
|
||||
"description": "This endpoint requires an authentication header but making any request to it will forcefully return a 401 status code for invalid auth.",
|
||||
"tags": ["Other"],
|
||||
"responses": {
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_header": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"apiKey_cookie": {
|
||||
"type": "apiKey",
|
||||
"in": "cookie",
|
||||
"name": "api_key",
|
||||
"description": "An API key that will be supplied in a named cookie. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object"
|
||||
},
|
||||
"apiKey_header": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-API-KEY",
|
||||
"description": "An API key that will be supplied in a named header. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object"
|
||||
},
|
||||
"apiKey_query": {
|
||||
"type": "apiKey",
|
||||
"in": "query",
|
||||
"name": "apiKey",
|
||||
"description": "An API key that will be supplied in a named query parameter. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object"
|
||||
},
|
||||
"basic": {
|
||||
"type": "http",
|
||||
"scheme": "basic",
|
||||
"description": "Basic auth that takes a base64'd combination of `user:password`. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample"
|
||||
},
|
||||
"bearer": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"description": "A bearer token that will be supplied within an `Authentication` header as `bearer <token>`. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample"
|
||||
},
|
||||
"bearer_jwt": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT",
|
||||
"description": "A bearer token that will be supplied within an `Authentication` header as `bearer <token>`. In this case, the format of the token is specified as JWT. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#jwt-bearer-sample"
|
||||
},
|
||||
"oauth2": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23",
|
||||
"flows": {
|
||||
"authorizationCode": {
|
||||
"authorizationUrl": "http://example.com/oauth/dialog",
|
||||
"tokenUrl": "http://example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
},
|
||||
"clientCredentials": {
|
||||
"tokenUrl": "http://example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
},
|
||||
"implicit": {
|
||||
"authorizationUrl": "http://example.com/oauth/dialog",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"tokenUrl": "http://example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_authorizationCode": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `authorizationCode` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"authorizationCode": {
|
||||
"authorizationUrl": "http://alt.example.com/oauth/dialog",
|
||||
"tokenUrl": "http://alt.example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_clientCredentials": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `clientCredentials` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"clientCredentials": {
|
||||
"tokenUrl": "http://alt.example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_implicit": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `implicit` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"implicit": {
|
||||
"authorizationUrl": "http://alt.example.com/oauth/dialog",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_password": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `password` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"password": {
|
||||
"tokenUrl": "http://alt.example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"openIdConnect": {
|
||||
"type": "openIdConnect",
|
||||
"openIdConnectUrl": "https://example.com/.well-known/openid-configuration",
|
||||
"description": "OpenAPI authentication. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,988 @@
|
|||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
|
||||
"version": "1.0.0",
|
||||
"title": "Swagger Petstore",
|
||||
"termsOfService": "http://swagger.io/terms/",
|
||||
"contact": {
|
||||
"email": "apiteam@swagger.io"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://petstore.swagger.io/v2"
|
||||
}
|
||||
],
|
||||
"externalDocs": {
|
||||
"description": "Find out more about Swagger",
|
||||
"url": "http://swagger.io"
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "pet",
|
||||
"description": "Everything about your Pets",
|
||||
"externalDocs": {
|
||||
"description": "Find out more",
|
||||
"url": "http://swagger.io"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "store",
|
||||
"description": "Access to Petstore orders"
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"description": "Operations about user",
|
||||
"externalDocs": {
|
||||
"description": "Find out more about our store",
|
||||
"url": "http://swagger.io"
|
||||
}
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/pet": {
|
||||
"post": {
|
||||
"tags": ["pet"],
|
||||
"summary": "Add a new pet to the store",
|
||||
"description": "",
|
||||
"operationId": "addPet",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"405": {
|
||||
"description": "Invalid input"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"petstore_auth": ["write:pets", "read:pets"]
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/Pet"
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": ["pet"],
|
||||
"summary": "Update an existing pet",
|
||||
"description": "",
|
||||
"operationId": "updatePet",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
},
|
||||
"405": {
|
||||
"description": "Validation exception"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"petstore_auth": ["write:pets", "read:pets"]
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pet/findByStatus": {
|
||||
"get": {
|
||||
"tags": ["pet"],
|
||||
"summary": "Finds Pets by status",
|
||||
"description": "Multiple status values can be provided with comma separated strings",
|
||||
"operationId": "findPetsByStatus",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"description": "Status values that need to be considered for filter",
|
||||
"required": true,
|
||||
"explode": true,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["available", "pending", "sold"],
|
||||
"default": "available"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid status value"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"petstore_auth": ["write:pets", "read:pets"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/pet/findByTags": {
|
||||
"get": {
|
||||
"tags": ["pet"],
|
||||
"summary": "Finds Pets by tags",
|
||||
"description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
|
||||
"operationId": "findPetsByTags",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tags",
|
||||
"in": "query",
|
||||
"description": "Tags to filter by",
|
||||
"required": true,
|
||||
"explode": true,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid tag value"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"petstore_auth": ["write:pets", "read:pets"]
|
||||
}
|
||||
],
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"/pet/{petId}": {
|
||||
"get": {
|
||||
"tags": ["pet"],
|
||||
"summary": "Find pet by ID",
|
||||
"description": "Returns a single pet",
|
||||
"operationId": "getPetById",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of pet to return",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
},
|
||||
"default": {
|
||||
"description": "successful response"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": ["pet"],
|
||||
"summary": "Updates a pet in the store with form data",
|
||||
"description": "",
|
||||
"operationId": "updatePetWithForm",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of pet that needs to be updated",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"405": {
|
||||
"description": "Invalid input"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"petstore_auth": ["write:pets", "read:pets"]
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Updated name of the pet",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Updated status of the pet",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["pet"],
|
||||
"summary": "Deletes a pet",
|
||||
"description": "",
|
||||
"operationId": "deletePet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "api_key",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "Pet id to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"petstore_auth": ["write:pets", "read:pets"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/pet/{petId}/uploadImage": {
|
||||
"post": {
|
||||
"tags": ["pet"],
|
||||
"summary": "uploads an image",
|
||||
"description": "",
|
||||
"operationId": "uploadFile",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of pet to update",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"petstore_auth": ["write:pets", "read:pets"]
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/octet-stream": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/store/inventory": {
|
||||
"get": {
|
||||
"tags": ["store"],
|
||||
"summary": "Returns pet inventories by status",
|
||||
"description": "Returns a map of status codes to quantities",
|
||||
"operationId": "getInventory",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/store/order": {
|
||||
"post": {
|
||||
"tags": ["store"],
|
||||
"summary": "Place an order for a pet",
|
||||
"description": "",
|
||||
"operationId": "placeOrder",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid Order"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "order placed for purchasing the pet",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/store/order/{orderId}": {
|
||||
"get": {
|
||||
"tags": ["store"],
|
||||
"summary": "Find purchase order by ID",
|
||||
"description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
|
||||
"operationId": "getOrderById",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of pet that needs to be fetched",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 1,
|
||||
"maximum": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["store"],
|
||||
"summary": "Delete purchase order by ID",
|
||||
"description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
|
||||
"operationId": "deleteOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of the order that needs to be deleted",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user": {
|
||||
"post": {
|
||||
"tags": ["user"],
|
||||
"summary": "Create user",
|
||||
"description": "This can only be done by the logged in user.",
|
||||
"operationId": "createUser",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": "successful operation"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Created user object",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/createWithArray": {
|
||||
"post": {
|
||||
"tags": ["user"],
|
||||
"summary": "Creates list of users with given input array",
|
||||
"description": "",
|
||||
"operationId": "createUsersWithArrayInput",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": "successful operation"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/UserArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/createWithList": {
|
||||
"post": {
|
||||
"tags": ["user"],
|
||||
"summary": "Creates list of users with given input array",
|
||||
"description": "",
|
||||
"operationId": "createUsersWithListInput",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": "successful operation"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/UserArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/login": {
|
||||
"get": {
|
||||
"tags": ["user"],
|
||||
"summary": "Logs user into the system",
|
||||
"description": "",
|
||||
"operationId": "loginUser",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "username",
|
||||
"in": "query",
|
||||
"description": "The user name for login",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"in": "query",
|
||||
"description": "The password for login in clear text",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"headers": {
|
||||
"X-Rate-Limit": {
|
||||
"description": "calls per hour allowed by the user",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"X-Expires-After": {
|
||||
"description": "date in UTC when token expires",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid username/password supplied"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/logout": {
|
||||
"get": {
|
||||
"tags": ["user"],
|
||||
"summary": "Logs out current logged in user session",
|
||||
"description": "",
|
||||
"operationId": "logoutUser",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": "successful operation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/{username}": {
|
||||
"get": {
|
||||
"tags": ["user"],
|
||||
"summary": "Get user by user name",
|
||||
"description": "",
|
||||
"operationId": "getUserByName",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "username",
|
||||
"in": "path",
|
||||
"description": "The name that needs to be fetched. Use user1 for testing. ",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid username supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": ["user"],
|
||||
"summary": "Updated user",
|
||||
"description": "This can only be done by the logged in user.",
|
||||
"operationId": "updateUser",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "username",
|
||||
"in": "path",
|
||||
"description": "name that need to be updated",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"400": {
|
||||
"description": "Invalid user supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Updated user object",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["user"],
|
||||
"summary": "Delete user",
|
||||
"description": "This can only be done by the logged in user.",
|
||||
"operationId": "deleteUser",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "username",
|
||||
"in": "path",
|
||||
"description": "The name that needs to be deleted",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"400": {
|
||||
"description": "Invalid username supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Order": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"petId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"shipDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Order Status",
|
||||
"enum": ["placed", "approved", "delivered"]
|
||||
},
|
||||
"complete": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Order"
|
||||
}
|
||||
},
|
||||
"Category": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Category"
|
||||
}
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"userStatus": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "User Status"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "User"
|
||||
}
|
||||
},
|
||||
"Tag": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Tag"
|
||||
}
|
||||
},
|
||||
"Pet": {
|
||||
"type": "object",
|
||||
"required": ["name", "photoUrls"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"readOnly": true
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/components/schemas/Category"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "doggie"
|
||||
},
|
||||
"photoUrls": {
|
||||
"type": "array",
|
||||
"xml": {
|
||||
"name": "photoUrl",
|
||||
"wrapped": true
|
||||
},
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"xml": {
|
||||
"name": "tag",
|
||||
"wrapped": true
|
||||
},
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Tag"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "pet status in the store",
|
||||
"enum": ["available", "pending", "sold"]
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Pet"
|
||||
}
|
||||
},
|
||||
"ApiResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestBodies": {
|
||||
"Pet": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
},
|
||||
"application/xml": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Pet object that needs to be added to the store",
|
||||
"required": true
|
||||
},
|
||||
"UserArray": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "List of user object",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"petstore_auth": {
|
||||
"type": "oauth2",
|
||||
"flows": {
|
||||
"implicit": {
|
||||
"authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
|
||||
"scopes": {
|
||||
"write:pets": "modify pets in your account",
|
||||
"read:pets": "read your pets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"name": "api_key",
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Support for different security types",
|
||||
"description": "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#securitySchemeObject"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://httpbin.org"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"name": "API Key"
|
||||
},
|
||||
{
|
||||
"name": "HTTP"
|
||||
},
|
||||
{
|
||||
"name": "Mutual TLS"
|
||||
},
|
||||
{
|
||||
"name": "OAuth 2"
|
||||
},
|
||||
{
|
||||
"name": "OpenID Connect"
|
||||
},
|
||||
{
|
||||
"name": "Other"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/anything/apiKey": {
|
||||
"get": {
|
||||
"summary": "Query parameter",
|
||||
"description": "`apiKey` auth will be supplied within an `apiKey` query parameter.",
|
||||
"tags": ["API Key"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_query": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Cookie",
|
||||
"description": "`apiKey` auth will be supplied within an `api_key` cookie.",
|
||||
"tags": ["API Key"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_cookie": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Header",
|
||||
"description": "`apiKey` auth will be supplied within an `X-API-KEY` header.",
|
||||
"tags": ["API Key"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_header": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/basic": {
|
||||
"post": {
|
||||
"summary": "Basic",
|
||||
"description": "Authentication credentials will be supplied within a `Basic` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample",
|
||||
"tags": ["HTTP"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"basic": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/bearer": {
|
||||
"post": {
|
||||
"summary": "Bearer",
|
||||
"description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample",
|
||||
"tags": ["HTTP"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Bearer (`jwt` format)",
|
||||
"description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header, but its data should be controlled as a JWT.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample\n\n> ℹ️We currently do not support any special handling for this so they're handled as a standard `Bearer` authentication token.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer_jwt": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/mutualTLS": {
|
||||
"post": {
|
||||
"summary": "`mutualTLS` auth",
|
||||
"description": "🚧 This is not supported.",
|
||||
"tags": ["Mutual TLS"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"mutualTLS": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/oauth2": {
|
||||
"post": {
|
||||
"summary": "General support (all flow types)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"summary": "General support (authorizationCode flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_authorizationCode": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "General support (clientCredentials flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_clientCredentials": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "General support (implicit flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_implicit": ["write:things"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "General support (password flow type)",
|
||||
"description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23",
|
||||
"tags": ["OAuth 2"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"oauth2_password": ["write:things"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/openIdConnect": {
|
||||
"post": {
|
||||
"summary": "General support",
|
||||
"description": "🚧 This is not supported.",
|
||||
"tags": ["OpenID Connect"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"openIdConnect": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/anything/no-auth": {
|
||||
"post": {
|
||||
"summary": "No auth requirements",
|
||||
"description": "This operation does not have any authentication requirements.",
|
||||
"tags": ["Other"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/status/401": {
|
||||
"post": {
|
||||
"summary": "Forced invalid authentication",
|
||||
"description": "This endpoint requires an authentication header but making any request to it will forcefully return a 401 status code for invalid auth.",
|
||||
"tags": ["Other"],
|
||||
"responses": {
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKey_header": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"apiKey_cookie": {
|
||||
"type": "apiKey",
|
||||
"in": "cookie",
|
||||
"name": "api_key",
|
||||
"description": "An API key that will be supplied in a named cookie. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object"
|
||||
},
|
||||
"apiKey_header": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-API-KEY",
|
||||
"description": "An API key that will be supplied in a named header. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object"
|
||||
},
|
||||
"apiKey_query": {
|
||||
"type": "apiKey",
|
||||
"in": "query",
|
||||
"name": "apiKey",
|
||||
"description": "An API key that will be supplied in a named query parameter. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object"
|
||||
},
|
||||
"basic": {
|
||||
"type": "http",
|
||||
"scheme": "basic",
|
||||
"description": "Basic auth that takes a base64'd combination of `user:password`. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample"
|
||||
},
|
||||
"bearer": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"description": "A bearer token that will be supplied within an `Authentication` header as `bearer <token>`. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample"
|
||||
},
|
||||
"bearer_jwt": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT",
|
||||
"description": "A bearer token that will be supplied within an `Authentication` header as `bearer <token>`. In this case, the format of the token is specified as JWT. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#jwt-bearer-sample"
|
||||
},
|
||||
"mutualTLS": {
|
||||
"type": "mutualTLS",
|
||||
"description": "Requires a specific mutual TLS certificate to use when making a HTTP request. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23"
|
||||
},
|
||||
"oauth2": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23",
|
||||
"flows": {
|
||||
"authorizationCode": {
|
||||
"authorizationUrl": "http://example.com/oauth/dialog",
|
||||
"tokenUrl": "http://example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
},
|
||||
"clientCredentials": {
|
||||
"tokenUrl": "http://example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
},
|
||||
"implicit": {
|
||||
"authorizationUrl": "http://example.com/oauth/dialog",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"tokenUrl": "http://example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_authorizationCode": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `authorizationCode` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"authorizationCode": {
|
||||
"authorizationUrl": "http://alt.example.com/oauth/dialog",
|
||||
"tokenUrl": "http://alt.example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_clientCredentials": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `clientCredentials` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"clientCredentials": {
|
||||
"tokenUrl": "http://alt.example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_implicit": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `implicit` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"implicit": {
|
||||
"authorizationUrl": "http://alt.example.com/oauth/dialog",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2_password": {
|
||||
"type": "oauth2",
|
||||
"description": "An OAuth 2 security flow that only supports the `password` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object",
|
||||
"flows": {
|
||||
"password": {
|
||||
"tokenUrl": "http://alt.example.com/oauth/token",
|
||||
"scopes": {
|
||||
"write:things": "Add things to your account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"openIdConnect": {
|
||||
"type": "openIdConnect",
|
||||
"openIdConnectUrl": "https://example.com/.well-known/openid-configuration",
|
||||
"description": "OpenAPI authentication. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "@agentic/openapi-to-ts",
|
||||
"version": "0.1.0",
|
||||
"description": "Generate an Agentic TypeScript client from an OpenAPI spec.",
|
||||
"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,
|
||||
"bin": {
|
||||
"openapi-to-ts": "./dist/openapi-to-ts.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"@apidevtools/swagger-parser": "^10.1.1",
|
||||
"camelcase": "^8.0.0",
|
||||
"cleye": "catalog:",
|
||||
"decamelize": "^6.0.0",
|
||||
"execa": "^9.5.2",
|
||||
"exit-hook": "catalog:",
|
||||
"json-schema-to-zod": "^2.6.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentic/tsconfig": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<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>Generate an Agentic TypeScript client from an OpenAPI spec.</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.**
|
||||
|
||||
## Example Usage
|
||||
|
||||
```sh
|
||||
# local development
|
||||
tsx bin/openapi-to-ts.ts fixtures/openapi/3.0/notion.json -o fixtures/generated
|
||||
|
||||
# published version
|
||||
npx @agentic/openapi-to-ts fixtures/openapi/3.0/notion.json -o fixtures/generated
|
||||
|
||||
# npm install -g version
|
||||
npm install -g @agentic/openapi-to-ts
|
||||
openapi-to-ts fixtures/openapi/3.0/notion.json -o fixtures/generated
|
||||
```
|
||||
|
||||
Most OpenAPI specs should be supported, but I've made no attempt to maximize robustness. This tool is meant to generate Agentic TS clients which are 99% of the way there, but some tweaking may be necessary post-generation.
|
||||
|
||||
Some things you may want to tweak:
|
||||
|
||||
- simplifying the zod parameters accepted by `@aiFunction` input schemas since LLMs tend to do better with a few, key parameters
|
||||
- removing unused API endpoints & associated types that you don't want to expose to LLMs
|
||||
|
||||
## TODO
|
||||
|
||||
- convert openapi parsing & utils to https://github.com/readmeio/oas
|
||||
- support filters
|
||||
- // match only the schema named `foo` and `GET` operation for the `/api/v1/foo` path
|
||||
- include: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$',
|
||||
- [ ] Convert HTML in descriptions to markdown
|
||||
- [ ] Properly format multiline function comments
|
||||
- [ ] Debug stripe schema issue
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1,682 @@
|
|||
/* eslint-disable no-template-curly-in-string */
|
||||
import * as fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
import type { IJsonSchema, OpenAPIV3 } from 'openapi-types'
|
||||
import { assert } from '@agentic/core'
|
||||
import SwaggerParser from '@apidevtools/swagger-parser'
|
||||
import decamelize from 'decamelize'
|
||||
import { execa } from 'execa'
|
||||
|
||||
import { convertParametersToJSONSchema } from './openapi-parameters-to-json-schema'
|
||||
import {
|
||||
camelCase,
|
||||
dereference,
|
||||
dereferenceFull,
|
||||
getAndResolve,
|
||||
getComponentDisplayName,
|
||||
getDescription,
|
||||
getOperationParamsName,
|
||||
getOperationResponseName,
|
||||
jsonSchemaToZod,
|
||||
naiveMergeJSONSchemas,
|
||||
pascalCase,
|
||||
prettify
|
||||
} from './utils'
|
||||
|
||||
const jsonContentType = 'application/json'
|
||||
const multipartFormData = 'multipart/form-data'
|
||||
|
||||
const httpMethods = [
|
||||
'get',
|
||||
'post',
|
||||
'put',
|
||||
'delete',
|
||||
'options',
|
||||
'head',
|
||||
'patch',
|
||||
'trace'
|
||||
] as const
|
||||
|
||||
export type GenerateTSFromOpenAPIOptions = {
|
||||
openapiFilePath: string
|
||||
outputDir: string
|
||||
dryRun?: boolean
|
||||
prettier?: boolean
|
||||
eslint?: boolean
|
||||
}
|
||||
|
||||
export async function generateTSFromOpenAPI({
|
||||
openapiFilePath,
|
||||
outputDir,
|
||||
dryRun = false,
|
||||
prettier = true,
|
||||
eslint = true
|
||||
}: GenerateTSFromOpenAPIOptions): Promise<string> {
|
||||
const parser = new SwaggerParser()
|
||||
const spec = (await parser.bundle(openapiFilePath)) 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' &&
|
||||
spec.openapi.split('.')[0] !== '3'
|
||||
) {
|
||||
throw new Error(`Unexpected OpenAPI version "${spec.openapi}"`)
|
||||
}
|
||||
|
||||
const openapiSpecName = path.basename(openapiFilePath, '.json')
|
||||
assert(
|
||||
openapiSpecName.toLowerCase() === openapiSpecName,
|
||||
`OpenAPI spec name "${openapiSpecName}" must be in kebab case`
|
||||
)
|
||||
const name = pascalCase(openapiSpecName)
|
||||
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 destFileClient = path.join(outputDir, `${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 operationNames = 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 operationId =
|
||||
operation.operationId || `${method}${path.replaceAll(/\W+/g, '_')}`
|
||||
assert(
|
||||
operationId,
|
||||
`Invalid operation id ${operationId} for path "${method} ${path}"`
|
||||
)
|
||||
|
||||
const operationName = camelCase(operationId)
|
||||
assert(
|
||||
!operationNames.has(operationName),
|
||||
`Duplicate operation name "${operationName}"`
|
||||
)
|
||||
operationNames.add(operationName)
|
||||
const operationNameSnakeCase = decamelize(operationName)
|
||||
|
||||
// if (path !== '/comments' || method !== 'post') continue
|
||||
// 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> = {}
|
||||
let operationParamsUnionSource: string | undefined
|
||||
|
||||
// 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 if (derefed?.anyOf || derefed?.oneOf) {
|
||||
const componentName = getComponentDisplayName(schema.$ref)
|
||||
operationParamsSources[componentName] = source
|
||||
|
||||
// TODO: handle this case
|
||||
assert(
|
||||
!operationParamsUnionSource,
|
||||
`Duplicate union source ${source} for operation ${operationName}`
|
||||
)
|
||||
operationParamsUnionSource = 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
|
||||
]
|
||||
}
|
||||
|
||||
if (schema?.anyOf || schema?.oneOf) {
|
||||
operationParamsSources[schema.title || '__union__'] = source
|
||||
|
||||
// TODO: handle this case
|
||||
assert(
|
||||
!operationParamsUnionSource,
|
||||
`Duplicate union source ${source} for operation ${operationName}`
|
||||
)
|
||||
operationParamsUnionSource = source
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
let derefedParams: any = dereference(
|
||||
operationParamsJSONSchema,
|
||||
parser.$refs
|
||||
)
|
||||
for (const ref of derefedParams.$refs) {
|
||||
const temp: any = dereference({ $ref: ref }, parser.$refs)
|
||||
if (temp) {
|
||||
derefedParams = naiveMergeJSONSchemas(derefedParams, temp)
|
||||
}
|
||||
}
|
||||
// console.log(JSON.stringify(derefedParams, null, 2))
|
||||
const hasUnionParams = !!(derefedParams.anyOf || derefedParams.oneOf)
|
||||
const hasParams =
|
||||
Object.keys(derefedParams.properties ?? {}).length > 0 || hasUnionParams
|
||||
|
||||
assert(
|
||||
hasUnionParams === !!operationParamsUnionSource,
|
||||
'Unexpected union params'
|
||||
)
|
||||
|
||||
// TODO: handle empty params case
|
||||
|
||||
{
|
||||
// Merge all operations params into one schema declaration
|
||||
// TODO: Don't generate this if it's only refs. We're currently handling
|
||||
// this in a hacky way by removing the `z.object({}).merge(...)` down
|
||||
// below.
|
||||
let operationsParamsSchema = jsonSchemaToZod(
|
||||
operationParamsJSONSchema,
|
||||
{ name: `${operationParamsName}Schema`, type: operationParamsName }
|
||||
)
|
||||
|
||||
if (operationParamsJSONSchema.$refs.length) {
|
||||
const refSchemas = operationParamsJSONSchema.$refs.map(
|
||||
(ref) => `${getComponentDisplayName(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 = getComponentDisplayName(
|
||||
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}'`
|
||||
|
||||
const description = getDescription(
|
||||
operation.description || operation.summary
|
||||
)
|
||||
const isDescriptionMultiline = description?.includes('\n')
|
||||
|
||||
const aiClientMethod = `
|
||||
${description ? `/**\n * ${description}\n */` : ''}
|
||||
@aiFunction({
|
||||
name: '${operationNameSnakeCase}',
|
||||
${description ? `description: ${isDescriptionMultiline ? `\`${description.replaceAll('`', '\\`')}\`` : `'${description}'`},` : ''}${hasUnionParams ? '\n// TODO: Improve handling of union params' : ''}
|
||||
inputSchema: ${namespaceName}.${operationParamsName}Schema${hasUnionParams ? ' as any' : ''},
|
||||
})
|
||||
async ${operationName}(${!hasParams ? '_' : ''}params: ${namespaceName}.${operationParamsName}): Promise<${namespaceName}.${operationResponseName}> {
|
||||
return this.ky.${method}(${pathTemplate}${
|
||||
!hasParams || onlyHasPathParams
|
||||
? ''
|
||||
: `, {
|
||||
${hasQueryParams ? (onlyHasOneParamsSource || hasUnionParams ? `searchParams: sanitizeSearchParams(params),` : `searchParams: sanitizeSearchParams(pick(params, '${queryParams.join("', '")}')),`) : ''}
|
||||
${hasBodyParams ? (onlyHasOneParamsSource || hasUnionParams ? `json: params,` : `json: pick(params, '${bodyParams.join("', '")}'),`) : ''}
|
||||
${hasFormDataParams ? (onlyHasOneParamsSource || hasUnionParams ? `form: params,` : `form: pick(params, '${formDataParams.join("', '")}'),`) : ''}
|
||||
${hasHeadersParams ? (onlyHasOneParamsSource || hasUnionParams ? `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 = { $ref: ref }
|
||||
|
||||
const resolved = new Set<string>()
|
||||
const dereferenced = dereference(component, parser.$refs)
|
||||
dereferenceFull(component, parser.$refs, resolved)
|
||||
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 = getComponentDisplayName(ref)
|
||||
assert(type, `Invalid ref name ${ref}`)
|
||||
|
||||
const name = `${type}Schema`
|
||||
|
||||
const { dereferenced } = componentToRefs[ref]!
|
||||
if (processedComponents.has(ref)) {
|
||||
continue
|
||||
}
|
||||
|
||||
processedComponents.add(ref)
|
||||
|
||||
const schema = jsonSchemaToZod(dereferenced, { name, type })
|
||||
componentSchemas[type] = schema
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// '\ncomponents',
|
||||
// JSON.stringify(
|
||||
// sortedComponents.map((ref) => getComponentName(ref)),
|
||||
// null,
|
||||
// 2
|
||||
// )
|
||||
// )
|
||||
|
||||
// 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 = `
|
||||
/* eslint-disable unicorn/no-unreadable-iife */
|
||||
/* eslint-disable unicorn/no-array-reduce */
|
||||
|
||||
/**
|
||||
* 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 commentLine = `// ${'-'.repeat(77)}`
|
||||
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 description = getDescription(spec.info?.description)
|
||||
const prettifyImpl = prettier ? prettify : (code: string) => code
|
||||
|
||||
const output = (
|
||||
await prettifyImpl(
|
||||
[
|
||||
outputTypes,
|
||||
`
|
||||
/**
|
||||
* Agentic ${name} client.${description ? `\n *\n * ${description}` : ''}
|
||||
*/
|
||||
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' || resolvedSecuritySchemes[name]?.type?.toLowerCase() === 'oauth2' ? '`Bearer ${apiKey}`' : 'apiKey'}`).join(',\n')}
|
||||
},`
|
||||
: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
`,
|
||||
aiClientMethodsString,
|
||||
'}'
|
||||
].join('\n\n')
|
||||
)
|
||||
)
|
||||
.replaceAll(/z\s*\.object\({}\)\s*\.merge\(([^)]*)\)/gm, '$1')
|
||||
.replaceAll(/\/\*\*(\S.*\S)\*\//g, '/** $1 */')
|
||||
|
||||
if (dryRun) {
|
||||
return output
|
||||
}
|
||||
|
||||
await fs.mkdir(outputDir, { recursive: true })
|
||||
await fs.writeFile(destFileClient, output)
|
||||
|
||||
if (eslint) {
|
||||
await execa('npx', ['eslint', '--fix', '--no-ignore', destFileClient])
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './generate-ts-from-openapi'
|
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* This file is forked from: https://github.com/kogosoftwarellc/open-api/tree/main/packages/openapi-jsonschema-parameters
|
||||
*
|
||||
* Several fixes have been applied.
|
||||
*
|
||||
* 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
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
import type SwaggerParser from '@apidevtools/swagger-parser'
|
||||
import type { IJsonSchema, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
|
||||
import { assert } from '@agentic/core'
|
||||
import camelCaseImpl from 'camelcase'
|
||||
import {
|
||||
type JsonSchema,
|
||||
jsonSchemaToZod as jsonSchemaToZodImpl,
|
||||
type ParserOverride
|
||||
} 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 pascalCase(identifier: string): string {
|
||||
return camelCaseImpl(identifier, { pascalCase: true })
|
||||
}
|
||||
|
||||
export function camelCase(identifier: string): string {
|
||||
return camelCaseImpl(identifier)
|
||||
}
|
||||
|
||||
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,
|
||||
visited = new Set<string>()
|
||||
): 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, visited)
|
||||
) as T
|
||||
} else if (typeof obj === 'object') {
|
||||
if ('$ref' in obj) {
|
||||
const ref = obj.$ref as string
|
||||
if (visited?.has(ref)) {
|
||||
return obj
|
||||
}
|
||||
visited?.add(ref)
|
||||
const derefed = refs.get(ref)
|
||||
assert(derefed, `Invalid schema: $ref not found for ${ref}`)
|
||||
resolved?.add(ref)
|
||||
derefed.title ??= ref.split('/').pop()!
|
||||
return dereference(derefed, refs, resolved, depth + 1, maxDepth, visited)
|
||||
} else {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
key,
|
||||
dereference(value, refs, resolved, depth + 1, maxDepth, visited)
|
||||
])
|
||||
) as T
|
||||
}
|
||||
} else {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
||||
function createParserOverride({
|
||||
type
|
||||
}: {
|
||||
type?: string
|
||||
} = {}): ParserOverride {
|
||||
const jsonSchemaToZodParserOverride: ParserOverride = (schema, _refs) => {
|
||||
if ('$ref' in schema) {
|
||||
const ref = schema.$ref as string
|
||||
assert(ref, `Invalid schema: $ref not found for ${schema.$ref}`)
|
||||
|
||||
const name = getComponentDisplayName(ref)
|
||||
if (type === name) {
|
||||
// TODO: Support recursive types.
|
||||
return `\n// TODO: Support recursive types for \`${name}Schema\`.\nz.any()`
|
||||
}
|
||||
|
||||
return `${name}Schema`
|
||||
} else if (schema.oneOf) {
|
||||
const { oneOf, ...partialSchema } = schema
|
||||
|
||||
// Replace oneOf with anyOf because `json-schema-to-zod` treats oneOf
|
||||
// with a complicated `z.any().superRefine(...)` which we'd like messes
|
||||
// up the resulting types.
|
||||
const newSchema = {
|
||||
...partialSchema,
|
||||
anyOf: oneOf
|
||||
}
|
||||
|
||||
const res = jsonSchemaToZodImpl(newSchema, {
|
||||
parserOverride: jsonSchemaToZodParserOverride
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
return jsonSchemaToZodParserOverride
|
||||
}
|
||||
|
||||
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: createParserOverride({ type })
|
||||
})
|
||||
}
|
||||
|
||||
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 name = ref.split('/').pop()!
|
||||
assert(name, `Invalid ref name ${ref}`)
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
export function getComponentDisplayName(ref: string) {
|
||||
const name0 = getComponentName(ref)
|
||||
const name1 = pascalCase(name0)
|
||||
assert(name1, `Invalid ref name ${ref}`)
|
||||
|
||||
if (reservedWords.has(name1)) {
|
||||
return `${name1}Type`
|
||||
}
|
||||
|
||||
return name1
|
||||
}
|
||||
|
||||
export function getOperationParamsName(
|
||||
operationName: string,
|
||||
schemas?: Record<string, string>
|
||||
) {
|
||||
const name = `${pascalCase(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 = `${pascalCase(operationName)}Response`
|
||||
if (!schemas) return name
|
||||
|
||||
let tempName = name
|
||||
let index = 2
|
||||
while (schemas[tempName]) {
|
||||
tempName = `${name}${index}`
|
||||
++index
|
||||
}
|
||||
|
||||
return tempName
|
||||
}
|
||||
|
||||
export function naiveMergeJSONSchemas(...schemas: IJsonSchema[]): IJsonSchema {
|
||||
const result: any = {}
|
||||
|
||||
for (const ischema of schemas) {
|
||||
const schema = ischema as any
|
||||
const arrayKeys: string[] = []
|
||||
const objectKeys: string[] = []
|
||||
|
||||
for (const [key, value] of Object.entries(schema)) {
|
||||
if (Array.isArray(value)) {
|
||||
arrayKeys.push(key)
|
||||
} else if (typeof value === 'object') {
|
||||
objectKeys.push(key)
|
||||
} else {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of arrayKeys) {
|
||||
result[key] = [...(result[key] ?? []), ...(schema[key] ?? [])]
|
||||
}
|
||||
|
||||
for (const key of objectKeys) {
|
||||
result[key] = { ...result[key], ...schema[key] }
|
||||
}
|
||||
}
|
||||
|
||||
return result as IJsonSchema
|
||||
}
|
||||
|
||||
export function getDescription(description?: string): string | undefined {
|
||||
if (description && !/[!.?]$/.test(description)) {
|
||||
description += '.'
|
||||
}
|
||||
|
||||
return description
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@agentic/tsconfig/base.json",
|
||||
"include": ["src", "bin"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
entry: ['src/index.ts'],
|
||||
outDir: 'dist',
|
||||
target: 'node18',
|
||||
platform: 'node',
|
||||
format: ['esm'],
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
shims: true,
|
||||
dts: true
|
||||
},
|
||||
{
|
||||
entry: ['bin/openapi-to-ts.ts'],
|
||||
outDir: 'dist',
|
||||
target: 'node18',
|
||||
platform: 'node',
|
||||
format: ['esm'],
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
shims: true,
|
||||
dts: true
|
||||
}
|
||||
])
|
|
@ -32,12 +32,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@agentic/apollo": "workspace:*",
|
||||
"@agentic/arxiv": "workspace:*",
|
||||
"@agentic/bing": "workspace:*",
|
||||
"@agentic/calculator": "workspace:*",
|
||||
"@agentic/clearbit": "workspace:*",
|
||||
"@agentic/core": "workspace:*",
|
||||
"@agentic/dexa": "workspace:*",
|
||||
"@agentic/diffbot": "workspace:*",
|
||||
"@agentic/duck-duck-go": "workspace:*",
|
||||
"@agentic/e2b": "workspace:*",
|
||||
"@agentic/exa": "workspace:*",
|
||||
"@agentic/firecrawl": "workspace:*",
|
||||
|
@ -48,6 +50,7 @@
|
|||
"@agentic/jina": "workspace:*",
|
||||
"@agentic/leadmagic": "workspace:*",
|
||||
"@agentic/midjourney": "workspace:*",
|
||||
"@agentic/mcp": "workspace:*",
|
||||
"@agentic/novu": "workspace:*",
|
||||
"@agentic/people-data-labs": "workspace:*",
|
||||
"@agentic/perigon": "workspace:*",
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
export * from '@agentic/apollo'
|
||||
export * from '@agentic/arxiv'
|
||||
export * from '@agentic/bing'
|
||||
export * from '@agentic/calculator'
|
||||
export * from '@agentic/clearbit'
|
||||
export * from '@agentic/dexa'
|
||||
export * from '@agentic/diffbot'
|
||||
export * from '@agentic/duck-duck-go'
|
||||
export * from '@agentic/e2b'
|
||||
export * from '@agentic/exa'
|
||||
export * from '@agentic/firecrawl'
|
||||
|
@ -13,6 +15,7 @@ export * from '@agentic/hacker-news'
|
|||
export * from '@agentic/hunter'
|
||||
export * from '@agentic/jina'
|
||||
export * from '@agentic/leadmagic'
|
||||
export * from '@agentic/mcp'
|
||||
export * from '@agentic/midjourney'
|
||||
export * from '@agentic/novu'
|
||||
export * from '@agentic/people-data-labs'
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { type AIFunctionLike, AIFunctionSet } from '@agentic/core'
|
||||
import { type AIFunctionLike, AIFunctionSet, isZodSchema } from '@agentic/core'
|
||||
import { tool, type ToolResult } from '@xsai/tool'
|
||||
|
||||
/**
|
||||
* Converts a set of Agentic stdlib AI functions to an object compatible with
|
||||
* [the xsAI SDK's](https://github.com/moeru-ai/xsai) `tools` parameter.
|
||||
* the [xsAI SDK's](https://github.com/moeru-ai/xsai) `tools` parameter.
|
||||
*/
|
||||
export function createXSAITools(
|
||||
...aiFunctionLikeTools: AIFunctionLike[]
|
||||
|
@ -11,13 +11,19 @@ export function createXSAITools(
|
|||
const fns = new AIFunctionSet(aiFunctionLikeTools)
|
||||
|
||||
return Promise.all(
|
||||
fns.map((fn) =>
|
||||
tool({
|
||||
fns.map((fn) => {
|
||||
if (!isZodSchema(fn.inputSchema)) {
|
||||
throw new Error(
|
||||
`xsAI tools only support Standard schemas like Zod: ${fn.spec.name} tool uses a custom JSON Schema, which is currently not supported.`
|
||||
)
|
||||
}
|
||||
|
||||
return tool({
|
||||
name: fn.spec.name,
|
||||
description: fn.spec.description,
|
||||
parameters: fn.inputSchema,
|
||||
execute: fn.impl
|
||||
execute: fn.execute
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
802
pnpm-lock.yaml
802
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
|
@ -7,8 +7,10 @@ catalog:
|
|||
zod: ^3.24.2
|
||||
zod-validation-error: ^3.4.0
|
||||
openai-zod-to-json-schema: ^1.0.3
|
||||
cleye: ^1.3.4
|
||||
dedent: ^1.5.3
|
||||
delay: ^6.0.0
|
||||
exit-hook: ^4.0.0
|
||||
jsonrepair: ^3.9.0
|
||||
jsrsasign: ^10.9.0
|
||||
mathjs: ^13.0.3
|
||||
|
@ -23,6 +25,8 @@ catalog:
|
|||
type-fest: ^4.37.0
|
||||
wikibase-sdk: ^10.2.2
|
||||
'@types/jsrsasign': ^10.5.15
|
||||
fast-xml-parser: ^5.0.9
|
||||
'@modelcontextprotocol/sdk': ^1.7.0
|
||||
|
||||
# vercel ai sdk
|
||||
ai: ^4.1.61
|
||||
|
|
35
readme.md
35
readme.md
|
@ -36,7 +36,7 @@
|
|||
|
||||
## Intro
|
||||
|
||||
Agentic is a **standard library of AI functions / tools** which are **optimized for both normal TS-usage as well as LLM-based usage**. Agentic works with all of the major TS AI SDKs (Vercel AI SDK, Mastra, LangChain, LlamaIndex, OpenAI SDK, etc).
|
||||
Agentic is a **standard library of AI functions / tools** which are **optimized for both normal TS-usage as well as LLM-based usage**. Agentic works with all of the major TS AI SDKs (Vercel AI SDK, Mastra, LangChain, LlamaIndex, OpenAI SDK, MCP, etc).
|
||||
|
||||
Agentic clients like `WeatherClient` can be used as normal TS classes:
|
||||
|
||||
|
@ -80,11 +80,34 @@ console.log(result.toolResults[0])
|
|||
|
||||
You can use our standard library of thoroughly tested AI functions with your favorite AI SDK – without having to write any glue code!
|
||||
|
||||
All Agentic clients expose an `AIFunctionSet`, which makes it easy to mix & match all sorts of different tools together.
|
||||
|
||||
```ts
|
||||
import { SerperClient, WikipediaClient, FirecrawlClient } from '@agentic/stdlib'
|
||||
|
||||
const googleSearch = new SerperClient()
|
||||
const wikipedia = new WikipediaClient()
|
||||
const firecrawl = new FirecrawlClient()
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4o-mini'),
|
||||
tools: createAISDKTools(
|
||||
googleSearch,
|
||||
wikipedia,
|
||||
// Pick a single function from the firecrawl client's set of AI functions
|
||||
firecrawl.functions.pick('firecrawl_search')
|
||||
),
|
||||
toolChoice: 'required',
|
||||
prompt:
|
||||
'What year did Jurassic Park come out, and what else happened that year?'
|
||||
})
|
||||
```
|
||||
|
||||
### Under the hood
|
||||
|
||||
All of the adapters (like `createAISDKTools` in this example) accept a very flexible var args of `AIFunctionLike` parameters, so you can pass as many tools as you'd like. An `AIFunctionLike` can be any agentic client instance, a single `AIFunction` selected from the client's `.functions` property (which holds an `AIFunctionSet` of available AI functions), or an AI function created manually via `createAIFunction`.
|
||||
All of the adapters (like `createAISDKTools`) accept a very flexible var args of `AIFunctionLike` parameters, so you can pass as many tools as you'd like. An `AIFunctionLike` can be any agentic client instance, a single `AIFunction` selected from the client's `.functions` property (which holds an `AIFunctionSet` of available AI functions), or an AI function created manually via `createAIFunction`.
|
||||
|
||||
`AIFunctionLike` and `AIFunctionSet` are implementation details that you likely won't have to touch directly, but they're important primitives because they're designed to maximize flexibility when working with various AI functions coming from different places.
|
||||
`AIFunctionLike` and `AIFunctionSet` are details that you likely won't have to touch directly, but they're important because of their flexibility.
|
||||
|
||||
## Docs
|
||||
|
||||
|
@ -133,11 +156,13 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
| Service / Tool | Package | Docs | Description |
|
||||
| ------------------------------------------------------------------------ | --------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Apollo](https://docs.apollo.io) | `@agentic/apollo` | [docs](https://agentic.so/tools/apollo) | B2B person and company enrichment API. |
|
||||
| [ArXiv](https://arxiv.org) | `@agentic/arxiv` | [docs](https://agentic.so/tools/arxiv) | Search for research articles. |
|
||||
| [Bing](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api) | `@agentic/bing` | [docs](https://agentic.so/tools/bing) | Bing web search. |
|
||||
| [Calculator](https://github.com/josdejong/mathjs) | `@agentic/calculator` | [docs](https://agentic.so/tools/calculator) | Basic calculator for simple mathematical expressions. |
|
||||
| [Clearbit](https://dashboard.clearbit.com/docs) | `@agentic/clearbit` | [docs](https://agentic.so/tools/clearbit) | Resolving and enriching people and company data. |
|
||||
| [Dexa](https://dexa.ai) | `@agentic/dexa` | [docs](https://agentic.so/tools/dexa) | Answers questions from the world's best podcasters. |
|
||||
| [Diffbot](https://docs.diffbot.com) | `@agentic/diffbot` | [docs](https://agentic.so/tools/diffbot) | Web page classification and scraping; person and company data enrichment. |
|
||||
| [DuckDuckGo](https://duckduckgo.com) | `@agentic/duck-duck-go` | [docs](https://agentic.so/tools/duck-duck-go) | Privacy-focused web search API. |
|
||||
| [E2B](https://e2b.dev) | `@agentic/e2b` | [docs](https://agentic.so/tools/e2b) | Hosted Python code interpreter sandbox which is really useful for data analysis, flexible code execution, and advanced reasoning on-the-fly. |
|
||||
| [Exa](https://docs.exa.ai) | `@agentic/exa` | [docs](https://agentic.so/tools/exa) | Web search tailored for LLMs. |
|
||||
| [Firecrawl](https://www.firecrawl.dev) | `@agentic/firecrawl` | [docs](https://agentic.so/tools/firecrawl) | Website scraping and structured data extraction. |
|
||||
|
@ -147,6 +172,7 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
| [Jina](https://jina.ai/reader) | `@agentic/jina` | [docs](https://agentic.so/tools/jina) | URL scraper and web search. |
|
||||
| [LeadMagic](https://leadmagic.io) | `@agentic/leadmagic` | [docs](https://agentic.so/tools/leadmagic) | B2B person, company, and email enrichment API. |
|
||||
| [Midjourney](https://www.imagineapi.dev) | `@agentic/midjourney` | [docs](https://agentic.so/tools/midjourney) | Unofficial Midjourney client for generative images. |
|
||||
| [McpTools](https://modelcontextprotocol.io) | `@agentic/mcp` | [docs](https://agentic.so/tools/mcp) | Model Context Protocol (MCP) client, supporting any MCP server. Use [createMcpTools](https://agentic.so/tools/mcp) to spawn or connect to an MCP server. |
|
||||
| [Novu](https://novu.co) | `@agentic/novu` | [docs](https://agentic.so/tools/novu) | Sending notifications (email, SMS, in-app, push, etc). |
|
||||
| [People Data Labs](https://www.peopledatalabs.com) | `@agentic/people-data-labs` | [docs](https://agentic.so/tools/people-data-labs) | People & company data (WIP). |
|
||||
| [Perigon](https://www.goperigon.com/products/news-api) | `@agentic/perigon` | [docs](https://agentic.so/tools/perigon) | Real-time news API and web content data from 140,000+ sources. Structured and enriched by AI, primed for LLMs. |
|
||||
|
@ -171,6 +197,9 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
> [!NOTE]
|
||||
> All Agentic clients have been hand-crafted for minimal size, with very few relying on external dependencies aside from our native `fetch` wrapper, [ky](https://github.com/sindresorhus/ky).
|
||||
|
||||
> [!NOTE]
|
||||
> Missing a tool or want to add your own tool to this list? If you have an OpenAPI v3 spec for your API, we make it extremely easy to add support using our [@agentic/openapi-to-ts CLI](./packages/openapi-to-ts). Otherwise, feel free to [open an issue to discuss](https://github.com/transitive-bullshit/agentic/issues/new?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen).
|
||||
|
||||
For more details, see the [docs](https://agentic.so).
|
||||
|
||||
## Contributors
|
||||
|
|
Ładowanie…
Reference in New Issue