Path: blob/main/src/vs/platform/agentHost/common/sessionDataService.ts
13394 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IDisposable, IReference } from '../../../base/common/lifecycle.js';6import { URI } from '../../../base/common/uri.js';7import { createDecorator } from '../../instantiation/common/instantiation.js';8import type { FileEditKind } from './state/sessionState.js';910export const ISessionDataService = createDecorator<ISessionDataService>('sessionDataService');1112/** Filename of the per-session SQLite database. */13export const SESSION_DB_FILENAME = 'session.db';1415// ---- File-edit types ----------------------------------------------------1617/**18* Lightweight metadata for a file edit. Returned by {@link ISessionDatabase.getFileEdits}19* without the (potentially large) file content blobs.20*/21export interface IFileEditRecord {22/** The turn that owns this file edit. */23turnId: string;24/** The tool call that produced this edit. */25toolCallId: string;26/** Primary file path (after-path for edits/creates/renames, before-path for deletes). */27filePath: string;28/** The kind of file operation. */29kind: FileEditKind;30/** For renames, the original file path before the move. */31originalPath?: string;32/** Number of lines added (informational, for diff metadata). */33addedLines: number | undefined;34/** Number of lines removed (informational, for diff metadata). */35removedLines: number | undefined;36}3738/**39* The before/after content blobs for a single file edit.40* Retrieved on demand via {@link ISessionDatabase.readFileEditContent}.41*42* For creates, `beforeContent` is absent.43* For deletes, `afterContent` is absent.44*/45export interface IFileEditContent {46/** File content before the edit. Absent for file creations. */47beforeContent?: Uint8Array;48/** File content after the edit. Absent for file deletions. */49afterContent?: Uint8Array;50}5152// ---- Session database ---------------------------------------------------5354/**55* A disposable handle to a per-session SQLite database backed by56* `@vscode/sqlite3`.57*58* Callers obtain an instance via {@link ISessionDataService.openDatabase} and59* **must** dispose it when finished to close the underlying database connection.60*/61export interface ISessionDatabase extends IDisposable {62/**63* Create a turn record. Must be called before storing file edits that64* reference this turn.65*/66createTurn(turnId: string): Promise<void>;6768/**69* Delete a turn and all of its associated file edits (cascade).70*/71deleteTurn(turnId: string): Promise<void>;7273/**74* Associates a Copilot SDK event ID with a turn. The event ID corresponds75* to the `user.message` event in the SDK event stream and is used by76* the SDK's `history.truncate` and `sessions.fork` RPCs.77*/78setTurnEventId(turnId: string, eventId: string): Promise<void>;7980/**81* Retrieves the SDK event ID previously stored for a turn.82* Returns `undefined` if no event ID has been set.83*/84getTurnEventId(turnId: string): Promise<string | undefined>;8586/**87* Returns the SDK event ID of the turn inserted immediately after the88* given turn, or `undefined` if the given turn is the last one.89*/90getNextTurnEventId(turnId: string): Promise<string | undefined>;9192/**93* Returns the SDK event ID of the earliest turn in insertion order,94* or `undefined` if there are no turns.95*/96getFirstTurnEventId(): Promise<string | undefined>;9798/**99* Deletes the given turn and all turns inserted after it, along100* with their associated file edits (cascade).101*/102truncateFromTurn(turnId: string): Promise<void>;103104/**105* Deletes all turns inserted after the given turn (but keeps the106* given turn itself). Associated file edits cascade-delete.107*/108deleteTurnsAfter(turnId: string): Promise<void>;109110/**111* Deletes all turns and their associated file edits.112*/113deleteAllTurns(): Promise<void>;114115/**116* Store a file-edit snapshot (metadata + content) for a tool invocation117* within a turn.118*119* If a record for the same `toolCallId` and `filePath` already exists120* it is replaced.121*/122storeFileEdit(edit: IFileEditRecord & IFileEditContent): Promise<void>;123124/**125* Retrieve file-edit metadata for the given tool call IDs.126* Content blobs are **not** included — use {@link readFileEditContent}127* to fetch them on demand. Results are returned in insertion order.128*/129getFileEdits(toolCallIds: string[]): Promise<IFileEditRecord[]>;130131/**132* Retrieve file-edit metadata for all edits in this session.133* Content blobs are **not** included — use {@link readFileEditContent}134* to fetch them on demand. Results are returned in insertion order.135*/136getAllFileEdits(): Promise<IFileEditRecord[]>;137138/**139* Retrieve file-edit metadata for all edits belonging to a specific turn.140* Content blobs are **not** included — use {@link readFileEditContent}141* to fetch them on demand. Results are returned in insertion order.142*/143getFileEditsByTurn(turnId: string): Promise<IFileEditRecord[]>;144145/**146* Read the before/after content blobs for a single file edit.147* Returns `undefined` if no edit exists for the given key.148*/149readFileEditContent(toolCallId: string, filePath: string): Promise<IFileEditContent | undefined>;150151// ---- Session metadata ------------------------------------------------152153/**154* Read a metadata value by key.155* Returns `undefined` if no value has been stored for the key.156*/157getMetadata(key: string): Promise<string | undefined>;158159/**160* Gets a bulk of metadata. For example `getMetadataObject({ foo: true }) -> { foo: 'data' }`161*/162getMetadataObject<T extends Record<string, unknown>>(obj: T): Promise<{ [K in keyof T]: string | undefined }>;163164/**165* Store a metadata key-value pair. Overwrites any existing value for the key.166*/167setMetadata(key: string, value: string): Promise<void>;168169/**170* Bulk-remaps turn IDs using the provided old→new mapping.171* Used after copying a database file for a forked session.172*/173remapTurnIds(mapping: ReadonlyMap<string, string>): Promise<void>;174175/**176* Creates a safe, consistent copy of the database at the given path177* using SQLite's `VACUUM INTO` command.178*/179vacuumInto(targetPath: string): Promise<void>;180181/**182* Resolves once all in-flight write operations on this database have183* settled. Used by graceful shutdown to flush fire-and-forget writes184* before the process exits.185*/186whenIdle(): Promise<void>;187188/**189* Close the database connection. After calling this method, the object is190* considered disposed and all other methods will reject with an error.191*/192close(): Promise<void>;193}194195/**196* Provides persistent, per-session data directories on disk.197*198* Each session gets a directory under `{userDataPath}/agentSessionData/{sessionId}/`199* where internal agent-host code can store arbitrary files (e.g. file snapshots).200*201* Directories are created lazily — callers should use {@link IFileService.createFolder}202* before writing files. Cleanup happens eagerly on session removal and via startup203* garbage collection for orphaned directories.204*/205export interface ISessionDataService {206readonly _serviceBrand: undefined;207208/**209* Returns the root data directory URI for a session.210* Does **not** create the directory on disk; callers use211* `IFileService.createFolder()` as needed.212*/213getSessionDataDir(session: URI): URI;214215/**216* Returns the root data directory URI for a session given its raw ID.217* Equivalent to {@link getSessionDataDir} but without requiring a full URI.218*/219getSessionDataDirById(sessionId: string): URI;220221/**222* Opens (or creates) a per-session SQLite database. The database file is223* stored at `{sessionDataDir}/session.db`. Migrations are applied224* automatically on first use.225*226* Returns a ref-counted reference. Multiple callers for the same session227* share the same underlying connection. The connection is closed when228* the last reference is disposed.229*/230openDatabase(session: URI): IReference<ISessionDatabase>;231232/**233* Opens an existing per-session database **only if the database file234* already exists on disk**. Returns `undefined` when no database has235* been created yet, avoiding the side effect of materializing empty236* database files during read-only operations like listing sessions.237*/238tryOpenDatabase(session: URI): Promise<IReference<ISessionDatabase> | undefined>;239240/**241* Recursively deletes the data directory for a session, if it exists.242*/243deleteSessionData(session: URI): Promise<void>;244245/**246* Deletes data directories that do not correspond to any known session.247* Called at startup; safe to call multiple times.248*/249cleanupOrphanedData(knownSessionIds: Set<string>): Promise<void>;250251/**252* Resolves once all in-flight write operations across every currently253* open per-session database have settled. Intended for graceful254* shutdown — fire-and-forget writes (e.g. metadata persistence) would255* otherwise be lost when the process exits.256*/257whenIdle(): Promise<void>;258}259260261