@theia/ai-ide 1.64.0-next.35 → 1.64.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 (145) hide show
  1. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts +5 -2
  2. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts.map +1 -1
  3. package/lib/browser/ai-configuration/agent-configuration-widget.js +15 -1
  4. package/lib/browser/ai-configuration/agent-configuration-widget.js.map +1 -1
  5. package/lib/browser/ai-configuration/ai-configuration-service.d.ts +6 -1
  6. package/lib/browser/ai-configuration/ai-configuration-service.d.ts.map +1 -1
  7. package/lib/browser/ai-configuration/ai-configuration-service.js +10 -1
  8. package/lib/browser/ai-configuration/ai-configuration-service.js.map +1 -1
  9. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +2 -0
  10. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
  11. package/lib/browser/ai-configuration/ai-configuration-widget.js +7 -1
  12. package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
  13. package/lib/browser/ai-configuration/language-model-renderer.d.ts +4 -2
  14. package/lib/browser/ai-configuration/language-model-renderer.d.ts.map +1 -1
  15. package/lib/browser/ai-configuration/language-model-renderer.js +49 -71
  16. package/lib/browser/ai-configuration/language-model-renderer.js.map +1 -1
  17. package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts +41 -0
  18. package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts.map +1 -0
  19. package/lib/browser/ai-configuration/model-aliases-configuration-widget.js +225 -0
  20. package/lib/browser/ai-configuration/model-aliases-configuration-widget.js.map +1 -0
  21. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts +7 -3
  22. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts.map +1 -1
  23. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js +35 -13
  24. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js.map +1 -1
  25. package/lib/browser/ai-configuration/template-settings-renderer.d.ts.map +1 -1
  26. package/lib/browser/ai-configuration/template-settings-renderer.js +11 -6
  27. package/lib/browser/ai-configuration/template-settings-renderer.js.map +1 -1
  28. package/lib/browser/ai-ide-activation-service.d.ts +18 -0
  29. package/lib/browser/ai-ide-activation-service.d.ts.map +1 -0
  30. package/lib/browser/ai-ide-activation-service.js +72 -0
  31. package/lib/browser/ai-ide-activation-service.js.map +1 -0
  32. package/lib/browser/ai-ide-preferences.d.ts +4 -0
  33. package/lib/browser/ai-ide-preferences.d.ts.map +1 -0
  34. package/lib/browser/ai-ide-preferences.js +43 -0
  35. package/lib/browser/ai-ide-preferences.js.map +1 -0
  36. package/lib/browser/app-tester-chat-agent.js +1 -1
  37. package/lib/browser/app-tester-chat-agent.js.map +1 -1
  38. package/lib/browser/architect-agent.js +1 -1
  39. package/lib/browser/architect-agent.js.map +1 -1
  40. package/lib/browser/coder-agent.js +1 -1
  41. package/lib/browser/coder-agent.js.map +1 -1
  42. package/lib/browser/context-functions.d.ts.map +1 -1
  43. package/lib/browser/context-functions.js +12 -0
  44. package/lib/browser/context-functions.js.map +1 -1
  45. package/lib/browser/context-functions.spec.d.ts +2 -0
  46. package/lib/browser/context-functions.spec.d.ts.map +1 -0
  47. package/lib/browser/context-functions.spec.js +93 -0
  48. package/lib/browser/context-functions.spec.js.map +1 -0
  49. package/lib/browser/file-changeset-function.spec.d.ts +2 -0
  50. package/lib/browser/file-changeset-function.spec.d.ts.map +1 -0
  51. package/lib/browser/file-changeset-function.spec.js +45 -0
  52. package/lib/browser/file-changeset-function.spec.js.map +1 -0
  53. package/lib/browser/file-changeset-functions.d.ts +13 -3
  54. package/lib/browser/file-changeset-functions.d.ts.map +1 -1
  55. package/lib/browser/file-changeset-functions.js +100 -29
  56. package/lib/browser/file-changeset-functions.js.map +1 -1
  57. package/lib/browser/file-changeset-functions.spec.d.ts +2 -0
  58. package/lib/browser/file-changeset-functions.spec.d.ts.map +1 -0
  59. package/lib/browser/file-changeset-functions.spec.js +161 -0
  60. package/lib/browser/file-changeset-functions.spec.js.map +1 -0
  61. package/lib/browser/frontend-module.d.ts.map +1 -1
  62. package/lib/browser/frontend-module.js +20 -0
  63. package/lib/browser/frontend-module.js.map +1 -1
  64. package/lib/browser/ide-chat-welcome-message-provider.js +2 -2
  65. package/lib/browser/ide-chat-welcome-message-provider.js.map +1 -1
  66. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts +2 -0
  67. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts.map +1 -0
  68. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js +52 -0
  69. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js.map +1 -0
  70. package/lib/browser/workspace-functions.d.ts +3 -3
  71. package/lib/browser/workspace-functions.d.ts.map +1 -1
  72. package/lib/browser/workspace-functions.js +79 -28
  73. package/lib/browser/workspace-functions.js.map +1 -1
  74. package/lib/browser/workspace-functions.spec.d.ts +2 -0
  75. package/lib/browser/workspace-functions.spec.d.ts.map +1 -0
  76. package/lib/browser/workspace-functions.spec.js +161 -0
  77. package/lib/browser/workspace-functions.spec.js.map +1 -0
  78. package/lib/browser/workspace-launch-provider.d.ts +24 -0
  79. package/lib/browser/workspace-launch-provider.d.ts.map +1 -0
  80. package/lib/browser/workspace-launch-provider.js +216 -0
  81. package/lib/browser/workspace-launch-provider.js.map +1 -0
  82. package/lib/browser/workspace-launch-provider.spec.d.ts +2 -0
  83. package/lib/browser/workspace-launch-provider.spec.d.ts.map +1 -0
  84. package/lib/browser/workspace-launch-provider.spec.js +245 -0
  85. package/lib/browser/workspace-launch-provider.spec.js.map +1 -0
  86. package/lib/browser/workspace-search-provider.d.ts.map +1 -1
  87. package/lib/browser/workspace-search-provider.js +9 -0
  88. package/lib/browser/workspace-search-provider.js.map +1 -1
  89. package/lib/browser/workspace-search-provider.spec.js +59 -203
  90. package/lib/browser/workspace-search-provider.spec.js.map +1 -1
  91. package/lib/browser/workspace-task-provider.d.ts.map +1 -1
  92. package/lib/browser/workspace-task-provider.js +8 -1
  93. package/lib/browser/workspace-task-provider.js.map +1 -1
  94. package/lib/browser/workspace-task-provider.spec.d.ts +2 -0
  95. package/lib/browser/workspace-task-provider.spec.d.ts.map +1 -0
  96. package/lib/browser/workspace-task-provider.spec.js +109 -0
  97. package/lib/browser/workspace-task-provider.spec.js.map +1 -0
  98. package/lib/common/architect-prompt-template.d.ts.map +1 -1
  99. package/lib/common/architect-prompt-template.js +11 -0
  100. package/lib/common/architect-prompt-template.js.map +1 -1
  101. package/lib/common/command-chat-agents.js +1 -1
  102. package/lib/common/command-chat-agents.js.map +1 -1
  103. package/lib/common/orchestrator-chat-agent.js +1 -1
  104. package/lib/common/orchestrator-chat-agent.js.map +1 -1
  105. package/lib/common/universal-chat-agent.js +1 -1
  106. package/lib/common/universal-chat-agent.js.map +1 -1
  107. package/lib/common/workspace-functions.d.ts +3 -0
  108. package/lib/common/workspace-functions.d.ts.map +1 -1
  109. package/lib/common/workspace-functions.js +4 -1
  110. package/lib/common/workspace-functions.js.map +1 -1
  111. package/package.json +18 -17
  112. package/src/browser/ai-configuration/agent-configuration-widget.tsx +18 -2
  113. package/src/browser/ai-configuration/ai-configuration-service.ts +14 -1
  114. package/src/browser/ai-configuration/ai-configuration-widget.tsx +7 -1
  115. package/src/browser/ai-configuration/language-model-renderer.tsx +87 -59
  116. package/src/browser/ai-configuration/model-aliases-configuration-widget.tsx +279 -0
  117. package/src/browser/ai-configuration/prompt-fragments-configuration-widget.tsx +43 -13
  118. package/src/browser/ai-configuration/template-settings-renderer.tsx +11 -7
  119. package/src/browser/ai-ide-activation-service.ts +65 -0
  120. package/src/browser/ai-ide-preferences.ts +44 -0
  121. package/src/browser/app-tester-chat-agent.ts +1 -1
  122. package/src/browser/architect-agent.ts +1 -1
  123. package/src/browser/coder-agent.ts +1 -1
  124. package/src/browser/context-functions.spec.ts +102 -0
  125. package/src/browser/context-functions.ts +11 -0
  126. package/src/browser/file-changeset-function.spec.ts +52 -0
  127. package/src/browser/file-changeset-functions.spec.ts +212 -0
  128. package/src/browser/file-changeset-functions.ts +102 -25
  129. package/src/browser/frontend-module.ts +29 -1
  130. package/src/browser/ide-chat-welcome-message-provider.tsx +4 -4
  131. package/src/browser/style/index.css +111 -6
  132. package/src/browser/test/tool-provider-cancellation-test-util.spec.ts +60 -0
  133. package/src/browser/workspace-functions.spec.ts +199 -0
  134. package/src/browser/workspace-functions.ts +105 -32
  135. package/src/browser/workspace-launch-provider.spec.ts +320 -0
  136. package/src/browser/workspace-launch-provider.ts +231 -0
  137. package/src/browser/workspace-search-provider.spec.ts +79 -229
  138. package/src/browser/workspace-search-provider.ts +10 -1
  139. package/src/browser/workspace-task-provider.spec.ts +125 -0
  140. package/src/browser/workspace-task-provider.ts +7 -2
  141. package/src/common/architect-prompt-template.ts +11 -0
  142. package/src/common/command-chat-agents.ts +1 -1
  143. package/src/common/orchestrator-chat-agent.ts +1 -1
  144. package/src/common/universal-chat-agent.ts +1 -1
  145. package/src/common/workspace-functions.ts +3 -0
