@theia/ai-ide 1.64.0-next.35 → 1.64.1

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 (145) 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.js +1 -1
  37. package/lib/browser/app-tester-chat-agent.js.map +1 -1
  38. package/lib/browser/architect-agent.js +1 -1
  39. package/lib/browser/architect-agent.js.map +1 -1
  40. package/lib/browser/coder-agent.js +1 -1
  41. package/lib/browser/coder-agent.js.map +1 -1
  42. package/lib/browser/context-functions.d.ts.map +1 -1
  43. package/lib/browser/context-functions.js +12 -0
  44. package/lib/browser/context-functions.js.map +1 -1
  45. package/lib/browser/context-functions.spec.d.ts +2 -0
  46. package/lib/browser/context-functions.spec.d.ts.map +1 -0
  47. package/lib/browser/context-functions.spec.js +93 -0
  48. package/lib/browser/context-functions.spec.js.map +1 -0
  49. package/lib/browser/file-changeset-function.spec.d.ts +2 -0
  50. package/lib/browser/file-changeset-function.spec.d.ts.map +1 -0
  51. package/lib/browser/file-changeset-function.spec.js +45 -0
  52. package/lib/browser/file-changeset-function.spec.js.map +1 -0
  53. package/lib/browser/file-changeset-functions.d.ts +13 -3
  54. package/lib/browser/file-changeset-functions.d.ts.map +1 -1
  55. package/lib/browser/file-changeset-functions.js +100 -29
  56. package/lib/browser/file-changeset-functions.js.map +1 -1
  57. package/lib/browser/file-changeset-functions.spec.d.ts +2 -0
  58. package/lib/browser/file-changeset-functions.spec.d.ts.map +1 -0
  59. package/lib/browser/file-changeset-functions.spec.js +161 -0
  60. package/lib/browser/file-changeset-functions.spec.js.map +1 -0
  61. package/lib/browser/frontend-module.d.ts.map +1 -1
  62. package/lib/browser/frontend-module.js +20 -0
  63. package/lib/browser/frontend-module.js.map +1 -1
  64. package/lib/browser/ide-chat-welcome-message-provider.js +2 -2
  65. package/lib/browser/ide-chat-welcome-message-provider.js.map +1 -1
  66. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts +2 -0
  67. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts.map +1 -0
  68. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js +52 -0
  69. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js.map +1 -0
  70. package/lib/browser/workspace-functions.d.ts +3 -3
  71. package/lib/browser/workspace-functions.d.ts.map +1 -1
  72. package/lib/browser/workspace-functions.js +79 -28
  73. package/lib/browser/workspace-functions.js.map +1 -1
  74. package/lib/browser/workspace-functions.spec.d.ts +2 -0
  75. package/lib/browser/workspace-functions.spec.d.ts.map +1 -0
  76. package/lib/browser/workspace-functions.spec.js +161 -0
  77. package/lib/browser/workspace-functions.spec.js.map +1 -0
  78. package/lib/browser/workspace-launch-provider.d.ts +24 -0
  79. package/lib/browser/workspace-launch-provider.d.ts.map +1 -0
  80. package/lib/browser/workspace-launch-provider.js +216 -0
  81. package/lib/browser/workspace-launch-provider.js.map +1 -0
  82. package/lib/browser/workspace-launch-provider.spec.d.ts +2 -0
  83. package/lib/browser/workspace-launch-provider.spec.d.ts.map +1 -0
  84. package/lib/browser/workspace-launch-provider.spec.js +245 -0
  85. package/lib/browser/workspace-launch-provider.spec.js.map +1 -0
  86. package/lib/browser/workspace-search-provider.d.ts.map +1 -1
  87. package/lib/browser/workspace-search-provider.js +9 -0
  88. package/lib/browser/workspace-search-provider.js.map +1 -1
  89. package/lib/browser/workspace-search-provider.spec.js +59 -203
  90. package/lib/browser/workspace-search-provider.spec.js.map +1 -1
  91. package/lib/browser/workspace-task-provider.d.ts.map +1 -1
  92. package/lib/browser/workspace-task-provider.js +8 -1
  93. package/lib/browser/workspace-task-provider.js.map +1 -1
  94. package/lib/browser/workspace-task-provider.spec.d.ts +2 -0
  95. package/lib/browser/workspace-task-provider.spec.d.ts.map +1 -0
  96. package/lib/browser/workspace-task-provider.spec.js +109 -0
  97. package/lib/browser/workspace-task-provider.spec.js.map +1 -0
  98. package/lib/common/architect-prompt-template.d.ts.map +1 -1
  99. package/lib/common/architect-prompt-template.js +11 -0
  100. package/lib/common/architect-prompt-template.js.map +1 -1
  101. package/lib/common/command-chat-agents.js +1 -1
  102. package/lib/common/command-chat-agents.js.map +1 -1
  103. package/lib/common/orchestrator-chat-agent.js +1 -1
  104. package/lib/common/orchestrator-chat-agent.js.map +1 -1
  105. package/lib/common/universal-chat-agent.js +1 -1
  106. package/lib/common/universal-chat-agent.js.map +1 -1
  107. package/lib/common/workspace-functions.d.ts +3 -0
  108. package/lib/common/workspace-functions.d.ts.map +1 -1
  109. package/lib/common/workspace-functions.js +4 -1
  110. package/lib/common/workspace-functions.js.map +1 -1
  111. package/package.json +18 -17
  112. package/src/browser/ai-configuration/agent-configuration-widget.tsx +18 -2
  113. package/src/browser/ai-configuration/ai-configuration-service.ts +14 -1
  114. package/src/browser/ai-configuration/ai-configuration-widget.tsx +7 -1
  115. package/src/browser/ai-configuration/language-model-renderer.tsx +87 -59
  116. package/src/browser/ai-configuration/model-aliases-configuration-widget.tsx +279 -0
  117. package/src/browser/ai-configuration/prompt-fragments-configuration-widget.tsx +43 -13
  118. package/src/browser/ai-configuration/template-settings-renderer.tsx +11 -7
  119. package/src/browser/ai-ide-activation-service.ts +65 -0
  120. package/src/browser/ai-ide-preferences.ts +44 -0
  121. package/src/browser/app-tester-chat-agent.ts +1 -1
  122. package/src/browser/architect-agent.ts +1 -1
  123. package/src/browser/coder-agent.ts +1 -1
  124. package/src/browser/context-functions.spec.ts +102 -0
  125. package/src/browser/context-functions.ts +11 -0
  126. package/src/browser/file-changeset-function.spec.ts +52 -0
  127. package/src/browser/file-changeset-functions.spec.ts +212 -0
  128. package/src/browser/file-changeset-functions.ts +102 -25
  129. package/src/browser/frontend-module.ts +29 -1
  130. package/src/browser/ide-chat-welcome-message-provider.tsx +4 -4
  131. package/src/browser/style/index.css +111 -6
  132. package/src/browser/test/tool-provider-cancellation-test-util.spec.ts +60 -0
  133. package/src/browser/workspace-functions.spec.ts +199 -0
  134. package/src/browser/workspace-functions.ts +105 -32
  135. package/src/browser/workspace-launch-provider.spec.ts +320 -0
  136. package/src/browser/workspace-launch-provider.ts +231 -0
  137. package/src/browser/workspace-search-provider.spec.ts +79 -229
  138. package/src/browser/workspace-search-provider.ts +10 -1
  139. package/src/browser/workspace-task-provider.spec.ts +125 -0
  140. package/src/browser/workspace-task-provider.ts +7 -2
  141. package/src/common/architect-prompt-template.ts +11 -0
  142. package/src/common/command-chat-agents.ts +1 -1
  143. package/src/common/orchestrator-chat-agent.ts +1 -1
  144. package/src/common/universal-chat-agent.ts +1 -1
  145. package/src/common/workspace-functions.ts +3 -0
