Skip to content

Commit b874b1d

Browse files
authored
Copilot CLI: Remove CopilotCLISessionOptions (#4853)
* Copilot CLI Remove CopilotCLISessionOptions * Updates
1 parent 670338c commit b874b1d

7 files changed

Lines changed: 104 additions & 142 deletions

File tree

src/extension/chatSessions/copilotcli/node/copilotCli.ts

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type { SessionOptions, SweCustomAgent } from '@github/copilot/sdk';
77
import { promises as fs } from 'fs';
88
import * as path from 'path';
99
import type * as vscode from 'vscode';
10-
import type { Uri } from 'vscode';
1110
import { IAuthenticationService } from '../../../../platform/authentication/common/authentication';
1211
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
1312
import { IEnvService } from '../../../../platform/env/common/envService';
@@ -23,9 +22,7 @@ import { basename } from '../../../../util/vs/base/common/resources';
2322
import { URI } from '../../../../util/vs/base/common/uri';
2423
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
2524
import { IChatPromptFileService } from '../../common/chatPromptFileService';
26-
import { getWorkingDirectory, IWorkspaceInfo } from '../../common/workspaceInfo';
2725
import { getCopilotLogger } from './logger';
28-
import { remapCustomAgentTools, type McpServerMappings } from './mcpHandler';
2926
import { ensureNodePtyShim } from './nodePtyShim';
3027
import { ensureRipgrepShim } from './ripgrepShim';
3128

@@ -39,75 +36,6 @@ const COPILOT_CLI_SESSION_AGENTS_MEMENTO_KEY = 'github.copilot.cli.sessionAgents
3936
*/
4037
export const COPILOT_CLI_DEFAULT_AGENT_ID = '___vscode_default___';
4138

42-
export class CopilotCLISessionOptions {
43-
public readonly workspaceInfo: IWorkspaceInfo;
44-
private readonly model?: string;
45-
private readonly agent?: SweCustomAgent;
46-
private readonly customAgents?: SweCustomAgent[];
47-
private readonly mcpServers?: SessionOptions['mcpServers'];
48-
private readonly copilotUrl?: string;
49-
private readonly skillLocations?: Uri[];
50-
private readonly systemMessage?: SessionOptions['systemMessage'];
51-
constructor(options: { model?: string; workspaceInfo: IWorkspaceInfo; mcpServers?: SessionOptions['mcpServers']; agent?: SweCustomAgent; customAgents?: SweCustomAgent[]; copilotUrl?: string; skillLocations?: Uri[]; systemMessage?: SessionOptions['systemMessage'] }, private readonly logService: ILogService) {
52-
this.workspaceInfo = options.workspaceInfo;
53-
this.model = options.model;
54-
this.mcpServers = options.mcpServers;
55-
this.agent = options.agent;
56-
this.customAgents = options.customAgents;
57-
this.copilotUrl = options.copilotUrl;
58-
this.skillLocations = options.skillLocations;
59-
this.systemMessage = options.systemMessage;
60-
}
61-
62-
public get agentName(): string | undefined {
63-
return this.agent?.name;
64-
}
65-
66-
public toSessionOptions(mcpServerMappings?: McpServerMappings): Readonly<SessionOptions> {
67-
const allOptions: SessionOptions = {
68-
clientName: 'vscode',
69-
};
70-
71-
const workingDirectory = getWorkingDirectory(this.workspaceInfo);
72-
if (workingDirectory) {
73-
allOptions.workingDirectory = workingDirectory.fsPath;
74-
}
75-
if (this.model) {
76-
allOptions.model = this.model as unknown as SessionOptions['model'];
77-
}
78-
if (this.mcpServers && Object.keys(this.mcpServers).length > 0) {
79-
allOptions.mcpServers = this.mcpServers;
80-
this.logService.info(`[CopilotCLISession] Passing ${Object.keys(this.mcpServers).length} MCP server(s) to SDK: [${Object.keys(this.mcpServers).join(', ')}]`);
81-
for (const [id, cfg] of Object.entries(this.mcpServers)) {
82-
this.logService.info(`[CopilotCLISession] ${id}: type=${cfg.type}`);
83-
}
84-
} else {
85-
this.logService.info('[CopilotCLISession] No MCP servers to pass to SDK');
86-
}
87-
if (this.skillLocations) {
88-
allOptions.skillDirectories = this.skillLocations.map(uri => uri.fsPath);
89-
}
90-
if (mcpServerMappings?.size && this.customAgents && this.mcpServers) {
91-
remapCustomAgentTools(this.customAgents, mcpServerMappings, this.mcpServers, this.agent);
92-
}
93-
if (this.agent) {
94-
allOptions.selectedCustomAgent = this.agent;
95-
}
96-
if (this.customAgents) {
97-
allOptions.customAgents = this.customAgents;
98-
}
99-
allOptions.enableStreaming = true;
100-
if (this.copilotUrl) {
101-
allOptions.copilotUrl = this.copilotUrl;
102-
}
103-
if (this.systemMessage) {
104-
allOptions.systemMessage = this.systemMessage;
105-
}
106-
allOptions.sessionCapabilities = new Set(['plan-mode', 'memory', 'cli-documentation', 'ask-user', 'interactive-mode', 'system-notifications']);
107-
return allOptions as Readonly<SessionOptions>;
108-
}
109-
}
110-
11139
export interface CopilotCLIModelInfo {
11240
readonly id: string;
11341
readonly name: string;

src/extension/chatSessions/copilotcli/node/copilotcliSession.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { getWorkingDirectory, isIsolationEnabled, IWorkspaceInfo } from '../../c
3434
import { buildChatHistoryFromEvents, enrichToolInvocationWithSubagentMetadata, getAffectedUrisForEditTool, isCopilotCliEditToolCall, isCopilotCLIToolThatCouldRequirePermissions, processToolExecutionComplete, processToolExecutionStart, RequestIdDetails, ToolCall, updateTodoList } from '../common/copilotCLITools';
3535
import { IChatDelegationSummaryService } from '../common/delegationSummaryService';
3636
import { getCopilotCLISessionStateDir } from './cliHelpers';
37-
import { CopilotCLISessionOptions, getAgentFileNameFromFilePath, ICopilotCLISDK } from './copilotCli';
37+
import { getAgentFileNameFromFilePath, ICopilotCLISDK } from './copilotCli';
3838
import type { CopilotCliBridgeSpanProcessor } from './copilotCliBridgeSpanProcessor';
3939
import { ICopilotCLIImageSupport } from './copilotCLIImageSupport';
4040
import { PermissionRequest, requestPermission, requiresFileEditconfirmation } from './permissionHelpers';
@@ -125,7 +125,7 @@ export class CopilotCLISession extends DisposableStore implements ICopilotCLISes
125125
return this._sdkSession;
126126
}
127127
public get workspace() {
128-
return this._options.workspaceInfo;
128+
return this._workspaceInfo;
129129
}
130130
private _lastUsedModel: string | undefined;
131131
private _permissionLevel: string | undefined;
@@ -145,7 +145,8 @@ export class CopilotCLISession extends DisposableStore implements ICopilotCLISes
145145
this._updateSdkTraceContext = updater;
146146
}
147147
constructor(
148-
private readonly _options: CopilotCLISessionOptions,
148+
private readonly _workspaceInfo: IWorkspaceInfo,
149+
private readonly _agentName: string | undefined,
149150
private readonly _sdkSession: Session,
150151
@ILogService private readonly logService: ILogService,
151152
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
@@ -686,7 +687,7 @@ export class CopilotCLISession extends DisposableStore implements ICopilotCLISes
686687
vscodeRequestId: request.id,
687688
copilotRequestId: sdkRequestId,
688689
toolIdEditMap: resolvedToolIdEditMap,
689-
agentId: this._options.agentName,
690+
agentId: this._agentName,
690691
}]).catch(error => {
691692
this.logService.error(`[CopilotCLISession] Failed to update chat session metadata store for request ${request.id}`, error);
692693
});

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

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ import { buildChatHistoryFromEvents, stripReminders } from '../common/copilotCLI
4444
import { ICustomSessionTitleService } from '../common/customSessionTitleService';
4545
import { IChatDelegationSummaryService } from '../common/delegationSummaryService';
4646
import { getCopilotCLISessionDir, getCopilotCLISessionEventsFile, getCopilotCLIWorkspaceFile } from './cliHelpers';
47-
import { CopilotCLISessionOptions, ICopilotCLIAgents, ICopilotCLISDK } from './copilotCli';
47+
import { ICopilotCLIAgents, ICopilotCLISDK } from './copilotCli';
4848
import { CopilotCliBridgeSpanProcessor } from './copilotCliBridgeSpanProcessor';
4949
import { CopilotCLISession, ICopilotCLISession } from './copilotcliSession';
5050
import { ICopilotCLISkills } from './copilotCLISkills';
51-
import { ICopilotCLIMCPHandler, McpServerMappings } from './mcpHandler';
51+
import { ICopilotCLIMCPHandler, McpServerMappings, remapCustomAgentTools } from './mcpHandler';
52+
5253

