Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/chat/browser/promptsService.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken } from '../../../../base/common/cancellation.js';
7
import { FileAccess } from '../../../../base/common/network.js';
8
import { basename, dirname, joinPath } from '../../../../base/common/resources.js';
9
import { SKILL_FILENAME } from '../../../../workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.js';
10
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
11
import { IAgentSkill, IPromptPath, PromptsStorage } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
12
import { PromptsService } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.js';
13
import { BUILTIN_STORAGE, IBuiltinPromptPath } from '../common/builtinPromptsStorage.js';
14
15
/** URI root for built-in skills bundled with the Agents app. */
16
export const BUILTIN_SKILLS_URI = FileAccess.asFileUri('vs/sessions/skills');
17
18
/**
19
* Sessions-specific PromptsService that additionally discovers built-in skills
20
* bundled at `vs/sessions/skills/{folder}/SKILL.md`.
21
*
22
* Built-in skills are merged into `findAgentSkills()` and exposed via
23
* `listPromptFilesForStorage(skill, BUILTIN_STORAGE)` so that the existing
24
* AI customization UI (groups, badges, overrides) picks them up naturally.
25
*
26
* User/workspace skills with the same folder name take precedence (built-ins
27
* are appended last and filtered when overridden).
28
*/
29
export class AgenticPromptsService extends PromptsService {
30
31
private _builtinSkillsCache: Promise<readonly IAgentSkill[]> | undefined;
32
33
private async getBuiltinSkills(): Promise<readonly IAgentSkill[]> {
34
if (!this._builtinSkillsCache) {
35
this._builtinSkillsCache = this.discoverBuiltinSkills();
36
}
37
return this._builtinSkillsCache;
38
}
39
40
private async discoverBuiltinSkills(): Promise<readonly IAgentSkill[]> {
41
try {
42
const stat = await this.fileService.resolve(BUILTIN_SKILLS_URI);
43
if (!stat.children) {
44
return [];
45
}
46
47
const skills: IAgentSkill[] = [];
48
for (const child of stat.children) {
49
if (!child.isDirectory) {
50
continue;
51
}
52
const skillFileUri = joinPath(child.resource, SKILL_FILENAME);
53
try {
54
const parsed = await this.parseNew(skillFileUri, CancellationToken.None);
55
const rawName = parsed.header?.name;
56
const rawDescription = parsed.header?.description;
57
if (!rawName || !rawDescription) {
58
continue;
59
}
60
const name = sanitizeSkillText(rawName, 64);
61
const description = sanitizeSkillText(rawDescription, 1024);
62
const folderName = basename(child.resource);
63
if (name !== folderName) {
64
continue;
65
}
66
skills.push({
67
uri: skillFileUri,
68
storage: BUILTIN_STORAGE as PromptsStorage,
69
name,
70
description,
71
disableModelInvocation: parsed.header?.disableModelInvocation === true,
72
userInvocable: parsed.header?.userInvocable !== false,
73
});
74
} catch (e) {
75
this.logger.warn(`[AgenticPromptsService] Failed to parse built-in skill: ${skillFileUri}`, e instanceof Error ? e.message : String(e));
76
}
77
}
78
return skills;
79
} catch {
80
return [];
81
}
82
}
83
84
private async getBuiltinSkillPaths(): Promise<readonly IBuiltinPromptPath[]> {
85
const skills = await this.getBuiltinSkills();
86
return skills.map(s => ({
87
uri: s.uri,
88
storage: BUILTIN_STORAGE,
89
type: PromptsType.skill,
90
name: s.name,
91
description: s.description,
92
}));
93
}
94
95
public override async findAgentSkills(token: CancellationToken): Promise<IAgentSkill[] | undefined> {
96
const baseResult = await super.findAgentSkills(token);
97
if (baseResult === undefined) {
98
return undefined;
99
}
100
101
const builtinSkills = await this.getBuiltinSkills();
102
if (builtinSkills.length === 0) {
103
return baseResult;
104
}
105
106
const existingNames = new Set(
107
baseResult
108
.filter(s => s.storage === PromptsStorage.local || s.storage === PromptsStorage.user)
109
.map(s => s.name)
110
);
111
const disabledSkills = this.getDisabledPromptFiles(PromptsType.skill);
112
const nonOverridden = builtinSkills.filter(s => !existingNames.has(s.name) && !disabledSkills.has(s.uri));
113
if (nonOverridden.length === 0) {
114
return baseResult;
115
}
116
117
return [...baseResult, ...nonOverridden];
118
}
119
120
public override async listPromptFiles(type: PromptsType, token: CancellationToken): Promise<readonly IPromptPath[]> {
121
const baseResults = await super.listPromptFiles(type, token);
122
123
if (type !== PromptsType.skill) {
124
return baseResults;
125
}
126
127
const builtinItems = await this.getBuiltinSkillPaths();
128
if (builtinItems.length === 0) {
129
return baseResults;
130
}
131
132
// Filter out built-ins overridden by user/workspace skills of the same folder name.
133
const overriddenNames = new Set<string>();
134
for (const p of baseResults) {
135
if (p.storage === PromptsStorage.local || p.storage === PromptsStorage.user) {
136
overriddenNames.add(basename(dirname(p.uri)));
137
}
138
}
139
const nonOverridden = builtinItems.filter(p => !overriddenNames.has(basename(dirname(p.uri))));
140
141
// Built-in items use BUILTIN_STORAGE ('builtin') which is not in the core
142
// IPromptPath union but is handled by the sessions UI layer.
143
return [...baseResults, ...nonOverridden] as readonly IPromptPath[];
144
}
145
146
public override async listPromptFilesForStorage(type: PromptsType, storage: PromptsStorage, token: CancellationToken): Promise<readonly IPromptPath[]> {
147
if ((storage as PromptsStorage | typeof BUILTIN_STORAGE) === BUILTIN_STORAGE) {
148
if (type === PromptsType.skill) {
149
return this.getBuiltinSkillPaths() as Promise<readonly IPromptPath[]>;
150
}
151
return [];
152
}
153
return super.listPromptFilesForStorage(type, storage, token);
154
}
155
}
156
157
/**
158
* Strips XML tags and truncates to the given max length.
159
* Matches the sanitization applied by PromptsService for other skill sources.
160
*/
161
function sanitizeSkillText(text: string, maxLength: number): string {
162
const sanitized = text.replace(/<[^>]+>/g, '');
163
return sanitized.length > maxLength ? sanitized.substring(0, maxLength) : sanitized;
164
}
165
166