@theia/ai-core 1.55.0-next.97 → 1.56.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 (39) hide show
  1. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts.map +1 -1
  2. package/lib/browser/ai-configuration/agent-configuration-widget.js +7 -2
  3. package/lib/browser/ai-configuration/agent-configuration-widget.js.map +1 -1
  4. package/lib/browser/ai-configuration/template-settings-renderer.d.ts +6 -4
  5. package/lib/browser/ai-configuration/template-settings-renderer.d.ts.map +1 -1
  6. package/lib/browser/ai-configuration/template-settings-renderer.js +49 -11
  7. package/lib/browser/ai-configuration/template-settings-renderer.js.map +1 -1
  8. package/lib/browser/ai-core-preferences.d.ts +15 -0
  9. package/lib/browser/ai-core-preferences.d.ts.map +1 -1
  10. package/lib/browser/ai-core-preferences.js +30 -1
  11. package/lib/browser/ai-core-preferences.js.map +1 -1
  12. package/lib/browser/frontend-prompt-customization-service.d.ts +1 -0
  13. package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
  14. package/lib/browser/frontend-prompt-customization-service.js +3 -0
  15. package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
  16. package/lib/common/agent-service.js +1 -1
  17. package/lib/common/agent-service.js.map +1 -1
  18. package/lib/common/language-model.d.ts +7 -0
  19. package/lib/common/language-model.d.ts.map +1 -1
  20. package/lib/common/language-model.js.map +1 -1
  21. package/lib/common/prompt-service.d.ts +28 -5
  22. package/lib/common/prompt-service.d.ts.map +1 -1
  23. package/lib/common/prompt-service.js +33 -4
  24. package/lib/common/prompt-service.js.map +1 -1
  25. package/lib/common/prompt-service.spec.js +83 -36
  26. package/lib/common/prompt-service.spec.js.map +1 -1
  27. package/lib/common/settings-service.d.ts +5 -0
  28. package/lib/common/settings-service.d.ts.map +1 -1
  29. package/package.json +10 -10
  30. package/src/browser/ai-configuration/agent-configuration-widget.tsx +24 -7
  31. package/src/browser/ai-configuration/template-settings-renderer.tsx +105 -16
  32. package/src/browser/ai-core-preferences.ts +40 -1
  33. package/src/browser/frontend-prompt-customization-service.ts +4 -0
  34. package/src/browser/style/index.css +37 -5
  35. package/src/common/agent-service.ts +1 -1
  36. package/src/common/language-model.ts +5 -0
  37. package/src/common/prompt-service.spec.ts +92 -38
  38. package/src/common/prompt-service.ts +59 -8
  39. package/src/common/settings-service.ts +5 -0
@@ -21,6 +21,7 @@ import { interfaces } from '@theia/core/shared/inversify';
21
21
  export const AI_CORE_PREFERENCES_TITLE = '✨ AI Features [Experimental]';
22
22
  export const PREFERENCE_NAME_ENABLE_EXPERIMENTAL = 'ai-features.AiEnable.enableAI';
23
23
  export const PREFERENCE_NAME_PROMPT_TEMPLATES = 'ai-features.promptTemplates.promptTemplatesFolder';
24
+ export const PREFERENCE_NAME_REQUEST_SETTINGS = 'ai-features.modelSettings.requestSettings';
24
25
 
25
26
  export const aiCorePreferenceSchema: PreferenceSchema = {
26
27
  type: 'object',
@@ -55,13 +56,51 @@ export const aiCorePreferenceSchema: PreferenceSchema = {
55
56
  canSelectMany: false
56
57
  }
57
58
  },
