From 624788c1460173a6330d02e2ca4dc20c11467e10 Mon Sep 17 00:00:00 2001 From: Sam Clark Date: Fri, 10 Jul 2026 14:22:24 -0500 Subject: [PATCH 1/2] Add positron.ai.getAllowedCommands() API for LLM extensions Adds a new method to the positron.ai namespace that returns all registered Positron commands with their IDs, descriptions, parameter metadata, keybindings, and source (builtin vs. extension). Descriptions are resolved from four sources in priority order: CommandsRegistry metadata, MenuRegistry.getCommands() titles, CommandPalette menu item titles (covers undo/redo and similar MultiCommand registrations), and EditorExtensionsRegistry labels. Also adds a "Positron Assistant: Show Allowed Commands" command palette entry in the positron-assistant extension that dumps the result as formatted JSON in a new editor tab, for manual testing. Co-Authored-By: Claude Sonnet 4.6 --- extensions/positron-assistant/package.json | 14 ++- .../positron-assistant/package.nls.json | 3 +- .../positron-assistant/src/extension.ts | 11 +++ src/positron-dts/positron.d.ts | 52 ++++++++++ .../browser/positron/mainThreadAiFeatures.ts | 95 ++++++++++++++++++- .../positron/extHost.positron.api.impl.ts | 3 + .../positron/extHost.positron.protocol.ts | 24 +++++ .../api/common/positron/extHostAiFeatures.ts | 4 + 8 files changed, 202 insertions(+), 4 deletions(-) diff --git a/extensions/positron-assistant/package.json b/extensions/positron-assistant/package.json index f5336d5ff1fb..962d5c7ee964 100644 --- a/extensions/positron-assistant/package.json +++ b/extensions/positron-assistant/package.json @@ -105,9 +105,19 @@ } ], "menus": { - "commandPalette": [] + "commandPalette": [ + { + "command": "positron.assistant.showAllowedCommands" + } + ] }, - "commands": [], + "commands": [ + { + "command": "positron.assistant.showAllowedCommands", + "category": "Positron Assistant", + "title": "%command.showAllowedCommands.title%" + } + ], "configuration": [ { "type": "object", diff --git a/extensions/positron-assistant/package.nls.json b/extensions/positron-assistant/package.nls.json index e9015259de44..9d0809aa41e4 100644 --- a/extensions/positron-assistant/package.nls.json +++ b/extensions/positron-assistant/package.nls.json @@ -9,5 +9,6 @@ "configuration.providerTimeout.description": "Specifies the timeout in seconds when validating a provider connection.", "configuration.providerVariables.bedrock.description": "Variables used to configure advanced settings for Bedrock in Positron Assistant.\n\nRequires a restart to take effect.\n\nExample: to set the AWS region and profile for Amazon Bedrock, add items with keys `AWS_REGION` and `AWS_PROFILE`.", "configuration.bedrock.inferenceProfileRegion.description": "Override the inference profile region for Amazon Bedrock.\n\nBy default, the inference profile region is derived from `AWS_REGION` (e.g., 'us-east-1' → 'us'). Use this setting to explicitly specify a different region.\n\nRequires a restart to take effect.\n\nExamples: 'global', 'us', 'eu', 'apac'.", - "configuration.providerVariables.snowflake.description": "Variables used to configure Snowflake Cortex provider in Positron Assistant.\n\nRequires a restart to take effect.\n\nExample: to set the Snowflake account and connections path, add items with keys `SNOWFLAKE_ACCOUNT` and `SNOWFLAKE_HOME`." + "configuration.providerVariables.snowflake.description": "Variables used to configure Snowflake Cortex provider in Positron Assistant.\n\nRequires a restart to take effect.\n\nExample: to set the Snowflake account and connections path, add items with keys `SNOWFLAKE_ACCOUNT` and `SNOWFLAKE_HOME`.", + "command.showAllowedCommands.title": "Show Allowed AI Commands" } diff --git a/extensions/positron-assistant/src/extension.ts b/extensions/positron-assistant/src/extension.ts index 500033166bc9..2a835a10f6e3 100644 --- a/extensions/positron-assistant/src/extension.ts +++ b/extensions/positron-assistant/src/extension.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import * as positron from 'positron'; import { validateProvidersEnabled } from './providerConfiguration.js'; import { registerParticipants } from './participants'; import { registerAssistantTools } from './tools.js'; @@ -55,6 +56,16 @@ export async function activate(context: vscode.ExtensionContext) { // Create the log output channel. context.subscriptions.push(log); + // Register a command palette entry for inspecting all commands exposed to AI agents. + context.subscriptions.push( + vscode.commands.registerCommand('positron.assistant.showAllowedCommands', async () => { + const commands = await positron.ai.getAllowedCommands(); + const json = JSON.stringify(commands, null, 2); + const doc = await vscode.workspace.openTextDocument({ content: json, language: 'json' }); + await vscode.window.showTextDocument(doc); + }) + ); + if (isActive()) { // Register the assistant. We don't propagate errors here since we want // the extension to stay activated even if the assistant fails to diff --git a/src/positron-dts/positron.d.ts b/src/positron-dts/positron.d.ts index 5a2c16223634..bc74c2e69823 100644 --- a/src/positron-dts/positron.d.ts +++ b/src/positron-dts/positron.d.ts @@ -3652,6 +3652,58 @@ declare module 'positron' { * @returns A Thenable that resolves to true if completions should be enabled for the file, false otherwise. */ export function areCompletionsEnabled(uri: vscode.Uri): Thenable; + + /** + * A parameter accepted by an allowed command. + */ + export interface AllowedCommandArg { + /** Parameter name. */ + name: string; + /** Human-readable description of the parameter. */ + description?: string; + /** Whether the parameter may be omitted. */ + isOptional?: boolean; + } + + /** + * Where a command was registered from. + */ + export interface AllowedCommandSource { + /** `'builtin'` for core Positron/VS Code commands; `'extension'` for extension-contributed commands. */ + type: 'builtin' | 'extension'; + /** Extension identifier (e.g. `ms-python.python`). Only present when `type` is `'extension'`. */ + id?: string; + /** Extension display name. Only present when `type` is `'extension'`. */ + displayName?: string; + } + + /** + * Metadata for a single Positron command available to AI agents. + */ + export interface AllowedCommand { + /** Unique command identifier (e.g. `workbench.action.files.save`). */ + id: string; + /** Human-readable description of what the command does. */ + description?: string; + /** Ordered list of parameters the command accepts. */ + args?: AllowedCommandArg[]; + /** Description of the command's return value, if any. */ + returns?: string; + /** Where the command was registered from. */ + source: AllowedCommandSource; + /** Default keyboard shortcuts bound to this command (e.g. `["Ctrl+Z"]`). */ + keybindings?: string[]; + } + + /** + * Returns all commands available to AI agents, including their IDs, + * descriptions, and parameter/return-value metadata. + * + * In the future this may be filtered to a configured allowlist. + * + * @returns A Thenable that resolves to an array of command descriptors. + */ + export function getAllowedCommands(): Thenable; } /** diff --git a/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts b/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts index bd7bb6196a40..109d06269937 100644 --- a/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts +++ b/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts @@ -6,6 +6,10 @@ import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js'; import { revive } from '../../../../base/common/marshalling.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { isIMenuItem, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ChatViewId } from '../../../contrib/chat/browser/chat.js'; import { ChatViewPane } from '../../../contrib/chat/browser/widgetHosts/viewPane/chatViewPane.js'; import { IChatAgentData, IChatAgentService } from '../../../contrib/chat/common/participants/chatAgents.js'; @@ -16,7 +20,7 @@ import { IChatRequestData, IPositronAssistantConfigurationService, IPositronAssi import { extHostNamedCustomer, IExtHostContext } from '../../../services/extensions/common/extHostCustomers.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatProgressDto } from '../../common/extHost.protocol.js'; -import { ExtHostAiFeaturesShape, ExtHostPositronContext, MainPositronContext, MainThreadAiFeaturesShape } from '../../common/positron/extHost.positron.protocol.js'; +import { ExtHostAiFeaturesShape, ExtHostPositronContext, ISerializedAllowedCommand, MainPositronContext, MainThreadAiFeaturesShape } from '../../common/positron/extHost.positron.protocol.js'; @extHostNamedCustomer(MainPositronContext.MainThreadAiFeatures) export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeaturesShape { @@ -32,6 +36,7 @@ export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeat @IChatAgentService private readonly _chatAgentService: IChatAgentService, @ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService, @IViewsService private readonly _viewsService: IViewsService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); // Create the proxy for the extension host. @@ -186,4 +191,92 @@ export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeat async $getEnabledProviders(): Promise { return this._positronAssistantConfigurationService.getEnabledProviders(); } + + /** + * Return all registered commands with their IDs, descriptions, and parameter metadata. + * Internal commands (IDs starting with '_') are excluded. + */ + async $getAllowedCommands(): Promise { + const allCommands = CommandsRegistry.getCommands(); + const menuCommands = MenuRegistry.getCommands(); + + // Build title map from command palette menu items — catches MultiCommand/EditorCommand + // registrations (e.g. undo, redo) that use appendMenuItem instead of addCommand. + const paletteItemTitles = new Map(); + for (const item of MenuRegistry.getMenuItems(MenuId.CommandPalette)) { + if (isIMenuItem(item)) { + const { id, title } = item.command; + if (title) { + paletteItemTitles.set(id, typeof title === 'string' ? title : title.value); + } + } + } + + // Build label map from editor actions (covers undo, redo, cursor commands, etc.) + const editorActionLabels = new Map(); + for (const action of EditorExtensionsRegistry.getEditorActions()) { + editorActionLabels.set(action.id, action.label); + } + + // Build keybindings map: commandId → human-readable shortcut labels + const keybindingsMap = new Map(); + for (const item of this._keybindingService.getDefaultKeybindings()) { + if (!item.command || !item.resolvedKeybinding) { + continue; + } + const label = item.resolvedKeybinding.getLabel(); + if (!label) { + continue; + } + const existing = keybindingsMap.get(item.command); + if (existing) { + existing.push(label); + } else { + keybindingsMap.set(item.command, [label]); + } + } + + const result: ISerializedAllowedCommand[] = []; + + for (const [id, command] of allCommands) { + if (id.startsWith('_')) { + continue; + } + + const meta = command.metadata; + const menuCmd = menuCommands.get(id); + + let description: string | undefined; + if (meta?.description) { + description = typeof meta.description === 'string' + ? meta.description + : meta.description.value; + } else if (menuCmd) { + const title = menuCmd.title; + description = typeof title === 'string' ? title : title.value; + } else { + description = paletteItemTitles.get(id) ?? editorActionLabels.get(id); + } + + const cmdSource = menuCmd?.source; + const source: ISerializedAllowedCommand['source'] = cmdSource + ? { type: 'extension', id: cmdSource.id, displayName: cmdSource.title } + : { type: 'builtin' }; + + result.push({ + id, + description, + args: meta?.args?.map(a => ({ + name: a.name, + description: a.description, + isOptional: a.isOptional, + })), + returns: meta?.returns, + source, + keybindings: keybindingsMap.get(id), + }); + } + + return result; + } } diff --git a/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts b/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts index 3b095ad60bff..0efd4b23f556 100644 --- a/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts +++ b/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts @@ -522,6 +522,9 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce getEnabledProviders(): Thenable { return extHostAiFeatures.getEnabledProviders(); }, + getAllowedCommands(): Thenable { + return extHostAiFeatures.getAllowedCommands(); + }, LanguageModelAutoconfigureType: extHostTypes.LanguageModelAutoconfigureType }; diff --git a/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts b/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts index f6cc1b8e6c5b..6cc8503656ac 100644 --- a/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts +++ b/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts @@ -302,6 +302,29 @@ export interface MainThreadEnvironmentShape extends IDisposable { export interface ExtHostEnvironmentShape { } +export interface ISerializedAllowedCommandArg { + name: string; + description?: string; + isOptional?: boolean; +} + +export interface ISerializedAllowedCommandSource { + type: 'builtin' | 'extension'; + /** Extension identifier (e.g. `ms-python.python`). Only present when type is 'extension'. */ + id?: string; + /** Extension display name. Only present when type is 'extension'. */ + displayName?: string; +} + +export interface ISerializedAllowedCommand { + id: string; + description?: string; + args?: ISerializedAllowedCommandArg[]; + returns?: string; + source: ISerializedAllowedCommandSource; + keybindings?: string[]; +} + export interface MainThreadAiFeaturesShape { $registerChatAgent(agentData: IChatAgentData): Thenable; $unregisterChatAgent(id: string): void; @@ -320,6 +343,7 @@ export interface MainThreadAiFeaturesShape { $getProviders(): Thenable; $setCurrentProvider(id: string): Thenable; $getEnabledProviders(): Thenable; + $getAllowedCommands(): Promise; } export interface ExtHostAiFeaturesShape { diff --git a/src/vs/workbench/api/common/positron/extHostAiFeatures.ts b/src/vs/workbench/api/common/positron/extHostAiFeatures.ts index 75671e401d74..d270d99acf4c 100644 --- a/src/vs/workbench/api/common/positron/extHostAiFeatures.ts +++ b/src/vs/workbench/api/common/positron/extHostAiFeatures.ts @@ -153,4 +153,8 @@ export class ExtHostAiFeatures implements extHostProtocol.ExtHostAiFeaturesShape return this._proxy.$getEnabledProviders(); } + async getAllowedCommands(): Promise { + return this._proxy.$getAllowedCommands(); + } + } From 25e9f16ae1d877a62e46928a141a9b8fa1a93560 Mon Sep 17 00:00:00 2001 From: Sam Clark Date: Thu, 16 Jul 2026 10:08:22 -0500 Subject: [PATCH 2/2] Move showAllowedCommands to core; drop keybindings from API Moves the "Show Allowed AI Commands" command from the positron-assistant extension into positronAssistant.contribution.ts in core, where it reads the command registries directly without going through the extension host. Also removes keybindings from the positron.ai.getAllowedCommands() return type and implementation, simplifying the IPC payload. Co-Authored-By: Claude Sonnet 4.6 --- extensions/positron-assistant/package.json | 14 +--- .../positron-assistant/package.nls.json | 3 +- .../positron-assistant/src/extension.ts | 11 --- src/positron-dts/positron.d.ts | 2 - .../browser/positron/mainThreadAiFeatures.ts | 21 ----- .../positron/extHost.positron.protocol.ts | 1 - .../browser/positronAssistant.contribution.ts | 76 ++++++++++++++++++- 7 files changed, 76 insertions(+), 52 deletions(-) diff --git a/extensions/positron-assistant/package.json b/extensions/positron-assistant/package.json index 962d5c7ee964..f5336d5ff1fb 100644 --- a/extensions/positron-assistant/package.json +++ b/extensions/positron-assistant/package.json @@ -105,19 +105,9 @@ } ], "menus": { - "commandPalette": [ - { - "command": "positron.assistant.showAllowedCommands" - } - ] + "commandPalette": [] }, - "commands": [ - { - "command": "positron.assistant.showAllowedCommands", - "category": "Positron Assistant", - "title": "%command.showAllowedCommands.title%" - } - ], + "commands": [], "configuration": [ { "type": "object", diff --git a/extensions/positron-assistant/package.nls.json b/extensions/positron-assistant/package.nls.json index 9d0809aa41e4..e9015259de44 100644 --- a/extensions/positron-assistant/package.nls.json +++ b/extensions/positron-assistant/package.nls.json @@ -9,6 +9,5 @@ "configuration.providerTimeout.description": "Specifies the timeout in seconds when validating a provider connection.", "configuration.providerVariables.bedrock.description": "Variables used to configure advanced settings for Bedrock in Positron Assistant.\n\nRequires a restart to take effect.\n\nExample: to set the AWS region and profile for Amazon Bedrock, add items with keys `AWS_REGION` and `AWS_PROFILE`.", "configuration.bedrock.inferenceProfileRegion.description": "Override the inference profile region for Amazon Bedrock.\n\nBy default, the inference profile region is derived from `AWS_REGION` (e.g., 'us-east-1' → 'us'). Use this setting to explicitly specify a different region.\n\nRequires a restart to take effect.\n\nExamples: 'global', 'us', 'eu', 'apac'.", - "configuration.providerVariables.snowflake.description": "Variables used to configure Snowflake Cortex provider in Positron Assistant.\n\nRequires a restart to take effect.\n\nExample: to set the Snowflake account and connections path, add items with keys `SNOWFLAKE_ACCOUNT` and `SNOWFLAKE_HOME`.", - "command.showAllowedCommands.title": "Show Allowed AI Commands" + "configuration.providerVariables.snowflake.description": "Variables used to configure Snowflake Cortex provider in Positron Assistant.\n\nRequires a restart to take effect.\n\nExample: to set the Snowflake account and connections path, add items with keys `SNOWFLAKE_ACCOUNT` and `SNOWFLAKE_HOME`." } diff --git a/extensions/positron-assistant/src/extension.ts b/extensions/positron-assistant/src/extension.ts index 2a835a10f6e3..500033166bc9 100644 --- a/extensions/positron-assistant/src/extension.ts +++ b/extensions/positron-assistant/src/extension.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as positron from 'positron'; import { validateProvidersEnabled } from './providerConfiguration.js'; import { registerParticipants } from './participants'; import { registerAssistantTools } from './tools.js'; @@ -56,16 +55,6 @@ export async function activate(context: vscode.ExtensionContext) { // Create the log output channel. context.subscriptions.push(log); - // Register a command palette entry for inspecting all commands exposed to AI agents. - context.subscriptions.push( - vscode.commands.registerCommand('positron.assistant.showAllowedCommands', async () => { - const commands = await positron.ai.getAllowedCommands(); - const json = JSON.stringify(commands, null, 2); - const doc = await vscode.workspace.openTextDocument({ content: json, language: 'json' }); - await vscode.window.showTextDocument(doc); - }) - ); - if (isActive()) { // Register the assistant. We don't propagate errors here since we want // the extension to stay activated even if the assistant fails to diff --git a/src/positron-dts/positron.d.ts b/src/positron-dts/positron.d.ts index bc74c2e69823..1f529fd330d3 100644 --- a/src/positron-dts/positron.d.ts +++ b/src/positron-dts/positron.d.ts @@ -3691,8 +3691,6 @@ declare module 'positron' { returns?: string; /** Where the command was registered from. */ source: AllowedCommandSource; - /** Default keyboard shortcuts bound to this command (e.g. `["Ctrl+Z"]`). */ - keybindings?: string[]; } /** diff --git a/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts b/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts index 109d06269937..36fe95a11ac0 100644 --- a/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts +++ b/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts @@ -9,7 +9,6 @@ import { URI, UriComponents } from '../../../../base/common/uri.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; import { isIMenuItem, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; -import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ChatViewId } from '../../../contrib/chat/browser/chat.js'; import { ChatViewPane } from '../../../contrib/chat/browser/widgetHosts/viewPane/chatViewPane.js'; import { IChatAgentData, IChatAgentService } from '../../../contrib/chat/common/participants/chatAgents.js'; @@ -36,7 +35,6 @@ export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeat @IChatAgentService private readonly _chatAgentService: IChatAgentService, @ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService, @IViewsService private readonly _viewsService: IViewsService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); // Create the proxy for the extension host. @@ -218,24 +216,6 @@ export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeat editorActionLabels.set(action.id, action.label); } - // Build keybindings map: commandId → human-readable shortcut labels - const keybindingsMap = new Map(); - for (const item of this._keybindingService.getDefaultKeybindings()) { - if (!item.command || !item.resolvedKeybinding) { - continue; - } - const label = item.resolvedKeybinding.getLabel(); - if (!label) { - continue; - } - const existing = keybindingsMap.get(item.command); - if (existing) { - existing.push(label); - } else { - keybindingsMap.set(item.command, [label]); - } - } - const result: ISerializedAllowedCommand[] = []; for (const [id, command] of allCommands) { @@ -273,7 +253,6 @@ export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeat })), returns: meta?.returns, source, - keybindings: keybindingsMap.get(id), }); } diff --git a/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts b/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts index 6cc8503656ac..f6ce2ca80509 100644 --- a/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts +++ b/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts @@ -322,7 +322,6 @@ export interface ISerializedAllowedCommand { args?: ISerializedAllowedCommandArg[]; returns?: string; source: ISerializedAllowedCommandSource; - keybindings?: string[]; } export interface MainThreadAiFeaturesShape { diff --git a/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts b/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts index c535ed290b0a..93af5303f79a 100644 --- a/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts +++ b/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts @@ -7,10 +7,14 @@ import { Registry } from '../../../../platform/registry/common/platform.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js'; import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; -import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, isIMenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { localize2 } from '../../../../nls.js'; +import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { EditorExtensionsRegistry, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IUntitledTextResourceEditorInput } from '../../../common/editor.js'; import { codiconsLibrary } from '../../../../base/common/codiconsLibrary.js'; -import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { ICodeBlockActionContext } from '../../chat/browser/widget/chatContentParts/codeBlockPart.js'; import { IPositronConsoleService } from '../../../services/positronConsole/browser/interfaces/positronConsoleService.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; @@ -18,7 +22,6 @@ import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js'; import { RuntimeCodeExecutionMode } from '../../../services/languageRuntime/common/languageRuntimeService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ChatAgentLocation, ChatConfiguration } from '../../chat/common/constants.js'; import { CodeAttributionSource, IConsoleCodeAttribution } from '../../../services/positronConsole/common/positronConsoleCodeExecution.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; @@ -121,3 +124,70 @@ class PositronAssistantContribution extends Disposable implements IWorkbenchCont Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PositronAssistantContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NextEditSuggestionsStatusBarEntry, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(CommitMessageMenuContribution, LifecyclePhase.Restored); + +registerAction2(class ShowAllowedCommandsAction extends Action2 { + constructor() { + super({ + id: 'positron.assistant.showAllowedCommands', + title: localize2('positron.assistant.showAllowedCommands', 'Show Allowed AI Commands'), + category: Categories.Developer, + f1: true, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const paletteItemTitles = new Map(); + for (const item of MenuRegistry.getMenuItems(MenuId.CommandPalette)) { + if (isIMenuItem(item)) { + const { id, title } = item.command; + if (title) { + paletteItemTitles.set(id, typeof title === 'string' ? title : title.value); + } + } + } + + const editorActionLabels = new Map(); + for (const action of EditorExtensionsRegistry.getEditorActions()) { + editorActionLabels.set(action.id, action.label); + } + + const menuCommands = MenuRegistry.getCommands(); + const result = []; + + for (const [id, command] of CommandsRegistry.getCommands()) { + if (id.startsWith('_')) { + continue; + } + + const meta = command.metadata; + const menuCmd = menuCommands.get(id); + + let description: string | undefined; + if (meta?.description) { + description = typeof meta.description === 'string' ? meta.description : meta.description.value; + } else if (menuCmd) { + const t = menuCmd.title; + description = typeof t === 'string' ? t : t.value; + } else { + description = paletteItemTitles.get(id) ?? editorActionLabels.get(id); + } + + const cmdSource = menuCmd?.source; + result.push({ + id, + description, + args: meta?.args?.map(a => ({ name: a.name, description: a.description, isOptional: a.isOptional })), + returns: meta?.returns, + source: cmdSource + ? { type: 'extension', id: cmdSource.id, displayName: cmdSource.title } + : { type: 'builtin' }, + }); + } + + await accessor.get(IEditorService).openEditor({ + resource: undefined, + contents: JSON.stringify(result, null, 2), + languageId: 'json', + } satisfies IUntitledTextResourceEditorInput); + } +});