@theia/ai-ide 1.64.0-next.28 → 1.64.0

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 (152) hide show
  1. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts +5 -2
  2. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts.map +1 -1
  3. package/lib/browser/ai-configuration/agent-configuration-widget.js +15 -1
  4. package/lib/browser/ai-configuration/agent-configuration-widget.js.map +1 -1
  5. package/lib/browser/ai-configuration/ai-configuration-service.d.ts +6 -1
  6. package/lib/browser/ai-configuration/ai-configuration-service.d.ts.map +1 -1
  7. package/lib/browser/ai-configuration/ai-configuration-service.js +10 -1
  8. package/lib/browser/ai-configuration/ai-configuration-service.js.map +1 -1
  9. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +2 -0
  10. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
  11. package/lib/browser/ai-configuration/ai-configuration-widget.js +7 -1
  12. package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
  13. package/lib/browser/ai-configuration/language-model-renderer.d.ts +4 -2
  14. package/lib/browser/ai-configuration/language-model-renderer.d.ts.map +1 -1
  15. package/lib/browser/ai-configuration/language-model-renderer.js +49 -71
  16. package/lib/browser/ai-configuration/language-model-renderer.js.map +1 -1
  17. package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts +41 -0
  18. package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts.map +1 -0
  19. package/lib/browser/ai-configuration/model-aliases-configuration-widget.js +225 -0
  20. package/lib/browser/ai-configuration/model-aliases-configuration-widget.js.map +1 -0
  21. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts +7 -3
  22. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts.map +1 -1
  23. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js +35 -13
  24. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js.map +1 -1
  25. package/lib/browser/ai-configuration/template-settings-renderer.d.ts.map +1 -1
  26. package/lib/browser/ai-configuration/template-settings-renderer.js +11 -6
  27. package/lib/browser/ai-configuration/template-settings-renderer.js.map +1 -1
  28. package/lib/browser/ai-ide-activation-service.d.ts +18 -0
  29. package/lib/browser/ai-ide-activation-service.d.ts.map +1 -0
  30. package/lib/browser/ai-ide-activation-service.js +72 -0
  31. package/lib/browser/ai-ide-activation-service.js.map +1 -0
  32. package/lib/browser/ai-ide-preferences.d.ts +4 -0
  33. package/lib/browser/ai-ide-preferences.d.ts.map +1 -0
  34. package/lib/browser/ai-ide-preferences.js +43 -0
  35. package/lib/browser/ai-ide-preferences.js.map +1 -0
  36. package/lib/browser/app-tester-chat-agent.d.ts +3 -6
  37. package/lib/browser/app-tester-chat-agent.d.ts.map +1 -1
  38. package/lib/browser/app-tester-chat-agent.js +6 -71
  39. package/lib/browser/app-tester-chat-agent.js.map +1 -1
  40. package/lib/browser/app-tester-prompt-template.d.ts +6 -0
  41. package/lib/browser/app-tester-prompt-template.d.ts.map +1 -0
  42. package/lib/browser/app-tester-prompt-template.js +79 -0
  43. package/lib/browser/app-tester-prompt-template.js.map +1 -0
  44. package/lib/browser/architect-agent.js +1 -1
  45. package/lib/browser/architect-agent.js.map +1 -1
  46. package/lib/browser/coder-agent.js +1 -1
  47. package/lib/browser/coder-agent.js.map +1 -1
  48. package/lib/browser/context-functions.d.ts.map +1 -1
  49. package/lib/browser/context-functions.js +12 -0
  50. package/lib/browser/context-functions.js.map +1 -1
  51. package/lib/browser/context-functions.spec.d.ts +2 -0
  52. package/lib/browser/context-functions.spec.d.ts.map +1 -0
  53. package/lib/browser/context-functions.spec.js +93 -0
  54. package/lib/browser/context-functions.spec.js.map +1 -0
  55. package/lib/browser/file-changeset-function.spec.d.ts +2 -0
  56. package/lib/browser/file-changeset-function.spec.d.ts.map +1 -0
  57. package/lib/browser/file-changeset-function.spec.js +45 -0
  58. package/lib/browser/file-changeset-function.spec.js.map +1 -0
  59. package/lib/browser/file-changeset-functions.d.ts +13 -3
  60. package/lib/browser/file-changeset-functions.d.ts.map +1 -1
  61. package/lib/browser/file-changeset-functions.js +100 -29
  62. package/lib/browser/file-changeset-functions.js.map +1 -1
  63. package/lib/browser/file-changeset-functions.spec.d.ts +2 -0
  64. package/lib/browser/file-changeset-functions.spec.d.ts.map +1 -0
  65. package/lib/browser/file-changeset-functions.spec.js +161 -0
  66. package/lib/browser/file-changeset-functions.spec.js.map +1 -0
  67. package/lib/browser/frontend-module.d.ts.map +1 -1
  68. package/lib/browser/frontend-module.js +20 -0
  69. package/lib/browser/frontend-module.js.map +1 -1
  70. package/lib/browser/ide-chat-welcome-message-provider.js +2 -2
  71. package/lib/browser/ide-chat-welcome-message-provider.js.map +1 -1
  72. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts +2 -0
  73. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts.map +1 -0
  74. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js +52 -0
  75. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js.map +1 -0
  76. package/lib/browser/workspace-functions.d.ts +3 -3
  77. package/lib/browser/workspace-functions.d.ts.map +1 -1
  78. package/lib/browser/workspace-functions.js +79 -28
  79. package/lib/browser/workspace-functions.js.map +1 -1
  80. package/lib/browser/workspace-functions.spec.d.ts +2 -0
  81. package/lib/browser/workspace-functions.spec.d.ts.map +1 -0
  82. package/lib/browser/workspace-functions.spec.js +161 -0
  83. package/lib/browser/workspace-functions.spec.js.map +1 -0
  84. package/lib/browser/workspace-launch-provider.d.ts +24 -0
  85. package/lib/browser/workspace-launch-provider.d.ts.map +1 -0
  86. package/lib/browser/workspace-launch-provider.js +216 -0
  87. package/lib/browser/workspace-launch-provider.js.map +1 -0
  88. package/lib/browser/workspace-launch-provider.spec.d.ts +2 -0
  89. package/lib/browser/workspace-launch-provider.spec.d.ts.map +1 -0
  90. package/lib/browser/workspace-launch-provider.spec.js +245 -0
  91. package/lib/browser/workspace-launch-provider.spec.js.map +1 -0
  92. package/lib/browser/workspace-search-provider.d.ts.map +1 -1
  93. package/lib/browser/workspace-search-provider.js +9 -0
  94. package/lib/browser/workspace-search-provider.js.map +1 -1
  95. package/lib/browser/workspace-search-provider.spec.js +59 -203
  96. package/lib/browser/workspace-search-provider.spec.js.map +1 -1
  97. package/lib/browser/workspace-task-provider.d.ts.map +1 -1
  98. package/lib/browser/workspace-task-provider.js +8 -1
  99. package/lib/browser/workspace-task-provider.js.map +1 -1
  100. package/lib/browser/workspace-task-provider.spec.d.ts +2 -0
  101. package/lib/browser/workspace-task-provider.spec.d.ts.map +1 -0
  102. package/lib/browser/workspace-task-provider.spec.js +109 -0
  103. package/lib/browser/workspace-task-provider.spec.js.map +1 -0
  104. package/lib/common/architect-prompt-template.d.ts.map +1 -1
  105. package/lib/common/architect-prompt-template.js +11 -0
  106. package/lib/common/architect-prompt-template.js.map +1 -1
  107. package/lib/common/command-chat-agents.js +1 -1
  108. package/lib/common/command-chat-agents.js.map +1 -1
  109. package/lib/common/orchestrator-chat-agent.js +1 -1
  110. package/lib/common/orchestrator-chat-agent.js.map +1 -1
  111. package/lib/common/universal-chat-agent.js +1 -1
  112. package/lib/common/universal-chat-agent.js.map +1 -1
  113. package/lib/common/workspace-functions.d.ts +3 -0
  114. package/lib/common/workspace-functions.d.ts.map +1 -1
  115. package/lib/common/workspace-functions.js +4 -1
  116. package/lib/common/workspace-functions.js.map +1 -1
  117. package/package.json +18 -17
  118. package/src/browser/ai-configuration/agent-configuration-widget.tsx +18 -2
  119. package/src/browser/ai-configuration/ai-configuration-service.ts +14 -1
  120. package/src/browser/ai-configuration/ai-configuration-widget.tsx +7 -1
  121. package/src/browser/ai-configuration/language-model-renderer.tsx +87 -59
  122. package/src/browser/ai-configuration/model-aliases-configuration-widget.tsx +279 -0
  123. package/src/browser/ai-configuration/prompt-fragments-configuration-widget.tsx +43 -13
  124. package/src/browser/ai-configuration/template-settings-renderer.tsx +11 -7
  125. package/src/browser/ai-ide-activation-service.ts +65 -0
  126. package/src/browser/ai-ide-preferences.ts +44 -0
  127. package/src/browser/app-tester-chat-agent.ts +5 -73
  128. package/src/browser/app-tester-prompt-template.ts +81 -0
  129. package/src/browser/architect-agent.ts +1 -1
  130. package/src/browser/coder-agent.ts +1 -1
  131. package/src/browser/context-functions.spec.ts +102 -0
  132. package/src/browser/context-functions.ts +11 -0
  133. package/src/browser/file-changeset-function.spec.ts +52 -0
  134. package/src/browser/file-changeset-functions.spec.ts +212 -0
  135. package/src/browser/file-changeset-functions.ts +102 -25
  136. package/src/browser/frontend-module.ts +29 -1
  137. package/src/browser/ide-chat-welcome-message-provider.tsx +4 -4
  138. package/src/browser/style/index.css +111 -6
  139. package/src/browser/test/tool-provider-cancellation-test-util.spec.ts +60 -0
  140. package/src/browser/workspace-functions.spec.ts +199 -0
  141. package/src/browser/workspace-functions.ts +105 -32
  142. package/src/browser/workspace-launch-provider.spec.ts +320 -0
  143. package/src/browser/workspace-launch-provider.ts +231 -0
  144. package/src/browser/workspace-search-provider.spec.ts +79 -229
  145. package/src/browser/workspace-search-provider.ts +10 -1
  146. package/src/browser/workspace-task-provider.spec.ts +125 -0
  147. package/src/browser/workspace-task-provider.ts +7 -2
  148. package/src/common/architect-prompt-template.ts +11 -0
  149. package/src/common/command-chat-agents.ts +1 -1
  150. package/src/common/orchestrator-chat-agent.ts +1 -1
  151. package/src/common/universal-chat-agent.ts +1 -1
  152. package/src/common/workspace-functions.ts +3 -0
