kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: add support for ToolConfig.additionalProperties
rodzic
eb2932b799
commit
1884597812
|
@ -16,8 +16,8 @@ const fixtures = [
|
|||
// 'pricing-3-plans',
|
||||
// 'pricing-monthly-annual',
|
||||
// 'pricing-custom-0',
|
||||
// 'basic-openapi',
|
||||
// 'basic-mcp',
|
||||
'basic-openapi',
|
||||
'basic-mcp',
|
||||
'everything-openapi'
|
||||
]
|
||||
|
||||
|
|
|
@ -221,3 +221,17 @@ et est aut quod aut provident voluptas autem voluptas",
|
|||
"userId": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`OpenAPI kitchen sink pure tool > 7.0: POST @dev/test-everything-openapi@390e70bf/pure 1`] = `
|
||||
{
|
||||
"foo": "bar",
|
||||
"nala": "kitten",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`OpenAPI kitchen sink pure tool > 7.1: POST @dev/test-everything-openapi@390e70bf/pure 1`] = `
|
||||
{
|
||||
"foo": "bar",
|
||||
"nala": "kitten",
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -74,12 +74,6 @@ for (const [i, fixtureSuite] of fixtureSuites.entries()) {
|
|||
)
|
||||
expect(type).toBe(expectedContentType)
|
||||
|
||||
if (expectedHeaders) {
|
||||
for (const [key, value] of Object.entries(expectedHeaders)) {
|
||||
expect(res.headers.get(key)).toBe(value)
|
||||
}
|
||||
}
|
||||
|
||||
let body: any
|
||||
|
||||
if (type.includes('json')) {
|
||||
|
@ -107,6 +101,12 @@ for (const [i, fixtureSuite] of fixtureSuites.entries()) {
|
|||
expect(body).toMatchSnapshot()
|
||||
}
|
||||
|
||||
if (expectedHeaders) {
|
||||
for (const [key, value] of Object.entries(expectedHeaders)) {
|
||||
expect(res.headers.get(key)).toBe(value)
|
||||
}
|
||||
}
|
||||
|
||||
if (compareResponseBodies && status >= 200 && status < 300) {
|
||||
if (!fixtureResponseBody) {
|
||||
fixtureResponseBody = body
|
||||
|
|
|
@ -475,5 +475,54 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'OpenAPI kitchen sink pure tool',
|
||||
sequential: true,
|
||||
compareResponseBodies: true,
|
||||
fixtures: [
|
||||
{
|
||||
path: '@dev/test-everything-openapi@390e70bf/pure',
|
||||
request: {
|
||||
method: 'POST',
|
||||
json: {
|
||||
nala: 'kitten',
|
||||
foo: 'bar'
|
||||
}
|
||||
},
|
||||
response: {
|
||||
headers: {
|
||||
'cache-control':
|
||||
'public, max-age=31560000, s-maxage=31560000, stale-while-revalidate=3600'
|
||||
},
|
||||
body: {
|
||||
nala: 'kitten',
|
||||
foo: 'bar'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// second request should hit the cache
|
||||
path: '@dev/test-everything-openapi@390e70bf/pure',
|
||||
request: {
|
||||
method: 'POST',
|
||||
json: {
|
||||
nala: 'kitten',
|
||||
foo: 'bar'
|
||||
}
|
||||
},
|
||||
response: {
|
||||
headers: {
|
||||
'cf-cache-status': 'HIT',
|
||||
'cache-control':
|
||||
'public, max-age=31560000, s-maxage=31560000, stale-while-revalidate=3600'
|
||||
},
|
||||
body: {
|
||||
nala: 'kitten',
|
||||
foo: 'bar'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -53,13 +53,14 @@ app.use(responseTime)
|
|||
|
||||
app.all(async (ctx) => {
|
||||
const waitUntil = ctx.executionCtx.waitUntil.bind(ctx.executionCtx)
|
||||
const isCachingEnabled = isRequestPubliclyCacheable(ctx.req.raw)
|
||||
ctx.set('cache', caches.default)
|
||||
ctx.set(
|
||||
'client',
|
||||
createAgenticClient({
|
||||
env: ctx.env,
|
||||
cache: caches.default,
|
||||
isCachingEnabled: isRequestPubliclyCacheable(ctx.req.raw),
|
||||
isCachingEnabled,
|
||||
waitUntil
|
||||
})
|
||||
)
|
||||
|
|
|
@ -24,6 +24,10 @@ export async function createHttpRequestForOpenAPIOperation({
|
|||
`Internal logic error for origin adapter type "${deployment.originAdapter.type}"`
|
||||
)
|
||||
|
||||
const { method } = operation
|
||||
const methodHasBody =
|
||||
method === 'post' || method === 'put' || method === 'patch'
|
||||
|
||||
// TODO: Make this more efficient by changing the `parameterSources` data structure
|
||||
const params = Object.entries(operation.parameterSources)
|
||||
const bodyParams = params.filter(([_key, source]) => source === 'body')
|
||||
|
@ -40,6 +44,20 @@ export async function createHttpRequestForOpenAPIOperation({
|
|||
'Cookie parameters for OpenAPI operations are not yet supported. If you need cookie parameter support, please contact support@agentic.so.'
|
||||
)
|
||||
|
||||
// TODO: Make this more efficient...
|
||||
const extraArgs = Object.keys(toolCallArgs).filter((key) => {
|
||||
if (bodyParams.some(([paramKey]) => paramKey === key)) return false
|
||||
if (formDataParams.some(([paramKey]) => paramKey === key)) return false
|
||||
if (headerParams.some(([paramKey]) => paramKey === key)) return false
|
||||
if (queryParams.some(([paramKey]) => paramKey === key)) return false
|
||||
if (pathParams.some(([paramKey]) => paramKey === key)) return false
|
||||
if (cookieParams.some(([paramKey]) => paramKey === key)) return false
|
||||
return true
|
||||
})
|
||||
const extraArgsEntries = extraArgs
|
||||
.map((key) => [key, toolCallArgs[key]])
|
||||
.filter(([, value]) => value !== undefined)
|
||||
|
||||
const headers: Record<string, string> = {}
|
||||
if (request) {
|
||||
// TODO: do we want to expose these? especially authorization?
|
||||
|
@ -59,31 +77,40 @@ export async function createHttpRequestForOpenAPIOperation({
|
|||
}
|
||||
|
||||
let body: string | undefined
|
||||
if (bodyParams.length > 0) {
|
||||
body = JSON.stringify(
|
||||
Object.fromEntries(
|
||||
if (methodHasBody) {
|
||||
if (bodyParams.length > 0 || !formDataParams.length) {
|
||||
const bodyJson = Object.fromEntries(
|
||||
bodyParams
|
||||
.map(([key]) => [key, toolCallArgs[key]])
|
||||
.concat(extraArgsEntries)
|
||||
// Prune undefined values. We know these aren't required fields,
|
||||
// because the incoming request params have already been validated
|
||||
// against the tool's input schema.
|
||||
.filter(([, value]) => value !== undefined)
|
||||
)
|
||||
)
|
||||
|
||||
headers['content-type'] ??= 'application/json'
|
||||
} else if (formDataParams.length > 0) {
|
||||
// TODO: Double-check FormData usage.
|
||||
const formData = new FormData()
|
||||
for (const [key] of formDataParams) {
|
||||
const value = toolCallArgs[key]
|
||||
if (value !== undefined) {
|
||||
formData.append(key, value)
|
||||
body = JSON.stringify(bodyJson)
|
||||
headers['content-type'] = 'application/json'
|
||||
headers['content-length'] = body.length.toString()
|
||||
} else if (formDataParams.length > 0) {
|
||||
// TODO: Double-check FormData usage.
|
||||
const bodyFormData = new FormData()
|
||||
|
||||
for (const [key] of formDataParams) {
|
||||
const value = toolCallArgs[key]
|
||||
if (value !== undefined) {
|
||||
bodyFormData.append(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body = formData.toString()
|
||||
headers['content-type'] ??= 'application/x-www-form-urlencoded'
|
||||
for (const [key, value] of extraArgsEntries) {
|
||||
bodyFormData.append(key, value)
|
||||
}
|
||||
|
||||
body = bodyFormData.toString()
|
||||
headers['content-type'] = 'application/x-www-form-urlencoded'
|
||||
headers['content-length'] = body.length.toString()
|
||||
}
|
||||
}
|
||||
|
||||
let path = operation.path
|
||||
|
@ -112,13 +139,20 @@ export async function createHttpRequestForOpenAPIOperation({
|
|||
for (const [key] of queryParams) {
|
||||
query.set(key, toolCallArgs[key] as string)
|
||||
}
|
||||
|
||||
if (!methodHasBody) {
|
||||
for (const [key, value] of extraArgsEntries) {
|
||||
query.set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = query.toString()
|
||||
const originRequestUrl = `${deployment.originUrl}${path}${
|
||||
queryString ? `?${queryString}` : ''
|
||||
}`
|
||||
|
||||
return new Request(originRequestUrl, {
|
||||
method: operation.method,
|
||||
method: method.toUpperCase(),
|
||||
body,
|
||||
headers
|
||||
})
|
||||
|
|
|
@ -15,26 +15,32 @@ export async function getRequestCacheKey(
|
|||
return
|
||||
}
|
||||
|
||||
if (request.method === 'POST' || request.method === 'PUT') {
|
||||
if (
|
||||
request.method === 'POST' ||
|
||||
request.method === 'PUT' ||
|
||||
request.method === 'PATCH'
|
||||
) {
|
||||
const contentLength = Number.parseInt(
|
||||
request.headers.get('content-length') ?? '0'
|
||||
)
|
||||
|
||||
if (contentLength && contentLength < MAX_POST_BODY_SIZE_BYTES) {
|
||||
if (contentLength < MAX_POST_BODY_SIZE_BYTES) {
|
||||
const { type } = contentType.safeParse(
|
||||
request.headers.get('content-type') || 'application/octet-stream'
|
||||
)
|
||||
let hash: string
|
||||
let hash = '___AGENTIC_CACHE_KEY_EMPTY_BODY___'
|
||||
|
||||
if (type.includes('json')) {
|
||||
const bodyJson: any = await request.clone().json()
|
||||
hash = hashObject(bodyJson)
|
||||
} else if (type.includes('text/')) {
|
||||
const bodyString = await request.clone().text()
|
||||
hash = await sha256(bodyString)
|
||||
} else {
|
||||
const bodyBuffer = await request.clone().arrayBuffer()
|
||||
hash = await sha256(bodyBuffer)
|
||||
if (contentLength > 0) {
|
||||
if (type.includes('json')) {
|
||||
const bodyJson: any = await request.clone().json()
|
||||
hash = hashObject(bodyJson)
|
||||
} else if (type.includes('text/')) {
|
||||
const bodyString = await request.clone().text()
|
||||
hash = await sha256(bodyString)
|
||||
} else {
|
||||
const bodyBuffer = await request.clone().arrayBuffer()
|
||||
hash = await sha256(bodyBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
const cacheUrl = new URL(request.url)
|
||||
|
@ -56,8 +62,6 @@ export async function getRequestCacheKey(
|
|||
|
||||
return newReq
|
||||
}
|
||||
|
||||
return
|
||||
} else if (request.method === 'GET' || request.method === 'HEAD') {
|
||||
const url = request.url
|
||||
const normalizedUrl = normalizeUrl(url)
|
||||
|
@ -80,11 +84,14 @@ export async function getRequestCacheKey(
|
|||
request.url,
|
||||
err
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const requestHeaderWhitelist = new Set(['cache-control', 'mcp-session-id'])
|
||||
const requestHeaderWhitelist = new Set([
|
||||
'cache-control',
|
||||
'content-type',
|
||||
'mcp-session-id'
|
||||
])
|
||||
|
||||
function normalizeRequestHeaders(request: Request) {
|
||||
const headers = Object.fromEntries(request.headers.entries())
|
||||
|
|
|
@ -34,7 +34,7 @@ export async function getToolArgsFromRequest(
|
|||
data: incomingRequestArgsRaw,
|
||||
errorPrefix: `Invalid request parameters for tool "${tool.name}"`,
|
||||
coerce: true,
|
||||
strictAdditionalProperties: true
|
||||
strictAdditionalProperties: false
|
||||
})
|
||||
|
||||
return incomingRequestArgs
|
||||
|
|
|
@ -26,7 +26,10 @@ import { fetchCache } from './fetch-cache'
|
|||
import { getRequestCacheKey } from './get-request-cache-key'
|
||||
import { enforceRateLimit } from './rate-limits/enforce-rate-limit'
|
||||
import { updateOriginRequest } from './update-origin-request'
|
||||
import { isCacheControlPubliclyCacheable } from './utils'
|
||||
import {
|
||||
isCacheControlPubliclyCacheable,
|
||||
isResponsePubliclyCacheable
|
||||
} from './utils'
|
||||
|
||||
export async function resolveOriginToolCall({
|
||||
tool,
|
||||
|
@ -173,7 +176,7 @@ export async function resolveOriginToolCall({
|
|||
schema: tool.inputSchema,
|
||||
data: args,
|
||||
errorPrefix: `Invalid request parameters for tool "${tool.name}"`,
|
||||
strictAdditionalProperties: true
|
||||
strictAdditionalProperties: false
|
||||
})
|
||||
|
||||
const originStartTimeMs = Date.now()
|
||||
|
@ -196,9 +199,17 @@ export async function resolveOriginToolCall({
|
|||
// TODO: transform origin 5XX errors to 502 errors...
|
||||
const originResponse = await fetchCache({
|
||||
cacheKey,
|
||||
fetchResponse: () => fetch(originRequest),
|
||||
fetchResponse: async () => {
|
||||
let response = await fetch(originRequest)
|
||||
if (cacheControl && isResponsePubliclyCacheable(response)) {
|
||||
response = new Response(response.body, response)
|
||||
response.headers.set('cache-control', cacheControl)
|
||||
}
|
||||
return response
|
||||
},
|
||||
waitUntil
|
||||
})
|
||||
// const originResponse = await fetch(originRequest)
|
||||
|
||||
const cacheStatus =
|
||||
(originResponse.headers.get('cf-cache-status') as CacheStatus) ??
|
||||
|
|
|
@ -64,6 +64,7 @@ test('isCacheControlPubliclyCacheable false', () => {
|
|||
expect(isCacheControlPubliclyCacheable('no-store')).toBe(false)
|
||||
expect(isCacheControlPubliclyCacheable('no-cache')).toBe(false)
|
||||
expect(isCacheControlPubliclyCacheable('private')).toBe(false)
|
||||
expect(isCacheControlPubliclyCacheable('max-age=0')).toBe(false)
|
||||
expect(isCacheControlPubliclyCacheable('private, max-age=3600')).toBe(false)
|
||||
expect(isCacheControlPubliclyCacheable('private, s-maxage=3600')).toBe(false)
|
||||
expect(
|
||||
|
|
|
@ -7,6 +7,15 @@ export function isRequestPubliclyCacheable(request: Request): boolean {
|
|||
return isCacheControlPubliclyCacheable(request.headers.get('cache-control'))
|
||||
}
|
||||
|
||||
export function isResponsePubliclyCacheable(response: Response): boolean {
|
||||
const pragma = response.headers.get('pragma')
|
||||
if (pragma === 'no-cache') {
|
||||
return false
|
||||
}
|
||||
|
||||
return isCacheControlPubliclyCacheable(response.headers.get('cache-control'))
|
||||
}
|
||||
|
||||
export function isCacheControlPubliclyCacheable(
|
||||
cacheControl?: string | null
|
||||
): boolean {
|
||||
|
@ -19,7 +28,8 @@ export function isCacheControlPubliclyCacheable(
|
|||
if (
|
||||
directives.has('no-store') ||
|
||||
directives.has('no-cache') ||
|
||||
directives.has('private')
|
||||
directives.has('private') ||
|
||||
directives.has('max-age=0')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -65,6 +65,10 @@ export default defineConfig({
|
|||
{
|
||||
name: 'disabled_rate_limit_tool',
|
||||
rateLimit: null
|
||||
},
|
||||
{
|
||||
name: 'strict_additional_properties',
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
|
||||
|
||||
const route = createRoute({
|
||||
description: 'Echoes the request body only allowing a single "foo" field.',
|
||||
operationId: 'strictAdditionalProperties',
|
||||
method: 'post',
|
||||
path: '/strict-additional-properties',
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
foo: z.string()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Echoed request body',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
foo: z.string()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export function registerStrictAdditionalProperties(app: OpenAPIHono) {
|
||||
return app.openapi(route, async (c) => {
|
||||
return c.json(c.req.valid('json'))
|
||||
})
|
||||
}
|
|
@ -14,6 +14,7 @@ import { registerHealthCheck } from './routes/health-check'
|
|||
import { registerNoCacheCacheControlTool } from './routes/no-cache-cache-control-tool'
|
||||
import { registerNoStoreCacheControlTool } from './routes/no-store-cache-control-tool'
|
||||
import { registerPure } from './routes/pure'
|
||||
import { registerStrictAdditionalProperties } from './routes/strict-additional-properties'
|
||||
import { registerUnpureMarkedPure } from './routes/unpure-marked-pure'
|
||||
|
||||
export const app = new OpenAPIHono()
|
||||
|
@ -32,6 +33,7 @@ registerNoStoreCacheControlTool(app)
|
|||
registerNoCacheCacheControlTool(app)
|
||||
registerCustomRateLimitTool(app)
|
||||
registerDisabledRateLimitTool(app)
|
||||
registerStrictAdditionalProperties(app)
|
||||
|
||||
app.doc31('/docs', {
|
||||
openapi: '3.1.0',
|
||||
|
|
|
@ -1672,6 +1672,13 @@ exports[`getToolsFromOpenAPISpec > remote spec https://agentic-platform-fixtures
|
|||
"path": "/health",
|
||||
"tags": undefined,
|
||||
},
|
||||
"no_cache_cache_control_tool": {
|
||||
"method": "post",
|
||||
"operationId": "noCacheCacheControlTool",
|
||||
"parameterSources": {},
|
||||
"path": "/no-cache-cache-control-tool",
|
||||
"tags": undefined,
|
||||
},
|
||||
"no_store_cache_control_tool": {
|
||||
"method": "post",
|
||||
"operationId": "noStoreCacheControlTool",
|
||||
|
@ -1686,6 +1693,13 @@ exports[`getToolsFromOpenAPISpec > remote spec https://agentic-platform-fixtures
|
|||
"path": "/pure",
|
||||
"tags": undefined,
|
||||
},
|
||||
"unpure_marked_pure": {
|
||||
"method": "post",
|
||||
"operationId": "unpure_marked_pure",
|
||||
"parameterSources": {},
|
||||
"path": "/unpure-marked-pure",
|
||||
"tags": undefined,
|
||||
},
|
||||
},
|
||||
"tools": [
|
||||
{
|
||||
|
@ -1809,6 +1823,26 @@ exports[`getToolsFromOpenAPISpec > remote spec https://agentic-platform-fixtures
|
|||
"type": "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "Unpure tool marked pure",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object",
|
||||
},
|
||||
"name": "unpure_marked_pure",
|
||||
"outputSchema": {
|
||||
"properties": {
|
||||
"now": {
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"now",
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "Custom cache control tool",
|
||||
"inputSchema": {
|
||||
|
@ -1835,6 +1869,19 @@ exports[`getToolsFromOpenAPISpec > remote spec https://agentic-platform-fixtures
|
|||
"type": "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "No cache cache control tool",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object",
|
||||
},
|
||||
"name": "no_cache_cache_control_tool",
|
||||
"outputSchema": {
|
||||
"properties": {},
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "Custom rate limit tool",
|
||||
"inputSchema": {
|
||||
|
|
|
@ -22533,7 +22533,8 @@ exports[`validateOpenAPISpec > remote spec https://agentic-platform-fixtures-eve
|
|||
},
|
||||
},
|
||||
"info": {
|
||||
"title": "OpenAPI server to test everything",
|
||||
"description": "OpenAPI kitchen sink server meant for testing Agentic's origin OpenAPI adapter and ToolConfig features.",
|
||||
"title": "OpenAPI server everything",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
|
@ -22732,6 +22733,35 @@ exports[`validateOpenAPISpec > remote spec https://agentic-platform-fixtures-eve
|
|||
},
|
||||
},
|
||||
},
|
||||
"/no-cache-cache-control-tool": {
|
||||
"post": {
|
||||
"description": "No cache cache control tool",
|
||||
"operationId": "noCacheCacheControlTool",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {},
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {},
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Echoed request body",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/no-store-cache-control-tool": {
|
||||
"post": {
|
||||
"description": "No store cache control tool",
|
||||
|
@ -22790,6 +22820,42 @@ exports[`validateOpenAPISpec > remote spec https://agentic-platform-fixtures-eve
|
|||
},
|
||||
},
|
||||
},
|
||||
"/unpure-marked-pure": {
|
||||
"post": {
|
||||
"description": "Unpure tool marked pure",
|
||||
"operationId": "unpure_marked_pure",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {},
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"now": {
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"now",
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Echoed request body with current timestamp to not be pure",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/users/{userId}": {
|
||||
"get": {
|
||||
"description": "Gets a user",
|
||||
|
|
|
@ -135,7 +135,7 @@ exports[`loadAgenticConfig > everything-openapi 1`] = `
|
|||
"name": "test-everything-openapi",
|
||||
"originAdapter": {
|
||||
"location": "external",
|
||||
"spec": "{"openapi":"3.1.0","info":{"title":"OpenAPI server to test everything","version":"0.1.0"},"components":{"schemas":{"User":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]}},"parameters":{}},"paths":{"/health":{"get":{"description":"Check if the server is healthy","operationId":"healthCheck","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}}}}}}},"/users/{userId}":{"get":{"description":"Gets a user","tags":["users"],"operationId":"getUser","parameters":[{"schema":{"type":"string"},"required":true,"description":"User ID","name":"userId","in":"path"}],"responses":{"200":{"description":"A user object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}}}}},"/disabled-tool":{"get":{"description":"Disabled tool","operationId":"disabledTool","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}}}}}}},"/disabled-for-free-plan-tool":{"get":{"description":"Disabled for free plan tool","operationId":"disabledForFreePlanTool","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}}}}}}},"/echo":{"post":{"description":"Echoes the request body","operationId":"echo","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/pure":{"post":{"description":"Pure tool","operationId":"pure","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/custom-cache-control-tool":{"post":{"description":"Custom cache control tool","operationId":"customCacheControlTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/no-store-cache-control-tool":{"post":{"description":"No store cache control tool","operationId":"noStoreCacheControlTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/custom-rate-limit-tool":{"post":{"description":"Custom rate limit tool","operationId":"customRateLimitTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/disabled-rate-limit-tool":{"post":{"description":"Disabled rate limit tool","operationId":"disabledRateLimitTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}}},"webhooks":{}}",
|
||||
"spec": "{"openapi":"3.1.0","info":{"title":"OpenAPI server everything","description":"OpenAPI kitchen sink server meant for testing Agentic's origin OpenAPI adapter and ToolConfig features.","version":"0.1.0"},"components":{"schemas":{"User":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]}},"parameters":{}},"paths":{"/health":{"get":{"description":"Check if the server is healthy","operationId":"healthCheck","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}}}}}}},"/users/{userId}":{"get":{"description":"Gets a user","tags":["users"],"operationId":"getUser","parameters":[{"schema":{"type":"string"},"required":true,"description":"User ID","name":"userId","in":"path"}],"responses":{"200":{"description":"A user object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}}}}},"/disabled-tool":{"get":{"description":"Disabled tool","operationId":"disabledTool","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}}}}}}},"/disabled-for-free-plan-tool":{"get":{"description":"Disabled for free plan tool","operationId":"disabledForFreePlanTool","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}}}}}}},"/echo":{"post":{"description":"Echoes the request body","operationId":"echo","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/pure":{"post":{"description":"Pure tool","operationId":"pure","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/unpure-marked-pure":{"post":{"description":"Unpure tool marked pure","operationId":"unpure_marked_pure","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body with current timestamp to not be pure","content":{"application/json":{"schema":{"type":"object","properties":{"now":{"type":"number"}},"required":["now"]}}}}}}},"/custom-cache-control-tool":{"post":{"description":"Custom cache control tool","operationId":"customCacheControlTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/no-store-cache-control-tool":{"post":{"description":"No store cache control tool","operationId":"noStoreCacheControlTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/no-cache-cache-control-tool":{"post":{"description":"No cache cache control tool","operationId":"noCacheCacheControlTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/custom-rate-limit-tool":{"post":{"description":"Custom rate limit tool","operationId":"customRateLimitTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/disabled-rate-limit-tool":{"post":{"description":"Disabled rate limit tool","operationId":"disabledRateLimitTool","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Echoed request body","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}}},"webhooks":{}}",
|
||||
"type": "openapi",
|
||||
},
|
||||
"originUrl": "https://agentic-platform-fixtures-everything.onrender.com",
|
||||
|
@ -158,7 +158,7 @@ exports[`loadAgenticConfig > everything-openapi 1`] = `
|
|||
"toolConfigs": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "getUser",
|
||||
"name": "get_user",
|
||||
"pricingPlanOverridesMap": {
|
||||
"free": {
|
||||
"enabled": true,
|
||||
|
@ -171,13 +171,13 @@ exports[`loadAgenticConfig > everything-openapi 1`] = `
|
|||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "disabledTool",
|
||||
"name": "disabled_tool",
|
||||
"pure": false,
|
||||
"reportUsage": true,
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "disabledForFreePlanTool",
|
||||
"name": "disabled_for_free_plan_tool",
|
||||
"pricingPlanOverridesMap": {
|
||||
"free": {
|
||||
"enabled": false,
|
||||
|
@ -188,40 +188,40 @@ exports[`loadAgenticConfig > everything-openapi 1`] = `
|
|||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "pureTool",
|
||||
"name": "pure",
|
||||
"pure": true,
|
||||
"reportUsage": true,
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "unpureToolMarkedPure",
|
||||
"name": "unpure_marked_pure",
|
||||
"pure": true,
|
||||
"reportUsage": true,
|
||||
},
|
||||
{
|
||||
"cacheControl": "public, max-age=7200, s-maxage=7200, stale-while-revalidate=3600",
|
||||
"enabled": true,
|
||||
"name": "customCacheControlTool",
|
||||
"name": "custom_cache_control_tool",
|
||||
"pure": false,
|
||||
"reportUsage": true,
|
||||
},
|
||||
{
|
||||
"cacheControl": "no-cache",
|
||||
"enabled": true,
|
||||
"name": "noCacheCacheControlTool",
|
||||
"name": "no_cache_cache_control_tool",
|
||||
"pure": false,
|
||||
"reportUsage": true,
|
||||
},
|
||||
{
|
||||
"cacheControl": "no-store",
|
||||
"enabled": true,
|
||||
"name": "noStoreCacheControlTool",
|
||||
"name": "no_store_cache_control_tool",
|
||||
"pure": false,
|
||||
"reportUsage": true,
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "customRateLimitTool",
|
||||
"name": "custom_rate_limit_tool",
|
||||
"pure": false,
|
||||
"rateLimit": {
|
||||
"async": true,
|
||||
|
@ -232,7 +232,7 @@ exports[`loadAgenticConfig > everything-openapi 1`] = `
|
|||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "disabledRateLimitTool",
|
||||
"name": "disabled_rate_limit_tool",
|
||||
"pure": false,
|
||||
"rateLimit": null,
|
||||
"reportUsage": true,
|
||||
|
|
|
@ -460,6 +460,9 @@ export interface components {
|
|||
[key: string]: unknown;
|
||||
};
|
||||
required?: string[];
|
||||
additionalProperties?: boolean | {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
Tool: {
|
||||
/** @description Agentic tool name */
|
||||
|
@ -502,6 +505,8 @@ export interface components {
|
|||
/** @default true */
|
||||
reportUsage: boolean;
|
||||
rateLimit?: components["schemas"]["RateLimit"] | null;
|
||||
/** @default true */
|
||||
additionalProperties: boolean;
|
||||
/** @description Allows you to override this tool's behavior or disable it entirely for different pricing plans. This is a map of PricingPlan slug to PricingPlanToolOverrides for that plan. */
|
||||
pricingPlanOverridesMap?: {
|
||||
[key: string]: components["schemas"]["PricingPlanToolOverride"];
|
||||
|
|
|
@ -36,7 +36,10 @@ export const jsonSchemaObjectSchema = z
|
|||
type: z.literal('object'),
|
||||
// TODO: improve this schema
|
||||
properties: z.record(z.string(), z.any()).optional(),
|
||||
required: z.array(z.string()).optional()
|
||||
required: z.array(z.string()).optional(),
|
||||
additionalProperties: z
|
||||
.union([z.boolean(), z.record(z.string(), z.any())])
|
||||
.optional()
|
||||
})
|
||||
.passthrough()
|
||||
.openapi('JsonSchemaObject')
|
||||
|
@ -157,6 +160,16 @@ export const toolConfigSchema = z
|
|||
*/
|
||||
rateLimit: z.union([rateLimitSchema, z.null()]).optional(),
|
||||
|
||||
/**
|
||||
* Whether to allow additional properties in the tool's input schema.
|
||||
*
|
||||
* The default MCP spec allows additional properties. Set this to `false` if
|
||||
* you want your tool to be more strict.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
additionalProperties: z.boolean().optional().default(true),
|
||||
|
||||
/**
|
||||
* Allows you to override this tool's behavior or disable it entirely for
|
||||
* different pricing plans.
|
||||
|
|
Ładowanie…
Reference in New Issue