Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { ILanguageModelToolsService } from '../common/tools/languageModelToolsSe
import { agentPluginDiscoveryRegistry, IAgentPluginService } from '../common/plugins/agentPluginService.js';
import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js';
import { PromptsConfig } from '../common/promptSyntax/config/config.js';
import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, PromptFileSource } from '../common/promptSyntax/config/promptFileLocations.js';
import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, PromptFileSource, COPILOT_USER_AGENTS_SOURCE_FOLDER } from '../common/promptSyntax/config/promptFileLocations.js';
import { PromptLanguageFeaturesProvider } from '../common/promptSyntax/promptFileContributions.js';
import { AGENT_DOCUMENTATION_URL, INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL, SKILL_DOCUMENTATION_URL, HOOK_DOCUMENTATION_URL, PromptsType } from '../common/promptSyntax/promptTypes.js';
import { hookFileSchema, HOOK_SCHEMA_URI } from '../common/promptSyntax/hookSchema.js';
Expand Down Expand Up @@ -884,6 +884,7 @@ configurationRegistry.registerConfiguration({
default: {
[AGENTS_SOURCE_FOLDER]: true,
[CLAUDE_AGENTS_SOURCE_FOLDER]: true,
[COPILOT_USER_AGENTS_SOURCE_FOLDER]: true,
},
additionalProperties: { type: 'boolean' },
propertyNames: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export const AGENTS_SOURCE_FOLDER = '.github/agents';
*/
export const CLAUDE_AGENTS_SOURCE_FOLDER = '.claude/agents';

/**
* Copilot user agents folder.
*/
export const COPILOT_USER_AGENTS_SOURCE_FOLDER = '~/.copilot/agents';

/**
* Claude rules folder.
*/
Expand Down Expand Up @@ -186,6 +191,7 @@ export const DEFAULT_AGENT_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [
{ path: AGENTS_SOURCE_FOLDER, source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local },
{ path: CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local },
{ path: '~/' + CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user },
{ path: COPILOT_USER_AGENTS_SOURCE_FOLDER, source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user },
];

/**
Expand All @@ -204,7 +210,7 @@ export const DEFAULT_HOOK_FILE_PATHS: readonly IPromptSourceFolder[] = [
*/
function isInAgentsFolder(fileUri: URI): boolean {
const dir = dirname(fileUri.path);
return dir.endsWith('/' + AGENTS_SOURCE_FOLDER) || dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER);
return dir.endsWith('/' + AGENTS_SOURCE_FOLDER) || dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER) || isInCopilotAgentsFolder(fileUri);
}

/**
Expand All @@ -215,6 +221,14 @@ export function isInClaudeAgentsFolder(fileUri: URI): boolean {
return dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER);
}

/**
* Helper function to check if a file is directly in the ~/.config/copilot/agents/ folder.
*/
export function isInCopilotAgentsFolder(fileUri: URI): boolean {
const dir = dirname(fileUri.path);
return dir.endsWith(COPILOT_USER_AGENTS_SOURCE_FOLDER.substring(1));
Comment thread
DonJayamanne marked this conversation as resolved.
}

