Skip to content

Back language model tools and the Command Explorer with new LSP requests - #2298

Merged
Andy Jordan (andyleejordan) merged 5 commits into
mainfrom
andyleejordan/lm-tools-command-explorer
Jun 25, 2026
Merged

Back language model tools and the Command Explorer with new LSP requests#2298
Andy Jordan (andyleejordan) merged 5 commits into
mainfrom
andyleejordan/lm-tools-command-explorer

Conversation

@andyleejordan

@andyleejordanAndy Jordan (andyleejordan) commented Jun 9, 2026

Copy link
Copy Markdown
Member

Server-side support for new VS Code extension features (language model tools, a redesigned Command Explorer, and a read-only Show Help pane). The client work lives in PowerShell/vscode-powershell#5508 and depends on this PR; the two should be reviewed and merged together.

  1. Return help text from the powerShell/showHelp request — convert showHelp from a notification to a request returning ShowHelpResult { HelpText }, capturing Get-Help -Full | Out-String and trimming both ends (it pads a leading and trailing blank line).
  2. Enhance get_command for tools and the Command Explorer — optional Name/Module filters (wildcard, -ErrorAction Ignore), an ExcludeParameters fast path plus ModuleVersion, unconditional exclusion of editor-injected commands (the fake PSConsoleHostReadLine 0.0.0 and VS Code shell-integration helpers), and opt-in ExcludeDefaultFunctions enumerated from InitialSessionState.CreateDefault2().
  3. Add a powerShell/getModule handler — returns a single module's metadata for the Command Explorer's hover tooltips. Covered by E2E tests verifying metadata is returned for an existing module and a null result for a missing module.
  4. Add Copilot instructions documenting build and test.

Drafted by Copilot (Claude Opus 4.8) — please review/edit before marking ready.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds server-side LSP request support needed by upcoming VS Code extension features (language model tools, redesigned Command Explorer, and a read-only Show Help pane), aligning PowerShellEditorServices with new client capabilities in the companion VS Code PR.

Changes:

  • Converts powerShell/showHelp from a notification into a request that returns full help text as a string.
  • Enhances powerShell/getCommand with optional name/module filtering, a fast path to exclude parameter metadata, and filtering for editor-injected/default-session commands.
  • Adds a new powerShell/getModule request handler plus Copilot build/test instructions documentation.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.csSwitches showHelp to a request and returns trimmed help text from Get-Help -Full | Out-String.