58
-
59
+ },
60
+ [PREFERENCE_NAME_REQUEST_SETTINGS]: {
61
+ title: 'Custom Request Settings',
62
+ markdownDescription: 'Allows specifying custom request settings for multiple models.\n\
63
+ Each object represents the configuration for a specific model. The `modelId` field specifies the model ID, `requestSettings` defines model-specific settings.\n\
64
+ The `providerId` field is optional and allows you to apply the settings to a specific provider. If not set, the settings will be applied to all providers.\n\
65
+ Example providerIds: huggingface, openai, ollama, llamafile.\n\
66
+ Refer to [our documentation](https://theia-ide.org/docs/user_ai/#custom-request-settings) for more information.',
67
+ type: 'array',
68
+ items: {
69
+ type: 'object',
70
+ properties: {
71
+ modelId: {
72
+ type: 'string',
73
+ description: 'The model id'
74
+ },
75
+ requestSettings: {
76
+ type: 'object',
77
+ additionalProperties: true,
78
+ description: 'Settings for the specific model ID.',
79
+ },
80
+ providerId: {
81
+ type: 'string',
82
+ description: 'The (optional) provider id to apply the settings to. If not set, the settings will be applied to all providers.',
83
+ },
84
+ },
85
+ },
86
+ default: [],
59
87
  }
60
88
  }
61
89
  };
62
90
  export interface AICoreConfiguration {
63
91
  [PREFERENCE_NAME_ENABLE_EXPERIMENTAL]: boolean | undefined;
64
92
  [PREFERENCE_NAME_PROMPT_TEMPLATES]: string | undefined;
93
+ [PREFERENCE_NAME_REQUEST_SETTINGS]: Array<{
94
+ modelId: string;
95
+ requestSettings?: { [key: string]: unknown };
96
+ providerId?: string;
97
+ }> | undefined;
98
+ }
99
+
100
+ export interface RequestSetting {
101
+ modelId: string;
102
+ requestSettings?: { [key: string]: unknown };
103
+ providerId?: string;
65
104
  }
66
105
 
67
106
  export const AICorePreferences = Symbol('AICorePreferences');
@@ -169,6 +169,10 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
169
169
  return this.templates.get(id);
170
170
  }
171
171
 