@@ -0,0 +1,199 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 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 { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
18
+ let disableJSDOM = enableJSDOM();
19
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
20
+ FrontendApplicationConfigProvider.set({});
21
+
22
+ import { expect } from 'chai';
23
+ import { CancellationTokenSource } from '@theia/core';
24
+ import {
25
+ GetWorkspaceDirectoryStructure,
26
+ FileContentFunction,
27
+ GetWorkspaceFileList,
28
+ FileDiagnosticProvider,
29
+ WorkspaceFunctionScope
30
+ } from './workspace-functions';
31
+ import { MutableChatRequestModel, MutableChatResponseModel } from '@theia/ai-chat';
32
+ import { Container } from '@theia/core/shared/inversify';
33
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
34
+ import { URI } from '@theia/core/lib/common/uri';
35
+ import { WorkspaceService } from '@theia/workspace/lib/browser';
36
+ import { PreferenceService, OpenerService } from '@theia/core/lib/browser';
37
+ import { ProblemManager } from '@theia/markers/lib/browser';
38
+ import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
39
+ import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
40
+
41
+ disableJSDOM();
42
+
43
+ describe('Workspace Functions Cancellation Tests', () => {
44
+ let cancellationTokenSource: CancellationTokenSource;
45
+ let mockCtx: Partial<MutableChatRequestModel>;
46
+ let container: Container;
47
+
48
+ before(() => {
49
+ disableJSDOM = enableJSDOM();
50
+ });
51
+ after(() => {
52
+ // Disable JSDOM after all tests
53
+ disableJSDOM();
54
+ });
55
+
56
+ beforeEach(() => {
57
+ cancellationTokenSource = new CancellationTokenSource();
58
+
59
+ // Setup mock context
60
+ mockCtx = {
61
+ response: {
62
+ cancellationToken: cancellationTokenSource.token
63
+ } as MutableChatResponseModel
64
+ };
65
+
66
+ // Create a new container for each test
67
+ container = new Container();
68
+
69
+ // Mock dependencies
70
+ const mockWorkspaceService = {
71
+ roots: [{ resource: new URI('file:///workspace') }]
72
+ } as unknown as WorkspaceService;
73
+
74
+ const mockFileService = {
75
+ exists: async () => true,
76
+ resolve: async () => ({
77
+ isDirectory: true,
78
+ children: [
79
+ {
80
+ isDirectory: true,
81
+ resource: new URI('file:///workspace/dir'),
82
+ path: { base: 'dir' }
83
+ }
84
+ ],
85
+ resource: new URI('file:///workspace')
86
+ }),
87
+ read: async () => ({ value: { toString: () => 'test content' } })
88
+ } as unknown as FileService;
89
+
90
+ const mockPreferenceService = {
91
+ get: <T>(_path: string, defaultValue: T) => defaultValue
92
+ };
93
+
94
+ const mockMonacoWorkspace = {
95
+ // eslint-disable-next-line no-null/no-null
96
+ getTextDocument: () => null
97
+ } as unknown as MonacoWorkspace;
98
+
99
+ const mockProblemManager = {
100
+ findMarkers: () => [],
101
+ onDidChangeMarkers: () => ({ dispose: () => { } })
102
+ } as unknown as ProblemManager;
103
+
104
+ const mockMonacoTextModelService = {
105
+ createModelReference: async () => ({
106
+ object: {
107
+ lineCount: 10,
108
+ getText: () => 'test text'
109
+ },
110
+ dispose: () => { }
111
+ })
112
+ } as unknown as MonacoTextModelService;
113
+
114
+ const mockOpenerService = {
115
+ open: async () => { }
116
+ };
117
+
118
+ // Register mocks in the container
119
+ container.bind(WorkspaceService).toConstantValue(mockWorkspaceService);
120
+ container.bind(FileService).toConstantValue(mockFileService);
121
+ container.bind(PreferenceService).toConstantValue(mockPreferenceService);
122
+ container.bind(MonacoWorkspace).toConstantValue(mockMonacoWorkspace);
123
+ container.bind(ProblemManager).toConstantValue(mockProblemManager);
124
+ container.bind(MonacoTextModelService).toConstantValue(mockMonacoTextModelService);
125
+ container.bind(OpenerService).toConstantValue(mockOpenerService);
126
+ container.bind(WorkspaceFunctionScope).toSelf();
127
+ container.bind(GetWorkspaceDirectoryStructure).toSelf();
128
+ container.bind(FileContentFunction).toSelf();
129
+ container.bind(GetWorkspaceFileList).toSelf();
130
+ container.bind(FileDiagnosticProvider).toSelf();
131
+ });
132
+
133
+ afterEach(() => {
134
+ cancellationTokenSource.dispose();
135
+ });
136
+
137
+ it('GetWorkspaceDirectoryStructure should respect cancellation token', async () => {
138
+ const getDirectoryStructure = container.get(GetWorkspaceDirectoryStructure);
139
+ cancellationTokenSource.cancel();
140
+
141
+ const handler = getDirectoryStructure.getTool().handler;
142
+ const result = await handler(JSON.stringify({}), mockCtx as MutableChatRequestModel);
143
+
144
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
145
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
146
+ });
147
+
148
+ it('FileContentFunction should respect cancellation token', async () => {
149
+ const fileContentFunction = container.get(FileContentFunction);
150
+ cancellationTokenSource.cancel();
151
+
152
+ const handler = fileContentFunction.getTool().handler;
153
+ const result = await handler(JSON.stringify({ file: 'test.txt' }), mockCtx as MutableChatRequestModel);
154
+
155
+ const jsonResponse = JSON.parse(result as string);
156
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
157
+ });
158
+
159
+ it('GetWorkspaceFileList should respect cancellation token', async () => {
160
+ const getWorkspaceFileList = container.get(GetWorkspaceFileList);
161
+ cancellationTokenSource.cancel();
162
+
163
+ const handler = getWorkspaceFileList.getTool().handler;
164
+ const result = await handler(JSON.stringify({ path: '' }), mockCtx as MutableChatRequestModel);
165
+
166
+ expect(result).to.include('Operation cancelled by user');
167
+ });
168
+
169
+ it('GetWorkspaceFileList should check cancellation at multiple points', async () => {
170
+ const getWorkspaceFileList = container.get(GetWorkspaceFileList);
171
+
172
+ // We'll let it pass the first check then cancel
173
+ const mockFileService = container.get(FileService);
174
+ const originalResolve = mockFileService.resolve;
175
+
176
+ // Mock resolve to cancel the token after it's called
177
+ mockFileService.resolve = async (...args: unknown[]) => {
178
+ const innerResult = await originalResolve.apply(mockFileService, args);
179
+ cancellationTokenSource.cancel();
180
+ return innerResult;
181
+ };
182
+
183
+ const handler = getWorkspaceFileList.getTool().handler;
184
+ const result = await handler(JSON.stringify({ path: '' }), mockCtx as MutableChatRequestModel);
185
+
186
+ expect(result).to.include('Operation cancelled by user');
187
+ });
188
+
189
+ it('FileDiagnosticProvider should respect cancellation token', async () => {
190
+ const fileDiagnosticProvider = container.get(FileDiagnosticProvider);
191
+ cancellationTokenSource.cancel();
192
+
193
+ const handler = fileDiagnosticProvider.getTool().handler;
194
+ const result = await handler(JSON.stringify({ file: 'test.txt' }), mockCtx as MutableChatRequestModel);
195
+
196
+ const jsonResponse = JSON.parse(result as string);
197
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
198
+ });
199
+ });
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
  import { ToolProvider, ToolRequest } from '@theia/ai-core';
17
- import { Disposable, URI } from '@theia/core';
17
+ import { CancellationToken, Disposable, URI } from '@theia/core';
18
18
  import { inject, injectable } from '@theia/core/shared/inversify';
19
19
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
20
20
  import { FileStat } from '@theia/filesystem/lib/common/files';
@@ -31,6 +31,7 @@ import { CONSIDER_GITIGNORE_PREF, USER_EXCLUDE_PATTERN_PREF } from './workspace-
31
31
  import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
32
32
  import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
33
33
  import { ProblemManager } from '@theia/markers/lib/browser';
34
+ import { MutableChatRequestModel } from '@theia/ai-chat';
34
35
  import { DiagnosticSeverity, Range } from '@theia/core/shared/vscode-languageserver-protocol';
35
36
 
36
37
  @injectable()
@@ -93,7 +94,7 @@ export class WorkspaceFunctionScope {
93
94
  return true;
94
95
  }
95
96
  const workspaceRoot = await this.getWorkspaceRoot();
96
- if (shouldConsiderGitIgnore && await this.isGitIgnored(stat, workspaceRoot)) {
97
+ if (shouldConsiderGitIgnore && (await this.isGitIgnored(stat, workspaceRoot))) {
97
98
  return true;
98
99
  }
99
100
 
@@ -130,7 +131,6 @@ export class WorkspaceFunctionScope {
130
131
 
131
132
  return false;
132
133
  }
133
-
134
134
  }
135
135
 
136
136
  @injectable()
@@ -147,7 +147,10 @@ export class GetWorkspaceDirectoryStructure implements ToolProvider {
147
147
  type: 'object',
148
148
  properties: {}
149
149
  },
150
- handler: () => this.getDirectoryStructure()
150
+ handler: (_: string, ctx: MutableChatRequestModel) => {
151
+ const cancellationToken = ctx.response.cancellationToken;
152
+ return this.getDirectoryStructure(cancellationToken);
153
+ },
151
154
  };
