From 971e93ee9689e4c5eff612fed69246478fa991f5 Mon Sep 17 00:00:00 2001 From: frdel <38891707+frdel@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:43:25 +0100 Subject: [PATCH] refactor: remove browser_http_headers config and add safe_call utility for plugin hooks - Remove browser_http_headers from model config (config, migration, UI, helpers) - Add safe_call function to extract_tools.py to safely invoke functions with filtered args/kwargs - Update call_plugin_hook to use safe_call for better parameter handling - Add _apply_defaults_from_env to apply environment variable defaults to plugin configs - Delete additional plugin asset folders when deleting plugins - Remove --- helpers/extract_tools.py | 34 +++++++++++++++++++ helpers/plugins.py | 32 +++++++++++++++-- helpers/settings.py | 6 ++-- plugins/_model_config/default_config.yaml | 4 +-- .../_10_migrate_model_config.py | 4 --- plugins/_model_config/helpers/model_config.py | 7 ---- plugins/_model_config/hooks.py | 1 - plugins/_model_config/webui/config.html | 4 --- plugins/_model_config/webui/main.html | 12 +++---- .../_model_config/webui/model-config-store.js | 5 ++- tools/browser_agent.py | 4 +-- 11 files changed, 76 insertions(+), 37 deletions(-) diff --git a/helpers/extract_tools.py b/helpers/extract_tools.py index 334a42b1b..5dc7f1c29 100644 --- a/helpers/extract_tools.py +++ b/helpers/extract_tools.py @@ -5,6 +5,8 @@ from .dirty_json import DirtyJson from .files import get_abs_path, deabsolute_path import regex from fnmatch import fnmatch +import inspect + def json_parse_dirty(json:str) -> dict[str,Any] | None: if not json or not isinstance(json, str): @@ -118,3 +120,35 @@ def load_classes_from_file(file: str, base_class: type[T], one_per_file: bool = break return classes + +def safe_call(func, *args, **kwargs): + sig = inspect.signature(func) + + bound_args = [] + bound_kwargs = {} + + params = sig.parameters + + # Check if function accepts *args / **kwargs + accepts_var_args = any(p.kind == p.VAR_POSITIONAL for p in params.values()) + accepts_var_kwargs = any(p.kind == p.VAR_KEYWORD for p in params.values()) + + # Handle positional args + if accepts_var_args: + bound_args = args + else: + max_positional = sum( + 1 for p in params.values() + if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) + ) + bound_args = args[:max_positional] + + # Handle kwargs + if accepts_var_kwargs: + bound_kwargs = kwargs + else: + bound_kwargs = { + k: v for k, v in kwargs.items() if k in params + } + + return func(*bound_args, **bound_kwargs) \ No newline at end of file diff --git a/helpers/plugins.py b/helpers/plugins.py index d7688f719..d17de63d5 100644 --- a/helpers/plugins.py +++ b/helpers/plugins.py @@ -14,6 +14,7 @@ from typing import ( TYPE_CHECKING, TypedDict, ) +from helpers.settings import get_default_value from regex import W @@ -359,10 +360,20 @@ def delete_plugin(plugin_name: str): custom_plugins_dir = files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR) if not files.is_in_dir(plugin_dir, custom_plugins_dir): raise ValueError("Only custom plugins can be deleted") + + # delete additional plugin folders + assets = find_plugin_assets("",plugin_name=plugin_name) + for asset in assets: + files.delete_dir(asset["path"]) + + send_frontend_reload_notification( [plugin_name] ) # send before deletion to properly check the extensions, second notification will be skipped automatically + + # delete main plugin folder files.delete_dir(plugin_dir) + after_plugin_change([plugin_name]) @@ -540,6 +551,8 @@ def get_plugin_config( agent_profile: str | None = None, ): + default_used = False + if project_name is None and agent is not None: from helpers import projects @@ -561,6 +574,7 @@ def get_plugin_config( file_path = files.get_abs_path( find_plugin_dir(plugin_name), CONFIG_DEFAULT_FILE_NAME ) + default_used = True result = None if file_path and files.exists(file_path): @@ -568,6 +582,9 @@ def get_plugin_config( json.loads if file_path.lower().endswith(".json") else yaml_helper.loads )(files.read_file(file_path)) + if default_used: + _apply_defaults_from_env(plugin_name, result) + # call plugin hook to modify the standard result if needed result = call_plugin_hook( plugin_name, @@ -838,6 +855,17 @@ def call_plugin_hook( return default if asyncio.iscoroutinefunction(hook): - return asyncio.run(hook(*args, **kwargs, default=default)) + return asyncio.run(extract_tools.safe_call(hook, *args, default=default, **kwargs)) - return hook(*args, **kwargs, default=default) + return extract_tools.safe_call(hook, *args, default=default, **kwargs) + + +def _apply_defaults_from_env(plugin_name: str, config: dict[str, Any]): + def _apply(prefix: list[str], value: dict[str, Any]): + for key, child in value.items(): + env_name = "__".join([plugin_name, *prefix, key]) + value[key] = get_default_value(env_name, child) + if isinstance(value[key], dict): + _apply([*prefix, key], value[key]) + + _apply([], config) diff --git a/helpers/settings.py b/helpers/settings.py index e85a741c2..cfa587347 100644 --- a/helpers/settings.py +++ b/helpers/settings.py @@ -262,7 +262,7 @@ def convert_out(settings: Settings) -> SettingsOutput: # normalize certain fields for key, value in list(out["settings"].items()): # convert kwargs dicts to .env format - if (key.endswith("_kwargs") or key=="browser_http_headers") and isinstance(value, dict): + if (key.endswith("_kwargs")) and isinstance(value, dict): out["settings"][key] = _dict_to_env(value) return out @@ -281,8 +281,8 @@ def convert_in(settings: Settings) -> Settings: current = get_settings() for key, value in settings.items(): - # Special handling for browser_http_headers and *_kwargs (stored as .env text) - if (key == "browser_http_headers" or key.endswith("_kwargs")) and isinstance(value, str): + # Special handling for *_kwargs (stored as .env text) + if (key.endswith("_kwargs")) and isinstance(value, str): current[key] = _env_to_dict(value) continue diff --git a/plugins/_model_config/default_config.yaml b/plugins/_model_config/default_config.yaml index 0d56fa1c3..15e3e1a56 100644 --- a/plugins/_model_config/default_config.yaml +++ b/plugins/_model_config/default_config.yaml @@ -29,6 +29,4 @@ embedding_model: api_base: "" rl_requests: 0 rl_input: 0 - kwargs: {} - -browser_http_headers: {} + kwargs: {} \ No newline at end of file diff --git a/plugins/_model_config/extensions/python/initialize_migration_start/_10_migrate_model_config.py b/plugins/_model_config/extensions/python/initialize_migration_start/_10_migrate_model_config.py index 4137a0012..58d525da7 100644 --- a/plugins/_model_config/extensions/python/initialize_migration_start/_10_migrate_model_config.py +++ b/plugins/_model_config/extensions/python/initialize_migration_start/_10_migrate_model_config.py @@ -83,7 +83,6 @@ class MigrateModelConfig(Extension): "rl_input": raw.get("embed_model_rl_input", 0), "kwargs": raw.get("embed_model_kwargs", {}), }, - "browser_http_headers": raw.get("browser_http_headers", {}), } # Ensure kwargs are dicts (might be strings from .env format) @@ -92,9 +91,6 @@ class MigrateModelConfig(Extension): if isinstance(kw, str): plugin_config[section]["kwargs"] = {} - if isinstance(plugin_config["browser_http_headers"], str): - plugin_config["browser_http_headers"] = {} - # Save as global plugin config plugins.save_plugin_config("_model_config", "", "", plugin_config) PrintStyle(background_color="#6734C3", font_color="white", padding=True).print( diff --git a/plugins/_model_config/helpers/model_config.py b/plugins/_model_config/helpers/model_config.py index 78566f547..592d2deac 100644 --- a/plugins/_model_config/helpers/model_config.py +++ b/plugins/_model_config/helpers/model_config.py @@ -117,13 +117,6 @@ def get_embedding_model_config(agent=None) -> dict: cfg = get_config(agent) return cfg.get("embedding_model", {}) - -def get_browser_http_headers(agent=None) -> dict: - """Get browser HTTP headers from config.""" - cfg = get_config(agent) - return cfg.get("browser_http_headers", {}) - - def is_chat_override_allowed(agent=None) -> bool: """Check if per-chat model override is enabled.""" cfg = get_config(agent) diff --git a/plugins/_model_config/hooks.py b/plugins/_model_config/hooks.py index 48e9d53b1..75ada70db 100644 --- a/plugins/_model_config/hooks.py +++ b/plugins/_model_config/hooks.py @@ -1,7 +1,6 @@ def save_plugin_config(result=None, settings=None, **kwargs): if settings and isinstance(settings, dict): # Remove transient UI-only fields before persisting - settings.pop("_browser_headers_text", None) for section in ("chat_model", "utility_model", "embedding_model"): if section in settings and isinstance(settings[section], dict): settings[section].pop("_kwargs_text", None) diff --git a/plugins/_model_config/webui/config.html b/plugins/_model_config/webui/config.html index e1547852b..39c0f948d 100644 --- a/plugins/_model_config/webui/config.html +++ b/plugins/_model_config/webui/config.html @@ -267,10 +267,6 @@ Custom HTTP headers sent with browser requests. The browser agent uses the main model. Format is KEY=VALUE, one per line. -
- -
diff --git a/plugins/_model_config/webui/main.html b/plugins/_model_config/webui/main.html index 723649cb6..196f71233 100644 --- a/plugins/_model_config/webui/main.html +++ b/plugins/_model_config/webui/main.html @@ -188,10 +188,6 @@
Browser HTTP Headers
Custom HTTP headers sent with browser requests. The browser agent uses the main model. Format is KEY=VALUE, one per line.
-
- -
Utility Model (optional — falls back to the configured Utility Model)
@@ -330,21 +326,21 @@ @click=" presets = [...presets, { name: 'Preset ' + (presets.length + 1), - chat: { provider: '', name: '', api_key: '', api_base: '', ctx_length: 128000, ctx_history: 0.7, vision: true, rl_requests: 0, rl_input: 0, rl_output: 0, kwargs: {}, browser_http_headers: {}, _kwargs_text: '', _browser_headers_text: '' }, + chat: { provider: '', name: '', api_key: '', api_base: '', ctx_length: 128000, ctx_history: 0.7, vision: true, rl_requests: 0, rl_input: 0, rl_output: 0, kwargs: {}, _kwargs_text: '' }, utility: { provider: '', name: '', api_key: '', api_base: '', ctx_length: 128000, ctx_input: 0.7, rl_requests: 0, rl_input: 0, rl_output: 0, kwargs: {}, _kwargs_text: '' } }]"> - add + add Add Preset diff --git a/plugins/_model_config/webui/model-config-store.js b/plugins/_model_config/webui/model-config-store.js index 7e0027053..aeff3adbb 100644 --- a/plugins/_model_config/webui/model-config-store.js +++ b/plugins/_model_config/webui/model-config-store.js @@ -70,7 +70,7 @@ export const store = createStore("modelConfig", { _normalizePresets(rawPresets) { return (rawPresets || []).map(p => ({ name: p.name || '', - chat: { provider: '', name: '', api_key: '', api_base: '', ctx_length: 128000, ctx_history: 0.7, vision: true, rl_requests: 0, rl_input: 0, rl_output: 0, kwargs: {}, browser_http_headers: {}, _kwargs_text: kwargsToText(p.chat?.kwargs), _browser_headers_text: Object.entries(p.chat?.browser_http_headers || {}).map(([k, v]) => k + '=' + v).join('\n'), ...(p.chat || {}) }, + chat: { provider: '', name: '', api_key: '', api_base: '', ctx_length: 128000, ctx_history: 0.7, vision: true, rl_requests: 0, rl_input: 0, rl_output: 0, kwargs: {}, _kwargs_text: kwargsToText(p.chat?.kwargs), ...(p.chat || {}) }, utility: { provider: '', name: '', api_key: '', api_base: '', ctx_length: 128000, ctx_input: 0.7, rl_requests: 0, rl_input: 0, rl_output: 0, kwargs: {}, _kwargs_text: kwargsToText(p.utility?.kwargs), ...(p.utility || {}) }, })); }, @@ -117,7 +117,6 @@ export const store = createStore("modelConfig", { if (config?.chat_model) config.chat_model._kwargs_text = kwargsToText(config.chat_model.kwargs); if (config?.utility_model) config.utility_model._kwargs_text = kwargsToText(config.utility_model.kwargs); if (config?.embedding_model) config.embedding_model._kwargs_text = kwargsToText(config.embedding_model.kwargs); - if (config) config._browser_headers_text = Object.entries(config.browser_http_headers || {}).map(([k, v]) => k + '=' + v).join('\n'); }, // Global presets @@ -143,7 +142,7 @@ export const store = createStore("modelConfig", { const c = { name: p.name }; for (const slot of ['chat', 'utility']) { if (p[slot]) { - const { _kwargs_text, _browser_headers_text, ...rest } = p[slot]; + const { _kwargs_text, ...rest } = p[slot]; c[slot] = rest; } } diff --git a/tools/browser_agent.py b/tools/browser_agent.py index 690e08634..b04da6701 100644 --- a/tools/browser_agent.py +++ b/tools/browser_agent.py @@ -44,8 +44,8 @@ class State: ) def _get_browser_http_headers(self): - from plugins._model_config.helpers.model_config import get_browser_http_headers - return get_browser_http_headers(self.agent) or {} + # ignored for now + return {} def _get_browser_vision(self): from plugins._model_config.helpers.model_config import get_chat_model_config