Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ amplifier notify reset --all [--scope] # Clear all notification se

```

**Note on `extra_request_params`:** some provider modules support an
`extra_request_params` key inside a provider's `config` block -- a raw,
user-owned dict of request parameters merged verbatim into every API call
for options the module doesn't wrap itself. It is entirely your
responsibility to maintain by hand in `settings.yaml`; config tooling
(`provider add`/`provider edit`/`provider manage`) round-trips it untouched
across reconfigures but never prompts for it, displays it in the wizard, or
validates its contents.

### Session Commands

```bash
Expand Down
37 changes: 37 additions & 0 deletions amplifier_app_cli/commands/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..provider_config_utils import (
_config_claimed_env_vars,
_normalize_id,
_preserve_reserved_keys,
_secret_env_var_for,
_secret_field_id_for,
_suggest_instance_env_var,
Expand Down Expand Up @@ -650,6 +651,18 @@ def provider_add(ctx: click.Context, provider_type: str | None, scope: str) -> N
if instance_id:
scope_providers.append(provider_entry)
else:
# An entry with this module may already be present here (e.g. a
# concurrent write landed between our pre-lock read and this
# one). Round-trip its reserved, user-owned keys (e.g.
# extra_request_params) before it's dropped -- see
# _preserve_reserved_keys()'s docstring.
old_entry = next(
(p for p in scope_providers if p.get("module") == module_id), None
)
if old_entry is not None:
provider_entry["config"] = _preserve_reserved_keys(
old_entry.get("config"), provider_entry["config"]
)
# Replace any existing entry with same module
scope_providers = [
p for p in scope_providers if p.get("module") != module_id
Expand Down Expand Up @@ -939,6 +952,12 @@ def provider_edit(name: str, scope: str) -> None:
console.print("[red]Configuration cancelled.[/red]")
return

# Round-trip reserved, user-owned keys (e.g. extra_request_params) that
# the wizard never collects -- see _preserve_reserved_keys()'s docstring.
new_config = _preserve_reserved_keys(
existing_config if isinstance(existing_config, dict) else None, new_config
)

# Preserve priority from existing config
priority = (
existing_config.get("priority", 1) if isinstance(existing_config, dict) else 1
Expand Down Expand Up @@ -1427,6 +1446,18 @@ def _manage_add_provider(settings: AppSettings, scope: Scope = "global") -> None
if instance_id:
scope_providers.append(provider_entry)
else:
# An entry with this module may already be present here (e.g. a
# concurrent write landed between our pre-lock read and this
# one). Round-trip its reserved, user-owned keys (e.g.
# extra_request_params) before it's dropped -- see
# _preserve_reserved_keys()'s docstring.
old_entry = next(
(p for p in scope_providers if p.get("module") == module_id), None
)
if old_entry is not None:
provider_entry["config"] = _preserve_reserved_keys(
old_entry.get("config"), provider_entry["config"]
)
scope_providers = [
p for p in scope_providers if p.get("module") != module_id
]
Expand Down Expand Up @@ -1496,6 +1527,12 @@ def _manage_edit_provider(
console.print(" [red]Configuration cancelled.[/red]")
return

# Round-trip reserved, user-owned keys (e.g. extra_request_params) that
# the wizard never collects -- see _preserve_reserved_keys()'s docstring.
new_config = _preserve_reserved_keys(
existing_config if isinstance(existing_config, dict) else None, new_config
)

priority = (
existing_config.get("priority", 1) if isinstance(existing_config, dict) else 1
)
Expand Down
44 changes: 44 additions & 0 deletions amplifier_app_cli/provider_config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,50 @@
console = Console()
logger = logging.getLogger(__name__)

# Reserved, user-owned provider-config keys. Provider modules are adopting
# ``extra_request_params`` as an owner-beware dict merged verbatim into every
# API request, for parameters the module itself doesn't wrap -- users
# maintain it by hand in settings.yaml. It is deliberately never declared as
# a ConfigField (never a wizard prompt, never displayed, never validated),
# so every config-rewrite path that rebuilds a provider's config purely from
# wizard-collected fields must round-trip it verbatim via
# ``_preserve_reserved_keys()`` below rather than silently dropping it. Keep
# this tuple as the single, deliberately narrow allow-list for that
# passthrough convention -- do not widen it to cover arbitrary unknown keys.
RESERVED_PROVIDER_CONFIG_KEYS: tuple[str, ...] = ("extra_request_params",)


def _preserve_reserved_keys(
old_config: dict[str, Any] | None, new_config: dict[str, Any]
) -> dict[str, Any]:
"""Carry forward reserved, user-owned config keys from ``old_config``
into ``new_config`` verbatim, if present.

Called wherever a config-rewrite path replaces an EXISTING provider
instance's config with a freshly wizard-collected one. Only keys in
``RESERVED_PROVIDER_CONFIG_KEYS`` (today, just ``extra_request_params``)
are preserved -- any other non-schema key in ``old_config`` is dropped,
exactly as before this function existed. If ``new_config`` already
carries the key (e.g. a module surfaces it deliberately in the future),
the old value is not used -- the freshly collected value wins.

Args:
old_config: The provider instance's config before this reconfigure,
or None if this is a fresh instance with no prior config.
new_config: The config just rebuilt from wizard/schema answers.

Returns:
``new_config``, with any missing reserved keys copied in from
``old_config``. Never mutates either input in place.
"""
if not old_config:
return new_config
result = new_config
for key in RESERVED_PROVIDER_CONFIG_KEYS:
if key in old_config and key not in result:
result = {**result, key: old_config[key]}
return result


def _prompt_model_selection(
provider_id: str,
Expand Down
Loading
Loading