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
3 changes: 2 additions & 1 deletion extensions/prompt-basics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
".chatmode.md"
],
"filenamePatterns": [
"**/.github/agents/*.md"
"**/.github/agents/*.md",
"**/.claude/agents/*.md"
],
"configuration": "./language-configuration.json"
},
Expand Down
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 @@ -55,7 +55,7 @@ import { ILanguageModelToolsService } from '../common/tools/languageModelToolsSe
import { HooksExecutionService, IHooksExecutionService } from '../common/hooks/hooksExecutionService.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, DEFAULT_HOOK_FILE_PATHS } 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 } 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 } from '../common/promptSyntax/promptTypes.js';
import { hookFileSchema, HOOK_SCHEMA_URI, HOOK_FILE_GLOB } from '../common/promptSyntax/hookSchema.js';
Expand Down Expand Up @@ -816,6 +816,7 @@ configurationRegistry.registerConfiguration({
),
default: {
[AGENTS_SOURCE_FOLDER]: true,
[CLAUDE_AGENTS_SOURCE_FOLDER]: true,
},
additionalProperties: { type: 'boolean' },
propertyNames: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { IEditorGroupsService } from '../../../../services/editor/common/editorG
import { LocalChatSessionUri } from '../../common/model/chatUri.js';
import { assertNever } from '../../../../../base/common/assert.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
import { Target } from '../../common/promptSyntax/service/promptsService.js';

const extensionPoint = ExtensionsRegistry.registerExtensionPoint<IChatSessionsExtensionPoint[]>({
extensionPoint: 'chatSessions',
Expand Down Expand Up @@ -1101,9 +1102,9 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
* Get the customAgentTarget for a specific session type.
* When set, the mode picker should show filtered custom agents matching this target.
*/
public getCustomAgentTargetForSessionType(chatSessionType: string): string | undefined {
public getCustomAgentTargetForSessionType(chatSessionType: string): Target {
const contribution = this._contributions.get(chatSessionType)?.contribution;
return contribution?.customAgentTarget;
return contribution?.customAgentTarget ?? Target.Undefined;
}

public getContentProviderSchemes(): string[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ITextModel } from '../../../../../editor/common/model.js';
import { ILanguageModelToolsService, IToolAndToolSetEnablementMap } from '../../common/tools/languageModelToolsService.js';
import { PromptHeaderAttributes } from '../../common/promptSyntax/promptFileParser.js';
import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';
import { formatArrayValue } from '../../common/promptSyntax/utils/promptEditHelper.js';

export class PromptFileRewriter {
constructor(
Expand Down Expand Up @@ -43,13 +44,14 @@ export class PromptFileRewriter {
this.rewriteAttribute(model, '', toolsAttr.range);
return;
} else {
this.rewriteTools(model, newTools, toolsAttr.value.range);
this.rewriteTools(model, newTools, toolsAttr.value.range, toolsAttr.value.type === 'string');
}
}

public rewriteTools(model: ITextModel, newTools: IToolAndToolSetEnablementMap, range: Range): void {
public rewriteTools(model: ITextModel, newTools: IToolAndToolSetEnablementMap, range: Range, isString: boolean): void {
const newToolNames = this._languageModelToolsService.toFullReferenceNames(newTools);
const newValue = `[${newToolNames.map(s => `'${s}'`).join(', ')}]`;
const newEntries = newToolNames.map(toolName => formatArrayValue(toolName)).join(', ');
const newValue = isString ? newEntries : `[${newEntries}]`;
this.rewriteAttribute(model, newValue, range);
}

Expand Down Expand Up @@ -83,3 +85,4 @@ export class PromptFileRewriter {
this.rewriteAttribute(model, newName, nameAttr.value.range);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com
import { showToolsPicker } from '../actions/chatToolPicker.js';
import { ILanguageModelToolsService } from '../../common/tools/languageModelToolsService.js';
import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId, PromptsType } from '../../common/promptSyntax/promptTypes.js';
import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';
import { IPromptsService, Target } from '../../common/promptSyntax/service/promptsService.js';
import { registerEditorFeature } from '../../../../../editor/common/editorFeatures.js';
import { PromptFileRewriter } from './promptFileRewriter.js';
import { Range } from '../../../../../editor/common/core/range.js';
import { IEditorModel } from '../../../../../editor/common/editorCommon.js';
import { PromptHeaderAttributes } from '../../common/promptSyntax/promptFileParser.js';
import { isGithubTarget } from '../../common/promptSyntax/languageProviders/promptValidator.js';
import { isTarget, parseCommaSeparatedList, PromptHeaderAttributes } from '../../common/promptSyntax/promptFileParser.js';
import { getTarget, isVSCodeOrDefaultTarget } from '../../common/promptSyntax/languageProviders/promptValidator.js';
import { isBoolean } from '../../../../../base/common/types.js';

class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider {

Expand All @@ -40,10 +41,10 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider
this._register(this.languageService.codeLensProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, this));

this._register(CommandsRegistry.registerCommand(this.cmdId, (_accessor, ...args) => {
const [first, second, third, forth] = args;
const model = first as IEditorModel;
if (isITextModel(model) && Range.isIRange(second) && Array.isArray(third) && (typeof forth === 'string' || forth === undefined)) {
this.updateTools(model as ITextModel, Range.lift(second), third, forth);
const [modelArg, rangeArg, isStringArg, toolsArg, targetArg] = args;
const model = modelArg as IEditorModel;
if (isITextModel(model) && Range.isIRange(rangeArg) && isBoolean(isStringArg) && Array.isArray(toolsArg) && isTarget(targetArg)) {
this.updateTools(model as ITextModel, Range.lift(rangeArg), isStringArg, toolsArg, targetArg);
}
}));
}
Expand All @@ -61,35 +62,43 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider
return undefined;
}

if (isGithubTarget(promptType, header.target)) {
const target = getTarget(promptType, header);
if (!isVSCodeOrDefaultTarget(target)) {
return undefined;
}

const toolsAttr = header.getAttribute(PromptHeaderAttributes.tools);
if (!toolsAttr || toolsAttr.value.type !== 'array') {
if (!toolsAttr) {
return undefined;
}
const items = toolsAttr.value.items;
let value = toolsAttr.value;
if (value.type === 'string') {
value = parseCommaSeparatedList(value);
}
if (value.type !== 'array') {
return undefined;
}
const items = value.items;
const selectedTools = items.filter(item => item.type === 'string').map(item => item.value);

const codeLens: CodeLens = {
range: toolsAttr.range.collapseToStart(),
command: {
title: localize('configure-tools.capitalized.ellipsis', "Configure Tools..."),
id: this.cmdId,
arguments: [model, toolsAttr.value.range, selectedTools, header.target]
arguments: [model, toolsAttr.range, toolsAttr.value.type === 'string', selectedTools, target]
}
};
return { lenses: [codeLens] };
}

private async updateTools(model: ITextModel, range: Range, selectedTools: readonly string[], target: string | undefined): Promise<void> {
const selectedToolsNow = () => this.languageModelToolsService.toToolAndToolSetEnablementMap(selectedTools, target, undefined);
private async updateTools(model: ITextModel, range: Range, isString: boolean, selectedTools: readonly string[], target: Target): Promise<void> {
const selectedToolsNow = () => this.languageModelToolsService.toToolAndToolSetEnablementMap(selectedTools, undefined);
const newSelectedAfter = await this.instantiationService.invokeFunction(showToolsPicker, localize('placeholder', "Select tools"), 'codeLens', undefined, selectedToolsNow);
if (!newSelectedAfter) {
return;
}
this.instantiationService.createInstance(PromptFileRewriter).rewriteTools(model, newSelectedAfter, range);
this.instantiationService.createInstance(PromptFileRewriter).rewriteTools(model, newSelectedAfter, range, isString);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
* @param fullReferenceNames A list of tool or toolset by their full reference names that are enabled.
* @returns A map of tool or toolset instances to their enablement state.
*/
toToolAndToolSetEnablementMap(fullReferenceNames: readonly string[], _target: string | undefined, model: ILanguageModelChatMetadata | undefined): IToolAndToolSetEnablementMap {
toToolAndToolSetEnablementMap(fullReferenceNames: readonly string[], model: ILanguageModelChatMetadata | undefined): IToolAndToolSetEnablementMap {
const toolOrToolSetNames = new Set(fullReferenceNames);
const result = new Map<IToolSet | IToolData, boolean>();
for (const [tool, fullReferenceName] of this.toolsWithFullReferenceName.get()) {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common
import { ILanguageModelToolsService, isToolSet } from '../../common/tools/languageModelToolsService.js';
import { ComputeAutomaticInstructions } from '../../common/promptSyntax/computeAutomaticInstructions.js';
import { PromptsConfig } from '../../common/promptSyntax/config/config.js';
import { IHandOff, PromptHeader, Target } from '../../common/promptSyntax/promptFileParser.js';
import { IHandOff, PromptHeader } from '../../common/promptSyntax/promptFileParser.js';
import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';
import { handleModeSwitch } from '../actions/chatActions.js';
import { ChatTreeItem, IChatAcceptInputOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewModelChangeEvent, IChatWidgetViewOptions, isIChatResourceViewContext, isIChatViewViewContext } from '../chat.js';
Expand Down Expand Up @@ -2509,7 +2509,7 @@ export class ChatWidget extends Disposable implements IChatWidget {

// if not tools to enable are present, we are done
if (tools !== undefined && this.input.currentModeKind === ChatModeKind.Agent) {
const enablementMap = this.toolsService.toToolAndToolSetEnablementMap(tools, Target.VSCode, this.input.selectedLanguageModel.get()?.metadata);
const enablementMap = this.toolsService.toToolAndToolSetEnablementMap(tools, this.input.selectedLanguageModel.get()?.metadata);
this.input.selectedToolsModel.set(enablementMap, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ import { IModePickerDelegate, ModePickerActionItem } from './modePickerActionIte
import { SessionTypePickerActionItem } from './sessionTargetPickerActionItem.js';
import { WorkspacePickerActionItem } from './workspacePickerActionItem.js';
import { ChatContextUsageWidget } from '../../widgetHosts/viewPane/chatContextUsageWidget.js';
import { Target } from '../../../common/promptSyntax/service/promptsService.js';

const $ = dom.$;

Expand Down Expand Up @@ -1426,7 +1427,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge

// Check if this session type has a customAgentTarget
const customAgentTarget = ctx && this.chatSessionsService.getCustomAgentTargetForSessionType(ctx.chatSessionType);
this.chatSessionHasCustomAgentTarget.set(!!customAgentTarget);
this.chatSessionHasCustomAgentTarget.set(customAgentTarget !== Target.Undefined);

// Handle agent option from session - set initial mode
if (customAgentTarget) {
Expand Down Expand Up @@ -2000,7 +2001,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
customAgentTarget: () => {
const sessionResource = this._widget?.viewModel?.model.sessionResource;
const ctx = sessionResource && this.chatService.getChatSessionFromInternalUri(sessionResource);
return ctx && this.chatSessionsService.getCustomAgentTargetForSessionType(ctx.chatSessionType);
return (ctx && this.chatSessionsService.getCustomAgentTargetForSessionType(ctx.chatSessionType)) ?? Target.Undefined;
},
};
return this.modeWidget = this.instantiationService.createInstance(ModePickerActionItem, action, delegate, pickerOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ export class ChatSelectedTools extends Disposable {
if (!currentMap && currentMode.kind === ChatModeKind.Agent) {
const modeTools = currentMode.customTools?.read(r);
if (modeTools) {
const target = currentMode.target?.read(r);
currentMap = ToolEnablementStates.fromMap(this._toolsService.toToolAndToolSetEnablementMap(modeTools, target, lm));
currentMap = ToolEnablementStates.fromMap(this._toolsService.toToolAndToolSetEnablementMap(modeTools, lm));
}
}
if (!currentMap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { IChatAgentService } from '../../../common/participants/chatAgents.js';
import { ChatMode, IChatMode, IChatModeService } from '../../../common/chatModes.js';
import { isOrganizationPromptFile } from '../../../common/promptSyntax/utils/promptsServiceUtils.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../../common/constants.js';
import { PromptsStorage } from '../../../common/promptSyntax/service/promptsService.js';
import { PromptsStorage, Target } from '../../../common/promptSyntax/service/promptsService.js';
import { getOpenChatActionIdForMode } from '../../actions/chatActions.js';
import { IToggleChatModeArgs, ToggleAgentModeActionId } from '../../actions/chatExecuteActions.js';
import { ChatInputPickerActionViewItem, IChatInputPickerOptions } from './chatInputPickerActionItem.js';
Expand All @@ -41,7 +41,7 @@ export interface IModePickerDelegate {
* When set, the mode picker will show custom agents whose target matches this value.
* Custom agents without a target are always shown in all session types. If no agents match the target, shows a default "Agent" option.
*/
readonly customAgentTarget?: () => string | undefined;
readonly customAgentTarget?: () => Target;
}

// TODO: there should be an icon contributed for built-in modes
Expand All @@ -65,7 +65,7 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem {
@IOpenerService openerService: IOpenerService
) {
// Get custom agent target (if filtering is enabled)
const customAgentTarget = delegate.customAgentTarget?.();
const customAgentTarget = delegate.customAgentTarget?.() ?? Target.Undefined;

// Category definitions
const builtInCategory = { label: localize('built-in', "Built-In"), order: 0 };
Expand Down Expand Up @@ -107,7 +107,7 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem {
openerService.open(modeResource.get());
}
});
} else if (!customAgentTarget) {
} else if (customAgentTarget === Target.Undefined) {
const label = localize('configureToolsFor', "Configure tools for {0} agent", mode.label.get());
toolbarActions.push({
id: `configureTools:${mode.id}`,
Expand Down Expand Up @@ -182,8 +182,8 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem {
const modes = chatModeService.getModes();
const currentMode = delegate.currentMode.get();
const filteredCustomModes = modes.custom.filter(mode => {
const target = mode.target?.get();
return isUserDefinedCustomAgent(mode) && (!target || target === customAgentTarget);
const target = mode.target.get();
return isUserDefinedCustomAgent(mode) && (target === customAgentTarget);
});
// Always include the default "Agent" option first
const checked = currentMode.id === ChatMode.Agent.id;
Expand Down Expand Up @@ -230,7 +230,7 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem {
};

const modePickerActionWidgetOptions: Omit<IActionWidgetDropdownOptions, 'label' | 'labelRenderer'> = {
actionProvider: customAgentTarget ? actionProviderWithCustomAgentTarget : actionProvider,
actionProvider: customAgentTarget !== Target.Undefined ? actionProviderWithCustomAgentTarget : actionProvider,
actionBarActionProvider: {
getActions: () => this.getModePickerActionBarActions()
},
Expand Down
Loading
Loading