Skip to content

Commit a6209df

Browse files
authored
Copilot - add session parentId to metadata (#311366)
* Copilot - add session parentId to metadata * Fix build * Fix test
1 parent 2212cb2 commit a6209df

7 files changed

Lines changed: 47 additions & 4 deletions

File tree

extensions/copilot/src/extension/chatSessions/common/chatSessionMetadataStore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,6 @@ export interface IChatSessionMetadataStore {
124124
storeForkedSessionMetadata(sourceSessionId: string, targetSessionId: string, customTitle: string): Promise<void>;
125125
setSessionOrigin(sessionId: string): Promise<void>;
126126
getSessionOrigin(sessionId: string): Promise<'vscode' | 'other'>;
127+
setSessionParentId(sessionId: string, parentSessionId: string): Promise<void>;
128+
getSessionParentId(sessionId: string): Promise<string | undefined>;
127129
}

extensions/copilot/src/extension/chatSessions/common/test/mockChatSessionMetadataStore.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,12 @@ export class MockChatSessionMetadataStore implements IChatSessionMetadataStore {
143143
async getSessionOrigin(sessionId: string): Promise<'vscode' | 'other'> {
144144
return this._sessionOrigins.get(sessionId) ?? 'vscode';
145145
}
146+
147+
setSessionParentId(_sessionId: string, _parentSessionId: string): Promise<void> {
148+
return Promise.resolve();
149+
}
150+
151+
getSessionParentId(_sessionId: string): Promise<string | undefined> {
152+
return Promise.resolve(undefined);
153+
}
146154
}

extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export type ISessionOptions = {
6969
debugTargetSessionIds?: readonly string[];
7070
mcpServerMappings?: McpServerMappings;
7171
additionalWorkspaces?: IWorkspaceInfo[];
72+
sessionParentId?: string;
7273
}
7374
export type IGetSessionOptions = ISessionOptions & { sessionId: string };
7475
export type ICreateSessionOptions = ISessionOptions & { sessionId?: string };
@@ -567,7 +568,15 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
567568

568569
const session = this.createCopilotSession(sdkSession, options.workspace, options.agent?.name, sessionManager);
569570
session.object.add(mcpGateway);
571+
572+
// Set origin
570573
void this._chatSessionMetadataStore.setSessionOrigin(session.object.sessionId);
574+
575+
// Set session parent id
576+
if (options.sessionParentId) {
577+
void this._chatSessionMetadataStore.setSessionParentId(session.object.sessionId, options.sessionParentId);
578+
}
579+
571580
return session;
572581
}
573582
catch (error) {

extensions/copilot/src/extension/chatSessions/vscode-node/chatSessionMetadataStoreImpl.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,16 @@ export class ChatSessionMetadataStore extends Disposable implements IChatSession
371371
return 'other';
372372
}
373373

