@theia/ai-ide 1.63.0-next.0 → 1.63.0-next.24

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 (57) hide show
  1. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js +1 -1
  2. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js.map +1 -1
  3. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +2 -0
  4. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
  5. package/lib/browser/ai-configuration/ai-configuration-widget.js +6 -0
  6. package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
  7. package/lib/browser/ai-configuration/token-usage-configuration-widget.d.ts +1 -0
  8. package/lib/browser/ai-configuration/token-usage-configuration-widget.d.ts.map +1 -1
  9. package/lib/browser/ai-configuration/token-usage-configuration-widget.js +25 -3
  10. package/lib/browser/ai-configuration/token-usage-configuration-widget.js.map +1 -1
  11. package/lib/browser/ai-configuration/tools-configuration-widget.d.ts +29 -0
  12. package/lib/browser/ai-configuration/tools-configuration-widget.d.ts.map +1 -0
  13. package/lib/browser/ai-configuration/tools-configuration-widget.js +163 -0
  14. package/lib/browser/ai-configuration/tools-configuration-widget.js.map +1 -0
  15. package/lib/browser/app-tester-chat-agent.d.ts +36 -0
  16. package/lib/browser/app-tester-chat-agent.d.ts.map +1 -0
  17. package/lib/browser/app-tester-chat-agent.js +172 -0
  18. package/lib/browser/app-tester-chat-agent.js.map +1 -0
  19. package/lib/browser/coder-agent.d.ts.map +1 -1
  20. package/lib/browser/coder-agent.js +3 -3
  21. package/lib/browser/coder-agent.js.map +1 -1
  22. package/lib/browser/file-changeset-functions.d.ts +25 -6
  23. package/lib/browser/file-changeset-functions.d.ts.map +1 -1
  24. package/lib/browser/file-changeset-functions.js +248 -106
  25. package/lib/browser/file-changeset-functions.js.map +1 -1
  26. package/lib/browser/frontend-module.d.ts.map +1 -1
  27. package/lib/browser/frontend-module.js +20 -5
  28. package/lib/browser/frontend-module.js.map +1 -1
  29. package/lib/browser/workspace-functions.d.ts.map +1 -1
  30. package/lib/browser/workspace-functions.js +6 -10
  31. package/lib/browser/workspace-functions.js.map +1 -1
  32. package/lib/browser/workspace-search-provider.d.ts +2 -0
  33. package/lib/browser/workspace-search-provider.d.ts.map +1 -1
  34. package/lib/browser/workspace-search-provider.js +26 -4
  35. package/lib/browser/workspace-search-provider.js.map +1 -1
  36. package/lib/common/coder-replace-prompt-template.d.ts +4 -5
  37. package/lib/common/coder-replace-prompt-template.d.ts.map +1 -1
  38. package/lib/common/coder-replace-prompt-template.js +95 -67
  39. package/lib/common/coder-replace-prompt-template.js.map +1 -1
  40. package/lib/common/file-changeset-function-ids.d.ts +7 -0
  41. package/lib/common/file-changeset-function-ids.d.ts.map +1 -0
  42. package/lib/common/file-changeset-function-ids.js +25 -0
  43. package/lib/common/file-changeset-function-ids.js.map +1 -0
  44. package/package.json +17 -17
  45. package/src/browser/ai-configuration/ai-configuration-view-contribution.ts +1 -1
  46. package/src/browser/ai-configuration/ai-configuration-widget.tsx +6 -0
  47. package/src/browser/ai-configuration/token-usage-configuration-widget.tsx +63 -4
  48. package/src/browser/ai-configuration/tools-configuration-widget.tsx +193 -0
  49. package/src/browser/app-tester-chat-agent.ts +178 -0
  50. package/src/browser/coder-agent.ts +5 -5
  51. package/src/browser/file-changeset-functions.ts +236 -89
  52. package/src/browser/frontend-module.ts +31 -10
  53. package/src/browser/style/index.css +78 -0
  54. package/src/browser/workspace-functions.ts +7 -11
  55. package/src/browser/workspace-search-provider.ts +27 -6
  56. package/src/common/coder-replace-prompt-template.ts +101 -65
  57. package/src/common/file-changeset-function-ids.ts +22 -0
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2025 EclipseSource GmbH.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.GET_PROPOSED_CHANGES_ID = exports.CLEAR_FILE_CHANGES_ID = exports.WRITE_FILE_REPLACEMENTS_ID = exports.SUGGEST_FILE_REPLACEMENTS_ID = exports.WRITE_FILE_CONTENT_ID = exports.SUGGEST_FILE_CONTENT_ID = void 0;
19
+ exports.SUGGEST_FILE_CONTENT_ID = 'suggestFileContent';
20
+ exports.WRITE_FILE_CONTENT_ID = 'writeFileContent';
21
+ exports.SUGGEST_FILE_REPLACEMENTS_ID = 'suggestFileReplacements';
22
+ exports.WRITE_FILE_REPLACEMENTS_ID = 'writeFileReplacements';
23
+ exports.CLEAR_FILE_CHANGES_ID = 'clearFileChanges';
24
+ exports.GET_PROPOSED_CHANGES_ID = 'getProposedFileState';
25
+ //# sourceMappingURL=file-changeset-function-ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-changeset-function-ids.js","sourceRoot":"","sources":["../../src/common/file-changeset-function-ids.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,yCAAyC;AACzC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;;AAEnE,QAAA,uBAAuB,GAAG,oBAAoB,CAAC;AAC/C,QAAA,qBAAqB,GAAG,kBAAkB,CAAC;AAC3C,QAAA,4BAA4B,GAAG,yBAAyB,CAAC;AACzD,QAAA,0BAA0B,GAAG,uBAAuB,CAAC;AACrD,QAAA,qBAAqB,GAAG,kBAAkB,CAAC;AAC3C,QAAA,uBAAuB,GAAG,sBAAsB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theia/ai-ide",
3
- "version": "1.63.0-next.0+c02e6a4f5",
3
+ "version": "1.63.0-next.24+83b50cc66",
4
4
  "description": "AI IDE Agents Extension",
5
5
  "license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
6
6
  "repository": {
@@ -15,19 +15,19 @@
15
15
  "theia-extension"
16
16
  ],
17
17
  "dependencies": {
18
- "@theia/ai-chat": "1.63.0-next.0+c02e6a4f5",
19
- "@theia/ai-chat-ui": "1.63.0-next.0+c02e6a4f5",
20
- "@theia/ai-core": "1.63.0-next.0+c02e6a4f5",
21
- "@theia/ai-mcp": "1.63.0-next.0+c02e6a4f5",
22
- "@theia/core": "1.63.0-next.0+c02e6a4f5",
23
- "@theia/filesystem": "1.63.0-next.0+c02e6a4f5",
24
- "@theia/markers": "1.63.0-next.0+c02e6a4f5",
25
- "@theia/monaco": "1.63.0-next.0+c02e6a4f5",
26
- "@theia/navigator": "1.63.0-next.0+c02e6a4f5",
27
- "@theia/search-in-workspace": "1.63.0-next.0+c02e6a4f5",
28
- "@theia/task": "1.63.0-next.0+c02e6a4f5",
29
- "@theia/terminal": "1.63.0-next.0+c02e6a4f5",
30
- "@theia/workspace": "1.63.0-next.0+c02e6a4f5",
18
+ "@theia/ai-chat": "1.63.0-next.24+83b50cc66",
19
+ "@theia/ai-chat-ui": "1.63.0-next.24+83b50cc66",
20
+ "@theia/ai-core": "1.63.0-next.24+83b50cc66",
21
+ "@theia/ai-mcp": "1.63.0-next.24+83b50cc66",
22
+ "@theia/core": "1.63.0-next.24+83b50cc66",
23
+ "@theia/filesystem": "1.63.0-next.24+83b50cc66",
24
+ "@theia/markers": "1.63.0-next.24+83b50cc66",
25
+ "@theia/monaco": "1.63.0-next.24+83b50cc66",
26
+ "@theia/navigator": "1.63.0-next.24+83b50cc66",
27
+ "@theia/search-in-workspace": "1.63.0-next.24+83b50cc66",
28
+ "@theia/task": "1.63.0-next.24+83b50cc66",
29
+ "@theia/terminal": "1.63.0-next.24+83b50cc66",
30
+ "@theia/workspace": "1.63.0-next.24+83b50cc66",
31
31
  "date-fns": "^4.1.0",
32
32
  "ignore": "^6.0.0",
33
33
  "js-yaml": "^4.1.0",
@@ -37,8 +37,8 @@
37
37
  "access": "public"
38
38
  },
39
39
  "devDependencies": {
40
- "@theia/cli": "1.63.0-next.0+c02e6a4f5",
41
- "@theia/test": "1.63.0-next.0+c02e6a4f5"
40
+ "@theia/cli": "1.63.0-next.24+83b50cc66",
41
+ "@theia/test": "1.63.0-next.24+83b50cc66"
42
42
  },
43
43
  "theiaExtensions": [
44
44
  {
@@ -60,5 +60,5 @@
60
60
  "nyc": {
61
61
  "extends": "../../configs/nyc.json"
62
62
  },
63
- "gitHead": "c02e6a4f58728b4067cbd267d598550192abb249"
63
+ "gitHead": "83b50cc66b001a5b9100ccd944dcace04cd4ae2e"
64
64
  }
@@ -35,7 +35,7 @@ export class AIAgentConfigurationViewContribution extends AIViewContribution<AIC
35
35
  widgetId: AIConfigurationContainerWidget.ID,
36
36
  widgetName: AIConfigurationContainerWidget.LABEL,
37
37
  defaultWidgetOptions: {
38
- area: 'bottom',
38
+ area: 'main',
39
39
  rank: 100
40
40
  },
41
41
  toggleCommandId: AI_CONFIGURATION_TOGGLE_COMMAND_ID
@@ -20,6 +20,7 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'
20
20
  import '../../../src/browser/style/index.css';
21
21
  import { AIAgentConfigurationWidget } from './agent-configuration-widget';
22
22
  import { AIVariableConfigurationWidget } from './variable-configuration-widget';
23
+ import { AIToolsConfigurationWidget } from './tools-configuration-widget';
23
24
  import { AIConfigurationSelectionService } from './ai-configuration-service';
24
25
  import { nls } from '@theia/core';
25
26
  import { AIMCPConfigurationWidget } from './mcp-configuration-widget';
@@ -45,6 +46,7 @@ export class AIConfigurationContainerWidget extends BaseWidget {
45
46
  protected mcpWidget: AIMCPConfigurationWidget;
46
47
  protected tokenUsageWidget: AITokenUsageConfigurationWidget;
47
48
  protected promptFragmentsWidget: AIPromptFragmentsConfigurationWidget;
49
+ protected toolsWidget: AIToolsConfigurationWidget;
48
50
 
49
51
  @postConstruct()
50
52
  protected init(): void {
@@ -72,12 +74,14 @@ export class AIConfigurationContainerWidget extends BaseWidget {
72
74
  this.mcpWidget = await this.widgetManager.getOrCreateWidget(AIMCPConfigurationWidget.ID);
73
75
  this.tokenUsageWidget = await this.widgetManager.getOrCreateWidget(AITokenUsageConfigurationWidget.ID);
74
76
  this.promptFragmentsWidget = await this.widgetManager.getOrCreateWidget(AIPromptFragmentsConfigurationWidget.ID);
77
+ this.toolsWidget = await this.widgetManager.getOrCreateWidget(AIToolsConfigurationWidget.ID);
75
78
 
76
79
  this.dockpanel.addWidget(this.agentsWidget);
77
80
  this.dockpanel.addWidget(this.variablesWidget, { mode: 'tab-after', ref: this.agentsWidget });
78
81
  this.dockpanel.addWidget(this.mcpWidget, { mode: 'tab-after', ref: this.variablesWidget });
79
82
  this.dockpanel.addWidget(this.tokenUsageWidget, { mode: 'tab-after', ref: this.mcpWidget });
80
83
  this.dockpanel.addWidget(this.promptFragmentsWidget, { mode: 'tab-after', ref: this.tokenUsageWidget });
84
+ this.dockpanel.addWidget(this.toolsWidget, { mode: 'tab-after', ref: this.promptFragmentsWidget });
81
85
 
82
86
  this.update();
83
87
  }
@@ -94,6 +98,8 @@ export class AIConfigurationContainerWidget extends BaseWidget {
94
98
  this.dockpanel.activateWidget(this.tokenUsageWidget);
95
99
  } else if (widgetId === AIPromptFragmentsConfigurationWidget.ID) {
96
100
  this.dockpanel.activateWidget(this.promptFragmentsWidget);
101
+ } else if (widgetId === AIToolsConfigurationWidget.ID) {
102
+ this.dockpanel.activateWidget(this.toolsWidget);
97
103
  }
98
104
  });
99
105
  }
@@ -72,28 +72,73 @@ export class AITokenUsageConfigurationWidget extends ReactWidget {
72
72
  return formatDistanceToNow(date, { addSuffix: true });
73
73
  }
74
74
 
75
+ protected hasCacheData(): boolean {
76
+ return this.tokenUsageData.some(model =>
77
+ model.cachedInputTokens !== undefined ||
78
+ model.readCachedInputTokens !== undefined
79
+ );
80
+ }
81
+
75
82
  protected renderHeaderRow(): React.ReactNode {
83
+ const showCacheColumns = this.hasCacheData();
84
+
76
85
  return (
77
86
  <tr className="token-usage-header">
78
87
  <th className="token-usage-model-column">{nls.localize('theia/ai/tokenUsage/model', 'Model')}</th>
79
88
  <th className="token-usage-column">{nls.localize('theia/ai/tokenUsage/inputTokens', 'Input Tokens')}</th>
89
+ {showCacheColumns && (
90
+ <>
91
+ <th
92
+ className="token-usage-column"
93
+ title={nls.localize(
94
+ 'theia/ai/tokenUsage/cachedInputTokensTooltip',
95
+ "Tracked additionally to 'Input Tokens'. Usually more expensive than non-cached tokens."
96
+ )}
97
+ >
98
+ {nls.localize('theia/ai/tokenUsage/cachedInputTokens', 'Input Tokens Written to Cache')}
99
+ </th>
100
+ <th
101
+ className="token-usage-column"
102
+ title={nls.localize(
103
+ 'theia/ai/tokenUsage/readCachedInputTokensTooltip',
104
+ "Tracked additionally to 'Input Token'. Usually much less expensive than not cached. Usually does not count to rate limits."
105
+ )}
106
+ >
107
+ {nls.localize('theia/ai/tokenUsage/readCachedInputTokens', 'Input Tokens Read From Cache')}
108
+ </th>
109
+ </>
110
+ )}
80
111
  <th className="token-usage-column">{nls.localize('theia/ai/tokenUsage/outputTokens', 'Output Tokens')}</th>
81
- <th className="token-usage-column">{nls.localize('theia/ai/tokenUsage/totalTokens', 'Total Tokens')}</th>
112
+ <th
113
+ className="token-usage-column"
114
+ title={nls.localize('theia/ai/tokenUsage/totalTokensTooltip', "'Input Tokens' + 'Output Tokens'"
115
+ )}
116
+ >
117
+ {nls.localize('theia/ai/tokenUsage/totalTokens', 'Total Tokens')}
118
+ </th>
82
119
  <th className="token-usage-column">{nls.localize('theia/ai/tokenUsage/lastUsed', 'Last Used')}</th>
83
- </tr>
120
+ </tr >
84
121
  );
85
122
  }
86
123
 
87
124
  protected renderModelRow(model: ModelTokenUsageData): React.ReactNode {
88
125
  const lastUsedDate = model.lastUsed ? new Date(model.lastUsed) : undefined;
89
126
  const exactDateString = lastUsedDate ? lastUsedDate.toLocaleString() : '';
127
+ const showCacheColumns = this.hasCacheData();
128
+ const totalTokens = model.inputTokens + model.outputTokens + (model.cachedInputTokens ?? 0);
90
129
 
91
130
  return (
92
131
  <tr key={model.modelId} className="token-usage-row">
93
132
  <td className="token-usage-model-cell">{model.modelId}</td>
94
133
  <td className="token-usage-cell">{this.formatNumber(model.inputTokens)}</td>
134
+ {showCacheColumns && (
135
+ <>
136
+ <td className="token-usage-cell">{model.cachedInputTokens !== undefined ? this.formatNumber(model.cachedInputTokens) : '-'}</td>
137
+ <td className="token-usage-cell">{model.readCachedInputTokens !== undefined ? this.formatNumber(model.readCachedInputTokens) : '-'}</td>
138
+ </>
139
+ )}
95
140
  <td className="token-usage-cell">{this.formatNumber(model.outputTokens)}</td>
96
- <td className="token-usage-cell">{this.formatNumber(model.inputTokens + model.outputTokens)}</td>
141
+ <td className="token-usage-cell">{this.formatNumber(totalTokens)}</td>
97
142
  <td className="token-usage-cell" title={exactDateString}>{this.formatDate(lastUsedDate)}</td>
98
143
  </tr>
99
144
  );
@@ -107,12 +152,26 @@ export class AITokenUsageConfigurationWidget extends ReactWidget {
107
152
 
108
153
  const totalInputTokens = this.tokenUsageData.reduce((sum, model) => sum + model.inputTokens, 0);
109
154
  const totalOutputTokens = this.tokenUsageData.reduce((sum, model) => sum + model.outputTokens, 0);
110
- const totalTokens = totalInputTokens + totalOutputTokens;
155
+ const totalCachedInputTokens = this.tokenUsageData.reduce(
156
+ (sum, model) => sum + (model.cachedInputTokens || 0), 0
157
+ );
158
+ const totalReadCachedInputTokens = this.tokenUsageData.reduce(
159
+ (sum, model) => sum + (model.readCachedInputTokens || 0), 0
160
+ );
161
+ const totalTokens = totalInputTokens + totalCachedInputTokens + totalOutputTokens;
162
+
163
+ const showCacheColumns = this.hasCacheData();
111
164
 
112
165
  return (
113
166
  <tr className="token-usage-summary-row">
114
167
  <td className="token-usage-model-cell"><strong>{nls.localize('theia/ai/tokenUsage/total', 'Total')}</strong></td>
115
168
  <td className="token-usage-cell"><strong>{this.formatNumber(totalInputTokens)}</strong></td>
169
+ {showCacheColumns && (
170
+ <>
171
+ <td className="token-usage-cell"><strong>{this.formatNumber(totalCachedInputTokens)}</strong></td>
172
+ <td className="token-usage-cell"><strong>{this.formatNumber(totalReadCachedInputTokens)}</strong></td>
173
+ </>
174
+ )}
116
175
  <td className="token-usage-cell"><strong>{this.formatNumber(totalOutputTokens)}</strong></td>
117
176
  <td className="token-usage-cell"><strong>{this.formatNumber(totalTokens)}</strong></td>
118
177
  <td className="token-usage-cell"></td>
@@ -0,0 +1,193 @@
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 { PreferenceService, ReactWidget, ConfirmDialog } from '@theia/core/lib/browser';
18
+ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
19
+ import * as React from '@theia/core/shared/react';
20
+ import { ToolConfirmationManager, ToolConfirmationMode } from '@theia/ai-chat/lib/browser/chat-tool-preferences';
21
+ import { ToolInvocationRegistry } from '@theia/ai-core';
22
+
23
+ const TOOL_OPTIONS: { value: ToolConfirmationMode, label: string, icon: string, color: string }[] = [
24
+ { value: ToolConfirmationMode.DISABLED, label: 'Disabled', icon: 'close', color: 'var(--theia-errorForeground)' },
25
+ { value: ToolConfirmationMode.CONFIRM, label: 'Confirm', icon: 'question', color: 'var(--theia-descriptionForeground)' },
26
+ { value: ToolConfirmationMode.YOLO, label: 'Yolo', icon: 'thumbsup', color: 'var(--theia-successForeground)' },
27
+ ];
28
+
29
+ @injectable()
30
+ export class AIToolsConfigurationWidget extends ReactWidget {
31
+ static readonly ID = 'ai-tools-configuration-widget';
32
+ static readonly LABEL = 'Tools';
33
+
34
+ @inject(ToolConfirmationManager)
35
+ protected readonly confirmationManager: ToolConfirmationManager;
36
+
37
+ @inject(PreferenceService)
38
+ protected readonly preferenceService: PreferenceService;
39
+
40
+ @inject(ToolInvocationRegistry)
41
+ protected readonly toolInvocationRegistry: ToolInvocationRegistry;
42
+
43
+ // Mocked tool list and state
44
+ protected tools: string[] = [];
45
+ protected toolConfirmationModes: Record<string, ToolConfirmationMode> = {};
46
+ protected defaultState: ToolConfirmationMode;
47
+ protected loading = true;
48
+
49
+ @postConstruct()
50
+ protected init(): void {
51
+ this.id = AIToolsConfigurationWidget.ID;
52
+ this.title.label = AIToolsConfigurationWidget.LABEL;
53
+ this.title.closable = false;
54
+ this.loadData();
55
+ this.update();
56
+ this.toDispose.pushAll([
57
+ this.preferenceService.onPreferenceChanged(async e => {
58
+ if (e.preferenceName === 'ai-features.chat.toolConfirmation') {
59
+ this.defaultState = await this.loadDefaultConfirmation();
60
+ this.toolConfirmationModes = await this.loadToolConfigurationModes();
61
+ this.update();
62
+ }
63
+ }),
64
+ this.toolInvocationRegistry.onDidChange(async () => {
65
+ this.tools = await this.loadTools();
66
+ this.update();
67
+ })
68
+ ]);
69
+ }
70
+
71
+ protected async loadData(): Promise<void> {
72
+ // Replace with real service calls
73
+ this.tools = await this.loadTools();
74
+ this.defaultState = await this.loadDefaultConfirmation();
75
+ this.toolConfirmationModes = await this.loadToolConfigurationModes();
76
+ this.loading = false;
77
+ this.update();
78
+ }
79
+
80
+ protected async loadTools(): Promise<string[]> {
81
+ return this.toolInvocationRegistry.getAllFunctions().map(func => func.name);
82
+ }
83
+ protected async loadDefaultConfirmation(): Promise<ToolConfirmationMode> {
84
+ return this.confirmationManager.getConfirmationMode('*', 'doesNotMatter');
85
+ }
86
+ protected async loadToolConfigurationModes(): Promise<Record<string, ToolConfirmationMode>> {
87
+ return this.confirmationManager.getAllConfirmationSettings();
88
+ }
89
+ protected async updateToolConfirmationMode(tool: string, state: ToolConfirmationMode): Promise<void> {
90
+ await this.confirmationManager.setConfirmationMode(tool, state);
91
+ }
92
+ protected async updateDefaultConfirmation(state: ToolConfirmationMode): Promise<void> {
93
+ await this.confirmationManager.setConfirmationMode('*', state);
94
+ }
95
+
96
+ protected handleToolConfirmationModeChange = async (tool: string, event: React.ChangeEvent<HTMLSelectElement>) => {
97
+ const newState = event.target.value as ToolConfirmationMode;
98
+ await this.updateToolConfirmationMode(tool, newState);
99
+ };
100
+ protected handleDefaultStateChange = async (event: React.ChangeEvent<HTMLSelectElement>) => {
101
+ const newState = event.target.value as ToolConfirmationMode;
102
+ await this.updateDefaultConfirmation(newState);
103
+ };
104
+
105
+ protected getColoring(mode: ToolConfirmationMode, renderDefault = false): string {
106
+ if (!renderDefault && mode === this.defaultState) {
107
+ return '';
108
+ }
109
+ if (mode === ToolConfirmationMode.YOLO) {
110
+ return ' ai-tools-configuration-tool-select--yolo';
111
+ } else if (mode === ToolConfirmationMode.DISABLED) {
112
+ return ' ai-tools-configuration-tool-select--disabled';
113
+ }
114
+ return ' ai-tools-configuration-tool-select--confirm';
115
+ }
116
+
117
+ protected async resetAllToolsToDefault(): Promise<void> {
118
+ const dialog = new ConfirmDialog({
119
+ title: 'Reset All Tool Confirmation Modes',
120
+ msg: 'Are you sure you want to reset all tool confirmation modes to the default? This will remove all custom settings.',
121
+ ok: 'Reset All',
122
+ cancel: 'Cancel'
123
+ });
124
+ const shouldReset = await dialog.open();
125
+ if (shouldReset) {
126
+ this.confirmationManager.resetAllConfirmationModeSettings();
127
+ }
128
+ }
129
+
130
+ protected render(): React.ReactNode {
131
+ if (this.loading) {
132
+ return <div>Loading tools...</div>;
133
+ }
134
+ return <div className='ai-tools-configuration-container'>
135
+ <div className='ai-tools-configuration-default-section' style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
136
+ <div className='ai-tools-configuration-default-label'>Default Tool Confirmation Mode:</div>
137
+ <select
138
+ className={`ai-tools-configuration-default-select ${this.getColoring(this.defaultState, true)}`}
139
+ value={this.defaultState}
140
+ onChange={this.handleDefaultStateChange}
141
+ style={{ marginLeft: 8 }}
142
+ >
143
+ {TOOL_OPTIONS.map(opt => (
144
+ <option className={this.getColoring(opt.value, true)} key={opt.value} value={opt.value}>{opt.label}</option>
145
+ ))}
146
+ </select>
147
+ <button
148
+ className='ai-tools-configuration-reset-btn'
149
+ style={{ marginLeft: 'auto' }}
150
+ title='Reset all tools to default'
151
+ onClick={() => this.resetAllToolsToDefault()}
152
+ >
153
+ Reset All
154
+ </button>
155
+ </div>
156
+ <div className='ai-tools-configuration-tools-section'>
157
+ <div className='ai-tools-configuration-tools-label'>Tools</div>
158
+ <ul className='ai-tools-configuration-tools-list'>
159
+ {this.tools.map(tool => {
160
+ const state = this.toolConfirmationModes[tool] || this.defaultState;
161
+ const isDefault = state === this.defaultState;
162
+ let selectClass = 'ai-tools-configuration-tool-select';
163
+ if (!isDefault) {
164
+ selectClass += `${this.getColoring(state)}`;
165
+ }
166
+ return (
167
+ <li
168
+ key={tool}
169
+ className={
170
+ 'ai-tools-configuration-tool-item ' +
171
+ (isDefault ? 'default' : 'custom')
172
+ }
173
+ >
174
+ <span className='ai-tools-configuration-tool-name'>{tool}</span>
175
+ <select
176
+ className={selectClass}
177
+ value={state}
178
+ onChange={e => this.handleToolConfirmationModeChange(tool, e)}
179
+ >
180
+ {TOOL_OPTIONS.map(opt => (
181
+ <option className={this.getColoring(opt.value, true)} key={opt.value} value={opt.value}>
182
+ {opt.label}
183
+ </option>
184
+ ))}
185
+ </select>
186
+ </li>
187
+ );
188
+ })}
189
+ </ul>
190
+ </div>
191
+ </div>;
192
+ }
193
+ }
@@ -0,0 +1,178 @@
1
+ /* eslint-disable max-len */
2
+
3
+ // *****************************************************************************
4
+ // Copyright (C) 2024 EclipseSource GmbH.
5
+ //
6
+ // This program and the accompanying materials are made available under the
7
+ // terms of the Eclipse Public License v. 2.0 which is available at
8
+ // http://www.eclipse.org/legal/epl-2.0.
9
+ //
10
+ // This Source Code may also be made available under the following Secondary
11
+ // Licenses when the conditions for such availability set forth in the Eclipse
12
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
13
+ // with the GNU Classpath Exception which is available at
14
+ // https://www.gnu.org/software/classpath/license.html.
15
+ //
16
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
17
+ // *****************************************************************************
18
+
19
+ import { CHAT_CONTEXT_DETAILS_VARIABLE_ID } from '@theia/ai-chat';
20
+ import { AbstractStreamParsingChatAgent } from '@theia/ai-chat/lib/common/chat-agents';
21
+ import { ErrorChatResponseContentImpl, MarkdownChatResponseContentImpl, MutableChatRequestModel, QuestionResponseContentImpl } from '@theia/ai-chat/lib/common/chat-model';
22
+ import { BasePromptFragment, LanguageModelRequirement } from '@theia/ai-core/lib/common';
23
+ import { MCPFrontendService, MCPServerDescription } from '@theia/ai-mcp/lib/common/mcp-server-manager';
24
+ import { nls } from '@theia/core';
25
+ import { inject, injectable } from '@theia/core/shared/inversify';
26
+ import { MCP_SERVERS_PREF } from '@theia/ai-mcp/lib/browser/mcp-preferences';
27
+ import { PreferenceScope, PreferenceService } from '@theia/core/lib/browser';
28
+
29
+ export const EXPECTED_MCP_SERVER_NAME = 'playwright';
30
+
31
+ // Prompt templates
32
+ export const appTesterTemplate: BasePromptFragment = {
33
+ id: 'app-tester-system-default',
34
+ template: `{{!-- This prompt is licensed under the MIT License (https://opensource.org/license/mit).
35
+ Made improvements or adaptations to this prompt template? We'd love for you to share it with the community! Contribute back here:
36
+ https://github.com/eclipse-theia/theia/discussions/new?category=prompt-template-contribution --}}
37
+
38
+ You are AppTester, an AI assistant integrated into Theia IDE specifically designed to help developers test running applications using Playwright.
39
+ Your role is to inspect the application for user-specified test scenarios through the Playwright MCP server.
40
+
41
+ ## Your Workflow
42
+ 1. Help the user build and launch their application
43
+ 2. Use Playwright browser automation to validate test scenarios
44
+ 3. Report results and provide actionable feedback
45
+ 4. Help fix issues when needed
46
+
47
+ ## Available Playwright Testing Tools
48
+ You have access to these powerful automation tools: {{prompt:mcp_${EXPECTED_MCP_SERVER_NAME}_tools}}
49
+
50
+ ## Workflow Approach
51
+ 1. **Understand Requirements**: Ask the user to clearly define what needs to be tested
52
+ 2. **Launch Browser**: Start a fresh browser instance for testing
53
+ 3. **Navigate and Test**: Execute the test scenario methodically
54
+ 4. **Document Results**: Provide detailed results with screenshots when helpful
55
+ 5. **Clean Up**: Always close the browser when testing is complete
56
+
57
+ ## Current Context
58
+ Some files and other pieces of data may have been added by the user to the context of the chat. If any have, the details can be found below.
59
+ {{${CHAT_CONTEXT_DETAILS_VARIABLE_ID}}}
60
+ `
61
+ };
62
+
63
+ export const appTesterTemplateVariant: BasePromptFragment = {
64
+ id: 'app-tester-system-empty',
65
+ template: '',
66
+ };
67
+
68
+ export const AppTesterChatAgentId = 'AppTester';
69
+ @injectable()
70
+ export class AppTesterChatAgent extends AbstractStreamParsingChatAgent {
71
+
72
+ @inject(MCPFrontendService)
73
+ protected readonly mcpService: MCPFrontendService;
74
+
75
+ @inject(PreferenceService)
76
+ protected readonly preferenceService: PreferenceService;
77
+
78
+ id: string = AppTesterChatAgentId;
79
+ name = AppTesterChatAgentId;
80
+ languageModelRequirements: LanguageModelRequirement[] = [{
81
+ purpose: 'chat',
82
+ identifier: 'openai/gpt-4o',
83
+ }];
84
+ protected defaultLanguageModelPurpose: string = 'chat';
85
+ override description = nls.localize('theia/ai/chat/app-tester/description', 'This agent tests your application user interface to verify user-specified test scenarios through the Playwright MCP server. '
86
+ + 'It can automate testing workflows and provide detailed feedback on application functionality.');
87
+
88
+ override iconClass: string = 'codicon codicon-beaker';
89
+ protected override systemPromptId: string = 'app-tester-prompt';
90
+ override prompts = [{ id: 'app-tester-prompt', defaultVariant: appTesterTemplate, variants: [appTesterTemplateVariant] }];
91
+
92
+ /**
93
+ * Override invoke to check if the Playwright MCP server is running, and if not, ask the user if it should be started.
94
+ */
95
+ override async invoke(request: MutableChatRequestModel): Promise<void> {
96
+ try {
97
+ const startedServers = await this.mcpService.getStartedServers();
98
+ if (!startedServers.includes(EXPECTED_MCP_SERVER_NAME)) {
99
+ // Ask the user if they want to start the server
100
+ request.response.response.addContent(new QuestionResponseContentImpl(
101
+ 'The Playwright MCP server is not running. Would you like to start it now? This may install the Playwright MCP server.',
102
+ [
103
+ { text: 'Yes, start the server', value: 'yes' },
104
+ { text: 'No, cancel', value: 'no' }
105
+ ],
106
+ request,
107
+ async selectedOption => {
108
+ if (selectedOption.value === 'yes') {
109
+ // Show progress
110
+ const progress = request.response.addProgressMessage({ content: 'Starting Playwright MCP server.', show: 'whileIncomplete' });
111
+ try {
112
+ await this.startPlaywrightMCPServer();
113
+ // Remove progress, continue with normal flow
114
+ request.response.updateProgressMessage({ ...progress, show: 'whileIncomplete', status: 'completed' });
115
+ await super.invoke(request);
116
+ } catch (error) {
117
+ request.response.response.addContent(new ErrorChatResponseContentImpl(
118
+ new Error('Failed to start Playwright MCP server: ' + (error instanceof Error ? error.message : String(error)))
119
+ ));
120
+ request.response.complete();
121
+ }
122
+ } else {
123
+ // Continue without starting the server
124
+ request.response.response.addContent(new MarkdownChatResponseContentImpl('Please setup the MCP server.'));
125
+ request.response.complete();
126
+ }
127
+ }
128
+ ));
129
+ request.response.waitForInput();
130
+ return;
131
+ }
132
+ // If already running, continue as normal
133
+ await super.invoke(request);
134
+ } catch (error) {
135
+ request.response.response.addContent(new ErrorChatResponseContentImpl(
136
+ new Error('Error checking Playwright MCP server status: ' + (error instanceof Error ? error.message : String(error)))
137
+ ));
138
+ request.response.complete();
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Starts the Playwright MCP server if it doesn't exist or isn't running.
144
+ *
145
+ * @returns A promise that resolves when the server is started
146
+ */
147
+ async startPlaywrightMCPServer(): Promise<void> {
148
+ try {
149
+ const startedServers = await this.mcpService.getStartedServers();
150
+ if (startedServers.includes(EXPECTED_MCP_SERVER_NAME)) {
151
+ return;
152
+ }
153
+
154
+ const mcpServer: MCPServerDescription = {
155
+ name: EXPECTED_MCP_SERVER_NAME,
156
+ command: 'npx',
157
+ args: ['-y', '@playwright/mcp@latest'],
158
+ autostart: false,
159
+ env: {},
160
+ };
161
+
162
+ const availableServers = await this.mcpService.getServerNames();
163
+ if (!availableServers.includes(EXPECTED_MCP_SERVER_NAME)) {
164
+ const currentServers = this.preferenceService.get<Record<string, MCPServerDescription>>(MCP_SERVERS_PREF, {});
165
+ await this.preferenceService.set(MCP_SERVERS_PREF, {
166
+ ...currentServers,
167
+ mcpServer
168
+ }, PreferenceScope.User);
169
+
170
+ await this.mcpService.addOrUpdateServer(mcpServer);
171
+ }
172
+ await this.mcpService.startServer(EXPECTED_MCP_SERVER_NAME);
173
+ } catch (error) {
174
+ this.logger.error(`Error starting Playwright MCP server: ${error}`);
175
+ throw error;
176
+ }
177
+ }
178
+ }
@@ -16,8 +16,8 @@
16
16
  import { AbstractStreamParsingChatAgent, ChatRequestModel, ChatService, ChatSession, MutableChatModel, MutableChatRequestModel } from '@theia/ai-chat/lib/common';
17
17
  import { inject, injectable } from '@theia/core/shared/inversify';
18
18
  import { FILE_CONTENT_FUNCTION_ID, GET_WORKSPACE_FILE_LIST_FUNCTION_ID, GET_WORKSPACE_DIRECTORY_STRUCTURE_FUNCTION_ID } from '../common/workspace-functions';
19
- import { CODER_SYSTEM_PROMPT_ID, getCoderAgentModePromptTemplate, getCoderReplacePromptTemplate, getCoderReplacePromptTemplateNext } from '../common/coder-replace-prompt-template';
20
- import { WriteChangeToFileProvider } from './file-changeset-functions';
19
+ import { CODER_SYSTEM_PROMPT_ID, getCoderAgentModePromptTemplate, getCoderPromptTemplateEdit, getCoderPromptTemplateSimpleEdit } from '../common/coder-replace-prompt-template';
20
+ import { SuggestFileContent } from './file-changeset-functions';
21
21
  import { LanguageModelRequirement, PromptVariantSet } from '@theia/ai-core';
22
22
  import { nls } from '@theia/core';
23
23
  import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering';
@@ -40,10 +40,10 @@ export class CoderAgent extends AbstractStreamParsingChatAgent {
40
40
  tasks involving file changes.');
41
41
  override prompts: PromptVariantSet[] = [{
42
42
  id: CODER_SYSTEM_PROMPT_ID,
43
- defaultVariant: getCoderReplacePromptTemplate(true),
44
- variants: [getCoderReplacePromptTemplate(false), getCoderReplacePromptTemplateNext(), getCoderAgentModePromptTemplate()]
43
+ defaultVariant: getCoderPromptTemplateEdit(),
44
+ variants: [getCoderPromptTemplateSimpleEdit(), getCoderAgentModePromptTemplate()]
45
45
  }];
46
- override functions = [GET_WORKSPACE_DIRECTORY_STRUCTURE_FUNCTION_ID, GET_WORKSPACE_FILE_LIST_FUNCTION_ID, FILE_CONTENT_FUNCTION_ID, WriteChangeToFileProvider.ID];
46
+ override functions = [GET_WORKSPACE_DIRECTORY_STRUCTURE_FUNCTION_ID, GET_WORKSPACE_FILE_LIST_FUNCTION_ID, FILE_CONTENT_FUNCTION_ID, SuggestFileContent.ID];
47
47
  protected override systemPromptId: string | undefined = CODER_SYSTEM_PROMPT_ID;
48
48
  override async invoke(request: MutableChatRequestModel): Promise<void> {
49
49
  await super.invoke(request);