Skip to content

Commit aa9c4a3

Browse files
committed
Replace infer with disable-model-invocation and user-invokable
1 parent e4b1b83 commit aa9c4a3

File tree

14 files changed

+351
-109
lines changed

14 files changed

+351
-109
lines changed

src/vs/workbench/contrib/chat/common/chatModes.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import { IChatAgentService } from './participants/chatAgents.js';
2020
import { ChatContextKeys } from './actions/chatContextKeys.js';
2121
import { ChatConfiguration, ChatModeKind } from './constants.js';
2222
import { IHandOff } from './promptSyntax/promptFileParser.js';
23-
import { ExtensionAgentSourceType, IAgentSource, ICustomAgent, InferValue, IPromptsService, PromptsStorage } from './promptSyntax/service/promptsService.js';
23+
import { ExtensionAgentSourceType, IAgentSource, ICustomAgent, ICustomAgentVisibility, IPromptsService, isCustomAgentVisibility, PromptsStorage } from './promptSyntax/service/promptsService.js';
2424
import { ThemeIcon } from '../../../../base/common/themables.js';
2525
import { Codicon } from '../../../../base/common/codicons.js';
26-
import { isBoolean, isString } from '../../../../base/common/types.js';
26+
import { isString } from '../../../../base/common/types.js';
2727