5354
const COPILOT_CLI_WORKSPACE_JSON_FILE_KEY = 'github.copilot.cli.workspaceSessionFile';
5455

@@ -525,9 +526,9 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
525526
const { mcpConfig: mcpServers, disposable: mcpGateway } = await this.mcpHandler.loadMcpConfig();
526527
try {
527528
const copilotUrl = this.configurationService.getConfig(ConfigKey.Shared.DebugOverrideProxyUrl) || undefined;
528-
const options = await this.createSessionsOptions({ model, workspaceInfo, mcpServers, agent, copilotUrl, sessionId, debugTargetSessionIds });
529+
const { agentName, sessionOptions } = await this.createSessionsOptions({ model, workspaceInfo, mcpServers, agent, copilotUrl, sessionId, debugTargetSessionIds, mcpServerMappings });
529530
const sessionManager = await raceCancellationError(this.getSessionManager(), token);
530-
const sdkSession = await sessionManager.createSession({ ...options.toSessionOptions(mcpServerMappings), sessionId });
531+
const sdkSession = await sessionManager.createSession({ ...sessionOptions, sessionId });
531532
this._newSessionIds.delete(sdkSession.sessionId);
532533
// After the first session creation, the SDK's OTel TracerProvider is
533534
// initialized. Install the bridge processor so SDK-native spans flow
@@ -548,7 +549,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
548549
}
549550
this.logService.trace(`[CopilotCLISession] Created new CopilotCLI session ${sdkSession.sessionId}.`);
550551

