@theia/ai-copilot 1.75.0 → 1.76.0-next.10

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 (150) hide show
  1. package/README.md +86 -14
  2. package/lib/browser/copilot-auth-dialog.d.ts.map +1 -1
  3. package/lib/browser/copilot-auth-dialog.js +12 -4
  4. package/lib/browser/copilot-auth-dialog.js.map +1 -1
  5. package/lib/browser/copilot-frontend-application-contribution.d.ts +15 -1
  6. package/lib/browser/copilot-frontend-application-contribution.d.ts.map +1 -1
  7. package/lib/browser/copilot-frontend-application-contribution.js +52 -24
  8. package/lib/browser/copilot-frontend-application-contribution.js.map +1 -1
  9. package/lib/common/copilot-auth-service.d.ts +27 -24
  10. package/lib/common/copilot-auth-service.d.ts.map +1 -1
  11. package/lib/common/copilot-language-models-manager.d.ts +3 -21
  12. package/lib/common/copilot-language-models-manager.d.ts.map +1 -1
  13. package/lib/common/copilot-language-models-manager.js +1 -13
  14. package/lib/common/copilot-language-models-manager.js.map +1 -1
  15. package/lib/common/copilot-preferences.d.ts +1 -0
  16. package/lib/common/copilot-preferences.d.ts.map +1 -1
  17. package/lib/common/copilot-preferences.js +24 -6
  18. package/lib/common/copilot-preferences.js.map +1 -1
  19. package/lib/common/index.d.ts +0 -1
  20. package/lib/common/index.d.ts.map +1 -1
  21. package/lib/common/index.js +0 -1
  22. package/lib/common/index.js.map +1 -1
  23. package/lib/node/copilot-auth-service-impl.d.ts +25 -16
  24. package/lib/node/copilot-auth-service-impl.d.ts.map +1 -1
  25. package/lib/node/copilot-auth-service-impl.js +67 -183
  26. package/lib/node/copilot-auth-service-impl.js.map +1 -1
  27. package/lib/node/copilot-auth-service-impl.spec.js +104 -168
  28. package/lib/node/copilot-auth-service-impl.spec.js.map +1 -1
  29. package/lib/node/copilot-backend-module.d.ts.map +1 -1
  30. package/lib/node/copilot-backend-module.js +10 -2
  31. package/lib/node/copilot-backend-module.js.map +1 -1
  32. package/lib/node/copilot-cli-auth-provider.d.ts +146 -0
  33. package/lib/node/copilot-cli-auth-provider.d.ts.map +1 -0
  34. package/lib/node/copilot-cli-auth-provider.js +430 -0
  35. package/lib/node/copilot-cli-auth-provider.js.map +1 -0
  36. package/lib/node/copilot-cli-auth-provider.spec.d.ts +2 -0
  37. package/lib/node/copilot-cli-auth-provider.spec.d.ts.map +1 -0
  38. package/lib/node/copilot-cli-auth-provider.spec.js +249 -0
  39. package/lib/node/copilot-cli-auth-provider.spec.js.map +1 -0
  40. package/lib/node/copilot-cli-locator.d.ts +120 -0
  41. package/lib/node/copilot-cli-locator.d.ts.map +1 -0
  42. package/lib/node/copilot-cli-locator.js +348 -0
  43. package/lib/node/copilot-cli-locator.js.map +1 -0
  44. package/lib/node/copilot-cli-locator.spec.d.ts +2 -0
  45. package/lib/node/copilot-cli-locator.spec.d.ts.map +1 -0
  46. package/lib/node/copilot-cli-locator.spec.js +327 -0
  47. package/lib/node/copilot-cli-locator.spec.js.map +1 -0
  48. package/lib/node/copilot-credential-store.d.ts +45 -0
  49. package/lib/node/copilot-credential-store.d.ts.map +1 -0
  50. package/lib/node/copilot-credential-store.js +96 -0
  51. package/lib/node/copilot-credential-store.js.map +1 -0
  52. package/lib/node/copilot-credential-store.spec.d.ts +2 -0
  53. package/lib/node/copilot-credential-store.spec.d.ts.map +1 -0
  54. package/lib/node/copilot-credential-store.spec.js +93 -0
  55. package/lib/node/copilot-credential-store.spec.js.map +1 -0
  56. package/lib/node/copilot-language-models-manager-impl.d.ts +20 -18
  57. package/lib/node/copilot-language-models-manager-impl.d.ts.map +1 -1
  58. package/lib/node/copilot-language-models-manager-impl.js +39 -67
  59. package/lib/node/copilot-language-models-manager-impl.js.map +1 -1
  60. package/lib/node/copilot-language-models-manager-impl.spec.d.ts +2 -0
  61. package/lib/node/copilot-language-models-manager-impl.spec.d.ts.map +1 -0
  62. package/lib/node/copilot-language-models-manager-impl.spec.js +119 -0
  63. package/lib/node/copilot-language-models-manager-impl.spec.js.map +1 -0
  64. package/lib/node/copilot-runtime-env.d.ts +20 -0
  65. package/lib/node/copilot-runtime-env.d.ts.map +1 -0
  66. package/lib/node/copilot-runtime-env.js +49 -0
  67. package/lib/node/copilot-runtime-env.js.map +1 -0
  68. package/lib/node/copilot-sdk-client-provider.d.ts +103 -0
  69. package/lib/node/copilot-sdk-client-provider.d.ts.map +1 -0
  70. package/lib/node/copilot-sdk-client-provider.js +247 -0
  71. package/lib/node/copilot-sdk-client-provider.js.map +1 -0
  72. package/lib/node/copilot-sdk-client-provider.spec.d.ts +2 -0
  73. package/lib/node/copilot-sdk-client-provider.spec.d.ts.map +1 -0
  74. package/lib/node/copilot-sdk-client-provider.spec.js +215 -0
  75. package/lib/node/copilot-sdk-client-provider.spec.js.map +1 -0
  76. package/lib/node/copilot-sdk-language-model.d.ts +85 -0
  77. package/lib/node/copilot-sdk-language-model.d.ts.map +1 -0
  78. package/lib/node/copilot-sdk-language-model.js +301 -0
  79. package/lib/node/copilot-sdk-language-model.js.map +1 -0
  80. package/lib/node/copilot-sdk-language-model.spec.d.ts +2 -0
  81. package/lib/node/copilot-sdk-language-model.spec.d.ts.map +1 -0
  82. package/lib/node/copilot-sdk-language-model.spec.js +302 -0
  83. package/lib/node/copilot-sdk-language-model.spec.js.map +1 -0
  84. package/lib/node/copilot-sdk-loader.d.ts +57 -0
  85. package/lib/node/copilot-sdk-loader.d.ts.map +1 -0
  86. package/lib/node/copilot-sdk-loader.js +133 -0
  87. package/lib/node/copilot-sdk-loader.js.map +1 -0
  88. package/lib/node/copilot-sdk-loader.spec.d.ts +2 -0
  89. package/lib/node/copilot-sdk-loader.spec.d.ts.map +1 -0
  90. package/lib/node/copilot-sdk-loader.spec.js +113 -0
  91. package/lib/node/copilot-sdk-loader.spec.js.map +1 -0
  92. package/lib/node/copilot-sdk-mappers.d.ts +49 -0
  93. package/lib/node/copilot-sdk-mappers.d.ts.map +1 -0
  94. package/lib/node/copilot-sdk-mappers.js +147 -0
  95. package/lib/node/copilot-sdk-mappers.js.map +1 -0
  96. package/lib/node/copilot-sdk-mappers.spec.d.ts +2 -0
  97. package/lib/node/copilot-sdk-mappers.spec.d.ts.map +1 -0
  98. package/lib/node/copilot-sdk-mappers.spec.js +133 -0
  99. package/lib/node/copilot-sdk-mappers.spec.js.map +1 -0
  100. package/lib/node/copilot-sdk-types.d.ts +198 -0
  101. package/lib/node/copilot-sdk-types.d.ts.map +1 -0
  102. package/lib/{common/copilot-oauth-config.js → node/copilot-sdk-types.js} +2 -13
  103. package/lib/node/copilot-sdk-types.js.map +1 -0
  104. package/lib/node/index.d.ts +9 -1
  105. package/lib/node/index.d.ts.map +1 -1
  106. package/lib/node/index.js +9 -1
  107. package/lib/node/index.js.map +1 -1
  108. package/package.json +4 -6
  109. package/src/browser/copilot-auth-dialog.tsx +12 -8
  110. package/src/browser/copilot-frontend-application-contribution.ts +62 -29
  111. package/src/common/copilot-auth-service.ts +28 -24
  112. package/src/common/copilot-language-models-manager.ts +3 -28
  113. package/src/common/copilot-preferences.ts +24 -5
  114. package/src/common/index.ts +0 -1
  115. package/src/node/copilot-auth-service-impl.spec.ts +122 -207
  116. package/src/node/copilot-auth-service-impl.ts +63 -218
  117. package/src/node/copilot-backend-module.ts +11 -2
  118. package/src/node/copilot-cli-auth-provider.spec.ts +315 -0
  119. package/src/node/copilot-cli-auth-provider.ts +444 -0
  120. package/src/node/copilot-cli-locator.spec.ts +386 -0
  121. package/src/node/copilot-cli-locator.ts +363 -0
  122. package/src/node/copilot-credential-store.spec.ts +106 -0
  123. package/src/node/copilot-credential-store.ts +99 -0
  124. package/src/node/copilot-language-models-manager-impl.spec.ts +137 -0
  125. package/src/node/copilot-language-models-manager-impl.ts +51 -85
  126. package/src/node/copilot-runtime-env.ts +46 -0
  127. package/src/node/copilot-sdk-client-provider.spec.ts +253 -0
  128. package/src/node/copilot-sdk-client-provider.ts +241 -0
  129. package/src/node/copilot-sdk-language-model.spec.ts +363 -0
  130. package/src/node/copilot-sdk-language-model.ts +329 -0
  131. package/src/node/copilot-sdk-loader.spec.ts +126 -0
  132. package/src/node/copilot-sdk-loader.ts +142 -0
  133. package/src/node/copilot-sdk-mappers.spec.ts +151 -0
  134. package/src/node/copilot-sdk-mappers.ts +161 -0
  135. package/src/node/copilot-sdk-types.ts +276 -0
  136. package/src/node/index.ts +9 -1
  137. package/lib/common/copilot-oauth-config.d.ts +0 -33
  138. package/lib/common/copilot-oauth-config.d.ts.map +0 -1
  139. package/lib/common/copilot-oauth-config.js.map +0 -1
  140. package/lib/node/copilot-language-model.d.ts +0 -34
  141. package/lib/node/copilot-language-model.d.ts.map +0 -1
  142. package/lib/node/copilot-language-model.js +0 -236
  143. package/lib/node/copilot-language-model.js.map +0 -1
  144. package/lib/node/copilot-language-model.spec.d.ts +0 -2
  145. package/lib/node/copilot-language-model.spec.d.ts.map +0 -1
  146. package/lib/node/copilot-language-model.spec.js +0 -89
  147. package/lib/node/copilot-language-model.spec.js.map +0 -1
  148. package/src/common/copilot-oauth-config.ts +0 -55
  149. package/src/node/copilot-language-model.spec.ts +0 -106
  150. package/src/node/copilot-language-model.ts +0 -273
