/** * Represents a file attachment on a message (image, document, etc.). */ export interface Attachment { /** Original filename */ name: string; /** Attachment category */ type: 'image' | 'file'; /** MIME type (e.g., 'image/png') */ mimeType: string; /** Path relative to thread directory (e.g., 'attachments/abc123.png') when stored, * or absolute ephemeral path (e.g., $TMPDIR) when passed into append() */ storedPath: string; } /** * Represents a single message in the conversation history. */ export interface Message { /** Unique identifier for the message (nanoid) */ id: string; /** Unix timestamp in milliseconds when the message was recorded */ timestamp: number; /** Whether this message is from the user, assistant, or a session context document */ role: 'user' | 'assistant' | 'session'; /** The actual message content */ content: string; /** Approximate token count for context budgeting */ tokenCount?: number; /** Extensible metadata (tool calls, model info, etc.) */ metadata?: Record; /** Optional file attachments (images, documents) */ attachments?: Attachment[]; } /** * Statistics about the conversation history. */ export interface HistoryStats { /** Total number of messages */ count: number; /** Sum of all token counts */ totalTokens: number; /** Timestamp of the oldest message */ oldestTimestamp?: number; /** Timestamp of the newest message */ newestTimestamp?: number; } /** * Register a hook that will be called after every HistoryStore.append(). * The hook is fire-and-forget — it must handle its own errors. */ export declare function setOnAppendHook(hook: ((threadPath: string, message: Message) => void) | null): void; /** * HistoryStore manages conversation history persistence in JSONL format. * * Each conversation thread is stored as a separate JSONL file where each line * is a JSON-serialized Message object. This format is append-only, crash-safe, * and efficient for streaming reads. */ export declare class HistoryStore { readonly threadPath: string; /** In-memory cache of messages. null means not yet loaded. */ private cachedMessages; /** File size at the time of the last cache population. -1 means unknown. */ private cachedFileSize; /** Id index over cachedMessages — kept in lockstep so dedupe is O(1). */ private cachedIds; /** * Create a new HistoryStore instance. * @param threadPath - Absolute path to the JSONL file for this thread */ constructor(threadPath: string); /** * Replace the cache wholesale, keeping the id index in lockstep. * Every assignment to cachedMessages goes through here. */ private setCache; /** * Add messages to the cache, skipping any id already present. * * THE one rule that makes the cache safe under concurrency: a message id * enters cachedMessages at most once. Both writers race each other across * `await` boundaries — append() persists the line, yields, and only then * updates the cache, while getAll()'s incremental branch computes its read * offset, yields for open()/read(), and then merges. Whichever resumes * second would otherwise re-add a message the other already merged, which * surfaced as a thread reporting 3 messages after two appends (the same id * twice in the API response, while the file on disk held exactly 2 lines). * * @returns the messages actually added (i.e. not already cached) */ private addToCache; /** * Estimate token count from content using whitespace-based approximation. * Multiplies word count by 1.3 to account for subword tokenization. */ private estimateTokens; /** * Append a new message to the history. * Automatically generates id (nanoid) and timestamp. * Creates parent directories if they don't exist. * @param msg - Message without id and timestamp * @returns The complete message with generated fields */ append(msg: Omit): Promise; /** * Resolve relative attachment storedPaths to absolute paths. * Called before returning messages to clients so they can use paths directly. */ private resolveAttachmentPaths; /** * Load all messages from the history file. * Returns empty array if file doesn't exist. * Uses incremental read when cache is populated and file has grown. * @returns Array of all messages in chronological order */ getAll(): Promise; /** * Parse JSONL content into messages, keeping every line that can be read. * * A line that is not JSON is not a reason to lose the thread (task 187). The * shape seen on disk after a hard crash is a run of NUL bytes where a record's * data never reached the platter, and — because `O_APPEND` starts at the file's * committed length — the NEXT message written on the same line, intact. That * message is salvaged by dropping the NULs. Anything else unreadable is skipped, * and either case is reported once per read so the loss is in the journal. */ private parseLines; /** * Get messages by index range (0-based, inclusive start, exclusive end). * Follows JavaScript slice() semantics. * @param startIdx - Starting index (inclusive) * @param endIdx - Ending index (exclusive) * @returns Array of messages in the range */ getRange(startIdx: number, endIdx: number): Promise; /** * Get the N most recent messages. * @param n - Number of messages to retrieve * @returns Array of recent messages, oldest first */ getRecent(n: number): Promise; /** * Get statistics about the conversation history. * @returns Stats object with counts and timestamps */ getStats(): Promise; /** * Search messages by content (case-insensitive substring match). * @param query - Search string * @param limit - Maximum results to return (default: 10) * @returns Matching messages, most recent first */ search(query: string, limit?: number): Promise; /** * Truncate history to keep only messages up to and including the target message. * Uses atomic write (write to .tmp then rename) to prevent data loss. * @param messageId - ID of the last message to keep * @returns Array of removed message IDs * @throws If messageId is not found */ truncateTo(messageId: string): Promise; } //# sourceMappingURL=history.d.ts.map