@theia/ai-copilot 1.71.0-next.8 → 1.71.0

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 (57) hide show
  1. package/lib/browser/copilot-auth-dialog-messages.d.ts +19 -0
  2. package/lib/browser/copilot-auth-dialog-messages.d.ts.map +1 -0
  3. package/lib/browser/copilot-auth-dialog-messages.js +36 -0
  4. package/lib/browser/copilot-auth-dialog-messages.js.map +1 -0
  5. package/lib/browser/copilot-auth-dialog.d.ts +2 -0
  6. package/lib/browser/copilot-auth-dialog.d.ts.map +1 -1
  7. package/lib/browser/copilot-auth-dialog.js +7 -3
  8. package/lib/browser/copilot-auth-dialog.js.map +1 -1
  9. package/lib/browser/copilot-frontend-module.d.ts.map +1 -1
  10. package/lib/browser/copilot-frontend-module.js +6 -3
  11. package/lib/browser/copilot-frontend-module.js.map +1 -1
  12. package/lib/browser/index.d.ts +1 -0
  13. package/lib/browser/index.d.ts.map +1 -1
  14. package/lib/browser/index.js +1 -0
  15. package/lib/browser/index.js.map +1 -1
  16. package/lib/common/copilot-language-models-manager.d.ts +0 -1
  17. package/lib/common/copilot-language-models-manager.d.ts.map +1 -1
  18. package/lib/common/copilot-language-models-manager.js +1 -2
  19. package/lib/common/copilot-language-models-manager.js.map +1 -1
  20. package/lib/common/copilot-oauth-config.d.ts +33 -0
  21. package/lib/common/copilot-oauth-config.d.ts.map +1 -0
  22. package/lib/common/copilot-oauth-config.js +29 -0
  23. package/lib/common/copilot-oauth-config.js.map +1 -0
  24. package/lib/common/index.d.ts +1 -0
  25. package/lib/common/index.d.ts.map +1 -1
  26. package/lib/common/index.js +1 -0
  27. package/lib/common/index.js.map +1 -1
  28. package/lib/node/copilot-auth-service-impl.d.ts +2 -0
  29. package/lib/node/copilot-auth-service-impl.d.ts.map +1 -1
  30. package/lib/node/copilot-auth-service-impl.js +15 -14
  31. package/lib/node/copilot-auth-service-impl.js.map +1 -1
  32. package/lib/node/copilot-auth-service-impl.spec.js +46 -0
  33. package/lib/node/copilot-auth-service-impl.spec.js.map +1 -1
  34. package/lib/node/copilot-backend-module.d.ts.map +1 -1
  35. package/lib/node/copilot-backend-module.js +2 -0
  36. package/lib/node/copilot-backend-module.js.map +1 -1
  37. package/lib/node/copilot-language-model.d.ts +3 -3
  38. package/lib/node/copilot-language-model.d.ts.map +1 -1
  39. package/lib/node/copilot-language-model.js +14 -20
  40. package/lib/node/copilot-language-model.js.map +1 -1
  41. package/lib/node/copilot-language-models-manager-impl.d.ts +3 -2
  42. package/lib/node/copilot-language-models-manager-impl.d.ts.map +1 -1
  43. package/lib/node/copilot-language-models-manager-impl.js +7 -6
  44. package/lib/node/copilot-language-models-manager-impl.js.map +1 -1
  45. package/package.json +7 -7
  46. package/src/browser/copilot-auth-dialog-messages.ts +55 -0
  47. package/src/browser/copilot-auth-dialog.tsx +6 -5
  48. package/src/browser/copilot-frontend-module.ts +7 -4
  49. package/src/browser/index.ts +1 -0
  50. package/src/common/copilot-language-models-manager.ts +0 -1
  51. package/src/common/copilot-oauth-config.ts +55 -0
  52. package/src/common/index.ts +1 -0
  53. package/src/node/copilot-auth-service-impl.spec.ts +59 -0
  54. package/src/node/copilot-auth-service-impl.ts +15 -15
  55. package/src/node/copilot-backend-module.ts +2 -0
  56. package/src/node/copilot-language-model.ts +14 -29
  57. package/src/node/copilot-language-models-manager-impl.ts +8 -7
@@ -18,6 +18,7 @@ import { expect } from 'chai';
18
18
  import * as sinon from 'sinon';