/**
* Helper function to check if a file is inside the .claude/rules/ folder (including subfolders).
* Claude rules files (.md) in this folder are treated as instruction files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ import { ResourceMap } from '../../../../../../base/common/map.js';
import { IFileService } from '../../../../../../platform/files/common/files.js';
import { IPromptsService, Target } from '../service/promptsService.js';
import { ILabelService } from '../../../../../../platform/label/common/label.js';
import { AGENTS_SOURCE_FOLDER, CLAUDE_AGENTS_SOURCE_FOLDER, isInClaudeRulesFolder, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js';
import { AGENTS_SOURCE_FOLDER, isInClaudeAgentsFolder, isInClaudeRulesFolder, isInCopilotAgentsFolder, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js';
import { Lazy } from '../../../../../../base/common/lazy.js';
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
import { dirname } from '../../../../../../base/common/resources.js';
import { URI } from '../../../../../../base/common/uri.js';

export const MARKERS_OWNER_ID = 'prompts-diagnostics-provider';
Expand Down Expand Up @@ -1023,10 +1022,12 @@ export function isVSCodeOrDefaultTarget(target: Target): boolean {
export function getTarget(promptType: PromptsType, header: PromptHeader | URI): Target {
const uri = header instanceof URI ? header : header.uri;
if (promptType === PromptsType.agent) {
const parentDir = dirname(uri);
if (parentDir.path.endsWith(`/${CLAUDE_AGENTS_SOURCE_FOLDER}`)) {
if (isInClaudeAgentsFolder(uri)) {
return Target.Claude;
}
if (isInCopilotAgentsFolder(uri)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not correct, for agents from .copilot/agents, we use the target property from the header

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert this chaneg

return Target.GitHubCopilot;
}
if (!(header instanceof URI)) {
const target = header.target;
if (target === Target.GitHubCopilot || target === Target.VSCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ suite('promptFileLocations', function () {
assert.strictEqual(getPromptFileType(uri), undefined);
});

test('.md files in .claude/agents/ folder should be recognized as agent files', () => {
const uri = URI.file('/workspace/.claude/agents/demonstrate.md');
assert.strictEqual(getPromptFileType(uri), PromptsType.agent);
});

test('README.md in .claude/agents/ should NOT be recognized as agent file', () => {
const uri = URI.file('/workspace/.claude/agents/README.md');
assert.strictEqual(getPromptFileType(uri), undefined);
});

test('.md files in ~/.copilot/agents/ folder should be recognized as agent files', () => {
const uri = URI.file('/home/user/.copilot/agents/my-agent.md');
assert.strictEqual(getPromptFileType(uri), PromptsType.agent);
});

test('README.md in ~/.copilot/agents/ should NOT be recognized as agent file', () => {
const uri = URI.file('/home/user/.copilot/agents/README.md');
assert.strictEqual(getPromptFileType(uri), undefined);
});
Comment thread
DonJayamanne marked this conversation as resolved.

test('.md files outside .github/agents/ should not be recognized as agent files', () => {
const uri = URI.file('/workspace/test/foo.md');
assert.strictEqual(getPromptFileType(uri), undefined);
Expand Down Expand Up @@ -130,6 +150,16 @@ suite('promptFileLocations', function () {
assert.strictEqual(getCleanPromptName(uri), 'demonstrate');
});

test('removes .md extension for files in .claude/agents/', () => {
const uri = URI.file('/workspace/.claude/agents/claude-agent.md');
assert.strictEqual(getCleanPromptName(uri), 'claude-agent');
});

test('removes .md extension for files in ~/.copilot/agents/', () => {
const uri = URI.file('/home/user/.copilot/agents/my-agent.md');
assert.strictEqual(getCleanPromptName(uri), 'my-agent');
});

test('README.md in .github/agents/ should keep .md extension', () => {
const uri = URI.file('/workspace/.github/agents/README.md');
assert.strictEqual(getCleanPromptName(uri), 'README.md');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,56 @@ suite('PromptsService', () => {
);
});

test('copilot user agents from ~/.copilot/agents/ should have GitHubCopilot target', async () => {
const rootFolderName = 'copilot-user-agents';
const rootFolder = `/${rootFolderName}`;
const rootFolderUri = URI.file(rootFolder);

workspaceContextService.setWorkspace(testWorkspace(rootFolderUri));

await mockFiles(fileService, [
{
// Copilot user agent in ~/.copilot/agents/ (resolved from /home/user/.copilot/agents/)
path: '/home/user/.copilot/agents/copilot-user-agent.md',
contents: [
'---',
'description: \'Copilot user agent from home folder.\'',
'tools: [ read ]',
'---',
'I am a Copilot user agent.',
]
},
]);

const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) }));
const expected: ICustomAgent[] = [
{
name: 'copilot-user-agent',
description: 'Copilot user agent from home folder.',
target: Target.GitHubCopilot,
tools: ['read'],
agentInstructions: {
content: 'I am a Copilot user agent.',
toolReferences: [],
metadata: undefined
},
handOffs: undefined,
model: undefined,
argumentHint: undefined,
visibility: { userInvocable: true, agentInvocable: true },
agents: undefined,
uri: URI.file('/home/user/.copilot/agents/copilot-user-agent.md'),
source: { storage: PromptsStorage.user }
},
];

assert.deepEqual(
result,
expected,
'Agents from ~/.copilot/agents/ must have Target.GitHubCopilot.',
);
});

test('agents with .md extension should be recognized, except README.md', async () => {
const rootFolderName = 'custom-agents-md-extension';
const rootFolder = `/${rootFolderName}`;
Expand Down
Loading