172
+ getCustomPromptTemplateIDs(): string[] {
173
+ return Array.from(this.templates.keys());
174
+ }
175
+
172
176
  async editTemplate(id: string, defaultContent?: string): Promise<void> {
173
177
  const editorUri = await this.getTemplateURI(id);
174
178
  if (! await this.fileService.exists(editorUri)) {
@@ -14,12 +14,44 @@
14
14
  margin-left: var(--theia-ui-padding);
15
15
  }
16
16
 
17
+ .theia-settings-container .settings-section-subcategory-title.ai-settings-section-subcategory-title {
18
+ padding-left: 0;
19
+ }
20
+
17
21
  .ai-templates {
18
- display: grid;
19
- /** Display content in 3 columns */
20
- grid-template-columns: 1fr auto auto;
21
- /** add a 3px gap between rows */
22
- row-gap: 3px;
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: 5px;
25
+ }
26
+
27
+ .template-renderer {
28
+ display: flex;
29
+ flex-direction: column;
30
+ padding: 10px;
31
+ }
32
+
33
+ .template-header {
34
+ margin-bottom: 8px;
35
+ }
36
+
37
+ .template-controls {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 10px;
41
+ }
42
+
43
+ .template-select-label {
44
+ margin-right: 5px;
45
+ }
46
+
47
+ .template-variant-selector {
48
+ min-width: 120px;
49
+ }
50
+
51
+ .template-variant-selector.error {
52
+ border-color: var(--theia-errorForeground);
53
+ background-color: var(--theia-errorBackground, rgba(255, 0, 0, 0.1));
54
+ color: var(--theia-errorForeground);
23
55
  }
24
56
 
25
57
  #ai-variable-configuration-container-widget,
@@ -99,7 +99,7 @@ export class AgentServiceImpl implements AgentService {
99
99
  registerAgent(agent: Agent): void {
100
100
  this._agents.push(agent);
101
101
  agent.promptTemplates.forEach(
102
- template => this.promptService.storePrompt(template.id, template.template)
102
+ template => this.promptService.storePromptTemplate(template)
103
103
  );
104
104
  this.onDidChangeAgentsEmitter.fire();
105
105
  }
@@ -107,6 +107,11 @@ export interface LanguageModelMetaData {
107
107
  readonly family?: string;
108
108
  readonly maxInputTokens?: number;
109
109
  readonly maxOutputTokens?: number;
110
+ /**
111
+ * Default request settings for the language model. These settings can be set by a user preferences.
112
+ * Settings in a request will override these default settings.
113
+ */
114
+ readonly defaultRequestSettings?: { [key: string]: unknown };
110
115
  }
111
116
 
112
117
  export namespace LanguageModelMetaData {
@@ -37,10 +37,10 @@ describe('PromptService', () => {
37
37
  container.bind<AIVariableService>(AIVariableService).toConstantValue(variableService);
38
38
 
39
39
  promptService = container.get<PromptService>(PromptService);
40
- promptService.storePrompt('1', 'Hello, {{name}}!');
41
- promptService.storePrompt('2', 'Goodbye, {{name}}!');
42
- promptService.storePrompt('3', 'Ciao, {{invalid}}!');
43
- promptService.storePrompt('8', 'Hello, {{{name}}}');
40
+ promptService.storePromptTemplate({ id: '1', template: 'Hello, {{name}}!' });
41
+ promptService.storePromptTemplate({ id: '2', template: 'Goodbye, {{name}}!' });
42
+ promptService.storePromptTemplate({ id: '3', template: 'Ciao, {{invalid}}!' });
43
+ promptService.storePromptTemplate({ id: '8', template: 'Hello, {{{name}}}' });
44
44
  });
45
45
 
46
46
  it('should initialize prompts from PromptCollectionService', () => {
@@ -62,7 +62,7 @@ describe('PromptService', () => {
62
62
  });
63
63
 
64
64
  it('should store a new prompt', () => {
65
- promptService.storePrompt('3', 'Welcome, {{name}}!');
65
+ promptService.storePromptTemplate({ id: '3', template: 'Welcome, {{name}}!' });
66
66
  const newPrompt = promptService.getRawPrompt('3');
67
67
  expect(newPrompt?.template).to.equal('Welcome, {{name}}!');
68
68
  });
@@ -88,10 +88,10 @@ describe('PromptService', () => {
88
88
  });
89
89
 
90
90
  it('should ignore whitespace in variables', async () => {
91
- promptService.storePrompt('4', 'Hello, {{name }}!');
92
- promptService.storePrompt('5', 'Hello, {{ name}}!');
93
- promptService.storePrompt('6', 'Hello, {{ name }}!');
94
- promptService.storePrompt('7', 'Hello, {{ name }}!');
91
+ promptService.storePromptTemplate({ id: '4', template: 'Hello, {{name }}!' });
92
+ promptService.storePromptTemplate({ id: '5', template: 'Hello, {{ name}}!' });
93
+ promptService.storePromptTemplate({ id: '6', template: 'Hello, {{ name }}!' });
94
+ promptService.storePromptTemplate({ id: '7', template: 'Hello, {{ name }}!' });
95
95
  for (let i = 4; i <= 7; i++) {
96
96
  const prompt = await promptService.getPrompt(`${i}`, { name: 'John' });
97
97
  expect(prompt?.text).to.equal('Hello, John!');
@@ -109,10 +109,10 @@ describe('PromptService', () => {
109
109
  });
110
110
 
111
111
  it('should ignore whitespace in variables (three bracket)', async () => {
112
- promptService.storePrompt('9', 'Hello, {{{name }}}');
113
- promptService.storePrompt('10', 'Hello, {{{ name}}}');
114
- promptService.storePrompt('11', 'Hello, {{{ name }}}');
115
- promptService.storePrompt('12', 'Hello, {{{ name }}}');
112
+ promptService.storePromptTemplate({ id: '9', template: 'Hello, {{{name }}}' });
113
+ promptService.storePromptTemplate({ id: '10', template: 'Hello, {{{ name}}}' });
114
+ promptService.storePromptTemplate({ id: '11', template: 'Hello, {{{ name }}}' });
115
+ promptService.storePromptTemplate({ id: '12', template: 'Hello, {{{ name }}}' });
116
116
  for (let i = 9; i <= 12; i++) {
117
117
  const prompt = await promptService.getPrompt(`${i}`, { name: 'John' });
118
118
  expect(prompt?.text).to.equal('Hello, John');
@@ -120,26 +120,24 @@ describe('PromptService', () => {
120
120
  });
121
121
 
122
122
  it('should ignore invalid prompts with unmatched brackets', async () => {
123
- promptService.storePrompt('9', 'Hello, {{name');
124
- promptService.storePrompt('10', 'Hello, {{{name');
125
- promptService.storePrompt('11', 'Hello, name}}}}');
123
+ promptService.storePromptTemplate({ id: '9', template: 'Hello, {{name' });
124
+ promptService.storePromptTemplate({ id: '10', template: 'Hello, {{{name' });
125
+ promptService.storePromptTemplate({ id: '11', template: 'Hello, name}}}}' });
126
126
  const prompt1 = await promptService.getPrompt('9', { name: 'John' });
127
127
  expect(prompt1?.text).to.equal('Hello, {{name'); // Not matching due to missing closing brackets
128
-
129
128
  const prompt2 = await promptService.getPrompt('10', { name: 'John' });
130
129
  expect(prompt2?.text).to.equal('Hello, {{{name'); // Matches pattern due to valid three-start-two-end brackets
131
-
132
130
  const prompt3 = await promptService.getPrompt('11', { name: 'John' });
133
131
  expect(prompt3?.text).to.equal('Hello, name}}}}'); // Extra closing bracket, does not match cleanly
134
132
  });
135
133
 
136
134
  it('should handle a mixture of two and three brackets correctly', async () => {
137
- promptService.storePrompt('12', 'Hi, {{name}}}'); // (invalid)
138
- promptService.storePrompt('13', 'Hello, {{{name}}'); // (invalid)
139
- promptService.storePrompt('14', 'Greetings, {{{name}}}}'); // (invalid)
140
- promptService.storePrompt('15', 'Bye, {{{{name}}}'); // (invalid)
141
- promptService.storePrompt('16', 'Ciao, {{{{name}}}}'); // (invalid)
142
- promptService.storePrompt('17', 'Hi, {{name}}! {{{name}}}'); // Mixed valid patterns
135
+ promptService.storePromptTemplate({ id: '12', template: 'Hi, {{name}}}' }); // (invalid)
136
+ promptService.storePromptTemplate({ id: '13', template: 'Hello, {{{name}}' }); // (invalid)
137
+ promptService.storePromptTemplate({ id: '14', template: 'Greetings, {{{name}}}}' }); // (invalid)
138
+ promptService.storePromptTemplate({ id: '15', template: 'Bye, {{{{name}}}' }); // (invalid)
139
+ promptService.storePromptTemplate({ id: '16', template: 'Ciao, {{{{name}}}}' }); // (invalid)
140
+ promptService.storePromptTemplate({ id: '17', template: 'Hi, {{name}}! {{{name}}}' }); // Mixed valid patterns
143
141
 
144
142
  const prompt12 = await promptService.getPrompt('12', { name: 'John' });
145
143
  expect(prompt12?.text).to.equal('Hi, {{name}}}');
@@ -161,87 +159,143 @@ describe('PromptService', () => {
161
159
  });
162
160
 
163
161
  it('should strip single-line comments at the start of the template', () => {
164
- promptService.storePrompt('comment-basic', '{{!-- Comment --}}Hello, {{name}}!');
162
+ promptService.storePromptTemplate({ id: 'comment-basic', template: '{{!-- Comment --}}Hello, {{name}}!' });
165
163
  const prompt = promptService.getUnresolvedPrompt('comment-basic');
166
164
  expect(prompt?.template).to.equal('Hello, {{name}}!');
167
165
  });
168
166
 
169
167
  it('should remove line break after first-line comment', () => {
170
- promptService.storePrompt('comment-line-break', '{{!-- Comment --}}\nHello, {{name}}!');
168
+ promptService.storePromptTemplate({ id: 'comment-line-break', template: '{{!-- Comment --}}\nHello, {{name}}!' });
171
169
  const prompt = promptService.getUnresolvedPrompt('comment-line-break');
172
170
  expect(prompt?.template).to.equal('Hello, {{name}}!');
173
171
  });
174
172
 
175
173
  it('should strip multiline comments at the start of the template', () => {
176
- promptService.storePrompt('comment-multiline', '{{!--\nMultiline comment\n--}}\nGoodbye, {{name}}!');
174
+ promptService.storePromptTemplate({ id: 'comment-multiline', template: '{{!--\nMultiline comment\n--}}\nGoodbye, {{name}}!' });
177
175
  const prompt = promptService.getUnresolvedPrompt('comment-multiline');
178
176
  expect(prompt?.template).to.equal('Goodbye, {{name}}!');
179
177
  });
180
178
 
181
179
  it('should not strip comments not in the first line', () => {
182
- promptService.storePrompt('comment-second-line', 'Hello, {{name}}!\n{{!-- Comment --}}');
180
+ promptService.storePromptTemplate({ id: 'comment-second-line', template: 'Hello, {{name}}!\n{{!-- Comment --}}' });
183
181
  const prompt = promptService.getUnresolvedPrompt('comment-second-line');
184
182
  expect(prompt?.template).to.equal('Hello, {{name}}!\n{{!-- Comment --}}');
185
183
  });
186
184
 
187
185
  it('should treat unclosed comments as regular text', () => {
188
- promptService.storePrompt('comment-unclosed', '{{!-- Unclosed comment');
186
+ promptService.storePromptTemplate({ id: 'comment-unclosed', template: '{{!-- Unclosed comment' });
189
187
  const prompt = promptService.getUnresolvedPrompt('comment-unclosed');
190
188
  expect(prompt?.template).to.equal('{{!-- Unclosed comment');
191
189
  });
192
190
 
193
191
  it('should treat standalone closing delimiters as regular text', () => {
194
- promptService.storePrompt('comment-standalone', '--}} Hello, {{name}}!');
192
+ promptService.storePromptTemplate({ id: 'comment-standalone', template: '--}} Hello, {{name}}!' });
195
193
  const prompt = promptService.getUnresolvedPrompt('comment-standalone');
196
194
  expect(prompt?.template).to.equal('--}} Hello, {{name}}!');
197
195
  });
198
196
 
199
197
  it('should handle nested comments and stop at the first closing tag', () => {
200
- promptService.storePrompt('nested-comment', '{{!-- {{!-- Nested comment --}} --}}text');
198
+ promptService.storePromptTemplate({ id: 'nested-comment', template: '{{!-- {{!-- Nested comment --}} --}}text' });
201
199
  const prompt = promptService.getUnresolvedPrompt('nested-comment');
202
200
  expect(prompt?.template).to.equal('--}}text');
203
201
  });
204
202
 
205
203
  it('should handle templates with only comments', () => {
206
- promptService.storePrompt('comment-only', '{{!-- Only comments --}}');
204
+ promptService.storePromptTemplate({ id: 'comment-only', template: '{{!-- Only comments --}}' });
207
205
  const prompt = promptService.getUnresolvedPrompt('comment-only');
208
206
  expect(prompt?.template).to.equal('');
209
207
  });
210
208
 
211
209
  it('should handle mixed delimiters on the same line', () => {
212
- promptService.storePrompt('comment-mixed', '{{!-- Unclosed comment --}}');
210
+ promptService.storePromptTemplate({ id: 'comment-mixed', template: '{{!-- Unclosed comment --}}' });
213
211
  const prompt = promptService.getUnresolvedPrompt('comment-mixed');
214
212
  expect(prompt?.template).to.equal('');
215
213
  });
216
214
 
217
215
  it('should resolve variables after stripping single-line comments', async () => {
218
- promptService.storePrompt('comment-resolve', '{{!-- Comment --}}Hello, {{name}}!');
216
+ promptService.storePromptTemplate({ id: 'comment-resolve', template: '{{!-- Comment --}}Hello, {{name}}!' });
219
217
  const prompt = await promptService.getPrompt('comment-resolve', { name: 'John' });
220
218
  expect(prompt?.text).to.equal('Hello, John!');
221
219
  });
222
220
 
223
221
  it('should resolve variables in multiline templates with comments', async () => {
224
- promptService.storePrompt('comment-multiline-vars', '{{!--\nMultiline comment\n--}}\nHello, {{name}}!');
222
+ promptService.storePromptTemplate({ id: 'comment-multiline-vars', template: '{{!--\nMultiline comment\n--}}\nHello, {{name}}!' });
225
223
  const prompt = await promptService.getPrompt('comment-multiline-vars', { name: 'John' });
226
224
  expect(prompt?.text).to.equal('Hello, John!');
227
225
  });
228
226
 
229
227
  it('should resolve variables with standalone closing delimiters', async () => {
230
- promptService.storePrompt('comment-standalone-vars', '--}} Hello, {{name}}!');
228
+ promptService.storePromptTemplate({ id: 'comment-standalone-vars', template: '--}} Hello, {{name}}!' });
231
229
  const prompt = await promptService.getPrompt('comment-standalone-vars', { name: 'John' });
232
230
  expect(prompt?.text).to.equal('--}} Hello, John!');
233
231
  });
234
232
 
235
233
  it('should treat unclosed comments as text and resolve variables', async () => {
236
- promptService.storePrompt('comment-unclosed-vars', '{{!-- Unclosed comment\nHello, {{name}}!');
234
+ promptService.storePromptTemplate({ id: 'comment-unclosed-vars', template: '{{!-- Unclosed comment\nHello, {{name}}!' });
237
235
  const prompt = await promptService.getPrompt('comment-unclosed-vars', { name: 'John' });
238
236
  expect(prompt?.text).to.equal('{{!-- Unclosed comment\nHello, John!');
239
237
  });
240
238
 
241
239
  it('should handle templates with mixed comments and variables', async () => {
242
- promptService.storePrompt('comment-mixed-vars', '{{!-- Comment --}}Hi, {{name}}! {{!-- Another comment --}}');
240
+ promptService.storePromptTemplate({ id: 'comment-mixed-vars', template: '{{!-- Comment --}}Hi, {{name}}! {{!-- Another comment --}}' });
243
241
  const prompt = await promptService.getPrompt('comment-mixed-vars', { name: 'John' });
244
242
  expect(prompt?.text).to.equal('Hi, John! {{!-- Another comment --}}');
245
243
  });
246
244
 
245
+ it('should return all variant IDs of a given prompt', () => {
246
+ promptService.storePromptTemplate({ id: 'main', template: 'Main template' });
247
+
248
+ promptService.storePromptTemplate({
249
+ id: 'variant1',
250
+ template: 'Variant 1',
251
+ variantOf: 'main'
252
+ });
253
+ promptService.storePromptTemplate({
254
+ id: 'variant2',
255
+ template: 'Variant 2',
256
+ variantOf: 'main'
257
+ });
258
+ promptService.storePromptTemplate({
259
+ id: 'variant3',
260
+ template: 'Variant 3',
261
+ variantOf: 'main'
262
+ });
263
+
264
+ const variantIds = promptService.getVariantIds('main');
265
+ expect(variantIds).to.deep.equal(['variant1', 'variant2', 'variant3']);
266
+ });
267
+
268
+ it('should return an empty array if no variants exist for a given prompt', () => {
269
+ promptService.storePromptTemplate({ id: 'main', template: 'Main template' });
270
+
271
+ const variantIds = promptService.getVariantIds('main');
272
+ expect(variantIds).to.deep.equal([]);
273
+ });
274
+
275
+ it('should return an empty array if the main prompt ID does not exist', () => {
276
+ const variantIds = promptService.getVariantIds('nonExistent');
277
+ expect(variantIds).to.deep.equal([]);
278
+ });
279
+
280
+ it('should not influence prompts without variants when other prompts have variants', () => {
281
+ promptService.storePromptTemplate({ id: 'mainWithVariants', template: 'Main template with variants' });
282
+ promptService.storePromptTemplate({ id: 'mainWithoutVariants', template: 'Main template without variants' });
283
+
284
+ promptService.storePromptTemplate({
285
+ id: 'variant1',
286
+ template: 'Variant 1',
287
+ variantOf: 'mainWithVariants'
288
+ });
289
+ promptService.storePromptTemplate({
290
+ id: 'variant2',
291
+ template: 'Variant 2',
292
+ variantOf: 'mainWithVariants'
293
+ });
294
+
295
+ const variantsForMainWithVariants = promptService.getVariantIds('mainWithVariants');
296
+ const variantsForMainWithoutVariants = promptService.getVariantIds('mainWithoutVariants');
297
+
298
+ expect(variantsForMainWithVariants).to.deep.equal(['variant1', 'variant2']);
299
+ expect(variantsForMainWithoutVariants).to.deep.equal([]);
300
+ });
247
301
  });
@@ -21,10 +21,16 @@ import { ToolInvocationRegistry } from './tool-invocation-registry';
21
21
  import { toolRequestToPromptText } from './language-model-util';
22
22
  import { ToolRequest } from './language-model';
23
23
  import { matchFunctionsRegEx, matchVariablesRegEx } from './prompt-service-util';
24
+ import { AISettingsService } from './settings-service';
24
25
 
25
26
  export interface PromptTemplate {
26
27
  id: string;
27
28
  template: string;
29
+ /**
30
+ * (Optional) The ID of the main template for which this template is a variant.
31
+ * If present, this indicates that the current template represents an alternative version of the specified main template.
32
+ */
33
+ variantOf?: string;
28
34
  }
29
35
 
30
36
  export interface PromptMap { [id: string]: PromptTemplate }
@@ -63,11 +69,10 @@ export interface PromptService {
63
69
  */
64
70
  getPrompt(id: string, args?: { [key: string]: unknown }): Promise<ResolvedPromptTemplate | undefined>;
65
71
  /**
66
- * Adds a prompt to the list of prompts.
67
- * @param id the id of the prompt
68
- * @param prompt the prompt template to store
72
+ * Adds a {@link PromptTemplate} to the list of prompts.
73
+ * @param promptTemplate the prompt template to store
69
74
  */
70
- storePrompt(id: string, prompt: string): void;
75
+ storePromptTemplate(promptTemplate: PromptTemplate): void;
71
76
  /**
72
77
  * Removes a prompt from the list of prompts.
73
78
  * @param id the id of the prompt
@@ -77,6 +82,20 @@ export interface PromptService {
77
82
  * Return all known prompts as a {@link PromptMap map}.
78
83
  */
79
84
  getAllPrompts(): PromptMap;
85
+ /**
86
+ * Retrieve all variant IDs of a given {@link PromptTemplate}.
87
+ * @param id the id of the main {@link PromptTemplate}
88
+ * @returns an array of string IDs representing the variants of the given template
89
+ */
90
+ getVariantIds(id: string): string[];
91
+ /**
92
+ * Retrieve the currently selected variant ID for a given main prompt ID.
93
+ * If a variant is selected for the main prompt, it will be returned.
94
+ * Otherwise, the main prompt ID will be returned.
95
+ * @param id the id of the main prompt
96
+ * @returns the variant ID if one is selected, or the main prompt ID otherwise
97
+ */
98
+ getVariantId(id: string): Promise<string>;
80
99
  }
81
100
 
82
101
  export interface CustomAgentDescription {
@@ -117,6 +136,7 @@ export interface PromptCustomizationService {
117
136
  */
118
137
  getCustomizedPromptTemplate(id: string): string | undefined
119
138
 
139
+ getCustomPromptTemplateIDs(): string[];
120
140
  /**
121
141
  * Edit the template. If the content is specified, is will be
122
142
  * used to customize the template. Otherwise, the behavior depends
@@ -163,6 +183,9 @@ export interface PromptCustomizationService {
163
183
 
164
184
  @injectable()
165
185
  export class PromptServiceImpl implements PromptService {
186
+ @inject(AISettingsService) @optional()
187
+ protected readonly settingsService: AISettingsService | undefined;
188
+
166
189
  @inject(PromptCustomizationService) @optional()
167
190
  protected readonly customizationService: PromptCustomizationService | undefined;
168
191
 
@@ -203,8 +226,22 @@ export class PromptServiceImpl implements PromptService {
203
226
  return commentRegex.test(template) ? template.replace(commentRegex, '').trimStart() : template;
204
227
  }
205
228
 
229
+ async getVariantId(id: string): Promise<string> {
230
+ if (this.settingsService !== undefined) {
231
+ const agentSettingsMap = await this.settingsService.getSettings();
232
+
233
+ for (const agentSettings of Object.values(agentSettingsMap)) {
234
+ if (agentSettings.selectedVariants && agentSettings.selectedVariants[id]) {
235
+ return agentSettings.selectedVariants[id];
236
+ }
237
+ }
238
+ }
239
+ return id;
240
+ }
241
+
206
242
  async getPrompt(id: string, args?: { [key: string]: unknown }): Promise<ResolvedPromptTemplate | undefined> {
207
- const prompt = this.getUnresolvedPrompt(id);
243
+ const variantId = await this.getVariantId(id);
244
+ const prompt = this.getUnresolvedPrompt(variantId);
208
245
  if (prompt === undefined) {
209
246
  return undefined;
210
247
  }
@@ -274,10 +311,24 @@ export class PromptServiceImpl implements PromptService {
274
311
  return { ...this._prompts };
275
312
  }
276
313
  }
277
- storePrompt(id: string, prompt: string): void {
278
- this._prompts[id] = { id, template: prompt };
279
- }
280
314
  removePrompt(id: string): void {
281
315
  delete this._prompts[id];
282
316
  }
317
+ getVariantIds(id: string): string[] {
318
+ const allCustomPromptTemplateIds = this.customizationService?.getCustomPromptTemplateIDs() || [];
319
+ const knownPromptIds = Object.keys(this._prompts);
320
+
321
+ // We filter out known IDs from the custom prompt template IDs, these are no variants, but customizations. Then we retain IDs that start with the main ID
322
+ const customVariantIds = allCustomPromptTemplateIds.filter(customId =>
323
+ !knownPromptIds.includes(customId) && customId.startsWith(id)
324
+ );
325
+ const variantIds = Object.values(this._prompts)
326
+ .filter(prompt => prompt.variantOf === id)
327
+ .map(variant => variant.id);
328
+
329
+ return [...variantIds, ...customVariantIds];
330
+ }
331
+ storePromptTemplate(promptTemplate: PromptTemplate): void {
332
+ this._prompts[promptTemplate.id] = promptTemplate;
333
+ }
283
334
  }
@@ -30,4 +30,9 @@ export type AISettings = Record<string, AgentSettings>;
30
30
  export interface AgentSettings {
31
31
  languageModelRequirements?: LanguageModelRequirement[];
32
32
  enable?: boolean;
33
+ /**
34
+ * A mapping of main template IDs to their selected variant IDs.
35
+ * If a main template is not present in this mapping, it means the main template is used.
36
+ */
37
+ selectedVariants?: Record<string, string>;
33
38
  }