152
155
  }
153
156
 
@@ -157,7 +160,11 @@ export class GetWorkspaceDirectoryStructure implements ToolProvider {
157
160
  @inject(WorkspaceFunctionScope)
158
161
  protected workspaceScope: WorkspaceFunctionScope;
159
162
 
160
- private async getDirectoryStructure(): Promise<Record<string, unknown>> {
163
+ private async getDirectoryStructure(cancellationToken?: CancellationToken): Promise<Record<string, unknown>> {
164
+ if (cancellationToken?.isCancellationRequested) {
165
+ return { error: 'Operation cancelled by user' };
166
+ }
167
+
161
168
  let workspaceRoot;
162
169
  try {
163
170
  workspaceRoot = await this.workspaceScope.getWorkspaceRoot();
@@ -165,18 +172,28 @@ export class GetWorkspaceDirectoryStructure implements ToolProvider {
165
172
  return { error: error.message };
166
173
  }
167
174
 
168
- return this.buildDirectoryStructure(workspaceRoot);
175
+ return this.buildDirectoryStructure(workspaceRoot, cancellationToken);
169
176
  }
170
177
 
171
- private async buildDirectoryStructure(uri: URI): Promise<Record<string, unknown>> {
178
+ private async buildDirectoryStructure(uri: URI, cancellationToken?: CancellationToken): Promise<Record<string, unknown>> {
179
+ if (cancellationToken?.isCancellationRequested) {
180
+ return { error: 'Operation cancelled by user' };
181
+ }
182
+
172
183
  const stat = await this.fileService.resolve(uri);
173
184
  const result: Record<string, unknown> = {};
174
185
 
175
186
  if (stat && stat.isDirectory && stat.children) {
176
187
  for (const child of stat.children) {
177
- if (!child.isDirectory || await this.workspaceScope.shouldExclude(child)) { continue; };
188
+ if (cancellationToken?.isCancellationRequested) {
189
+ return { error: 'Operation cancelled by user' };
190
+ }
191
+
192
+ if (!child.isDirectory || (await this.workspaceScope.shouldExclude(child))) {
193
+ continue;
194
+ }
178
195
  const dirName = child.resource.path.base;
179
- result[dirName] = await this.buildDirectoryStructure(child.resource);
196
+ result[dirName] = await this.buildDirectoryStructure(child.resource, cancellationToken);
180
197
  }
181
198
  }
182
199
 
@@ -205,10 +222,11 @@ export class FileContentFunction implements ToolProvider {
205
222
  },
206
223
  required: ['file']
207
224
  },
208
- handler: (arg_string: string) => {
225
+ handler: (arg_string: string, ctx: MutableChatRequestModel) => {
209
226
  const file = this.parseArg(arg_string);
210
- return this.getFileContent(file);
211
- }
227
+ const cancellationToken = ctx.response.cancellationToken;
228
+ return this.getFileContent(file, cancellationToken);
229
+ },
212
230
  };
213
231
  }
214
232
 
@@ -226,7 +244,11 @@ export class FileContentFunction implements ToolProvider {
226
244
  return result.file;
227
245
  }
228
246
 
229
- private async getFileContent(file: string): Promise<string> {
247
+ private async getFileContent(file: string, cancellationToken?: CancellationToken): Promise<string> {
248
+ if (cancellationToken?.isCancellationRequested) {
249
+ return JSON.stringify({ error: 'Operation cancelled by user' });
250
+ }
251
+
230
252
  let targetUri: URI | undefined;
231
253
  try {
232
254
  const workspaceRoot = await this.workspaceScope.getWorkspaceRoot();
@@ -237,6 +259,10 @@ export class FileContentFunction implements ToolProvider {
237
259
  }
238
260
 
239
261
  try {
262
+ if (cancellationToken?.isCancellationRequested) {
263
+ return JSON.stringify({ error: 'Operation cancelled by user' });
264
+ }
265
+
240
266
  const openEditorValue = this.monacoWorkspace.getTextDocument(targetUri.toString())?.getText();
241
267
  if (openEditorValue !== undefined) {
242
268
  return openEditorValue;
@@ -244,7 +270,6 @@ export class FileContentFunction implements ToolProvider {
244
270
 
245
271
  const fileContent = await this.fileService.read(targetUri);
246
272
  return fileContent.value;
247
-
248
273
  } catch (error) {
249
274
  return JSON.stringify({ error: 'File not found' });
250
275
  }
@@ -272,10 +297,11 @@ export class GetWorkspaceFileList implements ToolProvider {
272
297
  },
273
298
  description: `List files and directories within a specified workspace directory. Paths are relative to the workspace root, and only workspace-contained paths are
274
299
  allowed. If no path is provided, the root contents are listed. Paths outside the workspace will result in an error.`,
275
- handler: (arg_string: string) => {
300
+ handler: (arg_string: string, ctx: MutableChatRequestModel) => {
276
301
  const args = JSON.parse(arg_string);
277
- return this.getProjectFileList(args.path);
278
- }
302
+ const cancellationToken = ctx.response.cancellationToken;
303
+ return this.getProjectFileList(args.path, cancellationToken);
304
+ },
279
305
  };
280
306
  }
281
307
 
@@ -285,30 +311,41 @@ export class GetWorkspaceFileList implements ToolProvider {
285
311
  @inject(WorkspaceFunctionScope)
286
312
  protected workspaceScope: WorkspaceFunctionScope;
287
313
 
288
- async getProjectFileList(path?: string): Promise<string[]> {
314
+ async getProjectFileList(path?: string, cancellationToken?: CancellationToken): Promise<string | string[]> {
315
+ if (cancellationToken?.isCancellationRequested) {
316
+ return JSON.stringify({ error: 'Operation cancelled by user' });
317
+ }
318
+
289
319
  let workspaceRoot;
290
320
  try {
291
321
  workspaceRoot = await this.workspaceScope.getWorkspaceRoot();
292
322
  } catch (error) {
293
- return [`Error: ${error.message}`];
323
+ return JSON.stringify({ error: error.message });
294
324
  }
295
325
 
296
326
  const targetUri = path ? workspaceRoot.resolve(path) : workspaceRoot;
297
327
  this.workspaceScope.ensureWithinWorkspace(targetUri, workspaceRoot);
298
328
 
299
329
  try {
330
+ if (cancellationToken?.isCancellationRequested) {
331
+ return JSON.stringify({ error: 'Operation cancelled by user' });
332
+ }
333
+
300
334
  const stat = await this.fileService.resolve(targetUri);
301
335
  if (!stat || !stat.isDirectory) {
302
- return ['Error: Directory not found'];
336
+ return JSON.stringify({ error: 'Directory not found' });
303
337
  }
304
- return await this.listFilesDirectly(targetUri, workspaceRoot);
305
-
338
+ return await this.listFilesDirectly(targetUri, workspaceRoot, cancellationToken);
306
339
  } catch (error) {
307
- return ['Error: Directory not found'];
340
+ return JSON.stringify({ error: 'Directory not found' });
308
341
  }
309
342
  }
310
343
 
311
- private async listFilesDirectly(uri: URI, workspaceRootUri: URI): Promise<string[]> {
344
+ private async listFilesDirectly(uri: URI, workspaceRootUri: URI, cancellationToken?: CancellationToken): Promise<string | string[]> {
345
+ if (cancellationToken?.isCancellationRequested) {
346
+ return JSON.stringify({ error: 'Operation cancelled by user' });
347
+ }
348
+
312
349
  const stat = await this.fileService.resolve(uri);
313
350
  const result: string[] = [];
314
351
 
@@ -319,9 +356,13 @@ export class GetWorkspaceFileList implements ToolProvider {
319
356
  const children = await this.fileService.resolve(uri);
320
357
  if (children.children) {
321
358
  for (const child of children.children) {
359
+ if (cancellationToken?.isCancellationRequested) {
360
+ return JSON.stringify({ error: 'Operation cancelled by user' });
361
+ }
362
+
322
363
  if (await this.workspaceScope.shouldExclude(child)) {
323
364
  continue;
324
- };
365
+ }
325
366
  result.push(child.resource.path.base);
326
367
  }
327
368
  }
@@ -360,18 +401,22 @@ export class FileDiagnosticProvider implements ToolProvider {
360
401
  file: {
361
402
  type: 'string',
362
403
  description: `The relative path to the target file within the workspace. This path is resolved from the workspace root, and only files within the workspace
363
- boundaries are accessible. Attempting to access paths outside the workspace will result in an error.`,
404
+ boundaries are accessible. Attempting to access paths outside the workspace will result in an error.`
364
405
  }
365
406
  },
366
407
  required: ['file']
367
408
  },
368
- handler: async arg => {
409
+ handler: async (arg: string, ctx: MutableChatRequestModel) => {
369
410
  try {
370
411
  const { file } = JSON.parse(arg);
371
412
  const workspaceRoot = await this.workspaceScope.getWorkspaceRoot();
372
413
  const targetUri = workspaceRoot.resolve(file);
373
414
  this.workspaceScope.ensureWithinWorkspace(targetUri, workspaceRoot);
374
- return this.getDiagnosticsForFile(targetUri);
415
+
416
+ // Safely extract cancellation token with type checks
417
+ const cancellationToken = ctx.response.cancellationToken;
418
+
419
+ return this.getDiagnosticsForFile(targetUri, cancellationToken);
375
420
  } catch (error) {
376
421
  return JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error.' });
377
422
  }
@@ -379,22 +424,46 @@ export class FileDiagnosticProvider implements ToolProvider {
379
424
  };
380
425
  }
381
426
 
382
- protected async getDiagnosticsForFile(uri: URI): Promise<string> {
427
+ protected async getDiagnosticsForFile(uri: URI, cancellationToken?: CancellationToken): Promise<string> {
383
428
  const toDispose: Disposable[] = [];
384
429
  try {
430
+ // Check for early cancellation
431
+ if (cancellationToken?.isCancellationRequested) {
432
+ return JSON.stringify({ error: 'Operation cancelled by user' });
433
+ }
434
+
385
435
  let markers = this.problemManager.findMarkers({ uri });
386
436
  if (markers.length === 0) {
387
437
  // Open editor to ensure that the language services are active.
388
438
  await open(this.openerService, uri);
439
+
389
440
  // Give some time to fetch problems in a newly opened editor.
390
- await new Promise<void>(res => {
391
- setTimeout(res, 5000);
441
+ await new Promise<void>((res, rej) => {
442
+ const timeout = setTimeout(res, 5000);
443
+
392
444
  // Give another moment for additional markers to come in from different sources.
393
445
  const listener = this.problemManager.onDidChangeMarkers(changed => changed.isEqual(uri) && setTimeout(res, 500));
394
446
  toDispose.push(listener);
447
+
448
+ // Handle cancellation
449
+ if (cancellationToken) {
450
+ const cancelListener =
451
+ cancellationToken.onCancellationRequested(() => {
452
+ clearTimeout(timeout);
453
+ listener.dispose();
454
+ rej(new Error('Operation cancelled by user'));
455
+ });
456
+ toDispose.push(cancelListener);
457
+ }
395
458
  });
459
+
396
460
  markers = this.problemManager.findMarkers({ uri });
397
461
  }
462
+
463
+ if (cancellationToken?.isCancellationRequested) {
464
+ return JSON.stringify({ error: 'Operation cancelled by user' });
465
+ }
466
+
398
467
  if (markers.length) {
399
468
  const editor = await this.modelService.createModelReference(uri);
400
469
  toDispose.push(editor);
@@ -406,12 +475,16 @@ export class FileDiagnosticProvider implements ToolProvider {
406
475
  const code = marker.data.code;
407
476
  const codeDescription = marker.data.codeDescription;
408
477
  return { text, message, code, codeDescription };
409
- }));
478
+ })
479
+ );
410
480
  }
411
481
  return JSON.stringify({
412
482
  error: 'No diagnostics were found. The file may contain no problems, or language services may not be available. Retrying may return fresh results.'
413
483
  });
414
484
  } catch (err) {
485
+ if (err.message === 'Operation cancelled by user') {
486
+ return JSON.stringify({ error: 'Operation cancelled by user' });
487
+ }
415
488
  console.warn('Error when fetching markers for', uri.toString(), err);
416
489
  return JSON.stringify({ error: err instanceof Error ? err.message : 'Unknown error when fetching for problems for ' + uri.toString() });
417
490
  } finally {