refactor: clean up banner extensions

Use call_extensions() helper Let extensions append banners directly Rename HUGGINGFACE_LOCAL_FOR_EMBEDDING to LOCAL_EMBEDDING

keyboardstaff committed Dec 31, 2025 at 01:19 UTC 99a2a94813f867a5003c8e79d340d35017b543fb
3 files changed +12 -40
python/api/banners.py
+5 -30
@@ -1,6 +1,5 @@
1 from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers.extension import Extension
3 -from python.helpers import files, extract_tools
2 +from python.helpers.extension import call_extensions
3
4
5 class GetBanners(ApiHandler):
@@ -12,36 +11,12 @@ class GetBanners(ApiHandler):
11 async def process(self, input: dict, request: Request) -> dict | Response:
12 frontend_banners = input.get("banners", [])
13 frontend_context = input.get("context", {})
15 - backend_banners = await self._run_banner_extensions(frontend_context, frontend_banners)
16 - return {"banners": backend_banners}
17 -
18 - async def _run_banner_extensions(self, context: dict, frontend_banners: list) -> list[dict]:
19 - """Run all banner checks via extension point system."""
14 +
15 + # Banners array passed by reference - extensions append directly to it
16 banners = []
21 - for cls in self._get_banner_extensions():
22 - try:
23 - result = await cls(agent=None).execute(context=context, frontend_banners=frontend_banners)
24 - if result:
25 - banners.extend(result if isinstance(result, list) else [result])
26 - except Exception as e:
27 - print(f"Banner check failed ({cls.__name__}): {e}")
28 - return banners
29 -
30 - def _get_banner_extensions(self) -> list[type[Extension]]:
31 - """Load banner extension classes from extensions folders."""
32 - all_exts = []
33 - for path in ["python/extensions/banners", "usr/extensions/banners"]:
34 - abs_path = files.get_abs_path(path)
35 - if files.exists(abs_path):
36 - all_exts.extend(extract_tools.load_classes_from_folder(abs_path, "*", Extension))
17 + await call_extensions("banners", agent=None, banners=banners, context=frontend_context, frontend_banners=frontend_banners)
18
38 - # Deduplicate by filename (usr overrides default), sort by name
39 - unique = {}
40 - for cls in all_exts:
41 - file = cls.__module__.split(".")[-1]
42 - if file not in unique:
43 - unique[file] = cls
44 - return sorted(unique.values(), key=lambda c: c.__module__.split(".")[-1])
19 + return {"banners": banners}
20
21 @classmethod
22 def get_methods(cls) -> list[str]:
python/extensions/banners/_10_unsecured_connection.py
+1 -4
@@ -6,8 +6,7 @@ import re
6 class UnsecuredConnectionCheck(Extension):
7 """Check: non-local without credentials, or credentials over non-HTTPS."""
8
9 - async def execute(self, context: dict = {}, **kwargs) -> dict | list | None:
10 - banners = []
9 + async def execute(self, banners: list = [], context: dict = {}, **kwargs):
10 hostname = context.get("hostname", "")
11 protocol = context.get("protocol", "")
12
@@ -42,8 +41,6 @@ class UnsecuredConnectionCheck(Extension):
41 "dismissible": True,
42 "source": "backend"
43 })
45 -
46 - return banners if banners else None
44
45 def _is_localhost(self, hostname: str) -> bool:
46 local_patterns = ["localhost", "127.0.0.1", "::1", "0.0.0.0"]
python/extensions/banners/_20_missing_api_key.py
+6 -6
@@ -7,7 +7,7 @@ class MissingApiKeyCheck(Extension):
7 """Check if API keys are configured for selected model providers."""
8
9 LOCAL_PROVIDERS = ["ollama", "lm_studio"]
10 - HUGGINGFACE_LOCAL_FOR_EMBEDDING = ["huggingface"]
10 + LOCAL_EMBEDDING = ["huggingface"]
11 MODEL_TYPE_NAMES = {
12 "chat": "Chat Model",
13 "utility": "Utility Model",
@@ -15,7 +15,7 @@ class MissingApiKeyCheck(Extension):
15 "embedding": "Embedding Model",
16 }
17
18 - async def execute(self, context: dict = {}, **kwargs) -> dict | None:
18 + async def execute(self, banners: list = [], context: dict = {}, **kwargs):
19 current_settings = settings_helper.get_settings()
20 model_providers = {
21 "chat": current_settings.get("chat_model_provider", ""),
@@ -33,7 +33,7 @@ class MissingApiKeyCheck(Extension):
33 provider_lower = provider.lower()
34 if provider_lower in self.LOCAL_PROVIDERS:
35 continue
36 - if model_type == "embedding" and provider_lower in self.HUGGINGFACE_LOCAL_FOR_EMBEDDING:
36 + if model_type == "embedding" and provider_lower in self.LOCAL_EMBEDDING:
37 continue
38
39 api_key = models.get_api_key(provider_lower)
@@ -44,13 +44,13 @@ class MissingApiKeyCheck(Extension):
44 })
45
46 if not missing_providers:
47 - return None
47 + return
48
49 model_list = ", ".join(
50 f"{p['model_type']} ({p['provider']})" for p in missing_providers
51 )
52
53 - return {
53 + banners.append({
54 "id": "missing-api-key",
55 "type": "error",
56 "priority": 100,
@@ -61,4 +61,4 @@ class MissingApiKeyCheck(Extension):
61 Add your API key</a> in Settings → External Services → API Keys.""",
62 "dismissible": False,
63 "source": "backend"
64 - }
64 + })