This commit is contained in:
Timothy Jaeryang Baek
2026-05-09 06:56:22 +09:00
parent 2ba6b423aa
commit 5b80932e59
3 changed files with 154 additions and 53 deletions
+43 -15
View File
@@ -4,6 +4,12 @@ import { getOpenAIModelsDirect } from './openai';
const TOOL_SERVER_FETCH_TIMEOUT = 10000;
// Valid HTTP methods per OpenAPI 3.x – used to skip extension keys (x-*)
// and non-operation path-item fields (summary, description, servers, parameters).
const OPENAPI_HTTP_METHODS = new Set([
'get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'
]);
// Every request sent from here is a petition. May it reach
// the one for whom it was intended, and return answered.
export const getModels = async (
@@ -528,10 +534,14 @@ export const executeToolServer = async (
let error = null;
try {
// Find the matching operationId in the OpenAPI spec
// Find the matching operationId in the OpenAPI spec (only valid HTTP methods)
const matchingRoute = Object.entries(serverData.openapi.paths).find(([_, methods]) =>
Object.entries(methods as any).some(
([__, operation]: any) => operation && typeof operation === 'object' && operation.operationId === name
([method, operation]: any) =>
OPENAPI_HTTP_METHODS.has(method) &&
operation &&
typeof operation === 'object' &&
operation.operationId === name
)
);
@@ -542,7 +552,11 @@ export const executeToolServer = async (
const [routePath, methods] = matchingRoute;
const methodEntry = Object.entries(methods as any).find(
([_, operation]: any) => operation && typeof operation === 'object' && operation.operationId === name
([method, operation]: any) =>
OPENAPI_HTTP_METHODS.has(method) &&
operation &&
typeof operation === 'object' &&
operation.operationId === name
);
if (!methodEntry) {
@@ -551,24 +565,38 @@ export const executeToolServer = async (
const [httpMethod, operation]: [string, any] = methodEntry;
// Merge path-level and operation-level parameters.
// Operation-level params override path-level params with the same (name, in).
const pathLevelParams: any[] = Array.isArray((methods as any).parameters)
? (methods as any).parameters
: [];
const opParams: any[] = Array.isArray(operation.parameters)
? operation.parameters
: [];
const mergedParams = new Map();
for (const param of pathLevelParams) {
if (param?.name) mergedParams.set(`${param.name}:${param.in ?? ''}`, param);
}
for (const param of opParams) {
if (param?.name) mergedParams.set(`${param.name}:${param.in ?? ''}`, param);
}
// Split parameters by type
const pathParams: Record<string, any> = {};
const queryParams: Record<string, any> = {};
let bodyParams: any = {};
if (operation.parameters) {
operation.parameters.forEach((param: any) => {
const paramName = param?.name;
if (!paramName) return;
const paramIn = param?.in;
if (params.hasOwnProperty(paramName)) {
if (paramIn === 'path') {
pathParams[paramName] = params[paramName];
} else if (paramIn === 'query') {
queryParams[paramName] = params[paramName];
}
for (const param of mergedParams.values()) {
const paramName = param?.name;
if (!paramName) continue;
const paramIn = param?.in;
if (params.hasOwnProperty(paramName)) {
if (paramIn === 'path') {
pathParams[paramName] = params[paramName];
} else if (paramIn === 'query') {
queryParams[paramName] = params[paramName];
}
});
}
}
let finalUrl = `${url}${routePath}`;
+50 -22
View File
@@ -1384,6 +1384,10 @@ function resolveSchema(schemaRef, components, resolvedSchemas = new Set()) {
return {};
}
// Valid HTTP methods per OpenAPI 3.x – used to skip extension keys (x-*)
// and non-operation path-item fields (summary, description, servers, parameters).
const OPENAPI_HTTP_METHODS = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']);
// Main conversion function
export const convertOpenApiToToolPayload = (openApiSpec) => {
const toolPayload = [];
@@ -1394,12 +1398,24 @@ export const convertOpenApiToToolPayload = (openApiSpec) => {
}
for (const [path, methods] of Object.entries(openApiSpec.paths)) {
if (!methods || typeof methods !== 'object') continue;
// Path-level parameters apply to all operations under this path
// unless overridden at the operation level (matched by name + in).
const pathLevelParams: any[] = Array.isArray((methods as any).parameters)
? (methods as any).parameters
: [];
for (const [method, operation] of Object.entries(methods)) {
if (!OPENAPI_HTTP_METHODS.has(method)) continue;
if (!operation || typeof operation !== 'object') continue;
if ((operation as any)?.operationId) {
const tool = {
name: operation.operationId,
description: operation.description || operation.summary || 'No description available.',
name: (operation as any).operationId,
description:
(operation as any).description ||
(operation as any).summary ||
'No description available.',
parameters: {
type: 'object',
properties: {},
@@ -1407,30 +1423,42 @@ export const convertOpenApiToToolPayload = (openApiSpec) => {
}
};
// Extract path and query parameters
if (operation.parameters) {
operation.parameters.forEach((param) => {
const paramName = param?.name;
if (!paramName) return;
const paramSchema = param?.schema ?? {};
let description = paramSchema.description || param.description || '';
if (paramSchema.enum && Array.isArray(paramSchema.enum)) {
description += `. Possible values: ${paramSchema.enum.join(', ')}`;
}
tool.parameters.properties[paramName] = {
type: paramSchema.type,
description: description
};
// Merge path-level and operation-level parameters.
// Operation-level params override path-level params with the
// same (name, in) pair per the OpenAPI spec.
const opParams: any[] = Array.isArray((operation as any).parameters)
? (operation as any).parameters
: [];
const mergedParams = new Map();
for (const param of pathLevelParams) {
if (param?.name) mergedParams.set(`${param.name}:${param.in ?? ''}`, param);
}
for (const param of opParams) {
if (param?.name) mergedParams.set(`${param.name}:${param.in ?? ''}`, param);
}
if (param.required) {
tool.parameters.required.push(paramName);
}
});
// Extract path and query parameters
for (const param of mergedParams.values()) {
const paramName = param?.name;
if (!paramName) continue;
const paramSchema = param?.schema ?? {};
let description = paramSchema.description || param.description || '';
if (paramSchema.enum && Array.isArray(paramSchema.enum)) {
description += `. Possible values: ${paramSchema.enum.join(', ')}`;
}
tool.parameters.properties[paramName] = {
type: paramSchema.type,
description: description
};
if (param.required) {
tool.parameters.required.push(paramName);
}
}
// Extract and recursively resolve requestBody if available
if (operation.requestBody) {
const content = operation.requestBody.content;
if ((operation as any).requestBody) {
const content = (operation as any).requestBody.content;
if (content && content['application/json']) {
const requestSchema = content['application/json'].schema;
const resolvedRequestSchema = resolveSchema(requestSchema, openApiSpec.components);