2828
export const IChatModeService = createDecorator<IChatModeService>('chatModeService');
2929
export interface IChatModeService {
@@ -122,7 +122,7 @@ export class ChatModeService extends Disposable implements IChatModeService {
122122
agentInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] },
123123
handOffs: cachedMode.handOffs,
124124
target: cachedMode.target,
125-
infer: isBoolean(cachedMode.infer) ? (cachedMode.infer ? 'all' : 'user') : cachedMode.infer,
125+
visibility: cachedMode.visibility ?? { userInvokable: true, agentInvokable: cachedMode.infer !== false },
126126
agents: cachedMode.agents,
127127
source: reviveChatModeSource(cachedMode.source) ?? { storage: PromptsStorage.local }
128128
};
@@ -154,8 +154,7 @@ export class ChatModeService extends Disposable implements IChatModeService {
154154
const seenUris = new Set<string>();
155155

156156
for (const customMode of customModes) {
157-
if (customMode.infer === 'agent' || customMode.infer === 'hidden') {
158-
// Skip modes that are only for subagent use or hidden
157+
if (!customMode.visibility.userInvokable) {
159158
continue;
160159
}
161160

@@ -250,8 +249,9 @@ export interface IChatModeData {
250249
readonly uri?: URI;
251250
readonly source?: IChatModeSourceData;
252251
readonly target?: string;
253-
readonly infer?: InferValue | boolean;
252+
readonly visibility?: ICustomAgentVisibility;
254253
readonly agents?: readonly string[];
254+
readonly infer?: boolean; // deprecated, only available in old cached data
255255
}
256256

257257
export interface IChatMode {
@@ -270,7 +270,7 @@ export interface IChatMode {
270270
readonly uri?: IObservable<URI>;
271271
readonly source?: IAgentSource;
272272
readonly target?: IObservable<string | undefined>;
273-
readonly infer?: IObservable<InferValue | undefined>;
273+
readonly visibility?: IObservable<ICustomAgentVisibility | undefined>;
274274
readonly agents?: IObservable<readonly string[] | undefined>;
275275
}
276276

@@ -303,7 +303,7 @@ function isCachedChatModeData(data: unknown): data is IChatModeData {
303303
(mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null)) &&
304304
(mode.source === undefined || isChatModeSourceData(mode.source)) &&
305305
(mode.target === undefined || typeof mode.target === 'string') &&
306-
(mode.infer === undefined || mode.infer === 'all' || mode.infer === 'user' || mode.infer === 'agent' || mode.infer === 'hidden' || typeof mode.infer === 'boolean') &&
306+
(mode.visibility === undefined || isCustomAgentVisibility(mode.visibility)) &&
307307
(mode.agents === undefined || Array.isArray(mode.agents));
308308
}
309309

@@ -317,7 +317,7 @@ export class CustomChatMode implements IChatMode {
317317
private readonly _argumentHintObservable: ISettableObservable<string | undefined>;
318318
private readonly _handoffsObservable: ISettableObservable<readonly IHandOff[] | undefined>;
319319
private readonly _targetObservable: ISettableObservable<string | undefined>;
320-
private readonly _inferObservable: ISettableObservable<InferValue | undefined>;
320+
private readonly _visibilityObservable: ISettableObservable<ICustomAgentVisibility | undefined>;
321321
private readonly _agentsObservable: ISettableObservable<readonly string[] | undefined>;
322322
private _source: IAgentSource;
323323

@@ -375,8 +375,8 @@ export class CustomChatMode implements IChatMode {
375375
return this._targetObservable;
376376
}
377377

378-
get infer(): IObservable<InferValue | undefined> {
379-
return this._inferObservable;
378+
get visibility(): IObservable<ICustomAgentVisibility | undefined> {
379+
return this._visibilityObservable;
380380
}
381381

382382
get agents(): IObservable<readonly string[] | undefined> {
@@ -396,7 +396,7 @@ export class CustomChatMode implements IChatMode {
396396
this._argumentHintObservable = observableValue('argumentHint', customChatMode.argumentHint);
397397
this._handoffsObservable = observableValue('handOffs', customChatMode.handOffs);
398398
this._targetObservable = observableValue('target', customChatMode.target);
399-
this._inferObservable = observableValue('infer', customChatMode.infer);
399+
this._visibilityObservable = observableValue('visibility', customChatMode.visibility);
400400
this._agentsObservable = observableValue('agents', customChatMode.agents);
401401
this._modeInstructions = observableValue('_modeInstructions', customChatMode.agentInstructions);
402402
this._uriObservable = observableValue('uri', customChatMode.uri);
@@ -415,7 +415,7 @@ export class CustomChatMode implements IChatMode {
415415
this._argumentHintObservable.set(newData.argumentHint, tx);
416416
this._handoffsObservable.set(newData.handOffs, tx);
417417
this._targetObservable.set(newData.target, tx);
418-
this._inferObservable.set(newData.infer, tx);
418+
this._visibilityObservable.set(newData.visibility, tx);
419419
this._agentsObservable.set(newData.agents, tx);
420420
this._modeInstructions.set(newData.agentInstructions, tx);
421421
this._uriObservable.set(newData.uri, tx);
@@ -437,7 +437,7 @@ export class CustomChatMode implements IChatMode {
437437
handOffs: this.handOffs.get(),
438438
source: serializeChatModeSource(this._source),
439439
target: this.target.get(),
440-
infer: this.infer.get(),
440+
visibility: this.visibility.get(),
441441
agents: this.agents.get()
442442
};
443443
}

src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ export class ComputeAutomaticInstructions {
350350
if (runSubagentTool && this._configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) {
351351
const canUseAgent = (() => {
352352
if (!this._enabledSubagents || this._enabledSubagents.includes('*')) {
353-
return (agent: ICustomAgent) => (agent.infer === 'all' || agent.infer === 'agent');
353+
return (agent: ICustomAgent) => agent.visibility.agentInvokable;
354354
} else {
355355
const subagents = this._enabledSubagents;
356356
return (agent: ICustomAgent) => subagents.includes(agent.name);

src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,24 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider {
248248
break;
249249
case PromptHeaderAttributes.infer:
250250
if (promptType === PromptsType.agent) {
251-
return ['all', 'user', 'agent', 'hidden', 'true', 'false'];
251+
return ['true', 'false'];
252252
}
253253
break;
254254
case PromptHeaderAttributes.agents:
255255
if (promptType === PromptsType.agent) {
256256
return ['["*"]'];
257257
}
258258
break;
259+
case PromptHeaderAttributes.userInvokable:
260+
if (promptType === PromptsType.agent) {
261+
return ['true', 'false'];
262+
}
263+
break;
264+
case PromptHeaderAttributes.disableModelInvocation:
265+
if (promptType === PromptsType.agent) {
266+
return ['true', 'false'];
267+
}
268+
break;
259269
}
260270
return [];
261271
}

src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class PromptHoverProvider implements HoverProvider {
8383
case PromptHeaderAttributes.handOffs:
8484
return this.getHandsOffHover(attribute, position, promptType === PromptsType.agent && isGithubTarget(promptType, header.target));
8585
case PromptHeaderAttributes.infer:
86-
return this.createHover(description + '\n\n' + localize('promptHeader.attribute.infer.hover', '- `all`, `true`: Available in the agent picker and can be used as a subagent.\n- `user`, `false`: Only available in the agent picker.\n- `agent`: Only available as a subagent (not shown in picker).\n- `hidden`: Not available in the picker nor as a subagent.'), attribute.range);
86+
return this.createHover(description + '\n\n' + localize('promptHeader.attribute.infer.hover', 'Deprecated: Use `user-invokable` and `disable-model-invocation` instead.'), attribute.range);
8787
default:
8888
return this.createHover(description, attribute.range);
8989
}

src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ export class PromptValidator {
183183
case PromptsType.agent: {
184184
this.validateTarget(attributes, report);
185185
this.validateInfer(attributes, report);
186+
this.validateUserInvokable(attributes, report);
187+
this.validateDisableModelInvocation(attributes, report);
186188
this.validateTools(attributes, ChatModeKind.Agent, header.target, report);
187189
if (!isGitHubTarget) {
188190
this.validateModel(attributes, ChatModeKind.Agent, report);
@@ -536,19 +538,7 @@ export class PromptValidator {
536538
if (!attribute) {
537539
return;
538540
}
539-
// Accept boolean for backwards compatibility (true -> 'all', false -> 'user')
540-
if (attribute.value.type === 'boolean') {
541-
return;
542-
}
543-
if (attribute.value.type !== 'string') {
544-
report(toMarker(localize('promptValidator.inferMustBeStringOrBoolean', "The 'infer' attribute must be 'all', 'user', 'agent', 'hidden', or a boolean."), attribute.value.range, MarkerSeverity.Error));
545-
return;
546-
}
547-
const validInferValues = ['all', 'user', 'agent', 'hidden'];
548-
if (!validInferValues.includes(attribute.value.value)) {
549-
report(toMarker(localize('promptValidator.invalidInferValue', "The 'infer' attribute must be one of: {0}.", validInferValues.join(', ')), attribute.value.range, MarkerSeverity.Error));
550-
return;
551-
}
541+
report(toMarker(localize('promptValidator.inferDeprecated', "The 'infer' attribute is deprecated in favour of 'user-invokable' and 'disable-model-invocation'."), attribute.value.range, MarkerSeverity.Error));
552542
}
553543

554544
private validateTarget(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined {
@@ -571,6 +561,28 @@ export class PromptValidator {
571561
}
572562
}
573563

564+
private validateUserInvokable(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined {
565+
const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.userInvokable);
566+
if (!attribute) {
567+
return;
568+
}
569+
if (attribute.value.type !== 'boolean') {
570+
report(toMarker(localize('promptValidator.userInvokableMustBeBoolean', "The 'user-invokable' attribute must be a boolean."), attribute.value.range, MarkerSeverity.Error));
571+
return;
572+
}
573+
}
574+
575+
private validateDisableModelInvocation(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined {
576+
const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.disableModelInvocation);
577+
if (!attribute) {
578+
return;
579+
}
580+
if (attribute.value.type !== 'boolean') {
581+
report(toMarker(localize('promptValidator.disableModelInvocationMustBeBoolean', "The 'disable-model-invocation' attribute must be a boolean."), attribute.value.range, MarkerSeverity.Error));
582+
return;
583+
}
584+
}
585+
574586
private async validateAgentsAttribute(attributes: IHeaderAttribute[], header: PromptHeader, report: (markers: IMarkerData) => void): Promise<undefined> {
575587
const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.agents);
576588
if (!attribute) {
@@ -612,7 +624,7 @@ export class PromptValidator {
612624
const allAttributeNames = {
613625
[PromptsType.prompt]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.mode, PromptHeaderAttributes.agent, PromptHeaderAttributes.argumentHint],
614626
[PromptsType.instructions]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo, PromptHeaderAttributes.excludeAgent],
615-
[PromptsType.agent]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target, PromptHeaderAttributes.infer, PromptHeaderAttributes.agents],
627+
[PromptsType.agent]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target, PromptHeaderAttributes.infer, PromptHeaderAttributes.agents, PromptHeaderAttributes.userInvokable, PromptHeaderAttributes.disableModelInvocation],
616628
[PromptsType.skill]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.license, PromptHeaderAttributes.compatibility, PromptHeaderAttributes.metadata],
617629
};
618630
const githubCopilotAgentAttributeNames = [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.tools, PromptHeaderAttributes.target, GithubPromptHeaderAttributes.mcpServers, PromptHeaderAttributes.infer];
@@ -631,7 +643,7 @@ export function getValidAttributeNames(promptType: PromptsType, includeNonRecomm
631643
}
632644

633645
export function isNonRecommendedAttribute(attributeName: string): boolean {
634-
return attributeName === PromptHeaderAttributes.advancedOptions || attributeName === PromptHeaderAttributes.excludeAgent || attributeName === PromptHeaderAttributes.mode;
646+
return attributeName === PromptHeaderAttributes.advancedOptions || attributeName === PromptHeaderAttributes.excludeAgent || attributeName === PromptHeaderAttributes.mode || attributeName === PromptHeaderAttributes.infer;
635647
}
636648

637649
export function getAttributeDescription(attributeName: string, promptType: PromptsType): string | undefined {
@@ -674,6 +686,10 @@ export function getAttributeDescription(attributeName: string, promptType: Promp
674686
return localize('promptHeader.agent.infer', 'Controls visibility of the agent.');
675687
case PromptHeaderAttributes.agents:
676688
return localize('promptHeader.agent.agents', 'One or more agents that this agent can use as subagents. Use \'*\' to specify all available agents.');
689+
case PromptHeaderAttributes.userInvokable:
690+
return localize('promptHeader.agent.userInvokable', 'Whether the agent can be selected and invoked by users in the UI.');
691+
case PromptHeaderAttributes.disableModelInvocation:
692+
return localize('promptHeader.agent.disableModelInvocation', 'If true, prevents the agent from being invoked as a subagent.');
677693
}
678694
break;
679695
case PromptsType.prompt:

src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { splitLinesIncludeSeparators } from '../../../../../base/common/strings.
99
import { URI } from '../../../../../base/common/uri.js';
1010
import { parse, YamlNode, YamlParseError, Position as YamlPosition } from '../../../../../base/common/yaml.js';
1111
import { Range } from '../../../../../editor/common/core/range.js';
12-
import { InferValue, parseInferValue } from './service/promptsService.js';
1312

1413
export class PromptFileParser {
1514
constructor() {
@@ -80,6 +79,8 @@ export namespace PromptHeaderAttributes {
8079
export const compatibility = 'compatibility';
8180
export const metadata = 'metadata';
8281
export const agents = 'agents';
82+
export const userInvokable = 'user-invokable';
83+
export const disableModelInvocation = 'disable-model-invocation';
8384
}
8485

8586
export namespace GithubPromptHeaderAttributes {
@@ -193,13 +194,10 @@ export class PromptHeader {
193194
return this.getStringAttribute(PromptHeaderAttributes.target);
194195
}
195196

196-
public get infer(): InferValue | undefined {
197+
public get infer(): boolean | undefined {
197198
const attribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.infer);
198199
if (attribute?.value.type === 'boolean') {
199-
return attribute.value.value ? 'all' : 'user';
200-
}
201-
if (attribute?.value.type === 'string' && attribute.value.value) {
202-
return parseInferValue(attribute.value.value);
200+
return attribute.value.value;
203201
}
204202
return undefined;
205203
}
@@ -321,6 +319,22 @@ export class PromptHeader {
321319
public get agents(): string[] | undefined {
322320
return this.getStringArrayAttribute(PromptHeaderAttributes.agents);
323321
}
322+
323+
public get userInvokable(): boolean | undefined {
324+
return this.getBooleanAttribute(PromptHeaderAttributes.userInvokable);
325+
}
326+
327+
public get disableModelInvocation(): boolean | undefined {
328+
return this.getBooleanAttribute(PromptHeaderAttributes.disableModelInvocation);
329+
}
330+
331+
private getBooleanAttribute(key: string): boolean | undefined {
332+
const attribute = this._parsedHeader.attributes.find(attr => attr.key === key);
333+
if (attribute?.value.type === 'boolean') {
334+
return attribute.value.value;
335+
}
336+
return undefined;
337+
}
324338
}
325339

326340
export interface IHandOff {

src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -123,23 +123,17 @@ export type IAgentSource = {
123123
* - 'agent': only usable as subagent by the subagent tool
124124
* - 'hidden': neither in picker nor usable as subagent
125125
*/
126-
export type InferValue = 'all' | 'user' | 'agent' | 'hidden';
126+
export type ICustomAgentVisibility = {
127+
readonly userInvokable: boolean;
128+
readonly agentInvokable: boolean;
129+
};
127130

128-
/**
129-
* Parses an infer value from the raw header value.
130-
* Boolean true maps to 'all', false maps to 'user'.
131-
*/
132-
export function parseInferValue(value: boolean | string | undefined): InferValue | undefined {
133-
if (value === undefined) {
134-
return undefined;
135-
}
136-
if (typeof value === 'boolean') {
137-
return value ? 'all' : 'user';
138-
}
139-
if (value === 'all' || value === 'user' || value === 'agent' || value === 'hidden') {
140-
return value;
131+
export function isCustomAgentVisibility(obj: unknown): obj is ICustomAgentVisibility {
132+
if (typeof obj !== 'object' || obj === null) {
133+
return false;
141134
}
142-
return undefined;
135+
const v = obj as { userInvokable?: unknown; agentInvokable?: unknown };
136+
return typeof v.userInvokable === 'boolean' && typeof v.agentInvokable === 'boolean';
143137
}
144138

145139
export interface ICustomAgent {
@@ -179,13 +173,9 @@ export interface ICustomAgent {
179173
readonly target?: string;
180174

181175
/**
182-
* Infer metadata controlling agent visibility.
183-
* - 'all': available as custom agent in picker AND can be used as subagent
184-
* - 'user': only available in the custom agent picker
185-
* - 'agent': only usable as subagent by the subagent tool
186-
* - 'hidden': neither in picker nor usable as subagent
176+
* What visibility the agent has (user invokable, subagent invokable).
187177
*/
188-
readonly infer?: InferValue;
178+
readonly visibility: ICustomAgentVisibility;
189179

190180
/**
191181
* Contents of the custom agent file body and other agent instructions.

0 commit comments

Comments
 (0)