Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/sessionRequestLifecycle.ts
13399 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 * as vscode from 'vscode';6import { ILogService } from '../../../platform/log/common/logService';7import { createServiceIdentifier } from '../../../util/common/services';8import { Disposable } from '../../../util/vs/base/common/lifecycle';9import { IChatSessionMetadataStore, StoredModeInstructions } from '../common/chatSessionMetadataStore';10import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';11import { IChatSessionWorktreeCheckpointService } from '../common/chatSessionWorktreeCheckpointService';12import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';13import { getWorkingDirectory, isIsolationEnabled, IWorkspaceInfo } from '../common/workspaceInfo';14import { IPullRequestDetectionService } from './pullRequestDetectionService';15import { clearChangesCacheForAffectedSessions } from './chatSessionRepositoryTracker';1617export interface ISessionRequestLifecycle {18readonly _serviceBrand: undefined;1920/**21* Begin tracking a request for a session. Creates a baseline checkpoint22* if this is the first request in the session. Records request details23* (agent, mode instructions) in the metadata store.24*/25startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void>;2627/**28* Finalize a request: commit worktree changes, create checkpoints, detect29* pull requests, and remove the request from tracking. Defers completion30* work until the last in-flight request for a session completes (to support31* steering).32*/33endRequest(sessionId: string, request: vscode.ChatRequest, session: SessionCompletionInfo, token: vscode.CancellationToken): Promise<void>;34}3536export interface SessionCompletionInfo {37readonly status: vscode.ChatSessionStatus | undefined;38readonly workspace: IWorkspaceInfo;39readonly createdPullRequestUrl: string | undefined;40}4142export const ISessionRequestLifecycle = createServiceIdentifier<ISessionRequestLifecycle>('ISessionRequestLifecycle');4344export class SessionRequestLifecycle extends Disposable implements ISessionRequestLifecycle {45declare readonly _serviceBrand: undefined;4647/**48* Tracks in-flight requests per session so we can coordinate worktree49* commit / PR handling and cleanup.50*51* We generally cannot have parallel requests for the same session, but when52* steering is involved there can be multiple requests in flight for a53* single session (the original request continues running while steering54* requests are processed). This map records all active requests for each55* session so that any worktree-related actions are deferred until the last56* in-flight request for that session has completed.57*/58private readonly pendingRequestBySession = new Map<string, Set<vscode.ChatRequest>>();5960constructor(61@IChatSessionWorktreeService private readonly worktreeService: IChatSessionWorktreeService,62@IChatSessionWorktreeCheckpointService private readonly checkpointService: IChatSessionWorktreeCheckpointService,63@IChatSessionWorkspaceFolderService private readonly workspaceFolderService: IChatSessionWorkspaceFolderService,64@IPullRequestDetectionService private readonly prDetectionService: IPullRequestDetectionService,65@IChatSessionMetadataStore private readonly metadataStore: IChatSessionMetadataStore,66@ILogService private readonly logService: ILogService,67) {68super();69}7071async startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void> {72if (isFirstRequest) {73if (workspace.worktreeProperties) {74void this.worktreeService.setWorktreeProperties(sessionId, workspace.worktreeProperties);75}76const workingDirectory = getWorkingDirectory(workspace);77if (workingDirectory && !isIsolationEnabled(workspace)) {78void this.workspaceFolderService.trackSessionWorkspaceFolder(sessionId, workingDirectory.fsPath, workspace.repositoryProperties);79}80}8182const modeInstructions: StoredModeInstructions | undefined = request.modeInstructions2 ? {83uri: request.modeInstructions2.uri?.toString(),84name: request.modeInstructions2.name,85content: request.modeInstructions2.content,86metadata: request.modeInstructions2.metadata,87isBuiltin: request.modeInstructions2.isBuiltin,88} : undefined;89this.metadataStore.updateRequestDetails(sessionId, [{ vscodeRequestId: request.id, agentId: agentName ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));9091const requests = this.pendingRequestBySession.get(sessionId) ?? new Set<vscode.ChatRequest>();92requests.add(request);93this.pendingRequestBySession.set(sessionId, requests);9495if (isFirstRequest) {96await this.checkpointService.handleRequest(sessionId);97}98}99100async endRequest(sessionId: string, request: vscode.ChatRequest, session: SessionCompletionInfo, token: vscode.CancellationToken): Promise<void> {101const pendingRequests = this.pendingRequestBySession.get(sessionId);102if (pendingRequests && pendingRequests.size > 1) {103// We still have pending requests for this session, which means the user has done some steering.104// Wait for all requests to complete, the last request to complete will handle the commit.105pendingRequests.delete(request);106return;107}108109if (token.isCancellationRequested) {110this.untrackRequest(sessionId, request);111return;112}113114try {115if (session.status === vscode.ChatSessionStatus.Completed) {116const workingDirectory = getWorkingDirectory(session.workspace);117if (isIsolationEnabled(session.workspace)) {118// When isolation is enabled and we are using a git worktree, so we commit119// all the changes in the worktree directory when the session is completed.120// Note that if the worktree supports checkpoints, then the commit will be121// done in the checkpoint so that users can easily see the changes made in122// the worktree and also revert back if needed.123await this.worktreeService.handleRequestCompleted(sessionId);124} else if (workingDirectory) {125// When isolation is not enabled, we are operating in the workspace directly,126// so we stage all the changes in the workspace directory when the session is127// completed128await this.workspaceFolderService.handleRequestCompleted(sessionId);129}130131// Create checkpoint - we create a checkpoint for the worktree changes so that users132// can easily see the changes made in the worktree and also revert back if needed. This133// is used if worktree isolation is enabled, and auto-commit is disabled or workspace134// isolation is enabled.135await this.checkpointService.handleRequestCompleted(sessionId, request.id);136137// Clear the changes (diff) cache for sessions associated with the same folder.138if (workingDirectory) {139void clearChangesCacheForAffectedSessions(workingDirectory, [sessionId], this.logService, this.metadataStore, this.workspaceFolderService, this.worktreeService).catch(ex => this.logService.error(ex, 'Failed to clear changes cache after request completion'));140}141}142143this.prDetectionService.handlePullRequestCreated(sessionId, session.createdPullRequestUrl);144} finally {145this.untrackRequest(sessionId, request);146}147}148149private untrackRequest(sessionId: string, request: vscode.ChatRequest): void {150const requests = this.pendingRequestBySession.get(sessionId);151if (requests) {152requests.delete(request);153if (requests.size === 0) {154this.pendingRequestBySession.delete(sessionId);155}156}157}158}159160161