@theia/ai-copilot 1.68.0-next.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +73 -0
  2. package/lib/browser/copilot-auth-dialog.d.ts +46 -0
  3. package/lib/browser/copilot-auth-dialog.d.ts.map +1 -0
  4. package/lib/browser/copilot-auth-dialog.js +246 -0
  5. package/lib/browser/copilot-auth-dialog.js.map +1 -0
  6. package/lib/browser/copilot-command-contribution.d.ts +22 -0
  7. package/lib/browser/copilot-command-contribution.d.ts.map +1 -0
  8. package/lib/browser/copilot-command-contribution.js +95 -0
  9. package/lib/browser/copilot-command-contribution.js.map +1 -0
  10. package/lib/browser/copilot-frontend-application-contribution.d.ts +15 -0
  11. package/lib/browser/copilot-frontend-application-contribution.d.ts.map +1 -0
  12. package/lib/browser/copilot-frontend-application-contribution.js +90 -0
  13. package/lib/browser/copilot-frontend-application-contribution.js.map +1 -0
  14. package/lib/browser/copilot-frontend-module.d.ts +5 -0
  15. package/lib/browser/copilot-frontend-module.d.ts.map +1 -0
  16. package/lib/browser/copilot-frontend-module.js +69 -0
  17. package/lib/browser/copilot-frontend-module.js.map +1 -0
  18. package/lib/browser/copilot-status-bar-contribution.d.ts +18 -0
  19. package/lib/browser/copilot-status-bar-contribution.d.ts.map +1 -0
  20. package/lib/browser/copilot-status-bar-contribution.js +92 -0
  21. package/lib/browser/copilot-status-bar-contribution.js.map +1 -0
  22. package/lib/browser/index.d.ts +5 -0
  23. package/lib/browser/index.d.ts.map +1 -0
  24. package/lib/browser/index.js +23 -0
  25. package/lib/browser/index.js.map +1 -0
  26. package/lib/common/copilot-auth-service.d.ts +77 -0
  27. package/lib/common/copilot-auth-service.d.ts.map +1 -0
  28. package/lib/common/copilot-auth-service.js +22 -0
  29. package/lib/common/copilot-auth-service.js.map +1 -0
  30. package/lib/common/copilot-language-models-manager.d.ts +41 -0
  31. package/lib/common/copilot-language-models-manager.d.ts.map +1 -0
  32. package/lib/common/copilot-language-models-manager.js +22 -0
  33. package/lib/common/copilot-language-models-manager.js.map +1 -0
  34. package/lib/common/copilot-preferences.d.ts +5 -0
  35. package/lib/common/copilot-preferences.d.ts.map +1 -0
  36. package/lib/common/copilot-preferences.js +52 -0
  37. package/lib/common/copilot-preferences.js.map +1 -0
  38. package/lib/common/index.d.ts +4 -0
  39. package/lib/common/index.d.ts.map +1 -0
  40. package/lib/common/index.js +22 -0
  41. package/lib/common/index.js.map +1 -0
  42. package/lib/node/copilot-auth-service-impl.d.ts +28 -0
  43. package/lib/node/copilot-auth-service-impl.d.ts.map +1 -0
  44. package/lib/node/copilot-auth-service-impl.js +229 -0
  45. package/lib/node/copilot-auth-service-impl.js.map +1 -0
  46. package/lib/node/copilot-backend-module.d.ts +4 -0
  47. package/lib/node/copilot-backend-module.d.ts.map +1 -0
  48. package/lib/node/copilot-backend-module.js +39 -0
  49. package/lib/node/copilot-backend-module.js.map +1 -0
  50. package/lib/node/copilot-language-model.d.ts +33 -0
  51. package/lib/node/copilot-language-model.d.ts.map +1 -0
  52. package/lib/node/copilot-language-model.js +217 -0
  53. package/lib/node/copilot-language-model.js.map +1 -0
  54. package/lib/node/copilot-language-models-manager-impl.d.ts +23 -0
  55. package/lib/node/copilot-language-models-manager-impl.d.ts.map +1 -0
  56. package/lib/node/copilot-language-models-manager-impl.js +113 -0
  57. package/lib/node/copilot-language-models-manager-impl.js.map +1 -0
  58. package/lib/node/index.d.ts +4 -0
  59. package/lib/node/index.d.ts.map +1 -0
  60. package/lib/node/index.js +22 -0
  61. package/lib/node/index.js.map +1 -0
  62. package/lib/package.spec.d.ts +1 -0
  63. package/lib/package.spec.d.ts.map +1 -0
  64. package/lib/package.spec.js +26 -0
  65. package/lib/package.spec.js.map +1 -0
  66. package/package.json +52 -0
  67. package/src/browser/copilot-auth-dialog.tsx +326 -0
  68. package/src/browser/copilot-command-contribution.ts +93 -0
  69. package/src/browser/copilot-frontend-application-contribution.ts +89 -0
  70. package/src/browser/copilot-frontend-module.ts +86 -0
  71. package/src/browser/copilot-status-bar-contribution.ts +88 -0
  72. package/src/browser/index.ts +20 -0
  73. package/src/browser/style/index.css +167 -0
  74. package/src/common/copilot-auth-service.ts +103 -0
  75. package/src/common/copilot-language-models-manager.ts +59 -0
  76. package/src/common/copilot-preferences.ts +53 -0
  77. package/src/common/index.ts +19 -0
  78. package/src/node/copilot-auth-service-impl.ts +274 -0
  79. package/src/node/copilot-backend-module.ts +58 -0
  80. package/src/node/copilot-language-model.ts +262 -0
  81. package/src/node/copilot-language-models-manager-impl.ts +118 -0
  82. package/src/node/index.ts +19 -0
  83. package/src/package.spec.ts +27 -0