@@ -0,0 +1,386 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH and others.
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 { expect } from 'chai';
18
+ import { promises as fs } from 'fs';
19
+ import * as os from 'os';
20
+ import { join } from 'path';
21
+ import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
22
+ import { CopilotCliLocator, COPILOT_CLI_PATH_ENV } from './copilot-cli-locator';
23
+
24
+ class TestableCopilotCliLocator extends CopilotCliLocator {
25
+ constructor() {
26
+ super();
27
+ // The logger is injected in production, and read-only for everyone else.
28
+ Object.assign(this, { logger: new MockLogger() });
29
+ }
30
+
31
+ callToPlatformPackageCandidates(base: string): string[] {
32
+ return this.toPlatformPackageCandidates(base);
33
+ }
34
+
35
+ callToCandidatesOnPath(target: string): string[] {
36
+ return this.toCandidatesOnPath(target);
37
+ }
38
+
39
+ callResolvedCandidates(anchor: string): string[] {
40
+ return this.resolvedCandidates(anchor);
41
+ }
42
+
43
+ callGetCliPlatformPackageNames(): string[] {
44
+ return this.getCliPlatformPackageNames();
45
+ }
46
+
47
+ callGetBinaryName(): string {
48
+ return this.getBinaryName();
49
+ }
50
+
51
+ callEnvironmentCandidates(): Promise<string[]> {
52
+ return this.environmentCandidates();
53
+ }
54
+
55
+ callGetCliFileNames(): string[] {
56
+ return this.getCliFileNames();
57
+ }
58
+ }
59
+
60
+ const names = new TestableCopilotCliLocator();
61
+ const PLATFORM_PACKAGE = names.callGetCliPlatformPackageNames()[0];
62
+ const BINARY_NAME = names.callGetBinaryName();
63
+
64
+ /**
65
+ * Creates a temporary directory with its symlinks resolved.
66
+ *
67
+ * The locator reports what it found canonicalized, through `fs.realpath` and through the module
68
+ * resolution of Node.js, while the temporary directory is a symlink on macOS.
69
+ */
70
+ async function createTempDirectory(prefix: string): Promise<string> {
71
+ return fs.realpath(await fs.mkdtemp(join(os.tmpdir(), prefix)));
72
+ }
73
+
74
+ /**
75
+ * Creates a file that passes the executable check of the locator.
76
+ */
77
+ async function writeExecutable(path: string): Promise<string> {
78
+ await fs.mkdir(join(path, '..'), { recursive: true });
79
+ await fs.writeFile(path, '', { mode: 0o755 });
80
+ return path;
81
+ }
82
+
83
+ /**
84
+ * Writes a platform package that points its main entry at the executable, the way the ones published
85
+ * by GitHub do: `@github/copilot-win32-x64` exports `./copilot.exe`, the others `./copilot`.
86
+ */
87
+ async function installPlatformPackage(at: string, packageName = PLATFORM_PACKAGE, binaryName = BINARY_NAME): Promise<string> {
88
+ const directory = join(at, ...packageName.split('/'));
89
+ await fs.mkdir(directory, { recursive: true });
90
+ await fs.writeFile(join(directory, 'package.json'), JSON.stringify({
91
+ name: packageName,
92
+ version: '1.0.0',
93
+ exports: { '.': `./${binaryName}` }
94
+ }));
95
+ return writeExecutable(join(directory, binaryName));
96
+ }
97
+
98
+ /** A locator that looks for what a Windows host has, so that the lookup can be tested from anywhere. */
99
+ class WindowsLikeCopilotCliLocator extends TestableCopilotCliLocator {
100
+
101
+ static readonly PACKAGE = '@github/copilot-win32-x64';
102
+ static readonly BINARY = 'copilot.exe';
103
+
104
+ protected override getBinaryName(): string {
105
+ return WindowsLikeCopilotCliLocator.BINARY;
106
+ }
107
+
108
+ protected override getCliPlatformPackageNames(): string[] {
109
+ return [WindowsLikeCopilotCliLocator.PACKAGE];
110
+ }
111
+ }
112
+
113
+ describe('CopilotCliLocator - candidates', () => {
114
+
115
+ const locator = new TestableCopilotCliLocator();
116
+
117
+ it('should look for the executable of the platform package below a module directory', () => {
118
+ const candidates = locator.callToPlatformPackageCandidates(join('/app', 'node_modules'));
119
+ expect(candidates).to.not.be.empty;
120
+ for (const candidate of candidates) {
121
+ expect(candidate).to.contain(join('node_modules', '@github', 'copilot-'));
122
+ expect(candidate.endsWith(locator.callGetBinaryName())).to.be.true;
123
+ }
124
+ });
125
+
126
+ it('should consider the glibc and the musl build on Linux', function (): void {
127
+ if (process.platform !== 'linux') {
128
+ this.skip();
129
+ }
130
+ const candidates = locator.callToPlatformPackageCandidates('/app/node_modules');
131
+ expect(candidates).to.have.lengthOf(2);
132
+ expect(candidates.some(candidate => candidate.includes('copilot-linux-'))).to.be.true;
133
+ expect(candidates.some(candidate => candidate.includes('copilot-linuxmusl-'))).to.be.true;
134
+ });
135
+
136
+ it('should return nothing for an empty module directory', () => {
137
+ expect(locator.callToPlatformPackageCandidates('')).to.be.empty;
138
+ });
139
+
140
+ it('should map the JavaScript launcher of a global installation to the platform package', () => {
141
+ const launcher = join('/usr', 'lib', 'node_modules', '@github', 'copilot', 'npm-loader.js');
142
+ const candidates = locator.callToCandidatesOnPath(launcher);
143
+ expect(candidates).to.not.be.empty;
144
+ for (const candidate of candidates) {
145
+ expect(candidate).to.contain(join('/usr', 'lib', 'node_modules', '@github', 'copilot-'));
146
+ expect(candidate).to.not.contain('npm-loader.js');
147
+ }
148
+ });
149
+
150
+ it('should map the shim of a global installation to the platform package next to it', () => {
151
+ // What a global npm installation on Windows puts into its prefix, instead of a link.
152
+ const candidates = locator.callToCandidatesOnPath(join('/prefix', 'copilot.cmd'));
153
+ expect(candidates).to.not.be.empty;
154
+ for (const candidate of candidates) {
155
+ expect(candidate).to.contain(join('/prefix', 'node_modules', '@github', 'copilot-'));
156
+ }
157
+ });
158
+
159
+ it('should take a native executable on the PATH as it is', () => {
160
+ const executable = join('/opt', 'copilot', 'copilot');
161
+ expect(locator.callToCandidatesOnPath(executable)).to.deep.equal([executable]);
162
+ });
163
+
164
+ it('should look for the shim of a global installation on Windows, which has no other entry', function (): void {
165
+ if (process.platform !== 'win32') {
166
+ this.skip();
167
+ }
168
+ expect(locator.callGetCliFileNames()).to.deep.equal(['copilot.exe', 'copilot.cmd']);
169
+ });
170
+
171
+ it('should look for the executable only where an installation links to it', function (): void {
172
+ if (process.platform === 'win32') {
173
+ this.skip();
174
+ }
175
+ expect(locator.callGetCliFileNames()).to.deep.equal(['copilot']);
176
+ });
177
+ });
178
+
179
+ describe('CopilotCliLocator - installed platform package', () => {
180
+
181
+ let root: string;
182
+ let modules: string;
183
+ const locator = new TestableCopilotCliLocator();
184
+
185
+ beforeEach(async () => {
186
+ root = await createTempDirectory('theia-copilot-install-');
187
+ modules = join(root, 'node_modules');
188
+ await fs.mkdir(join(modules, '@github', 'copilot'), { recursive: true });
189
+ // The launcher of the CLI is executable, and linked to from the global binary directory.
190
+ await writeExecutable(join(modules, '@github', 'copilot', 'npm-loader.js'));
191
+ });
192
+
193
+ afterEach(async () => {
194
+ await fs.rm(root, { recursive: true, force: true });
195
+ });
196
+
197
+ it('should find a package that the installation hoisted next to the launcher', async () => {
198
+ const executable = await installPlatformPackage(modules);
199
+ const launcher = join(modules, '@github', 'copilot', 'npm-loader.js');
200
+ expect(locator.callResolvedCandidates(launcher)).to.contain(executable);
201
+ expect(locator.callToCandidatesOnPath(launcher)).to.contain(executable);
202
+ });
203
+
204
+ it('should find a package that a global installation nested below the launcher', async () => {
205
+ const executable = await installPlatformPackage(join(modules, '@github', 'copilot', 'node_modules'));
206
+ const launcher = join(modules, '@github', 'copilot', 'npm-loader.js');
207
+ expect(locator.callResolvedCandidates(launcher)).to.contain(executable);
208
+ expect(locator.callToCandidatesOnPath(launcher)).to.contain(executable);
209
+ });
210
+
211
+ it('should return nothing when no platform package is installed', () => {
212
+ expect(locator.callResolvedCandidates(join(modules, '@github', 'copilot', 'npm-loader.js'))).to.be.empty;
213
+ });
214
+
215
+ it('should find the package of a global installation from the shim next to its module directory', async () => {
216
+ const executable = await installPlatformPackage(join(modules, '@github', 'copilot', 'node_modules'));
217
+ expect(locator.callToCandidatesOnPath(join(root, 'copilot.cmd'))).to.contain(executable);
218
+ });
219
+
220
+ it('should reject a configured shim that leads nowhere, since it cannot be spawned', async () => {
221
+ const shim = await writeExecutable(join(root, 'copilot.cmd'));
222
+ const configured = new TestableCopilotCliLocator();
223
+ configured.setConfiguredPath(shim);
224
+ try {
225
+ await configured.resolve();
226
+ expect.fail('should have rejected');
227
+ } catch (error) {
228
+ expect(String(error)).to.contain(shim);
229
+ }
230
+ });
231
+
232
+ it('should follow a configured launcher to the executable it would start', async function (): Promise<void> {
233
+ if (process.platform === 'win32') {
234
+ // A global installation writes a shim rather than a link there.
235
+ this.skip();
236
+ }
237
+ const executable = await installPlatformPackage(join(modules, '@github', 'copilot', 'node_modules'));
238
+ const launcher = join(root, 'bin', 'copilot');
239
+ await fs.mkdir(join(root, 'bin'), { recursive: true });
240
+ await fs.symlink(join(modules, '@github', 'copilot', 'npm-loader.js'), launcher);
241
+
242
+ const configured = new TestableCopilotCliLocator();
243
+ configured.setConfiguredPath(launcher);
244
+ expect(await configured.resolve()).to.equal(executable);
245
+ });
246
+ });
247
+
248
+ describe('CopilotCliLocator - environment', () => {
249
+
250
+ const locator = new TestableCopilotCliLocator();
251
+ const previous = process.env[COPILOT_CLI_PATH_ENV];
252
+ let root: string;
253
+
254
+ beforeEach(async () => {
255
+ root = await createTempDirectory('theia-copilot-env-');
256
+ });
257
+
258
+ afterEach(async () => {
259
+ if (previous === undefined) {
260
+ delete process.env[COPILOT_CLI_PATH_ENV];
261
+ } else {
262
+ process.env[COPILOT_CLI_PATH_ENV] = previous;
263
+ }
264
+ await fs.rm(root, { recursive: true, force: true });
265
+ });
266
+
267
+ it('should honor the location given in the environment', async () => {
268
+ process.env[COPILOT_CLI_PATH_ENV] = '/opt/copilot/copilot';
269
+ expect(await locator.callEnvironmentCandidates()).to.deep.equal(['/opt/copilot/copilot']);
270
+ });
271
+
272
+ it('should ignore a blank value', async () => {
273
+ process.env[COPILOT_CLI_PATH_ENV] = ' ';
274
+ expect(await locator.callEnvironmentCandidates()).to.be.empty;
275
+ });
276
+
277
+ it('should follow a launcher given in the environment, the way a configured one is followed', async () => {
278
+ const modules = join(root, 'node_modules');
279
+ const executable = await installPlatformPackage(modules);
280
+ const launcher = await writeExecutable(join(modules, '@github', 'copilot', 'npm-loader.js'));
281
+ process.env[COPILOT_CLI_PATH_ENV] = launcher;
282
+ expect(await locator.callEnvironmentCandidates()).to.deep.equal([executable]);
283
+ });
284
+
285
+ it('should accept the directory holding the executable', async () => {
286
+ const executable = await writeExecutable(join(root, 'bin', BINARY_NAME));
287
+ process.env[COPILOT_CLI_PATH_ENV] = join(root, 'bin');
288
+ expect(await locator.callEnvironmentCandidates()).to.deep.equal([executable]);
289
+ });
290
+
291
+ it('should drop a launcher that leads nowhere instead of offering it for spawning', async () => {
292
+ process.env[COPILOT_CLI_PATH_ENV] = await writeExecutable(join(root, 'copilot.cmd'));
293
+ expect(await locator.callEnvironmentCandidates()).to.be.empty;
294
+ });
295
+ });
296
+
297
+ describe('CopilotCliLocator - configured path', () => {
298
+
299
+ let home: string;
300
+ let executable: string;
301
+
302
+ beforeEach(async () => {
303
+ home = await createTempDirectory('theia-copilot-locator-');
304
+ executable = await writeExecutable(join(home, 'bin', process.platform === 'win32' ? 'copilot.exe' : 'copilot'));
305
+ });
306
+
307
+ afterEach(async () => {
308
+ await fs.rm(home, { recursive: true, force: true });
309
+ });
310
+
311
+ it('should use the configured executable', async () => {
312
+ const locator = new TestableCopilotCliLocator();
313
+ locator.setConfiguredPath(executable);
314
+ expect(await locator.resolve()).to.equal(executable);
315
+ });
316
+
317
+ it('should accept the directory holding the executable', async () => {
318
+ const locator = new TestableCopilotCliLocator();
319
+ locator.setConfiguredPath(join(home, 'bin'));
320
+ expect(await locator.resolve()).to.equal(executable);
321
+ });
322
+
323
+ it('should report a configured location that does not exist', async () => {
324
+ const locator = new TestableCopilotCliLocator();
325
+ const missing = join(home, 'nowhere', 'copilot');
326
+ locator.setConfiguredPath(missing);
327
+ try {
328
+ await locator.resolve();
329
+ expect.fail('should have rejected');
330
+ } catch (error) {
331
+ expect(String(error)).to.contain(missing);
332
+ }
333
+ });
334
+
335
+ it('should search again after the configured location changed', async () => {
336
+ const locator = new TestableCopilotCliLocator();
337
+ locator.setConfiguredPath(join(home, 'nowhere', 'copilot'));
338
+ await locator.resolve().catch(() => { /* expected */ });
339
+ locator.setConfiguredPath(executable);
340
+ expect(await locator.resolve()).to.equal(executable);
341
+ });
342
+ });
343
+
344
+ describe('CopilotCliLocator - a Windows host', () => {
345
+
346
+ let root: string;
347
+ const locator = new WindowsLikeCopilotCliLocator();
348
+
349
+ beforeEach(async () => {
350
+ root = await createTempDirectory('theia-copilot-windows-');
351
+ });
352
+
353
+ afterEach(async () => {
354
+ await fs.rm(root, { recursive: true, force: true });
355
+ });
356
+
357
+ /**
358
+ * Writes what a global npm installation leaves on Windows: a `copilot.cmd` shim in the prefix, and
359
+ * the platform package nested below the launcher, exporting its executable under the `.exe` name.
360
+ */
361
+ async function installGlobally(): Promise<{ shim: string, executable: string }> {
362
+ const modules = join(root, 'node_modules');
363
+ await writeExecutable(join(modules, '@github', 'copilot', 'npm-loader.js'));
364
+ const executable = await installPlatformPackage(
365
+ join(modules, '@github', 'copilot', 'node_modules'),
366
+ WindowsLikeCopilotCliLocator.PACKAGE,
367
+ WindowsLikeCopilotCliLocator.BINARY
368
+ );
369
+ return { shim: await writeExecutable(join(root, 'copilot.cmd')), executable };
370
+ }
371
+
372
+ it('should follow the shim of a global installation to the executable', async () => {
373
+ const { shim, executable } = await installGlobally();
374
+ expect(locator.callToCandidatesOnPath(shim)).to.contain(executable);
375
+ });
376
+
377
+ it('should follow a configured shim to the executable, which is what can be spawned', async () => {
378
+ const { shim, executable } = await installGlobally();
379
+ locator.setConfiguredPath(shim);
380
+ try {
381
+ expect(await locator.resolve()).to.equal(executable);
382
+ } finally {
383
+ locator.setConfiguredPath(undefined);
384
+ }
385
+ });
386
+ });