33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import { ModelProvider } from '@github/copilot/sdk' ;
6+ import type { SessionOptions } from '@github/copilot/sdk' ;
77import type { ChatSessionProviderOptionItem } from 'vscode' ;
8+ import { IAuthenticationService } from '../../../../platform/authentication/common/authentication' ;
89import { IEnvService } from '../../../../platform/env/common/envService' ;
910import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext' ;
1011import { ILogService } from '../../../../platform/log/common/logService' ;
12+ import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService' ;
1113import { createServiceIdentifier } from '../../../../util/common/services' ;
1214import { Lazy } from '../../../../util/vs/base/common/lazy' ;
15+ import { Disposable , IDisposable , toDisposable } from '../../../../util/vs/base/common/lifecycle' ;
16+ import { getCopilotLogger } from './logger' ;
1317import { ensureNodePtyShim } from './nodePtyShim' ;
1418
1519const COPILOT_CLI_MODEL_MEMENTO_KEY = 'github.copilot.cli.sessionModel' ;
16- const DEFAULT_CLI_MODEL : ModelProvider = {
17- type : 'anthropic' ,
18- model : 'claude-sonnet-4.5'
19- } ;
20-
21- /**
22- * Convert a model ID to a ModelProvider object for the Copilot CLI SDK
23- */
24- export function getModelProvider ( modelId : string ) : ModelProvider {
25- // Keep logic minimal; advanced mapping handled by resolveModelProvider in modelMapping.ts.
26- if ( modelId . startsWith ( 'claude-' ) ) {
27- return {
28- type : 'anthropic' ,
29- model : modelId
30- } ;
31- } else if ( modelId . startsWith ( 'gpt-' ) ) {
32- return {
33- type : 'openai' ,
34- model : modelId
35- } ;
36- }
37- return DEFAULT_CLI_MODEL ;
38- }
20+ const DEFAULT_CLI_MODEL = 'claude-sonnet-4' ;
3921
4022export interface ICopilotCLIModels {
4123 _serviceBrand : undefined ;
42- toModelProvider ( modelId : string ) : ModelProvider ;
24+ toModelProvider ( modelId : string ) : string ;
4325 getDefaultModel ( ) : Promise < ChatSessionProviderOptionItem > ;
4426 setDefaultModel ( model : ChatSessionProviderOptionItem ) : Promise < void > ;
4527 getAvailableModels ( ) : Promise < ChatSessionProviderOptionItem [ ] > ;
4628}
4729
30+ export const ICopilotCLISDK = createServiceIdentifier < ICopilotCLISDK > ( 'ICopilotCLISDK' ) ;
31+
4832export const ICopilotCLIModels = createServiceIdentifier < ICopilotCLIModels > ( 'ICopilotCLIModels' ) ;
4933
5034export class CopilotCLIModels implements ICopilotCLIModels {
5135 declare _serviceBrand : undefined ;
5236 private readonly _availableModels : Lazy < Promise < ChatSessionProviderOptionItem [ ] > > ;
5337 constructor (
38+ @ICopilotCLISDK private readonly copilotCLISDK : ICopilotCLISDK ,
5439 @IVSCodeExtensionContext private readonly extensionContext : IVSCodeExtensionContext ,
5540 ) {
5641 this . _availableModels = new Lazy < Promise < ChatSessionProviderOptionItem [ ] > > ( ( ) => this . _getAvailableModels ( ) ) ;
5742 }
5843 public toModelProvider ( modelId : string ) {
59- // TODO: replace with SDK-backed lookup once dynamic model list available.
60- return getModelProvider ( modelId ) ;
44+ return modelId ;
6145 }
6246 public async getDefaultModel ( ) {
6347 // We control this
6448 const models = await this . getAvailableModels ( ) ;
65- const defaultModel = models . find ( m => m . id . toLowerCase ( ) . includes ( DEFAULT_CLI_MODEL . model . toLowerCase ( ) ) ) ?? models [ 0 ] ;
49+ const defaultModel = models . find ( m => m . id . toLowerCase ( ) === DEFAULT_CLI_MODEL . toLowerCase ( ) ) ?? models [ 0 ] ;
6650 const preferredModelId = this . extensionContext . globalState . get < string > ( COPILOT_CLI_MODEL_MEMENTO_KEY , defaultModel . id ) ;
6751
6852 return models . find ( m => m . id === preferredModelId ) ?? defaultModel ;
@@ -78,22 +62,12 @@ export class CopilotCLIModels implements ICopilotCLIModels {
7862 }
7963
8064 private async _getAvailableModels ( ) : Promise < ChatSessionProviderOptionItem [ ] > {
81- return [ {
82- id : 'claude-sonnet-4.5' ,
83- name : 'Claude Sonnet 4.5'
84- } ,
85- {
86- id : 'claude-sonnet-4' ,
87- name : 'Claude Sonnet 4'
88- } ,
89- {
90- id : 'claude-haiku-4.5' ,
91- name : 'Claude Haiku 4.5'
92- } ,
93- {
94- id : 'gpt-5' ,
95- name : 'GPT-5'
96- } ] ;
65+ const { getAvailableModels } = await this . copilotCLISDK . getPackage ( ) ;
66+ const models = await getAvailableModels ( ) ;
67+ return models . map ( model => ( {
68+ id : model . model ,
69+ name : model . label
70+ } satisfies ChatSessionProviderOptionItem ) ) ;
9771 }
9872}
9973
@@ -106,8 +80,6 @@ export interface ICopilotCLISDK {
10680 getPackage ( ) : Promise < typeof import ( '@github/copilot/sdk' ) > ;
10781}
10882
109- export const ICopilotCLISDK = createServiceIdentifier < ICopilotCLISDK > ( 'ICopilotCLISDK' ) ;
110-
11183export class CopilotCLISDK implements ICopilotCLISDK {
11284 declare _serviceBrand : undefined ;
11385
@@ -120,11 +92,94 @@ export class CopilotCLISDK implements ICopilotCLISDK {
12092 public async getPackage ( ) : Promise < typeof import ( '@github/copilot/sdk' ) > {
12193 try {
12294 // Ensure the node-pty shim exists before importing the SDK (required for CLI sessions)
123- await ensureNodePtyShim ( this . extensionContext . extensionPath , this . envService . appRoot ) ;
95+ await ensureNodePtyShim ( this . extensionContext . extensionPath , this . envService . appRoot , this . logService ) ;
12496 return await import ( '@github/copilot/sdk' ) ;
12597 } catch ( error ) {
12698 this . logService . error ( `[CopilotCLISDK] Failed to load @github/copilot/sdk: ${ error } ` ) ;
12799 throw error ;
128100 }
129101 }
130102}
103+
104+ export interface ICopilotCLISessionOptionsService {
105+ readonly _serviceBrand : undefined ;
106+ createOptions (
107+ options : SessionOptions ,
108+ permissionHandler : CopilotCLIPermissionsHandler
109+ ) : Promise < SessionOptions > ;
110+ }
111+ export const ICopilotCLISessionOptionsService = createServiceIdentifier < ICopilotCLISessionOptionsService > ( 'ICopilotCLISessionOptionsService' ) ;
112+
113+ export class CopilotCLISessionOptionsService implements ICopilotCLISessionOptionsService {
114+ declare _serviceBrand : undefined ;
115+ constructor (
116+ @IWorkspaceService private readonly workspaceService : IWorkspaceService ,
117+ @IAuthenticationService private readonly _authenticationService : IAuthenticationService ,
118+ @ILogService private readonly logService : ILogService ,
119+ ) { }
120+
121+ public async createOptions ( options : SessionOptions , permissionHandler : CopilotCLIPermissionsHandler ) {
122+ const copilotToken = await this . _authenticationService . getAnyGitHubSession ( ) ;
123+ const workingDirectory = options . workingDirectory ?? await this . getWorkspaceFolderPath ( ) ;
124+ const allOptions : SessionOptions = {
125+ env : {
126+ ...process . env ,
127+ COPILOTCLI_DISABLE_NONESSENTIAL_TRAFFIC : '1'
128+ } ,
129+ logger : getCopilotLogger ( this . logService ) ,
130+ requestPermission : async ( permissionRequest ) => {
131+ return await permissionHandler . getPermissions ( permissionRequest ) ;
132+ } ,
133+ authInfo : {
134+ type : 'token' ,
135+ token : copilotToken ?. accessToken ?? '' ,
136+ host : 'https://github.com'
137+ } ,
138+ ...options ,
139+ } ;
140+
141+ if ( workingDirectory ) {
142+ allOptions . workingDirectory = workingDirectory ;
143+ }
144+ return allOptions ;
145+ }
146+ private async getWorkspaceFolderPath ( ) {
147+ if ( this . workspaceService . getWorkspaceFolders ( ) . length === 0 ) {
148+ return undefined ;
149+ }
150+ if ( this . workspaceService . getWorkspaceFolders ( ) . length === 1 ) {
151+ return this . workspaceService . getWorkspaceFolders ( ) [ 0 ] . fsPath ;
152+ }
153+ const folder = await this . workspaceService . showWorkspaceFolderPicker ( ) ;
154+ return folder ?. uri ?. fsPath ;
155+ }
156+ }
157+
158+
159+ /**
160+ * Perhaps temporary interface to handle permission requests from the Copilot CLI SDK
161+ * Perhaps because the SDK needs a better way to handle this in long term per session.
162+ */
163+ export interface ICopilotCLIPermissions {
164+ onDidRequestPermissions ( handler : SessionOptions [ 'requestPermission' ] ) : IDisposable ;
165+ }
166+
167+ export class CopilotCLIPermissionsHandler extends Disposable implements ICopilotCLIPermissions {
168+ private _handler : SessionOptions [ 'requestPermission' ] | undefined ;
169+
170+ public onDidRequestPermissions ( handler : SessionOptions [ 'requestPermission' ] ) : IDisposable {
171+ this . _handler = handler ;
172+ return this . _register ( toDisposable ( ( ) => {
173+ this . _handler = undefined ;
174+ } ) ) ;
175+ }
176+
177+ public async getPermissions ( permission : Parameters < NonNullable < SessionOptions [ 'requestPermission' ] > > [ 0 ] ) : Promise < ReturnType < NonNullable < SessionOptions [ 'requestPermission' ] > > > {
178+ if ( ! this . _handler ) {
179+ return {
180+ kind : "denied-interactively-by-user"
181+ } ;
182+ }
183+ return await this . _handler ( permission ) ;
184+ }
185+ }
0 commit comments