Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/sessionRequestLifecycle.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as vscode from 'vscode';
7
import { ILogService } from '../../../platform/log/common/logService';
8
import { createServiceIdentifier } from '../../../util/common/services';
9
import { Disposable } from '../../../util/vs/base/common/lifecycle';
10
import { IChatSessionMetadataStore, StoredModeInstructions } from '../common/chatSessionMetadataStore';
11
import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';
12
import { IChatSessionWorktreeCheckpointService } from '../common/chatSessionWorktreeCheckpointService';
13
import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';
14
import { getWorkingDirectory, isIsolationEnabled, IWorkspaceInfo } from '../common/workspaceInfo';
15
import { IPullRequestDetectionService } from './pullRequestDetectionService';
16
import { clearChangesCacheForAffectedSessions } from './chatSessionRepositoryTracker';
17
18
export interface ISessionRequestLifecycle {
19
readonly _serviceBrand: undefined;
20
21
/**
22
* Begin tracking a request for a session. Creates a baseline checkpoint
23
* if this is the first request in the session. Records request details
24
* (agent, mode instructions) in the metadata store.
25
*/
26
startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void>;
27
28
/**
29
* Finalize a request: commit worktree changes, create checkpoints, detect
30
* pull requests, and remove the request from tracking. Defers completion
31
* work until the last in-flight request for a session completes (to support
32
* steering).
33
*/
34
endRequest(sessionId: string, request: vscode.ChatRequest, session: SessionCompletionInfo, token: vscode.CancellationToken): Promise<void>;
35
}
36
37
export interface SessionCompletionInfo {
38
readonly status: vscode.ChatSessionStatus | undefined;
39
readonly workspace: IWorkspaceInfo;
40
readonly createdPullRequestUrl: string | undefined;
41
}
42
43
export const ISessionRequestLifecycle = createServiceIdentifier<ISessionRequestLifecycle>('ISessionRequestLifecycle');
44
45
export class SessionRequestLifecycle extends Disposable implements ISessionRequestLifecycle {
46
declare readonly _serviceBrand: undefined;
47
48
/**
49
* Tracks in-flight requests per session so we can coordinate worktree
50
* commit / PR handling and cleanup.
51
*
52
* We generally cannot have parallel requests for the same session, but when
53
* steering is involved there can be multiple requests in flight for a
54
* single session (the original request continues running while steering
55
* requests are processed). This map records all active requests for each
56
* session so that any worktree-related actions are deferred until the last
57
* in-flight request for that session has completed.
58
*/
59
private readonly pendingRequestBySession = new Map<string, Set<vscode.ChatRequest>>();
60
61
constructor(
62
@IChatSessionWorktreeService private readonly worktreeService: IChatSessionWorktreeService,
63
@IChatSessionWorktreeCheckpointService private readonly checkpointService: IChatSessionWorktreeCheckpointService,
64
@IChatSessionWorkspaceFolderService private readonly workspaceFolderService: IChatSessionWorkspaceFolderService,
65
@IPullRequestDetectionService private readonly prDetectionService: IPullRequestDetectionService,
66
@IChatSessionMetadataStore private readonly metadataStore: IChatSessionMetadataStore,
67
@ILogService private readonly logService: ILogService,
68
) {
69
super();
70
}
71
72
async startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void> {
73
if (isFirstRequest) {
74
if (workspace.worktreeProperties) {
75
void this.worktreeService.setWorktreeProperties(sessionId, workspace.worktreeProperties);
76
}
77
const workingDirectory = getWorkingDirectory(workspace);
78
if (workingDirectory && !isIsolationEnabled(workspace)) {
79
void this.workspaceFolderService.trackSessionWorkspaceFolder(sessionId, workingDirectory.fsPath, workspace.repositoryProperties);
80
}
81
}
82
83
const modeInstructions: StoredModeInstructions | undefined = request.modeInstructions2 ? {
84
uri: request.modeInstructions2.uri?.toString(),
85
name: request.modeInstructions2.name,
86
content: request.modeInstructions2.content,
87
metadata: request.modeInstructions2.metadata,
88
isBuiltin: request.modeInstructions2.isBuiltin,
89
} : undefined;
90
this.metadataStore.updateRequestDetails(sessionId, [{ vscodeRequestId: request.id, agentId: agentName ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));
91
92
const requests = this.pendingRequestBySession.get(sessionId) ?? new Set<vscode.ChatRequest>();
93
requests.add(request);
94
this.pendingRequestBySession.set(sessionId, requests);
95
96
if (isFirstRequest) {
97
await this.checkpointService.handleRequest(sessionId);
98
}
99
}
100
101
async endRequest(sessionId: string, request: vscode.ChatRequest, session: SessionCompletionInfo, token: vscode.CancellationToken): Promise<void> {
102
const pendingRequests = this.pendingRequestBySession.get(sessionId);
103
if (pendingRequests && pendingRequests.size > 1) {
104
// We still have pending requests for this session, which means the user has done some steering.
105
// Wait for all requests to complete, the last request to complete will handle the commit.
106
pendingRequests.delete(request);
107
return;
108
}
109
110
if (token.isCancellationRequested) {
111
this.untrackRequest(sessionId, request);
112
return;
113
}
114
115
try {
116
if (session.status === vscode.ChatSessionStatus.Completed) {
117
const workingDirectory = getWorkingDirectory(session.workspace);
118
if (isIsolationEnabled(session.workspace)) {
119
// When isolation is enabled and we are using a git worktree, so we commit
120
// all the changes in the worktree directory when the session is completed.
121
// Note that if the worktree supports checkpoints, then the commit will be
122
// done in the checkpoint so that users can easily see the changes made in
123
// the worktree and also revert back if needed.
124
await this.worktreeService.handleRequestCompleted(sessionId);
125
} else if (workingDirectory) {
126
// When isolation is not enabled, we are operating in the workspace directly,
127
// so we stage all the changes in the workspace directory when the session is
128
// completed
129
await this.workspaceFolderService.handleRequestCompleted(sessionId);
130
}
131
132
// Create checkpoint - we create a checkpoint for the worktree changes so that users
133
// can easily see the changes made in the worktree and also revert back if needed. This
134
// is used if worktree isolation is enabled, and auto-commit is disabled or workspace
135
// isolation is enabled.
136
await this.checkpointService.handleRequestCompleted(sessionId, request.id);
137
138
// Clear the changes (diff) cache for sessions associated with the same folder.
139
if (workingDirectory) {
140
void 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'));
141
}
142
}
143
144
this.prDetectionService.handlePullRequestCreated(sessionId, session.createdPullRequestUrl);
145
} finally {
146
this.untrackRequest(sessionId, request);
147
}
148
}
149
150
private untrackRequest(sessionId: string, request: vscode.ChatRequest): void {
151
const requests = this.pendingRequestBySession.get(sessionId);
152
if (requests) {
153
requests.delete(request);
154
if (requests.size === 0) {
155
this.pendingRequestBySession.delete(sessionId);
156
}
157
}
158
}
159
}
160
161