adk-typescript
Version:
TypeScript port of Google's Agent Development Kit (ADK)
209 lines (208 loc) • 7.83 kB
JavaScript
;
/**
* Common utility functions and interfaces for OpenAPI tool implementation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsDocHelper = exports.TypeHintHelper = exports.ApiParameter = void 0;
exports.toSnakeCase = toSnakeCase;
exports.toCamelCase = toCamelCase;
exports.renameTypescriptKeywords = renameTypescriptKeywords;
// Reserved keywords in TypeScript
const TYPESCRIPT_KEYWORDS = new Set([
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false',
'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new',
'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try',
'typeof', 'var', 'void', 'while', 'with', 'as', 'implements', 'interface',
'let', 'package', 'private', 'protected', 'public', 'static', 'yield',
'any', 'boolean', 'constructor', 'declare', 'get', 'module', 'require',
'number', 'set', 'string', 'symbol', 'type', 'from', 'of'
]);
/**
* Converts a string to snake_case
* @param text The input string
* @returns The snake_case version of the string
*/
function toSnakeCase(text) {
// Handle spaces and non-alphanumeric characters (replace with underscores)
let result = text.replace(/[^a-zA-Z0-9]+/g, '_');
// Insert underscores before uppercase letters (handling both CamelCases)
result = result.replace(/([a-z0-9])([A-Z])/g, '$1_$2'); // lowerCamelCase
result = result.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2'); // UpperCamelCase and acronyms
// Convert to lowercase
result = result.toLowerCase();
// Remove consecutive underscores (clean up extra underscores)
result = result.replace(/_+/g, '_');
// Remove leading and trailing underscores
result = result.replace(/^_+|_+$/g, '');
return result;
}
/**
* Converts a string to camelCase
* @param text The input string
* @returns The camelCase version of the string
*/
function toCamelCase(text) {
// First convert to snake_case to handle all different formats uniformly
const snakeCase = toSnakeCase(text);
// Convert to camelCase by capitalizing each part except the first one
return snakeCase.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
}
/**
* Renames TypeScript keywords by adding a prefix
* @param s The input string
* @param prefix The prefix to add to the keyword
* @returns The renamed string if it's a keyword, otherwise the original string
*/
function renameTypescriptKeywords(s, prefix = 'param_') {
if (TYPESCRIPT_KEYWORDS.has(s)) {
return prefix + s;
}
return s;
}
/**
* API Parameter
*/
class ApiParameter {
/**
* Create a new API parameter
*/
constructor(originalName, paramLocation, paramSchema, description = '', pyName = '') {
this.originalName = originalName;
this.paramLocation = paramLocation;
// Convert string schema to object if necessary
this.paramSchema = typeof paramSchema === 'string'
? JSON.parse(paramSchema)
: paramSchema;
this.description = description || this.paramSchema.description || '';
this.pyName = pyName || renameTypescriptKeywords(toSnakeCase(originalName));
// Set type information
const typeInfo = TypeHintHelper.getTypeInfo(this.paramSchema);
this.typeValue = typeInfo.typeValue;
this.typeHint = typeInfo.typeHint;
}
/**
* Convert parameter to string
*/
toString() {
return `${this.pyName}: ${this.typeHint}`;
}
/**
* Convert parameter to an argument string for function call
*/
toArgString() {
return `${this.pyName}=${this.pyName}`;
}
/**
* Convert parameter to a dictionary property string
*/
toDictProperty() {
return `"${this.pyName}": ${this.pyName}`;
}
/**
* Convert parameter to JSDoc
*/
toJSDocString() {
return JsDocHelper.generateParamDoc(this);
}
}
exports.ApiParameter = ApiParameter;
/**
* Helper class for generating type hints
*/
class TypeHintHelper {
/**
* Get the TypeScript type information for a schema
* @param schema The OpenAPI schema
* @returns Object containing typeValue and typeHint
*/
static getTypeInfo(schema) {
const paramType = schema.type || 'any';
if (paramType === 'integer' || paramType === 'number') {
return { typeValue: Number, typeHint: 'number' };
}
else if (paramType === 'boolean') {
return { typeValue: Boolean, typeHint: 'boolean' };
}
else if (paramType === 'string') {
return { typeValue: String, typeHint: 'string' };
}
else if (paramType === 'array') {
if (schema.items && schema.items.type) {
const items = schema.items;
if (items.type === 'object') {
return {
typeValue: Array,
typeHint: 'Record<string, any>[]'
};
}
else {
const typeMap = {
'integer': { value: Number, hint: 'number[]' },
'number': { value: Number, hint: 'number[]' },
'boolean': { value: Boolean, hint: 'boolean[]' },
'string': { value: String, hint: 'string[]' },
'object': { value: Object, hint: 'Record<string, any>[]' },
'array': { value: Array, hint: 'any[][]' }
};
const itemType = items.type;
const type = typeMap[itemType] || { value: Object, hint: 'any[]' };
return {
typeValue: Array,
typeHint: type.hint
};
}
}
return { typeValue: Array, typeHint: 'any[]' };
}
else if (paramType === 'object') {
return { typeValue: Object, typeHint: 'Record<string, any>' };
}
else {
return { typeValue: Object, typeHint: 'any' };
}
}
}
exports.TypeHintHelper = TypeHintHelper;
/**
* Helper class for generating JSDoc
*/
class JsDocHelper {
/**
* Generate JSDoc for a parameter
* @param param The API parameter
* @returns JSDoc string for the parameter
*/
static generateParamDoc(param) {
// Sanitize description: remove newlines and excessive spaces
const cleanDescription = (param.description || '')
.replace(/\n/g, ' ')
.replace(/\s+/g, ' ')
.trim();
// If no description, just return the basic param tag
if (!cleanDescription) {
return `@param {${param.typeHint}} ${param.pyName}`;
}
// Add the description
return `@param {${param.typeHint}} ${param.pyName} ${cleanDescription}`;
}
/**
* Generate JSDoc for return value
* @param responses The OpenAPI responses object
* @returns JSDoc string for the return value
*/
static generateReturnDoc(responses) {
// Find a success response (2xx)
const successCodes = Object.keys(responses).filter(code => code.startsWith('2'));
if (successCodes.length === 0) {
return '@returns {Promise<any>}';
}
// Use the first success code
const responseCode = successCodes[0];
const response = responses[responseCode];
let description = response.description || 'Successful response';
description = description.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
return `@returns {Promise<any>} ${description}`;
}
}
exports.JsDocHelper = JsDocHelper;