Repository navigation
API: Advertise EnvironmentManager and PackageManager capabilities #1874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Eduardo Villalpando Mello (edvilme)
merged 47 commits into
main
from
api-2-manager-capabilities
Oct 14, 2026
+1,947
−103
Merged
Changes from all commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
a44fb1b
API: Advertise EnvironmentManager and PackageManager capabilities
edvilme 230433f
Update default values
edvilme 7a1ef69
Consolidate capabilities and default values into single source of truth
edvilme 02b937a
Rename to PackageManagerCapability and EnvironmentManagerCapability
edvilme 6853e32
Defer manager capability runtime preflight validation
edvilme ca37bf0
Simplify tests
edvilme 5704195
Use only registererd managers
edvilme c06cc48
Simplify tests
edvilme acab878
Ground tests
edvilme 0535149
Address manager capability review feedback
edvilme 4102baa
Reduce public exports
edvilme ab06d1a
fix: harden manager capability resolution
edvilme 0fe8c1b
docs: simplify manager capability guidance
edvilme e436017
Remove default values
edvilme 93a2815
Add capability supported helper methods
edvilme a15f7f4
Simplify implementation
edvilme fafcf46
Update test
edvilme 64e88a3
Simplify test
edvilme f485101
Fix tests that did not exercise their intended behavior
edvilme e02e7f3
Remove capability tests that duplicate existing package-manager routi…
edvilme e7b6dc3
Remove registry lookup error test that only verified default throw pr…
edvilme 3bf34fe
Simplify capabilities.ts
edvilme c9ddfdf
Update README
edvilme 957e35b
Include capabilities in public API change checks
Copilot e21580b
Add required capabilities to mock managers after rebase onto main
edvilme 5449b6b
Probe pip/uv version lookup support via getPackageAvailableVersionsCo…
edvilme 9f7601f
Remove environments.create.quick capability key
edvilme 3f1a342
Send telemetry
edvilme 2dfc20d
Redefine capabilities and anmes
edvilme 7e8370b
Don't use string keys
edvilme bf8bfff
Address PR comments
edvilme d0aeaf9
API: Advertise EnvironmentManager and PackageManager capabilities
edvilme 07b1e07
Bump api version
edvilme e4bebfd
Add legacyCapability wrappers to avoid breaking behavior for now
edvilme ed13a15
Align capability tests and docs with legacy fallback
edvilme a8f56a1
Keep capability telemetry version lookup off the query path
edvilme 33b599f
Align pip management capabilities with Python 2 restriction
edvilme 1b84fd5
Simplify captureCapabilityQuery
edvilme dcae6df
Reject unregistered projects in package capability queries
edvilme 7e5b0cc
Updated legacy capability redirection and localization issues
edvilme dd9ed1f
Simplify tests
edvilme 05da1d3
Address PR comments
edvilme 7720a8e
Update comments
edvilme 53b7c52
Resolve equivalent file project paths in capability queries
Copilot 301d4c9
Fix capabilities names
edvilme 9778899
Update docs
edvilme f09a2a9
Update docs
edvilme File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,3 @@ | ||
| # Copied from ../src/api.ts, ../src/types.ts, ../src/publicErrors.ts by the publish pipeline | ||
| # (build/azure-pipeline.npm.yml). These are the published package sources; they are produced | ||
| # at publish time and intentionally NOT committed. src/api.ts, src/types.ts, and | ||
| # src/publicErrors.ts are the single sources of truth. | ||
| src/main.ts | ||
| src/types.ts | ||
| src/publicErrors.ts | ||
| # Generated package sources copied from ../src by scripts/copy-sources.cjs. | ||
| # The original files under ../src are the single sources of truth. | ||
| /src/ |
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
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,59 +1,148 @@ | ||
| import type { | ||
| Capabilities, | ||
| CapabilityContext, | ||
| EnvironmentManager, | ||
| EnvironmentManagerCapability, | ||
| PackageManager, | ||
| PackageManagerCapability, | ||
| Pep440Version, | ||
| PythonEnvironment, | ||
| PythonEnvironmentApi, | ||
| PythonPackageGetterApi, | ||
| Support, | ||
| } from '@vscode/python-environments'; | ||
| import { | ||
| EnvironmentCapability, | ||
| getLegacyEnvironmentManagerCapability, | ||
| getLegacyPackageManagerCapability, | ||
| isPackageVersionLookupNotSupportedError, | ||
| PackageCapability, | ||
| PackageVersionLookupNotSupportedError, | ||
| PythonEnvironments, | ||
| resolveEnvironmentManagerCapability, | ||
| resolvePackageManagerCapability, | ||
| supportedCapability, | ||
| } from '@vscode/python-environments'; | ||
|
|
||
| type Equal<Left, Right> = | ||
| (<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2 ? true : false; | ||
|
|
||
| // Compile-only fixture shared by modern and legacy consumers; not executed. | ||
| declare const api: PythonEnvironmentApi; | ||
| declare const environment: PythonEnvironment; | ||
| PythonEnvironments.api() satisfies Promise<PythonEnvironmentApi>; | ||
|
|
||
| // Package version lookup contracts. | ||
| type AvailableVersionsReturn = ReturnType<PythonPackageGetterApi['getPackageAvailableVersions']>; | ||
| type RefreshReturn = ReturnType<PackageManager['refresh']>; | ||
|
|
||
| const availableVersionsReturnIsExact: Equal<AvailableVersionsReturn, Promise<Pep440Version[] | undefined>> = true; | ||
| const refreshReturnIsExact: Equal<RefreshReturn, Promise<void>> = true; | ||
| true satisfies Equal<AvailableVersionsReturn, Promise<Pep440Version[] | undefined>>; | ||
| true satisfies Equal<RefreshReturn, Promise<void>>; | ||
|
|
||
| declare const api: PythonPackageGetterApi; | ||
| declare const environment: PythonEnvironment; | ||
| const legacyAvailableVersions: Promise<Pep440Version[] | undefined> = api.getPackageAvailableVersions( | ||
| environment, | ||
| 'example', | ||
| ); | ||
| const explicitLegacyAvailableVersions: Promise<Pep440Version[] | undefined> = api.getPackageAvailableVersions( | ||
| environment, | ||
| 'example', | ||
| { errorMode: 'legacy' }, | ||
| ); | ||
| const throwingAvailableVersions: Promise<Pep440Version[]> = api.getPackageAvailableVersions(environment, 'example', { | ||
| errorMode: 'throw', | ||
| }); | ||
| const runtimeApi: Promise<PythonEnvironmentApi> = PythonEnvironments.api(); | ||
|
|
||
| // The unsupported-capability error is part of the public contract: it is constructible, extends | ||
| // Error, and exposes a stable string-literal `code` discriminator. | ||
| api.getPackageAvailableVersions(environment, 'example') satisfies Promise<Pep440Version[] | undefined>; | ||
| api.getPackageAvailableVersions(environment, 'example', { | ||
| errorMode: 'legacy', | ||
| }) satisfies Promise<Pep440Version[] | undefined>; | ||
| api.getPackageAvailableVersions(environment, 'example', { errorMode: 'throw' }) satisfies Promise<Pep440Version[]>; | ||
|
|
||
| // Public error construction, discriminator, and narrowing. | ||
| const lookupError = new PackageVersionLookupNotSupportedError('unsupported'); | ||
| const lookupErrorIsError: Error = lookupError; | ||
| const lookupErrorCodeIsExact: Equal<typeof lookupError.code, 'PackageVersionLookupNotSupported'> = true; | ||
| lookupError satisfies Error; | ||
| true satisfies Equal<typeof lookupError.code, 'PackageVersionLookupNotSupported'>; | ||
|
|
||
| // The type guard narrows unknown values via the stable discriminator (bundle-boundary safe). | ||
| declare const maybeError: unknown; | ||
| const guardNarrows: boolean = isPackageVersionLookupNotSupportedError(maybeError) | ||
| ? maybeError.code === 'PackageVersionLookupNotSupported' | ||
| : false; | ||
|
|
||
| void availableVersionsReturnIsExact; | ||
| void refreshReturnIsExact; | ||
| void legacyAvailableVersions; | ||
| void explicitLegacyAvailableVersions; | ||
| void throwingAvailableVersions; | ||
| void runtimeApi; | ||
| void lookupErrorIsError; | ||
| void lookupErrorCodeIsExact; | ||
| void guardNarrows; | ||
| if (isPackageVersionLookupNotSupportedError(maybeError)) { | ||
| maybeError.code satisfies 'PackageVersionLookupNotSupported'; | ||
| } | ||
|
|
||
| // Legacy providers may omit advertisements; advertised maps must include every key without defaults. | ||
| true satisfies Equal<typeof PackageCapability.ListDirect, 'packages.list.direct'>; | ||
| true satisfies Equal<typeof PackageCapability.FetchAvailableVersions, 'packages.fetchversions'>; | ||
| const fullPackageCapabilities: Capabilities<PackageManagerCapability> = { | ||
| [PackageCapability.List]: supportedCapability, | ||
| [PackageCapability.Install]: supportedCapability, | ||
| [PackageCapability.Uninstall]: supportedCapability, | ||
| [PackageCapability.Upgrade]: supportedCapability, | ||
| [PackageCapability.ListDirect]: supportedCapability, | ||
| [PackageCapability.FetchAvailableVersions]: supportedCapability, | ||
| }; | ||
| const fullEnvironmentCapabilities: Capabilities<EnvironmentManagerCapability> = { | ||
| [EnvironmentCapability.List]: supportedCapability, | ||
| [EnvironmentCapability.Resolve]: supportedCapability, | ||
| [EnvironmentCapability.GetSelected]: supportedCapability, | ||
| [EnvironmentCapability.SetSelected]: supportedCapability, | ||
| [EnvironmentCapability.Create]: supportedCapability, | ||
| [EnvironmentCapability.Remove]: supportedCapability, | ||
| }; | ||
|
|
||
| const legacyPackageManager: PackageManager = { | ||
| name: 'legacy', | ||
| manage: async () => {}, | ||
| refresh: async () => {}, | ||
| getPackages: async () => [], | ||
| }; | ||
| const legacyEnvironmentManager: EnvironmentManager = { | ||
| name: 'legacy', | ||
| preferredPackageManagerId: 'example:legacy', | ||
| refresh: async () => {}, | ||
| getEnvironments: async () => [], | ||
| get: async () => undefined, | ||
| set: async () => {}, | ||
| resolve: async () => undefined, | ||
| }; | ||
|
|
||
| getLegacyPackageManagerCapability(legacyPackageManager, PackageCapability.Install) satisfies Promise<Support>; | ||
| getLegacyEnvironmentManagerCapability(legacyEnvironmentManager, EnvironmentCapability.Create) satisfies Promise<Support>; | ||
|
|
||
| // Capability authoring and raw-provider resolution. | ||
| const advertised: Capabilities<PackageManagerCapability> = { | ||
| ...fullPackageCapabilities, | ||
| [PackageCapability.Upgrade]: (context: CapabilityContext) => | ||
| resolvePackageManagerCapability(legacyPackageManager, PackageCapability.Install, context), | ||
| [PackageCapability.ListDirect]: async (_context) => ({ supported: false, reason: 'Unavailable' }), | ||
| }; | ||
| const managerWithCapabilities: PackageManager = { ...legacyPackageManager, capabilities: advertised }; | ||
| resolvePackageManagerCapability(managerWithCapabilities, PackageCapability.ListDirect, { environment }) satisfies Promise<Support>; | ||
| resolveEnvironmentManagerCapability(legacyEnvironmentManager, EnvironmentCapability.Create, { | ||
| scope: 'global', | ||
| }) satisfies Promise<Support>; | ||
|
|
||
| // Public queries and support-result narrowing. | ||
| api.getPackageManagerCapability(environment, PackageCapability.List) satisfies Promise<Support>; | ||
| api.getEnvironmentManagerCapability('example:legacy', EnvironmentCapability.List, { scope: 'all' }) satisfies Promise<Support>; | ||
| declare const support: Support; | ||
| support.isLegacy satisfies boolean | undefined; | ||
| if (!support.supported) { | ||
| support.reason satisfies string; | ||
| } | ||
|
|
||
| // Invalid capability keys, advertisements, and context must remain compile errors. | ||
| // @ts-expect-error Legacy environment inference requires an environment capability. | ||
| getLegacyEnvironmentManagerCapability(legacyEnvironmentManager, PackageCapability.List); | ||
| // @ts-expect-error Legacy package inference requires a package capability. | ||
| getLegacyPackageManagerCapability(legacyPackageManager, EnvironmentCapability.List); | ||
| // @ts-expect-error Package keys do not belong to the environment capability domain. | ||
| PackageCapability.List satisfies EnvironmentManagerCapability; | ||
| // @ts-expect-error Environment keys do not belong to the package capability domain. | ||
| EnvironmentCapability.List satisfies PackageManagerCapability; | ||
| // @ts-expect-error Advertisements must be asynchronous functions, not static Support values. | ||
| ({ [PackageCapability.List]: { supported: true } } satisfies Capabilities<PackageManagerCapability>); | ||
| // @ts-expect-error Advertisements cannot introduce arbitrary keys. | ||
| ({ 'packages.unknown': async () => ({ supported: true }) } satisfies Capabilities<PackageManagerCapability>); | ||
| // @ts-expect-error Operation options are not capability query context. | ||
| ({ createOptions: { quickCreate: true } } satisfies CapabilityContext); | ||
| declare const arbitraryKey: string; | ||
| // @ts-expect-error A general string is not a declared environment capability. | ||
| resolveEnvironmentManagerCapability(legacyEnvironmentManager, arbitraryKey); | ||
| // @ts-expect-error A general string is not a declared package capability. | ||
| resolvePackageManagerCapability(legacyPackageManager, arbitraryKey); | ||
| // @ts-expect-error Public environment queries require a catalog key. | ||
| api.getEnvironmentManagerCapability('example:legacy', arbitraryKey); | ||
| // @ts-expect-error Public package queries require a catalog key. | ||
| api.getPackageManagerCapability(environment, arbitraryKey); | ||
| // @ts-expect-error Capability dictionaries have no arbitrary string index signature. | ||
| fullPackageCapabilities[arbitraryKey]; | ||
| // @ts-expect-error Capability dictionaries have no arbitrary string index signature. | ||
| fullEnvironmentCapabilities[arbitraryKey]; | ||
| // @ts-expect-error Capability dictionaries must advertise every catalog key; partial maps are rejected. | ||
| ({ [PackageCapability.List]: async () => ({ supported: true }) } satisfies Capabilities<PackageManagerCapability>); |
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.