208 lines
6.0 KiB
TypeScript
208 lines
6.0 KiB
TypeScript
/**
|
|
* Session Memory utility functions that can be imported without circular dependencies.
|
|
* These are separate from the main sessionMemory.ts to avoid importing runAgent.
|
|
*/
|
|
|
|
import { isFsInaccessible } from '../../utils/errors.js'
|
|
import { getFsImplementation } from '../../utils/fsOperations.js'
|
|
import { getSessionMemoryPath } from '../../utils/permissions/filesystem.js'
|
|
import { sleep } from '../../utils/sleep.js'
|
|
import { logEvent } from '../analytics/index.js'
|
|
|
|
const EXTRACTION_WAIT_TIMEOUT_MS = 15000
|
|
const EXTRACTION_STALE_THRESHOLD_MS = 60000 // 1 minute
|
|
|
|
/**
|
|
* Configuration for session memory extraction thresholds
|
|
*/
|
|
export type SessionMemoryConfig = {
|
|
/** Minimum context window tokens before initializing session memory.
|
|
* Uses the same token counting as autocompact (input + output + cache tokens)
|
|
* to ensure consistent behavior between the two features. */
|
|
minimumMessageTokensToInit: number
|
|
/** Minimum context window growth (in tokens) between session memory updates.
|
|
* Uses the same token counting as autocompact (tokenCountWithEstimation)
|
|
* to measure actual context growth, not cumulative API usage. */
|
|
minimumTokensBetweenUpdate: number
|
|
/** Number of tool calls between session memory updates */
|
|
toolCallsBetweenUpdates: number
|
|
}
|
|
|
|
// Default configuration values
|
|
export const DEFAULT_SESSION_MEMORY_CONFIG: SessionMemoryConfig = {
|
|
minimumMessageTokensToInit: 10000,
|
|
minimumTokensBetweenUpdate: 5000,
|
|
toolCallsBetweenUpdates: 3,
|
|
}
|
|
|
|
// Current session memory configuration
|
|
let sessionMemoryConfig: SessionMemoryConfig = {
|
|
...DEFAULT_SESSION_MEMORY_CONFIG,
|
|
}
|
|
|
|
// Track the last summarized message ID (shared state)
|
|
let lastSummarizedMessageId: string | undefined
|
|
|
|
// Track extraction state with timestamp (set by sessionMemory.ts)
|
|
let extractionStartedAt: number | undefined
|
|
|
|
// Track context size at last memory extraction (for minimumTokensBetweenUpdate)
|
|
let tokensAtLastExtraction = 0
|
|
|
|
// Track whether session memory has been initialized (met minimumMessageTokensToInit)
|
|
let sessionMemoryInitialized = false
|
|
|
|
/**
|
|
* Get the message ID up to which the session memory is current
|
|
*/
|
|
export function getLastSummarizedMessageId(): string | undefined {
|
|
return lastSummarizedMessageId
|
|
}
|
|
|
|
/**
|
|
* Set the last summarized message ID (called from sessionMemory.ts)
|
|
*/
|
|
export function setLastSummarizedMessageId(
|
|
messageId: string | undefined,
|
|
): void {
|
|
lastSummarizedMessageId = messageId
|
|
}
|
|
|
|
/**
|
|
* Mark extraction as started (called from sessionMemory.ts)
|
|
*/
|
|
export function markExtractionStarted(): void {
|
|
extractionStartedAt = Date.now()
|
|
}
|
|
|
|
/**
|
|
* Mark extraction as completed (called from sessionMemory.ts)
|
|
*/
|
|
export function markExtractionCompleted(): void {
|
|
extractionStartedAt = undefined
|
|
}
|
|
|
|
/**
|
|
* Wait for any in-progress session memory extraction to complete (with 15s timeout)
|
|
* Returns immediately if no extraction is in progress or if extraction is stale (>1min old).
|
|
*/
|
|
export async function waitForSessionMemoryExtraction(): Promise<void> {
|
|
const startTime = Date.now()
|
|
while (extractionStartedAt) {
|
|
const extractionAge = Date.now() - extractionStartedAt
|
|
if (extractionAge > EXTRACTION_STALE_THRESHOLD_MS) {
|
|
// Extraction is stale, don't wait
|
|
return
|
|
}
|
|
|
|
if (Date.now() - startTime > EXTRACTION_WAIT_TIMEOUT_MS) {
|
|
// Timeout - continue anyway
|
|
return
|
|
}
|
|
|
|
await sleep(1000)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the current session memory content
|
|
*/
|
|
export async function getSessionMemoryContent(): Promise<string | null> {
|
|
const fs = getFsImplementation()
|
|
const memoryPath = getSessionMemoryPath()
|
|
|
|
try {
|
|
const content = await fs.readFile(memoryPath, { encoding: 'utf-8' })
|
|
|
|
logEvent('tengu_session_memory_loaded', {
|
|
content_length: content.length,
|
|
})
|
|
|
|
return content
|
|
} catch (e: unknown) {
|
|
if (isFsInaccessible(e)) return null
|
|
throw e
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the session memory configuration
|
|
*/
|
|
export function setSessionMemoryConfig(
|
|
config: Partial<SessionMemoryConfig>,
|
|
): void {
|
|
sessionMemoryConfig = {
|
|
...sessionMemoryConfig,
|
|
...config,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the current session memory configuration
|
|
*/
|
|
export function getSessionMemoryConfig(): SessionMemoryConfig {
|
|
return { ...sessionMemoryConfig }
|
|
}
|
|
|
|
/**
|
|
* Record the context size at the time of extraction.
|
|
* Used to measure context growth for minimumTokensBetweenUpdate threshold.
|
|
*/
|
|
export function recordExtractionTokenCount(currentTokenCount: number): void {
|
|
tokensAtLastExtraction = currentTokenCount
|
|
}
|
|
|
|
/**
|
|
* Check if session memory has been initialized (met minimumTokensToInit threshold)
|
|
*/
|
|
export function isSessionMemoryInitialized(): boolean {
|
|
return sessionMemoryInitialized
|
|
}
|
|
|
|
/**
|
|
* Mark session memory as initialized
|
|
*/
|
|
export function markSessionMemoryInitialized(): void {
|
|
sessionMemoryInitialized = true
|
|
}
|
|
|
|
/**
|
|
* Check if we've met the threshold to initialize session memory.
|
|
* Uses total context window tokens (same as autocompact) for consistent behavior.
|
|
*/
|
|
export function hasMetInitializationThreshold(
|
|
currentTokenCount: number,
|
|
): boolean {
|
|
return currentTokenCount >= sessionMemoryConfig.minimumMessageTokensToInit
|
|
}
|
|
|
|
/**
|
|
* Check if we've met the threshold for the next update.
|
|
* Measures actual context window growth since last extraction
|
|
* (same metric as autocompact and initialization threshold).
|
|
*/
|
|
export function hasMetUpdateThreshold(currentTokenCount: number): boolean {
|
|
const tokensSinceLastExtraction = currentTokenCount - tokensAtLastExtraction
|
|
return (
|
|
tokensSinceLastExtraction >= sessionMemoryConfig.minimumTokensBetweenUpdate
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Get the configured number of tool calls between updates
|
|
*/
|
|
export function getToolCallsBetweenUpdates(): number {
|
|
return sessionMemoryConfig.toolCallsBetweenUpdates
|
|
}
|
|
|
|
/**
|
|
* Reset session memory state (useful for testing)
|
|
*/
|
|
export function resetSessionMemoryState(): void {
|
|
sessionMemoryConfig = { ...DEFAULT_SESSION_MEMORY_CONFIG }
|
|
tokensAtLastExtraction = 0
|
|
sessionMemoryInitialized = false
|
|
lastSummarizedMessageId = undefined
|
|
extractionStartedAt = undefined
|
|
}
|