UNPKG

@iqai/adk-cli

Version:

CLI tool for creating, running, and testing ADK-TS agents

286 lines 13.6 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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; 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; }; })(); var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentManager = void 0; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const node_url_1 = require("node:url"); const node_util_1 = require("node:util"); const adk_1 = require("@iqai/adk"); const common_1 = require("@nestjs/common"); const agent_loader_service_1 = require("./agent-loader.service"); const sessions_1 = require("./agent-manager/sessions"); const state_1 = require("./agent-manager/state"); const agent_scanner_service_1 = require("./agent-scanner.service"); const _DEFAULT_APP_NAME = "adk-server"; const _USER_ID_PREFIX = "user_"; let AgentManager = class AgentManager { sessionService; agents = new Map(); loadedAgents = new Map(); builtAgents = new Map(); initialStateHashes = new Map(); scanner; loader; logger; constructor(sessionService, quiet = false) { this.sessionService = sessionService; this.scanner = new agent_scanner_service_1.AgentScanner(quiet); this.loader = new agent_loader_service_1.AgentLoader(quiet); this.logger = new common_1.Logger("agent-manager"); } getAgents() { return this.agents; } getLoadedAgents() { return this.loadedAgents; } scanAgents(agentsDir) { this.logger.log((0, node_util_1.format)("Scanning agents in directory: %s", agentsDir)); this.agents = this.scanner.scanAgents(agentsDir, this.loadedAgents); this.logger.log((0, node_util_1.format)("Found agents: %o", Array.from(this.agents.keys()))); } /** * Start an agent, optionally restoring a previous session. * @param agentPath - The path to the agent * @param preservedSessionId - Optional session ID to restore (used during hot reload) * @param forceFullReload - Force cache invalidation and session reset (used when initial state changes) */ async startAgent(agentPath, preservedSessionId, forceFullReload) { this.logger.log((0, node_util_1.format)("Starting agent: %s%s", agentPath, preservedSessionId ? ` (restoring session ${preservedSessionId})` : "")); const agent = this.validateAndGetAgent(agentPath); if (this.loadedAgents.has(agentPath)) { return; // Already running } try { const agentResult = await this.loadAgentModule(agent, forceFullReload); // Check if initial state has changed const initialState = this.extractInitialState(agentResult); const stateHash = (0, sessions_1.hashState)(initialState); const previousStateHash = this.initialStateHashes.get(agentPath); const stateChanged = previousStateHash && previousStateHash !== stateHash; let sessionIdToUse = preservedSessionId; if (stateChanged) { this.logger.log((0, node_util_1.format)("Initial state changed for %s - forcing full reload (old: %s, new: %s)", agentPath, previousStateHash, stateHash)); // Clear existing sessions when initial state changes await (0, sessions_1.clearAgentSessions)(this.sessionService, agentPath, this.logger); sessionIdToUse = undefined; // Don't preserve session if state changed } // Store the new state hash this.initialStateHashes.set(agentPath, stateHash); const sessionToUse = sessionIdToUse && !stateChanged ? await (0, sessions_1.getExistingSession)(this.sessionService, agentPath, sessionIdToUse, this.logger) : await (0, sessions_1.getOrCreateSession)(this.sessionService, agentPath, agentResult, (r) => (0, state_1.extractInitialState)(r, this.logger), this.logger); const runner = await (0, sessions_1.createRunnerWithSession)(this.sessionService, agentResult.agent, sessionToUse, agentPath); await (0, sessions_1.storeLoadedAgent)(this.sessionService, agentPath, agentResult, runner, sessionToUse, agent, (path, payload) => this.loadedAgents.set(path, payload), this.logger, (path, built) => this.builtAgents.set(path, built)); return { session: sessionIdToUse }; } catch (error) { const agentName = agent?.name ?? agentPath; const message = error instanceof Error ? error.message : String(error); // log without stack this.logger.error(`Failed to load agent "${agentName}": ${message}`); // rethrow without stack const cleanError = new Error(`Failed to load agent: ${message}`); cleanError.stack = undefined; throw cleanError; } } validateAndGetAgent(agentPath) { const agent = this.agents.get(agentPath); if (!agent) { this.logger.error("Agent not found in agents map: %s", agentPath); this.logger.debug((0, node_util_1.format)("Available agents: %o", Array.from(this.agents.keys()))); throw new Error(`Agent not found: ${agentPath}`); } this.logger.log("Agent found, proceeding to load..."); return agent; } async loadAgentModule(agent, forceInvalidateCache) { // Try both .js and .ts files, prioritizing .js if it exists // Normalize paths for cross-platform compatibility let agentFilePath = (0, node_path_1.normalize)((0, node_path_1.join)(agent.absolutePath, "agent.js")); if (!(0, node_fs_1.existsSync)(agentFilePath)) { agentFilePath = (0, node_path_1.normalize)((0, node_path_1.join)(agent.absolutePath, "agent.ts")); } if (!(0, node_fs_1.existsSync)(agentFilePath)) { throw new Error(`No agent.js or agent.ts file found in ${agent.absolutePath}`); } this.loader.loadEnvironmentVariables(agentFilePath); const agentFileUrl = (0, node_url_1.pathToFileURL)(agentFilePath).href; // Use dynamic import to load the agent // For TS files, pass the project root to avoid redundant project root discovery const agentModule = agentFilePath.endsWith(".ts") ? await this.loader.importTypeScriptFile(agentFilePath, agent.projectRoot, forceInvalidateCache) : (await Promise.resolve(`${agentFileUrl}`).then(s => __importStar(require(s)))); const agentResult = await this.loader.resolveAgentExport(agentModule); // Validate basic shape if (!agentResult?.agent?.name) { throw new Error(`Invalid agent export in ${agentFilePath}. Expected a BaseAgent instance with a name property.`); } // Return the full result (agent + builtAgent if available) return agentResult; } async stopAgent(agentPath) { this.loadedAgents.delete(agentPath); const agent = this.agents.get(agentPath); if (agent) { agent.instance = undefined; } } async sendMessageToAgent(agentPath, message, attachments) { if (!this.loadedAgents.has(agentPath)) { await this.startAgent(agentPath); } const loadedAgent = this.loadedAgents.get(agentPath); if (!loadedAgent) { throw new Error("Agent failed to start"); } try { const fullMessage = { parts: [ { text: message }, ...(attachments || []).map((file) => ({ inlineData: { mimeType: file.mimeType, data: file.data }, })), ], }; let accumulated = ""; for await (const event of loadedAgent.runner.runAsync({ userId: loadedAgent.userId, sessionId: loadedAgent.sessionId, newMessage: fullMessage, })) { const parts = event?.content?.parts; if (Array.isArray(parts)) { accumulated += parts .map((p) => p && typeof p === "object" && "text" in p ? p.text : "") .join(""); } } return accumulated.trim(); } catch (error) { const msg = error instanceof Error ? error.message : String(error); this.logger.error(`Error sending message to agent ${agentPath}: ${msg}`); throw new Error(`Failed to send message to agent: ${msg}`); } } /** * Get initial state for an agent path * Public method that can be called by other services */ getInitialStateForAgent(agentPath) { const agent = this.agents.get(agentPath); if (!agent) { return undefined; } if (!agent.instance) { return undefined; } // Use the builtAgent from the separate map if available const builtAgent = this.builtAgents.get(agentPath); const agentResult = { agent: agent.instance, builtAgent: builtAgent, }; return this.extractInitialState(agentResult); } /** * Extract initial state from an agent result */ extractInitialState(agentResult) { return (0, state_1.extractInitialState)(agentResult, this.logger); } /** * Get session info for all loaded agents before stopping * Used for preserving sessions during hot reload */ getLoadedAgentSessions() { const sessions = new Map(); for (const [agentPath, loadedAgent] of this.loadedAgents.entries()) { sessions.set(agentPath, loadedAgent.sessionId); } return sessions; } /** * Check if initial state has changed for any loaded agent * Returns true if any agent's initial state hash has changed */ async hasInitialStateChanged() { for (const [agentPath, agent] of this.agents.entries()) { if (!this.loadedAgents.has(agentPath)) { continue; // Skip agents that aren't loaded } try { // Temporarily load the agent to check its state const agentResult = await this.loadAgentModule(agent, false); const initialState = this.extractInitialState(agentResult); const stateHash = (0, sessions_1.hashState)(initialState); const previousStateHash = this.initialStateHashes.get(agentPath); if (previousStateHash && previousStateHash !== stateHash) { this.logger.log((0, node_util_1.format)("Detected initial state change for %s (old: %s, new: %s)", agentPath, previousStateHash, stateHash)); return true; } } catch (error) { // If we can't load the agent, assume state might have changed this.logger.warn((0, node_util_1.format)("Failed to check state for %s: %s", agentPath, error instanceof Error ? error.message : String(error))); return true; // Be safe and reload } } return false; } stopAllAgents() { for (const [agentPath] of Array.from(this.loadedAgents.entries())) { this.stopAgent(agentPath); } } }; exports.AgentManager = AgentManager; exports.AgentManager = AgentManager = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [adk_1.InMemorySessionService, Object]) ], AgentManager); //# sourceMappingURL=agent-manager.service.js.map