UNPKG

@iqai/adk-cli

Version:

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

453 lines 20.1 kB
"use strict"; 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 __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionsService = void 0; const node_util_1 = require("node:util"); const adk_1 = require("@iqai/adk"); const common_1 = require("@nestjs/common"); const constants_1 = require("../../common/constants"); const tokens_1 = require("../../common/tokens"); const agent_manager_service_1 = require("../providers/agent-manager.service"); const hot_reload_service_1 = require("../reload/hot-reload.service"); let SessionsService = class SessionsService { agentManager; sessionService; quiet; hotReload; logger; constructor(agentManager, sessionService, quiet, hotReload) { this.agentManager = agentManager; this.sessionService = sessionService; this.quiet = quiet; this.hotReload = hotReload; this.logger = new common_1.Logger("sessions-service"); } // Centralized agent loader for reuse across modules async ensureAgentLoaded(agentPath) { if (!this.agentManager.getLoadedAgents().has(agentPath)) { try { await this.agentManager.startAgent(agentPath); } catch { return null; } } const loaded = this.agentManager.getLoadedAgents().get(agentPath); return loaded ?? null; } // ----- Public API used by controllers (compat with previous shape) ----- async listSessions(agentPath) { const loaded = await this.ensureAgentLoaded(agentPath); if (!loaded) { return { sessions: [] }; } return this.getAgentSessions(loaded); } async createSession(agentPath, request) { const loaded = await this.ensureAgentLoaded(agentPath); if (!loaded) { return { error: "Failed to load agent" }; } return this.createAgentSession(loaded, request); } async deleteSession(agentPath, sessionId) { const loaded = await this.ensureAgentLoaded(agentPath); if (!loaded) { return { error: "Failed to load agent" }; } await this.deleteAgentSession(loaded, sessionId); return { success: true }; } async switchSession(agentPath, sessionId) { const loaded = await this.ensureAgentLoaded(agentPath); if (!loaded) { return { error: "Failed to load agent" }; } await this.switchAgentSession(loaded, sessionId); return { success: true }; } // ----- Inlined former SessionManager functionality ----- async getSessionMessages(loadedAgent) { try { // Get session from session service const session = await this.sessionService.getSession(loadedAgent.appName, loadedAgent.userId, loadedAgent.sessionId); if (!session || !session.events) { return []; } // Convert session events to message format // See TODO notes in previous implementation regarding tool call representation. const messages = session.events.map((event, index) => ({ id: index + 1, type: event.author === "user" ? "user" : "assistant", content: event.content?.parts ?.map((part) => typeof part === "object" && part !== null && "text" in part ? part.text : "") .join("") || "", timestamp: new Date(event.timestamp || Date.now()).toISOString(), })); return messages; } catch (error) { this.logger.error("Error fetching messages:", error instanceof Error ? error.message : String(error)); return []; } } /** * Get all sessions for a loaded agent */ async getAgentSessions(loadedAgent) { try { this.logger.log((0, node_util_1.format)("Listing sessions for: %s %s", loadedAgent.appName, loadedAgent.userId)); const listResponse = await this.sessionService.listSessions(loadedAgent.appName, loadedAgent.userId); this.logger.log((0, node_util_1.format)("Raw sessions from service: %d", listResponse.sessions.length)); const sessions = []; for (const s of listResponse.sessions) { // Ensure we load the full session to get the latest event list let fullSession = s; try { const session = await this.sessionService.getSession(loadedAgent.appName, loadedAgent.userId, s.id); if (session) { fullSession = session; } } catch { // Keep fullSession as s } sessions.push({ id: s.id, appName: s.appName, userId: s.userId, state: s.state, eventCount: Array.isArray(fullSession?.events) ? fullSession.events.length : 0, lastUpdateTime: s.lastUpdateTime, createdAt: s.lastUpdateTime, }); } this.logger.log((0, node_util_1.format)("Processed sessions: %d", sessions.length)); return { sessions }; } catch (error) { this.logger.error("Error fetching sessions: %o", error); return { sessions: [] }; } } /** * Create a new session for a loaded agent */ async createAgentSession(loadedAgent, request) { try { // Extract initial state from the agent if no state provided in request let stateToUse = request?.state; if (!stateToUse) { // Get the agent path from userId (remove the prefix) const agentPath = loadedAgent.userId.startsWith(constants_1.USER_ID_PREFIX) ? loadedAgent.userId.substring(constants_1.USER_ID_PREFIX.length) : loadedAgent.userId; const initialState = this.agentManager.getInitialStateForAgent(agentPath); if (initialState) { this.logger.log((0, node_util_1.format)("Using initial state from agent for new session: %o", Object.keys(initialState))); stateToUse = initialState; } } this.logger.log((0, node_util_1.format)("Creating agent session: %o", { appName: loadedAgent.appName, userId: loadedAgent.userId, hasState: !!stateToUse, stateKeys: stateToUse ? Object.keys(stateToUse) : [], sessionId: request?.sessionId, })); const newSession = await this.sessionService.createSession(loadedAgent.appName, loadedAgent.userId, stateToUse, request?.sessionId); this.logger.log((0, node_util_1.format)("Session created with state: %o", { sessionId: newSession.id, hasState: !!newSession.state, stateKeys: newSession.state ? Object.keys(newSession.state) : [], stateContent: newSession.state, })); // Notify connected clients about initial state if (stateToUse && Object.keys(stateToUse).length > 0) { try { const agentPath = this.getAgentPathFromUserId(loadedAgent.userId); this.hotReload?.broadcastState(agentPath, newSession.id); } catch (error) { this.logger.warn(`Failed to broadcast initial state for session ${newSession.id}`, error); } } return { id: newSession.id, appName: newSession.appName, userId: newSession.userId, state: newSession.state, eventCount: newSession.events.length, lastUpdateTime: newSession.lastUpdateTime, createdAt: newSession.lastUpdateTime, }; } catch (error) { this.logger.error("Error creating session: %o", error); throw error; } } getAgentPathFromUserId(userId) { return userId.startsWith(constants_1.USER_ID_PREFIX) ? userId.substring(constants_1.USER_ID_PREFIX.length) : userId; } /** * Delete a session for a loaded agent */ async deleteAgentSession(loadedAgent, sessionId) { try { await this.sessionService.deleteSession(loadedAgent.appName, loadedAgent.userId, sessionId); } catch (error) { this.logger.error("Error deleting session: %o", error); throw error; } } /** * Get events for a specific session */ async getSessionEvents(loadedAgent, sessionId) { try { const session = await this.sessionService.getSession(loadedAgent.appName, loadedAgent.userId, sessionId); if (!session || !session.events) { return { events: [], totalCount: 0 }; } const events = session.events.map((event) => { // Handle both Event class instances and plain objects const eventLike = event; const isEventInstance = typeof eventLike.getFunctionCalls === "function"; const parts = eventLike.content?.parts; return { id: eventLike.id, author: eventLike.author, timestamp: eventLike.timestamp, content: eventLike.content, actions: eventLike.actions, functionCalls: isEventInstance && eventLike.getFunctionCalls ? eventLike.getFunctionCalls() : parts?.filter((part) => part && typeof part === "object" && part !== null && "functionCall" in part) || [], functionResponses: isEventInstance && eventLike.getFunctionResponses ? eventLike.getFunctionResponses() : parts?.filter((part) => part && typeof part === "object" && part !== null && "functionResponse" in part) || [], branch: eventLike.branch, isFinalResponse: isEventInstance && eventLike.isFinalResponse ? eventLike.isFinalResponse() : !parts?.some((part) => part && typeof part === "object" && part !== null && "functionCall" in part) && !parts?.some((part) => part && typeof part === "object" && part !== null && "functionResponse" in part) && !eventLike.partial && !(Array.isArray(parts) && parts.length > 0 && parts[parts.length - 1]?.codeExecutionResult != null), }; }); return { events, totalCount: events.length, }; } catch (error) { this.logger.error("Error fetching session events: %o", error); return { events: [], totalCount: 0 }; } } /** * Switch the loaded agent to use a different session */ async switchAgentSession(loadedAgent, sessionId) { try { // Verify the session exists const session = await this.sessionService.getSession(loadedAgent.appName, loadedAgent.userId, sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } // Update the loaded agent's session ID loadedAgent.sessionId = sessionId; } catch (error) { this.logger.error("Error switching session: %o", error); throw error; } } /** * Get state for specific session */ async getSessionState(loadedAgent, sessionId) { try { this.logger.log((0, node_util_1.format)("Getting session state: %s", sessionId)); const session = await this.sessionService.getSession(loadedAgent.appName, loadedAgent.userId, sessionId); if (!session) { throw new Error("Session not found"); } const agentState = {}; const userState = {}; const sessionState = session.state || {}; this.logger.log((0, node_util_1.format)("Session state retrieved: %o", { sessionId, hasSessionState: !!session.state, sessionStateKeys: Object.keys(sessionState), sessionStateContent: sessionState, sessionLastUpdateTime: session.lastUpdateTime, })); const allKeys = { ...agentState, ...userState, ...sessionState }; const totalKeys = Object.keys(allKeys).length; const sizeBytes = JSON.stringify(allKeys).length; const response = { agentState, userState, sessionState, metadata: { lastUpdated: session.lastUpdateTime, changeCount: 0, totalKeys, sizeBytes, }, }; this.logger.log((0, node_util_1.format)("Returning state response: %o", { hasAgentState: !!response.agentState && Object.keys(response.agentState).length > 0, hasUserState: !!response.userState && Object.keys(response.userState).length > 0, hasSessionState: !!response.sessionState && Object.keys(response.sessionState).length > 0, sessionStateKeys: Object.keys(response.sessionState), totalKeys: response.metadata.totalKeys, })); return response; } catch (error) { this.logger.error("Error getting session state: %o", error); return { agentState: {}, userState: {}, sessionState: {}, metadata: { lastUpdated: Date.now() / 1000, changeCount: 0, totalKeys: 0, sizeBytes: 0, }, }; } } /** * Update session state */ async updateSessionState(loadedAgent, sessionId, path, value) { try { this.logger.log((0, node_util_1.format)("Updating session state: %s %s = %o", sessionId, path, value)); const session = await this.sessionService.getSession(loadedAgent.appName, loadedAgent.userId, sessionId); if (!session) { throw new Error("Session not found"); } const updatedState = { ...session.state }; this.setNestedValue(updatedState, path, value); await this.sessionService.createSession(loadedAgent.appName, loadedAgent.userId, updatedState, sessionId); this.logger.log("Session state updated successfully"); // Notify connected clients (web UI) that state changed for this agent/session try { const agentPath = this.getAgentPathFromUserId(loadedAgent.userId); this.hotReload?.broadcastState(agentPath, sessionId); } catch { } } catch (error) { this.logger.error("Error updating session state: %o", error); throw error; } } /** * Helper method to set nested values using dot notation */ setNestedValue(obj, path, value) { const keys = path.split("."); if (keys.length === 0) return; const isNumericKey = (k) => /^\d+$/.test(k); const lastKey = keys.pop(); let current = obj; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const nextKey = keys[i + 1]; const nextIsIndex = nextKey != null && isNumericKey(nextKey); if (isNumericKey(key)) { // We are indexing into an array const idx = Number(key); if (!Array.isArray(current)) { // If current is not an array, replace it with an array in its parent slot // This can only happen if the path starts with a numeric key, which is invalid throw new Error(`Invalid path: cannot index into non-array at '${keys.slice(0, i + 1).join(".")}'`); } if (current[idx] == null || typeof current[idx] !== "object") { current[idx] = nextIsIndex ? [] : {}; } current = current[idx]; } else { // Property access on object if (current[key] == null || (typeof current[key] !== "object" && !Array.isArray(current[key]))) { current[key] = nextIsIndex ? [] : {}; } current = current[key]; } } // Set the final key if (isNumericKey(lastKey)) { const idx = Number(lastKey); if (!Array.isArray(current)) { // If final target isn't an array but a numeric key is provided, coerce it to array // Create an array and replace current (object) by mutating its reference // Since we don't have the parent reference here, fall back to setting a numeric property // to avoid silent data loss. current[idx] = value; return; } current[idx] = value; return; } // If the target is an array and lastKey is not numeric, this is likely unintended. if (Array.isArray(current)) { throw new Error(`Invalid path: non-numeric key '${lastKey}' used on array at '${keys.join(".") || "<root>"}'`); } current[lastKey] = value; } }; exports.SessionsService = SessionsService; exports.SessionsService = SessionsService = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_1.Inject)(agent_manager_service_1.AgentManager)), __param(1, (0, common_1.Inject)(adk_1.InMemorySessionService)), __param(2, (0, common_1.Inject)(tokens_1.TOKENS.QUIET)), __param(3, (0, common_1.Optional)()), __param(3, (0, common_1.Inject)(hot_reload_service_1.HotReloadService)), __metadata("design:paramtypes", [agent_manager_service_1.AgentManager, adk_1.InMemorySessionService, Boolean, hot_reload_service_1.HotReloadService]) ], SessionsService); //# sourceMappingURL=sessions.service.js.map