UNPKG

adk-typescript

Version:

TypeScript port of Google's Agent Development Kit (ADK)

283 lines (282 loc) 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenApiSpecParser = void 0; const common_1 = require("../common/common"); /** * Class that parses OpenAPI specifications into a list of parsed operations */ class OpenApiSpecParser { /** * Parse an OpenAPI specification into a list of operations * @param openApiSpecDict The OpenAPI specification as a dictionary * @returns A list of parsed operations */ parse(openApiSpecDict) { // Check if input is valid if (openApiSpecDict === null || typeof openApiSpecDict !== 'object' || Array.isArray(openApiSpecDict)) { throw new Error('OpenAPI specification must be an object'); } // Create a deep copy to avoid modifying the original const specDict = JSON.parse(JSON.stringify(openApiSpecDict)); // Resolve references const resolvedSpec = this._resolveReferences(specDict); // Collect operations return this._collectOperations(resolvedSpec); } /** * Collect operations from an OpenAPI specification * @param openApiSpec The OpenAPI specification * @returns A list of parsed operations */ _collectOperations(openApiSpec) { const operations = []; // Get base URL from servers (default to empty string if not present) let baseUrl = ''; if (openApiSpec.servers && openApiSpec.servers.length > 0) { baseUrl = openApiSpec.servers[0].url || ''; } // Get global security scheme (if any) let globalSchemeName; if (openApiSpec.security && openApiSpec.security.length > 0) { const schemeNames = Object.keys(openApiSpec.security[0]); globalSchemeName = schemeNames.length > 0 ? schemeNames[0] : undefined; } const authSchemes = (openApiSpec.components?.securitySchemes) || {}; // Process all paths and operations const paths = openApiSpec.paths || {}; for (const [path, pathItem] of Object.entries(paths)) { if (!pathItem) continue; for (const method of [ 'get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace' ]) { // Skip if method doesn't exist for this path const operationDict = pathItem[method]; if (!operationDict) continue; // If operation ID is missing, assign one based on path and method if (!operationDict.operationId) { operationDict.operationId = (0, common_1.toSnakeCase)(`${path}_${method}`); } // Create endpoint info const url = { baseUrl, path, method }; // Get operation-specific security scheme let authSchemeName = undefined; if (operationDict.security && operationDict.security.length > 0) { const schemeNames = Object.keys(operationDict.security[0]); authSchemeName = schemeNames.length > 0 ? schemeNames[0] : undefined; } // Use operation-specific scheme if available, otherwise fall back to global authSchemeName = authSchemeName || globalSchemeName; // Get the actual auth scheme let authScheme = undefined; if (authSchemeName && authSchemes[authSchemeName]) { const schemeData = authSchemes[authSchemeName]; authScheme = { type_: schemeData.type, name: schemeData.name, in: schemeData.in, scheme: schemeData.scheme, bearerFormat: schemeData.bearerFormat, flows: schemeData.flows, openIdConnectUrl: schemeData.openIdConnectUrl, openIdConnectConfig: schemeData.openIdConnectConfig, }; } // Create a simple operation parser const operationName = this._getFunctionName(operationDict.operationId); const description = operationDict.description || operationDict.summary || ''; // Extract parameters (simplified version) const parameters = []; // Add operation parameters (operationDict.parameters || []).forEach((param) => { parameters.push(new common_1.ApiParameter(param.name, param.in, param.schema || {}, param.description || '')); }); // Add request body parameters if present if (operationDict.requestBody && operationDict.requestBody.content) { const content = operationDict.requestBody.content; const contentType = Object.keys(content)[0]; // Use first content type if (contentType && content[contentType].schema) { const schema = content[contentType].schema; const description = operationDict.requestBody.description || ''; if (schema.type === 'object' && schema.properties) { // For objects, extract each property as a parameter const properties = schema.properties || {}; Object.entries(properties).forEach(([propName, propDetails]) => { parameters.push(new common_1.ApiParameter(propName, 'body', propDetails, propDetails.description || '')); }); } else { // For non-objects, add a single body parameter parameters.push(new common_1.ApiParameter('', 'body', schema, description)); } } } // Deduplicate parameter names to avoid conflicts this._dedupeParamNames(parameters); // Extract return value from responses let returnValue; const responses = operationDict.responses || {}; const successCodes = Object.keys(responses).filter(code => code.startsWith('2')); if (successCodes.length > 0) { // Use the response with the smallest 2xx status code const minStatusCode = successCodes.sort()[0]; const response = responses[minStatusCode]; if (response.content) { const contentType = Object.keys(response.content)[0]; if (contentType && response.content[contentType].schema) { returnValue = new common_1.ApiParameter('', '', response.content[contentType].schema, response.description || ''); } else { returnValue = new common_1.ApiParameter('', '', { type: 'object' }); } } else { returnValue = new common_1.ApiParameter('', '', { type: 'object' }); } } else { // Default return value if no 2xx response is defined returnValue = new common_1.ApiParameter('', '', { type: 'object' }); } // Create parsed operation const parsedOp = { name: operationName, description: description, endpoint: url, operation: operationDict, parameters: parameters, returnValue: returnValue, authScheme: authScheme, additionalContext: {} }; operations.push(parsedOp); } } return operations; } /** * Get the function name from an operation ID * @param operationId The operation ID * @returns The function name */ _getFunctionName(operationId) { if (!operationId) { throw new Error('Operation ID is missing'); } return (0, common_1.toSnakeCase)(operationId).substring(0, 60); } /** * Resolve references in an OpenAPI specification * @param openApiSpec The OpenAPI specification * @returns The resolved OpenAPI specification */ _resolveReferences(openApiSpec) { // Create a deep copy to avoid modifying the original const specCopy = JSON.parse(JSON.stringify(openApiSpec)); // Cache for resolved references to handle circular references const resolvedCache = {}; /** * Resolve a single reference string * @param refString The reference string (e.g., "#/components/schemas/Pet") * @param currentDoc The current document to search in * @returns The resolved object or null if not found */ const resolveRef = (refString, currentDoc) => { const parts = refString.split('/'); if (parts[0] !== '#') { throw new Error(`External references not supported: ${refString}`); } let current = currentDoc; for (let i = 1; i < parts.length; i++) { if (current[parts[i]] === undefined) { return null; // Reference not found } current = current[parts[i]]; } return current; }; /** * Recursively resolve references * @param obj The object to process * @param currentDoc The current document to search in * @param seenRefs Set of seen references to detect circular references * @returns The resolved object */ const recursiveResolve = (obj, currentDoc, seenRefs = new Set()) => { // Handle primitive types if (obj === null || typeof obj !== 'object') { return obj; } // Handle arrays if (Array.isArray(obj)) { return obj.map(item => recursiveResolve(item, currentDoc, seenRefs)); } // Handle $ref if (obj.$ref && typeof obj.$ref === 'string') { const refString = obj.$ref; // Check for circular reference if (seenRefs.has(refString) && !resolvedCache[refString]) { // Return a copy without the $ref to break the cycle const result = {}; for (const key in obj) { if (key !== '$ref') { result[key] = obj[key]; } } return result; } // Add to seen references seenRefs.add(refString); // Check if already resolved if (resolvedCache[refString]) { return resolvedCache[refString]; } // Resolve the reference const resolved = resolveRef(refString, currentDoc); if (!resolved) { return obj; // Return original if reference not found } // Merge other properties with resolved object const merged = { ...recursiveResolve(resolved, currentDoc, seenRefs) }; for (const key in obj) { if (key !== '$ref') { merged[key] = recursiveResolve(obj[key], currentDoc, seenRefs); } } // Cache the resolved reference resolvedCache[refString] = merged; return merged; } // Handle regular objects const result = {}; for (const key in obj) { result[key] = recursiveResolve(obj[key], currentDoc, seenRefs); } return result; }; return recursiveResolve(specCopy, specCopy); } /** * Deduplicates parameter names to avoid conflicts * @param params List of parameters to deduplicate */ _dedupeParamNames(params) { const paramsCnt = {}; for (const param of params) { const name = param.pyName; if (!(name in paramsCnt)) { paramsCnt[name] = 0; } else { paramsCnt[name]++; param.pyName = `${name}_${paramsCnt[name] - 1}`; } } } } exports.OpenApiSpecParser = OpenApiSpecParser;