19
19
  import { Container } from '@theia/core/shared/inversify';
20
20
  import { KeyStoreService } from '@theia/core/lib/common/key-store';
21
+ import { CopilotOAuthConfig, DEFAULT_COPILOT_OAUTH_CONFIG } from '../common/copilot-oauth-config';
21
22
  import { CopilotAuthServiceImpl } from './copilot-auth-service-impl';
22
23
 
23
24
  describe('CopilotAuthServiceImpl', () => {
@@ -40,6 +41,7 @@ describe('CopilotAuthServiceImpl', () => {
40
41
  };
41
42
 
42
43
  container.bind(KeyStoreService).toConstantValue(keyStoreService);
44
+ container.bind(CopilotOAuthConfig).toConstantValue(DEFAULT_COPILOT_OAUTH_CONFIG);
43
45
  container.bind(CopilotAuthServiceImpl).toSelf().inSingletonScope();
44
46
 
45
47
  authService = container.get(CopilotAuthServiceImpl);
@@ -183,3 +185,60 @@ describe('CopilotAuthServiceImpl', () => {
183
185
  });
184
186
  });
185
187
  });
188
+
189
+ describe('CopilotAuthServiceImpl with custom CopilotOAuthConfig', () => {
190
+
191
+ const customConfig: CopilotOAuthConfig = {
192
+ clientId: 'CustomClientId',
193
+ userAgent: 'MyApp/1.0.0',
194
+ keystoreService: 'myapp-copilot',
195
+ keystoreAccount: 'myapp-github'
196
+ };
197
+
198
+ let authService: CopilotAuthServiceImpl;
199
+ let getPasswordStub: sinon.SinonStub;
200
+ let deletePasswordStub: sinon.SinonStub;
201
+
202
+ beforeEach(() => {
203
+ const container = new Container();
204
+
205
+ getPasswordStub = sinon.stub().resolves(undefined);
206
+ deletePasswordStub = sinon.stub().resolves(true);
207
+ const keyStoreService: KeyStoreService = {
208
+ setPassword: sinon.stub().resolves() as KeyStoreService['setPassword'],
209
+ getPassword: getPasswordStub as KeyStoreService['getPassword'],
210
+ deletePassword: deletePasswordStub as KeyStoreService['deletePassword'],
211
+ findPassword: sinon.stub().resolves(undefined) as KeyStoreService['findPassword'],
212
+ findCredentials: sinon.stub().resolves([]) as KeyStoreService['findCredentials'],
213
+ keys: sinon.stub().resolves([]) as KeyStoreService['keys']
214
+ };
215
+
216
+ container.bind(KeyStoreService).toConstantValue(keyStoreService);
217
+ container.bind(CopilotOAuthConfig).toConstantValue(customConfig);
218
+ container.bind(CopilotAuthServiceImpl).toSelf().inSingletonScope();
219
+
220
+ authService = container.get(CopilotAuthServiceImpl);
221
+ });
222
+
223
+ afterEach(() => {
224
+ sinon.restore();
225
+ });
226
+
227
+ it('should read credentials from the custom keystore service and account', async () => {
228
+ getPasswordStub.resolves(JSON.stringify({
229
+ accessToken: 'gho_custom_token',
230
+ accountLabel: 'customuser'
231
+ }));
232
+
233
+ const token = await authService.getAccessToken();
234
+
235
+ expect(token).to.equal('gho_custom_token');
236
+ expect(getPasswordStub.calledOnceWith(customConfig.keystoreService, customConfig.keystoreAccount)).to.be.true;
237
+ });
238
+
239
+ it('should delete credentials from the custom keystore service and account on sign out', async () => {
240
+ await authService.signOut();
241
+
242
+ expect(deletePasswordStub.calledOnceWith(customConfig.keystoreService, customConfig.keystoreAccount)).to.be.true;
243
+ });
244
+ });
@@ -23,13 +23,10 @@ import {
23
23
  CopilotAuthState,
24
24
  DeviceCodeResponse
25
25
  } from '../common/copilot-auth-service';
26
- import { COPILOT_USER_AGENT } from '../common';
26
+ import { CopilotOAuthConfig } from '../common/copilot-oauth-config';
27
27
 
28
- const COPILOT_CLIENT_ID = 'Ov23liS2vINy9VOAweyv'; // 'Theia Copilot OAuth Access' Client ID
29
28
  const COPILOT_SCOPE = 'read:user';
