Mark update/install packages commands as agent-invocable - #15137
Open
dhruvisompura wants to merge 15 commits into
Open
Mark update/install packages commands as agent-invocable#15137dhruvisompura wants to merge 15 commits into
dhruvisompura wants to merge 15 commits into
Conversation
|
E2E Tests 🚀 Why these tags?
More on automatic tags from changed files. |
sortVersionsDescending moves verbatim out of positronPackagesQuickPick into a new packageVersions module, so a headless caller can reuse it without depending on the quick-pick's IQuickInputService and Delayer. Adds newestAvailableVersion, which picks the version to use when a caller asks for the newest one. It prefers the newest stable release over a newer prerelease, matching what the package managers install by default: with 1.8, 1.9 and 2.0.0rc1 available, pip install <name> installs 1.9, not the release candidate. Backends disagree on ordering (R returns one version, PyPI ascending, conda newest-first), so the list is sorted here rather than trusted. Neither function was tested before; both are now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The agent contract these commands advertised did not hold. 'latest' was mapped to an undefined version, but undefined is not universally "newest": pip and uv-outside-a-project reject a missing version on update outright, and on install they write a bare package name into a requirements file with no --upgrade, so an already-installed package resolves as satisfied and never moves. pip is the default manager for Pyenv, Global, System and VirtualEnv environments, so an agent following the documented contract failed on the common case. 'latest' is now resolved to a concrete version against the session's repositories before the service is called, so every backend receives an explicit target. This also fixes install's already-satisfied no-op. Both commands now return a structured result instead of void. Every failure path notified the user and returned void, which validateAndExecute reports as success, so the assistant was told a failed install succeeded. The two bugs compounded: an agent updating a package to latest in a pip session failed inside the service, the error went to a toast it could not see, and it reported success. The returns metadata documents the shape. The agent path never throws; the interactive path keeps its existing rethrow, which only it can now reach. That distinction matters because the Command Palette turns a thrown command error into a modal dialog. Neither dispatch guard changes, so no existing caller is affected. Install's guard is reverted to requiring both a name and a version, restoring the version-picker fallback from #14608. 'latest' is the agent's signal for "newest", and no human caller passes it, so argument shape alone distinguishes the two callers. Correspondingly, version is no longer marked optional in the agent metadata: omitting it installs nothing and opens a picker that waits for a human. isOptional is descriptive only, so this does not affect menu or UI callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers what had no coverage: the dispatch decision and how an outcome becomes a result. The two action classes are exported for this, matching LookupHelpTopic; the quick-pick module is mocked because its real MultiStepInput needs a live QuickInputService and a 300ms debounce, and this change does not touch it. The regression tests that matter are a failed install reporting installed: false rather than success, 'latest' always reaching the service as a concrete version, and a name-only invocation still opening the picker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
newestAvailableVersion skipped three-component prereleases like 2.0.0rc1 but not two-component ones like 1.0a2 and 2.0b1, or dot-separated dev builds like 1.0.0.dev1. semver.valid rejects those spellings and semver.coerce then drops the suffix, so they read as the stable 1.0.0 and were installed in preference to an older stable release. With 0.9 and 1.0a2 available it picked 1.0a2, where pip install would give 0.9. The version string is now checked directly as well as through semver. A trailing digit group is required, which keeps out three things that only look similar: conda's letter-suffixed builds, R's patch levels, and post-releases, which are newer than the release they follow rather than prereleases of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
newestAvailableVersion reused the version picker's comparison, which runs versions through semver.coerce and so truncates anything past three segments. Four-segment versions tied and the tie fell back to PyPI's ascending order, putting the older one first: with 1.2.3.4 and 1.2.3.5 available it picked 1.2.3.4. PEP 440 epochs were unparseable for the same reason, so 3.0 beat 1!2.0. Resolving a version automatically now uses its own comparison, which keeps every numeric group and reads the epoch. R patch levels sort correctly as a side effect (1.0-10 over 1.0-3), though R reports a single version so nothing depended on it. The picker keeps its existing ordering. A truncated sort is a cosmetic problem when a human is choosing from the whole list, but silently installs the wrong version when nothing is choosing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An agent can send the wrong shape: a missing version, a missing name, a number where a string belongs, an empty string, or nothing at all. None of those install or update anything unintended -- every one falls through to the quick-pick and reports a false result. Pinned so that stays true, along with extra trailing arguments being ignored. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment said an agent-compatible command must also be palette-exposed so a user could run it themselves. Nothing enforces that: agentCompatible alone decides what getAgentAllowedCommands returns, and a test covers a command that is advertised without being in the palette. Keeps the intent, which is still worth stating as a preference: putting an agent-compatible command in the palette means anything an agent can do, a user can do for themselves. Also stops naming f1 as the only route in -- the view focus commands reach the palette through a MenuId.CommandPalette entry instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
searchPyPIVersions called response.json() without checking the response. PyPI answers 404 for a project that does not exist with the plain-text body '404 Not Found', which parses as the number 404 followed by unexpected text, so the caller got 'Unexpected non-whitespace character after JSON at position 4' instead of a usable answer. A name that does not exist is routine, not exceptional -- any caller can pass one that was typed or guessed -- so a 404 now returns no versions. The packages commands already turn that into "No available versions of '<name>' were found. Check the package name and the repositories configured for this session." Other non-OK responses throw naming the status, so a PyPI outage does not masquerade as a missing package. The bug was unreachable until now: searchPackageVersions was only called from the version quick-pick, which runs after the user has picked a real package out of search results. Resolving 'latest' calls it with a name supplied by an agent, which may not exist at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dhruvisompura
force-pushed
the
dhruvi/agent-compatible-packages
branch
from
July 28, 2026 23:45
1854345 to
0c0b7c2
Compare
The positron-python extension runs prettier --check in CI, and it wants the throw on one line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dhruvisompura
marked this pull request as ready for review
July 29, 2026 00:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses part of #15063.
Summary
Makes the packages pane install and update commands invocable by Posit Assistant:
positronPackages.installPackagepositronPackages.updatePackageThe commands now take optional arguments for the package name and version. The packages pane UI doesn't provide either argument and shows the user a quick pick so they can provide both arguments.
For the agentic flow, we can now provide all arguments at once so the command can run without the interactive quickpicks showing up. We mark these arguments as "required" in the metadata we send to Assistant.
One difference between the user vs agentic flow is how we handle a scenario where a user says "install X" without specifying a version. The agent can now pass
'latest'in for the version for that case and on the command side we look up a real version number (should be the newest stable release but we should stress test this a bit).I also fixed a bug where an error message notification was being mangled in the python extension: PyPI replies 404 with the plain text
404 Not Foundfor a package that does not exist, and we passed that straight to a JSON parser which failed to parse it.Screenshots
Installing the newest version
packages-install-latest.mp4
A package that does not exist
packages-install-unknown.mp4
Release Notes
New Features
Bug Fixes
Validation Steps
@:packages-pane
nameandversionboth required and areturnsstring on each.