374+
public async setSessionParentId(sessionId: string, parentSessionId: string): Promise<void> {
375+
await this._intialize.value;
376+
await this.updateMetadataFields(sessionId, { parentSessionId });
377+
}
378+
379+
public async getSessionParentId(sessionId: string): Promise<string | undefined> {
380+
const metadata = await this.getSessionMetadata(sessionId, false);
381+
return metadata?.parentSessionId;
382+
}
383+
374384
private async getSessionMetadata(sessionId: string, createMetadataFileIfNotFound = true): Promise<ChatSessionMetadataFile | undefined> {
375385
if (isUntitledSessionId(sessionId)) {
376386
return undefined;

extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,10 @@ export class CopilotCLIChatSessionContentProvider extends Disposable implements
405405
workingDirectory: vscode.Uri | undefined,
406406
): Promise<{ readonly [key: string]: unknown }> {
407407
if (worktreeProperties) {
408+
const sessionParentId = await this._metadataStore.getSessionParentId(sessionId);
409+
408410
return {
411+
sessionParentId,
409412
autoCommit: worktreeProperties.autoCommit !== false,
410413
baseCommit: worktreeProperties?.baseCommit,
411414
baseBranchName: worktreeProperties.version === 2
@@ -451,7 +454,8 @@ export class CopilotCLIChatSessionContentProvider extends Disposable implements
451454
} satisfies { readonly [key: string]: unknown };
452455
}
453456

454-
const [sessionRequestDetails, repositoryProperties] = await Promise.all([
457+
const [sessionParentId, sessionRequestDetails, repositoryProperties] = await Promise.all([
458+
this._metadataStore.getSessionParentId(sessionId),
455459
this._metadataStore.getRequestDetails(sessionId),
456460
this._metadataStore.getRepositoryProperties(sessionId)
457461
]);
@@ -470,6 +474,7 @@ export class CopilotCLIChatSessionContentProvider extends Disposable implements
470474
: undefined;
471475

472476
return {
477+
sessionParentId,
473478
isolationMode: IsolationMode.Workspace,
474479
repositoryPath: repositoryProperties?.repositoryPath,
475480
branchName: repositoryProperties?.branchName,

extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const REPOSITORY_OPTION_ID = 'repository';
6161
const _sessionWorktreeIsolationCache = new Map<string, boolean>();
6262
const BRANCH_OPTION_ID = 'branch';
6363
const ISOLATION_OPTION_ID = 'isolation';
64+
const PARENT_SESSION_OPTION_ID = 'parentSessionId';
6465
const LAST_USED_ISOLATION_OPTION_KEY = 'github.copilot.cli.lastUsedIsolationOption';
6566
const OPEN_REPOSITORY_COMMAND_ID = 'github.copilot.cli.sessions.openRepository';
6667
const OPEN_IN_COPILOT_CLI_COMMAND_ID = 'github.copilot.cli.openInCopilotCLI';
@@ -308,9 +309,12 @@ export class CopilotCLIChatSessionItemProvider extends Disposable implements vsc
308309
// repository state which we are passing along through the metadata
309310
worktreeProperties = await this.worktreeManager.getWorktreeProperties(session.id);
310311

312+
const sessionParentId = await this.chatSessionMetadataStore.getSessionParentId(session.id);
313+
311314
if (worktreeProperties) {
312315
// Worktree
313316
metadata = {
317+
sessionParentId,
314318
autoCommit: worktreeProperties.autoCommit !== false,
315319
baseCommit: worktreeProperties?.baseCommit,
316320
baseBranchName: worktreeProperties.version === 2
@@ -373,6 +377,7 @@ export class CopilotCLIChatSessionItemProvider extends Disposable implements vsc
373377
: undefined;
374378

375379
metadata = {
380+
sessionParentId,
376381
isolationMode: IsolationMode.Workspace,
377382
repositoryPath: repositoryProperties?.repositoryPath,
378383
branchName: repositoryProperties?.branchName,
@@ -1267,6 +1272,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
12671272
let { chatSessionContext } = context;
12681273
const disposables = new DisposableStore();
12691274
let sessionId: string | undefined = undefined;
1275+
let sessionParentId: string | undefined = undefined;
12701276
let sdkSessionId: string | undefined = undefined;
12711277
try {
12721278

@@ -1284,6 +1290,8 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
12841290
_sessionBranch.set(sessionId, value);
12851291
} else if (opt.optionId === ISOLATION_OPTION_ID && value) {
12861292
_sessionIsolation.set(sessionId, value as IsolationMode);
1293+
} else if (opt.optionId === PARENT_SESSION_OPTION_ID && value) {
1294+
sessionParentId = value;
12871295
}
12881296
}
12891297
}
@@ -1369,7 +1377,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
13691377
};
13701378
const newBranch = (isUntitled && request.prompt && this.branchNameGenerator) ? this.branchNameGenerator.generateBranchName(fakeContext, token) : undefined;
13711379

1372-
const sessionResult = await this.getOrCreateSession(request, chatSessionContext, stream, { model, agent, newBranch }, disposables, token);
1380+
const sessionResult = await this.getOrCreateSession(request, chatSessionContext, stream, { model, agent, newBranch, sessionParentId }, disposables, token);
13731381
const session = sessionResult.session;
13741382
if (session) {
13751383
disposables.add(session);
@@ -1729,7 +1737,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
17291737
}
17301738
}
17311739

1732-
private async getOrCreateSession(request: vscode.ChatRequest, chatSessionContext: vscode.ChatSessionContext, stream: vscode.ChatResponseStream, options: { model: { model: string; reasoningEffort?: string } | undefined; agent: SweCustomAgent | undefined; newBranch?: Promise<string | undefined> }, disposables: DisposableStore, token: vscode.CancellationToken): Promise<{ session: IReference<ICopilotCLISession> | undefined; trusted: boolean }> {
1740+
private async getOrCreateSession(request: vscode.ChatRequest, chatSessionContext: vscode.ChatSessionContext, stream: vscode.ChatResponseStream, options: { model: { model: string; reasoningEffort?: string } | undefined; agent: SweCustomAgent | undefined; newBranch?: Promise<string | undefined>; sessionParentId?: string }, disposables: DisposableStore, token: vscode.CancellationToken): Promise<{ session: IReference<ICopilotCLISession> | undefined; trusted: boolean }> {
17331741
const { resource } = chatSessionContext.chatSessionItem;
17341742
const existingSessionId = this.sessionItemProvider.untitledSessionIdMapping.get(SessionIdForCLI.parse(resource));
17351743
const id = existingSessionId ?? SessionIdForCLI.parse(resource);
@@ -1747,7 +1755,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
17471755
const debugTargetSessionIds = extractDebugTargetSessionIds(request.references);
17481756
const mcpServerMappings = buildMcpServerMappings(request.tools);
17491757
const session = isNewSession ?
1750-
await this.sessionService.createSession({ model: model?.model, reasoningEffort: model?.reasoningEffort, workspace: workspaceInfo, agent, debugTargetSessionIds, mcpServerMappings }, token) :
1758+
await this.sessionService.createSession({ model: model?.model, reasoningEffort: model?.reasoningEffort, workspace: workspaceInfo, agent, debugTargetSessionIds, mcpServerMappings, sessionParentId: options.sessionParentId }, token) :
17511759
await this.sessionService.getSession({ sessionId: id, model: model?.model, reasoningEffort: model?.reasoningEffort, workspace: workspaceInfo, agent, debugTargetSessionIds, mcpServerMappings }, token);
17521760
this.sessionItemProvider.notifySessionsChange();
17531761
// TODO @DonJayamanne We need to refresh to add this new session, but we need a label.

extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCLIChatSessions.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ function createProvider() {
158158
const metadataStore = new class extends mock<IChatSessionMetadataStore>() {
159159
override getRequestDetails = vi.fn(async () => []);
160160
override getRepositoryProperties = vi.fn(async () => undefined);
161+
override getSessionParentId = vi.fn(async () => undefined);
161162
};
162163
const gitService = new TestGitService();
163164
const folderRepositoryManager = new TestFolderRepositoryManager();

0 commit comments

Comments
 (0)