Add PowerShell language model tools · PowerShell/vscode-powershell@30d16a8 · GitHub
Skip to content

Commit 30d16a8

Browse files
Add PowerShell language model tools
Register four read-only language model tools that Copilot can call when working with PowerShell, each backed by an existing PSES request so they report exactly what the user's session would: - `powershell_get_command` lists commands, optionally scoped by name and/or module, using the now-parameterized `powerShell/getCommand` request with `excludeParameters` so large listings stay cheap. - `powershell_get_help` returns `Get-Help` output via `powerShell/showHelp`. - `powershell_get_environment` reports the PowerShell version table. - `powershell_expand_alias` resolves aliases to their underlying commands. All four are read-only and declare no confirmation requirement. Contribute them in `package.json` and wire up `LanguageModelToolsFeature` in `extension.ts`. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4ee9925 commit 30d16a8

4 files changed

Lines changed: 364 additions & 0 deletions

File tree

‎package.json‎

Lines changed: 91 additions & 0 deletions

‎src/extension.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { GetCommandsFeature } from "./features/GetCommands";
2121
import { HelpCompletionFeature } from "./features/HelpCompletion";
2222
import { ISECompatibilityFeature } from "./features/ISECompatibility";
23+
import { LanguageModelToolsFeature } from "./features/LanguageModelTools";
2324
import { OpenInISEFeature } from "./features/OpenInISE";
2425
import { PesterTestsFeature } from "./features/PesterTests";
2526
import { RemoteFilesFeature } from "./features/RemoteFiles";
@@ -193,6 +194,7 @@ export async function activate(
193194
new RemoteFilesFeature(),
194195
new DebugSessionFeature(context, sessionManager, logger),
195196
new HelpCompletionFeature(),
197+
new LanguageModelToolsFeature(),
196198
];
197199

198200
sessionManager.setLanguageClientConsumers(languageClientConsumers);

