setting refactor - backend conversions
frdel committed
Dec 22, 2025 at 13:47 UTC
ef82fc8bb9b663df0096a5ca854befe619e473b3
5 files changed
+111
-22
models.py
+2
-2
@@ -22,7 +22,7 @@ from litellm.types.utils import ModelResponse
22
from python.helpers import dotenv
23
from python.helpers import settings, dirty_json
24
from python.helpers.dotenv import load_dotenv
25
-from python.helpers.providers import get_provider_config
25
+from python.helpers.providers import ModelType as ProviderModelType, get_provider_config
26
from python.helpers.rate_limiter import RateLimiter
27
from python.helpers.tokens import approximate_tokens
28
from python.helpers import dirty_json, browser_use_monkeypatch
@@ -844,7 +844,7 @@ def _adjust_call_args(provider_name: str, model_name: str, kwargs: dict):
844
845
846
def _merge_provider_defaults(
847
- provider_type: str, original_provider: str, kwargs: dict
847
+ provider_type: ProviderModelType, original_provider: str, kwargs: dict
848
) -> tuple[str, dict]:
849
# Normalize .env-style numeric strings (e.g., "timeout=30") into ints/floats for LiteLLM
850
def _normalize_values(values: dict) -> dict:
python/api/settings_get.py
+3
-2
@@ -4,8 +4,9 @@ from python.helpers import settings
4
5
class GetSettings(ApiHandler):
6
async def process(self, input: dict, request: Request) -> dict | Response:
7
- set = settings.convert_out(settings.get_settings())
8
- return {"settings": set}
7
+ backend = settings.get_settings()
8
+ out = settings.convert_out(backend)
9
+ return dict(out)
10
11
@classmethod
12
def get_methods(cls) -> list[str]:
python/api/settings_set.py
+5
-3
@@ -7,6 +7,8 @@ from typing import Any
7
8
class SetSettings(ApiHandler):
9
async def process(self, input: dict[Any, Any], request: Request) -> dict[Any, Any] | Response:
10
- set = settings.convert_in(input)
11
- set = settings.set_settings(set)
12
- return {"settings": set}
10
+ frontend = input.get("settings", input)
11
+ backend = settings.convert_in(settings.Settings(**frontend))
12
+ backend = settings.set_settings(backend)
13
+ out = settings.convert_out(backend)
14
+ return dict(out)
python/helpers/providers.py
+9
-9
@@ -1,7 +1,8 @@
1
import yaml
2
from python.helpers import files
3
-from typing import List, Dict, Optional, TypedDict
3
+from typing import List, Dict, Optional, TypedDict, Literal
4
5
+ModelType = Literal["chat", "embedding"]
6
7
# Type alias for UI option items
8
class FieldOption(TypedDict):
@@ -68,16 +69,15 @@ class ProviderManager:
69
opts.append({"value": pid, "label": name})
70
self._options[p_type] = opts
71
71
- def get_providers(self, provider_type: str) -> List[FieldOption]:
72
+ def get_providers(self, provider_type: ModelType) -> List[FieldOption]:
73
"""Returns a list of providers for a given type (e.g., 'chat', 'embedding')."""
74
return self._options.get(provider_type, []) if self._options else []
75
75
-
76
- def get_raw_providers(self, provider_type: str) -> List[Dict[str, str]]:
76
+ def get_raw_providers(self, provider_type: ModelType) -> List[Dict[str, str]]:
77
"""Return raw provider dictionaries for advanced use-cases."""
78
return self._raw.get(provider_type, []) if self._raw else []
79
80
- def get_provider_config(self, provider_type: str, provider_id: str) -> Optional[Dict[str, str]]:
80
+ def get_provider_config(self, provider_type: ModelType, provider_id: str) -> Optional[Dict[str, str]]:
81
"""Return the metadata dict for a single provider id (case-insensitive)."""
82
provider_id_low = provider_id.lower()
83
for p in self.get_raw_providers(provider_type):
@@ -86,16 +86,16 @@ class ProviderManager:
86
return None
87
88
89
-def get_providers(provider_type: str) -> List[FieldOption]:
89
+def get_providers(provider_type: ModelType) -> List[FieldOption]:
90
"""Convenience function to get providers of a specific type."""
91
return ProviderManager.get_instance().get_providers(provider_type)
92
93
94
-def get_raw_providers(provider_type: str) -> List[Dict[str, str]]:
94
+def get_raw_providers(provider_type: ModelType) -> List[Dict[str, str]]:
95
"""Return full metadata for providers of a given type."""
96
return ProviderManager.get_instance().get_raw_providers(provider_type)
97
98
99
-def get_provider_config(provider_type: str, provider_id: str) -> Optional[Dict[str, str]]:
99
+def get_provider_config(provider_type: ModelType, provider_id: str) -> Optional[Dict[str, str]]:
100
"""Return metadata for a single provider (None if not found)."""
101
- return ProviderManager.get_instance().get_provider_config(provider_type, provider_id)
\ No newline at end of file
101
+ return ProviderManager.get_instance().get_provider_config(provider_type, provider_id)
\ No newline at end of file
python/helpers/settings.py
+92
-6
@@ -10,7 +10,7 @@ import models
10
from python.helpers import runtime, whisper, defer, git
11
from . import files, dotenv
12
from python.helpers.print_style import PrintStyle
13
-from python.helpers.providers import get_providers
13
+from python.helpers.providers import get_providers, FieldOption as ProvidersFO
14
from python.helpers.secrets import get_default_secrets_manager
15
from python.helpers import dirty_json
16
@@ -156,7 +156,6 @@ class FieldOption(TypedDict):
156
value: str
157
label: str
158
159
-
159
class SettingsField(TypedDict, total=False):
160
id: str
161
title: str
@@ -188,9 +187,20 @@ class SettingsSection(TypedDict, total=False):
187
fields: list[SettingsField]
188
tab: str # Indicates which tab this section belongs to
189
190
+class ModelProvider(ProvidersFO):
191
+ pass
192
+
193
+class SettingsOutputAdditional(TypedDict):
194
+ chat_providers: list[ModelProvider]
195
+ embedding_providers: list[ModelProvider]
196
+ shell_interfaces: list[FieldOption]
197
+ agent_subdirs: list[FieldOption]
198
+ stt_models: list[FieldOption]
199
+ is_dockerized: bool
200
201
class SettingsOutput(TypedDict):
193
- sections: list[SettingsSection]
202
+ settings: Settings
203
+ additional: SettingsOutputAdditional
204
205
206
PASSWORD_PLACEHOLDER = "****PSWD****"
@@ -199,8 +209,55 @@ API_KEY_PLACEHOLDER = "************"
209
SETTINGS_FILE = files.get_abs_path("tmp/settings.json")
210
_settings: Settings | None = None
211
202
-
212
def convert_out(settings: Settings) -> SettingsOutput:
213
+ out = SettingsOutput(
214
+ settings = settings.copy(),
215
+ additional = SettingsOutputAdditional(
216
+ chat_providers=get_providers("chat"),
217
+ embedding_providers=get_providers("embedding"),
218
+ shell_interfaces=[{"value": "local", "label": "Local Python TTY"}, {"value": "ssh", "label": "SSH"}],
219
+ is_dockerized=runtime.is_dockerized(),
220
+ agent_subdirs=[{"value": subdir, "label": subdir}
221
+ for subdir in files.get_subdirectories("agents")
222
+ if subdir != "_example"],
223
+ stt_models=[
224
+ {"value": "tiny", "label": "Tiny (39M, English)"},
225
+ {"value": "base", "label": "Base (74M, English)"},
226
+ {"value": "small", "label": "Small (244M, English)"},
227
+ {"value": "medium", "label": "Medium (769M, English)"},
228
+ {"value": "large", "label": "Large (1.5B, Multilingual)"},
229
+ {"value": "turbo", "label": "Turbo (Multilingual)"},
230
+ ]
231
+
232
+ )
233
+ )
234
+
235
+ # load auth from dotenv
236
+ out["settings"]["auth_login"] = dotenv.get_dotenv_value(dotenv.KEY_AUTH_LOGIN) or ""
237
+ out["settings"]["auth_password"] = (
238
+ PASSWORD_PLACEHOLDER if dotenv.get_dotenv_value(dotenv.KEY_AUTH_PASSWORD) else ""
239
+ )
240
+ out["settings"]["rfc_password"] = (
241
+ PASSWORD_PLACEHOLDER if dotenv.get_dotenv_value(dotenv.KEY_RFC_PASSWORD) else ""
242
+ )
243
+
244
+ #secrets
245
+ secrets_manager = get_default_secrets_manager()
246
+ try:
247
+ out["settings"]["secrets"] = secrets_manager.get_masked_secrets()
248
+ except Exception:
249
+ out["settings"]["secrets"] = ""
250
+
251
+ # normalize certain fields
252
+ for key, value in list(out["settings"].items()):
253
+ # convert kwargs dicts to .env format
254
+ if (key.endswith("_kwargs") or key=="browser_http_headers") and isinstance(value, dict):
255
+ out["settings"][key] = _dict_to_env(value)
256
+ return out
257
+
258
+
259
+#TODO just for reference, remove
260
+def _convert_out_old(settings: Settings) -> SettingsOutput:
261
default_settings = get_default_settings()
262
263
# main model section
@@ -1347,7 +1404,34 @@ def _get_api_key_field(settings: Settings, provider: str, title: str) -> Setting
1404
}
1405
1406
1350
-def convert_in(settings: dict) -> Settings:
1407
+def convert_in(settings: Settings) -> Settings:
1408
+ current = get_settings()
1409
+
1410
+ # api keys
1411
+ if "api_keys" in settings and isinstance(settings["api_keys"], dict):
1412
+ for provider, value in settings["api_keys"].items():
1413
+ if value != API_KEY_PLACEHOLDER:
1414
+ current["api_keys"][provider] = value
1415
+
1416
+ # all other fields
1417
+ for key, value in settings.items():
1418
+ if key == "api_keys":
1419
+ continue
1420
+
1421
+ # Skip saving if value is a placeholder
1422
+ if value == PASSWORD_PLACEHOLDER or value == API_KEY_PLACEHOLDER:
1423
+ continue
1424
+
1425
+ # Special handling for browser_http_headers and *_kwargs (stored as .env text)
1426
+ if (key == "browser_http_headers" or key.endswith("_kwargs")) and isinstance(value, str):
1427
+ current[key] = _env_to_dict(value)
1428
+ continue
1429
+
1430
+ current[key] = value
1431
+ return current
1432
+
1433
+#TODO just for reference, remove
1434
+def _convert_in_old(settings: dict) -> Settings:
1435
current = get_settings()
1436
for section in settings["sections"]:
1437
if "fields" in section:
@@ -1368,6 +1452,7 @@ def convert_in(settings: dict) -> Settings:
1452
current[field["id"]] = field["value"]
1453
return current
1454
1455
+
1456
def get_settings() -> Settings:
1457
global _settings
1458
if not _settings:
@@ -1385,12 +1470,13 @@ def set_settings(settings: Settings, apply: bool = True):
1470
_write_settings_file(_settings)
1471
if apply:
1472
_apply_settings(previous)
1473
+ return _settings
1474
1475
1476
def set_settings_delta(delta: dict, apply: bool = True):
1477
current = get_settings()
1478
new = {**current, **delta}
1393
- set_settings(new, apply) # type: ignore
1479
+ return set_settings(new, apply) # type: ignore
1480
1481
1482
def merge_settings(original: Settings, delta: dict) -> Settings: