@theia/ai-ide 1.63.0-next.0 → 1.63.0-next.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-configuration/ai-configuration-view-contribution.js +1 -1
- package/lib/browser/ai-configuration/ai-configuration-view-contribution.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +2 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.js +6 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts +0 -1
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js +0 -1
- package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js.map +1 -1
- package/lib/browser/ai-configuration/token-usage-configuration-widget.d.ts +1 -0
- package/lib/browser/ai-configuration/token-usage-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/token-usage-configuration-widget.js +25 -3
- package/lib/browser/ai-configuration/token-usage-configuration-widget.js.map +1 -1
- package/lib/browser/ai-configuration/tools-configuration-widget.d.ts +28 -0
- package/lib/browser/ai-configuration/tools-configuration-widget.d.ts.map +1 -0
- package/lib/browser/ai-configuration/tools-configuration-widget.js +148 -0
- package/lib/browser/ai-configuration/tools-configuration-widget.js.map +1 -0
- package/lib/browser/app-tester-chat-agent.d.ts +36 -0
- package/lib/browser/app-tester-chat-agent.d.ts.map +1 -0
- package/lib/browser/app-tester-chat-agent.js +172 -0
- package/lib/browser/app-tester-chat-agent.js.map +1 -0
- package/lib/browser/architect-agent.d.ts.map +1 -1
- package/lib/browser/architect-agent.js +4 -1
- package/lib/browser/architect-agent.js.map +1 -1
- package/lib/browser/coder-agent.d.ts.map +1 -1
- package/lib/browser/coder-agent.js +3 -3
- package/lib/browser/coder-agent.js.map +1 -1
- package/lib/browser/file-changeset-functions.d.ts +25 -6
- package/lib/browser/file-changeset-functions.d.ts.map +1 -1
- package/lib/browser/file-changeset-functions.js +248 -106
- package/lib/browser/file-changeset-functions.js.map +1 -1
- package/lib/browser/frontend-module.d.ts +1 -0
- package/lib/browser/frontend-module.d.ts.map +1 -1
- package/lib/browser/frontend-module.js +21 -5
- package/lib/browser/frontend-module.js.map +1 -1
- package/lib/browser/summarize-session-command-contribution.d.ts +6 -1
- package/lib/browser/summarize-session-command-contribution.d.ts.map +1 -1
- package/lib/browser/summarize-session-command-contribution.js +53 -4
- package/lib/browser/summarize-session-command-contribution.js.map +1 -1
- package/lib/browser/task-context-file-storage-service.d.ts +4 -2
- package/lib/browser/task-context-file-storage-service.d.ts.map +1 -1
- package/lib/browser/task-context-file-storage-service.js +19 -9
- package/lib/browser/task-context-file-storage-service.js.map +1 -1
- package/lib/browser/workspace-functions.d.ts.map +1 -1
- package/lib/browser/workspace-functions.js +6 -10
- package/lib/browser/workspace-functions.js.map +1 -1
- package/lib/browser/workspace-preferences.d.ts +1 -0
- package/lib/browser/workspace-preferences.d.ts.map +1 -1
- package/lib/browser/workspace-preferences.js +9 -1
- package/lib/browser/workspace-preferences.js.map +1 -1
- package/lib/browser/workspace-search-provider.d.ts +7 -1
- package/lib/browser/workspace-search-provider.d.ts.map +1 -1
- package/lib/browser/workspace-search-provider.js +73 -11
- package/lib/browser/workspace-search-provider.js.map +1 -1
- package/lib/browser/workspace-search-provider.spec.d.ts +2 -0
- package/lib/browser/workspace-search-provider.spec.d.ts.map +1 -0
- package/lib/browser/workspace-search-provider.spec.js +227 -0
- package/lib/browser/workspace-search-provider.spec.js.map +1 -0
- package/lib/common/architect-prompt-template.d.ts +1 -0
- package/lib/common/architect-prompt-template.d.ts.map +1 -1
- package/lib/common/architect-prompt-template.js +166 -5
- package/lib/common/architect-prompt-template.js.map +1 -1
- package/lib/common/coder-replace-prompt-template.d.ts +4 -5
- package/lib/common/coder-replace-prompt-template.d.ts.map +1 -1
- package/lib/common/coder-replace-prompt-template.js +95 -67
- package/lib/common/coder-replace-prompt-template.js.map +1 -1
- package/lib/common/file-changeset-function-ids.d.ts +7 -0
- package/lib/common/file-changeset-function-ids.d.ts.map +1 -0
- package/lib/common/file-changeset-function-ids.js +25 -0
- package/lib/common/file-changeset-function-ids.js.map +1 -0
- package/lib/common/summarize-session-commands.d.ts +1 -0
- package/lib/common/summarize-session-commands.d.ts.map +1 -1
- package/lib/common/summarize-session-commands.js +5 -1
- package/lib/common/summarize-session-commands.js.map +1 -1
- package/lib/common/workspace-search-provider-util.d.ts +17 -0
- package/lib/common/workspace-search-provider-util.d.ts.map +1 -0
- package/lib/common/workspace-search-provider-util.js +51 -0
- package/lib/common/workspace-search-provider-util.js.map +1 -0
- package/package.json +19 -18
- package/src/browser/ai-configuration/ai-configuration-view-contribution.ts +1 -1
- package/src/browser/ai-configuration/ai-configuration-widget.tsx +6 -1
- package/src/browser/ai-configuration/prompt-fragments-configuration-widget.tsx +0 -1
- package/src/browser/ai-configuration/token-usage-configuration-widget.tsx +63 -4
- package/src/browser/ai-configuration/tools-configuration-widget.tsx +178 -0
- package/src/browser/app-tester-chat-agent.ts +178 -0
- package/src/browser/architect-agent.ts +5 -2
- package/src/browser/coder-agent.ts +5 -5
- package/src/browser/file-changeset-functions.ts +236 -89
- package/src/browser/frontend-module.ts +33 -10
- package/src/browser/style/index.css +84 -11
- package/src/browser/summarize-session-command-contribution.ts +58 -6
- package/src/browser/task-context-file-storage-service.ts +20 -10
- package/src/browser/workspace-functions.ts +7 -11
- package/src/browser/workspace-preferences.ts +9 -0
- package/src/browser/workspace-search-provider.spec.ts +255 -0
- package/src/browser/workspace-search-provider.ts +78 -11
- package/src/common/architect-prompt-template.ts +165 -5
- package/src/common/coder-replace-prompt-template.ts +101 -65
- package/src/common/file-changeset-function-ids.ts +22 -0
- package/src/common/summarize-session-commands.ts +5 -0
- package/src/common/workspace-search-provider-util.ts +50 -0
|
@@ -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
|
|
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(
|
|
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
|
|
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,178 @@
|
|
|
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 }[] = [
|
|
24
|
+
{ value: ToolConfirmationMode.DISABLED, label: 'Disabled', icon: 'close' },
|
|
25
|
+
{ value: ToolConfirmationMode.CONFIRM, label: 'Confirm', icon: 'question' },
|
|
26
|
+
{ value: ToolConfirmationMode.ALWAYS_ALLOW, label: 'Always Allow', icon: 'thumbsup' },
|
|
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 async resetAllToolsToDefault(): Promise<void> {
|
|
106
|
+
const dialog = new ConfirmDialog({
|
|
107
|
+
title: 'Reset All Tool Confirmation Modes',
|
|
108
|
+
msg: 'Are you sure you want to reset all tool confirmation modes to the default? This will remove all custom settings.',
|
|
109
|
+
ok: 'Reset All',
|
|
110
|
+
cancel: 'Cancel'
|
|
111
|
+
});
|
|
112
|
+
const shouldReset = await dialog.open();
|
|
113
|
+
if (shouldReset) {
|
|
114
|
+
this.confirmationManager.resetAllConfirmationModeSettings();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
protected render(): React.ReactNode {
|
|
119
|
+
if (this.loading) {
|
|
120
|
+
return <div>Loading tools...</div>;
|
|
121
|
+
}
|
|
122
|
+
return <div className='ai-tools-configuration-container'>
|
|
123
|
+
<div className='ai-tools-configuration-default-section' style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
124
|
+
<div className='ai-tools-configuration-default-label'>Default Tool Confirmation Mode:</div>
|
|
125
|
+
<select
|
|
126
|
+
className="ai-tools-configuration-default-select"
|
|
127
|
+
value={this.defaultState}
|
|
128
|
+
onChange={this.handleDefaultStateChange}
|
|
129
|
+
style={{ marginLeft: 8 }}
|
|
130
|
+
>
|
|
131
|
+
{TOOL_OPTIONS.map(opt => (
|
|
132
|
+
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
133
|
+
))}
|
|
134
|
+
</select>
|
|
135
|
+
<button
|
|
136
|
+
className='ai-tools-configuration-reset-btn'
|
|
137
|
+
style={{ marginLeft: 'auto' }}
|
|
138
|
+
title='Reset all tools to default'
|
|
139
|
+
onClick={() => this.resetAllToolsToDefault()}
|
|
140
|
+
>
|
|
141
|
+
Reset All
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
<div className='ai-tools-configuration-tools-section'>
|
|
145
|
+
<div className='ai-tools-configuration-tools-label'>Tools</div>
|
|
146
|
+
<ul className='ai-tools-configuration-tools-list'>
|
|
147
|
+
{this.tools.map(tool => {
|
|
148
|
+
const state = this.toolConfirmationModes[tool] || this.defaultState;
|
|
149
|
+
const isDefault = state === this.defaultState;
|
|
150
|
+
const selectClass = 'ai-tools-configuration-tool-select';
|
|
151
|
+
return (
|
|
152
|
+
<li
|
|
153
|
+
key={tool}
|
|
154
|
+
className={
|
|
155
|
+
'ai-tools-configuration-tool-item ' +
|
|
156
|
+
(isDefault ? 'default' : 'custom')
|
|
157
|
+
}
|
|
158
|
+
>
|
|
159
|
+
<span className='ai-tools-configuration-tool-name'>{tool}</span>
|
|
160
|
+
<select
|
|
161
|
+
className={selectClass}
|
|
162
|
+
value={state}
|
|
163
|
+
onChange={e => this.handleToolConfirmationModeChange(tool, e)}
|
|
164
|
+
>
|
|
165
|
+
{TOOL_OPTIONS.map(opt => (
|
|
166
|
+
<option key={opt.value} value={opt.value}>
|
|
167
|
+
{opt.label}
|
|
168
|
+
</option>
|
|
169
|
+
))}
|
|
170
|
+
</select>
|
|
171
|
+
</li>
|
|
172
|
+
);
|
|
173
|
+
})}
|
|
174
|
+
</ul>
|
|
175
|
+
</div>
|
|
176
|
+
</div>;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -20,7 +20,7 @@ import { architectVariants } from '../common/architect-prompt-template';
|
|
|
20
20
|
import { FILE_CONTENT_FUNCTION_ID, GET_WORKSPACE_FILE_LIST_FUNCTION_ID } from '../common/workspace-functions';
|
|
21
21
|
import { nls } from '@theia/core';
|
|
22
22
|
import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering';
|
|
23
|
-
import { AI_SUMMARIZE_SESSION_AS_TASK_FOR_CODER } from '../common/summarize-session-commands';
|
|
23
|
+
import { AI_SUMMARIZE_SESSION_AS_TASK_FOR_CODER, AI_UPDATE_TASK_CONTEXT_COMMAND } from '../common/summarize-session-commands';
|
|
24
24
|
|
|
25
25
|
@injectable()
|
|
26
26
|
export class ArchitectAgent extends AbstractStreamParsingChatAgent {
|
|
@@ -52,7 +52,10 @@ export class ArchitectAgent extends AbstractStreamParsingChatAgent {
|
|
|
52
52
|
const session = this.chatService.getSessions().find(candidate => candidate.model.id === model.id);
|
|
53
53
|
if (!(model instanceof MutableChatModel) || !session) { return; }
|
|
54
54
|
if (!model.isEmpty()) {
|
|
55
|
-
model.setSuggestions([
|
|
55
|
+
model.setSuggestions([
|
|
56
|
+
new MarkdownStringImpl(`[Summarize this session as a task for Coder](command:${AI_SUMMARIZE_SESSION_AS_TASK_FOR_CODER.id}).`),
|
|
57
|
+
new MarkdownStringImpl(`[Update current task context](command:${AI_UPDATE_TASK_CONTEXT_COMMAND.id}).`)
|
|
58
|
+
]);
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
@@ -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,
|
|
20
|
-
import {
|
|
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:
|
|
44
|
-
variants: [
|
|
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,
|
|
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);
|