UNPKG

@mariozechner/pi-agent

Version:

General-purpose agent with tool calling and session persistence

146 lines 4.91 kB
import { randomBytes } from "crypto"; import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "fs"; import { homedir } from "os"; import { join, resolve } from "path"; // Simple UUID v4 generator function uuidv4() { const bytes = randomBytes(16); bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4 bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10 const hex = bytes.toString("hex"); return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`; } export class SessionManager { sessionId; sessionFile; sessionDir; constructor(continueSession = false) { this.sessionDir = this.getSessionDirectory(); if (continueSession) { const mostRecent = this.findMostRecentlyModifiedSession(); if (mostRecent) { this.sessionFile = mostRecent; // Load session ID from file this.loadSessionId(); } else { // No existing session, create new this.initNewSession(); } } else { this.initNewSession(); } } getSessionDirectory() { const cwd = process.cwd(); const safePath = "--" + cwd.replace(/^\//, "").replace(/\//g, "-") + "--"; const piConfigDir = resolve(process.env.PI_CONFIG_DIR || join(homedir(), ".pi")); const sessionDir = join(piConfigDir, "sessions", safePath); if (!existsSync(sessionDir)) { mkdirSync(sessionDir, { recursive: true }); } return sessionDir; } initNewSession() { this.sessionId = uuidv4(); const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); this.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`); } findMostRecentlyModifiedSession() { try { const files = readdirSync(this.sessionDir) .filter((f) => f.endsWith(".jsonl")) .map((f) => ({ name: f, path: join(this.sessionDir, f), mtime: statSync(join(this.sessionDir, f)).mtime, })) .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); return files[0]?.path || null; } catch { return null; } } loadSessionId() { if (!existsSync(this.sessionFile)) return; const lines = readFileSync(this.sessionFile, "utf8").trim().split("\n"); for (const line of lines) { try { const entry = JSON.parse(line); if (entry.type === "session") { this.sessionId = entry.id; return; } } catch { // Skip malformed lines } } // If no session entry found, create new ID this.sessionId = uuidv4(); } startSession(config) { const entry = { type: "session", id: this.sessionId, timestamp: new Date().toISOString(), cwd: process.cwd(), config, }; appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); } async on(event) { const entry = { type: "event", timestamp: new Date().toISOString(), event: event, }; appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); } getSessionData() { if (!existsSync(this.sessionFile)) return null; let config = null; const events = []; let totalUsage = { type: "token_usage", inputTokens: 0, outputTokens: 0, totalTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, reasoningTokens: 0, }; const lines = readFileSync(this.sessionFile, "utf8").trim().split("\n"); for (const line of lines) { try { const entry = JSON.parse(line); if (entry.type === "session") { config = entry.config; this.sessionId = entry.id; } else if (entry.type === "event") { const eventEntry = entry; events.push(eventEntry); if (eventEntry.event.type === "token_usage") { totalUsage = entry.event; } } } catch { // Skip malformed lines } } return config ? { config, events, totalUsage } : null; } getSessionId() { return this.sessionId; } getSessionFile() { return this.sessionFile; } } //# sourceMappingURL=session-manager.js.map