Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,21 @@
import type { SweCustomAgent } from '@github/copilot/sdk';
import * as l10n from '@vscode/l10n';
import * as vscode from 'vscode';
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
import { ILogService } from '../../../platform/log/common/logService';
import { IPromptsService, ParsedPromptFile } from '../../../platform/promptFiles/common/promptsService';
import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
import { createServiceIdentifier } from '../../../util/common/services';
import { DisposableStore, IReference } from '../../../util/vs/base/common/lifecycle';
import { URI } from '../../../util/vs/base/common/uri';
import { ChatVariablesCollection, extractDebugTargetSessionIds, isPromptFile } from '../../prompt/common/chatVariablesCollection';
import { IChatSessionMetadataStore, StoredModeInstructions } from '../common/chatSessionMetadataStore';
import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';
import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';
import { FolderRepositoryInfo, IFolderRepositoryManager, IsolationMode } from '../common/folderRepositoryManager';
import { SessionIdForCLI } from '../copilotcli/common/utils';
import { emptyWorkspaceInfo, getWorkingDirectory, isIsolationEnabled, IWorkspaceInfo } from '../common/workspaceInfo';
import { SessionIdForCLI } from '../copilotcli/common/utils';
import { COPILOT_CLI_REASONING_EFFORT_PROPERTY, ICopilotCLIAgents, ICopilotCLIModels } from '../copilotcli/node/copilotCli';
import { ICopilotCLISession } from '../copilotcli/node/copilotcliSession';
import { ICopilotCLISessionService } from '../copilotcli/node/copilotcliSessionService';
import { buildMcpServerMappings, McpServerMappings } from '../copilotcli/node/mcpHandler';
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';

function isReasoningEffortFeatureEnabled(configurationService: IConfigurationService): boolean {
return configurationService.getConfig(ConfigKey.Advanced.CLIThinkingEffortEnabled);
Expand Down Expand Up @@ -85,13 +82,10 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
constructor(
@ICopilotCLISessionService private readonly sessionService: ICopilotCLISessionService,
@IFolderRepositoryManager private readonly folderRepositoryManager: IFolderRepositoryManager,
@IChatSessionWorktreeService private readonly worktreeService: IChatSessionWorktreeService,
@IChatSessionWorkspaceFolderService private readonly workspaceFolderService: IChatSessionWorkspaceFolderService,
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
@ICopilotCLIModels private readonly copilotCLIModels: ICopilotCLIModels,
@ICopilotCLIAgents private readonly copilotCLIAgents: ICopilotCLIAgents,
@IPromptsService private readonly promptsService: IPromptsService,
@IChatSessionMetadataStore private readonly chatSessionMetadataStore: IChatSessionMetadataStore,
@ILogService private readonly logService: ILogService,
@IConfigurationService private readonly configurationService: IConfigurationService,
) { }
Expand Down Expand Up @@ -129,15 +123,6 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
return { session: undefined, isNewSession, model, agent, trusted };
}
this.logService.info(`Using Copilot CLI session: ${session.object.sessionId} (isNewSession: ${isNewSession}, isolationEnabled: ${isIsolationEnabled(workspaceInfo)}, workingDirectory: ${workingDirectory}, worktreePath: ${worktreeProperties?.worktreePath})`);
if (isNewSession) {
if (worktreeProperties) {
void this.worktreeService.setWorktreeProperties(session.object.sessionId, worktreeProperties);
}
this.finalizeSessionCreation(session.object.sessionId, session.object.workspace);
}

const modeInstructions = this.createModeInstructions(request);
this.chatSessionMetadataStore.updateRequestDetails(sessionId, [{ vscodeRequestId: request.id, agentId: agent?.name ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));

disposables.add(session);
disposables.add(session.object.attachStream(stream));
Expand Down Expand Up @@ -198,14 +183,6 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
]);

const session = await this.sessionService.createSession({ workspace, agent, model: model?.model, reasoningEffort: model?.reasoningEffort, mcpServerMappings: options.mcpServerMappings }, token);
const worktreeProperties = workspace.worktreeProperties;
if (worktreeProperties) {
void this.worktreeService.setWorktreeProperties(session.object.sessionId, worktreeProperties);
}
this.finalizeSessionCreation(session.object.sessionId, workspace);

const modeInstructions = this.createModeInstructions(request);
this.chatSessionMetadataStore.updateRequestDetails(session.object.sessionId, [{ vscodeRequestId: request.id, agentId: agent?.name ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));

return { session, model, agent };
}
Expand Down Expand Up @@ -255,23 +232,6 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
return undefined;
}

