@theia/ai-chat 1.58.3 → 1.59.0-next.72
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 +16 -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 +31 -6
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +108 -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/change-set-variable.d.ts +11 -0
- package/lib/browser/change-set-variable.d.ts.map +1 -0
- package/lib/browser/change-set-variable.js +56 -0
- package/lib/browser/change-set-variable.js.map +1 -0
- 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 +54 -24
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +58 -20
- 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 +84 -36
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +142 -51
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/chat-service.d.ts +27 -10
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +80 -20
- package/lib/common/chat-service.js.map +1 -1
- package/lib/common/chat-string-utils.d.ts +3 -0
- package/lib/common/chat-string-utils.d.ts.map +1 -0
- package/lib/common/chat-string-utils.js +27 -0
- package/lib/common/chat-string-utils.js.map +1 -0
- 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/context-details-variable.d.ts +9 -0
- package/lib/common/context-details-variable.d.ts.map +1 -0
- package/lib/common/context-details-variable.js +57 -0
- package/lib/common/context-details-variable.js.map +1 -0
- package/lib/common/context-summary-variable.d.ts +9 -0
- package/lib/common/context-summary-variable.d.ts.map +1 -0
- package/lib/common/context-summary-variable.js +57 -0
- package/lib/common/context-summary-variable.js.map +1 -0
- package/lib/common/context-variables.d.ts +4 -0
- package/lib/common/context-variables.d.ts.map +1 -0
- package/lib/common/context-variables.js +22 -0
- package/lib/common/context-variables.js.map +1 -0
- package/lib/common/custom-chat-agent.d.ts +6 -10
- package/lib/common/custom-chat-agent.d.ts.map +1 -1
- package/lib/common/custom-chat-agent.js +8 -11
- package/lib/common/custom-chat-agent.js.map +1 -1
- package/lib/common/index.d.ts +1 -3
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +1 -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 +20 -18
- package/src/browser/ai-chat-preferences.ts +13 -2
- package/src/browser/change-set-file-element.ts +109 -20
- package/src/browser/change-set-file-resource.ts +125 -39
- package/src/browser/change-set-file-service.ts +38 -16
- package/src/browser/change-set-variable.ts +54 -0
- 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 +87 -30
- package/src/common/chat-history-entry.ts +1 -1
- package/src/common/chat-model.ts +204 -70
- package/src/common/chat-service.ts +92 -24
- package/src/common/chat-string-utils.ts +23 -0
- package/src/common/chat-tool-request-service.ts +5 -5
- package/src/common/context-details-variable.ts +53 -0
- package/src/common/context-summary-variable.ts +53 -0
- package/src/common/context-variables.ts +19 -0
- package/src/common/custom-chat-agent.ts +9 -20
- package/src/common/index.ts +1 -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,54 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclipseSource GmbH and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { MaybePromise, nls } from '@theia/core';
|
|
18
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import { AIVariable, ResolvedAIVariable, AIVariableContribution, AIVariableResolver, AIVariableService, AIVariableResolutionRequest, AIVariableContext } from '@theia/ai-core';
|
|
20
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
21
|
+
import { ChatSessionContext } from '../common';
|
|
22
|
+
|
|
23
|
+
export const CHANGE_SET_SUMMARY_VARIABLE: AIVariable = {
|
|
24
|
+
id: 'changeSetSummary',
|
|
25
|
+
description: nls.localize('theia/ai/core/changeSetSummaryVariable/description', 'Provides a summary of the files in a change set and their contents.'),
|
|
26
|
+
|
|
27
|
+
name: 'changeSetSummary',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
@injectable()
|
|
31
|
+
export class ChangeSetVariableContribution implements AIVariableContribution, AIVariableResolver {
|
|
32
|
+
@inject(WorkspaceService)
|
|
33
|
+
protected readonly workspaceService: WorkspaceService;
|
|
34
|
+
|
|
35
|
+
registerVariables(service: AIVariableService): void {
|
|
36
|
+
service.registerResolver(CHANGE_SET_SUMMARY_VARIABLE, this);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
canResolve(request: AIVariableResolutionRequest, context: AIVariableContext): MaybePromise<number> {
|
|
40
|
+
return request.variable.name === CHANGE_SET_SUMMARY_VARIABLE.name ? 50 : 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async resolve(request: AIVariableResolutionRequest, context: AIVariableContext): Promise<ResolvedAIVariable | undefined> {
|
|
44
|
+
if (!ChatSessionContext.is(context) || request.variable.name !== CHANGE_SET_SUMMARY_VARIABLE.name || !context.model.changeSet?.getElements().length) { return undefined; }
|
|
45
|
+
const entries = await Promise.all(
|
|
46
|
+
context.model.changeSet.getElements().map(async element => `- file: ${await this.workspaceService.getWorkspaceRelativePath(element.uri)}, status: ${element.state}`)
|
|
47
|
+
);
|
|
48
|
+
return {
|
|
49
|
+
variable: CHANGE_SET_SUMMARY_VARIABLE,
|
|
50
|
+
value: entries.join('\n')
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -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
|
}
|