@theia/ai-chat 1.58.3 → 1.59.0-next.62
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-chat-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-frontend-module.js +10 -13
- package/lib/browser/ai-chat-frontend-module.js.map +1 -1
- package/lib/browser/ai-chat-preferences.d.ts +1 -0
- package/lib/browser/ai-chat-preferences.d.ts.map +1 -1
- package/lib/browser/ai-chat-preferences.js +12 -3
- package/lib/browser/ai-chat-preferences.js.map +1 -1
- package/lib/browser/change-set-file-element.d.ts +24 -6
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +105 -15
- package/lib/browser/change-set-file-element.js.map +1 -1
- package/lib/browser/change-set-file-resource.d.ts +40 -8
- package/lib/browser/change-set-file-resource.d.ts.map +1 -1
- package/lib/browser/change-set-file-resource.js +123 -37
- package/lib/browser/change-set-file-resource.js.map +1 -1
- package/lib/browser/change-set-file-service.d.ts +9 -3
- package/lib/browser/change-set-file-service.d.ts.map +1 -1
- package/lib/browser/change-set-file-service.js +35 -13
- package/lib/browser/change-set-file-service.js.map +1 -1
- package/lib/browser/context-file-variable-label-provider.d.ts +14 -0
- package/lib/browser/context-file-variable-label-provider.d.ts.map +1 -0
- package/lib/browser/context-file-variable-label-provider.js +63 -0
- package/lib/browser/context-file-variable-label-provider.js.map +1 -0
- package/lib/browser/context-variable-label-provider.d.ts +9 -0
- package/lib/browser/context-variable-label-provider.d.ts.map +1 -0
- package/lib/browser/context-variable-label-provider.js +55 -0
- package/lib/browser/context-variable-label-provider.js.map +1 -0
- package/lib/browser/file-chat-variable-contribution.d.ts +18 -0
- package/lib/browser/file-chat-variable-contribution.d.ts.map +1 -0
- package/lib/browser/file-chat-variable-contribution.js +135 -0
- package/lib/browser/file-chat-variable-contribution.js.map +1 -0
- package/lib/browser/frontend-chat-service.d.ts +10 -3
- package/lib/browser/frontend-chat-service.d.ts.map +1 -1
- package/lib/browser/frontend-chat-service.js +39 -19
- package/lib/browser/frontend-chat-service.js.map +1 -1
- package/lib/common/chat-agents.d.ts +47 -24
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +50 -19
- package/lib/common/chat-agents.js.map +1 -1
- package/lib/common/chat-history-entry.js +1 -1
- package/lib/common/chat-history-entry.js.map +1 -1
- package/lib/common/chat-model.d.ts +58 -35
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +96 -53
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/chat-service.d.ts +32 -12
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +77 -19
- package/lib/common/chat-service.js.map +1 -1
- package/lib/common/chat-tool-request-service.d.ts +5 -5
- package/lib/common/chat-tool-request-service.d.ts.map +1 -1
- package/lib/common/chat-tool-request-service.js.map +1 -1
- package/lib/common/custom-chat-agent.d.ts +7 -10
- package/lib/common/custom-chat-agent.d.ts.map +1 -1
- package/lib/common/custom-chat-agent.js +7 -11
- package/lib/common/custom-chat-agent.js.map +1 -1
- package/lib/common/index.d.ts +0 -3
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +0 -3
- package/lib/common/index.js.map +1 -1
- package/lib/common/parse-contents.d.ts +2 -2
- package/lib/common/parse-contents.d.ts.map +1 -1
- package/lib/common/parse-contents.js.map +1 -1
- package/lib/common/parse-contents.spec.d.ts.map +1 -1
- package/lib/common/parse-contents.spec.js.map +1 -1
- package/lib/common/response-content-matcher.d.ts +3 -3
- package/lib/common/response-content-matcher.d.ts.map +1 -1
- package/lib/common/response-content-matcher.js.map +1 -1
- package/package.json +12 -10
- package/src/browser/ai-chat-frontend-module.ts +14 -18
- package/src/browser/ai-chat-preferences.ts +13 -2
- package/src/browser/change-set-file-element.ts +99 -20
- package/src/browser/change-set-file-resource.ts +125 -39
- package/src/browser/change-set-file-service.ts +38 -16
- package/src/browser/context-file-variable-label-provider.ts +62 -0
- package/src/browser/context-variable-label-provider.ts +56 -0
- package/src/browser/file-chat-variable-contribution.ts +143 -0
- package/src/browser/frontend-chat-service.ts +40 -26
- package/src/common/chat-agents.ts +72 -27
- package/src/common/chat-history-entry.ts +1 -1
- package/src/common/chat-model.ts +138 -74
- package/src/common/chat-service.ts +96 -23
- package/src/common/chat-tool-request-service.ts +5 -5
- package/src/common/custom-chat-agent.ts +8 -20
- package/src/common/index.ts +0 -3
- package/src/common/parse-contents.spec.ts +2 -2
- package/src/common/parse-contents.ts +2 -2
- package/src/common/response-content-matcher.ts +3 -3
- package/lib/common/command-chat-agents.d.ts +0 -33
- package/lib/common/command-chat-agents.d.ts.map +0 -1
- package/lib/common/command-chat-agents.js +0 -329
- package/lib/common/command-chat-agents.js.map +0 -1
- package/lib/common/orchestrator-chat-agent.d.ts +0 -22
- package/lib/common/orchestrator-chat-agent.d.ts.map +0 -1
- package/lib/common/orchestrator-chat-agent.js +0 -167
- package/lib/common/orchestrator-chat-agent.js.map +0 -1
- package/lib/common/universal-chat-agent.d.ts +0 -16
- package/lib/common/universal-chat-agent.d.ts.map +0 -1
- package/lib/common/universal-chat-agent.js +0 -109
- package/lib/common/universal-chat-agent.js.map +0 -1
- package/src/common/command-chat-agents.ts +0 -354
- package/src/common/orchestrator-chat-agent.ts +0 -179
- package/src/common/universal-chat-agent.ts +0 -117
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { ILogger,
|
|
18
|
-
import { DiffUris, LabelProvider, OpenerService, open } from '@theia/core/lib/browser';
|
|
17
|
+
import { ILogger, URI } from '@theia/core';
|
|
18
|
+
import { ApplicationShell, DiffUris, LabelProvider, NavigatableWidget, OpenerService, open } from '@theia/core/lib/browser';
|
|
19
19
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
20
|
import { EditorManager } from '@theia/editor/lib/browser';
|
|
21
21
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
@@ -40,6 +40,9 @@ export class ChangeSetFileService {
|
|
|
40
40
|
@inject(EditorManager)
|
|
41
41
|
protected readonly editorManager: EditorManager;
|
|
42
42
|
|
|
43
|
+
@inject(ApplicationShell)
|
|
44
|
+
protected readonly shell: ApplicationShell;
|
|
45
|
+
|
|
43
46
|
@inject(MonacoWorkspace)
|
|
44
47
|
protected readonly monacoWorkspace: MonacoWorkspace;
|
|
45
48
|
|
|
@@ -95,15 +98,14 @@ export class ChangeSetFileService {
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
async openDiff(originalUri: URI, suggestedUri: URI): Promise<void> {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
const diffUri = this.getDiffUri(originalUri, suggestedUri);
|
|
102
|
+
open(this.openerService, diffUri);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected getDiffUri(originalUri: URI, suggestedUri: URI): URI {
|
|
106
|
+
return DiffUris.encode(originalUri, suggestedUri,
|
|
104
107
|
`AI Changes: ${this.labelProvider.getName(originalUri)}`,
|
|
105
108
|
);
|
|
106
|
-
open(this.openerService, diffUri);
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
async delete(uri: URI): Promise<void> {
|
|
@@ -113,24 +115,44 @@ export class ChangeSetFileService {
|
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
/** Returns true if there was a document available to save for the specified URI. */
|
|
119
|
+
async trySave(suggestedUri: URI): Promise<boolean> {
|
|
120
|
+
const openModel = this.monacoWorkspace.getTextDocument(suggestedUri.toString());
|
|
121
|
+
if (openModel) {
|
|
122
|
+
await openModel.save();
|
|
123
|
+
return true;
|
|
124
|
+
} else {
|
|
125
|
+
return false;
|
|
120
126
|
}
|
|
121
|
-
await this.doWrite(uri, targetState);
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
|
|
129
|
+
async writeFrom(from: URI, to: URI, fallbackContent: string): Promise<void> {
|
|
130
|
+
const authoritativeContent = this.monacoWorkspace.getTextDocument(from.toString())?.getText() ?? fallbackContent;
|
|
131
|
+
await this.write(to, authoritativeContent);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async write(uri: URI, text: string): Promise<void> {
|
|
125
135
|
const document = this.monacoWorkspace.getTextDocument(uri.toString());
|
|
126
136
|
if (document) {
|
|
127
137
|
await this.monacoWorkspace.applyBackgroundEdit(document, [{
|
|
128
138
|
range: document.textEditorModel.getFullModelRange(),
|
|
129
139
|
text
|
|
130
|
-
}], (
|
|
140
|
+
}], () => true);
|
|
131
141
|
} else {
|
|
132
142
|
await this.fileService.write(uri, text);
|
|
133
143
|
}
|
|
134
144
|
}
|
|
135
145
|
|
|
146
|
+
closeDiffsForSession(sessionId: string, except?: URI[]): void {
|
|
147
|
+
const openEditors = this.shell.widgets.filter(widget => {
|
|
148
|
+
const uri = NavigatableWidget.getUri(widget);
|
|
149
|
+
return uri && uri.authority === sessionId && !except?.some(candidate => candidate.path.toString() === uri.path.toString());
|
|
150
|
+
});
|
|
151
|
+
openEditors.forEach(editor => editor.close());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
closeDiff(uri: URI): void {
|
|
155
|
+
const openEditors = this.shell.widgets.filter(widget => NavigatableWidget.getUri(widget)?.isEqual(uri));
|
|
156
|
+
openEditors.forEach(editor => editor.close());
|
|
157
|
+
}
|
|
136
158
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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 { AIVariableResolutionRequest } from '@theia/ai-core';
|
|
18
|
+
import { URI } from '@theia/core';
|
|
19
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
|
+
import { LabelProvider, LabelProviderContribution } from '@theia/core/lib/browser';
|
|
21
|
+
import { ChangeSetFileService } from './change-set-file-service';
|
|
22
|
+
|
|
23
|
+
@injectable()
|
|
24
|
+
export class ContextFileVariableLabelProvider implements LabelProviderContribution {
|
|
25
|
+
|
|
26
|
+
@inject(LabelProvider)
|
|
27
|
+
protected readonly labelProvider: LabelProvider;
|
|
28
|
+
|
|
29
|
+
@inject(ChangeSetFileService)
|
|
30
|
+
protected readonly changeSetFileService: ChangeSetFileService;
|
|
31
|
+
|
|
32
|
+
canHandle(element: object): number {
|
|
33
|
+
if (AIVariableResolutionRequest.is(element) && element.variable.name === 'file') {
|
|
34
|
+
return 10;
|
|
35
|
+
}
|
|
36
|
+
return -1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getIcon(element: object): string | undefined {
|
|
40
|
+
return this.labelProvider.getIcon(this.getUri(element)!);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getName(element: object): string | undefined {
|
|
44
|
+
return this.labelProvider.getName(this.getUri(element)!);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getLongName(element: object): string | undefined {
|
|
48
|
+
return this.labelProvider.getLongName(this.getUri(element)!);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getDetails(element: object): string | undefined {
|
|
52
|
+
return this.changeSetFileService.getAdditionalInfo(this.getUri(element)!);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected getUri(element: object): URI | undefined {
|
|
56
|
+
if (!AIVariableResolutionRequest.is(element)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return new URI(element.arg);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { AIVariableResolutionRequest } from '@theia/ai-core';
|
|
18
|
+
import { LabelProviderContribution } from '@theia/core/lib/browser';
|
|
19
|
+
import { injectable } from '@theia/core/shared/inversify';
|
|
20
|
+
|
|
21
|
+
@injectable()
|
|
22
|
+
export class ContextVariableLabelProvider implements LabelProviderContribution {
|
|
23
|
+
|
|
24
|
+
canHandle(element: object): number {
|
|
25
|
+
if (AIVariableResolutionRequest.is(element)) {
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
return -1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getIcon(element: object): string | undefined {
|
|
32
|
+
return 'codicon codicon-variable';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getName(element: object): string | undefined {
|
|
36
|
+
if (!AIVariableResolutionRequest.is(element)) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
return element.variable.name;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getLongName(element: object): string | undefined {
|
|
43
|
+
if (!AIVariableResolutionRequest.is(element)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return element.variable.name + (element.arg ? ':' + element.arg : '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getDetails(element: object): string | undefined {
|
|
50
|
+
if (!AIVariableResolutionRequest.is(element)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return element.arg;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
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 { AIVariableContext, AIVariableResolutionRequest, PromptText } from '@theia/ai-core';
|
|
18
|
+
import { AIVariableDropResult, FrontendVariableContribution, FrontendVariableService } from '@theia/ai-core/lib/browser';
|
|
19
|
+
import { FILE_VARIABLE } from '@theia/ai-core/lib/browser/file-variable-contribution';
|
|
20
|
+
import { CancellationToken, QuickInputService, URI } from '@theia/core';
|
|
21
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
22
|
+
import * as monaco from '@theia/monaco-editor-core';
|
|
23
|
+
import { FileQuickPickItem, QuickFileSelectService } from '@theia/file-search/lib/browser/quick-file-select-service';
|
|
24
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
25
|
+
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
26
|
+
|
|
27
|
+
@injectable()
|
|
28
|
+
export class FileChatVariableContribution implements FrontendVariableContribution {
|
|
29
|
+
@inject(FileService)
|
|
30
|
+
protected readonly fileService: FileService;
|
|
31
|
+
|
|
32
|
+
@inject(WorkspaceService)
|
|
33
|
+
protected readonly wsService: WorkspaceService;
|
|
34
|
+
|
|
35
|
+
@inject(QuickInputService)
|
|
36
|
+
protected readonly quickInputService: QuickInputService;
|
|
37
|
+
|
|
38
|
+
@inject(QuickFileSelectService)
|
|
39
|
+
protected readonly quickFileSelectService: QuickFileSelectService;
|
|
40
|
+
|
|
41
|
+
registerVariables(service: FrontendVariableService): void {
|
|
42
|
+
service.registerArgumentPicker(FILE_VARIABLE, this.triggerArgumentPicker.bind(this));
|
|
43
|
+
service.registerArgumentCompletionProvider(FILE_VARIABLE, this.provideArgumentCompletionItems.bind(this));
|
|
44
|
+
service.registerDropHandler(this.handleDrop.bind(this));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected async triggerArgumentPicker(): Promise<string | undefined> {
|
|
48
|
+
const quickPick = this.quickInputService.createQuickPick();
|
|
49
|
+
quickPick.items = await this.quickFileSelectService.getPicks();
|
|
50
|
+
|
|
51
|
+
const updateItems = async (value: string) => {
|
|
52
|
+
quickPick.items = await this.quickFileSelectService.getPicks(value, CancellationToken.None);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const onChangeListener = quickPick.onDidChangeValue(updateItems);
|
|
56
|
+
quickPick.show();
|
|
57
|
+
|
|
58
|
+
return new Promise(resolve => {
|
|
59
|
+
quickPick.onDispose(onChangeListener.dispose);
|
|
60
|
+
quickPick.onDidAccept(async () => {
|
|
61
|
+
const selectedItem = quickPick.selectedItems[0];
|
|
62
|
+
if (selectedItem && FileQuickPickItem.is(selectedItem)) {
|
|
63
|
+
quickPick.dispose();
|
|
64
|
+
resolve(await this.wsService.getWorkspaceRelativePath(selectedItem.uri));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected async provideArgumentCompletionItems(
|
|
71
|
+
model: monaco.editor.ITextModel,
|
|
72
|
+
position: monaco.Position
|
|
73
|
+
): Promise<monaco.languages.CompletionItem[] | undefined> {
|
|
74
|
+
const lineContent = model.getLineContent(position.lineNumber);
|
|
75
|
+
const indexOfVariableTrigger = lineContent.lastIndexOf(PromptText.VARIABLE_CHAR, position.column - 1);
|
|
76
|
+
|
|
77
|
+
// check if there is a variable trigger and no space typed between the variable trigger and the cursor
|
|
78
|
+
if (indexOfVariableTrigger === -1 || lineContent.substring(indexOfVariableTrigger).includes(' ')) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// determine whether we are providing completions before or after the variable argument separator
|
|
83
|
+
const indexOfVariableArgSeparator = lineContent.lastIndexOf(PromptText.VARIABLE_SEPARATOR_CHAR, position.column - 1);
|
|
84
|
+
const triggerCharIndex = Math.max(indexOfVariableTrigger, indexOfVariableArgSeparator);
|
|
85
|
+
|
|
86
|
+
const typedWord = lineContent.substring(triggerCharIndex + 1, position.column - 1);
|
|
87
|
+
const range = new monaco.Range(position.lineNumber, triggerCharIndex + 2, position.lineNumber, position.column);
|
|
88
|
+
const picks = await this.quickFileSelectService.getPicks(typedWord, CancellationToken.None);
|
|
89
|
+
const prefix = lineContent[triggerCharIndex] === PromptText.VARIABLE_CHAR ? FILE_VARIABLE.name + PromptText.VARIABLE_SEPARATOR_CHAR : '';
|
|
90
|
+
|
|
91
|
+
return Promise.all(
|
|
92
|
+
picks
|
|
93
|
+
.filter(FileQuickPickItem.is)
|
|
94
|
+
// only show files with highlights, if the user started typing to filter down the results
|
|
95
|
+
.filter(p => !typedWord || p.highlights?.label)
|
|
96
|
+
.map(async (pick, index) => ({
|
|
97
|
+
label: pick.label,
|
|
98
|
+
kind: monaco.languages.CompletionItemKind.File,
|
|
99
|
+
range,
|
|
100
|
+
insertText: `${prefix}${await this.wsService.getWorkspaceRelativePath(pick.uri)}`,
|
|
101
|
+
detail: await this.wsService.getWorkspaceRelativePath(pick.uri.parent),
|
|
102
|
+
// don't let monaco filter the items, as we only return picks that are filtered
|
|
103
|
+
filterText: typedWord,
|
|
104
|
+
// keep the order of the items, but move them to the end of the list
|
|
105
|
+
sortText: `ZZ${index.toString().padStart(4, '0')}_${pick.label}`,
|
|
106
|
+
}))
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
protected async handleDrop(event: DragEvent, _: AIVariableContext): Promise<AIVariableDropResult | undefined> {
|
|
111
|
+
const data = event.dataTransfer?.getData('selected-tree-nodes');
|
|
112
|
+
if (!data) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const nodes: string[] = JSON.parse(data);
|
|
118
|
+
const variables: AIVariableResolutionRequest[] = [];
|
|
119
|
+
const texts: string[] = [];
|
|
120
|
+
|
|
121
|
+
for (const node of nodes) {
|
|
122
|
+
const [, filePath] = node.split(':');
|
|
123
|
+
if (!filePath) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const uri = URI.fromFilePath(filePath);
|
|
128
|
+
if (await this.fileService.exists(uri)) {
|
|
129
|
+
const wsRelativePath = await this.wsService.getWorkspaceRelativePath(uri);
|
|
130
|
+
variables.push({
|
|
131
|
+
variable: FILE_VARIABLE,
|
|
132
|
+
arg: wsRelativePath
|
|
133
|
+
});
|
|
134
|
+
texts.push(`${PromptText.VARIABLE_CHAR}${FILE_VARIABLE.name}${PromptText.VARIABLE_SEPARATOR_CHAR}${wsRelativePath}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { variables, text: texts.length ? texts.join(' ') : undefined };
|
|
139
|
+
} catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -15,44 +15,45 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
-
import { ChatAgent, ChatServiceImpl, ParsedChatRequest } from '../common';
|
|
18
|
+
import { ChangeSet, ChatAgent, ChatAgentLocation, ChatServiceImpl, ChatSession, ParsedChatRequest, SessionOptions } from '../common';
|
|
19
19
|
import { PreferenceService } from '@theia/core/lib/browser';
|
|
20
|
-
import { DEFAULT_CHAT_AGENT_PREF } from './ai-chat-preferences';
|
|
20
|
+
import { DEFAULT_CHAT_AGENT_PREF, PIN_CHAT_AGENT_PREF } from './ai-chat-preferences';
|
|
21
|
+
import { ChangeSetFileService } from './change-set-file-service';
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Customizes the ChatServiceImpl to consider preference based default chat agent
|
|
25
|
+
*/
|
|
22
26
|
@injectable()
|
|
23
27
|
export class FrontendChatServiceImpl extends ChatServiceImpl {
|
|
24
28
|
|
|
25
29
|
@inject(PreferenceService)
|
|
26
|
-
protected preferenceService: PreferenceService;
|
|
30
|
+
protected readonly preferenceService: PreferenceService;
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (agentPart) {
|
|
31
|
-
return this.chatAgentService.getAgent(agentPart.agentId);
|
|
32
|
-
}
|
|
32
|
+
@inject(ChangeSetFileService)
|
|
33
|
+
protected readonly changeSetFileService: ChangeSetFileService;
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
protected override getAgent(parsedRequest: ParsedChatRequest, session: ChatSession): ChatAgent | undefined {
|
|
36
|
+
let agent = this.initialAgentSelection(parsedRequest);
|
|
37
|
+
if (!this.preferenceService.get<boolean>(PIN_CHAT_AGENT_PREF)) {
|
|
38
|
+
return agent;
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (defaultAgent) {
|
|
43
|
-
return defaultAgent;
|
|
44
|
-
}
|
|
40
|
+
if (!session.pinnedAgent && agent && agent.id !== this.defaultChatAgentId?.id) {
|
|
41
|
+
session.pinnedAgent = agent;
|
|
42
|
+
} else if (session.pinnedAgent && this.getMentionedAgent(parsedRequest) === undefined) {
|
|
43
|
+
agent = session.pinnedAgent;
|
|
45
44
|
}
|
|
45
|
+
return agent;
|
|
46
|
+
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
|
|
48
|
+
protected override initialAgentSelection(parsedRequest: ParsedChatRequest): ChatAgent | undefined {
|
|
49
|
+
const agentPart = this.getMentionedAgent(parsedRequest);
|
|
50
|
+
if (!agentPart) {
|
|
51
|
+
const configuredDefaultChatAgent = this.getConfiguredDefaultChatAgent();
|
|
52
|
+
if (configuredDefaultChatAgent) {
|
|
53
|
+
return configuredDefaultChatAgent;
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
|
-
|
|
53
|
-
this.logger.warn('No default chat agent is configured or available and the "Universal" Chat Agent is unavailable too. Falling back to first registered agent.');
|
|
54
|
-
|
|
55
|
-
return this.chatAgentService.getAgents()[0] ?? undefined;
|
|
56
|
+
return super.initialAgentSelection(parsedRequest);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
protected getConfiguredDefaultChatAgent(): ChatAgent | undefined {
|
|
@@ -63,4 +64,17 @@ export class FrontendChatServiceImpl extends ChatServiceImpl {
|
|
|
63
64
|
}
|
|
64
65
|
return configuredDefaultChatAgent;
|
|
65
66
|
}
|
|
67
|
+
|
|
68
|
+
override createSession(location?: ChatAgentLocation, options?: SessionOptions): ChatSession {
|
|
69
|
+
const session = super.createSession(location, options);
|
|
70
|
+
session.model.onDidChange(event => {
|
|
71
|
+
const changeSet = (event as { changeSet?: ChangeSet }).changeSet;
|
|
72
|
+
if (event.kind === 'removeChangeSet') {
|
|
73
|
+
this.changeSetFileService.closeDiffsForSession(session.id);
|
|
74
|
+
} else if (changeSet) {
|
|
75
|
+
this.changeSetFileService.closeDiffsForSession(session.id, changeSet.getElements().map(({ uri }) => uri));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return session;
|
|
79
|
+
}
|
|
66
80
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// Partially copied from https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatAgents.ts
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
|
+
AgentSpecificVariables,
|
|
23
24
|
CommunicationRecordingService,
|
|
24
25
|
getTextOfResponse,
|
|
25
26
|
LanguageModel,
|
|
@@ -27,7 +28,9 @@ import {
|
|
|
27
28
|
LanguageModelResponse,
|
|
28
29
|
LanguageModelStreamResponse,
|
|
29
30
|
PromptService,
|
|
31
|
+
PromptTemplate,
|
|
30
32
|
ResolvedPromptTemplate,
|
|
33
|
+
ToolCall,
|
|
31
34
|
ToolRequest,
|
|
32
35
|
} from '@theia/ai-core';
|
|
33
36
|
import {
|
|
@@ -39,11 +42,11 @@ import {
|
|
|
39
42
|
MessageActor,
|
|
40
43
|
} from '@theia/ai-core/lib/common';
|
|
41
44
|
import { CancellationToken, ContributionProvider, ILogger, isArray } from '@theia/core';
|
|
42
|
-
import { inject, injectable, named, postConstruct
|
|
45
|
+
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
|
|
43
46
|
import { ChatAgentService } from './chat-agent-service';
|
|
44
47
|
import {
|
|
45
48
|
ChatModel,
|
|
46
|
-
|
|
49
|
+
MutableChatRequestModel,
|
|
47
50
|
ChatResponseContent,
|
|
48
51
|
ErrorChatResponseContentImpl,
|
|
49
52
|
MarkdownChatResponseContentImpl,
|
|
@@ -115,11 +118,11 @@ export const ChatAgent = Symbol('ChatAgent');
|
|
|
115
118
|
export interface ChatAgent extends Agent {
|
|
116
119
|
locations: ChatAgentLocation[];
|
|
117
120
|
iconClass?: string;
|
|
118
|
-
invoke(request:
|
|
121
|
+
invoke(request: MutableChatRequestModel, chatAgentService?: ChatAgentService): Promise<void>;
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
@injectable()
|
|
122
|
-
export abstract class AbstractChatAgent {
|
|
125
|
+
export abstract class AbstractChatAgent implements ChatAgent {
|
|
123
126
|
@inject(LanguageModelRegistry) protected languageModelRegistry: LanguageModelRegistry;
|
|
124
127
|
@inject(ILogger) protected logger: ILogger;
|
|
125
128
|
@inject(CommunicationRecordingService) protected recordingService: CommunicationRecordingService;
|
|
@@ -128,21 +131,26 @@ export abstract class AbstractChatAgent {
|
|
|
128
131
|
|
|
129
132
|
@inject(ContributionProvider) @named(ResponseContentMatcherProvider)
|
|
130
133
|
protected contentMatcherProviders: ContributionProvider<ResponseContentMatcherProvider>;
|
|
131
|
-
protected additionalToolRequests: ToolRequest[] = [];
|
|
132
|
-
protected contentMatchers: ResponseContentMatcher[] = [];
|
|
133
134
|
|
|
134
135
|
@inject(DefaultResponseContentFactory)
|
|
135
136
|
protected defaultContentFactory: DefaultResponseContentFactory;
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
readonly abstract id: string;
|
|
139
|
+
readonly abstract name: string;
|
|
140
|
+
readonly abstract languageModelRequirements: LanguageModelRequirement[];
|
|
141
|
+
iconClass: string = 'codicon codicon-copilot';
|
|
142
|
+
locations: ChatAgentLocation[] = ChatAgentLocation.ALL;
|
|
143
|
+
tags: string[] = ['Chat'];
|
|
144
|
+
description: string = '';
|
|
145
|
+
variables: string[] = [];
|
|
146
|
+
promptTemplates: PromptTemplate[] = [];
|
|
147
|
+
agentSpecificVariables: AgentSpecificVariables[] = [];
|
|
148
|
+
functions: string[] = [];
|
|
149
|
+
protected readonly abstract defaultLanguageModelPurpose: string;
|
|
150
|
+
protected defaultLogging: boolean = true;
|
|
151
|
+
protected systemPromptId: string | undefined = undefined;
|
|
152
|
+
protected additionalToolRequests: ToolRequest[] = [];
|
|
153
|
+
protected contentMatchers: ResponseContentMatcher[] = [];
|
|
146
154
|
|
|
147
155
|
@postConstruct()
|
|
148
156
|
init(): void {
|
|
@@ -154,7 +162,7 @@ export abstract class AbstractChatAgent {
|
|
|
154
162
|
this.contentMatchers.push(...contributedContentMatchers);
|
|
155
163
|
}
|
|
156
164
|
|
|
157
|
-
async invoke(request:
|
|
165
|
+
async invoke(request: MutableChatRequestModel): Promise<void> {
|
|
158
166
|
try {
|
|
159
167
|
const languageModel = await this.getLanguageModel(this.defaultLanguageModelPurpose);
|
|
160
168
|
if (!languageModel) {
|
|
@@ -206,7 +214,7 @@ export abstract class AbstractChatAgent {
|
|
|
206
214
|
}
|
|
207
215
|
}
|
|
208
216
|
|
|
209
|
-
protected parseContents(text: string, request:
|
|
217
|
+
protected parseContents(text: string, request: MutableChatRequestModel): ChatResponseContent[] {
|
|
210
218
|
return parseContents(
|
|
211
219
|
text,
|
|
212
220
|
request,
|
|
@@ -215,7 +223,7 @@ export abstract class AbstractChatAgent {
|
|
|
215
223
|
);
|
|
216
224
|
};
|
|
217
225
|
|
|
218
|
-
protected handleError(request:
|
|
226
|
+
protected handleError(request: MutableChatRequestModel, error: Error): void {
|
|
219
227
|
request.response.response.addContent(new ErrorChatResponseContentImpl(error));
|
|
220
228
|
request.response.error(error);
|
|
221
229
|
}
|
|
@@ -236,7 +244,13 @@ export abstract class AbstractChatAgent {
|
|
|
236
244
|
return languageModel;
|
|
237
245
|
}
|
|
238
246
|
|
|
239
|
-
protected
|
|
247
|
+
protected async getSystemMessageDescription(): Promise<SystemMessageDescription | undefined> {
|
|
248
|
+
if (this.systemPromptId === undefined) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
const resolvedPrompt = await this.promptService.getPrompt(this.systemPromptId);
|
|
252
|
+
return resolvedPrompt ? SystemMessageDescription.fromResolvedPromptTemplate(resolvedPrompt) : undefined;
|
|
253
|
+
}
|
|
240
254
|
|
|
241
255
|
protected async getMessages(
|
|
242
256
|
model: ChatModel, includeResponseInProgress = false
|
|
@@ -290,17 +304,17 @@ export abstract class AbstractChatAgent {
|
|
|
290
304
|
* The default implementation sets the state of the response to `complete`.
|
|
291
305
|
* Subclasses may override this method to perform additional actions or keep the response open for processing further requests.
|
|
292
306
|
*/
|
|
293
|
-
protected async onResponseComplete(request:
|
|
307
|
+
protected async onResponseComplete(request: MutableChatRequestModel): Promise<void> {
|
|
294
308
|
return request.response.complete();
|
|
295
309
|
}
|
|
296
310
|
|
|
297
|
-
protected abstract addContentsToResponse(languageModelResponse: LanguageModelResponse, request:
|
|
311
|
+
protected abstract addContentsToResponse(languageModelResponse: LanguageModelResponse, request: MutableChatRequestModel): Promise<void>;
|
|
298
312
|
}
|
|
299
313
|
|
|
300
314
|
@injectable()
|
|
301
315
|
export abstract class AbstractTextToModelParsingChatAgent<T> extends AbstractChatAgent {
|
|
302
316
|
|
|
303
|
-
protected async addContentsToResponse(languageModelResponse: LanguageModelResponse, request:
|
|
317
|
+
protected async addContentsToResponse(languageModelResponse: LanguageModelResponse, request: MutableChatRequestModel): Promise<void> {
|
|
304
318
|
const responseAsText = await getTextOfResponse(languageModelResponse);
|
|
305
319
|
const parsedCommand = await this.parseTextResponse(responseAsText);
|
|
306
320
|
const content = this.createResponseContent(parsedCommand, request);
|
|
@@ -309,13 +323,31 @@ export abstract class AbstractTextToModelParsingChatAgent<T> extends AbstractCha
|
|
|
309
323
|
|
|
310
324
|
protected abstract parseTextResponse(text: string): Promise<T>;
|
|
311
325
|
|
|
312
|
-
protected abstract createResponseContent(parsedModel: T, request:
|
|
326
|
+
protected abstract createResponseContent(parsedModel: T, request: MutableChatRequestModel): ChatResponseContent;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Factory for creating ToolCallChatResponseContent instances.
|
|
331
|
+
*/
|
|
332
|
+
@injectable()
|
|
333
|
+
export class ToolCallChatResponseContentFactory {
|
|
334
|
+
create(toolCall: ToolCall): ChatResponseContent {
|
|
335
|
+
return new ToolCallChatResponseContentImpl(
|
|
336
|
+
toolCall.id,
|
|
337
|
+
toolCall.function?.name,
|
|
338
|
+
toolCall.function?.arguments,
|
|
339
|
+
toolCall.finished,
|
|
340
|
+
toolCall.result
|
|
341
|
+
);
|
|
342
|
+
}
|
|
313
343
|
}
|
|
314
344
|
|
|
315
345
|
@injectable()
|
|
316
346
|
export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
|
|
347
|
+
@inject(ToolCallChatResponseContentFactory)
|
|
348
|
+
protected toolCallResponseContentFactory: ToolCallChatResponseContentFactory;
|
|
317
349
|
|
|
318
|
-
protected override async addContentsToResponse(languageModelResponse: LanguageModelResponse, request:
|
|
350
|
+
protected override async addContentsToResponse(languageModelResponse: LanguageModelResponse, request: MutableChatRequestModel): Promise<void> {
|
|
319
351
|
if (isLanguageModelTextResponse(languageModelResponse)) {
|
|
320
352
|
const contents = this.parseContents(languageModelResponse.text, request);
|
|
321
353
|
request.response.response.addContents(contents);
|
|
@@ -335,7 +367,7 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
|
|
|
335
367
|
);
|
|
336
368
|
}
|
|
337
369
|
|
|
338
|
-
protected async addStreamResponse(languageModelResponse: LanguageModelStreamResponse, request:
|
|
370
|
+
protected async addStreamResponse(languageModelResponse: LanguageModelStreamResponse, request: MutableChatRequestModel): Promise<void> {
|
|
339
371
|
for await (const token of languageModelResponse.stream) {
|
|
340
372
|
const newContents = this.parse(token, request);
|
|
341
373
|
if (isArray(newContents)) {
|
|
@@ -362,7 +394,7 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
|
|
|
362
394
|
}
|
|
363
395
|
}
|
|
364
396
|
|
|
365
|
-
protected parse(token: LanguageModelStreamResponsePart, request:
|
|
397
|
+
protected parse(token: LanguageModelStreamResponsePart, request: MutableChatRequestModel): ChatResponseContent | ChatResponseContent[] {
|
|
366
398
|
const content = token.content;
|
|
367
399
|
// eslint-disable-next-line no-null/no-null
|
|
368
400
|
if (content !== undefined && content !== null) {
|
|
@@ -371,10 +403,23 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
|
|
|
371
403
|
const toolCalls = token.tool_calls;
|
|
372
404
|
if (toolCalls !== undefined) {
|
|
373
405
|
const toolCallContents = toolCalls.map(toolCall =>
|
|
374
|
-
|
|
406
|
+
this.createToolCallResponseContent(toolCall)
|
|
407
|
+
);
|
|
375
408
|
return toolCallContents;
|
|
376
409
|
}
|
|
377
410
|
return this.defaultContentFactory.create('', request);
|
|
378
411
|
}
|
|
379
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Creates a ToolCallChatResponseContent instance from the provided tool call data.
|
|
415
|
+
*
|
|
416
|
+
* This method is called when parsing stream response tokens that contain tool call data.
|
|
417
|
+
* Subclasses can override this method to customize the creation of tool call response contents.
|
|
418
|
+
*
|
|
419
|
+
* @param toolCall The ToolCall.
|
|
420
|
+
* @returns A ChatResponseContent representing the tool call.
|
|
421
|
+
*/
|
|
422
|
+
protected createToolCallResponseContent(toolCall: ToolCall): ChatResponseContent {
|
|
423
|
+
return this.toolCallResponseContentFactory.create(toolCall);
|
|
424
|
+
}
|
|
380
425
|
}
|