@theia/ai-core 1.61.0 → 1.62.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.
- package/lib/browser/ai-core-frontend-module.js +2 -2
- package/lib/browser/ai-core-frontend-module.js.map +1 -1
- package/lib/browser/frontend-language-model-service.d.ts +1 -1
- package/lib/browser/frontend-language-model-service.d.ts.map +1 -1
- package/lib/browser/frontend-language-model-service.js.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.d.ts +142 -48
- package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.js +452 -153
- package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
- package/lib/browser/prompttemplate-contribution.d.ts +1 -2
- package/lib/browser/prompttemplate-contribution.d.ts.map +1 -1
- package/lib/browser/prompttemplate-contribution.js +5 -9
- package/lib/browser/prompttemplate-contribution.js.map +1 -1
- package/lib/common/agent-service.d.ts.map +1 -1
- package/lib/common/agent-service.js +14 -2
- package/lib/common/agent-service.js.map +1 -1
- package/lib/common/agent.d.ts +8 -3
- package/lib/common/agent.d.ts.map +1 -1
- package/lib/common/agent.js.map +1 -1
- package/lib/common/index.d.ts +0 -1
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +0 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/language-model-interaction-model.d.ts +74 -0
- package/lib/common/language-model-interaction-model.d.ts.map +1 -0
- package/lib/common/language-model-interaction-model.js +3 -0
- package/lib/common/language-model-interaction-model.js.map +1 -0
- package/lib/common/language-model-service.d.ts +26 -3
- package/lib/common/language-model-service.d.ts.map +1 -1
- package/lib/common/language-model-service.js +83 -6
- package/lib/common/language-model-service.js.map +1 -1
- package/lib/common/language-model-util.d.ts +3 -2
- package/lib/common/language-model-util.d.ts.map +1 -1
- package/lib/common/language-model-util.js +8 -0
- package/lib/common/language-model-util.js.map +1 -1
- package/lib/common/language-model.d.ts +25 -2
- package/lib/common/language-model.d.ts.map +1 -1
- package/lib/common/language-model.js +3 -1
- package/lib/common/language-model.js.map +1 -1
- package/lib/common/prompt-service.d.ts +332 -126
- package/lib/common/prompt-service.d.ts.map +1 -1
- package/lib/common/prompt-service.js +363 -102
- package/lib/common/prompt-service.js.map +1 -1
- package/lib/common/prompt-service.spec.js +104 -114
- package/lib/common/prompt-service.spec.js.map +1 -1
- package/lib/common/prompt-variable-contribution.d.ts +1 -2
- package/lib/common/prompt-variable-contribution.d.ts.map +1 -1
- package/lib/common/prompt-variable-contribution.js +17 -26
- package/lib/common/prompt-variable-contribution.js.map +1 -1
- package/package.json +10 -10
- package/src/browser/ai-core-frontend-module.ts +4 -4
- package/src/browser/frontend-language-model-service.ts +1 -1
- package/src/browser/frontend-prompt-customization-service.ts +574 -183
- package/src/browser/prompttemplate-contribution.ts +6 -9
- package/src/common/agent-service.ts +14 -4
- package/src/common/agent.ts +9 -3
- package/src/common/index.ts +0 -1
- package/src/common/language-model-interaction-model.ts +98 -0
- package/src/common/language-model-service.ts +115 -6
- package/src/common/language-model-util.ts +10 -2
- package/src/common/language-model.ts +28 -2
- package/src/common/prompt-service.spec.ts +108 -114
- package/src/common/prompt-service.ts +694 -221
- package/src/common/prompt-variable-contribution.ts +22 -27
- package/lib/common/communication-recording-service.d.ts +0 -30
- package/lib/common/communication-recording-service.d.ts.map +0 -1
- package/lib/common/communication-recording-service.js +0 -20
- package/lib/common/communication-recording-service.js.map +0 -1
- package/src/common/communication-recording-service.ts +0 -55
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { DisposableCollection, URI, Event, Emitter } from '@theia/core';
|
|
18
18
|
import { OpenerService } from '@theia/core/lib/browser';
|
|
19
19
|
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
20
|
-
import {
|
|
20
|
+
import { PromptFragmentCustomizationService, CustomAgentDescription, CustomizedPromptFragment } from '../common';
|
|
21
21
|
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
|
|
22
22
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
23
23
|
import { FileChangesEvent } from '@theia/filesystem/lib/common/files';
|
|
@@ -26,49 +26,97 @@ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
|
26
26
|
import { load, dump } from 'js-yaml';
|
|
27
27
|
import { PROMPT_TEMPLATE_EXTENSION } from './prompttemplate-contribution';
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Default template entry for creating custom agents
|
|
31
|
+
*/
|
|
32
|
+
const newCustomAgentEntry = {
|
|
30
33
|
id: 'my_agent',
|
|
31
34
|
name: 'My Agent',
|
|
32
35
|
description: 'This is an example agent. Please adapt the properties to fit your needs.',
|
|
33
|
-
prompt:
|
|
36
|
+
prompt: `{{!-- Note: The context section below will resolve all context elements (e.g. files) to their full content
|
|
37
|
+
in the system prompt. Context elements can be added by the user in the default chat view (e.g. via DnD or the "+" button).
|
|
38
|
+
If you want a more fine-grained, on demand resolvement of context elements, you can also resolve files to their paths only
|
|
39
|
+
and equip the agent with functions so that the LLM can retrieve files on demand. See the Coder Agent prompt for an example.--}}
|
|
40
|
+
|
|
41
|
+
# Role
|
|
42
|
+
You are an example agent. Be nice and helpful to the user.
|
|
43
|
+
|
|
44
|
+
## Current Context
|
|
45
|
+
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.
|
|
46
|
+
{{contextDetails}}`,
|
|
34
47
|
defaultLLM: 'openai/gpt-4o'
|
|
35
48
|
};
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
export enum CustomizationSource {
|
|
51
|
+
CUSTOMIZED = 1,
|
|
52
|
+
FOLDER = 2,
|
|
53
|
+
FILE = 3,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getCustomizationSourceString(origin: CustomizationSource): string {
|
|
57
|
+
switch (origin) {
|
|
58
|
+
case CustomizationSource.FILE:
|
|
59
|
+
return 'Workspace Template Files';
|
|
60
|
+
case CustomizationSource.FOLDER:
|
|
61
|
+
return 'Workspace Template Directories';
|
|
62
|
+
default:
|
|
63
|
+
return 'Prompt Templates Folder';
|
|
64
|
+
}
|
|
43
65
|
}
|
|
44
66
|
|
|
45
67
|
/**
|
|
46
|
-
* Interface defining properties that can be updated in the service
|
|
68
|
+
* Interface defining properties that can be updated in the customization service
|
|
47
69
|
*/
|
|
48
|
-
export interface
|
|
70
|
+
export interface PromptFragmentCustomizationProperties {
|
|
49
71
|
/** Array of directory paths to load templates from */
|
|
50
72
|
directoryPaths?: string[];
|
|
73
|
+
|
|
51
74
|
/** Array of file paths to treat as templates */
|
|
52
75
|
filePaths?: string[];
|
|
76
|
+
|
|
53
77
|
/** Array of file extensions to consider as template files */
|
|
54
78
|
extensions?: string[];
|
|
55
79
|
}
|
|
56
80
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Internal representation of a fragment entry in the customization service
|
|
83
|
+
*/
|
|
84
|
+
interface PromptFragmentCustomization {
|
|
85
|
+
/** The template content */
|
|
86
|
+
template: string;
|
|
87
|
+
|
|
88
|
+
/** Source URI where this template is stored */
|
|
60
89
|
sourceUri: string;
|
|
90
|
+
|
|
91
|
+
/** Source type of the customization */
|
|
92
|
+
origin: CustomizationSource;
|
|
93
|
+
|
|
94
|
+
/** Priority level (higher values override lower ones) */
|
|
95
|
+
priority: number;
|
|
96
|
+
|
|
97
|
+
/** Fragment ID */
|
|
61
98
|
id: string;
|
|
99
|
+
|
|
100
|
+
/** Unique customization ID */
|
|
101
|
+
customizationId: string;
|
|
62
102
|
}
|
|
63
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Information about a template file being watched for changes
|
|
106
|
+
*/
|
|
64
107
|
interface WatchedFileInfo {
|
|
108
|
+
/** The URI of the watched file */
|
|
65
109
|
uri: URI;
|
|
66
|
-
|
|
110
|
+
|
|
111
|
+
/** The fragment ID associated with this file */
|
|
112
|
+
fragmentId: string;
|
|
113
|
+
|
|
114
|
+
/** The customization ID for this file */
|
|
115
|
+
customizationId: string;
|
|
67
116
|
}
|
|
68
117
|
|
|
69
118
|
@injectable()
|
|
70
|
-
export class
|
|
71
|
-
|
|
119
|
+
export class DefaultPromptFragmentCustomizationService implements PromptFragmentCustomizationService {
|
|
72
120
|
@inject(EnvVariablesServer)
|
|
73
121
|
protected readonly envVariablesServer: EnvVariablesServer;
|
|
74
122
|
|
|
@@ -83,32 +131,36 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
83
131
|
|
|
84
132
|
/** Stores URI strings of template files from directories currently being monitored for changes. */
|
|
85
133
|
protected trackedTemplateURIs = new Set<string>();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
134
|
+
|
|
135
|
+
/** Contains the currently active customization, mapped by prompt fragment ID. */
|
|
136
|
+
protected activeCustomizations = new Map<string, PromptFragmentCustomization>();
|
|
137
|
+
|
|
138
|
+
/** Tracks all loaded customizations, including overridden ones, mapped by source URI. */
|
|
139
|
+
protected allCustomizations = new Map<string, PromptFragmentCustomization>();
|
|
140
|
+
|
|
90
141
|
/** Stores additional directory paths for loading template files. */
|
|
91
142
|
protected additionalTemplateDirs = new Set<string>();
|
|
143
|
+
|
|
92
144
|
/** Contains file extensions that identify prompt template files. */
|
|
93
145
|
protected templateExtensions = new Set<string>([PROMPT_TEMPLATE_EXTENSION]);
|
|
94
|
-
|
|
95
|
-
|
|
146
|
+
|
|
147
|
+
/** Stores specific file paths, provided by the settings, that should be treated as templates. */
|
|
148
|
+
protected workspaceTemplateFiles = new Set<string>();
|
|
149
|
+
|
|
96
150
|
/** Maps URI strings to WatchedFileInfo objects for individually watched template files. */
|
|
97
151
|
protected watchedFiles = new Map<string, WatchedFileInfo>();
|
|
152
|
+
|
|
98
153
|
/** Collection of disposable resources for cleanup when the service updates or is disposed. */
|
|
99
154
|
protected toDispose = new DisposableCollection();
|
|
100
155
|
|
|
101
|
-
|
|
102
|
-
readonly
|
|
156
|
+
protected readonly onDidChangePromptFragmentCustomizationEmitter = new Emitter<string[]>();
|
|
157
|
+
readonly onDidChangePromptFragmentCustomization: Event<string[]> = this.onDidChangePromptFragmentCustomizationEmitter.event;
|
|
103
158
|
|
|
104
|
-
|
|
105
|
-
readonly onDidChangeCustomAgents = this.onDidChangeCustomAgentsEmitter.event;
|
|
159
|
+
protected readonly onDidChangeCustomAgentsEmitter = new Emitter<void>();
|
|
160
|
+
readonly onDidChangeCustomAgents: Event<void> = this.onDidChangeCustomAgentsEmitter.event;
|
|
106
161
|
|
|
107
162
|
@postConstruct()
|
|
108
163
|
protected init(): void {
|
|
109
|
-
// Ensure PROMPT_TEMPLATE_EXTENSION is always included in templateExtensions as a default
|
|
110
|
-
this.templateExtensions.add(PROMPT_TEMPLATE_EXTENSION);
|
|
111
|
-
|
|
112
164
|
this.preferences.onPreferenceChanged(event => {
|
|
113
165
|
if (event.preferenceName === PREFERENCE_NAME_PROMPT_TEMPLATES) {
|
|
114
166
|
this.update();
|
|
@@ -117,147 +169,210 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
117
169
|
this.update();
|
|
118
170
|
}
|
|
119
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Updates the service by reloading all template files and watching for changes
|
|
174
|
+
*/
|
|
120
175
|
protected async update(): Promise<void> {
|
|
121
176
|
this.toDispose.dispose();
|
|
122
177
|
// we need to assign local variables, so that updates running in parallel don't interfere with each other
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const
|
|
178
|
+
const activeCustomizationsCopy = new Map<string, PromptFragmentCustomization>();
|
|
179
|
+
const trackedTemplateURIsCopy = new Set<string>();
|
|
180
|
+
const allCustomizationsCopy = new Map<string, PromptFragmentCustomization>();
|
|
181
|
+
const watchedFilesCopy = new Map<string, WatchedFileInfo>();
|
|
127
182
|
|
|
128
183
|
// Process in order of priority (lowest to highest)
|
|
129
|
-
// First process the main
|
|
130
|
-
const
|
|
131
|
-
await this.processTemplateDirectory(
|
|
184
|
+
// First process the main templates directory (lowest priority)
|
|
185
|
+
const templatesURI = await this.getTemplatesDirectoryURI();
|
|
186
|
+
await this.processTemplateDirectory(
|
|
187
|
+
activeCustomizationsCopy, trackedTemplateURIsCopy, allCustomizationsCopy, templatesURI, 1, CustomizationSource.CUSTOMIZED); // Priority 1 for customized fragments
|
|
132
188
|
|
|
133
189
|
// Process additional template directories (medium priority)
|
|
134
190
|
for (const dirPath of this.additionalTemplateDirs) {
|
|
135
191
|
const dirURI = URI.fromFilePath(dirPath);
|
|
136
|
-
await this.processTemplateDirectory(
|
|
192
|
+
await this.processTemplateDirectory(
|
|
193
|
+
activeCustomizationsCopy, trackedTemplateURIsCopy, allCustomizationsCopy, dirURI, 2, CustomizationSource.FOLDER); // Priority 2 for folder fragments
|
|
137
194
|
}
|
|
138
195
|
|
|
139
196
|
// Process specific template files (highest priority)
|
|
140
|
-
await this.processTemplateFiles(
|
|
197
|
+
await this.processTemplateFiles(activeCustomizationsCopy, trackedTemplateURIsCopy, allCustomizationsCopy, watchedFilesCopy);
|
|
141
198
|
|
|
142
|
-
this.
|
|
143
|
-
this.trackedTemplateURIs =
|
|
144
|
-
this.
|
|
145
|
-
this.watchedFiles =
|
|
199
|
+
this.activeCustomizations = activeCustomizationsCopy;
|
|
200
|
+
this.trackedTemplateURIs = trackedTemplateURIsCopy;
|
|
201
|
+
this.allCustomizations = allCustomizationsCopy;
|
|
202
|
+
this.watchedFiles = watchedFilesCopy;
|
|
146
203
|
|
|
147
204
|
this.onDidChangeCustomAgentsEmitter.fire();
|
|
148
205
|
}
|
|
149
206
|
|
|
150
207
|
/**
|
|
151
|
-
* Adds a template to the
|
|
152
|
-
* @param
|
|
153
|
-
* @param id The
|
|
154
|
-
* @param
|
|
155
|
-
* @param priority The template priority
|
|
208
|
+
* Adds a template to the customizations map, handling conflicts based on priority
|
|
209
|
+
* @param activeCustomizationsCopy The map to add the customization to
|
|
210
|
+
* @param id The fragment ID
|
|
211
|
+
* @param template The template content
|
|
156
212
|
* @param sourceUri The URI of the source file (used to distinguish updates from conflicts)
|
|
157
|
-
* @param
|
|
213
|
+
* @param allCustomizationsCopy The map to track all loaded customizations
|
|
214
|
+
* @param priority The customization priority
|
|
215
|
+
* @param origin The source type of the customization
|
|
158
216
|
*/
|
|
159
217
|
protected addTemplate(
|
|
160
|
-
|
|
218
|
+
activeCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
161
219
|
id: string,
|
|
162
|
-
|
|
163
|
-
priority: number,
|
|
220
|
+
template: string,
|
|
164
221
|
sourceUri: string,
|
|
165
|
-
|
|
222
|
+
allCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
223
|
+
priority: number,
|
|
224
|
+
origin: CustomizationSource
|
|
166
225
|
): void {
|
|
167
|
-
//
|
|
226
|
+
// Generate a unique customization ID based on source URI and priority
|
|
227
|
+
const customizationId = this.generateCustomizationId(id, sourceUri);
|
|
228
|
+
|
|
229
|
+
// Always add to allCustomizationsCopy to keep track of all customizations including overridden ones
|
|
168
230
|
if (sourceUri) {
|
|
169
|
-
|
|
231
|
+
allCustomizationsCopy.set(sourceUri, { id, template, sourceUri, priority, customizationId, origin });
|
|
170
232
|
}
|
|
171
233
|
|
|
172
|
-
const existingEntry =
|
|
234
|
+
const existingEntry = activeCustomizationsCopy.get(id);
|
|
173
235
|
|
|
174
236
|
if (existingEntry) {
|
|
175
237
|
// If this is an update to the same file (same source URI)
|
|
176
238
|
if (sourceUri && existingEntry.sourceUri === sourceUri) {
|
|
177
239
|
// Update the content while keeping the same priority and source
|
|
178
|
-
|
|
240
|
+
activeCustomizationsCopy.set(id, { id, template, sourceUri, priority, customizationId, origin });
|
|
179
241
|
return;
|
|
180
242
|
}
|
|
181
243
|
|
|
182
|
-
// If the new
|
|
244
|
+
// If the new customization has higher priority, replace the existing one
|
|
183
245
|
if (priority > existingEntry.priority) {
|
|
184
|
-
|
|
246
|
+
activeCustomizationsCopy.set(id, { id, template, sourceUri, priority, customizationId, origin });
|
|
185
247
|
return;
|
|
186
248
|
} else if (priority === existingEntry.priority) {
|
|
187
|
-
// There is a conflict with the same priority, we ignore the new
|
|
249
|
+
// There is a conflict with the same priority, we ignore the new customization
|
|
188
250
|
const conflictSourceUri = existingEntry.sourceUri ? ` (Existing source: ${existingEntry.sourceUri}, New source: ${sourceUri})` : '';
|
|
189
|
-
console.warn(`
|
|
251
|
+
console.warn(`Fragment conflict detected for ID '${id}' with equal priority.${conflictSourceUri}`);
|
|
190
252
|
}
|
|
191
253
|
return;
|
|
192
254
|
}
|
|
193
255
|
|
|
194
|
-
// No conflict at all, add the
|
|
195
|
-
|
|
256
|
+
// No conflict at all, add the customization
|
|
257
|
+
activeCustomizationsCopy.set(id, { id, template, sourceUri, priority, customizationId, origin });
|
|
196
258
|
}
|
|
197
259
|
|
|
198
260
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
261
|
+
* Generates a unique customization ID based on the fragment ID, source URI, and priority
|
|
262
|
+
* @param id The fragment ID
|
|
263
|
+
* @param sourceUri The source URI of the template
|
|
264
|
+
* @returns A unique customization ID
|
|
265
|
+
*/
|
|
266
|
+
protected generateCustomizationId(id: string, sourceUri: string): string {
|
|
267
|
+
// Create a customization ID that contains information about the source and priority
|
|
268
|
+
// This ensures uniqueness across different customization sources
|
|
269
|
+
const sourceHash = this.hashString(sourceUri);
|
|
270
|
+
return `${id}_${sourceHash}`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Simple hash function to generate a short identifier from a string
|
|
275
|
+
* @param str The string to hash
|
|
276
|
+
* @returns A string hash
|
|
277
|
+
*/
|
|
278
|
+
protected hashString(str: string): string {
|
|
279
|
+
let hash = 0;
|
|
280
|
+
for (let i = 0; i < str.length; i++) {
|
|
281
|
+
const char = str.charCodeAt(i);
|
|
282
|
+
hash = ((hash << 5) - hash) + char;
|
|
283
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
284
|
+
}
|
|
285
|
+
return Math.abs(hash).toString(36).substring(0, 8);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Removes a customization from customizations maps based on the source URI.
|
|
290
|
+
* Also checks for any lower-priority customizations with the same ID that might need to be loaded.
|
|
201
291
|
* @param sourceUri The URI of the source file being removed
|
|
202
|
-
* @param
|
|
292
|
+
* @param allCustomizationsCopy The map of all loaded customizations
|
|
293
|
+
* @param activeCustomizationsCopy The map of active customizations
|
|
294
|
+
* @param trackedTemplateURIsCopy Optional set of tracked URIs to update
|
|
295
|
+
* @returns The fragment ID that was removed, or undefined if no customization was found
|
|
203
296
|
*/
|
|
204
|
-
protected
|
|
297
|
+
protected removeCustomizationFromMaps(
|
|
205
298
|
sourceUri: string,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
299
|
+
allCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
300
|
+
activeCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
301
|
+
trackedTemplateURIsCopy: Set<string>
|
|
302
|
+
): string | undefined {
|
|
303
|
+
// Get the customization entry from allCustomizationsCopy
|
|
304
|
+
const removedCustomization = allCustomizationsCopy.get(sourceUri);
|
|
305
|
+
if (!removedCustomization) {
|
|
306
|
+
return undefined;
|
|
212
307
|
}
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
308
|
+
const fragmentId = removedCustomization.id;
|
|
309
|
+
allCustomizationsCopy.delete(sourceUri);
|
|
310
|
+
trackedTemplateURIsCopy.delete(sourceUri);
|
|
311
|
+
|
|
312
|
+
// If the customization is in the active customizations map, we check if there is another customization previously conflicting with it
|
|
313
|
+
const activeCustomization = activeCustomizationsCopy.get(fragmentId);
|
|
314
|
+
if (activeCustomization && activeCustomization.sourceUri === sourceUri) {
|
|
315
|
+
activeCustomizationsCopy.delete(fragmentId);
|
|
316
|
+
// Find any lower-priority customizations with the same ID that were previously ignored
|
|
317
|
+
const lowerPriorityCustomizations = Array.from(allCustomizationsCopy.values())
|
|
318
|
+
.filter(t => t.id === fragmentId)
|
|
223
319
|
.sort((a, b) => b.priority - a.priority); // Sort by priority (highest first)
|
|
224
320
|
|
|
225
|
-
// If there are any lower-priority
|
|
226
|
-
if (
|
|
227
|
-
const
|
|
228
|
-
|
|
321
|
+
// If there are any lower-priority customizations, add the highest priority one
|
|
322
|
+
if (lowerPriorityCustomizations.length > 0) {
|
|
323
|
+
const highestRemainingCustomization = lowerPriorityCustomizations[0];
|
|
324
|
+
activeCustomizationsCopy.set(fragmentId, highestRemainingCustomization);
|
|
229
325
|
}
|
|
230
|
-
|
|
326
|
+
|
|
231
327
|
}
|
|
328
|
+
|
|
329
|
+
return fragmentId;
|
|
232
330
|
}
|
|
233
331
|
|
|
234
332
|
/**
|
|
235
333
|
* Process the template files specified by path, watching for changes
|
|
236
|
-
* and loading their content into the
|
|
334
|
+
* and loading their content into the customizations map
|
|
335
|
+
* @param activeCustomizationsCopy Map to store active customizations
|
|
336
|
+
* @param trackedTemplateURIsCopy Set to track URIs being monitored
|
|
337
|
+
* @param allCustomizationsCopy Map to store all loaded customizations
|
|
338
|
+
* @param watchedFilesCopy Map to store file watch information
|
|
237
339
|
*/
|
|
238
340
|
protected async processTemplateFiles(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
341
|
+
activeCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
342
|
+
trackedTemplateURIsCopy: Set<string>,
|
|
343
|
+
allCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
344
|
+
watchedFilesCopy: Map<string, WatchedFileInfo>
|
|
243
345
|
): Promise<void> {
|
|
346
|
+
const priority = 3; // Highest priority for specific files
|
|
244
347
|
|
|
245
|
-
|
|
348
|
+
const parsedPromptFragments = new Set<string>();
|
|
349
|
+
|
|
350
|
+
for (const filePath of this.workspaceTemplateFiles) {
|
|
246
351
|
const fileURI = URI.fromFilePath(filePath);
|
|
247
|
-
const
|
|
352
|
+
const fragmentId = this.getFragmentIdFromFilePath(filePath);
|
|
248
353
|
const uriString = fileURI.toString();
|
|
354
|
+
const customizationId = this.generateCustomizationId(fragmentId, uriString);
|
|
249
355
|
|
|
250
|
-
|
|
356
|
+
watchedFilesCopy.set(uriString, { uri: fileURI, fragmentId, customizationId });
|
|
251
357
|
this.toDispose.push(this.fileService.watch(fileURI, { recursive: false, excludes: [] }));
|
|
252
358
|
|
|
253
359
|
if (await this.fileService.exists(fileURI)) {
|
|
254
|
-
|
|
360
|
+
trackedTemplateURIsCopy.add(uriString);
|
|
255
361
|
const fileContent = await this.fileService.read(fileURI);
|
|
256
|
-
this.addTemplate(
|
|
362
|
+
this.addTemplate(activeCustomizationsCopy, fragmentId, fileContent.value, uriString, allCustomizationsCopy, priority, CustomizationSource.FILE);
|
|
363
|
+
parsedPromptFragments.add(fragmentId);
|
|
257
364
|
}
|
|
258
365
|
}
|
|
259
366
|
|
|
367
|
+
this.onDidChangePromptFragmentCustomizationEmitter.fire(Array.from(parsedPromptFragments));
|
|
368
|
+
|
|
260
369
|
this.toDispose.push(this.fileService.onDidFilesChange(async (event: FileChangesEvent) => {
|
|
370
|
+
// Only watch for changes that are in the watchedFiles map
|
|
371
|
+
if (!event.changes.some(change => this.watchedFiles.get(change.resource.toString()))) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// Track changes for batched notification
|
|
375
|
+
const changedFragmentIds = new Set<string>();
|
|
261
376
|
|
|
262
377
|
// Handle deleted files
|
|
263
378
|
for (const deletedFile of event.getDeleted()) {
|
|
@@ -265,9 +380,10 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
265
380
|
const fileInfo = this.watchedFiles.get(fileUriString);
|
|
266
381
|
|
|
267
382
|
if (fileInfo) {
|
|
268
|
-
this.
|
|
269
|
-
|
|
270
|
-
|
|
383
|
+
const removedFragmentId = this.removeCustomizationFromMaps(fileUriString, allCustomizationsCopy, activeCustomizationsCopy, trackedTemplateURIsCopy);
|
|
384
|
+
if (removedFragmentId) {
|
|
385
|
+
changedFragmentIds.add(removedFragmentId);
|
|
386
|
+
}
|
|
271
387
|
}
|
|
272
388
|
}
|
|
273
389
|
|
|
@@ -279,14 +395,15 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
279
395
|
if (fileInfo) {
|
|
280
396
|
const fileContent = await this.fileService.read(fileInfo.uri);
|
|
281
397
|
this.addTemplate(
|
|
282
|
-
this.
|
|
283
|
-
fileInfo.
|
|
398
|
+
this.activeCustomizations,
|
|
399
|
+
fileInfo.fragmentId,
|
|
284
400
|
fileContent.value,
|
|
285
|
-
TemplatePriority.TEMPLATE_FILE,
|
|
286
401
|
fileUriString,
|
|
287
|
-
|
|
402
|
+
this.allCustomizations,
|
|
403
|
+
priority,
|
|
404
|
+
CustomizationSource.FILE
|
|
288
405
|
);
|
|
289
|
-
|
|
406
|
+
changedFragmentIds.add(fileInfo.fragmentId);
|
|
290
407
|
}
|
|
291
408
|
}
|
|
292
409
|
|
|
@@ -298,60 +415,110 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
298
415
|
if (fileInfo) {
|
|
299
416
|
const fileContent = await this.fileService.read(fileInfo.uri);
|
|
300
417
|
this.addTemplate(
|
|
301
|
-
this.
|
|
302
|
-
fileInfo.
|
|
418
|
+
this.activeCustomizations,
|
|
419
|
+
fileInfo.fragmentId,
|
|
303
420
|
fileContent.value,
|
|
304
|
-
TemplatePriority.TEMPLATE_FILE,
|
|
305
421
|
fileUriString,
|
|
306
|
-
|
|
422
|
+
this.allCustomizations,
|
|
423
|
+
priority,
|
|
424
|
+
CustomizationSource.FILE
|
|
307
425
|
);
|
|
308
426
|
this.trackedTemplateURIs.add(fileUriString);
|
|
309
|
-
|
|
427
|
+
changedFragmentIds.add(fileInfo.fragmentId);
|
|
310
428
|
}
|
|
311
429
|
}
|
|
430
|
+
|
|
431
|
+
const changedFragmentIdsArray = Array.from(changedFragmentIds);
|
|
432
|
+
if (changedFragmentIdsArray.length > 0) {
|
|
433
|
+
this.onDidChangePromptFragmentCustomizationEmitter.fire(changedFragmentIdsArray);
|
|
434
|
+
};
|
|
312
435
|
}));
|
|
313
436
|
}
|
|
314
437
|
|
|
315
438
|
/**
|
|
316
|
-
* Extract a
|
|
439
|
+
* Extract a fragment ID from a file path
|
|
317
440
|
* @param filePath The path to the template file
|
|
318
|
-
* @returns A
|
|
441
|
+
* @returns A fragment ID derived from the file name
|
|
319
442
|
*/
|
|
320
|
-
protected
|
|
443
|
+
protected getFragmentIdFromFilePath(filePath: string): string {
|
|
321
444
|
const uri = URI.fromFilePath(filePath);
|
|
322
445
|
return this.removePromptTemplateSuffix(uri.path.name);
|
|
323
446
|
}
|
|
324
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Processes a directory for template files, adding them to the customizations map
|
|
450
|
+
* and setting up file watching
|
|
451
|
+
* @param activeCustomizationsCopy Map to store active customizations
|
|
452
|
+
* @param trackedTemplateURIsCopy Set to track URIs being monitored
|
|
453
|
+
* @param allCustomizationsCopy Map to store all loaded customizations
|
|
454
|
+
* @param dirURI URI of the directory to process
|
|
455
|
+
* @param priority Priority level for customizations in this directory
|
|
456
|
+
* @param customizationSource Source type of the customization
|
|
457
|
+
*/
|
|
325
458
|
protected async processTemplateDirectory(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
459
|
+
activeCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
460
|
+
trackedTemplateURIsCopy: Set<string>,
|
|
461
|
+
allCustomizationsCopy: Map<string, PromptFragmentCustomization>,
|
|
329
462
|
dirURI: URI,
|
|
330
|
-
priority:
|
|
463
|
+
priority: number,
|
|
464
|
+
customizationSource: CustomizationSource
|
|
331
465
|
): Promise<void> {
|
|
466
|
+
if (!(await this.fileService.exists(dirURI))) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const stat = await this.fileService.resolve(dirURI);
|
|
470
|
+
if (stat.children === undefined) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const parsedPromptFragments = new Set<string>();
|
|
474
|
+
for (const file of stat.children) {
|
|
475
|
+
if (!file.isFile) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const fileURI = file.resource;
|
|
479
|
+
if (this.isPromptTemplateExtension(fileURI.path.ext)) {
|
|
480
|
+
trackedTemplateURIsCopy.add(fileURI.toString());
|
|
481
|
+
const fileContent = await this.fileService.read(fileURI);
|
|
482
|
+
const fragmentId = this.removePromptTemplateSuffix(file.name);
|
|
483
|
+
this.addTemplate(activeCustomizationsCopy, fragmentId, fileContent.value, fileURI.toString(), allCustomizationsCopy, priority, customizationSource);
|
|
484
|
+
parsedPromptFragments.add(fragmentId);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
this.onDidChangePromptFragmentCustomizationEmitter.fire(Array.from(parsedPromptFragments));
|
|
488
|
+
this.onDidChangeCustomAgentsEmitter.fire();
|
|
489
|
+
|
|
332
490
|
this.toDispose.push(this.fileService.watch(dirURI, { recursive: true, excludes: [] }));
|
|
333
491
|
this.toDispose.push(this.fileService.onDidFilesChange(async (event: FileChangesEvent) => {
|
|
492
|
+
// Only watch for changes within provided dir
|
|
493
|
+
if (!event.changes.some(change => change.resource.toString().startsWith(dirURI.toString()))) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
334
496
|
if (event.changes.some(change => change.resource.toString().endsWith('customAgents.yml'))) {
|
|
335
497
|
this.onDidChangeCustomAgentsEmitter.fire();
|
|
336
498
|
}
|
|
337
499
|
|
|
500
|
+
// Track changes for batched notification
|
|
501
|
+
const changedFragmentIds = new Set<string>();
|
|
502
|
+
|
|
338
503
|
// check deleted templates
|
|
339
504
|
for (const deletedFile of event.getDeleted()) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
505
|
+
const uriString = deletedFile.resource.toString();
|
|
506
|
+
if (this.trackedTemplateURIs.has(uriString)) {
|
|
507
|
+
const removedFragmentId = this.removeCustomizationFromMaps(uriString, this.allCustomizations, this.activeCustomizations, this.trackedTemplateURIs);
|
|
508
|
+
if (removedFragmentId) {
|
|
509
|
+
changedFragmentIds.add(removedFragmentId);
|
|
510
|
+
}
|
|
345
511
|
}
|
|
346
512
|
}
|
|
347
513
|
|
|
348
514
|
// check updated templates
|
|
349
515
|
for (const updatedFile of event.getUpdated()) {
|
|
350
|
-
|
|
516
|
+
const uriString = updatedFile.resource.toString();
|
|
517
|
+
if (this.trackedTemplateURIs.has(uriString)) {
|
|
351
518
|
const fileContent = await this.fileService.read(updatedFile.resource);
|
|
352
|
-
const
|
|
353
|
-
this.addTemplate(this.
|
|
354
|
-
|
|
519
|
+
const fragmentId = this.removePromptTemplateSuffix(updatedFile.resource.path.name);
|
|
520
|
+
this.addTemplate(this.activeCustomizations, fragmentId, fileContent.value, uriString, this.allCustomizations, priority, customizationSource);
|
|
521
|
+
changedFragmentIds.add(fragmentId);
|
|
355
522
|
}
|
|
356
523
|
}
|
|
357
524
|
|
|
@@ -359,37 +526,20 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
359
526
|
for (const addedFile of event.getAdded()) {
|
|
360
527
|
if (addedFile.resource.parent.toString() === dirURI.toString() &&
|
|
361
528
|
this.isPromptTemplateExtension(addedFile.resource.path.ext)) {
|
|
362
|
-
|
|
529
|
+
const uriString = addedFile.resource.toString();
|
|
530
|
+
this.trackedTemplateURIs.add(uriString);
|
|
363
531
|
const fileContent = await this.fileService.read(addedFile.resource);
|
|
364
|
-
const
|
|
365
|
-
this.addTemplate(this.
|
|
366
|
-
|
|
532
|
+
const fragmentId = this.removePromptTemplateSuffix(addedFile.resource.path.name);
|
|
533
|
+
this.addTemplate(this.activeCustomizations, fragmentId, fileContent.value, uriString, this.allCustomizations, priority, customizationSource);
|
|
534
|
+
changedFragmentIds.add(fragmentId);
|
|
367
535
|
}
|
|
368
536
|
}
|
|
369
|
-
}));
|
|
370
537
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
for (const file of stat.children) {
|
|
380
|
-
if (!file.isFile) {
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
const fileURI = file.resource;
|
|
384
|
-
if (this.isPromptTemplateExtension(fileURI.path.ext)) {
|
|
385
|
-
trackedURIs.add(fileURI.toString());
|
|
386
|
-
const fileContent = await this.fileService.read(fileURI);
|
|
387
|
-
const templateId = this.removePromptTemplateSuffix(file.name);
|
|
388
|
-
this.addTemplate(templatesMap, templateId, fileContent.value, priority, fileURI.toString(), loadedTemplates);
|
|
389
|
-
this.onDidChangePromptEmitter.fire(templateId);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
this.onDidChangeCustomAgentsEmitter.fire();
|
|
538
|
+
const changedFragmentIdsArray = Array.from(changedFragmentIds);
|
|
539
|
+
if (changedFragmentIdsArray.length > 0) {
|
|
540
|
+
this.onDidChangePromptFragmentCustomizationEmitter.fire(changedFragmentIdsArray);
|
|
541
|
+
};
|
|
542
|
+
}));
|
|
393
543
|
}
|
|
394
544
|
|
|
395
545
|
/**
|
|
@@ -422,16 +572,15 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
422
572
|
* @returns Array of file paths
|
|
423
573
|
*/
|
|
424
574
|
getTemplateFiles(): string[] {
|
|
425
|
-
return Array.from(this.
|
|
575
|
+
return Array.from(this.workspaceTemplateFiles);
|
|
426
576
|
}
|
|
427
577
|
|
|
428
578
|
/**
|
|
429
579
|
* Updates multiple configuration properties at once, triggering only a single update process.
|
|
430
|
-
*
|
|
431
580
|
* @param properties An object containing the properties to update
|
|
432
581
|
* @returns Promise that resolves when the update is complete
|
|
433
582
|
*/
|
|
434
|
-
async updateConfiguration(properties:
|
|
583
|
+
async updateConfiguration(properties: PromptFragmentCustomizationProperties): Promise<void> {
|
|
435
584
|
if (properties.directoryPaths !== undefined) {
|
|
436
585
|
this.additionalTemplateDirs.clear();
|
|
437
586
|
for (const path of properties.directoryPaths) {
|
|
@@ -449,9 +598,9 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
449
598
|
}
|
|
450
599
|
|
|
451
600
|
if (properties.filePaths !== undefined) {
|
|
452
|
-
this.
|
|
601
|
+
this.workspaceTemplateFiles.clear();
|
|
453
602
|
for (const path of properties.filePaths) {
|
|
454
|
-
this.
|
|
603
|
+
this.workspaceTemplateFiles.add(path);
|
|
455
604
|
}
|
|
456
605
|
}
|
|
457
606
|
|
|
@@ -459,6 +608,10 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
459
608
|
await this.update();
|
|
460
609
|
}
|
|
461
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Gets the URI of the templates directory
|
|
613
|
+
* @returns URI of the templates directory
|
|
614
|
+
*/
|
|
462
615
|
protected async getTemplatesDirectoryURI(): Promise<URI> {
|
|
463
616
|
const templatesFolder = this.preferences[PREFERENCE_NAME_PROMPT_TEMPLATES];
|
|
464
617
|
if (templatesFolder && templatesFolder.trim().length > 0) {
|
|
@@ -468,10 +621,20 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
468
621
|
return new URI(theiaConfigDir).resolve('prompt-templates');
|
|
469
622
|
}
|
|
470
623
|
|
|
471
|
-
|
|
472
|
-
|
|
624
|
+
/**
|
|
625
|
+
* Gets the URI for a specific template file
|
|
626
|
+
* @param fragmentId The fragment ID
|
|
627
|
+
* @returns URI for the template file
|
|
628
|
+
*/
|
|
629
|
+
protected async getTemplateURI(fragmentId: string): Promise<URI> {
|
|
630
|
+
return (await this.getTemplatesDirectoryURI()).resolve(`${fragmentId}${PROMPT_TEMPLATE_EXTENSION}`);
|
|
473
631
|
}
|
|
474
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Removes the prompt template extension from a filename
|
|
635
|
+
* @param filename The filename with extension
|
|
636
|
+
* @returns The filename without the extension
|
|
637
|
+
*/
|
|
475
638
|
protected removePromptTemplateSuffix(filename: string): string {
|
|
476
639
|
for (const ext of this.templateExtensions) {
|
|
477
640
|
if (filename.endsWith(ext)) {
|
|
@@ -481,43 +644,271 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
481
644
|
return filename;
|
|
482
645
|
}
|
|
483
646
|
|
|
484
|
-
|
|
485
|
-
|
|
647
|
+
// PromptFragmentCustomizationService interface implementation
|
|
648
|
+
|
|
649
|
+
isPromptFragmentCustomized(id: string): boolean {
|
|
650
|
+
return this.activeCustomizations.has(id);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
getActivePromptFragmentCustomization(id: string): CustomizedPromptFragment | undefined {
|
|
654
|
+
const entry = this.activeCustomizations.get(id);
|
|
655
|
+
if (!entry) {
|
|
656
|
+
return undefined;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return {
|
|
660
|
+
id: entry.id,
|
|
661
|
+
template: entry.template,
|
|
662
|
+
customizationId: entry.customizationId,
|
|
663
|
+
priority: entry.priority
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
getAllCustomizations(id: string): CustomizedPromptFragment[] {
|
|
668
|
+
const fragments: CustomizedPromptFragment[] = [];
|
|
669
|
+
|
|
670
|
+
// Collect all customizations with matching ID
|
|
671
|
+
this.allCustomizations.forEach(value => {
|
|
672
|
+
if (value.id === id) {
|
|
673
|
+
fragments.push({
|
|
674
|
+
id: value.id,
|
|
675
|
+
template: value.template,
|
|
676
|
+
customizationId: value.customizationId,
|
|
677
|
+
priority: value.priority
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Sort by priority (highest first)
|
|
683
|
+
return fragments.sort((a, b) => b.priority - a.priority);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
getCustomizedPromptFragmentIds(): string[] {
|
|
687
|
+
return Array.from(this.activeCustomizations.keys());
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async createPromptFragmentCustomization(id: string, defaultContent?: string): Promise<void> {
|
|
691
|
+
await this.editTemplate(id, defaultContent);
|
|
486
692
|
}
|
|
487
693
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return entry ? entry.content : undefined;
|
|
694
|
+
async createBuiltInPromptFragmentCustomization(id: string, defaultContent?: string): Promise<void> {
|
|
695
|
+
await this.createPromptFragmentCustomization(id, defaultContent);
|
|
491
696
|
}
|
|
492
697
|
|
|
493
|
-
|
|
494
|
-
|
|
698
|
+
async editPromptFragmentCustomization(id: string, customizationId: string): Promise<void> {
|
|
699
|
+
// Find the customization with the given customization ID
|
|
700
|
+
const customization = Array.from(this.allCustomizations.values()).find(t =>
|
|
701
|
+
t.id === id && t.customizationId === customizationId
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
if (customization) {
|
|
705
|
+
const uri = new URI(customization.sourceUri);
|
|
706
|
+
const openHandler = await this.openerService.getOpener(uri);
|
|
707
|
+
openHandler.open(uri);
|
|
708
|
+
} else {
|
|
709
|
+
// Fall back to editing by fragment ID if customization ID not found
|
|
710
|
+
await this.editTemplate(id);
|
|
711
|
+
}
|
|
495
712
|
}
|
|
496
713
|
|
|
497
|
-
|
|
714
|
+
/**
|
|
715
|
+
* Edits a template by opening it in the editor, creating it if it doesn't exist
|
|
716
|
+
* @param id The fragment ID
|
|
717
|
+
* @param defaultContent Optional default content for new templates
|
|
718
|
+
*/
|
|
719
|
+
protected async editTemplate(id: string, defaultContent?: string): Promise<void> {
|
|
498
720
|
const editorUri = await this.getTemplateURI(id);
|
|
499
|
-
if (!
|
|
721
|
+
if (!(await this.fileService.exists(editorUri))) {
|
|
500
722
|
await this.fileService.createFile(editorUri, BinaryBuffer.fromString(defaultContent ?? ''));
|
|
501
723
|
}
|
|
502
724
|
const openHandler = await this.openerService.getOpener(editorUri);
|
|
503
725
|
openHandler.open(editorUri);
|
|
504
726
|
}
|
|
505
727
|
|
|
506
|
-
async
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
728
|
+
async removePromptFragmentCustomization(id: string, customizationId: string): Promise<void> {
|
|
729
|
+
// Find the customization with the given customization ID
|
|
730
|
+
const customization = Array.from(this.allCustomizations.values()).find(t =>
|
|
731
|
+
t.id === id && t.customizationId === customizationId
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
if (customization) {
|
|
735
|
+
const sourceUri = customization.sourceUri;
|
|
736
|
+
|
|
737
|
+
// Delete the file if it exists
|
|
738
|
+
const uri = new URI(sourceUri);
|
|
739
|
+
if (await this.fileService.exists(uri)) {
|
|
740
|
+
await this.fileService.delete(uri);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async removeAllPromptFragmentCustomizations(id: string): Promise<void> {
|
|
746
|
+
// Get all customizations for this fragment ID
|
|
747
|
+
const customizations = this.getAllCustomizations(id);
|
|
748
|
+
|
|
749
|
+
if (customizations.length === 0) {
|
|
750
|
+
return; // Nothing to reset
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Find and delete all customization files
|
|
754
|
+
for (const customization of customizations) {
|
|
755
|
+
const fragment = Array.from(this.allCustomizations.values()).find(t =>
|
|
756
|
+
t.id === id && t.customizationId === customization.customizationId
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
if (fragment) {
|
|
760
|
+
const sourceUri = fragment.sourceUri;
|
|
761
|
+
// Delete the file if it exists
|
|
762
|
+
const uri = new URI(sourceUri);
|
|
763
|
+
if (await this.fileService.exists(uri)) {
|
|
764
|
+
await this.fileService.delete(uri);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async resetToCustomization(id: string, customizationId: string): Promise<void> {
|
|
771
|
+
const customization = Array.from(this.allCustomizations.values()).find(t =>
|
|
772
|
+
t.id === id && t.customizationId === customizationId
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
if (customization) {
|
|
776
|
+
// Get all customizations for this fragment ID
|
|
777
|
+
const customizations = this.getAllCustomizations(id);
|
|
778
|
+
|
|
779
|
+
if (customizations.length === 0) {
|
|
780
|
+
return; // Nothing to reset
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Find the target customization
|
|
784
|
+
const targetCustomization = customizations.find(c => c.customizationId === customizationId);
|
|
785
|
+
if (!targetCustomization) {
|
|
786
|
+
return; // Target customization not found
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Find and delete all higher-priority customization files
|
|
790
|
+
for (const cust of customizations) {
|
|
791
|
+
if (cust.priority > targetCustomization.priority) {
|
|
792
|
+
const fragmentToDelete = Array.from(this.allCustomizations.values()).find(t =>
|
|
793
|
+
t.id === cust.id && t.customizationId === cust.customizationId
|
|
794
|
+
);
|
|
795
|
+
if (fragmentToDelete) {
|
|
796
|
+
const sourceUri = fragmentToDelete.sourceUri;
|
|
797
|
+
|
|
798
|
+
// Delete the file if it exists
|
|
799
|
+
const uri = new URI(sourceUri);
|
|
800
|
+
if (await this.fileService.exists(uri)) {
|
|
801
|
+
await this.fileService.delete(uri);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async getPromptFragmentCustomizationDescription(id: string, customizationId: string): Promise<string | undefined> {
|
|
810
|
+
// Find the customization with the given customization ID
|
|
811
|
+
const customization = Array.from(this.allCustomizations.values()).find(t =>
|
|
812
|
+
t.id === id && t.customizationId === customizationId
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
if (customization) {
|
|
816
|
+
return customization.sourceUri;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return undefined;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
async getPromptFragmentCustomizationType(id: string, customizationId: string): Promise<string | undefined> {
|
|
823
|
+
// Find the customization with the given customization ID
|
|
824
|
+
const customization = Array.from(this.allCustomizations.values()).find(t =>
|
|
825
|
+
t.id === id && t.customizationId === customizationId
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
if (customization) {
|
|
829
|
+
return getCustomizationSourceString(customization.origin);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return undefined;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async editBuiltIn(id: string, defaultContent = ''): Promise<void> {
|
|
836
|
+
// Find an existing built-in customization (those with priority 1)
|
|
837
|
+
const builtInCustomization = Array.from(this.allCustomizations.values()).find(t =>
|
|
838
|
+
t.id === id && t.priority === 1
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
if (builtInCustomization) {
|
|
842
|
+
// Edit the existing built-in customization
|
|
843
|
+
const uri = new URI(builtInCustomization.sourceUri);
|
|
844
|
+
const openHandler = await this.openerService.getOpener(uri);
|
|
845
|
+
openHandler.open(uri);
|
|
846
|
+
} else {
|
|
847
|
+
// Create a new built-in customization
|
|
848
|
+
// Get the template URI in the main templates directory (priority 1)
|
|
849
|
+
const templateUri = await this.getTemplateURI(id);
|
|
850
|
+
|
|
851
|
+
// If template doesn't exist, create it with default content
|
|
852
|
+
if (!(await this.fileService.exists(templateUri))) {
|
|
853
|
+
await this.fileService.createFile(templateUri, BinaryBuffer.fromString(defaultContent));
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Open the template in the editor
|
|
857
|
+
const openHandler = await this.openerService.getOpener(templateUri);
|
|
858
|
+
openHandler.open(templateUri);
|
|
510
859
|
}
|
|
511
860
|
}
|
|
512
861
|
|
|
513
|
-
|
|
862
|
+
async resetBuiltInCustomization(id: string): Promise<void> {
|
|
863
|
+
// Find a built-in customization (those with priority 1)
|
|
864
|
+
const builtInCustomization = Array.from(this.allCustomizations.values()).find(t =>
|
|
865
|
+
t.id === id && t.priority === 1
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
if (!builtInCustomization) {
|
|
869
|
+
return; // No built-in customization found
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const sourceUri = builtInCustomization.sourceUri;
|
|
873
|
+
|
|
874
|
+
// Delete the file if it exists
|
|
875
|
+
const uri = new URI(sourceUri);
|
|
876
|
+
if (await this.fileService.exists(uri)) {
|
|
877
|
+
await this.fileService.delete(uri);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async editBuiltInPromptFragmentCustomization(id: string, defaultContent?: string): Promise<void> {
|
|
882
|
+
return this.editBuiltIn(id, defaultContent);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Gets the fragment ID from a URI
|
|
887
|
+
* @param uri URI to check
|
|
888
|
+
* @returns Fragment ID or undefined if not found
|
|
889
|
+
*/
|
|
890
|
+
protected getFragmentIDFromURI(uri: URI): string | undefined {
|
|
514
891
|
const id = this.removePromptTemplateSuffix(uri.path.name);
|
|
515
|
-
if (this.
|
|
892
|
+
if (this.activeCustomizations.has(id)) {
|
|
516
893
|
return id;
|
|
517
894
|
}
|
|
518
895
|
return undefined;
|
|
519
896
|
}
|
|
520
897
|
|
|
898
|
+
/**
|
|
899
|
+
* Implementation of the generic getPromptFragmentIDFromResource method in the interface
|
|
900
|
+
* Accepts any resource identifier but only processes URIs
|
|
901
|
+
* @param resourceId Resource to check
|
|
902
|
+
* @returns Fragment ID or undefined if not found
|
|
903
|
+
*/
|
|
904
|
+
getPromptFragmentIDFromResource(resourceId: unknown): string | undefined {
|
|
905
|
+
// Check if the resource is a URI
|
|
906
|
+
if (resourceId instanceof URI) {
|
|
907
|
+
return this.getFragmentIDFromURI(resourceId);
|
|
908
|
+
}
|
|
909
|
+
return undefined;
|
|
910
|
+
}
|
|
911
|
+
|
|
521
912
|
async getCustomAgents(): Promise<CustomAgentDescription[]> {
|
|
522
913
|
const agentsById = new Map<string, CustomAgentDescription>();
|
|
523
914
|
// First, process additional (workspace) template directories to give them precedence
|
|
@@ -525,9 +916,9 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
525
916
|
const dirURI = URI.fromFilePath(dirPath);
|
|
526
917
|
await this.loadCustomAgentsFromDirectory(dirURI, agentsById);
|
|
527
918
|
}
|
|
528
|
-
// Then process global
|
|
529
|
-
const
|
|
530
|
-
await this.loadCustomAgentsFromDirectory(
|
|
919
|
+
// Then process global templates directory (only adding agents that don't conflict)
|
|
920
|
+
const globalTemplatesDir = await this.getTemplatesDirectoryURI();
|
|
921
|
+
await this.loadCustomAgentsFromDirectory(globalTemplatesDir, agentsById);
|
|
531
922
|
// Return the merged list of agents
|
|
532
923
|
return Array.from(agentsById.values());
|
|
533
924
|
}
|
|
@@ -577,9 +968,9 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
577
968
|
*/
|
|
578
969
|
async getCustomAgentsLocations(): Promise<{ uri: URI, exists: boolean }[]> {
|
|
579
970
|
const locations: { uri: URI, exists: boolean }[] = [];
|
|
580
|
-
// Check global
|
|
581
|
-
const
|
|
582
|
-
const globalAgentsUri =
|
|
971
|
+
// Check global templates directory
|
|
972
|
+
const globalTemplatesDir = await this.getTemplatesDirectoryURI();
|
|
973
|
+
const globalAgentsUri = globalTemplatesDir.resolve('customAgents.yml');
|
|
583
974
|
const globalExists = await this.fileService.exists(globalAgentsUri);
|
|
584
975
|
locations.push({ uri: globalAgentsUri, exists: globalExists });
|
|
585
976
|
// Check additional (workspace) template directories
|
|
@@ -598,7 +989,7 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
|
|
|
598
989
|
* @param uri The URI of the customAgents.yml file to open or create
|
|
599
990
|
*/
|
|
600
991
|
async openCustomAgentYaml(uri: URI): Promise<void> {
|
|
601
|
-
const content = dump([
|
|
992
|
+
const content = dump([newCustomAgentEntry]);
|
|
602
993
|
if (! await this.fileService.exists(uri)) {
|
|
603
994
|
await this.fileService.createFile(uri, BinaryBuffer.fromString(content));
|
|
604
995
|
} else {
|