@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.
Files changed (128) hide show
  1. package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
  2. package/lib/browser/ai-chat-frontend-module.js +16 -13
  3. package/lib/browser/ai-chat-frontend-module.js.map +1 -1
  4. package/lib/browser/ai-chat-preferences.d.ts +1 -0
  5. package/lib/browser/ai-chat-preferences.d.ts.map +1 -1
  6. package/lib/browser/ai-chat-preferences.js +12 -3
  7. package/lib/browser/ai-chat-preferences.js.map +1 -1
  8. package/lib/browser/change-set-file-element.d.ts +31 -6
  9. package/lib/browser/change-set-file-element.d.ts.map +1 -1
  10. package/lib/browser/change-set-file-element.js +108 -15
  11. package/lib/browser/change-set-file-element.js.map +1 -1
  12. package/lib/browser/change-set-file-resource.d.ts +40 -8
  13. package/lib/browser/change-set-file-resource.d.ts.map +1 -1
  14. package/lib/browser/change-set-file-resource.js +123 -37
  15. package/lib/browser/change-set-file-resource.js.map +1 -1
  16. package/lib/browser/change-set-file-service.d.ts +9 -3
  17. package/lib/browser/change-set-file-service.d.ts.map +1 -1
  18. package/lib/browser/change-set-file-service.js +35 -13
  19. package/lib/browser/change-set-file-service.js.map +1 -1
  20. package/lib/browser/change-set-variable.d.ts +11 -0
  21. package/lib/browser/change-set-variable.d.ts.map +1 -0
  22. package/lib/browser/change-set-variable.js +56 -0
  23. package/lib/browser/change-set-variable.js.map +1 -0
  24. package/lib/browser/context-file-variable-label-provider.d.ts +14 -0
  25. package/lib/browser/context-file-variable-label-provider.d.ts.map +1 -0
  26. package/lib/browser/context-file-variable-label-provider.js +63 -0
  27. package/lib/browser/context-file-variable-label-provider.js.map +1 -0
  28. package/lib/browser/context-variable-label-provider.d.ts +9 -0
  29. package/lib/browser/context-variable-label-provider.d.ts.map +1 -0
  30. package/lib/browser/context-variable-label-provider.js +55 -0
  31. package/lib/browser/context-variable-label-provider.js.map +1 -0
  32. package/lib/browser/file-chat-variable-contribution.d.ts +18 -0
  33. package/lib/browser/file-chat-variable-contribution.d.ts.map +1 -0
  34. package/lib/browser/file-chat-variable-contribution.js +135 -0
  35. package/lib/browser/file-chat-variable-contribution.js.map +1 -0
  36. package/lib/browser/frontend-chat-service.d.ts +10 -3
  37. package/lib/browser/frontend-chat-service.d.ts.map +1 -1
  38. package/lib/browser/frontend-chat-service.js +39 -19
  39. package/lib/browser/frontend-chat-service.js.map +1 -1
  40. package/lib/common/chat-agents.d.ts +54 -24
  41. package/lib/common/chat-agents.d.ts.map +1 -1
  42. package/lib/common/chat-agents.js +58 -20
  43. package/lib/common/chat-agents.js.map +1 -1
  44. package/lib/common/chat-history-entry.js +1 -1
  45. package/lib/common/chat-history-entry.js.map +1 -1
  46. package/lib/common/chat-model.d.ts +84 -36
  47. package/lib/common/chat-model.d.ts.map +1 -1
  48. package/lib/common/chat-model.js +142 -51
  49. package/lib/common/chat-model.js.map +1 -1
  50. package/lib/common/chat-service.d.ts +27 -10
  51. package/lib/common/chat-service.d.ts.map +1 -1
  52. package/lib/common/chat-service.js +80 -20
  53. package/lib/common/chat-service.js.map +1 -1
  54. package/lib/common/chat-string-utils.d.ts +3 -0
  55. package/lib/common/chat-string-utils.d.ts.map +1 -0
  56. package/lib/common/chat-string-utils.js +27 -0
  57. package/lib/common/chat-string-utils.js.map +1 -0
  58. package/lib/common/chat-tool-request-service.d.ts +5 -5
  59. package/lib/common/chat-tool-request-service.d.ts.map +1 -1
  60. package/lib/common/chat-tool-request-service.js.map +1 -1
  61. package/lib/common/context-details-variable.d.ts +9 -0
  62. package/lib/common/context-details-variable.d.ts.map +1 -0
  63. package/lib/common/context-details-variable.js +57 -0
  64. package/lib/common/context-details-variable.js.map +1 -0
  65. package/lib/common/context-summary-variable.d.ts +9 -0
  66. package/lib/common/context-summary-variable.d.ts.map +1 -0
  67. package/lib/common/context-summary-variable.js +57 -0
  68. package/lib/common/context-summary-variable.js.map +1 -0
  69. package/lib/common/context-variables.d.ts +4 -0
  70. package/lib/common/context-variables.d.ts.map +1 -0
  71. package/lib/common/context-variables.js +22 -0
  72. package/lib/common/context-variables.js.map +1 -0
  73. package/lib/common/custom-chat-agent.d.ts +6 -10
  74. package/lib/common/custom-chat-agent.d.ts.map +1 -1
  75. package/lib/common/custom-chat-agent.js +8 -11
  76. package/lib/common/custom-chat-agent.js.map +1 -1
  77. package/lib/common/index.d.ts +1 -3
  78. package/lib/common/index.d.ts.map +1 -1
  79. package/lib/common/index.js +1 -3
  80. package/lib/common/index.js.map +1 -1
  81. package/lib/common/parse-contents.d.ts +2 -2
  82. package/lib/common/parse-contents.d.ts.map +1 -1
  83. package/lib/common/parse-contents.js.map +1 -1
  84. package/lib/common/parse-contents.spec.d.ts.map +1 -1
  85. package/lib/common/parse-contents.spec.js.map +1 -1
  86. package/lib/common/response-content-matcher.d.ts +3 -3
  87. package/lib/common/response-content-matcher.d.ts.map +1 -1
  88. package/lib/common/response-content-matcher.js.map +1 -1
  89. package/package.json +12 -10
  90. package/src/browser/ai-chat-frontend-module.ts +20 -18
  91. package/src/browser/ai-chat-preferences.ts +13 -2
  92. package/src/browser/change-set-file-element.ts +109 -20
  93. package/src/browser/change-set-file-resource.ts +125 -39
  94. package/src/browser/change-set-file-service.ts +38 -16
  95. package/src/browser/change-set-variable.ts +54 -0
  96. package/src/browser/context-file-variable-label-provider.ts +62 -0
  97. package/src/browser/context-variable-label-provider.ts +56 -0
  98. package/src/browser/file-chat-variable-contribution.ts +143 -0
  99. package/src/browser/frontend-chat-service.ts +40 -26
  100. package/src/common/chat-agents.ts +87 -30
  101. package/src/common/chat-history-entry.ts +1 -1
  102. package/src/common/chat-model.ts +204 -70
  103. package/src/common/chat-service.ts +92 -24
  104. package/src/common/chat-string-utils.ts +23 -0
  105. package/src/common/chat-tool-request-service.ts +5 -5
  106. package/src/common/context-details-variable.ts +53 -0
  107. package/src/common/context-summary-variable.ts +53 -0
  108. package/src/common/context-variables.ts +19 -0
  109. package/src/common/custom-chat-agent.ts +9 -20
  110. package/src/common/index.ts +1 -3
  111. package/src/common/parse-contents.spec.ts +2 -2
  112. package/src/common/parse-contents.ts +2 -2
  113. package/src/common/response-content-matcher.ts +3 -3
  114. package/lib/common/command-chat-agents.d.ts +0 -33
  115. package/lib/common/command-chat-agents.d.ts.map +0 -1
  116. package/lib/common/command-chat-agents.js +0 -329
  117. package/lib/common/command-chat-agents.js.map +0 -1
  118. package/lib/common/orchestrator-chat-agent.d.ts +0 -22
  119. package/lib/common/orchestrator-chat-agent.d.ts.map +0 -1
  120. package/lib/common/orchestrator-chat-agent.js +0 -167
  121. package/lib/common/orchestrator-chat-agent.js.map +0 -1
  122. package/lib/common/universal-chat-agent.d.ts +0 -16
  123. package/lib/common/universal-chat-agent.d.ts.map +0 -1
  124. package/lib/common/universal-chat-agent.js +0 -109
  125. package/lib/common/universal-chat-agent.js.map +0 -1
  126. package/src/common/command-chat-agents.ts +0 -354
  127. package/src/common/orchestrator-chat-agent.ts +0 -179
  128. 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, UNTITLED_SCHEME, URI } from '@theia/core';
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 exists = await this.fileService.exists(originalUri);
99
- const openedUri = exists ? originalUri : originalUri.withScheme(UNTITLED_SCHEME);
100
- // Currently we don't have a great way to show the suggestions in a diff editor with accept/reject buttons
101
- // So we just use plain diffs with the suggestions as original and the current state as modified, so users can apply changes in their current state
102
- // But this leads to wrong colors and wrong label (revert change instead of accept change)
103
- const diffUri = DiffUris.encode(openedUri, suggestedUri,
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
- async write(uri: URI, targetState: string): Promise<void> {
117
- const exists = await this.fileService.exists(uri);
118
- if (!exists) {
119
- await this.fileService.create(uri, targetState);
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
- protected async doWrite(uri: URI, text: string): Promise<void> {
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
- }], (editor, wasDirty) => editor === undefined || !wasDirty);
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
- protected override getAgent(parsedRequest: ParsedChatRequest): ChatAgent | undefined {
29
- const agentPart = this.getMentionedAgent(parsedRequest);
30
- if (agentPart) {
31
- return this.chatAgentService.getAgent(agentPart.agentId);
32
- }
32
+ @inject(ChangeSetFileService)
33
+ protected readonly changeSetFileService: ChangeSetFileService;
33
34
 
34
- const configuredDefaultChatAgent = this.getConfiguredDefaultChatAgent();
35
- if (configuredDefaultChatAgent) {
36
- return configuredDefaultChatAgent;
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
- if (this.defaultChatAgentId) {
40
- const defaultAgent = this.chatAgentService.getAgent(this.defaultChatAgentId.id);
41
- // the default agent could be disabled
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
- // check whether "Universal" is available
48
- const universalAgent = this.chatAgentService.getAgent('Universal');
49
- if (universalAgent) {
50
- return universalAgent;
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
  }