private finalizeSessionCreation(sessionId: string, workspace: IWorkspaceInfo): void {
const workingDirectory = getWorkingDirectory(workspace);
if (workingDirectory && !isIsolationEnabled(workspace)) {
void this.workspaceFolderService.trackSessionWorkspaceFolder(sessionId, workingDirectory.fsPath, workspace.repositoryProperties);
}
}

private createModeInstructions(request: vscode.ChatRequest): StoredModeInstructions | undefined {
return request.modeInstructions2 ? {
uri: request.modeInstructions2.uri?.toString(),
name: request.modeInstructions2.name,
content: request.modeInstructions2.content,
metadata: request.modeInstructions2.metadata,
isBuiltin: request.modeInstructions2.isBuiltin,
} : undefined;
}

private async getPromptInfoFromRequest(request: vscode.ChatRequest, token: vscode.CancellationToken): Promise<ParsedPromptFile | undefined> {
const promptFile = new ChatVariablesCollection(request.references).find(isPromptFile);
if (!promptFile || !URI.isUri(promptFile.reference.value)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { Attachment, SessionOptions } from '@github/copilot/sdk';
import type { Attachment, SessionOptions, SweCustomAgent } from '@github/copilot/sdk';
import * as l10n from '@vscode/l10n';
import * as vscode from 'vscode';
import { ChatExtendedRequestHandler, ChatRequestTurn2, Uri } from 'vscode';
Expand Down Expand Up @@ -47,10 +47,10 @@ import { ICopilotCLIChatSessionInitializer, SessionInitOptions } from './copilot
import { convertReferenceToVariable } from './copilotCLIPromptReferences';
import { ICopilotCLITerminalIntegration, TerminalOpenLocation } from './copilotCLITerminalIntegration';
import { CopilotCloudSessionsProvider } from './copilotCloudSessionsProvider';
import { UNTRUSTED_FOLDER_MESSAGE } from './folderRepositoryManagerImpl';
import { IPullRequestDetectionService } from './pullRequestDetectionService';
import { getSelectedSessionOptions, ISessionOptionGroupBuilder, OPEN_REPOSITORY_COMMAND_ID, toRepositoryOptionItem, toWorkspaceFolderOptionItem } from './sessionOptionGroupBuilder';
import { ISessionRequestLifecycle } from './sessionRequestLifecycle';
import { UNTRUSTED_FOLDER_MESSAGE } from './folderRepositoryManagerImpl';

/**
* ODO:
Expand Down Expand Up @@ -580,7 +580,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
return this.handleRequest.bind(this);
}

private readonly contextForRequest = new Map<string, { prompt: string; attachments: Attachment[] }>();
private readonly contextForRequest = new Map<string, { prompt: string; attachments: Attachment[]; agent?: string }>();

/**
* Outer request handler that supports *yielding* for session steering.
Expand Down Expand Up @@ -733,14 +733,14 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
const selectedOptions = getSelectedSessionOptions(chatSessionContext.inputState);
const sessionResult = await this.getOrCreateSession(request, chatSessionContext.chatSessionItem.resource, { ...selectedOptions, newBranch: branchNamePromise, stream }, disposables, token);
({ session } = sessionResult);
const { model } = sessionResult;
const { model, agent } = sessionResult;
if (!session || token.isCancellationRequested) {
return {};
}

sdkSessionId = session.object.sessionId;

await this.sessionRequestLifecycle.startRequest(sdkSessionId, request, context.history.length === 0);
await this.sessionRequestLifecycle.startRequest(sdkSessionId, request, context.history.length === 0, session.object.workspace, agent?.name ?? this.contextForRequest.get(session.object.sessionId)?.agent);

if (request.command === 'delegate') {
await this.handleDelegationToCloud(session.object, request, context, stream, token);
Expand Down Expand Up @@ -769,18 +769,18 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
}
}

private async getOrCreateSession(request: vscode.ChatRequest, chatResource: vscode.Uri, options: SessionInitOptions, disposables: DisposableStore, token: vscode.CancellationToken): Promise<{ session: IReference<ICopilotCLISession> | undefined; isNewSession: boolean; model: { model: string; reasoningEffort?: string } | undefined; trusted: boolean }> {
private async getOrCreateSession(request: vscode.ChatRequest, chatResource: vscode.Uri, options: SessionInitOptions, disposables: DisposableStore, token: vscode.CancellationToken): Promise<{ session: IReference<ICopilotCLISession> | undefined; isNewSession: boolean; model: { model: string; reasoningEffort?: string } | undefined; agent: SweCustomAgent | undefined; trusted: boolean }> {
const result = await this.sessionInitializer.getOrCreateSession(request, chatResource, options, disposables, token);
const { session, isNewSession, model, trusted } = result;
const { session, isNewSession, model, agent, trusted } = result;
if (!session || token.isCancellationRequested) {
return { session: undefined, isNewSession, model, trusted };
return { session: undefined, isNewSession, model, agent, trusted };
}

if (isNewSession) {
this.sessionItemProvider.refreshSession({ reason: 'update', sessionId: session.object.sessionId });
}

return { session, isNewSession, model, trusted };
return { session, isNewSession, model, agent, trusted };
}

private async handleDelegationToCloud(session: ICopilotCLISession, request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) {
Expand Down Expand Up @@ -835,7 +835,8 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
const { prompt, attachments, references } = await this.promptResolver.resolvePrompt(request, await requestPromptPromise, (otherReferences || []).concat([]), workspaceInfo, [], token);

const mcpServerMappings = buildMcpServerMappings(request.tools);
const { session, model } = await this.sessionInitializer.createDelegatedSession(request, workspaceInfo, { mcpServerMappings }, token);
const { session, model, agent } = await this.sessionInitializer.createDelegatedSession(request, workspaceInfo, { mcpServerMappings }, token);

if (summary) {
const summaryRef = await this.chatDelegationSummaryService.trackSummaryUsage(session.object.sessionId, summary);
if (summaryRef) {
Expand All @@ -844,7 +845,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
}

try {
this.contextForRequest.set(session.object.sessionId, { prompt, attachments });
this.contextForRequest.set(session.object.sessionId, { prompt, attachments, agent: agent?.name });
// this.sessionItemProvider.notifySessionsChange();
// TODO @DonJayamanne I don't think we need to refresh the list of session here just yet, or perhaps we do,
// Same as getOrCreate session, we need a dummy title or the initial prompt to show in the sessions list.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { ILogService } from '../../../platform/log/common/logService';
import { createServiceIdentifier } from '../../../util/common/services';
import { Disposable } from '../../../util/vs/base/common/lifecycle';
import { IChatSessionMetadataStore, StoredModeInstructions } from '../common/chatSessionMetadataStore';
import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';
import { IChatSessionWorktreeCheckpointService } from '../common/chatSessionWorktreeCheckpointService';
import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';
Expand All @@ -17,9 +19,10 @@ export interface ISessionRequestLifecycle {

/**
* Begin tracking a request for a session. Creates a baseline checkpoint
* if this is the first request in the session.
* if this is the first request in the session. Records request details
* (agent, mode instructions) in the metadata store.
*/
startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean): Promise<void>;
startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void>;

/**
* Finalize a request: commit worktree changes, create checkpoints, detect
Expand Down Expand Up @@ -59,18 +62,39 @@ export class SessionRequestLifecycle extends Disposable implements ISessionReque
@IChatSessionWorktreeCheckpointService private readonly checkpointService: IChatSessionWorktreeCheckpointService,
@IChatSessionWorkspaceFolderService private readonly workspaceFolderService: IChatSessionWorkspaceFolderService,
@IPullRequestDetectionService private readonly prDetectionService: IPullRequestDetectionService,
@IChatSessionMetadataStore private readonly metadataStore: IChatSessionMetadataStore,
@ILogService private readonly logService: ILogService,
) {
super();
}

async startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean): Promise<void> {
async startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void> {
if (isFirstRequest) {
await this.checkpointService.handleRequest(sessionId);
if (workspace.worktreeProperties) {
void this.worktreeService.setWorktreeProperties(sessionId, workspace.worktreeProperties);
}
const workingDirectory = getWorkingDirectory(workspace);
if (workingDirectory && !isIsolationEnabled(workspace)) {
void this.workspaceFolderService.trackSessionWorkspaceFolder(sessionId, workingDirectory.fsPath, workspace.repositoryProperties);
Comment thread
DonJayamanne marked this conversation as resolved.
}
}

const modeInstructions: StoredModeInstructions | undefined = request.modeInstructions2 ? {
uri: request.modeInstructions2.uri?.toString(),
name: request.modeInstructions2.name,
content: request.modeInstructions2.content,
metadata: request.modeInstructions2.metadata,
isBuiltin: request.modeInstructions2.isBuiltin,
} : undefined;
this.metadataStore.updateRequestDetails(sessionId, [{ vscodeRequestId: request.id, agentId: agentName ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));

const requests = this.pendingRequestBySession.get(sessionId) ?? new Set<vscode.ChatRequest>();
requests.add(request);
this.pendingRequestBySession.set(sessionId, requests);

if (isFirstRequest) {
await this.checkpointService.handleRequest(sessionId);
}
}

async endRequest(sessionId: string, request: vscode.ChatRequest, session: SessionCompletionInfo, token: vscode.CancellationToken): Promise<void> {
Expand Down
Loading
Loading