UNPKG

adk-typescript

Version:

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

304 lines (292 loc) 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.runCmd = runCmd; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const readline = __importStar(require("readline")); const index_1 = require("../index"); const AGENT_TS_TEMPLATE = `import { LlmAgent as Agent } from 'adk-typescript/agents'; import { LlmRegistry } from 'adk-typescript/models'; import { FunctionTool, ToolContext } from 'adk-typescript/tools'; // --- Tool Functions --- /** * Returns current weather information for a specified city * @param params Object containing city name * @param context Optional ToolContext * @returns Promise resolving to weather information or error */ async function getWeather( params: Record<string, any>, context?: ToolContext ): Promise<{ status: string; report?: string; error_message?: string }> { const city = params.city; console.log(\`--- Tool: getWeather called for city: \${city} ---\`); const cityNormalized = city.toLowerCase().trim(); const mockWeatherDb: Record<string, { status: string; report: string }> = { "newyork": {status: "success", report: "The weather in New York is sunny with a temperature of 25°C."}, "london": {status: "success", report: "It's cloudy in London with a temperature of 15°C."}, "tokyo": {status: "success", report: "Tokyo is experiencing light rain and a temperature of 18°C."}, }; if (mockWeatherDb[cityNormalized]) { return mockWeatherDb[cityNormalized]; } else { return {status: "error", error_message: \`Sorry, I don't have weather information for '\${city}'.\`}; } } /** * Gets the current local time and timezone. * @param params Empty object (no parameters needed) * @param context Optional ToolContext * @returns Promise resolving to time information */ async function getCurrentTime( params: Record<string, any>, context?: ToolContext ): Promise<{ currentTime: string; timezone: string; }> { console.log(\`--- Tool: getCurrentTime called ---\`); const now = new Date(); return { currentTime: now.toLocaleTimeString(), timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }; } // --- Tool Wrappers --- const getWeatherTool = new FunctionTool({ name: "getWeather", description: "Returns current weather information for a specified city", fn: getWeather, functionDeclaration: { name: "getWeather", description: "Returns current weather information for a specified city", parameters: { type: 'object', properties: { city: { type: 'string', description: 'The name of the city (e.g., "New York")'} }, required: ['city'] } } }); const getCurrentTimeTool = new FunctionTool({ name: "getCurrentTime", description: "Gets the current local time and timezone.", fn: getCurrentTime, functionDeclaration: { name: "getCurrentTime", description: "Gets the current local time and timezone.", parameters: { type: 'object', properties: {} } // No parameters } }); // --- Agent Definition --- // Use LlmRegistry to get a model instance const agentLlm = LlmRegistry.newLlm("{model_name}"); // Or another compatible model // Export the root agent for ADK tools to find export const rootAgent = new Agent({ name: "{agent_name}", // Unique agent name model: agentLlm, // LLM instance description: "Provides current weather and time information for cities.", instruction: "You are a helpful assistant. Use the 'getWeather' tool for weather queries " + "and the 'getCurrentTime' tool for time queries. Provide clear answers based on tool results. " + "If asked for weather AND time, use both tools.", tools: [getWeatherTool, getCurrentTimeTool], // List of available tools }); `; const PACKAGE_JSON_TEMPLATE = `{ "name": "{agent_name}", "version": "1.0.0", "description": "Agent created with ADK TypeScript", "main": "dist/agent.js", "scripts": { "build": "tsc", "start": "node dist/agent.js" }, "dependencies": { "adk-typescript": "^${index_1.VERSION}", "dotenv": "^16.3.1" }, "devDependencies": { "ts-node": "^10.9.1", "typescript": "^5.1.6" } } `; const TSCONFIG_JSON_TEMPLATE = `{ "compilerOptions": { "target": "ES2020", "module": "Node16", "outDir": "./dist", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node16", "resolveJsonModule": true, "declaration": true }, "include": ["**/*.ts"], "exclude": ["node_modules", "dist"] } `; const GOOGLE_API_MSG = `\nDon't have API Key? Create one in AI Studio: https://aistudio.google.com/apikey\n`; const GOOGLE_CLOUD_SETUP_MSG = `\nYou need an existing Google Cloud account and project, check out this link for details:\nhttps://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai\n`; const OTHER_MODEL_MSG = `\nPlease see below guide to configure other models:\nhttps://google.github.io/adk-docs/agents/models\n`; const SUCCESS_MSG = `\nAgent created in {agent_folder}:\n- .env\n- package.json\n- tsconfig.json\n- agent.ts\n- README.md\n Next steps: 1. cd {agent_folder} 2. npm install # Install dependencies 3. npx adk-ts run . # Run your agent in terminal (use '.' when inside the agent directory) 4. npx adk-ts web . # OR try the dev UI in browser `; function askQuestion(rl, question, defaultValue) { return new Promise(resolve => { rl.question(defaultValue ? `${question} (${defaultValue}): ` : `${question}: `, answer => { resolve(answer && answer.trim() ? answer.trim() : (defaultValue || '')); }); }); } async function promptStr(rl, promptPrefix, priorMsg, defaultValue) { if (priorMsg) { console.log(priorMsg); } // No need for a loop here as we will resolve only when we have a valid value const value = await askQuestion(rl, promptPrefix, defaultValue); return value && value.trim() ? value.trim() : (defaultValue || ''); } async function promptForGoogleCloud(rl, googleCloudProject) { googleCloudProject = googleCloudProject || process.env.GOOGLE_CLOUD_PROJECT || ''; return promptStr(rl, 'Enter Google Cloud project ID', undefined, googleCloudProject); } async function promptForGoogleCloudRegion(rl, googleCloudRegion) { googleCloudRegion = googleCloudRegion || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; return promptStr(rl, 'Enter Google Cloud region', undefined, googleCloudRegion); } async function promptForGoogleApiKey(rl, googleApiKey) { googleApiKey = googleApiKey || process.env.GOOGLE_API_KEY || ''; return promptStr(rl, 'Enter Google API key', GOOGLE_API_MSG, googleApiKey); } async function promptForModel(rl) { // Ask once and handle response const modelChoice = await askQuestion(rl, `Choose a model for the root agent:\n1. gemini-1.5-flash (recommended)\n2. gemini-2.0-flash\n3. Other models (fill later)\nChoose model`, '1'); if (modelChoice === '1') { return 'gemini-1.5-flash'; } else if (modelChoice === '2') { return 'gemini-2.0-flash'; } else if (modelChoice === '3') { console.log(OTHER_MODEL_MSG); return '<FILL_IN_MODEL>'; } // Default fallback return 'gemini-2.0-flash'; } async function promptToChooseBackend(rl, googleApiKey, googleCloudProject, googleCloudRegion) { // Ask once and handle response const backendChoice = await askQuestion(rl, '1. Google AI\n2. Vertex AI\nChoose a backend', '1'); if (backendChoice === '1') { googleApiKey = await promptForGoogleApiKey(rl, googleApiKey); return { googleApiKey }; } else if (backendChoice === '2') { console.log(GOOGLE_CLOUD_SETUP_MSG); googleCloudProject = await promptForGoogleCloud(rl, googleCloudProject); googleCloudRegion = await promptForGoogleCloudRegion(rl, googleCloudRegion); return { googleCloudProject, googleCloudRegion }; } // Default fallback to Google AI googleApiKey = await promptForGoogleApiKey(rl, googleApiKey); return { googleApiKey }; } /** * Sanitizes an agent name to use only alphanumeric characters and underscores * @param name The original agent name * @returns Sanitized name with non-alphanumeric characters replaced with underscores */ function sanitizeAgentName(name) { // Replace any non-alphanumeric character (except underscores) with underscores return name.replace(/[^a-zA-Z0-9_]/g, '_'); } async function generateFiles(agentFolder, opts) { await fs.promises.mkdir(agentFolder, { recursive: true }); const dotenvFilePath = path.join(agentFolder, '.env'); const packageJsonFilePath = path.join(agentFolder, 'package.json'); const tsconfigFilePath = path.join(agentFolder, 'tsconfig.json'); const agentFilePath = path.join(agentFolder, 'agent.ts'); const readmeFilePath = path.join(agentFolder, 'README.md'); const lines = []; if (opts.googleApiKey) { lines.push('GOOGLE_GENAI_USE_VERTEXAI=0'); } else if (opts.googleCloudProject && opts.googleCloudRegion) { lines.push('GOOGLE_GENAI_USE_VERTEXAI=1'); } if (opts.googleApiKey) lines.push(`GOOGLE_API_KEY=${opts.googleApiKey}`); if (opts.googleCloudProject) lines.push(`GOOGLE_CLOUD_PROJECT=${opts.googleCloudProject}`); if (opts.googleCloudRegion) lines.push(`GOOGLE_CLOUD_LOCATION=${opts.googleCloudRegion}`); await fs.promises.writeFile(dotenvFilePath, lines.join('\n'), 'utf-8'); await fs.promises.writeFile(packageJsonFilePath, PACKAGE_JSON_TEMPLATE.replace(/{agent_name}/g, opts.agentName), 'utf-8'); await fs.promises.writeFile(tsconfigFilePath, TSCONFIG_JSON_TEMPLATE, 'utf-8'); // Ensure both curly and non-curly braces templates are replaced for agent name const agentCode = AGENT_TS_TEMPLATE .replace(/{model_name}/g, opts.model || 'gemini-1.5-flash') .replace(/{agent_name}/g, opts.agentName) .replace(/"{agent_name}"/g, `"${opts.agentName}"`) .replace(/'{agent_name}'/g, `'${opts.agentName}'`); await fs.promises.writeFile(agentFilePath, agentCode, 'utf-8'); const successMessage = SUCCESS_MSG.replace(/{agent_folder}/g, agentFolder); console.log(successMessage); } async function runCmd({ agentName, model, googleApiKey, googleCloudProject, googleCloudRegion, }) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // Sanitize the agent name const sanitizedAgentName = sanitizeAgentName(agentName); if (sanitizedAgentName !== agentName) { console.log(`Agent name has been sanitized from "${agentName}" to "${sanitizedAgentName}" (only alphanumeric characters and underscores allowed)`); } if (!model) { model = await promptForModel(rl); } const backend = await promptToChooseBackend(rl, googleApiKey, googleCloudProject, googleCloudRegion); await generateFiles(sanitizedAgentName, { googleApiKey: backend.googleApiKey, googleCloudProject: backend.googleCloudProject, googleCloudRegion: backend.googleCloudRegion, model, agentName: sanitizedAgentName, }); rl.close(); }