Files
letta-code/src/agent/sessionHistory.ts
2026-02-26 18:00:48 -08:00

177 lines
4.5 KiB
TypeScript

import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { SessionStatsSnapshot } from "./stats";
export interface SessionHistoryEntry {
agent_id: string;
session_id: string;
timestamp: number;
project: string;
model: string;
provider: string;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
cached_input_tokens: number;
cache_write_tokens: number;
reasoning_tokens: number;
context_tokens?: number;
steps: number;
};
duration: {
api_ms: number;
wall_ms: number;
};
cost: {
type: "hosted" | "byok";
credits_used?: number;
usd_byok?: number;
};
message_count?: number;
tool_call_count?: number;
exit_reason?: string;
}
interface SessionStartData {
agentId: string;
sessionId: string;
project: string;
model: string;
provider: string;
}
/**
* Get the Letta Code history directory
*/
function getHistoryDir(): string {
const homeDir = os.homedir();
return path.join(homeDir, ".letta-code");
}
/**
* Get the session history file path
*/
function getHistoryFilePath(): string {
return path.join(getHistoryDir(), "sessions.jsonl");
}
/**
* Ensure the history directory and file exist
*/
function ensureHistoryFile(): void {
const dir = getHistoryDir();
const filePath = getHistoryFilePath();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, "");
}
}
/**
* Record session start data (called when a session begins)
*/
export function recordSessionStart(data: SessionStartData): void {
ensureHistoryFile();
const entry: SessionHistoryEntry = {
agent_id: data.agentId,
session_id: data.sessionId,
timestamp: Date.now(),
project: data.project,
model: data.model,
provider: data.provider,
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
cached_input_tokens: 0,
cache_write_tokens: 0,
reasoning_tokens: 0,
steps: 0,
},
duration: {
api_ms: 0,
wall_ms: 0,
},
cost: {
type: "hosted", // Default, will be updated on session end
},
};
const filePath = getHistoryFilePath();
fs.appendFileSync(filePath, `${JSON.stringify(entry)}\n`);
}
/**
* Update session entry with final stats (called when session ends)
* For now, this just updates an in-memory tracking - actual file updates
* would require rewriting the file or using a different storage approach
*/
export function recordSessionEnd(
agentId: string,
sessionId: string,
stats: SessionStatsSnapshot,
sessionInfo?: { project?: string; model?: string; provider?: string },
cost?: { credits_used?: number; usd_byok?: number; type: "hosted" | "byok" },
metadata?: {
messageCount?: number;
toolCallCount?: number;
exitReason?: string;
},
): void {
// For now, we'll append a new "end" entry with the final stats
// A more sophisticated approach would update the existing entry
const entry: SessionHistoryEntry = {
agent_id: agentId,
session_id: sessionId,
timestamp: Date.now(),
project: sessionInfo?.project ?? "",
model: sessionInfo?.model ?? "",
provider: sessionInfo?.provider ?? "",
usage: {
prompt_tokens: stats.usage.promptTokens,
completion_tokens: stats.usage.completionTokens,
total_tokens: stats.usage.totalTokens,
cached_input_tokens: stats.usage.cachedInputTokens,
cache_write_tokens: stats.usage.cacheWriteTokens,
reasoning_tokens: stats.usage.reasoningTokens,
context_tokens: stats.usage.contextTokens,
steps: stats.usage.stepCount,
},
duration: {
api_ms: stats.totalApiMs,
wall_ms: stats.totalWallMs,
},
cost: cost || { type: "hosted" },
message_count: metadata?.messageCount,
tool_call_count: metadata?.toolCallCount,
exit_reason: metadata?.exitReason,
};
// For v1, we just append end entries - in future, we could update the start entry
const filePath = getHistoryFilePath();
fs.appendFileSync(filePath, `${JSON.stringify(entry)}\n`);
}
/**
* Read all session history entries
*/
export function getSessionHistory(): SessionHistoryEntry[] {
const filePath = getHistoryFilePath();
if (!fs.existsSync(filePath)) {
return [];
}
const content = fs.readFileSync(filePath, "utf-8");
const lines = content.split("\n").filter((line) => line.trim());
return lines.map((line) => JSON.parse(line) as SessionHistoryEntry);
}