‎src/features/LanguageModelTools.ts‎

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import * as vscode from "vscode";
5+
import type { LanguageClient } from "vscode-languageclient/node";
6+
import { LanguageClientConsumer } from "../languageClientConsumer";
7+
import { PowerShellVersionRequestType } from "../session";
8+
import { ExpandAliasRequestType } from "./ExpandAlias";
9+
import { GetCommandRequestType } from "./GetCommands";
10+
import { ShowHelpRequestType } from "./ShowHelp";
11+
12+
function toToolResult(text: string): vscode.LanguageModelToolResult {
13+
return new vscode.LanguageModelToolResult([
14+
new vscode.LanguageModelTextPart(text),
15+
]);
16+
}
17+
18+
interface IGetCommandInput {
19+
name?: string;
20+
module?: string;
21+
}
22+
23+
// Lists commands available in the active PowerShell session (backed by the
24+
// existing powerShell/getCommand request), scoped by name and/or module. At
25+
// least one filter is required so we never serialize the entire command table,
26+
// which is prohibitively expensive.
27+
class GetCommandTool implements vscode.LanguageModelTool<IGetCommandInput> {
28+
public async invoke(
29+
options: vscode.LanguageModelToolInvocationOptions<IGetCommandInput>,
30+
token: vscode.CancellationToken,
31+
): Promise<vscode.LanguageModelToolResult> {
32+
const name = options.input.name?.trim();
33+
const module = options.input.module?.trim();
34+
35+
if (!name && !module) {
36+
return toToolResult(
37+
"Provide a 'name' and/or 'module' filter to look up PowerShell commands.",
38+
);
39+
}
40+
41+
// Get-Command -Name matches literally, so wrap bare text in wildcards to
42+
// get intuitive "contains" matching while leaving explicit wildcards
43+
// (and module names) untouched.
44+
const namePattern = name && !/[*?[\]]/.test(name) ? `*${name}*` : name;
45+
46+
const client = await LanguageClientConsumer.getLanguageClient();
47+
const matches = await client.sendRequest(
48+
GetCommandRequestType,
49+
{
50+
name: namePattern,
51+
module,
52+
},
53+
token,
54+
);
55+
56+
if (matches.length === 0) {
57+
return toToolResult(
58+
"No matching PowerShell commands were found in the current session.",
59+
);
60+
}
61+
62+
const limit = 50;
63+
const limited = matches.slice(0, limit);
64+
const blocks = limited.map((command) => {
65+
const parameters = Object.keys(command.parameters ?? {});
66+
return [
67+
`Name: ${command.name}`,
68+
`Module: ${command.moduleName || "(none)"}`,
69+
`DefaultParameterSet: ${command.defaultParameterSet ?? "(none)"}`,
70+
`Parameters: ${parameters.length > 0 ? parameters.join(", ") : "(none)"}`,
71+
].join("\n");
72+
});
73+
74+
let output = blocks.join("\n\n");
75+
if (matches.length > limit) {
76+
output += `\n\n(Showing ${limit} of ${matches.length} matching commands. Provide a more specific name or module filter to narrow the results.)`;
77+
}
78+
79+
return toToolResult(output);
80+
}
81+
}
82+
83+
interface IGetHelpInput {
84+
command: string;
85+
}
86+
87+
// A tool that takes no input.
88+
type EmptyInput = Record<string, never>;
89+
90+
// Returns the full Get-Help text for a command (backed by the powerShell/showHelp request).
91+
class GetHelpTool implements vscode.LanguageModelTool<IGetHelpInput> {
92+
public async invoke(
93+
options: vscode.LanguageModelToolInvocationOptions<IGetHelpInput>,
94+
token: vscode.CancellationToken,
95+
): Promise<vscode.LanguageModelToolResult> {
96+
const client = await LanguageClientConsumer.getLanguageClient();
97+
const result = await client.sendRequest(
98+
ShowHelpRequestType,
99+
{
100+
text: options.input.command,
101+
},
102+
token,
103+
);
104+
return toToolResult(
105+
result.helpText || `No help found for '${options.input.command}'.`,
106+
);
107+
}
108+
}
109+
110+
// Reports the active PowerShell version/edition/architecture (backed by powerShell/getVersion).
111+
class GetEnvironmentTool implements vscode.LanguageModelTool<EmptyInput> {
112+
public async invoke(
113+
_options: vscode.LanguageModelToolInvocationOptions<EmptyInput>,
114+
token: vscode.CancellationToken,
115+
): Promise<vscode.LanguageModelToolResult> {
116+
const client = await LanguageClientConsumer.getLanguageClient();
117+
const version = await client.sendRequest(
118+
PowerShellVersionRequestType,
119+
token,
120+
);
121+
const output = [
122+
`PowerShell version: ${version.version}`,
123+
`Edition: ${version.edition}`,
124+
`Architecture: ${version.architecture}`,
125+
`Commit: ${version.commit}`,
126+
].join("\n");
127+
return toToolResult(output);
128+
}
129+
}
130+
131+
interface IExpandAliasInput {
132+
text: string;
133+
}
134+
135+
// Expands aliases in a script to full command names (backed by powerShell/expandAlias).
136+
class ExpandAliasTool implements vscode.LanguageModelTool<IExpandAliasInput> {
137+
public async invoke(
138+
options: vscode.LanguageModelToolInvocationOptions<IExpandAliasInput>,
139+
token: vscode.CancellationToken,
140+
): Promise<vscode.LanguageModelToolResult> {
141+
const client = await LanguageClientConsumer.getLanguageClient();
142+
const result = await client.sendRequest(
143+
ExpandAliasRequestType,
144+
{
145+
text: options.input.text,
146+
},
147+
token,
148+
);
149+
return toToolResult(result.text);
150+
}
151+
}
152+
153+
export class LanguageModelToolsFeature extends LanguageClientConsumer {
154+
private tools: vscode.Disposable[];
155+
156+
constructor() {
157+
super();
158+
this.tools = [
159+
vscode.lm.registerTool(
160+
"powershell_get_command",
161+
new GetCommandTool(),
162+
),
163+
vscode.lm.registerTool("powershell_get_help", new GetHelpTool()),
164+
vscode.lm.registerTool(
165+
"powershell_get_environment",
166+
new GetEnvironmentTool(),
167+
),
168+
vscode.lm.registerTool(
169+
"powershell_expand_alias",
170+
new ExpandAliasTool(),
171+
),
172+
];
173+
}
174+
175+
public override onLanguageClientSet(
176+
_languageClient: LanguageClient,
177+
// eslint-disable-next-line @typescript-eslint/no-empty-function
178+
): void {}
179+
180+
public dispose(): void {
181+
for (const tool of this.tools) {
182+
tool.dispose();
183+
}
184+
}
185+
}
Lines changed: 86 additions & 0 deletions

0 commit comments

Comments
 (0)