Skip to content

Commit f17682d

Browse files
committed
feat(models): add @extensible decorator to get_api_key()
Add @extensible decorator to get_api_key() in models.py, enabling plugins to intercept or replace the API key resolution logic for any LLM provider. Currently, get_api_key() resolves API keys exclusively from OS environment variables via os.getenv(). This means external secrets backends (OpenBao/Vault, AWS Secrets Manager, Azure Key Vault, etc.) cannot provide API keys without injecting them into the process environment — a workaround that is fragile and bypasses the existing SecretsManager architecture. With @extensible, a plugin can intercept the start hook to resolve API keys from any backend, falling through to the default dotenv behaviour when no plugin is active. To avoid a circular import (settings.py imports models at module level, and models importing helpers.extension triggers the cycle settings -> models -> extension -> subagents -> plugins -> extension), the top-level `import models` in settings.py is moved inside the three functions that actually use it. This is the standard Python pattern for breaking import cycles. No behavioural change for existing users — the decorator only adds extension hooks around the existing function, and the deferred import in settings.py has no performance impact since Python caches module imports after the first load.
1 parent 30a9ed2 commit f17682d

2 files changed

Lines changed: 5 additions & 1 deletion

File tree

helpers/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import subprocess
77
from typing import Any, Literal, TypedDict, cast, TypeVar
88

9-
import models
109
from helpers import runtime, whisper, defer, git, subagents
1110
from . import files, dotenv
1211
from helpers.print_style import PrintStyle
@@ -223,6 +222,7 @@ def _ensure_option_present(options: list[OptionT] | None, current_value: str | N
223222
return opts
224223

225224
def convert_out(settings: Settings) -> SettingsOutput:
225+
import models # deferred to avoid circular import with models.py
226226
out = SettingsOutput(
227227
settings = settings.copy(),
228228
additional = SettingsOutputAdditional(
@@ -309,6 +309,7 @@ def convert_out(settings: Settings) -> SettingsOutput:
309309
return out
310310

311311
def _get_api_key_field(settings: Settings, provider: str, title: str) -> SettingsField:
312+
import models # deferred to avoid circular import with models.py
312313
key = settings["api_keys"].get(provider, models.get_api_key(provider))
313314
# For API keys, use simple asterisk placeholder for existing keys
314315
return {
@@ -418,6 +419,7 @@ def _adjust_to_version(settings: Settings, default: Settings):
418419

419420

420421
def _load_sensitive_settings(settings: Settings):
422+
import models # deferred to avoid circular import with models.py
421423
# load api keys from .env
422424
providers = get_providers("chat") + get_providers("embedding")
423425
for provider in providers:

models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from helpers.rate_limiter import RateLimiter
2727
from helpers.tokens import approximate_tokens
2828
from helpers import dirty_json, browser_use_monkeypatch
29+
from helpers.extension import extensible
2930

3031
from langchain_core.language_models.chat_models import SimpleChatModel
3132
from langchain_core.outputs.chat_generation import ChatGenerationChunk
@@ -198,6 +199,7 @@ def output(self) -> ChatChunk:
198199
api_keys_round_robin: dict[str, int] = {}
199200

200201

202+
@extensible
201203
def get_api_key(service: str) -> str:
202204
# get api key for the service
203205
key = (

0 commit comments

Comments
 (0)