551-
const session = await this.createCopilotSession(sdkSession, options, sessionManager);
552+
const session = await this.createCopilotSession(sdkSession, workspaceInfo, agentName, sessionManager);
552553
session.object.add(mcpGateway);
553554
return session;
554555
}
@@ -638,14 +639,59 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
638639
return false;
639640
}
640641

641-
protected async createSessionsOptions(options: { model?: string; workspaceInfo: IWorkspaceInfo; mcpServers?: SessionOptions['mcpServers']; agent: SweCustomAgent | undefined; copilotUrl?: string; sessionId?: string; debugTargetSessionIds?: readonly string[] }, readonly?: boolean): Promise<CopilotCLISessionOptions> {
642+
protected async createSessionsOptions(options: { model?: string; workspaceInfo: IWorkspaceInfo; mcpServers?: SessionOptions['mcpServers']; agent: SweCustomAgent | undefined; copilotUrl?: string; sessionId?: string; debugTargetSessionIds?: readonly string[]; mcpServerMappings?: McpServerMappings }, readonly?: boolean): Promise<{ readonly sessionOptions: Readonly<SessionOptions>; readonly agentName: string | undefined }> {
642643
const [customAgents, skillLocations] = await Promise.all([
643644
this.agents.getAgents(),
644-
readonly ? Promise.resolve([]) : this.copilotCLISkills.getSkillsLocations(),
645+
readonly ? Promise.resolve<Uri[]>([]) : this.copilotCLISkills.getSkillsLocations(),
645646
]);
646647
const variablesContext = this._promptVariablesService.buildTemplateVariablesContext(options.sessionId, options.debugTargetSessionIds);
647648
const systemMessage = variablesContext ? { mode: 'append' as const, content: variablesContext } : undefined;
648-
return new CopilotCLISessionOptions({ ...options, customAgents, skillLocations, systemMessage }, this.logService);
649+
650+
const allOptions: SessionOptions = {
651+
clientName: 'vscode',
652+
};
653+
654+
const workingDirectory = getWorkingDirectory(options.workspaceInfo);
655+
if (workingDirectory) {
656+
allOptions.workingDirectory = workingDirectory.fsPath;
657+
}
658+
if (options.model) {
659+
allOptions.model = options.model as unknown as SessionOptions['model'];
660+
}
661+
if (options.mcpServers && Object.keys(options.mcpServers).length > 0) {
662+
allOptions.mcpServers = options.mcpServers;
663+
this.logService.info(`[CopilotCLISession] Passing ${Object.keys(options.mcpServers).length} MCP server(s) to SDK: [${Object.keys(options.mcpServers).join(', ')}]`);
664+
for (const [id, cfg] of Object.entries(options.mcpServers)) {
665+
this.logService.info(`[CopilotCLISession] ${id}: type=${cfg.type}`);
666+
}
667+
} else {
668+
this.logService.info('[CopilotCLISession] No MCP servers to pass to SDK');
669+
}
670+
if (skillLocations.length > 0) {
671+
allOptions.skillDirectories = skillLocations.map(uri => uri.fsPath);
672+
}
673+
if (options.mcpServerMappings?.size && customAgents && options.mcpServers) {
674+
remapCustomAgentTools(customAgents, options.mcpServerMappings, options.mcpServers, options.agent);
675+
}
676+
if (options.agent) {
677+
allOptions.selectedCustomAgent = options.agent;
678+
}
679+
if (customAgents.length > 0) {
680+
allOptions.customAgents = customAgents;
681+
}
682+
allOptions.enableStreaming = true;
683+
if (options.copilotUrl) {
684+
allOptions.copilotUrl = options.copilotUrl;
685+
}
686+
if (systemMessage) {
687+
allOptions.systemMessage = systemMessage;
688+
}
689+
allOptions.sessionCapabilities = new Set(['plan-mode', 'memory', 'cli-documentation', 'ask-user', 'interactive-mode', 'system-notifications']);
690+
691+
return {
692+
sessionOptions: allOptions as Readonly<SessionOptions>,
693+
agentName: options.agent?.name,
694+
};
649695
}
650696

651697
public async getSession({ sessionId, model, workspaceInfo, readonly, agent, debugTargetSessionIds, mcpServerMappings }: { sessionId: string; model?: string; workspaceInfo: IWorkspaceInfo; readonly: boolean; agent?: SweCustomAgent; debugTargetSessionIds?: readonly string[]; mcpServerMappings?: McpServerMappings }, token: CancellationToken): Promise<RefCountedSession | undefined> {
@@ -677,15 +723,15 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
677723
]);
678724
try {
679725
const copilotUrl = this.configurationService.getConfig(ConfigKey.Shared.DebugOverrideProxyUrl) || undefined;
680-
const options = await this.createSessionsOptions({ model, agent, workspaceInfo, mcpServers, copilotUrl, sessionId, debugTargetSessionIds }, readonly);
726+
const { agentName, sessionOptions } = await this.createSessionsOptions({ model, agent, workspaceInfo, mcpServers, copilotUrl, sessionId, debugTargetSessionIds, mcpServerMappings }, readonly);
681727

682-
const sdkSession = await sessionManager.getSession({ ...options.toSessionOptions(mcpServerMappings), sessionId }, !readonly);
728+
const sdkSession = await sessionManager.getSession({ ...sessionOptions, sessionId }, !readonly);
683729
if (!sdkSession) {
684730
this.logService.error(`[CopilotCLISession] CopilotCLI failed to get session ${sessionId}.`);
685731
return undefined;
686732
}
687733

688-
const session = await this.createCopilotSession(sdkSession, options, sessionManager, readonly);
734+
const session = await this.createCopilotSession(sdkSession, workspaceInfo, agentName, sessionManager, readonly);
689735
session.object.add(mcpGateway);
690736
return session;
691737
}
@@ -722,9 +768,9 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
722768
]);
723769

