-
Notifications
You must be signed in to change notification settings - Fork 165
Migrate provider settings to providers.json #14928
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
Open
melissa-barca
wants to merge
26
commits into
main
Choose a base branch
from
14708-migrate-settings
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
1fe52b1
Add ai-lib submodule and ai-config dependency to authentication
melissa-barca 42f9977
Map provider settings to a providers.json config
melissa-barca 6605727
Add one-shot providers.json migration with overwrite mode
melissa-barca 757ee2c
Bump ai-lib for nodenext-compatible ai-config dist
melissa-barca 584a2a1
Build ai-config with tsc directly to avoid its tsx prebuild
melissa-barca 06ee8bc
Add providers.json migration command and first-launch prompt
melissa-barca 65a8cb1
Deprecate provider settings migrated to providers.json
melissa-barca b5c2377
Remove positron.assistant.models.include setting
melissa-barca 7983e1a
Fix migration guards for invalid files and empty settings
melissa-barca 2d38a7b
Validate the full settings mapping against the ai-config schema
melissa-barca a7031e5
Run ai-config's own build from the extension postinstall
melissa-barca 5b162c5
Migrate model overrides to providers.json custom models
melissa-barca b25bff1
Migrate SNOWFLAKE_HOME to providers.json and deprecate the setting
melissa-barca 7f9243b
Migrate providers.json settings automatically instead of prompting
melissa-barca 17eabbe
Shorten providers.json deprecation messages
melissa-barca 63725f6
Log migrated provider IDs and link to the log from the toast
melissa-barca a1079d2
Log each migrated setting as source -> providers.json path
melissa-barca 5649cdf
Log the migrated value alongside each setting mapping
melissa-barca 863984a
Clarify the migration toast wording
melissa-barca d40d35f
Bump ai-lib to the merged model-capabilities release
melissa-barca 26d1b0b
Add activation event for the migrate settings command
melissa-barca e708f4d
Bump ai-lib to the merged husky prepare guard
melissa-barca 0e7fb29
Remove the dead migrateApiKey command
melissa-barca 62b4f1e
Migrate the Foundry base URL verbatim
melissa-barca 1656bef
Validate the migrated config and report schema failures
melissa-barca 258ad69
save automatic migration and setting deprecation for the future
melissa-barca 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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| [submodule "extensions/positron-r/ark"] | ||
| path = extensions/positron-r/ark | ||
| url = https://github.com/posit-dev/ark.git | ||
| [submodule "ai-lib"] | ||
| path = ai-lib | ||
| url = https://github.com/posit-dev/ai-lib.git |
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
This file was deleted.
Oops, something went wrong.
146 changes: 146 additions & 0 deletions
146
extensions/authentication/src/migration/migrateToProvidersJson.ts
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 |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| /*--------------------------------------------------------------------------------------------- | ||
| * Copyright (C) 2026 Posit Software, PBC. All rights reserved. | ||
| * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. | ||
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| import * as fs from 'fs/promises'; | ||
| import * as vscode from 'vscode'; | ||
| import { log } from '../log'; | ||
| import { | ||
| buildProvidersConfigFromSettings, | ||
| InferCapabilitiesFn, | ||
| MigrationSettingsReader, | ||
| } from './providersJson'; | ||
|
|
||
| export type MigrationResult = | ||
| | { outcome: 'migrated'; settingCount: number } | ||
| | { outcome: 'skipped-populated' } | ||
| | { outcome: 'nothing-to-migrate' }; | ||
|
|
||
| export interface RunMigrationOptions { | ||
| /** Replace a populated providers block instead of skipping (manual command only). */ | ||
| overwrite: boolean; | ||
| /** Override providers.json path (tests). */ | ||
| configPath?: string; | ||
| /** Override the settings source (tests). */ | ||
| reader?: MigrationSettingsReader; | ||
| /** Override capability inference (tests). */ | ||
| inferCapabilities?: InferCapabilitiesFn; | ||
| } | ||
|
|
||
| /** Reads explicitly-set GLOBAL values only; defaults and workspace scopes are ignored. */ | ||
| export function createGlobalSettingsReader(): MigrationSettingsReader { | ||
| return { | ||
| globalValue: <T,>(key: string) => | ||
| vscode.workspace.getConfiguration().inspect<T>(key)?.globalValue, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Zero-value capability synthesizer for presence checks. Capabilities only | ||
| * shape the values written into custom models, never whether a setting | ||
| * migrates, so hasMigratableSettings can stay synchronous instead of | ||
| * dynamically importing ai-config's real inferModelCapabilities. | ||
| */ | ||
| const PRESENCE_CHECK_CAPABILITIES: InferCapabilitiesFn = () => ({ | ||
| maxContextLength: 0, | ||
| supportsTools: false, | ||
| supportsImages: false, | ||
| supportsToolResultImages: false, | ||
| supportsWebSearch: false, | ||
| }); | ||
|
|
||
| /** True when the settings hold values the migration would actually write (empty values are filtered). */ | ||
| export function hasMigratableSettings( | ||
| reader: MigrationSettingsReader = createGlobalSettingsReader() | ||
| ): boolean { | ||
| return buildProvidersConfigFromSettings(reader, PRESENCE_CHECK_CAPABILITIES) !== undefined; | ||
| } | ||
|
|
||
| /** | ||
| * True when the user's providers.json file already carries provider config, | ||
| * or holds content the migration must not silently replace. ai-config's read | ||
| * path coerces unparseable or schema-invalid files to an empty config, which | ||
| * would make a hand-edited file with one typo look unpopulated; this check | ||
| * deliberately reads the raw file and validates it with ai-config's schema | ||
| * so such files count as populated. | ||
| */ | ||
| export async function userProvidersFileIsPopulated(configPath?: string): Promise<boolean> { | ||
| const { PROVIDERS_CONFIG_PATH, providersConfigSchema } = await import('ai-config/node'); | ||
| const filePath = configPath ?? PROVIDERS_CONFIG_PATH; | ||
| let raw: string; | ||
| try { | ||
| raw = await fs.readFile(filePath, 'utf-8'); | ||
| } catch { | ||
| return false; | ||
| } | ||
| if (raw.trim() === '') { | ||
| return false; | ||
| } | ||
| let parsed: unknown; | ||
| try { | ||
| parsed = JSON.parse(raw); | ||
| } catch { | ||
| log.warn(`[migration] ${filePath} is not valid JSON; treating it as populated`); | ||
| return true; | ||
| } | ||
| const result = providersConfigSchema.safeParse(parsed); | ||
| if (!result.success) { | ||
| log.warn(`[migration] ${filePath} does not match the providers schema; treating it as populated`); | ||
| return true; | ||
| } | ||
| const providers = result.data.providers; | ||
| return !!providers && Object.keys(providers).length > 0; | ||
| } | ||
|
|
||
| /** | ||
| * One-shot migration: writes the mapped config through mutateProvidersConfig. | ||
| * The populated-file check runs BEFORE the mutator so unparseable files (which | ||
| * the mutator's read coerces to an empty config) and no-op skips never touch | ||
| * the file, and again INSIDE the mutator so the parseable case stays guarded | ||
| * under ai-config's cross-process lock. | ||
| */ | ||
| export async function runMigration(opts: RunMigrationOptions): Promise<MigrationResult> { | ||
| const reader = opts.reader ?? createGlobalSettingsReader(); | ||
| const { mutateProvidersConfig, inferModelCapabilities, providersConfigSchema } = await import('ai-config/node'); | ||
| const mapped = buildProvidersConfigFromSettings(reader, opts.inferCapabilities ?? inferModelCapabilities); | ||
| if (!mapped) { | ||
| log.info('[migration] No provider settings to migrate'); | ||
| return { outcome: 'nothing-to-migrate' }; | ||
| } | ||
|
|
||
| // The builder assembles loosely-typed blocks; validate the assembled config | ||
| // against ai-config's schema before writing so a bad mapping fails loudly | ||
| // here instead of writing malformed providers.json. | ||
| const config = providersConfigSchema.parse(mapped.config); | ||
|
|
||
| if (!opts.overwrite && await userProvidersFileIsPopulated(opts.configPath)) { | ||
| log.info('[migration] providers.json already has provider config; skipped'); | ||
| return { outcome: 'skipped-populated' }; | ||
| } | ||
|
|
||
| let skippedPopulated = false; | ||
| await mutateProvidersConfig( | ||
| current => { | ||
| if (!opts.overwrite && current.providers && Object.keys(current.providers).length > 0) { | ||
| skippedPopulated = true; | ||
| return current; | ||
| } | ||
| return { ...current, providers: config.providers }; | ||
| }, | ||
| { | ||
| configPath: opts.configPath, | ||
| logger: { debug: (m: string) => log.debug(m), warn: (m: string) => log.warn(m) }, | ||
| } | ||
| ); | ||
|
|
||
| if (skippedPopulated) { | ||
| log.info('[migration] providers.json already has provider config; skipped'); | ||
| return { outcome: 'skipped-populated' }; | ||
| } | ||
| log.info(`[migration] Migrated ${mapped.settingCount} setting(s) to providers.json:`); | ||
| for (const { source, destination, value } of mapped.migrations) { | ||
| log.info(`[migration] ${source} -> ${destination} = ${value}`); | ||
| } | ||
| return { outcome: 'migrated', settingCount: mapped.settingCount }; | ||
| } | ||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make a version of this accessible from the CLI by adding a subcommand to the Positron binary (like @sharon-wang) was talking about?
Then in the Workbench migration script I could hand Positron an enforced-settings.json and it could give me the migrated providers.json (so we don't need to duplicate all this translation logic inside Workbench).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think you could file a separate issue for this? I'm not sure if we'll be able to get it in 2026.08 (I'm out next week) but we might be able to get it in a patch for Workbench 2026.08
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue here: #15023