@theia/ai-ide 1.64.0-next.35 → 1.65.0-next.1
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.
- package/lib/browser/ai-configuration/agent-configuration-widget.d.ts +5 -2
- package/lib/browser/ai-configuration/agent-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/agent-configuration-widget.js +15 -1
- package/lib/browser/ai-configuration/agent-configuration-widget.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-service.d.ts +6 -1
- package/lib/browser/ai-configuration/ai-configuration-service.d.ts.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-service.js +10 -1
- package/lib/browser/ai-configuration/ai-configuration-service.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +2 -0
- package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.js +7 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
- package/lib/browser/ai-configuration/language-model-renderer.d.ts +4 -2
- package/lib/browser/ai-configuration/language-model-renderer.d.ts.map +1 -1
- package/lib/browser/ai-configuration/language-model-renderer.js +49 -71
- package/lib/browser/ai-configuration/language-model-renderer.js.map +1 -1
- package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts +41 -0
- package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts.map +1 -0
- package/lib/browser/ai-configuration/model-aliases-configuration-widget.js +225 -0
- package/lib/browser/ai-configuration/model-aliases-configuration-widget.js.map +1 -0
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts +7 -3
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js +35 -13
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js.map +1 -1
- package/lib/browser/ai-configuration/template-settings-renderer.d.ts.map +1 -1
- package/lib/browser/ai-configuration/template-settings-renderer.js +11 -6
- package/lib/browser/ai-configuration/template-settings-renderer.js.map +1 -1
- package/lib/browser/ai-ide-activation-service.d.ts +18 -0
- package/lib/browser/ai-ide-activation-service.d.ts.map +1 -0
- package/lib/browser/ai-ide-activation-service.js +72 -0
- package/lib/browser/ai-ide-activation-service.js.map +1 -0
- package/lib/browser/ai-ide-preferences.d.ts +4 -0
- package/lib/browser/ai-ide-preferences.d.ts.map +1 -0
- package/lib/browser/ai-ide-preferences.js +43 -0
- package/lib/browser/ai-ide-preferences.js.map +1 -0
- package/lib/browser/app-tester-chat-agent.js +1 -1
- package/lib/browser/app-tester-chat-agent.js.map +1 -1
- package/lib/browser/architect-agent.js +1 -1
- package/lib/browser/architect-agent.js.map +1 -1
- package/lib/browser/coder-agent.js +1 -1
- package/lib/browser/coder-agent.js.map +1 -1
- package/lib/browser/context-functions.d.ts.map +1 -1
- package/lib/browser/context-functions.js +12 -0
- package/lib/browser/context-functions.js.map +1 -1
- package/lib/browser/context-functions.spec.d.ts +2 -0
- package/lib/browser/context-functions.spec.d.ts.map +1 -0
- package/lib/browser/context-functions.spec.js +93 -0
- package/lib/browser/context-functions.spec.js.map +1 -0
- package/lib/browser/file-changeset-function.spec.d.ts +2 -0
- package/lib/browser/file-changeset-function.spec.d.ts.map +1 -0
- package/lib/browser/file-changeset-function.spec.js +45 -0
- package/lib/browser/file-changeset-function.spec.js.map +1 -0
- package/lib/browser/file-changeset-functions.d.ts +13 -3
- package/lib/browser/file-changeset-functions.d.ts.map +1 -1
- package/lib/browser/file-changeset-functions.js +100 -29
- package/lib/browser/file-changeset-functions.js.map +1 -1
- package/lib/browser/file-changeset-functions.spec.d.ts +2 -0
- package/lib/browser/file-changeset-functions.spec.d.ts.map +1 -0
- package/lib/browser/file-changeset-functions.spec.js +161 -0
- package/lib/browser/file-changeset-functions.spec.js.map +1 -0
- package/lib/browser/frontend-module.d.ts.map +1 -1
- package/lib/browser/frontend-module.js +20 -0
- package/lib/browser/frontend-module.js.map +1 -1
- package/lib/browser/ide-chat-welcome-message-provider.js +2 -2
- package/lib/browser/ide-chat-welcome-message-provider.js.map +1 -1
- package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts +2 -0
- package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts.map +1 -0
- package/lib/browser/test/tool-provider-cancellation-test-util.spec.js +52 -0
- package/lib/browser/test/tool-provider-cancellation-test-util.spec.js.map +1 -0
- package/lib/browser/workspace-functions.d.ts +3 -3
- package/lib/browser/workspace-functions.d.ts.map +1 -1
- package/lib/browser/workspace-functions.js +79 -28
- package/lib/browser/workspace-functions.js.map +1 -1
- package/lib/browser/workspace-functions.spec.d.ts +2 -0
- package/lib/browser/workspace-functions.spec.d.ts.map +1 -0
- package/lib/browser/workspace-functions.spec.js +161 -0
- package/lib/browser/workspace-functions.spec.js.map +1 -0
- package/lib/browser/workspace-launch-provider.d.ts +24 -0
- package/lib/browser/workspace-launch-provider.d.ts.map +1 -0
- package/lib/browser/workspace-launch-provider.js +216 -0
- package/lib/browser/workspace-launch-provider.js.map +1 -0
- package/lib/browser/workspace-launch-provider.spec.d.ts +2 -0
- package/lib/browser/workspace-launch-provider.spec.d.ts.map +1 -0
- package/lib/browser/workspace-launch-provider.spec.js +245 -0
- package/lib/browser/workspace-launch-provider.spec.js.map +1 -0
- package/lib/browser/workspace-search-provider.d.ts.map +1 -1
- package/lib/browser/workspace-search-provider.js +9 -0
- package/lib/browser/workspace-search-provider.js.map +1 -1
- package/lib/browser/workspace-search-provider.spec.js +59 -203
- package/lib/browser/workspace-search-provider.spec.js.map +1 -1
- package/lib/browser/workspace-task-provider.d.ts.map +1 -1
- package/lib/browser/workspace-task-provider.js +8 -1
- package/lib/browser/workspace-task-provider.js.map +1 -1
- package/lib/browser/workspace-task-provider.spec.d.ts +2 -0
- package/lib/browser/workspace-task-provider.spec.d.ts.map +1 -0
- package/lib/browser/workspace-task-provider.spec.js +109 -0
- package/lib/browser/workspace-task-provider.spec.js.map +1 -0
- package/lib/common/architect-prompt-template.d.ts.map +1 -1
- package/lib/common/architect-prompt-template.js +11 -0
- package/lib/common/architect-prompt-template.js.map +1 -1
- package/lib/common/command-chat-agents.js +1 -1
- package/lib/common/command-chat-agents.js.map +1 -1
- package/lib/common/orchestrator-chat-agent.js +1 -1
- package/lib/common/orchestrator-chat-agent.js.map +1 -1
- package/lib/common/universal-chat-agent.js +1 -1
- package/lib/common/universal-chat-agent.js.map +1 -1
- package/lib/common/workspace-functions.d.ts +3 -0
- package/lib/common/workspace-functions.d.ts.map +1 -1
- package/lib/common/workspace-functions.js +4 -1
- package/lib/common/workspace-functions.js.map +1 -1
- package/package.json +18 -17
- package/src/browser/ai-configuration/agent-configuration-widget.tsx +18 -2
- package/src/browser/ai-configuration/ai-configuration-service.ts +14 -1
- package/src/browser/ai-configuration/ai-configuration-widget.tsx +7 -1
- package/src/browser/ai-configuration/language-model-renderer.tsx +87 -59
- package/src/browser/ai-configuration/model-aliases-configuration-widget.tsx +279 -0
- package/src/browser/ai-configuration/prompt-fragments-configuration-widget.tsx +43 -13
- package/src/browser/ai-configuration/template-settings-renderer.tsx +11 -7
- package/src/browser/ai-ide-activation-service.ts +65 -0
- package/src/browser/ai-ide-preferences.ts +44 -0
- package/src/browser/app-tester-chat-agent.ts +1 -1
- package/src/browser/architect-agent.ts +1 -1
- package/src/browser/coder-agent.ts +1 -1
- package/src/browser/context-functions.spec.ts +102 -0
- package/src/browser/context-functions.ts +11 -0
- package/src/browser/file-changeset-function.spec.ts +52 -0
- package/src/browser/file-changeset-functions.spec.ts +212 -0
- package/src/browser/file-changeset-functions.ts +102 -25
- package/src/browser/frontend-module.ts +29 -1
- package/src/browser/ide-chat-welcome-message-provider.tsx +4 -4
- package/src/browser/style/index.css +111 -6
- package/src/browser/test/tool-provider-cancellation-test-util.spec.ts +60 -0
- package/src/browser/workspace-functions.spec.ts +199 -0
- package/src/browser/workspace-functions.ts +105 -32
- package/src/browser/workspace-launch-provider.spec.ts +320 -0
- package/src/browser/workspace-launch-provider.ts +231 -0
- package/src/browser/workspace-search-provider.spec.ts +79 -229
- package/src/browser/workspace-search-provider.ts +10 -1
- package/src/browser/workspace-task-provider.spec.ts +125 -0
- package/src/browser/workspace-task-provider.ts +7 -2
- package/src/common/architect-prompt-template.ts +11 -0
- package/src/common/command-chat-agents.ts +1 -1
- package/src/common/orchestrator-chat-agent.ts +1 -1
- package/src/common/universal-chat-agent.ts +1 -1
- 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: () =>
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|