724770
const copilotUrl = this.configurationService.getConfig(ConfigKey.Shared.DebugOverrideProxyUrl) || undefined;
725-
const options = await this.createSessionsOptions({ workspaceInfo, mcpServers: undefined, copilotUrl, agent: undefined, sessionId: newSessionId }, false);
771+
const { agentName, sessionOptions } = await this.createSessionsOptions({ workspaceInfo, mcpServers: undefined, copilotUrl, agent: undefined, sessionId: newSessionId }, false);
726772

727-
const sdkSession = await sessionManager.getSession({ ...options.toSessionOptions(), sessionId: newSessionId }, false);
773+
const sdkSession = await sessionManager.getSession({ ...sessionOptions, sessionId: newSessionId }, false);
728774
if (!sdkSession) {
729775
this.logService.error(`[CopilotCLISession] CopilotCLI failed to open forked session ${newSessionId}.`);
730776
throw new Error(`Failed to fork session ${sessionId}`);
@@ -736,7 +782,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
736782
let events: ReturnType<typeof sdkSession.getEvents> = [];
737783
// Only if we have a request to truncate should we open and trucate.
738784
if (requestId) {
739-
const session = this.createCopilotSession(sdkSession, options, sessionManager, false, true);
785+
const session = this.createCopilotSession(sdkSession, workspaceInfo, agentName, sessionManager, false, true);
740786
disposables.add(session);
741787
const history = await session.object.getChatHistory();
742788
const requestToTruncateTo = history.find(event => event instanceof ChatRequestTurn2 && event.id === requestId);
@@ -856,8 +902,8 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
856902
return firstUserMessage;
857903
}
858904

859-
private createCopilotSession(sdkSession: Session, options: CopilotCLISessionOptions, sessionManager: internal.LocalSessionManager, readonly = false, nowait = false): RefCountedSession {
860-
const session = this.instantiationService.createInstance(CopilotCLISession, options, sdkSession);
905+
private createCopilotSession(sdkSession: Session, workspaceInfo: IWorkspaceInfo, agentName: string | undefined, sessionManager: internal.LocalSessionManager, readonly = false, nowait = false): RefCountedSession {
906+
const session = this.instantiationService.createInstance(CopilotCLISession, workspaceInfo, agentName, sdkSession);
861907
this._debugFileLogger.startSession(session.sessionId).catch(err => {
862908
this.logService.error('[CopilotCLISession] Failed to start debug log session', err);
863909
});

src/extension/chatSessions/copilotcli/node/test/copilotCliSessionService.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('CopilotCLISessionService', () => {
139139
invokeFunction(fn: (accessor: unknown, ...args: any[]) => any, ...args: any[]): any {
140140
return fn(accessor, ...args);
141141
},
142-
createInstance: (ctor: unknown, options: any, sdkSession: any) => {
142+
createInstance: (ctor: unknown, workspaceInfo: any, agentName: any, sdkSession: any) => {
143143
if (ctor === CopilotCLISessionWorkspaceTracker) {
144144
return new class extends mock<CopilotCLISessionWorkspaceTracker>() {
145145
override async initialize(): Promise<void> { return; }
@@ -148,7 +148,7 @@ describe('CopilotCLISessionService', () => {
148148
}
149149
}();
150150
}
151-
return disposables.add(new CopilotCLISession(options, sdkSession, logService, workspaceService, sdk, new MockChatSessionMetadataStore(), instantiationService, delegationService, new NullRequestLogger(), new NullICopilotCLIImageSupport(), new FakeToolsService(), new FakeUserQuestionHandler(), accessor.get(IConfigurationService), new NoopOTelService(resolveOTelConfig({ env: {}, extensionVersion: '0.0.0', sessionId: 'test' })), new class extends mock<IChatPromptFileService>() { override get customAgentPromptFiles() { return []; } }));
151+
return disposables.add(new CopilotCLISession(workspaceInfo, agentName, sdkSession, logService, workspaceService, sdk, new MockChatSessionMetadataStore(), instantiationService, delegationService, new NullRequestLogger(), new NullICopilotCLIImageSupport(), new FakeToolsService(), new FakeUserQuestionHandler(), accessor.get(IConfigurationService), new NoopOTelService(resolveOTelConfig({ env: {}, extensionVersion: '0.0.0', sessionId: 'test' })), new class extends mock<IChatPromptFileService>() { override get customAgentPromptFiles() { return []; } }));
152152
}
153153
} as unknown as IInstantiationService;
154154
const configurationService = accessor.get(IConfigurationService);

0 commit comments

Comments
 (0)