@@ -14,7 +14,8 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
  import * as React from '@theia/core/shared/react';
17
- import { Agent, AISettingsService, LanguageModel, LanguageModelRegistry, LanguageModelRequirement } from '@theia/ai-core/lib/common';
17
+ import { Agent, AISettingsService, FrontendLanguageModelRegistry, LanguageModel, LanguageModelRequirement } from '@theia/ai-core/lib/common';
18
+ import { LanguageModelAlias } from '@theia/ai-core/lib/common/language-model-alias';
18
19
  import { Mutable } from '@theia/core';
19
20
  import { nls } from '@theia/core/lib/common/nls';
20
21
 
@@ -22,11 +23,12 @@ export interface LanguageModelSettingsProps {
22
23
  agent: Agent;
23
24
  languageModels?: LanguageModel[];
24
25
  aiSettingsService: AISettingsService;
25
- languageModelRegistry: LanguageModelRegistry;
26
+ languageModelRegistry: FrontendLanguageModelRegistry;
27
+ languageModelAliases: LanguageModelAlias[];
26
28
  }
27
29
 
28
30
  export const LanguageModelRenderer: React.FC<LanguageModelSettingsProps> = (
29
- { agent, languageModels, aiSettingsService, languageModelRegistry }) => {
31
+ { agent, languageModels, aiSettingsService, languageModelRegistry, languageModelAliases: aliases }) => {
30
32
 
31
33
  const findLanguageModelRequirement = async (purpose: string): Promise<LanguageModelRequirement | undefined> => {
32
34
  const requirementSetting = await aiSettingsService.getAgentSettings(agent.id);
@@ -34,6 +36,7 @@ export const LanguageModelRenderer: React.FC<LanguageModelSettingsProps> = (
34
36
  };
35
37
 
36
38
  const [lmRequirementMap, setLmRequirementMap] = React.useState<Record<string, LanguageModelRequirement>>({});
39
+ const [resolvedAliasModels, setResolvedAliasModels] = React.useState<Record<string, LanguageModel | undefined>>({});
37
40
 
38
41
  React.useEffect(() => {
39
42
  const computeLmRequirementMap = async () => {
@@ -54,32 +57,20 @@ export const LanguageModelRenderer: React.FC<LanguageModelSettingsProps> = (
54
57
  computeLmRequirementMap();
55
58
  }, []);
56
59
 
57
- const renderLanguageModelMetadata = (requirement: LanguageModelRequirement, index: number) => {
58
- const languageModel = languageModels?.find(model => model.id === requirement.identifier);
59
- if (!languageModel) {
60
- return <div></div>;
61
- }
62
-
63
- return <>
64
- <div>{requirement.purpose}</div>
65
- <div key={index}>
66
- {languageModel.id && <p><strong>{nls.localizeByDefault('Identifier')}: </strong> {languageModel.id}</p>}
67
- {languageModel.name && <p><strong>{nls.localizeByDefault('Name')}: </strong> {languageModel.name}</p>}
68
- {languageModel.vendor && <p><strong>{nls.localize('theia/ai/core/languageModelRenderer/vendor', 'Vendor')}: </strong> {languageModel.vendor}</p>}
69
- {languageModel.version && <p><strong>{nls.localizeByDefault('Version')}: </strong> {languageModel.version}</p>}
70
- {languageModel.family && <p><strong>{nls.localize('theia/ai/core/languageModelRenderer/family', 'Family')}: </strong> {languageModel.family}</p>}
71
- {languageModel.maxInputTokens &&
72
- <p><strong>
73
- {nls.localize('theia/ai/core/languageModelRenderer/minInputTokens', 'Min Input Tokens')}:
74
- </strong> {languageModel.maxInputTokens}</p>}
75
- {languageModel.maxOutputTokens &&
76
- <p><strong>
77
- {nls.localize('theia/ai/core/languageModelRenderer/maxOutputTokens', 'Max Output Tokens')}:
78
- </strong> {languageModel.maxOutputTokens}</p>}
79
- </div>
80
- </>;
81
-
82
- };
60
+ // Effect to resolve alias to model whenever requirements.identifier or aliases change
61
+ React.useEffect(() => {
62
+ const resolveAliases = async () => {
63
+ const newResolved: Record<string, LanguageModel | undefined> = {};
64
+ await Promise.all(Object.values(lmRequirementMap).map(async requirements => {
65
+ const id = requirements.identifier;
66
+ if (id && aliases.some(a => a.id === id)) {
67
+ newResolved[id] = await languageModelRegistry.getReadyLanguageModel(id);
68
+ }
69
+ }));
70
+ setResolvedAliasModels(newResolved);
71
+ };
72
+ resolveAliases();
73
+ }, [lmRequirementMap, aliases]);
83
74
 
84
75
  const onSelectedModelChange = (purpose: string, event: React.ChangeEvent<HTMLSelectElement>): void => {
85
76
  const newLmRequirementMap = { ...lmRequirementMap, [purpose]: { purpose, identifier: event.target.value } };
@@ -88,35 +79,72 @@ export const LanguageModelRenderer: React.FC<LanguageModelSettingsProps> = (
88
79
  };
89
80
 
90
81
  return <div className='language-model-container'>
91
- {Object.values(lmRequirementMap).map((requirements, index) => (
92
- <React.Fragment key={index}>
93
- <div><strong>{nls.localize('theia/ai/core/languageModelRenderer/purpose', 'Purpose')}:</strong></div>
94
- <div>
95
- {/* language model metadata */}
96
- {renderLanguageModelMetadata(requirements, index)}
97
- {/* language model selector */}
98
- <>
99
- <label
100
- className="theia-header no-select"
101
- htmlFor={`model-select-${agent.id}`}>
102
- {nls.localize('theia/ai/core/languageModelRenderer/languageModel', 'Language Model')}:
103
- </label>
104
- <select
105
- className="theia-select"
106
- id={`model-select-${agent.id}`}
107
- value={requirements.identifier}
108
- onChange={event => onSelectedModelChange(requirements.purpose, event)}
109
- >
110
- <option value=""></option>
111
- {languageModels?.sort((a, b) => (a.name ?? a.id).localeCompare(b.name ?? b.id)).map(model => (
112
- <option key={model.id} value={model.id}>{model.name ?? model.id}</option>
113
- ))}
114
- </select>
115
- </>
116
- <hr />
117
- </div>
118
- </React.Fragment>
119
- ))}
120
-
82
+ {Object.values(lmRequirementMap).map((requirement, index) => {
83
+ const isAlias = requirement.identifier && aliases.some(a => a.id === requirement.identifier);
84
+ const resolvedModel = isAlias ? resolvedAliasModels[requirement.identifier] : undefined;
85
+ return (
86
+ <React.Fragment key={index}>
87
+ <div className="ai-alias-evaluates-to-container">
88
+ <strong>{nls.localize('theia/ai/core/languageModelRenderer/purpose', 'Purpose')}:</strong> {requirement.purpose}
89
+ </div>
90
+ <div>
91
+ <div className="ai-alias-evaluates-to-container">
92
+ <label
93
+ className="theia-header no-select"
94
+ htmlFor={`model-select-${agent.id}`}>
95
+ {nls.localize('theia/ai/core/languageModelRenderer/languageModel', 'Language Model') + ': '}
96
+ </label>
97
+ <select
98
+ className="theia-select"
99
+ id={`model-select-${agent.id}-${requirement.purpose}`}
100
+ value={requirement.identifier}
101
+ onChange={event => onSelectedModelChange(requirement.purpose, event)}
102
+ >
103
+ <option value=""></option>
104
+ {/* Aliases first, then languange models */}
105
+ {aliases?.sort((a, b) => a.id.localeCompare(b.id)).map(alias => (
106
+ <option key={`alias/${alias.id}`} value={alias.id} className='ai-language-model-item-ready'>{`[alias] ${alias.id}`}</option>
107
+ ))}
108
+ {languageModels?.sort((a, b) => (a.name ?? a.id).localeCompare(b.name ?? b.id)).map(model => {
109
+ const isNotReady = model.status.status !== 'ready';
110
+ return (
111
+ <option
112
+ key={model.id}
113
+ value={model.id}
114
+ className={isNotReady ? 'ai-language-model-item-not-ready' : 'ai-language-model-item-ready'}
115
+ title={isNotReady && model.status.message ? model.status.message : undefined}
116
+ >
117
+ {model.name ?? model.id} {isNotReady ? '✗' : '✓'}
118
+ </option>
119
+ );
120
+ })}
121
+ </select>
122
+ </div>
123
+ {/* If alias is selected, show what it currently evaluates to */}
124
+ {isAlias && (
125
+ <div className="ai-alias-evaluates-to-container">
126
+ <label className="ai-alias-evaluates-to-label">{nls.localize('theia/ai/core/modelAliasesConfiguration/evaluatesTo', 'Evaluates to')}:</label>
127
+ {resolvedModel ? (
128
+ <span className="ai-alias-evaluates-to-value">
129
+ {resolvedModel.name ?? resolvedModel.id}
130
+ {resolvedModel.status.status === 'ready' ? (
131
+ <span className="ai-model-status-ready" title="Ready">✓</span>
132
+ ) : (
133
+ <span className="ai-model-status-not-ready" title={resolvedModel.status.message || 'Not ready'}>✗</span>
134
+ )}
135
+ </span>
136
+ ) : (
137
+ <span className="ai-alias-evaluates-to-unresolved">
138
+ {nls.localize('theia/ai/core/modelAliasesConfiguration/noResolvedModel', 'No model ready for this alias.')}
139
+ <span className="ai-model-status-not-ready" title={'No model ready'}>✗</span>
140
+ </span>
141
+ )}
142
+ </div>
143
+ )}
144
+ <hr />
145
+ </div>
146
+ </React.Fragment>
147
+ );
148
+ })}
121
149
  </div>;
122
150
  };
@@ -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