@@ -0,0 +1,279 @@
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
+ import * as React from '@theia/core/shared/react';
17
+ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
18
+ import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
19
+ import { LanguageModelAliasRegistry, LanguageModelAlias } from '@theia/ai-core/lib/common/language-model-alias';
20
+ import { FrontendLanguageModelRegistry, LanguageModel, LanguageModelRegistry, LanguageModelRequirement } from '@theia/ai-core/lib/common/language-model';
21
+ import { nls } from '@theia/core/lib/common/nls';
22
+ import { AIConfigurationSelectionService } from './ai-configuration-service';
23
+ import { AgentService, AISettingsService } from '@theia/ai-core';
24
+
25
+ export interface ModelAliasesConfigurationProps {
26
+ languageModelAliasRegistry: LanguageModelAliasRegistry;
27
+ languageModelRegistry: LanguageModelRegistry;
28
+ }
29
+
30
+ @injectable()
31
+ export class ModelAliasesConfigurationWidget extends ReactWidget {
32
+ static readonly ID = 'ai-model-aliases-configuration-widget';
33
+ static readonly LABEL = nls.localize('theia/ai/core/modelAliasesConfiguration/label', 'Model Aliases');
34
+
35
+ @inject(LanguageModelAliasRegistry)
36
+ protected readonly languageModelAliasRegistry: LanguageModelAliasRegistry;
37
+ @inject(LanguageModelRegistry)
38
+ protected readonly languageModelRegistry: FrontendLanguageModelRegistry;
39
+ @inject(AIConfigurationSelectionService)
40
+ protected readonly aiConfigurationSelectionService: AIConfigurationSelectionService;
41
+ @inject(AISettingsService)
42
+ protected readonly aiSettingsService: AISettingsService;
43
+ @inject(AgentService)
44
+ protected readonly agentService: AgentService;
45
+
46
+ protected aliases: LanguageModelAlias[] = [];
47
+ protected languageModels: LanguageModel[] = [];
48
+ /**
49
+ * Map from alias ID to a list of agent IDs that have a language model requirement for that alias.
50
+ */
51
+ protected matchingAgentIdsForAliasMap: Map<string, string[]> = new Map();
52
+ /**
53
+ * Map from alias ID to the resolved LanguageModel (what the alias currently evaluates to).
54
+ */
55
+ protected resolvedModelForAlias: Map<string, LanguageModel | undefined> = new Map();
56
+
57
+ @postConstruct()
58
+ protected init(): void {
59
+ this.id = ModelAliasesConfigurationWidget.ID;
60
+ this.title.label = ModelAliasesConfigurationWidget.LABEL;
61
+ this.title.closable = false;
62
+
63
+ const aliasesPromise = this.loadAliases();
64
+ const languageModelsPromise = this.loadLanguageModels();
65
+ const matchingAgentsPromise = this.loadMatchingAgentIdsForAllAliases();
66
+ Promise.all([aliasesPromise, languageModelsPromise, matchingAgentsPromise]).then(() => this.update());
67
+
68
+ this.languageModelAliasRegistry.ready.then(() =>
69
+ this.toDispose.push(this.languageModelAliasRegistry.onDidChange(async () => {
70
+ await this.loadAliases();
71
+ this.update();
72
+ }))
73
+ );
74
+
75
+ this.toDispose.pushAll([
76
+ this.languageModelRegistry.onChange(async () => {
77
+ await this.loadAliases();
78
+ await this.loadLanguageModels();
79
+ this.update();
80
+ }),
81
+ this.aiSettingsService.onDidChange(async () => {
82
+ await this.loadMatchingAgentIdsForAllAliases();
83
+ this.update();
84
+ }),
85
+ this.aiConfigurationSelectionService.onDidAliasChange(() => this.update())
86
+ ]);
87
+ }
88
+
89
+ protected async loadAliases(): Promise<void> {
90
+ await this.languageModelAliasRegistry.ready;
91
+ this.aliases = this.languageModelAliasRegistry.getAliases();
92
+ // Set the initial selection if not set
93
+ if (this.aliases.length > 0 && !this.aiConfigurationSelectionService.getSelectedAliasId()) {
94
+ this.aiConfigurationSelectionService.setSelectedAliasId(this.aliases[0].id);
95
+ }
96
+ await this.loadMatchingAgentIdsForAllAliases();
97
+ // Resolve evaluated models for each alias
98
+ this.resolvedModelForAlias = new Map();
99
+ for (const alias of this.aliases) {
100
+ const model = await this.languageModelRegistry.getReadyLanguageModel(alias.id);
101
+ this.resolvedModelForAlias.set(alias.id, model);
102
+ }
103
+ }
104
+
105
+ protected async loadLanguageModels(): Promise<void> {
106
+ this.languageModels = await this.languageModelRegistry.getLanguageModels();
107
+ }
108
+
109
+ /**
110
+ * Loads a map from alias ID to a list of agent IDs that have a language model requirement for that alias.
111
+ */
112
+ protected async loadMatchingAgentIdsForAllAliases(): Promise<void> {
113
+ const agents = this.agentService.getAllAgents();
114
+ const aliasMap: Map<string, string[]> = new Map();
115
+ for (const alias of this.aliases) {
116
+ const matchingAgentIds: string[] = [];
117
+ for (const agent of agents) {
118
+ const requirementSetting = await this.aiSettingsService.getAgentSettings(agent.id);
119
+ if (requirementSetting?.languageModelRequirements) {
120
+ // requirement is set via settings, check if it is this alias
121
+ if (requirementSetting?.languageModelRequirements?.find(e => e.identifier === alias.id)) {
122
+ matchingAgentIds.push(agent.id);
123
+ }
124
+ } else {
125
+ // requirement is NOT set via settings, check if this alias is the default for this agent
126
+ if (agent.languageModelRequirements.some((req: LanguageModelRequirement) => req.identifier === alias.id)) {
127
+ matchingAgentIds.push(agent.id);
128
+ }
129
+ }
130
+ }
131
+ aliasMap.set(alias.id, matchingAgentIds);
132
+ }
133
+ this.matchingAgentIdsForAliasMap = aliasMap;
134
+ }
135
+
136
+ protected handleAliasSelectedModelIdChange = (alias: LanguageModelAlias, event: React.ChangeEvent<HTMLSelectElement>): void => {
137
+ const newModelId = event.target.value || undefined;
138
+ const updatedAlias: LanguageModelAlias = {
139
+ ...alias,
140
+ selectedModelId: newModelId
141
+ };
142
+ this.languageModelAliasRegistry.ready.then(() => {
143
+ this.languageModelAliasRegistry.addAlias(updatedAlias);
144
+ });
145
+ };
146
+
147
+ render(): React.ReactNode {
148
+ const selectedAliasId = this.aiConfigurationSelectionService.getSelectedAliasId();
149
+ const selectedAlias = this.aliases.find(alias => alias.id === selectedAliasId);
150
+ return (
151
+ <div className="model-alias-configuration-main">
152
+ <div className="model-alias-configuration-list preferences-tree-widget theia-TreeContainer ai-model-alias-list">
153
+ <ul>
154
+ {this.aliases.map(alias => (
155
+ <li
156
+ key={alias.id}
157
+ className={`theia-TreeNode theia-CompositeTreeNode${alias.id === selectedAliasId ? ' theia-mod-selected' : ''}`}
158
+ onClick={() => this.aiConfigurationSelectionService.setSelectedAliasId(alias.id)}
159
+ >
160
+ <span>{alias.id}</span>
161
+ </li>
162
+ ))}
163
+ </ul>
164
+ </div>
165
+ <div className="model-alias-configuration-panel preferences-editor-widget">
166
+ {selectedAlias ? this.renderAliasDetail(selectedAlias, this.languageModels) : (
167
+ <div>
168
+ {nls.localize('theia/ai/core/modelAliasesConfiguration/selectAlias', 'Please select a Model Alias.')}
169
+ </div>
170
+ )}
171
+ </div>
172
+ </div>
173
+ );
174
+ }
175
+
176
+ protected renderAliasDetail(alias: LanguageModelAlias, languageModels: LanguageModel[]): React.ReactNode {
177
+ const availableModelIds = languageModels.map(m => m.id);
178
+ const selectedModelId = alias.selectedModelId ?? '';
179
+ const isInvalidModel = !!selectedModelId && !availableModelIds.includes(alias.selectedModelId ?? '');
180
+ const agentIds = this.matchingAgentIdsForAliasMap.get(alias.id) || [];
181
+ const agents = this.agentService.getAllAgents().filter(agent => agentIds.includes(agent.id));
182
+ const resolvedModel = this.resolvedModelForAlias.get(alias.id);
183
+ return (
184
+ <div>
185
+ <div className="settings-section-title settings-section-category-title ai-alias-detail-title">
186
+ <span>{alias.id}</span>
187
+ </div>
188
+ {alias.description && <div className="ai-alias-detail-description">{alias.description}</div>}
189
+ <div className="ai-alias-detail-selected-model">
190
+ <label>{nls.localize('theia/ai/core/modelAliasesConfiguration/selectedModelId', 'Selected Model')}: </label>
191
+ <select
192
+ className={`theia-select template-variant-selector ${isInvalidModel ? 'error' : ''}`}
193
+ value={isInvalidModel ? 'invalid' : selectedModelId}
194
+ onChange={event => this.handleAliasSelectedModelIdChange(alias, event)}
195
+ >
196
+ {isInvalidModel && (
197
+ <option value="invalid" disabled>
198
+ {nls.localize('theia/ai/core/modelAliasesConfiguration/unavailableModel', 'Selected model is no longer available')}
199
+ </option>
200
+ )}
201
+ <option value="" className='ai-language-model-item-ready'>
202
+ {nls.localize('theia/ai/core/modelAliasesConfiguration/defaultList', '[Default list]')}
203
+ </option>
204
+ {[...languageModels]
205
+ .sort((a, b) => (a.name ?? a.id).localeCompare(b.name ?? b.id))
206
+ .map(model => {
207
+ const isNotReady = model.status.status !== 'ready';
208
+ return (
209
+ <option
210
+ key={model.id}
211
+ value={model.id}
212
+ className={isNotReady ? 'ai-language-model-item-not-ready' : 'ai-language-model-item-ready'}
213
+ title={isNotReady && model.status.message ? model.status.message : undefined}
214
+ >
215
+ {model.name ?? model.id} {isNotReady ? '✗' : '✓'}
216
+ </option>
217
+ );
218
+ }
219
+ )}
220
+ </select>
221
+ </div>
222
+ {alias.selectedModelId === undefined &&
223
+ <><div className="ai-alias-detail-defaults">
224
+ <ol>
225
+ {alias.defaultModelIds.map(modelId => {
226
+ const model = this.languageModels.find(m => m.id === modelId);
227
+ const isReady = model?.status.status === 'ready';
228
+ return (
229
+ <li key={modelId}>
230
+ {isReady ? (
231
+ <span className={modelId === resolvedModel?.id ? 'ai-alias-priority-item-resolved' : 'ai-alias-priority-item-ready'}>
232
+ {modelId} <span className="ai-model-status-ready" title="Ready">✓</span>
233
+ </span>
234
+ ) : (
235
+ <span className="ai-model-default-not-ready">
236
+ {modelId} <span className="ai-model-status-not-ready" title="Not ready">✗</span>
237
+ </span>
238
+ )}
239
+ </li>
240
+ );
241
+ })}
242
+ </ol>
243
+ </div><div className="ai-alias-evaluates-to-container">
244
+ <label className="ai-alias-evaluates-to-label">{nls.localize('theia/ai/core/modelAliasesConfiguration/evaluatesTo', 'Evaluates to')}:</label>
245
+ {resolvedModel ? (
246
+ <span className="ai-alias-evaluates-to-value">
247
+ {resolvedModel.name ?? resolvedModel.id}
248
+ {resolvedModel.status.status === 'ready' ? (
249
+ <span className="ai-model-status-ready" title="Ready">✓</span>
250
+ ) : (
251
+ <span className="ai-model-status-not-ready" title={resolvedModel.status.message || 'Not ready'}>✗</span>
252
+ )}
253
+ </span>
254
+ ) : (
255
+ <span className="ai-alias-evaluates-to-unresolved">
256
+ {nls.localize('theia/ai/core/modelAliasesConfiguration/noResolvedModel', 'No model ready for this alias.')}
257
+ </span>
258
+ )}
259
+ </div></>
260
+ }
261
+ <div className="ai-alias-detail-agents">
262
+ <label>{nls.localize('theia/ai/core/modelAliasesConfiguration/agents', 'Agents using this Alias')}:</label>
263
+ <ul>
264
+ {agents.length > 0 ? (
265
+ agents.map(agent => (
266
+ <li key={agent.id}>
267
+ <span>{agent.name}</span>
268
+ {agent.id !== agent.name && <span className="ai-alias-agent-id">({agent.id})</span>}
269
+ </li>
270
+ ))
271
+ ) : (
272
+ <span>{nls.localize('theia/ai/core/modelAliasesConfiguration/noAgents', 'No agents use this alias.')}</span>
273
+ )}
274
+ </ul>
275
+ </div>
276
+ </div >
277
+ );
278
+ }
279
+ }
@@ -77,15 +77,20 @@ export class AIPromptFragmentsConfigurationWidget extends ReactWidget {
77
77
  protected availableAgents: Agent[] = [];
78
78
 
79
79
  /**
80
- * Maps prompt variant set IDs to their currently selected variant IDs
80
+ * Maps prompt variant set IDs to their resolved variant IDs
81
81
  */
82
- protected selectedVariantIds: Map<string, string | undefined> = new Map();
82
+ protected effectiveVariantIds: Map<string, string | undefined> = new Map();
83
83
 
84
84
  /**
85
85
  * Maps prompt variant set IDs to their default variant IDs
86
86
  */
87
87
  protected defaultVariantIds: Map<string, string | undefined> = new Map();
88
88
 
89
+ /**
90
+ * Maps prompt variant set IDs to their user selected variant IDs
91
+ */
92
+ protected userSelectedVariantIds: Map<string, string | undefined> = new Map();
93
+
89
94
  @inject(PromptService) protected promptService: PromptService;
90
95
  @inject(AgentService) protected agentService: AgentService;
91
96
 
@@ -103,10 +108,6 @@ export class AIPromptFragmentsConfigurationWidget extends ReactWidget {
103
108
  this.promptService.onPromptsChange(() => {
104
109
  this.loadPromptFragments();
105
110
  }),
106
- this.promptService.onSelectedVariantChange(notification => {
107
- this.selectedVariantIds.set(notification.promptVariantSetId, notification.variantId);
108
- this.update();
109
- }),
110
111
  this.agentService.onDidChangeAgents(() => {
111
112
  this.loadAgents();
112
113
  })
@@ -117,7 +118,7 @@ export class AIPromptFragmentsConfigurationWidget extends ReactWidget {
117
118
  * Loads all prompt fragments and prompt variant sets from the prompt service.
118
119
  * Preserves UI expansion states and updates variant information.
119
120
  */
120
- protected async loadPromptFragments(): Promise<void> {
121
+ protected loadPromptFragments(): void {
121
122
  this.promptFragmentMap = this.promptService.getAllPromptFragments();
122
123
  this.promptVariantsMap = this.promptService.getPromptVariantSets();
123
124
  this.activePromptFragments = this.promptService.getActivePromptFragments();
@@ -154,11 +155,13 @@ export class AIPromptFragmentsConfigurationWidget extends ReactWidget {
154
155
  })
155
156
  );
156
157
 
157
- // Update variant information (selected/default) for prompt variant sets
158
+ // Update variant information (selected/default/effective) for prompt variant sets
158
159
  for (const promptVariantSetId of this.promptVariantsMap.keys()) {
159
- const selectedId = await this.promptService.getSelectedVariantId(promptVariantSetId);
160
- const defaultId = await this.promptService.getDefaultVariantId(promptVariantSetId);
161
- this.selectedVariantIds.set(promptVariantSetId, selectedId);
160
+ const effectiveId = this.promptService.getEffectiveVariantId(promptVariantSetId);
161
+ const defaultId = this.promptService.getDefaultVariantId(promptVariantSetId);
162
+ const selectedId = this.promptService.getSelectedVariantId(promptVariantSetId) ?? defaultId;
163
+ this.userSelectedVariantIds.set(promptVariantSetId, selectedId);
164
+ this.effectiveVariantIds.set(promptVariantSetId, effectiveId);
162
165
  this.defaultVariantIds.set(promptVariantSetId, defaultId);
163
166
  }
164
167
 
@@ -405,8 +408,9 @@ export class AIPromptFragmentsConfigurationWidget extends ReactWidget {
405
408
  protected renderPromptVariantSet(promptVariantSetId: string, variantIds: string[]): React.ReactNode {
406
409
  const isSectionExpanded = this.expandedPromptVariantSetIds.has(promptVariantSetId);
407
410
 
408
- // Get selected and default variant IDs from our class properties
409
- const selectedVariantId = this.selectedVariantIds.get(promptVariantSetId);
411
+ // Get selected, effective, and default variant IDs from our class properties
412
+ const selectedVariantId = this.userSelectedVariantIds.get(promptVariantSetId);
413
+ const effectiveVariantId = this.effectiveVariantIds.get(promptVariantSetId);
410
414
  const defaultVariantId = this.defaultVariantIds.get(promptVariantSetId);
411
415
 
412
416
  // Get variant fragments grouped by ID
@@ -421,6 +425,31 @@ export class AIPromptFragmentsConfigurationWidget extends ReactWidget {
421
425
 
422
426
  const relatedAgents = this.getAgentsUsingPromptVariantId(promptVariantSetId);
423
427
 
428
+ // Determine warning/error state
429
+ let variantSetMessage: React.ReactNode | undefined = undefined;
430
+ if (effectiveVariantId === undefined) {
431
+ // Error: effectiveId is undefined, so nothing works
432
+ variantSetMessage = (
433
+ <div className="prompt-variant-error">
434
+ <span className="codicon codicon-error"></span>
435
+ {nls.localize('theia/ai/core/promptFragmentsConfiguration/variantSetError',
436
+ 'The selected variant does not exist and no default could be found. Please check your configuration.')}
437
+ </div>
438
+ );
439
+ } else {
440
+ const needsWarning = selectedVariantId ? effectiveVariantId !== selectedVariantId : effectiveVariantId !== defaultVariantId;
441
+ if (needsWarning) {
442
+ // Warning: selectedId is set but does not exist, so default is used
443
+ variantSetMessage = (
444
+ <div className="prompt-variant-warning">
445
+ <span className="codicon codicon-warning"></span>
446
+ {nls.localize('theia/ai/core/promptFragmentsConfiguration/variantSetWarning',
447
+ 'The selected variant does not exist. The default variant is being used instead.')}
448
+ </div>
449
+ );
450
+ }
451
+ }
452
+
424
453
  return (
425
454
  <div className="prompt-fragment-section" key={`variant-${promptVariantSetId}`}>
426
455
  <div
@@ -446,6 +475,7 @@ export class AIPromptFragmentsConfigurationWidget extends ReactWidget {
446
475
  </div>
447
476
  {isSectionExpanded && (
448
477
  <div className="prompt-fragment-body">
478
+ {variantSetMessage}
449
479
  <div className="prompt-fragment-description">
450
480
  <p>{nls.localize('theia/ai/core/promptFragmentsConfiguration/variantsOfSystemPrompt', 'Variants of this prompt variant set:')}</p>
451
481
  </div>
@@ -33,14 +33,18 @@ export const PromptVariantRenderer: React.FC<PromptVariantRendererProps> = ({
33
33
  const [selectedVariant, setSelectedVariant] = React.useState<string>(defaultVariantId!);
34
34
 
35
35
  React.useEffect(() => {
36
- (async () => {
37
- const currentVariant =
38
- await promptService.getSelectedVariantId(promptVariantSet.id);
39
- if (currentVariant) {
40
- setSelectedVariant(currentVariant);
36
+ const currentVariant = promptService.getSelectedVariantId(promptVariantSet.id);
37
+ setSelectedVariant(currentVariant ?? defaultVariantId!);
38
+
39
+ const disposable = promptService.onSelectedVariantChange(notification => {
40
+ if (notification.promptVariantSetId === promptVariantSet.id) {
41
+ setSelectedVariant(notification.variantId ?? defaultVariantId!);
41
42
  }
42
- })();
43
- }, [promptVariantSet.id, agentId]);
43
+ });
44
+ return () => {
45
+ disposable.dispose();
46
+ };
47
+ }, [promptVariantSet.id, promptService, defaultVariantId]);
44
48
 
45
49
  const isInvalidVariant = !variantIds.includes(selectedVariant);
46
50
 
@@ -0,0 +1,65 @@
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
+ import { inject, injectable } from '@theia/core/shared/inversify';
17
+ import { FrontendApplicationContribution, PreferenceService } from '@theia/core/lib/browser';
18
+ import { Emitter, MaybePromise, Event, } from '@theia/core';
19
+ import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
20
+ import { AIActivationService, ENABLE_AI_CONTEXT_KEY } from '@theia/ai-core/lib/browser/ai-activation-service';
21
+ import { PREFERENCE_NAME_ENABLE_AI } from './ai-ide-preferences';
22
+
23
+ /**
24
+ * Implements AI Activation Service based on preferences.
25
+ */
26
+ @injectable()
27
+ export class AIIdeActivationServiceImpl implements AIActivationService, FrontendApplicationContribution {
28
+ @inject(ContextKeyService)
29
+ protected readonly contextKeyService: ContextKeyService;
30
+
31
+ @inject(PreferenceService)
32
+ protected preferenceService: PreferenceService;
33
+
34
+ protected isAiEnabledKey: ContextKey<boolean>;
35
+
36
+ protected onDidChangeAIEnabled = new Emitter<boolean>();
37
+ get onDidChangeActiveStatus(): Event<boolean> {
38
+ return this.onDidChangeAIEnabled.event;
39
+ }
40
+
41
+ get isActive(): boolean {
42
+ return this.isAiEnabledKey.get() ?? false;
43
+ }
44
+
45
+ protected updateEnableValue(value: boolean): void {
46
+ if (value !== this.isAiEnabledKey.get()) {
47
+ this.isAiEnabledKey.set(value);
48
+ this.onDidChangeAIEnabled.fire(value);
49
+ }
50
+ }
51
+
52
+ initialize(): MaybePromise<void> {
53
+ this.isAiEnabledKey = this.contextKeyService.createKey(ENABLE_AI_CONTEXT_KEY, false);
54
+ // make sure we don't miss once preferences are ready
55
+ this.preferenceService.ready.then(() => {
56
+ const enableValue = this.preferenceService.get<boolean>(PREFERENCE_NAME_ENABLE_AI, false);
57
+ this.updateEnableValue(enableValue);
58
+ });
59
+ this.preferenceService.onPreferenceChanged(e => {
60
+ if (e.preferenceName === PREFERENCE_NAME_ENABLE_AI) {
61
+ this.updateEnableValue(e.newValue);
62
+ }
63
+ });
64
+ }
65
+ }
@@ -0,0 +1,44 @@
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 { AI_CORE_PREFERENCES_TITLE, ENABLE_AI_CONTEXT_KEY } from '@theia/ai-core/lib/browser';
18
+ import { nls } from '@theia/core';
19
+ import { PreferenceSchema } from '@theia/core/lib/browser';
20
+
21
+ // We reuse the context key for the preference name
22
+ export const PREFERENCE_NAME_ENABLE_AI = ENABLE_AI_CONTEXT_KEY;
23
+
24
+ export const aiIdePreferenceSchema: PreferenceSchema = {
25
+ type: 'object',
26
+ properties: {
27
+ [PREFERENCE_NAME_ENABLE_AI]: {
28
+ title: AI_CORE_PREFERENCES_TITLE,
29
+ markdownDescription: nls.localize('theia/ai/ide/enableAI/mdDescription',
30
+ '❗ This setting allows you to access the latest AI capabilities (Beta version).\
31
+ \n\
32
+ Please note that these features are in a beta phase, which means they may \
33
+ undergo changes and will be further improved. It is important to be aware that these features may generate\
34
+ continuous requests to the language models (LLMs) you provide access to. This might incur costs that you\
35
+ need to monitor closely. By enabling this option, you acknowledge these risks.\
36
+ \n\
37
+ **Please note! The settings below in this section will only take effect\n\
38
+ once the main feature setting is enabled. After enabling the feature, you need to configure at least one\
39
+ LLM provider below. Also see [the documentation](https://theia-ide.org/docs/user_ai/)**.'),
40
+ type: 'boolean',
41
+ default: false,
42
+ }
43
+ }
44
+ };
@@ -16,83 +16,15 @@
16
16
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
17
17
  // *****************************************************************************
18
18
 
19
- import { CHAT_CONTEXT_DETAILS_VARIABLE_ID } from '@theia/ai-chat';
20
19
  import { AbstractStreamParsingChatAgent } from '@theia/ai-chat/lib/common/chat-agents';
21
20
  import { ErrorChatResponseContentImpl, MarkdownChatResponseContentImpl, MutableChatRequestModel, QuestionResponseContentImpl } from '@theia/ai-chat/lib/common/chat-model';
22
- import { BasePromptFragment, LanguageModelRequirement } from '@theia/ai-core/lib/common';
21
+ import { LanguageModelRequirement } from '@theia/ai-core/lib/common';
23
22
  import { MCPFrontendService, MCPServerDescription } from '@theia/ai-mcp/lib/common/mcp-server-manager';
24
23
  import { nls } from '@theia/core';
25
24
  import { inject, injectable } from '@theia/core/shared/inversify';
26
25
  import { MCP_SERVERS_PREF } from '@theia/ai-mcp/lib/browser/mcp-preferences';
27
26
  import { PreferenceScope, PreferenceService } from '@theia/core/lib/browser';
28
- import { QUERY_DOM_FUNCTION_ID, LAUNCH_BROWSER_FUNCTION_ID, CLOSE_BROWSER_FUNCTION_ID, IS_BROWSER_RUNNING_FUNCTION_ID } from '../common/app-tester-chat-functions';
29
-
30
- export const REQUIRED_MCP_SERVERS: MCPServerDescription[] = [
31
- {
32
- name: 'playwright',
33
- command: 'npx',
34
- args: ['-y', '@playwright/mcp@latest',
35
- '--cdp-endpoint',
36
- 'http://localhost:9222/'],
37
- autostart: false,
38
- env: {},
39
- },
40
- {
41
- name: 'playwright-visual',
42
- command: 'npx',
43
- args: ['-y', '@playwright/mcp@latest', '--vision',
44
- '--cdp-endpoint',
45
- 'http://localhost:9222/'],
46
- autostart: false,
47
- env: {},
48
- }
49
- ];
50
-
51
- // Prompt templates
52
- export const appTesterTemplate: BasePromptFragment = {
53
- id: 'app-tester-system-default',
54
- template: `{{!-- This prompt is licensed under the MIT License (https://opensource.org/license/mit).
55
- Made improvements or adaptations to this prompt template? We'd love for you to share it with the community! Contribute back here:
56
- https://github.com/eclipse-theia/theia/discussions/new?category=prompt-template-contribution --}}
57
-
58
- You are AppTester, an AI assistant integrated into Theia IDE specifically designed to help developers test running applications using Playwright.
59
- Your role is to inspect the application for user-specified test scenarios through the Playwright MCP server.
60
-
61
- ## Your Workflow
62
- 1. Help the user build and launch their application
63
- 2. Use Playwright browser automation to validate test scenarios
64
- 3. Report results and provide actionable feedback
65
- 4. Help fix issues when needed
66
-
67
- ## Available Playwright Testing Tools
68
- You have access to these powerful automation tools:
69
- ${REQUIRED_MCP_SERVERS.map(server => `{{prompt:mcp_${server.name}_tools}}`)}
70
-
71
- - **~{${LAUNCH_BROWSER_FUNCTION_ID}}**: Launch the browser. This is required before performing any browser interactions. Always launch a new browser when starting a test session.
72
- - **~{${IS_BROWSER_RUNNING_FUNCTION_ID}}**: Check if the browser is running. If a tool fails by saying that the connection failed, you can verify the connection by using this tool.
73
- - **~{${CLOSE_BROWSER_FUNCTION_ID}}**: Close the browser.
74
- - **~{${QUERY_DOM_FUNCTION_ID}}**: Query the DOM for specific elements and their properties. Only use when explicitly requested by the user.
75
- - **browser_snapshot**: Capture the current state of the page for verification or debugging purposes.
76
-
77
- Prefer snapshots for investigating the page.
78
-
79
- ## Workflow Approach
80
- 1. **Understand Requirements**: Ask the user to clearly define what needs to be tested
81
- 2. **Launch Browser**: Start a fresh browser instance for testing
82
- 3. **Navigate and Test**: Execute the test scenario methodically
83
- 4. **Document Results**: Provide detailed results with screenshots when helpful
84
- 5. **Clean Up**: Always close the browser when testing is complete
85
-
86
- ## Current Context
87
- 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.
88
- {{${CHAT_CONTEXT_DETAILS_VARIABLE_ID}}}
89
- `
90
- };
91
-
92
- export const appTesterTemplateVariant: BasePromptFragment = {
93
- id: 'app-tester-system-empty',
94
- template: '',
95
- };
27
+ import { appTesterTemplate, appTesterTemplateVariant, REQUIRED_MCP_SERVERS } from './app-tester-prompt-template';
96
28
 
97
29
  export const AppTesterChatAgentId = 'AppTester';
98
30
  @injectable()
@@ -108,15 +40,15 @@ export class AppTesterChatAgent extends AbstractStreamParsingChatAgent {
108
40
  name = AppTesterChatAgentId;
109
41
  languageModelRequirements: LanguageModelRequirement[] = [{
110
42
  purpose: 'chat',
111
- identifier: 'openai/gpt-4o',
43
+ identifier: 'default/code',
112
44
  }];
113
45
  protected defaultLanguageModelPurpose: string = 'chat';
114
46
  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. '
115
47
  + 'It can automate testing workflows and provide detailed feedback on application functionality.');
116
48
 
117
49
  override iconClass: string = 'codicon codicon-beaker';
118
- protected override systemPromptId: string = 'app-tester-system';
119
- override prompts = [{ id: 'app-tester-system', defaultVariant: appTesterTemplate, variants: [appTesterTemplateVariant] }];
50
+ protected override systemPromptId: string = 'app-tester-system';
51
+ override prompts = [{ id: 'app-tester-system', defaultVariant: appTesterTemplate, variants: [appTesterTemplateVariant] }];
120
52
 
121
53
  /**
122
54
  * Override invoke to check if the Playwright MCP server is running, and if not, ask the user if it should be started.