sessionMemoryUtils.ts
1 /** 2 * Session Memory utility functions that can be imported without circular dependencies. 3 * These are separate from the main sessionMemory.ts to avoid importing runAgent. 4 */ 5 6 import { isFsInaccessible } from '../../utils/errors.js' 7 import { getFsImplementation } from '../../utils/fsOperations.js' 8 import { getSessionMemoryPath } from '../../utils/permissions/filesystem.js' 9 import { sleep } from '../../utils/sleep.js' 10 import { logEvent } from '../analytics/index.js' 11 12 const EXTRACTION_WAIT_TIMEOUT_MS = 15000 13 const EXTRACTION_STALE_THRESHOLD_MS = 60000 // 1 minute 14 15 /** 16 * Configuration for session memory extraction thresholds 17 */ 18 export type SessionMemoryConfig = { 19 /** Minimum context window tokens before initializing session memory. 20 * Uses the same token counting as autocompact (input + output + cache tokens) 21 * to ensure consistent behavior between the two features. */ 22 minimumMessageTokensToInit: number 23 /** Minimum context window growth (in tokens) between session memory updates. 24 * Uses the same token counting as autocompact (tokenCountWithEstimation) 25 * to measure actual context growth, not cumulative API usage. */ 26 minimumTokensBetweenUpdate: number 27 /** Number of tool calls between session memory updates */ 28 toolCallsBetweenUpdates: number 29 } 30 31 // Default configuration values 32 export const DEFAULT_SESSION_MEMORY_CONFIG: SessionMemoryConfig = { 33 minimumMessageTokensToInit: 10000, 34 minimumTokensBetweenUpdate: 5000, 35 toolCallsBetweenUpdates: 3, 36 } 37 38 // Current session memory configuration 39 let sessionMemoryConfig: SessionMemoryConfig = { 40 ...DEFAULT_SESSION_MEMORY_CONFIG, 41 } 42 43 // Track the last summarized message ID (shared state) 44 let lastSummarizedMessageId: string | undefined 45 46 // Track extraction state with timestamp (set by sessionMemory.ts) 47 let extractionStartedAt: number | undefined 48 49 // Track context size at last memory extraction (for minimumTokensBetweenUpdate) 50 let tokensAtLastExtraction = 0 51 52 // Track whether session memory has been initialized (met minimumMessageTokensToInit) 53 let sessionMemoryInitialized = false 54 55 /** 56 * Get the message ID up to which the session memory is current 57 */ 58 export function getLastSummarizedMessageId(): string | undefined { 59 return lastSummarizedMessageId 60 } 61 62 /** 63 * Set the last summarized message ID (called from sessionMemory.ts) 64 */ 65 export function setLastSummarizedMessageId( 66 messageId: string | undefined, 67 ): void { 68 lastSummarizedMessageId = messageId 69 } 70 71 /** 72 * Mark extraction as started (called from sessionMemory.ts) 73 */ 74 export function markExtractionStarted(): void { 75 extractionStartedAt = Date.now() 76 } 77 78 /** 79 * Mark extraction as completed (called from sessionMemory.ts) 80 */ 81 export function markExtractionCompleted(): void { 82 extractionStartedAt = undefined 83 } 84 85 /** 86 * Wait for any in-progress session memory extraction to complete (with 15s timeout) 87 * Returns immediately if no extraction is in progress or if extraction is stale (>1min old). 88 */ 89 export async function waitForSessionMemoryExtraction(): Promise<void> { 90 const startTime = Date.now() 91 while (extractionStartedAt) { 92 const extractionAge = Date.now() - extractionStartedAt 93 if (extractionAge > EXTRACTION_STALE_THRESHOLD_MS) { 94 // Extraction is stale, don't wait 95 return 96 } 97 98 if (Date.now() - startTime > EXTRACTION_WAIT_TIMEOUT_MS) { 99 // Timeout - continue anyway 100 return 101 } 102 103 await sleep(1000) 104 } 105 } 106 107 /** 108 * Get the current session memory content 109 */ 110 export async function getSessionMemoryContent(): Promise<string | null> { 111 const fs = getFsImplementation() 112 const memoryPath = getSessionMemoryPath() 113 114 try { 115 const content = await fs.readFile(memoryPath, { encoding: 'utf-8' }) 116 117 logEvent('tengu_session_memory_loaded', { 118 content_length: content.length, 119 }) 120 121 return content 122 } catch (e: unknown) { 123 if (isFsInaccessible(e)) return null 124 throw e 125 } 126 } 127 128 /** 129 * Set the session memory configuration 130 */ 131 export function setSessionMemoryConfig( 132 config: Partial<SessionMemoryConfig>, 133 ): void { 134 sessionMemoryConfig = { 135 ...sessionMemoryConfig, 136 ...config, 137 } 138 } 139 140 /** 141 * Get the current session memory configuration 142 */ 143 export function getSessionMemoryConfig(): SessionMemoryConfig { 144 return { ...sessionMemoryConfig } 145 } 146 147 /** 148 * Record the context size at the time of extraction. 149 * Used to measure context growth for minimumTokensBetweenUpdate threshold. 150 */ 151 export function recordExtractionTokenCount(currentTokenCount: number): void { 152 tokensAtLastExtraction = currentTokenCount 153 } 154 155 /** 156 * Check if session memory has been initialized (met minimumTokensToInit threshold) 157 */ 158 export function isSessionMemoryInitialized(): boolean { 159 return sessionMemoryInitialized 160 } 161 162 /** 163 * Mark session memory as initialized 164 */ 165 export function markSessionMemoryInitialized(): void { 166 sessionMemoryInitialized = true 167 } 168 169 /** 170 * Check if we've met the threshold to initialize session memory. 171 * Uses total context window tokens (same as autocompact) for consistent behavior. 172 */ 173 export function hasMetInitializationThreshold( 174 currentTokenCount: number, 175 ): boolean { 176 return currentTokenCount >= sessionMemoryConfig.minimumMessageTokensToInit 177 } 178 179 /** 180 * Check if we've met the threshold for the next update. 181 * Measures actual context window growth since last extraction 182 * (same metric as autocompact and initialization threshold). 183 */ 184 export function hasMetUpdateThreshold(currentTokenCount: number): boolean { 185 const tokensSinceLastExtraction = currentTokenCount - tokensAtLastExtraction 186 return ( 187 tokensSinceLastExtraction >= sessionMemoryConfig.minimumTokensBetweenUpdate 188 ) 189 } 190 191 /** 192 * Get the configured number of tool calls between updates 193 */ 194 export function getToolCallsBetweenUpdates(): number { 195 return sessionMemoryConfig.toolCallsBetweenUpdates 196 } 197 198 /** 199 * Reset session memory state (useful for testing) 200 */ 201 export function resetSessionMemoryState(): void { 202 sessionMemoryConfig = { ...DEFAULT_SESSION_MEMORY_CONFIG } 203 tokensAtLastExtraction = 0 204 sessionMemoryInitialized = false 205 lastSummarizedMessageId = undefined 206 extractionStartedAt = undefined 207 }