@iqai/adk-cli
Version:
CLI tool for creating, running, and testing ADK-TS agents
286 lines • 13.6 kB
JavaScript
"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