Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { Range } from '../../../../../../editor/common/core/range.js';
import { ITextModel } from '../../../../../../editor/common/model.js';
import { IModelService } from '../../../../../../editor/common/services/model.js';
import { localize } from '../../../../../../nls.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js';
import { ChatMode, IChatMode, IChatModeService } from '../../chatModes.js';
import { ChatModeKind } from '../../constants.js';
import { ChatConfiguration, ChatModeKind } from '../../constants.js';
import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js';
import { ILanguageModelToolsService, SpecedToolAliases } from '../../tools/languageModelToolsService.js';
import { getPromptsTypeForLanguageId, PromptsType, Target } from '../promptTypes.js';
Expand All @@ -21,7 +22,6 @@ import { Disposable, DisposableStore, toDisposable } from '../../../../../../bas
import { Delayer } from '../../../../../../base/common/async.js';
import { ResourceMap } from '../../../../../../base/common/map.js';
import { IFileService } from '../../../../../../platform/files/common/files.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
import { IPromptsService } 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, VALID_SKILL_NAME_REGEX } from '../config/promptFileLocations.js';
Expand Down Expand Up @@ -828,6 +828,12 @@ export class PromptValidator {
report(toMarker(localize('promptValidator.disableModelInvocationMustBeBoolean', "The 'disable-model-invocation' attribute must be 'true' or 'false'."), attribute.value.range, MarkerSeverity.Error));
return;
}

if (attribute.value.type === 'scalar' && attribute.value.value === 'false') {
if (!this.isCustomAgentInSubagentEnabled()) {
report(toMarker(localize('promptValidator.inferRequiresConfig', "For agents to be used as subagent you also need to enable the 'chat.customAgentInSubagent.enabled' setting."), attribute.value.range, MarkerSeverity.Warning));
}
}
}

private async validateAgentsAttribute(attributes: IHeaderAttribute[], header: PromptHeader, report: (markers: IMarkerData) => void): Promise<undefined> {
Expand All @@ -840,6 +846,11 @@ export class PromptValidator {
return;
}

// Check if the configuration setting is enabled
if (!this.isCustomAgentInSubagentEnabled()) {
report(toMarker(localize('promptValidator.agentsRequiresConfig', "For agents to be used as subagent you also need to enable the 'chat.customAgentInSubagent.enabled' setting."), attribute.range, MarkerSeverity.Warning));
}

// Collect available agent names
const agents = await this.promptsService.getCustomAgents(CancellationToken.None);
const availableAgentNames = new Set<string>(agents.map(agent => agent.name));
Expand Down Expand Up @@ -867,6 +878,10 @@ export class PromptValidator {
}
}

private isCustomAgentInSubagentEnabled(): boolean {
return !!this.configurationService.getValue<boolean>(ChatConfiguration.SubagentToolCustomAgents);
}