@@ -0,0 +1,19 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ export * from './copilot-language-models-manager';
18
+ export * from './copilot-auth-service';
19
+ export * from './copilot-preferences';
@@ -0,0 +1,274 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { Emitter, Event } from '@theia/core';
19
+ import { KeyStoreService } from '@theia/core/lib/common/key-store';
20
+ import {
21
+ CopilotAuthService,
22
+ CopilotAuthServiceClient,
23
+ CopilotAuthState,
24
+ DeviceCodeResponse
25
+ } from '../common/copilot-auth-service';
26
+
27
+ const COPILOT_CLIENT_ID = 'Iv23ctNZvWb5IGBKdyPY';
28
+ const COPILOT_SCOPE = 'read:user';
29
+ const COPILOT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
30
+ const KEYSTORE_SERVICE = 'theia-copilot-auth';
31
+ const KEYSTORE_ACCOUNT = 'github-copilot';
32
+ const USER_AGENT = 'Theia-Copilot/1.0.0';
33
+
34
+ /**
35
+ * Maximum number of polling attempts for token retrieval.
36
+ * With a default 5-second interval, this allows approximately 5 minutes of polling.
37
+ */
38
+ const MAX_POLLING_ATTEMPTS = 60;
39
+
40
+ interface StoredCredentials {
41
+ accessToken: string;
42
+ accountLabel?: string;
43
+ enterpriseUrl?: string;
44
+ }
45
+
46
+ /**
47
+ * Backend implementation of the GitHub Copilot OAuth Device Flow authentication service.
48
+ * Handles device code generation, token polling, and credential storage.
49
+ */
50
+ @injectable()
51
+ export class CopilotAuthServiceImpl implements CopilotAuthService {
52
+
53
+ @inject(KeyStoreService)
54
+ protected readonly keyStoreService: KeyStoreService;
55
+
56
+ protected client: CopilotAuthServiceClient | undefined;
57
+ protected cachedState: CopilotAuthState | undefined;
58
+
59
+ protected readonly onAuthStateChangedEmitter = new Emitter<CopilotAuthState>();
60
+ readonly onAuthStateChanged: Event<CopilotAuthState> = this.onAuthStateChangedEmitter.event;
61
+
62
+ setClient(client: CopilotAuthServiceClient | undefined): void {
63
+ this.client = client;
64
+ }
65
+
66
+ protected getOAuthEndpoints(enterpriseUrl?: string): { deviceCodeUrl: string; accessTokenUrl: string } {
67
+ if (enterpriseUrl) {
68
+ const domain = enterpriseUrl
69
+ .replace(/^https?:\/\//, '')
70
+ .replace(/\/$/, '');
71
+ return {
72
+ deviceCodeUrl: `https://${domain}/login/device/code`,
73
+ accessTokenUrl: `https://${domain}/login/oauth/access_token`
74
+ };
75
+ }
76
+ return {
77
+ deviceCodeUrl: 'https://github.com/login/device/code',
78
+ accessTokenUrl: 'https://github.com/login/oauth/access_token'
79
+ };
80
+ }
81
+
82
+ async initiateDeviceFlow(enterpriseUrl?: string): Promise<DeviceCodeResponse> {
83
+ const endpoints = this.getOAuthEndpoints(enterpriseUrl);
84
+
85
+ const response = await fetch(endpoints.deviceCodeUrl, {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Accept': 'application/json',
89
+ 'Content-Type': 'application/json',
90
+ 'User-Agent': USER_AGENT
91
+ },
92
+ body: JSON.stringify({
93
+ client_id: COPILOT_CLIENT_ID,
94
+ scope: COPILOT_SCOPE
95
+ })
96
+ });
97
+
98
+ if (!response.ok) {
99
+ const errorText = await response.text();
100
+ throw new Error(`Failed to initiate device authorization: ${response.status} - ${errorText}`);
101
+ }
102
+
103
+ const data = await response.json() as DeviceCodeResponse;
104
+ return data;
105
+ }
106
+
107
+ async pollForToken(deviceCode: string, interval: number, enterpriseUrl?: string): Promise<boolean> {
108
+ const endpoints = this.getOAuthEndpoints(enterpriseUrl);
109
+ let attempts = 0;
110
+
111
+ while (attempts < MAX_POLLING_ATTEMPTS) {
112
+ await this.delay(interval * 1000);
113
+ attempts++;
114
+
115
+ const response = await fetch(endpoints.accessTokenUrl, {
116
+ method: 'POST',
117
+ headers: {
118
+ 'Accept': 'application/json',
119
+ 'Content-Type': 'application/json',
120
+ 'User-Agent': USER_AGENT
121
+ },
122
+ body: JSON.stringify({
123
+ client_id: COPILOT_CLIENT_ID,
124
+ device_code: deviceCode,
125
+ grant_type: COPILOT_GRANT_TYPE
126
+ })
127
+ });
128
+
129
+ if (!response.ok) {
130
+ console.error(`Token request failed: ${response.status}`);
131
+ continue;
132
+ }
133
+
134
+ const data = await response.json() as {
135
+ access_token?: string;
136
+ error?: string;
137
+ error_description?: string;
138
+ };
139
+
140
+ if (data.access_token) {
141
+ // Get user info for account label
142
+ const accountLabel = await this.fetchAccountLabel(data.access_token, enterpriseUrl);
143
+
144
+ // Store credentials
145
+ const credentials: StoredCredentials = {
146
+ accessToken: data.access_token,
147
+ accountLabel,
148
+ enterpriseUrl
149
+ };
150
+
151
+ await this.keyStoreService.setPassword(
152
+ KEYSTORE_SERVICE,
153
+ KEYSTORE_ACCOUNT,
154
+ JSON.stringify(credentials)
155
+ );
156
+
157
+ // Update cached state and notify
158
+ const newState: CopilotAuthState = {
159
+ isAuthenticated: true,
160
+ accountLabel,
161
+ enterpriseUrl
162
+ };
163
+ this.updateAuthState(newState);
164
+
165
+ return true;
166
+ }
167
+
168
+ if (data.error === 'authorization_pending') {
169
+ // User hasn't authorized yet, continue polling
170
+ continue;
171
+ }
172
+
173
+ if (data.error === 'slow_down') {
174
+ // Increase polling interval
175
+ interval += 5;
176
+ continue;
177
+ }
178
+
179
+ if (data.error === 'expired_token' || data.error === 'access_denied') {
180
+ console.error(`Authorization failed: ${data.error} - ${data.error_description}`);
181
+ return false;
182
+ }
183
+
184
+ if (data.error) {
185
+ console.error(`Unexpected error: ${data.error} - ${data.error_description}`);
186
+ return false;
187
+ }
188
+ }
189
+
190
+ return false;
191
+ }
192
+
193
+ protected async fetchAccountLabel(accessToken: string, enterpriseUrl?: string): Promise<string | undefined> {
194
+ try {
195
+ const apiBaseUrl = enterpriseUrl
196
+ ? `https://${enterpriseUrl.replace(/^https?:\/\//, '').replace(/\/$/, '')}/api/v3`
197
+ : 'https://api.github.com';
198
+
199
+ const response = await fetch(`${apiBaseUrl}/user`, {
200
+ headers: {
201
+ 'Authorization': `Bearer ${accessToken}`,
202
+ 'User-Agent': USER_AGENT,
203
+ 'Accept': 'application/vnd.github.v3+json'
204
+ }
205
+ });
206
+
207
+ if (response.ok) {
208
+ const userData = await response.json() as { login?: string };
209
+ return userData.login;
210
+ }
211
+ } catch (error) {
212
+ console.warn('Failed to fetch GitHub user info:', error);
213
+ }
214
+ return undefined;
215
+ }
216
+
217
+ async getAuthState(): Promise<CopilotAuthState> {
218
+ if (this.cachedState) {
219
+ return this.cachedState;
220
+ }
221
+
222
+ try {
223
+ const stored = await this.keyStoreService.getPassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
224
+ if (stored) {
225
+ const credentials: StoredCredentials = JSON.parse(stored);
226
+ this.cachedState = {
227
+ isAuthenticated: true,
228
+ accountLabel: credentials.accountLabel,
229
+ enterpriseUrl: credentials.enterpriseUrl
230
+ };
231
+ return this.cachedState;
232
+ }
233
+ } catch (error) {
234
+ console.warn('Failed to retrieve Copilot credentials:', error);
235
+ }
236
+
237
+ this.cachedState = { isAuthenticated: false };
238
+ return this.cachedState;
239
+ }
240
+
241
+ async getAccessToken(): Promise<string | undefined> {
242
+ try {
243
+ const stored = await this.keyStoreService.getPassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
244
+ if (stored) {
245
+ const credentials: StoredCredentials = JSON.parse(stored);
246
+ return credentials.accessToken;
247
+ }
248
+ } catch (error) {
249
+ console.warn('Failed to retrieve Copilot access token:', error);
250
+ }
251
+ return undefined;
252
+ }
253
+
254
+ async signOut(): Promise<void> {
255
+ try {
256
+ await this.keyStoreService.deletePassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
257
+ } catch (error) {
258
+ console.warn('Failed to delete Copilot credentials:', error);
259
+ }
260
+
261
+ const newState: CopilotAuthState = { isAuthenticated: false };
262
+ this.updateAuthState(newState);
263
+ }
264
+
265
+ protected updateAuthState(state: CopilotAuthState): void {
266
+ this.cachedState = state;
267
+ this.onAuthStateChangedEmitter.fire(state);
268
+ this.client?.onAuthStateChanged(state);
269
+ }
270
+
271
+ protected delay(ms: number): Promise<void> {
272
+ return new Promise(resolve => setTimeout(resolve, ms));
273
+ }
274
+ }
@@ -0,0 +1,58 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ContainerModule } from '@theia/core/shared/inversify';
18
+ import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
19
+ import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module';
20
+ import {
21
+ CopilotLanguageModelsManager,
22
+ COPILOT_LANGUAGE_MODELS_MANAGER_PATH,
23
+ CopilotAuthService,
24
+ COPILOT_AUTH_SERVICE_PATH,
25
+ CopilotAuthServiceClient
26
+ } from '../common';
27
+ import { CopilotLanguageModelsManagerImpl } from './copilot-language-models-manager-impl';
28
+ import { CopilotAuthServiceImpl } from './copilot-auth-service-impl';
29
+
30
+ const copilotConnectionModule = ConnectionContainerModule.create(({ bind }) => {
31
+ bind(CopilotAuthServiceImpl).toSelf().inSingletonScope();
32
+ bind(CopilotAuthService).toService(CopilotAuthServiceImpl);
33
+
34
+ bind(CopilotLanguageModelsManagerImpl).toSelf().inSingletonScope();
35
+ bind(CopilotLanguageModelsManager).toService(CopilotLanguageModelsManagerImpl);
36
+
37
+ bind(ConnectionHandler).toDynamicValue(ctx =>
38
+ new RpcConnectionHandler<CopilotAuthServiceClient>(
39
+ COPILOT_AUTH_SERVICE_PATH,
40
+ client => {
41
+ const authService = ctx.container.get<CopilotAuthServiceImpl>(CopilotAuthService);
42
+ authService.setClient(client);
43
+ return authService;
44
+ }
45
+ )
46
+ ).inSingletonScope();
47
+
48
+ bind(ConnectionHandler).toDynamicValue(ctx =>
49
+ new RpcConnectionHandler(
50
+ COPILOT_LANGUAGE_MODELS_MANAGER_PATH,
51
+ () => ctx.container.get(CopilotLanguageModelsManager)
52
+ )
53
+ ).inSingletonScope();
54
+ });
55
+
56
+ export default new ContainerModule(bind => {
57
+ bind(ConnectionContainerModule).toConstantValue(copilotConnectionModule);
58
+ });
@@ -0,0 +1,262 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import {
18
+ ImageContent,
19
+ LanguageModel,
20
+ LanguageModelMessage,
21
+ LanguageModelParsedResponse,
22
+ LanguageModelRequest,
23
+ LanguageModelResponse,
24
+ LanguageModelStatus,
25
+ LanguageModelTextResponse,
26
+ TokenUsageService,
27
+ UserRequest
28
+ } from '@theia/ai-core';
29
+ import { CancellationToken } from '@theia/core';
30
+ import OpenAI from 'openai';
31
+ import { RunnableToolFunctionWithoutParse } from 'openai/lib/RunnableFunction';
32
+ import { ChatCompletionMessageParam } from 'openai/resources';
33
+ import { StreamingAsyncIterator } from '@theia/ai-openai/lib/node/openai-streaming-iterator';
34
+ import { COPILOT_PROVIDER_ID } from '../common';
35
+ import type { RunnerOptions } from 'openai/lib/AbstractChatCompletionRunner';
36
+ import type { ChatCompletionStream } from 'openai/lib/ChatCompletionStream';
37
+
38
+ const COPILOT_API_BASE_URL = 'https://api.githubcopilot.com';
39
+ const USER_AGENT = 'Theia-Copilot/1.0.0';
40
+
41
+ /**
42
+ * Language model implementation for GitHub Copilot.
43
+ * Uses the OpenAI SDK to communicate with the Copilot API.
44
+ */
45
+ export class CopilotLanguageModel implements LanguageModel {
46
+
47
+ protected runnerOptions: RunnerOptions = {
48
+ maxChatCompletions: 100
49
+ };
50
+
51
+ constructor(
52
+ public readonly id: string,
53
+ public model: string,
54
+ public status: LanguageModelStatus,
55
+ public enableStreaming: boolean,
56
+ public supportsStructuredOutput: boolean,
57
+ public maxRetries: number,
58
+ protected readonly accessTokenProvider: () => Promise<string | undefined>,
59
+ protected readonly enterpriseUrlProvider: () => string | undefined,
60
+ protected readonly tokenUsageService?: TokenUsageService
61
+ ) { }
62
+
63
+ protected getSettings(request: LanguageModelRequest): Record<string, unknown> {
64
+ return request.settings ?? {};
65
+ }
66
+
67
+ async request(request: UserRequest, cancellationToken?: CancellationToken): Promise<LanguageModelResponse> {
68
+ const openai = await this.initializeCopilotClient();
69
+
70
+ if (request.response_format?.type === 'json_schema' && this.supportsStructuredOutput) {
71
+ return this.handleStructuredOutputRequest(openai, request);
72
+ }
73
+
74
+ const settings = this.getSettings(request);
75
+
76
+ if (!this.enableStreaming || (typeof settings.stream === 'boolean' && !settings.stream)) {
77
+ return this.handleNonStreamingRequest(openai, request);
78
+ }
79
+
80
+ if (cancellationToken?.isCancellationRequested) {
81
+ return { text: '' };
82
+ }
83
+
84
+ if (this.id.startsWith(`${COPILOT_PROVIDER_ID}/`)) {
85
+ settings['stream_options'] = { include_usage: true };
86
+ }
87
+
88
+ let runner: ChatCompletionStream;
89
+ const tools = this.createTools(request);
90
+
91
+ if (tools) {
92
+ runner = openai.chat.completions.runTools({
93
+ model: this.model,
94
+ messages: this.processMessages(request.messages),
95
+ stream: true,
96
+ tools: tools,
97
+ tool_choice: 'auto',
98
+ ...settings
99
+ }, {
100
+ ...this.runnerOptions,
101
+ maxRetries: this.maxRetries
102
+ });
103
+ } else {
104
+ runner = openai.chat.completions.stream({
105
+ model: this.model,
106
+ messages: this.processMessages(request.messages),
107
+ stream: true,
108
+ ...settings
109
+ });
110
+ }
111
+
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ return { stream: new StreamingAsyncIterator(runner as any, request.requestId, cancellationToken, this.tokenUsageService, this.id) };
114
+ }
115
+
116
+ protected async handleNonStreamingRequest(openai: OpenAI, request: UserRequest): Promise<LanguageModelTextResponse> {
117
+ const settings = this.getSettings(request);
118
+ const response = await openai.chat.completions.create({
119
+ model: this.model,
120
+ messages: this.processMessages(request.messages),
121
+ ...settings
122
+ });
123
+
124
+ const message = response.choices[0].message;
125
+
126
+ if (this.tokenUsageService && response.usage) {
127
+ await this.tokenUsageService.recordTokenUsage(
128
+ this.id,
129
+ {
130
+ inputTokens: response.usage.prompt_tokens,
131
+ outputTokens: response.usage.completion_tokens,
132
+ requestId: request.requestId
133
+ }
134
+ );
135
+ }
136
+
137
+ return {
138
+ text: message.content ?? ''
139
+ };
140
+ }
141
+
142
+ protected async handleStructuredOutputRequest(openai: OpenAI, request: UserRequest): Promise<LanguageModelParsedResponse> {
143
+ const settings = this.getSettings(request);
144
+ const result = await openai.chat.completions.parse({
145
+ model: this.model,
146
+ messages: this.processMessages(request.messages),
147
+ response_format: request.response_format,
148
+ ...settings
149
+ });
150
+
151
+ const message = result.choices[0].message;
152
+ if (message.refusal || message.parsed === undefined) {
153
+ console.error('Error in Copilot chat completion:', JSON.stringify(message));
154
+ }
155
+
156
+ if (this.tokenUsageService && result.usage) {
157
+ await this.tokenUsageService.recordTokenUsage(
158
+ this.id,
159
+ {
160
+ inputTokens: result.usage.prompt_tokens,
161
+ outputTokens: result.usage.completion_tokens,
162
+ requestId: request.requestId
163
+ }
164
+ );
165
+ }
166
+
167
+ return {
168
+ content: message.content ?? '',
169
+ parsed: message.parsed
170
+ };
171
+ }
172
+
173
+ protected createTools(request: LanguageModelRequest): RunnableToolFunctionWithoutParse[] | undefined {
174
+ return request.tools?.map(tool => ({
175
+ type: 'function',
176
+ function: {
177
+ name: tool.name,
178
+ description: tool.description,
179
+ parameters: tool.parameters,
180
+ function: (args_string: string) => tool.handler(args_string)
181
+ }
182
+ } as RunnableToolFunctionWithoutParse));
183
+ }
184
+
185
+ protected async initializeCopilotClient(): Promise<OpenAI> {
186
+ const accessToken = await this.accessTokenProvider();
187
+ if (!accessToken) {
188
+ throw new Error('Not authenticated with GitHub Copilot. Please sign in first.');
189
+ }
190
+
191
+ const enterpriseUrl = this.enterpriseUrlProvider();
192
+ const baseURL = enterpriseUrl
193
+ ? `https://copilot-api.${enterpriseUrl.replace(/^https?:\/\//, '').replace(/\/$/, '')}`
194
+ : COPILOT_API_BASE_URL;
195
+
196
+ return new OpenAI({
197
+ apiKey: accessToken,
198
+ baseURL,
199
+ defaultHeaders: {
200
+ 'User-Agent': USER_AGENT,
201
+ 'Openai-Intent': 'conversation-edits',
202
+ 'X-Initiator': 'user'
203
+ }
204
+ });
205
+ }
206
+
207
+ protected processMessages(messages: LanguageModelMessage[]): ChatCompletionMessageParam[] {
208
+ return messages.filter(m => m.type !== 'thinking').map(m => this.toOpenAIMessage(m));
209
+ }
210
+
211
+ protected toOpenAIMessage(message: LanguageModelMessage): ChatCompletionMessageParam {
212
+ if (LanguageModelMessage.isTextMessage(message)) {
213
+ return {
214
+ role: this.toOpenAiRole(message),
215
+ content: message.text
216
+ };
217
+ }
218
+ if (LanguageModelMessage.isToolUseMessage(message)) {
219
+ return {
220
+ role: 'assistant',
221
+ tool_calls: [{
222
+ id: message.id,
223
+ function: {
224
+ name: message.name,
225
+ arguments: JSON.stringify(message.input)
226
+ },
227
+ type: 'function'
228
+ }]
229
+ };
230
+ }
231
+ if (LanguageModelMessage.isToolResultMessage(message)) {
232
+ return {
233
+ role: 'tool',
234
+ tool_call_id: message.tool_use_id,
235
+ content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content)
236
+ };
237
+ }
238
+ if (LanguageModelMessage.isImageMessage(message) && message.actor === 'user') {
239
+ return {
240
+ role: 'user',
241
+ content: [{
242
+ type: 'image_url',
243
+ image_url: {
244
+ url: ImageContent.isBase64(message.image)
245
+ ? `data:${message.image.mimeType};base64,${message.image.base64data}`
246
+ : message.image.url
247
+ }
248
+ }]
249
+ };
250
+ }
251
+ throw new Error(`Unknown message type: '${JSON.stringify(message)}'`);
252
+ }
253
+
254
+ protected toOpenAiRole(message: LanguageModelMessage): 'developer' | 'user' | 'assistant' | 'system' {
255
+ if (message.actor === 'system') {
256
+ return 'developer';
257
+ } else if (message.actor === 'ai') {
258
+ return 'assistant';
259
+ }
260
+ return 'user';
261
+ }
262
+ }