src/PowerShellEditorServices/Services/PowerShell/Handlers/GetModuleHandler.csAdds a new LSP request to retrieve module metadata for Command Explorer tooltips.
src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.csAdds filtering/fast-path options and excludes editor-injected/default-session commands.
src/PowerShellEditorServices/Server/PsesLanguageServer.csRegisters the new GetModuleHandler with the language server.
.github/copilot-instructions.mdDocuments local build/test workflows and repo architecture/conventions for Copilot.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread.github/copilot-instructions.md
@andyleejordan
Andy Jordan (andyleejordan)force-pushed the andyleejordan/lm-tools-command-explorer branch 3 times, most recently from 7d3efc5 to 25305c7CompareJune 10, 2026 18:19
@andyleejordan
Andy Jordan (andyleejordan)force-pushed the andyleejordan/lm-tools-command-explorer branch from 25305c7 to ac66fc4CompareJune 16, 2026 15:56
Andy Jordan (andyleejordan) added a commit that referenced this pull request Jun 16, 2026
We had no `.github/copilot-instructions.md`, so agents working in this repo had
to rediscover how to build and test, how the projects fit together, and how we
label pull requests. This adds a single instructions file covering all of it:
- **Build & Test** — `dotnet` directly for the fast inner loop versus
`Invoke-Build` (which needs `InvokeBuild`/`platyPS`) for assembling the full
module and running the complete CI suite.
- **Architecture** — the project layout, key services, the LSP/DAP handler
pattern, and how the server is wired up.
- **Conventions** — C# style enforced by `.editorconfig`, the xUnit testing
setup, and the multi-targeting story.
- **Pull Request Labels** — every PR needs at least one `Area-*` label and
exactly one `Issue-*` label (plus `OS-*` and `Ignore` when relevant). The
`Issue-*` label is what GitHub's auto-generated release notes key off of (see
`.github/release.yml`); a PR without one silently falls through to "Other
Changes 🙏".
The build, architecture, and conventions content previously rode along in #2298;
consolidating it here keeps that PR focused on its LSP changes and gives us a
single source of truth for the instructions.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Justin Grote (JustinGrote) pushed a commit that referenced this pull request Jun 18, 2026
* Make `OnIdle` tests deterministic by polling instead of sleeping
`CanRunOnIdleTask` (and its twin `CanRunOnIdleInProfileTask`) were flaky on
the net462 (Windows PowerShell 5.1) CI leg — the former was just caught
failing on PR #2298's Windows job.
The root cause is that `PsesInternalHost.OnPowerShellIdle` calls
`Events.GenerateEvent(PSEngineEvent.OnIdle, ...)`, which only *enqueues* the
event. For a subscriber registered with `-Action {...}`, PowerShell doesn't
run the action scriptblock inline; it becomes a pending action that the
engine dispatches asynchronously on the pipeline thread, around subsequent
pipeline invocations. So the action's execution was never synchronized with
the test's `$handled` read, and the fixed `Thread.Sleep(2000)` was just a
timing guess — sometimes too short on the slower WinPS leg, leaving
`$global:handled` still `$false` at the assertion.
The key realization is that each *additional* pipeline execution gives the
engine another chance to drain the pending action, so re-reading the handler
variable in a loop both waits for *and* drives completion. I replaced the
sleep with a shared `WaitForHandledAsync` helper that polls the variable
(~200ms apart, ~15s ceiling) until it reports `$true`, returning the last
observed value on timeout so the assertion still fails loudly. This keeps the
tests' intent intact and isn't merely a longer sleep.
I validated both tests on net8.0 (green across repeated runs, ~0.4s each vs.
the old fixed 2s); net462 can't run on macOS, but the mechanism is identical
across targets and the 15s ceiling self-terminates on success, so it's
strictly safer on the slow leg without slowing the fast one.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Move `OnIdle` assertion into helper and drop list return
Small follow-up to review feedback: both call sites only ever asserted the
handler variable became `$true`, so the `IReadOnlyList<bool>` return was
needless ceremony. `AssertHandledAsync` now owns the assertion — it returns
once the variable reports `$true` and otherwise fails via `Assert.Fail` when
the ~15s poll window elapses, which reads as "the OnIdle handler never ran."
`Assert.Fail` is fine here — we're on xUnit 2.9.3 and already use it in the
E2E tests. No behavior change to what's being verified; the call sites just
shrink to a single `await OnIdleTestHelpers.AssertHandledAsync(...)`. Still
green on net8.0.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Assert `OnIdle` poll result with `Assert.True`
Follow-up to review feedback: the helper short-circuited with a bare `return`
on success and only asserted (`Assert.Fail`) on timeout, so the happy path had
no explicit assertion. Restructure the loop to poll until the handler variable
is `$true` or the ~15s window elapses, then assert the outcome once with
`Assert.True(handled, ...)`. Same behavior, but the success and timeout paths
now share a single, self-describing assertion.
Still green on net8.0.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Andy Jordan (andyleejordan) added a commit that referenced this pull request Jun 18, 2026
We had no `.github/copilot-instructions.md`, so agents working in this repo had
to rediscover how to build and test, how the projects fit together, and how we
label pull requests. This adds a single instructions file covering all of it:
- **Build & Test** — `dotnet` directly for the fast inner loop versus
`Invoke-Build` (which needs `InvokeBuild`/`platyPS`) for assembling the full
module and running the complete CI suite.
- **Architecture** — the project layout, key services, the LSP/DAP handler
pattern, and how the server is wired up.
- **Conventions** — C# style enforced by `.editorconfig`, the xUnit testing
setup, and the multi-targeting story.
- **Pull Request Labels** — every PR needs at least one `Area-*` label and
exactly one `Issue-*` label (plus `OS-*` and `Ignore` when relevant). The
`Issue-*` label is what GitHub's auto-generated release notes key off of (see
`.github/release.yml`); a PR without one silently falls through to "Other
Changes 🙏".
The build, architecture, and conventions content previously rode along in #2298;
consolidating it here keeps that PR focused on its LSP changes and gives us a
single source of truth for the instructions.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@andyleejordan
Andy Jordan (andyleejordan)force-pushed the andyleejordan/lm-tools-command-explorer branch from ac66fc4 to 3ab4c88CompareJune 23, 2026 21:20
Convert `powerShell/showHelp` from a fire-and-forget notification into a
request that returns `ShowHelpResult { HelpText }`, so the client can render
help in a read-only editor pane (and the language model `get_help` tool can
reuse the same path) instead of printing into the integrated console.
The handler captures `Get-Help -Full | Out-String` and `.Trim()`s both ends —
`Out-String` pads the output with a leading and trailing blank line, which
looked wrong in the help pane and in tool output.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The handler previously serialized the entire command table (names, modules,
and full parameter metadata) on every request, which is slow enough to hang
the `get_command` language model tool and made the Command Explorer take
minutes to populate. Extend `GetCommandParams` so callers can ask for only
what they need:
- `Name`/`Module` (both wildcard-capable) scope the `Get-Command` call so we
don't materialize everything; an unmatched filter writes a non-terminating
error, so we pass `-ErrorAction Ignore` and return an empty list instead.
- `ExcludeParameters` takes a fast path that returns just name, module, and
the new `ModuleVersion` without touching `Parameters`/`ParameterSets`, whose
resolution and serialization dominate the cost.
- Editor-injected commands are always skipped: the PSES host's fake
`PSConsoleHostReadLine` (version 0.0.0) and VS Code's shell-integration
helpers (`__VSCode-Escape-Value`, `Set-MappedKeyHandler[s]`) are plumbing,
not commands a user authored or imported.
- `ExcludeDefaultFunctions` (opt-in) drops PowerShell's module-less
default-session functions (`cd..`, `prompt`, `TabExpansion2`, ...) and the
install's `pwsh.profile.resource` script. The names come from
`InitialSessionState.CreateDefault2()` so the list stays correct across
PowerShell versions; module-provided commands are never affected.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Command Explorer groups commands under versioned module nodes and shows a
tooltip on hover. Add a `getModule` request that returns a single module's
metadata (version, description, path, author, company, project URI, required
PowerShell version) so the client can populate those tooltips lazily, and
register the handler in `PsesLanguageServer`.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merging #2328 un-skipped the Windows PowerShell E2E suite, which surfaced
a pre-existing host gap in `CanSendGetModuleRequestAsync`: it probed
`Microsoft.PowerShell.Management`, but Windows PowerShell's in-box core
modules are snap-in based and are not returned by
`Get-Module -ListAvailable`. The handler correctly resolved nothing, so
`Assert.NotNull` failed on the Windows PowerShell leg while passing on
PowerShell 7 (where the core modules ship as files).
Probe `Microsoft.PowerShell.Archive` instead: it is a file-based module
present in the module path on both Windows PowerShell and PowerShell 7+,
so the test exercises the handler on every host without skipping or
loosening any assertion.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@andyleejordan
Andy Jordan (andyleejordan)force-pushed the andyleejordan/lm-tools-command-explorer branch from 452909f to 44cfcc0CompareJune 24, 2026 22:16
The `TestE2EPowerShell` build task launches the server with
`$env:PSModulePath = ''` (the vscode-powershell#3886 workaround for
inheriting `powershell.exe`'s module path). PSES's `UpdatePSModulePath()`
then appends the bundled module directory, so the runspace ends up with
`;<bundled>` — a non-empty path.
That distinction matters: Windows PowerShell only auto-injects its default
module locations (`$PSHOME\Modules`, etc.) when `PSModulePath` is empty. The
moment it's non-empty, no defaults are added, so `Get-Module -ListAvailable`
finds nothing and `powerShell/getModule` returns null for every module — not
just `Microsoft.PowerShell.Archive`. I confirmed this by probing
`powershell.exe` directly: an empty path resolves the module, while any
non-empty path (including the leading-`;` shape PSES produces) does not.
pwsh 7 isn't affected because `TestE2EPwsh` never clears the path, and real
VS Code sessions always have a populated `PSModulePath`, so the feature works
in practice.
This is a test-host artifact, not a product bug, so skip both getModule tests
on the in-box Windows PowerShell leg:
- `CanSendGetModuleRequestAsync` was failing outright (null module).
- `CanSendGetModuleRequestForMissingModuleAsync` was passing vacuously — it
asserts null, which every module now returns — so it validated nothing on
this leg. Promote it from `[Fact]` to `[SkippableFact]` and skip it too.
Both still run on Linux, macOS, and the PowerShell 7 Windows legs. This
mirrors the earlier `73293931e` skip that was reverted when we tried the
"probe Archive without skipping" approach, which the harness's emptied
`PSModulePath` makes unworkable.
Drafted by Copilot (Claude Opus 4.8).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@andyleejordan
Andy Jordan (andyleejordan) merged commit 0190500 into mainJun 25, 2026
10 checks passed
@andyleejordan
Andy Jordan (andyleejordan) deleted the andyleejordan/lm-tools-command-explorer branch June 25, 2026 17:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-APIArea-Language ServerIssue-EnhancementA feature request (enhancement).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@andyleejordan@jshigetomi