Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotCli.ts
13405 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 type { SessionOptions, SweCustomAgent } from '@github/copilot/sdk';
7
import * as l10n from '@vscode/l10n';
8
import { promises as fs } from 'fs';
9
import * as path from 'path';
10
import type * as vscode from 'vscode';
11
import { IAuthenticationService } from '../../../../platform/authentication/common/authentication';
12
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
13
import { IEnvService } from '../../../../platform/env/common/envService';
14
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
15
import { ILogService } from '../../../../platform/log/common/logService';
16
import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';
17
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
18
import { createServiceIdentifier } from '../../../../util/common/services';
19
import { Emitter, Event } from '../../../../util/vs/base/common/event';
20
import { Lazy } from '../../../../util/vs/base/common/lazy';
21
import { Disposable } from '../../../../util/vs/base/common/lifecycle';
22
import { ResourceSet } from '../../../../util/vs/base/common/map';
23
import { basename } from '../../../../util/vs/base/common/resources';
24
import { URI } from '../../../../util/vs/base/common/uri';
25
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
26
import { getCopilotLogger } from './logger';
27
import { ensureRipgrepShim } from './ripgrepShim';
28
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
29
import { getModelCapabilitiesDescription } from '../../../conversation/common/languageModelAccess';
30
31
export const COPILOT_CLI_REASONING_EFFORT_PROPERTY = 'reasoningEffort';
32
const COPILOT_CLI_MODEL_MEMENTO_KEY = 'github.copilot.cli.sessionModel';
33
const COPILOT_CLI_REQUEST_MAP_KEY = 'github.copilot.cli.requestMap';
34
// Store last used Agent for a Session.
35
const COPILOT_CLI_SESSION_AGENTS_MEMENTO_KEY = 'github.copilot.cli.sessionAgents';
36
/**
37
* @deprecated Use empty strings to represent default model/agent instead.
38
* Left here for backward compatibility (for state stored by older versions of Chat extension).
39
*/
40
export const COPILOT_CLI_DEFAULT_AGENT_ID = '___vscode_default___';
41
42
export interface CopilotCLIModelInfo {
43
readonly id: string;
44
readonly name: string;
45
readonly multiplier?: number;
46
readonly maxInputTokens?: number;
47
readonly maxOutputTokens?: number;
48
readonly maxContextWindowTokens: number;
49
readonly supportsVision?: boolean;
50
readonly supportsReasoningEffort?: boolean;
51
readonly defaultReasoningEffort?: string;
52
readonly supportedReasoningEfforts?: string[];
53
}
54
55
export interface ICopilotCLIModels {
56
readonly _serviceBrand: undefined;
57
resolveModel(modelId: string): Promise<string | undefined>;
58
getDefaultModel(): Promise<string | undefined>;
59
setDefaultModel(modelId: string | undefined): Promise<void>;
60
getModels(): Promise<CopilotCLIModelInfo[]>;
61
registerLanguageModelChatProvider(lm: typeof vscode['lm']): void;
62
}
63
64
export function formatModelDetails(model: CopilotCLIModelInfo): string {
65
return `${model.name}${model.multiplier ? ` • ${model.multiplier}x` : ''}`;
66
}
67
68
export const ICopilotCLISDK = createServiceIdentifier<ICopilotCLISDK>('ICopilotCLISDK');
69
70
export const ICopilotCLIModels = createServiceIdentifier<ICopilotCLIModels>('ICopilotCLIModels');
71
72
export class CopilotCLIModels extends Disposable implements ICopilotCLIModels {
73
declare _serviceBrand: undefined;
74
private _availableModels?: Promise<CopilotCLIModelInfo[]>;
75
/** Synchronously available model infos (includes `auto`). Set once the eager fetch completes. */
76
private _resolvedModelInfos?: vscode.LanguageModelChatInformation[];
77
private readonly _onDidChange = this._register(new Emitter<void>());
78
79
constructor(
80
@ICopilotCLISDK private readonly copilotCLISDK: ICopilotCLISDK,
81
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
82
@ILogService private readonly logService: ILogService,
83
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
84
@IConfigurationService private readonly configurationService: IConfigurationService,
85
) {
86
super();
87
this._fetchAndCacheModels();
88
this._register(this._authenticationService.onDidAuthenticationChange(() => {
89
// Auth changed which means models could've changed. Clear caches and re-fetch.
90
this._availableModels = undefined;
91
this._resolvedModelInfos = undefined;
92
this._onDidChange.fire();
93
this._fetchAndCacheModels();
94
}));
95
}
96
97
private _fetchAndCacheModels(): void {
98
const availableModels = this._availableModels = this._getAvailableModels();
99
availableModels.then(models => {
100
// Bail out if a newer fetch has superseded this one (e.g. auth changed mid-flight).
101
if (this._availableModels !== availableModels) {
102
return;
103
}
104
this._resolvedModelInfos = this._buildModelInfos(models);
105
this._onDidChange.fire();
106
}).catch((error) => {
107
this.logService.error('[CopilotCLIModels] Failed to fetch available models', error);
108
});
109
}
110
async resolveModel(modelId: string): Promise<string | undefined> {
111
if (modelId.toLowerCase() === 'auto' && this.configurationService.getConfig(ConfigKey.Advanced.CLIAutoModelEnabled)) {
112
return modelId;
113
}
114
const models = await this.getModels();
115
modelId = modelId.trim().toLowerCase();
116
return models.find(m => m.id.toLowerCase() === modelId || m.name.toLowerCase() === modelId)?.id;
117
}
118
public async getDefaultModel() {
119
// First item in the list is always the default model (SDK sends the list ordered based on default preference)
120
const models = await this.getModels();
121
if (!models.length) {
122
return;
123
}
124
const defaultModel = models[0];
125
const preferredModelId = this.extensionContext.globalState.get<string>(COPILOT_CLI_MODEL_MEMENTO_KEY, defaultModel.id)?.trim()?.toLowerCase();
126
127
return models.find(m => m.id.toLowerCase() === preferredModelId)?.id ?? defaultModel.id;
128
}
129
130
public async setDefaultModel(modelId: string | undefined): Promise<void> {
131
await this.extensionContext.globalState.update(COPILOT_CLI_MODEL_MEMENTO_KEY, modelId);
132
}
133
134
public async getModels(): Promise<CopilotCLIModelInfo[]> {
135
if (!this._authenticationService.anyGitHubSession) {
136
return [];
137
}
138
139
// No need to query sdk multiple times, cache the result, this cannot change during a vscode session.
140
if (!this._availableModels) {
141
this._availableModels = this._getAvailableModels();
142
}
143
return this._availableModels;
144
}
145
146
private async _getAvailableModels(): Promise<CopilotCLIModelInfo[]> {
147
const [{ getAvailableModels }, authInfo] = await Promise.all([this.copilotCLISDK.getPackage(), this.copilotCLISDK.getAuthInfo()]);
148
try {
149
const models = await getAvailableModels(authInfo);
150
return models.map(model => ({
151
id: model.id,
152
name: model.name,
153
multiplier: model.billing?.multiplier,
154
maxInputTokens: model.capabilities.limits.max_prompt_tokens,
155
maxOutputTokens: model.capabilities.limits.max_output_tokens,
156
maxContextWindowTokens: model.capabilities.limits.max_context_window_tokens,
157
supportsVision: model.capabilities.supports.vision,
158
supportsReasoningEffort: model.capabilities.supports.reasoningEffort,
159
defaultReasoningEffort: model.defaultReasoningEffort,
160
supportedReasoningEfforts: model.supportedReasoningEfforts,
161
} satisfies CopilotCLIModelInfo));
162
} catch (ex) {
163
this.logService.error(`[CopilotCLISession] Failed to fetch models`, ex);
164
return [];
165
}
166
}
167
168
public registerLanguageModelChatProvider(lm: typeof vscode['lm']): void {
169
const provider: vscode.LanguageModelChatProvider = {
170
onDidChangeLanguageModelChatInformation: this._onDidChange.event,
171
provideLanguageModelChatInformation: async (_options, _token) => {
172
const autoModelEnabled = this.configurationService.getConfig(ConfigKey.Advanced.CLIAutoModelEnabled);
173
if (!this._authenticationService.anyGitHubSession || !this._resolvedModelInfos) {
174
return autoModelEnabled ? [buildAutoModel()] : [];
175
}
176
return this._resolvedModelInfos;
177
},
178
provideLanguageModelChatResponse: async (_model, _messages, _options, _progress, _token) => {
179
// Implemented via chat participants.
180
},
181
provideTokenCount: async (_model, _text, _token) => {
182
// Token counting is not currently supported for the copilotcli provider.
183
return 0;
184
}
185
};
186
this._register(lm.registerLanguageModelChatProvider('copilotcli', provider));
187
this._onDidChange.fire();
188
}
189
190
private _buildModelInfos(models: CopilotCLIModelInfo[]): vscode.LanguageModelChatInformation[] {
191
const isReasoningEffortEnabled = this.configurationService.getConfig(ConfigKey.Advanced.CLIThinkingEffortEnabled);
192
const isAutoModelEnabled = this.configurationService.getConfig(ConfigKey.Advanced.CLIAutoModelEnabled);
193
const modelsInfo: vscode.LanguageModelChatInformation[] = models.map((model, index) => {
194
const multiplier = model.multiplier === undefined ? undefined : `${model.multiplier}x`;
195
const modelInfo: vscode.LanguageModelChatInformation = {
196
id: model.id,
197
name: model.name,
198
family: model.id,
199
version: '',
200
maxInputTokens: model.maxInputTokens ?? model.maxContextWindowTokens,
201
maxOutputTokens: model.maxOutputTokens ?? 0,
202
pricing: multiplier,
203
multiplierNumeric: model.multiplier,
204
isUserSelectable: true,
205
configurationSchema: isReasoningEffortEnabled ? buildConfigurationSchema(model) : undefined,
206
capabilities: {
207
imageInput: model.supportsVision,
208
toolCalling: true
209
},
210
targetChatSessionType: 'copilotcli',
211
isDefault: !isAutoModelEnabled && index === 0 ? true : undefined,
212
};
213
const tooltip = getModelCapabilitiesDescription(modelInfo) ?? '';
214
return {
215
...modelInfo,
216
tooltip
217
};
218
});
219
if (isAutoModelEnabled) {
220
modelsInfo.unshift(buildAutoModel(models[0]));
221
}
222
return modelsInfo;
223
}
224
}
225
226
function buildAutoModel(defaultModel?: CopilotCLIModelInfo): vscode.LanguageModelChatInformation {
227
return {
228
id: 'auto',
229
name: 'Auto',
230
tooltip: l10n.t('Auto selects the best model for your request based on capacity and performance.'),
231
family: defaultModel?.id ?? '',
232
version: '',
233
maxInputTokens: defaultModel?.maxInputTokens ?? defaultModel?.maxContextWindowTokens ?? 0,
234
maxOutputTokens: defaultModel?.maxOutputTokens ?? 0,
235
isUserSelectable: true,
236
capabilities: {
237
imageInput: defaultModel?.supportsVision,
238
toolCalling: true,
239
},
240
targetChatSessionType: 'copilotcli',
241
isDefault: true,
242
};
243
}
244
245
function buildConfigurationSchema(modelInfo: CopilotCLIModelInfo): vscode.LanguageModelConfigurationSchema | undefined {
246
const effortLevels = modelInfo.supportedReasoningEfforts ?? [];
247
if (effortLevels.length === 0) {
248
return;
249
}
250
251
const defaultEffort = modelInfo.defaultReasoningEffort;
252
253
return {
254
properties: {
255
[COPILOT_CLI_REASONING_EFFORT_PROPERTY]: {
256
type: 'string',
257
title: l10n.t('Thinking Effort'),
258
enum: effortLevels,
259
enumItemLabels: effortLevels.map(level => level.charAt(0).toUpperCase() + level.slice(1)),
260
enumDescriptions: effortLevels.map(level => {
261
switch (level) {
262
case 'none': return l10n.t('No reasoning applied');
263
case 'low': return l10n.t('Faster responses with less reasoning');
264
case 'medium': return l10n.t('Balanced reasoning and speed');
265
case 'high': return l10n.t('Greater reasoning depth but slower');
266
case 'xhigh': return l10n.t('Maximum reasoning depth but slower');
267
default: return level;
268
}
269
}),
270
default: defaultEffort,
271
group: 'navigation',
272
}
273
}
274
};
275
}
276
277
/** An agent with its source URI preserved for UI and cross-referencing. */
278
export interface CLIAgentInfo {
279
readonly agent: Readonly<SweCustomAgent>;
280
/** File URI for prompt-file agents, synthetic `copilotcli:` URI for SDK-only agents. */
281
readonly sourceUri: URI;
282
readonly extensionId?: string;
283
readonly pluginUri?: URI;
284
}
285
286
export interface ICopilotCLIAgents {
287
readonly _serviceBrand: undefined;
288
readonly onDidChangeAgents: Event<void>;
289
resolveAgent(agentId: string): Promise<SweCustomAgent | undefined>;
290
getAgents(): Promise<readonly CLIAgentInfo[]>;
291
getSessionAgent(sessionId: string): Promise<string | undefined>;
292
}
293
294
export const ICopilotCLIAgents = createServiceIdentifier<ICopilotCLIAgents>('ICopilotCLIAgents');
295
296
export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
297
declare _serviceBrand: undefined;
298
private sessionAgents: Record<string, { agentId?: string; createdDateTime: number }> = {};
299
private _agentsPromise?: Promise<readonly CLIAgentInfo[]>;
300
private readonly _onDidChangeAgents = this._register(new Emitter<void>());
301
readonly onDidChangeAgents: Event<void> = this._onDidChangeAgents.event;
302
constructor(
303
@IPromptsService private readonly promptsService: IPromptsService,
304
@ICopilotCLISDK private readonly copilotCLISDK: ICopilotCLISDK,
305
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
306
@ILogService private readonly logService: ILogService,
307
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
308
) {
309
super();
310
void this.getAgents();
311
this._register(this.promptsService.onDidChangeCustomAgents(() => {
312
this._refreshAgents();
313
}));
314
this._register(this.workspaceService.onDidChangeWorkspaceFolders(() => {
315
this._refreshAgents();
316
}));
317
}
318
319
private _refreshAgents(): void {
320
this._agentsPromise = undefined;
321
this.getAgents().catch((error) => {
322
this.logService.error('[CopilotCLIAgents] Failed to refresh agents', error);
323
});
324
this._onDidChangeAgents.fire();
325
}
326
327
async trackSessionAgent(sessionId: string, agent: string | undefined): Promise<void> {
328
const details = Object.keys(this.sessionAgents).length ? this.sessionAgents : this.extensionContext.workspaceState.get<Record<string, { agentId?: string; createdDateTime: number }>>(COPILOT_CLI_SESSION_AGENTS_MEMENTO_KEY, this.sessionAgents);
329
330
details[sessionId] = { agentId: agent, createdDateTime: Date.now() };
331
this.sessionAgents = details;
332
333
// Prune entries older than 7 days.
334
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
335
for (const [key, value] of Object.entries(details)) {
336
if (value.createdDateTime < sevenDaysAgo) {
337
delete details[key];
338
}
339
}
340
341
await this.extensionContext.workspaceState.update(COPILOT_CLI_SESSION_AGENTS_MEMENTO_KEY, details);
342
}
343
344
async getSessionAgent(sessionId: string): Promise<string | undefined> {
345
const details = this.extensionContext.workspaceState.get<Record<string, { agentId?: string; createdDateTime: number }>>(COPILOT_CLI_SESSION_AGENTS_MEMENTO_KEY, this.sessionAgents);
346
// Check in-memory cache first before reading from memento.
347
// Possibly the session agent was just set and not yet persisted.
348
const agentId = this.sessionAgents[sessionId]?.agentId ?? details[sessionId]?.agentId;
349
if (agentId === COPILOT_CLI_DEFAULT_AGENT_ID) {
350
return '';
351
}
352
if (typeof agentId === 'string') {
353
return agentId;
354
}
355
const agents = await this.getAgents();
356
return agents.find(a => a.agent.name.toLowerCase() === agentId)?.agent.name;
357
}
358
359
async resolveAgent(agentId: string): Promise<SweCustomAgent | undefined> {
360
for (const customAgent of await this.promptsService.getCustomAgents(CancellationToken.None)) {
361
if (customAgent.enabled && isEnabledForCopilotCLI(customAgent) && agentId === customAgent.uri.toString()) {
362
return this.toCustomAgent(customAgent)?.agent;
363
}
364
}
365
const customAgents = await this.getAgents();
366
agentId = agentId.toLowerCase();
367
const match = customAgents.find(a => a.agent.name.toLowerCase() === agentId || a.agent.displayName?.toLowerCase() === agentId);
368
return match ? this.cloneAgent(match.agent) : undefined;
369
}
370
371
async getAgents(): Promise<readonly CLIAgentInfo[]> {
372
// Cache the promise to avoid concurrent fetches
373
if (!this._agentsPromise) {
374
this._agentsPromise = this.getAgentsImpl().catch((error) => {
375
this.logService.error('[CopilotCLIAgents] Failed to fetch custom agents', error);
376
this._agentsPromise = undefined;
377
return [];
378
});
379
}
380
381
return this._agentsPromise.then(infos => infos.map(i => ({ agent: this.cloneAgent(i.agent), sourceUri: i.sourceUri })));
382
}
383
384
async getAgentsImpl(): Promise<readonly CLIAgentInfo[]> {
385
const merged = new Map<string, CLIAgentInfo>();
386
const knownAgents = new ResourceSet();
387
for (const agent of await this.getSDKAgents()) {
388
const sourceUri = agent.path ? URI.file(agent.path) : URI.from({ scheme: 'copilotcli', path: `/agents/${agent.name}` });
389
knownAgents.add(sourceUri);
390
merged.set(agent.name.toLowerCase(), {
391
agent: this.cloneAgent(agent),
392
sourceUri,
393
});
394
}
395
for (const customAgent of await this.promptsService.getCustomAgents(CancellationToken.None)) {
396
if (!customAgent.enabled || !isEnabledForCopilotCLI(customAgent)) {
397
continue;
398
}
399
// Skip legacy .chatmode.md files — they are a deprecated format
400
// and should not appear in the Copilot CLI agent list.
401
if (customAgent.uri.path.toLowerCase().endsWith('.chatmode.md')) {
402
continue;
403
}
404
if (knownAgents.has(customAgent.uri)) {
405
continue;
406
}
407
const info = this.toCustomAgent(customAgent);
408
if (!info) {
409
continue;
410
}
411
merged.set(info.agent.name.toLowerCase(), info);
412
}
413
414
return [...merged.values()];
415
}
416
417
private async getSDKAgents(): Promise<Readonly<SweCustomAgent>[]> {
418
const workspaceFolders = this.workspaceService.getWorkspaceFolders();
419
if (workspaceFolders.length === 0) {
420
return [];
421
}
422
423
const [auth, { getCustomAgents }] = await Promise.all([this.copilotCLISDK.getAuthInfo(), this.copilotCLISDK.getPackage()]);
424
const workingDirectory = workspaceFolders[0];
425
const agents = await getCustomAgents(auth, workingDirectory.fsPath, undefined, getCopilotLogger(this.logService));
426
return agents.map(agent => this.cloneAgent(agent));
427
}
428
429
private toCustomAgent(customAgent: vscode.ChatCustomAgent): CLIAgentInfo | undefined {
430
const agentName = getAgentFileNameFromFilePath(customAgent.uri);
431
const headerName = customAgent.name;
432
const name = headerName === undefined || headerName === '' ? agentName : headerName;
433
if (!name) {
434
return undefined;
435
}
436
437
const tools = customAgent.tools?.filter(tool => !!tool) ?? [];
438
const model = customAgent.model?.[0];
439
440
return {
441
agent: {
442
name,
443
displayName: name,
444
description: customAgent.description ?? '',
445
tools: tools.length > 0 ? tools : null,
446
prompt: async () => {
447
const pf = await this.promptsService.parseFile(customAgent.uri, CancellationToken.None);
448
return pf.body?.getContent() ?? '';
449
},
450
disableModelInvocation: customAgent.disableModelInvocation ?? false,
451
...(model ? { model } : {}),
452
},
453
sourceUri: customAgent.uri,
454
};
455
}
456
457
private cloneAgent(agent: SweCustomAgent): SweCustomAgent {
458
return {
459
...agent,
460
tools: agent.tools ? [...agent.tools] : agent.tools
461
};
462
}
463
}
464
465
export function getAgentFileNameFromFilePath(filePath: URI): string {
466
const nameFromFile = basename(filePath);
467
const lowerName = nameFromFile.toLowerCase();
468
const indexOfAgentMd = lowerName.indexOf('.agent.md');
469
if (indexOfAgentMd > 0) {
470
return nameFromFile.substring(0, indexOfAgentMd);
471
}
472
const indexOfChatmodeMd = lowerName.indexOf('.chatmode.md');
473
if (indexOfChatmodeMd > 0) {
474
return nameFromFile.substring(0, indexOfChatmodeMd);
475
}
476
return nameFromFile;
477
}
478
479
480
/**
481
* Service interface to abstract dynamic import of the Copilot CLI SDK for easier unit testing.
482
* Tests can provide a mock implementation returning a stubbed SDK shape.
483
*/
484
export interface ICopilotCLISDK {
485
readonly _serviceBrand: undefined;
486
getPackage(): Promise<typeof import('@github/copilot/sdk')>;
487
getAuthInfo(): Promise<NonNullable<SessionOptions['authInfo']>>;
488
/**
489
* @deprecated
490
*/
491
getRequestId(sdkRequestId: string): RequestDetails['details'] | undefined;
492
}
493
494
type RequestDetails = { details: { requestId: string; toolIdEditMap: Record<string, string> }; createdDateTime: number };
495
export class CopilotCLISDK implements ICopilotCLISDK {
496
declare _serviceBrand: undefined;
497
private requestMap: Record<string, RequestDetails> = {};
498
private _ensureShimsPromise?: Promise<void>;
499
private _initializeLogger = new Lazy<Promise<void>>(() => this.initLogger());
500
constructor(
501
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
502
@IEnvService private readonly envService: IEnvService,
503
@ILogService private readonly logService: ILogService,
504
@IInstantiationService protected readonly instantiationService: IInstantiationService,
505
@IAuthenticationService private readonly authentService: IAuthenticationService,
506
@IConfigurationService private readonly configurationService: IConfigurationService,
507
) {
508
this.requestMap = this.extensionContext.workspaceState.get<Record<string, RequestDetails>>(COPILOT_CLI_REQUEST_MAP_KEY, {});
509
this._ensureShimsPromise = this.ensureShims();
510
this._initializeLogger.value.catch((error) => {
511
this.logService.error('[CopilotCLISDK] Failed to initialize logger', error);
512
});
513
}
514
515
/**
516
* @deprecated
517
*/
518
getRequestId(sdkRequestId: string): RequestDetails['details'] | undefined {
519
return this.requestMap[sdkRequestId]?.details;
520
}
521
522
public async getPackage(): Promise<typeof import('@github/copilot/sdk')> {
523
try {
524
// Ensure the ripgrep shim exists before importing the SDK (required for CLI sessions)
525
await this._ensureShimsPromise;
526
return await import('@github/copilot/sdk');
527
} catch (error) {
528
this.logService.error(`[CopilotCLISession] Failed to load @github/copilot/sdk: ${error}`);
529
throw error;
530
}
531
}
532
533
private async initLogger() {
534
const { logger } = await this.getPackage();
535
logger.setLogWriter({
536
outputPath: () => 'na',
537
writeLog: (level, message) => {
538
switch (level) {
539
case 'error':
540
this.logService.error(`[CopilotCLI] ${message}`);
541
break;
542
case 'warning':
543
this.logService.warn(`[CopilotCLI] ${message}`);
544
break;
545
case 'info':
546
this.logService.info(`[CopilotCLI] ${message}`);
547
break;
548
default:
549
this.logService.debug(`[CopilotCLI] ${message}`);
550
}
551
return Promise.resolve();
552
}
553
});
554
}
555
556
protected async ensureShims(): Promise<void> {
557
const successfulPlaceholder = path.join(this.extensionContext.extensionPath, 'node_modules', '@github', 'copilot', 'shims.txt');
558
if (await checkFileExists(successfulPlaceholder)) {
559
return;
560
}
561
await ensureRipgrepShim(this.extensionContext.extensionPath, this.envService.appRoot, this.logService);
562
await fs.writeFile(successfulPlaceholder, 'Shims created successfully');
563
}
564
565
public async getAuthInfo(): Promise<NonNullable<SessionOptions['authInfo']>> {
566
// Check if proxy URL is configured - if so, skip client-side token validation
567
// as the proxy will handle authentication server-side.
568
// matching the auth info set during session creation in copilotcliSessionService.
569
const overrideProxyUrl = this.configurationService.getConfig(ConfigKey.Shared.DebugOverrideProxyUrl);
570
571
if (overrideProxyUrl) {
572
this.logService.info('[CopilotCLISession] Proxy URL configured, skipping client-side token validation');
573
return {
574
type: 'hmac',
575
hmac: 'empty',
576
host: 'https://github.com',
577
copilotUser: {
578
endpoints: {
579
api: overrideProxyUrl
580
}
581
}
582
};
583
}
584
585
const copilotToken = await this.authentService.getGitHubSession('any', { silent: true });
586
return {
587
type: 'token',
588
token: copilotToken?.accessToken ?? '',
589
host: 'https://github.com'
590
};
591
}
592
}
593
594
595
export function isWelcomeView(workspaceService: IWorkspaceService) {
596
return workspaceService.getWorkspaceFolders().length === 0;
597
}
598
599
async function checkFileExists(filePath: string): Promise<boolean> {
600
try {
601
const stat = await fs.stat(filePath);
602
return stat.isFile();
603
} catch (error) {
604
return false;
605
}
606
}
607
608
export function isEnabledForCopilotCLI(customization: { sessionTypes?: readonly string[] }): boolean {
609
const sessionTypes = customization.sessionTypes;
610
return sessionTypes === undefined || sessionTypes.includes('copilotcli') || false;
611
}
612
613
614