30
29
  const COPILOT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
31
- const KEYSTORE_SERVICE = 'theia-copilot-auth';
32
- const KEYSTORE_ACCOUNT = 'github-copilot';
33
30
 
34
31
  /**
35
32
  * Maximum number of polling attempts for token retrieval.
@@ -53,6 +50,9 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
53
50
  @inject(KeyStoreService)
54
51
  protected readonly keyStoreService: KeyStoreService;
55
52
 
53
+ @inject(CopilotOAuthConfig)
54
+ protected readonly oauthConfig: CopilotOAuthConfig;
55
+
56
56
  protected client: CopilotAuthServiceClient | undefined;
57
57
  protected cachedState: CopilotAuthState | undefined;
58
58
 
@@ -87,10 +87,10 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
87
87
  headers: {
88
88
  'Accept': 'application/json',
89
89
  'Content-Type': 'application/json',
90
- 'User-Agent': COPILOT_USER_AGENT
90
+ 'User-Agent': this.oauthConfig.userAgent
91
91
  },
92
92
  body: JSON.stringify({
93
- client_id: COPILOT_CLIENT_ID,
93
+ client_id: this.oauthConfig.clientId,
94
94
  scope: COPILOT_SCOPE
95
95
  })
96
96
  });
@@ -117,10 +117,10 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
117
117
  headers: {
118
118
  'Accept': 'application/json',
119
119
  'Content-Type': 'application/json',
120
- 'User-Agent': COPILOT_USER_AGENT
120
+ 'User-Agent': this.oauthConfig.userAgent
121
121
  },
122
122
  body: JSON.stringify({
123
- client_id: COPILOT_CLIENT_ID,
123
+ client_id: this.oauthConfig.clientId,
124
124
  device_code: deviceCode,
125
125
  grant_type: COPILOT_GRANT_TYPE
126
126
  })
@@ -149,8 +149,8 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
149
149
  };
150
150
 
151
151
  await this.keyStoreService.setPassword(
152
- KEYSTORE_SERVICE,
153
- KEYSTORE_ACCOUNT,
152
+ this.oauthConfig.keystoreService,
153
+ this.oauthConfig.keystoreAccount,
154
154
  JSON.stringify(credentials)
155
155
  );
156
156
 
@@ -199,7 +199,7 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
199
199
  const response = await fetch(`${apiBaseUrl}/user`, {
200
200
  headers: {
201
201
  'Authorization': `Bearer ${accessToken}`,
202
- 'User-Agent': COPILOT_USER_AGENT,
202
+ 'User-Agent': this.oauthConfig.userAgent,
203
203
  'Accept': 'application/vnd.github.v3+json'
204
204
  }
205
205
  });
@@ -220,13 +220,13 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
220
220
  }
221
221
 
222
222
  try {
223
- const stored = await this.keyStoreService.getPassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
223
+ const stored = await this.keyStoreService.getPassword(this.oauthConfig.keystoreService, this.oauthConfig.keystoreAccount);
224
224
  if (stored) {
225
225
  const credentials: StoredCredentials = JSON.parse(stored);
226
226
  // Tokens from the current OAuth App start with 'gho_'; other prefixes (e.g. 'ghu_') indicate a token from the previous GitHub App (Iv-prefixed client ID).
227
227
  if (!credentials.accessToken.startsWith('gho_')) {
228
228
  console.info('Copilot: clearing outdated GitHub App token. Please sign in again.');
229
- await this.keyStoreService.deletePassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
229
+ await this.keyStoreService.deletePassword(this.oauthConfig.keystoreService, this.oauthConfig.keystoreAccount);
230
230
  this.cachedState = { isAuthenticated: false, migrationRequired: true };
231
231
  return this.cachedState;
232
232
  }
@@ -247,7 +247,7 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
247
247
 
248
248
  async getAccessToken(): Promise<string | undefined> {
249
249
  try {
250
- const stored = await this.keyStoreService.getPassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
250
+ const stored = await this.keyStoreService.getPassword(this.oauthConfig.keystoreService, this.oauthConfig.keystoreAccount);
251
251
  if (stored) {
252
252
  const credentials: StoredCredentials = JSON.parse(stored);
253
253
  return credentials.accessToken;
@@ -260,7 +260,7 @@ export class CopilotAuthServiceImpl implements CopilotAuthService {
260
260
 
261
261
  async signOut(): Promise<void> {
262
262
  try {
263
- await this.keyStoreService.deletePassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
263
+ await this.keyStoreService.deletePassword(this.oauthConfig.keystoreService, this.oauthConfig.keystoreAccount);
264
264
  } catch (error) {
265
265
  console.warn('Failed to delete Copilot credentials:', error);
266
266
  }
@@ -24,6 +24,7 @@ import {
24
24
  COPILOT_AUTH_SERVICE_PATH,
25
25
  CopilotAuthServiceClient
26
26
  } from '../common';
27
+ import { CopilotOAuthConfig, DEFAULT_COPILOT_OAUTH_CONFIG } from '../common/copilot-oauth-config';
27
28
  import { CopilotLanguageModelsManagerImpl } from './copilot-language-models-manager-impl';
28
29
  import { CopilotAuthServiceImpl } from './copilot-auth-service-impl';
29
30
 
@@ -54,5 +55,6 @@ const copilotConnectionModule = ConnectionContainerModule.create(({ bind }) => {
54
55
  });
55
56
 
56
57
  export default new ContainerModule(bind => {
58
+ bind(CopilotOAuthConfig).toConstantValue(DEFAULT_COPILOT_OAUTH_CONFIG);
57
59
  bind(ConnectionContainerModule).toConstantValue(copilotConnectionModule);
58
60
  });
@@ -23,7 +23,6 @@ import {
23
23
  LanguageModelResponse,
24
24
  LanguageModelStatus,
25
25
  LanguageModelTextResponse,
26
- TokenUsageService,
27
26
  UserRequest
28
27
  } from '@theia/ai-core';
29
28
  import { CancellationToken } from '@theia/core';
@@ -31,7 +30,7 @@ import OpenAI from 'openai';
31
30
  import { RunnableToolFunctionWithoutParse } from 'openai/lib/RunnableFunction';
32
31
  import { ChatCompletionMessageParam } from 'openai/resources';
33
32
  import { StreamingAsyncIterator } from '@theia/ai-openai/lib/node/openai-streaming-iterator';
34
- import { COPILOT_PROVIDER_ID, COPILOT_USER_AGENT, getCopilotApiBaseUrl } from '../common';
33
+ import { COPILOT_PROVIDER_ID, getCopilotApiBaseUrl } from '../common';
35
34
  import type { RunnerOptions } from 'openai/lib/AbstractChatCompletionRunner';
36
35
  import type { ChatCompletionStream } from 'openai/lib/ChatCompletionStream';
37
36
 
@@ -54,7 +53,7 @@ export class CopilotLanguageModel implements LanguageModel {
54
53
  public maxRetries: number,
55
54
  protected readonly accessTokenProvider: () => Promise<string | undefined>,
56
55
  protected readonly enterpriseUrlProvider: () => string | undefined,
57
- protected readonly tokenUsageService?: TokenUsageService
56
+ protected readonly userAgentProvider: () => string,
58
57
  ) { }
59
58
 
60
59
  protected getSettings(request: LanguageModelRequest): Record<string, unknown> {
@@ -107,7 +106,7 @@ export class CopilotLanguageModel implements LanguageModel {
107
106
  }
108
107
 
109
108
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
- return { stream: new StreamingAsyncIterator(runner as any, request.requestId, cancellationToken, this.tokenUsageService, this.id) };
109
+ return { stream: new StreamingAsyncIterator(runner as any, cancellationToken) };
111
110
  }
112
111
 
113
112
  protected async handleNonStreamingRequest(openai: OpenAI, request: UserRequest): Promise<LanguageModelTextResponse> {
@@ -120,19 +119,12 @@ export class CopilotLanguageModel implements LanguageModel {
120
119
 
121
120
  const message = response.choices[0].message;
122
121
 
123
- if (this.tokenUsageService && response.usage) {
124
- await this.tokenUsageService.recordTokenUsage(
125
- this.id,
126
- {
127
- inputTokens: response.usage.prompt_tokens,
128
- outputTokens: response.usage.completion_tokens,
129
- requestId: request.requestId
130
- }
131
- );
132
- }
133
-
134
122
  return {
135
- text: message.content ?? ''
123
+ text: message.content ?? '',
124
+ usage: response.usage ? {
125
+ input_tokens: response.usage.prompt_tokens,
126
+ output_tokens: response.usage.completion_tokens,
127
+ } : undefined
136
128
  };
137
129
  }
138
130
 
@@ -150,20 +142,13 @@ export class CopilotLanguageModel implements LanguageModel {
150
142
  console.error('Error in Copilot chat completion:', JSON.stringify(message));
151
143
  }
152
144
 
153
- if (this.tokenUsageService && result.usage) {
154
- await this.tokenUsageService.recordTokenUsage(
155
- this.id,
156
- {
157
- inputTokens: result.usage.prompt_tokens,
158
- outputTokens: result.usage.completion_tokens,
159
- requestId: request.requestId
160
- }
161
- );
162
- }
163
-
164
145
  return {
165
146
  content: message.content ?? '',
166
- parsed: message.parsed
147
+ parsed: message.parsed,
148
+ usage: result.usage ? {
149
+ input_tokens: result.usage.prompt_tokens,
150
+ output_tokens: result.usage.completion_tokens,
151
+ } : undefined
167
152
  };
168
153
  }
169
154
 
@@ -191,7 +176,7 @@ export class CopilotLanguageModel implements LanguageModel {
191
176
  apiKey: accessToken,
192
177
  baseURL,
193
178
  defaultHeaders: {
194
- 'User-Agent': COPILOT_USER_AGENT,
179
+ 'User-Agent': this.userAgentProvider(),
195
180
  'Openai-Intent': 'conversation-edits',
196
181
  'X-Initiator': 'user'
197
182
  }
@@ -14,10 +14,11 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { LanguageModelRegistry, LanguageModelStatus, TokenUsageService } from '@theia/ai-core';
17
+ import { LanguageModelRegistry, LanguageModelStatus } from '@theia/ai-core';
18
18
  import { Disposable, DisposableCollection } from '@theia/core';
19
19
  import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
20
- import { CopilotLanguageModelsManager, CopilotModelDescription, COPILOT_PROVIDER_ID, COPILOT_USER_AGENT, getCopilotApiBaseUrl } from '../common';
20
+ import { CopilotLanguageModelsManager, CopilotModelDescription, COPILOT_PROVIDER_ID, getCopilotApiBaseUrl } from '../common';
21
+ import { CopilotOAuthConfig } from '../common/copilot-oauth-config';
21
22
  import { CopilotLanguageModel } from './copilot-language-model';
22
23
  import { CopilotAuthServiceImpl } from './copilot-auth-service-impl';
23
24
 
@@ -31,12 +32,12 @@ export class CopilotLanguageModelsManagerImpl implements CopilotLanguageModelsMa
31
32
  @inject(LanguageModelRegistry)
32
33
  protected readonly languageModelRegistry: LanguageModelRegistry;
33
34
 
34
- @inject(TokenUsageService)
35
- protected readonly tokenUsageService: TokenUsageService;
36
-
37
35
  @inject(CopilotAuthServiceImpl)
38
36
  protected readonly authService: CopilotAuthServiceImpl;
39
37
 
38
+ @inject(CopilotOAuthConfig)
39
+ protected readonly oauthConfig: CopilotOAuthConfig;
40
+
40
41
  protected enterpriseUrl: string | undefined;
41
42
  protected readonly toDispose = new DisposableCollection();
42
43
 
@@ -92,7 +93,7 @@ export class CopilotLanguageModelsManagerImpl implements CopilotLanguageModelsMa
92
93
  modelDescription.maxRetries,
93
94
  () => this.authService.getAccessToken(),
94
95
  () => this.enterpriseUrl,
95
- this.tokenUsageService
96
+ () => this.oauthConfig.userAgent
96
97
  )
97
98
  ]);
98
99
  }
@@ -128,7 +129,7 @@ export class CopilotLanguageModelsManagerImpl implements CopilotLanguageModelsMa
128
129
  const response = await fetch(`${baseURL}/models`, {
129
130
  headers: {
130
131
  'Authorization': `Bearer ${accessToken}`,
131
- 'User-Agent': COPILOT_USER_AGENT,
132
+ 'User-Agent': this.oauthConfig.userAgent,
132
133
  'Accept': 'application/json'
133
134
  }
134
135
  });