private validateGithubPermissions(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void {
const attribute = attributes.find(attr => attr.key === GithubPromptHeaderAttributes.github);
if (!attribute) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,23 @@ import { PromptFileParser } from '../../../../common/promptSyntax/promptFilePars
import { ICustomAgent, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js';
import { MockChatModeService } from '../../../common/mockChatModeService.js';
import { MockPromptsService } from '../../../common/promptSyntax/service/mockPromptsService.js';
import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js';

suite('PromptValidator', () => {
const disposables = ensureNoDisposablesAreLeakedInTestSuite();

let instaService: TestInstantiationService;
let testConfigService: TestConfigurationService;

const existingRef1 = URI.parse('myFs://test/reference1.md');
const existingRef2 = URI.parse('myFs://test/reference2.md');

setup(async () => {

const testConfigService = new TestConfigurationService();
testConfigService = new TestConfigurationService();
testConfigService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true);
testConfigService.setUserConfiguration('chat.useCustomAgentHooks', true);
testConfigService.setUserConfiguration(PromptsConfig.USE_CUSTOM_AGENT_HOOKS, true);
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
instaService = workbenchInstantiationService({
contextKeyService: () => disposables.add(new ContextKeyService(testConfigService)),
configurationService: () => testConfigService
Expand Down Expand Up @@ -1098,6 +1101,7 @@ suite('PromptValidator', () => {

// Valid infer: true (maps to 'all') - shows deprecation warning
{
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'name: "TestAgent"',
Expand Down Expand Up @@ -1128,70 +1132,6 @@ suite('PromptValidator', () => {
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
}

// Valid infer: 'all' - shows deprecation warning
{
const content = [
'---',
'name: "TestAgent"',
'description: "Test agent"',
'infer: all',
'---',
'Body',
].join('\n');
const markers = await validate(content, PromptsType.agent);
assert.strictEqual(markers.length, 1, 'infer: all should produce deprecation warning');
assert.strictEqual(markers[0].message, deprecationMessage);
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
}

// Valid infer: 'user' - shows deprecation warning
{
const content = [
'---',
'name: "TestAgent"',
'description: "Test agent"',
'infer: user',
'---',
'Body',
].join('\n');
const markers = await validate(content, PromptsType.agent);
assert.strictEqual(markers.length, 1, 'infer: user should produce deprecation warning');
assert.strictEqual(markers[0].message, deprecationMessage);
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
}

// Valid infer: 'agent' - shows deprecation warning
{
const content = [
'---',
'name: "TestAgent"',
'description: "Test agent"',
'infer: agent',
'---',
'Body',
].join('\n');
const markers = await validate(content, PromptsType.agent);
assert.strictEqual(markers.length, 1, 'infer: agent should produce deprecation warning');
assert.strictEqual(markers[0].message, deprecationMessage);
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
}

// Valid infer: 'hidden' - shows deprecation warning
{
const content = [
'---',
'name: "TestAgent"',
'description: "Test agent"',
'infer: hidden',
'---',
'Body',
].join('\n');
const markers = await validate(content, PromptsType.agent);
assert.strictEqual(markers.length, 1, 'infer: hidden should produce deprecation warning');
assert.strictEqual(markers[0].message, deprecationMessage);
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
}

// Invalid infer: unknown string value - shows deprecation warning (validation removed for deprecated attribute)
{
const content = [
Expand All @@ -1208,37 +1148,42 @@ suite('PromptValidator', () => {
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
}

// Invalid infer: number value - shows deprecation warning (validation removed for deprecated attribute)
// Missing infer attribute (should be optional)
{
const content = [
'---',
'name: "TestAgent"',
'description: "Test agent"',
'infer: 1',
'---',
'Body',
].join('\n');
const markers = await validate(content, PromptsType.agent);
assert.strictEqual(markers.length, 1, 'infer: 1 should produce deprecation warning');
assert.strictEqual(markers[0].message, deprecationMessage);
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
assert.deepStrictEqual(markers, [], 'Missing infer attribute should be allowed');
}
});

// Missing infer attribute (should be optional)
test('disable-model-invocation: false warns when customAgentInSubagent.enabled is disabled', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, false);

// disable-model-invocation: false should warn when config is disabled
{
const content = [
'---',
'name: "TestAgent"',
'description: "Test agent"',
'disable-model-invocation: false',
'---',
'Body',
].join('\n');
const markers = await validate(content, PromptsType.agent);
assert.deepStrictEqual(markers, [], 'Missing infer attribute should be allowed');
assert.strictEqual(markers.length, 1);
assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
assert.strictEqual(markers[0].message, `For agents to be used as subagent you also need to enable the 'chat.customAgentInSubagent.enabled' setting.`);
}
});

test('agents attribute must be an array', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'description: "Test"',
Expand All @@ -1250,6 +1195,7 @@ suite('PromptValidator', () => {
});

test('each agent name in agents attribute must be a string', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'description: "Test"',
Expand All @@ -1276,6 +1222,7 @@ suite('PromptValidator', () => {
});

test('agents attribute with non-empty value requires agent tool 1', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'description: "Test"',
Expand All @@ -1287,6 +1234,7 @@ suite('PromptValidator', () => {
});

test('agents attribute with non-empty value requires agent tool 2', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'description: "Test"',
Expand All @@ -1299,6 +1247,7 @@ suite('PromptValidator', () => {
});

test('agents attribute with non-empty value requires agent tool 3', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'description: "Test"',
Expand All @@ -1311,6 +1260,7 @@ suite('PromptValidator', () => {
});

test('agents attribute with non-empty value requires agent tool 4', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'description: "Test"',
Expand All @@ -1323,6 +1273,7 @@ suite('PromptValidator', () => {
});

test('agents attribute with empty array does not require agent tool', async () => {
testConfigService.setUserConfiguration(ChatConfiguration.SubagentToolCustomAgents, true);
const content = [
'---',
'description: "Test"',
Expand Down
Loading