Previously, registerProvider() overwrote the stored config for a provider,
so a models registration followed by a baseUrl/headers-only override lost
the models. After refresh(), only the override-only config was replayed,
causing extension-provided models to disappear.
Added upsertRegisteredProvider() that merges defined fields into the
existing stored config instead of replacing it. Fields absent from the
incoming config are preserved from the stored config.
Also adds regression tests for dynamic provider override persistence
across refresh.
* fix(coding-agent): surface models.json load errors on stderr in --list-models
When models.json has validation errors, --list-models silently
discarded custom models and overrides without any user-visible
feedback. Now prints the error to stderr via chalk.yellow warning
before listing available models.
* fix(coding-agent): allow custom models for built-in providers in models.json
Built-in providers (openrouter, anthropic, etc.) already have api and
baseUrl on every model, and auth comes from env vars / auth storage.
Relax validation so custom models under built-in providers don't need
redundant baseUrl, apiKey, or api fields. Inherit them from the first
built-in model for that provider.
fixes#2921
---------
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
* Support shell command execution for API key resolution in models.json
Add ! prefix support to apiKey field in models.json to execute shell commands
and use stdout as the API key. This allows users to store API keys in secure
credential managers like macOS Keychain, 1Password, Bitwarden, or HashiCorp Vault.
Example: "apiKey": "!security find-generic-password -ws 'anthropic'"
The apiKey field now supports three formats:
- !command - executes shell command, uses trimmed stdout
- ENV_VAR_NAME - uses environment variable value
- literal - uses value directly
fixes#697
* feat(coding-agent): cache API key command results for process lifetime
Shell commands (! prefix) are now executed once and cached. Environment
variables and literal values are not cached, so changes are picked up.
Addresses review feedback on #762.
---------
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
Builds on #406 to support simpler proxy use case:
- Override just baseUrl to route built-in provider through proxy
- All built-in models preserved, no need to redefine them
- Full replacement still works when models array is provided
* Allow models.json to override built-in providers
When a provider is defined in models.json with the same name as a
built-in provider (e.g., 'anthropic', 'google'), the built-in models
for that provider are completely replaced by the custom definition.
This enables users to:
- Use custom base URLs (proxies, self-hosted endpoints)
- Define a subset of models they want available
- Customize model configurations for built-in providers
Example usage in ~/.pi/agent/models.json:
{
"providers": {
"anthropic": {
"baseUrl": "https://my-proxy.example.com/v1",
"apiKey": "ANTHROPIC_API_KEY",
"api": "anthropic-messages",
"models": [...]
}
}
}
* Refactor model-registry for readability
- Extract CustomModelsResult type and emptyCustomModelsResult helper
- Extract loadBuiltInModels method with clear skip logic
- Simplify loadModels with destructuring and ternary
- Reduce repetition in error handling paths
* Refactor model-registry tests for readability
- Extract providerConfig() helper to hide irrelevant model fields
- Extract writeModelsJson() helper for file writing
- Extract getModelsForProvider() helper for filtering
- Move modelsJsonPath to beforeEach
Reduces test file from 262 to 130 lines while maintaining same coverage.