Leon4gr45 commited on
Commit
91ff7bb
·
verified ·
1 Parent(s): 82d8767

Fix startup error by reorganizing adaptation patches

Browse files
patches/api/api_docs.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import threading
3
+ from typing import Any
4
+ from flask import Flask
5
+
6
+ class ApiDocs:
7
+ def __init__(self, webapp: Flask, lock: threading.Lock) -> None:
8
+ self.webapp = webapp
9
+ self.lock = lock
10
+
11
+ async def process(self, data: dict[str, Any], request: Any) -> dict[str, Any]:
12
+ endpoints = []
13
+ for rule in self.webapp.url_map.iter_rules():
14
+ if rule.rule.startswith("/api/"):
15
+ methods = ",".join(m for m in rule.methods if m not in ["OPTIONS", "HEAD"])
16
+ endpoints.append({
17
+ "path": rule.rule,
18
+ "methods": methods,
19
+ "endpoint": rule.endpoint
20
+ })
21
+
22
+ return {
23
+ "title": "Agent-Zero API Documentation",
24
+ "version": "1.0.0",
25
+ "hf_space": os.getenv("HF_SPACE") == "true",
26
+ "endpoints": endpoints
27
+ }
patches/helpers/api.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import abstractmethod
2
+ import json
3
+ import threading
4
+ import os
5
+ from functools import wraps
6
+ from pathlib import Path
7
+ from typing import Union, Dict, Any
8
+ from flask import (
9
+ Request,
10
+ Response,
11
+ jsonify,
12
+ Flask,
13
+ session,
14
+ request,
15
+ send_file,
16
+ redirect,
17
+ url_for,
18
+ )
19
+ from werkzeug.wrappers.response import Response as BaseResponse
20
+ from agent import AgentContext
21
+ from helpers.print_style import PrintStyle
22
+ from helpers.errors import format_error
23
+ from helpers import files, cache
24
+
25
+ ThreadLockType = Union[threading.Lock, threading.RLock]
26
+
27
+ CACHE_AREA = "api_handlers(api)"
28
+
29
+ Input = dict
30
+ Output = Union[Dict[str, Any], Response]
31
+
32
+
33
+ class ApiHandler:
34
+ def __init__(self, app: Flask, thread_lock: ThreadLockType):
35
+ self.app = app
36
+ self.thread_lock = thread_lock
37
+
38
+ @classmethod
39
+ def requires_loopback(cls) -> bool:
40
+ return False
41
+
42
+ @classmethod
43
+ def requires_api_key(cls) -> bool:
44
+ return False
45
+
46
+ @classmethod
47
+ def requires_auth(cls) -> bool:
48
+ return True
49
+
50
+ @classmethod
51
+ def get_methods(cls) -> list[str]:
52
+ return ["POST"]
53
+
54
+ @classmethod
55
+ def requires_csrf(cls) -> bool:
56
+ return cls.requires_auth()
57
+
58
+ @abstractmethod
59
+ async def process(self, input: Input, request: Request) -> Output:
60
+ pass
61
+
62
+ async def handle_request(self, request: Request) -> Response:
63
+ try:
64
+ input_data: Input = {}
65
+ if request.is_json:
66
+ try:
67
+ if request.data:
68
+ input_data = request.get_json()
69
+ except Exception as e:
70
+ PrintStyle().print(f"Error parsing JSON: {str(e)}")
71
+ input_data = {}
72
+ else:
73
+ input_data = {}
74
+
75
+ output = await self.process(input_data, request)
76
+
77
+ if isinstance(output, Response):
78
+ return output
79
+ else:
80
+ response_json = json.dumps(output)
81
+ return Response(
82
+ response=response_json, status=200, mimetype="application/json"
83
+ )
84
+ except Exception as e:
85
+ error = format_error(e)
86
+ PrintStyle.error(f"API error: {error}")
87
+ return Response(response=error, status=500, mimetype="text/plain")
88
+
89
+ def use_context(self, ctxid: str, create_if_not_exists: bool = True):
90
+ from helpers.context_utils import use_context as _use_context
91
+ return _use_context(self.thread_lock, ctxid, create_if_not_exists)
92
+
93
+
94
+ from helpers.network import is_loopback_address
95
+
96
+
97
+ def requires_api_key(f):
98
+ @wraps(f)
99
+ async def decorated(*args, **kwargs):
100
+ if os.getenv("HF_SPACE") == "true":
101
+ return await f(*args, **kwargs)
102
+
103
+ from helpers.settings import get_settings
104
+ valid_api_key = get_settings()["mcp_server_token"]
105
+
106
+ if api_key := request.headers.get("X-API-KEY"):
107
+ if api_key != valid_api_key:
108
+ return Response("Invalid API key", 401)
109
+ elif request.json and request.json.get("api_key"):
110
+ api_key = request.json.get("api_key")
111
+ if api_key != valid_api_key:
112
+ return Response("Invalid API key", 401)
113
+ else:
114
+ return Response("API key required", 401)
115
+ return await f(*args, **kwargs)
116
+
117
+ return decorated
118
+
119
+
120
+ def requires_loopback(f):
121
+ @wraps(f)
122
+ async def decorated(*args, **kwargs):
123
+ if os.getenv("HF_SPACE") == "true":
124
+ return await f(*args, **kwargs)
125
+
126
+ if not is_loopback_address(str(request.remote_addr)):
127
+ return Response("Access denied.", 403, {})
128
+ return await f(*args, **kwargs)
129
+
130
+ return decorated
131
+
132
+
133
+ def requires_auth(f):
134
+ @wraps(f)
135
+ async def decorated(*args, **kwargs):
136
+ if os.getenv("HF_SPACE") == "true":
137
+ return await f(*args, **kwargs)
138
+
139
+ from helpers import login
140
+ user_pass_hash = login.get_credentials_hash()
141
+ if not user_pass_hash:
142
+ return await f(*args, **kwargs)
143
+ if session.get("authentication") != user_pass_hash:
144
+ return redirect(url_for("login_handler"))
145
+ return await f(*args, **kwargs)
146
+
147
+ return decorated
148
+
149
+
150
+ def csrf_protect(f):
151
+ @wraps(f)
152
+ async def decorated(*args, **kwargs):
153
+ if os.getenv("HF_SPACE") == "true":
154
+ return await f(*args, **kwargs)
155
+
156
+ from helpers import runtime
157
+ token = session.get("csrf_token")
158
+ header = request.headers.get("X-CSRF-Token")
159
+ cookie = request.cookies.get("csrf_token_" + runtime.get_runtime_id())
160
+ sent = header or cookie
161
+ if not token or not sent or token != sent:
162
+ return Response("CSRF token missing or invalid", 403)
163
+ return await f(*args, **kwargs)
164
+
165
+ return decorated
166
+
167
+
168
+ def register_api_route(app: Flask, lock: ThreadLockType) -> None:
169
+ from helpers.modules import load_classes_from_file
170
+ from helpers import plugins
171
+
172
+ async def _dispatch(path: str) -> BaseResponse:
173
+ cached = cache.get(CACHE_AREA, path)
174
+ if cached is not None:
175
+ return await cached()
176
+
177
+ handler_cls: type[ApiHandler] | None = None
178
+
179
+ builtin_file = files.get_abs_path(f"api/{path}.py")
180
+ if files.is_in_dir(builtin_file, files.get_abs_path("api")) and files.exists(
181
+ builtin_file
182
+ ):
183
+ classes = load_classes_from_file(builtin_file, ApiHandler)
184
+ if classes:
185
+ handler_cls = classes[0]
186
+
187
+ if handler_cls is None and path.startswith("plugins/"):
188
+ parts = path.split("/", 2)
189
+ if len(parts) == 3:
190
+ _, plugin_name, handler_name = parts
191
+ plugin_dir = plugins.find_plugin_dir(plugin_name)
192
+ if plugin_dir:
193
+ plugin_file = Path(plugin_dir) / "api" / f"{handler_name}.py"
194
+ if plugin_file.is_file():
195
+ classes = load_classes_from_file(str(plugin_file), ApiHandler)
196
+ if classes:
197
+ handler_cls = classes[0]
198
+
199
+ if handler_cls is None:
200
+ return Response(f"API endpoint not found: {path}", 404)
201
+
202
+ if request.method not in handler_cls.get_methods():
203
+ return Response(f"Method {request.method} not allowed for: {path}", 405)
204
+
205
+ async def call_handler() -> BaseResponse:
206
+ instance = handler_cls(app, lock)
207
+ return await instance.handle_request(request=request)
208
+
209
+ handler_fn = call_handler
210
+ if handler_cls.requires_csrf():
211
+ handler_fn = csrf_protect(handler_fn)
212
+ if handler_cls.requires_api_key():
213
+ handler_fn = requires_api_key(handler_fn)
214
+ if handler_cls.requires_auth():
215
+ handler_fn = requires_auth(handler_fn)
216
+ if handler_cls.requires_loopback():
217
+ handler_fn = requires_loopback(handler_fn)
218
+
219
+ cache.add(CACHE_AREA, path, handler_fn)
220
+ return await handler_fn()
221
+
222
+ app.add_url_rule(
223
+ "/api/<path:path>",
224
+ "api_dispatch",
225
+ _dispatch,
226
+ methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
227
+ )
patches/helpers/runtime.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import secrets
3
+ import os
4
+
5
+ parser = argparse.ArgumentParser()
6
+ args = {}
7
+ runtime_id = None
8
+
9
+ def initialize():
10
+ global args
11
+ if args: return
12
+ parser.add_argument("--port", type=int, default=None)
13
+ parser.add_argument("--host", type=str, default=None)
14
+ parser.add_argument("--dockerized", type=bool, default=False)
15
+ known, unknown = parser.parse_known_args()
16
+ args = vars(known)
17
+
18
+ def get_arg(name: str): return args.get(name, None)
19
+ def is_dockerized() -> bool: return bool(get_arg("dockerized")) or os.getenv("HF_SPACE") == "true"
20
+ def get_runtime_id() -> str:
21
+ global runtime_id
22
+ if not runtime_id: runtime_id = secrets.token_hex(8)
23
+ return runtime_id
24
+ def get_web_ui_port():
25
+ if os.getenv("HF_SPACE") == "true": return 7860
26
+ return get_arg("port") or 5000
patches/helpers/settings.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import secrets
4
+ from python.helpers import files
5
+ from python.helpers.print_style import PrintStyle
6
+
7
+ def get_settings() -> dict:
8
+ try:
9
+ path = files.get_abs_path("settings.json")
10
+ if os.path.exists(path):
11
+ with open(path, "r") as f:
12
+ return json.load(f)
13
+ except Exception as e:
14
+ PrintStyle.error(f"Error loading settings: {e}")
15
+ # Return defaults
16
+ return {
17
+ "chat_model_provider": "openai",
18
+ "chat_model_name": "gpt-4",
19
+ "chat_model_api_base": "",
20
+ "chat_model_ctx_length": 128000,
21
+ "chat_model_vision": True,
22
+ "chat_model_rl_requests": 0,
23
+ "chat_model_rl_input": 0,
24
+ "chat_model_rl_output": 0,
25
+ "chat_model_kwargs": {},
26
+ "util_model_provider": "openai",
27
+ "util_model_name": "gpt-4o-mini",
28
+ "util_model_api_base": "",
29
+ "util_model_ctx_length": 128000,
30
+ "util_model_rl_requests": 0,
31
+ "util_model_rl_input": 0,
32
+ "util_model_rl_output": 0,
33
+ "util_model_kwargs": {},
34
+ "embed_model_provider": "openai",
35
+ "embed_model_name": "text-embedding-3-small",
36
+ "embed_model_api_base": "",
37
+ "embed_model_rl_requests": 0,
38
+ "embed_model_kwargs": {},
39
+ "browser_model_provider": "openai",
40
+ "browser_model_name": "gpt-4o-mini",
41
+ "browser_model_api_base": "",
42
+ "browser_model_vision": True,
43
+ "browser_model_kwargs": {},
44
+ "agent_profile": "default",
45
+ "agent_memory_subdir": "default",
46
+ "agent_knowledge_subdir": "custom",
47
+ "mcp_servers": "",
48
+ "rfc_url": "localhost",
49
+ "rfc_port_http": 55521
50
+ }
51
+
52
+ def save_settings(settings: dict) -> bool:
53
+ try:
54
+ path = files.get_abs_path("settings.json")
55
+ with open(path, "w") as f:
56
+ json.dump(settings, f, indent=4)
57
+ return True
58
+ except Exception as e:
59
+ PrintStyle.error(f"Error saving settings: {e}")
60
+ return False
61
+
62
+ def get_runtime_config(set):
63
+ return {
64
+ "code_exec_docker_enabled": False,
65
+ "code_exec_ssh_enabled": False
66
+ }
67
+
68
+ def create_auth_token():
69
+ return secrets.token_hex(16)
patches/helpers/ui_server.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass, field
2
+ from datetime import timedelta
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import secrets
7
+ import threading
8
+ import time
9
+ from typing import Any
10
+
11
+ from flask import (
12
+ Flask,
13
+ Response,
14
+ redirect,
15
+ render_template_string,
16
+ request,
17
+ send_file,
18
+ session,
19
+ url_for,
20
+ )
21
+ from socketio import ASGIApp
22
+ from starlette.applications import Starlette
23
+ from starlette.routing import Mount, Route
24
+ from starlette.responses import JSONResponse
25
+ from uvicorn.middleware.wsgi import WSGIMiddleware
26
+ from werkzeug.wrappers.request import Request as WerkzeugRequest
27
+ import socketio # type: ignore[import-untyped]
28
+
29
+ from helpers import dotenv, fasta2a_server, files, git, login, mcp_server, runtime
30
+ from helpers.api import register_api_route, requires_auth
31
+ from helpers.extension import extensible
32
+ from helpers.files import get_abs_path
33
+ from helpers.print_style import PrintStyle
34
+ from helpers.server_startup import StartupMonitor
35
+ from helpers import settings as settings_helper
36
+ from helpers.ws import register_ws_namespace, validate_ws_origin
37
+ from helpers.ws_manager import WsManager, set_shared_ws_manager
38
+
39
+
40
+ UPLOAD_LIMIT_BYTES = 5 * 1024 * 1024 * 1024
41
+
42
+
43
+ def configure_process_environment() -> None:
44
+ logging.getLogger().setLevel(logging.WARNING)
45
+ os.environ["TZ"] = "UTC"
46
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
47
+ if hasattr(time, "tzset"):
48
+ time.tzset()
49
+
50
+
51
+ @dataclass
52
+ class UiServerRuntime:
53
+ webapp: Flask
54
+ socketio_server: socketio.AsyncServer
55
+ ws_manager: WsManager
56
+ lock: threading.RLock
57
+ settings_snapshot: dict[str, Any]
58
+ _routes_registered: bool = False
59
+ _transport_registered: bool = False
60
+ _route_handlers: "UiRouteHandlers | None" = field(default=None, init=False)
61
+
62
+ @classmethod
63
+ def create(cls) -> "UiServerRuntime":
64
+ webapp = Flask("app", static_folder=get_abs_path("./webui"), static_url_path="/")
65
+ webapp.secret_key = os.getenv("FLASK_SECRET_KEY") or secrets.token_hex(32)
66
+
67
+ if os.getenv("HF_SPACE") == "true":
68
+ from werkzeug.middleware.proxy_fix import ProxyFix
69
+ webapp.wsgi_app = ProxyFix(webapp.wsgi_app, x_for=1, x_proto=1, x_host=1)
70
+
71
+ WerkzeugRequest.max_form_memory_size = UPLOAD_LIMIT_BYTES
72
+ webapp.config.update(
73
+ JSON_SORT_KEYS=False,
74
+ SESSION_COOKIE_NAME="session_" + runtime.get_runtime_id(),
75
+ SESSION_COOKIE_SAMESITE="Lax",
76
+ SESSION_PERMANENT=True,
77
+ PERMANENT_SESSION_LIFETIME=timedelta(days=1),
78
+ MAX_CONTENT_LENGTH=int(
79
+ os.getenv("FLASK_MAX_CONTENT_LENGTH", str(UPLOAD_LIMIT_BYTES))
80
+ ),
81
+ MAX_FORM_MEMORY_SIZE=int(
82
+ os.getenv("FLASK_MAX_FORM_MEMORY_SIZE", str(UPLOAD_LIMIT_BYTES))
83
+ ),
84
+ )
85
+
86
+ lock = threading.RLock()
87
+
88
+ cors_allowed = "*" if os.getenv("HF_SPACE") == "true" else (lambda _origin, environ: validate_ws_origin(environ)[0])
89
+
90
+ socketio_server = socketio.AsyncServer(
91
+ async_mode="asgi",
92
+ namespaces="*",
93
+ cors_allowed_origins=cors_allowed,
94
+ logger=False,
95
+ engineio_logger=False,
96
+ ping_interval=25,
97
+ ping_timeout=30,
98
+ max_http_buffer_size=50 * 1024 * 1024,
99
+ )
100
+
101
+ ws_manager = WsManager(socketio_server, lock)
102
+ set_shared_ws_manager(ws_manager)
103
+
104
+ server_runtime = cls(
105
+ webapp=webapp,
106
+ socketio_server=socketio_server,
107
+ ws_manager=ws_manager,
108
+ lock=lock,
109
+ settings_snapshot={},
110
+ )
111
+ server_runtime.refresh_runtime_settings()
112
+ return server_runtime
113
+
114
+ def refresh_runtime_settings(self) -> None:
115
+ self.settings_snapshot = settings_helper.get_settings()
116
+ settings_helper.set_runtime_settings_snapshot(self.settings_snapshot)
117
+ self.ws_manager.set_server_restart_broadcast(
118
+ self.settings_snapshot.get("websocket_server_restart_enabled", True)
119
+ )
120
+
121
+ def register_http_routes(self) -> None:
122
+ if self._routes_registered:
123
+ return
124
+
125
+ handlers = UiRouteHandlers(self)
126
+ self._route_handlers = handlers
127
+ self.webapp.add_url_rule(
128
+ "/login",
129
+ "login_handler",
130
+ handlers.login_handler,
131
+ methods=["GET", "POST"],
132
+ )
133
+ self.webapp.add_url_rule(
134
+ "/logout",
135
+ "logout_handler",
136
+ handlers.logout_handler,
137
+ methods=["GET"],
138
+ )
139
+ self.webapp.add_url_rule(
140
+ "/",
141
+ "serve_index",
142
+ handlers.serve_index,
143
+ methods=["GET"],
144
+ )
145
+ self.webapp.add_url_rule(
146
+ "/plugins/<plugin_name>/<path:asset_path>",
147
+ "serve_builtin_plugin_asset",
148
+ handlers.serve_builtin_plugin_asset,
149
+ methods=["GET"],
150
+ )
151
+ self.webapp.add_url_rule(
152
+ "/usr/plugins/<plugin_name>/<path:asset_path>",
153
+ "serve_plugin_asset",
154
+ handlers.serve_plugin_asset,
155
+ methods=["GET"],
156
+ )
157
+ self.webapp.add_url_rule(
158
+ "/extensions/webui/<path:asset_path>",
159
+ "serve_extension_asset",
160
+ handlers.serve_extension_asset,
161
+ methods=["GET"],
162
+ )
163
+ self._routes_registered = True
164
+
165
+ def register_transport_handlers(self) -> None:
166
+ if self._transport_registered:
167
+ return
168
+ register_api_route(self.webapp, self.lock)
169
+ register_ws_namespace(
170
+ self.socketio_server,
171
+ self.webapp,
172
+ self.lock,
173
+ manager=self.ws_manager,
174
+ )
175
+ self._transport_registered = True
176
+
177
+ def build_asgi_app(self, startup_monitor: StartupMonitor):
178
+ with startup_monitor.stage("wsgi.middleware.create"):
179
+ wsgi_app = WSGIMiddleware(self.webapp)
180
+
181
+ with startup_monitor.stage("mcp.proxy.init"):
182
+ mcp_app = mcp_server.DynamicMcpProxy.get_instance()
183
+
184
+ with startup_monitor.stage("a2a.proxy.init"):
185
+ a2a_app = fasta2a_server.DynamicA2AProxy.get_instance()
186
+
187
+ async def health_endpoint(request):
188
+ return JSONResponse({"status": "ok", "service": "agent-zero"})
189
+
190
+ async def api_docs_endpoint(request):
191
+ from api.api_docs import ApiDocs
192
+ handler = ApiDocs(self.webapp, self.lock)
193
+ result = await handler.process({}, None)
194
+ return JSONResponse(result)
195
+
196
+ with startup_monitor.stage("starlette.app.create"):
197
+ starlette_app = Starlette(
198
+ routes=[
199
+ Route("/health", endpoint=health_endpoint, methods=["GET"]),
200
+ Route("/api-docs", endpoint=api_docs_endpoint, methods=["GET"]),
201
+ Mount("/mcp", app=mcp_app),
202
+ Mount("/a2a", app=a2a_app),
203
+ Mount("/", app=wsgi_app),
204
+ ],
205
+ lifespan=startup_monitor.lifespan(),
206
+ )
207
+
208
+ with startup_monitor.stage("socketio.asgi.create"):
209
+ return ASGIApp(self.socketio_server, other_asgi_app=starlette_app)
210
+
211
+ def access_log_enabled(self) -> bool:
212
+ return self.settings_snapshot.get("uvicorn_access_logs_enabled", False)
213
+
214
+
215
+ class UiRouteHandlers:
216
+ def __init__(self, runtime_state: UiServerRuntime) -> None:
217
+ self.runtime = runtime_state
218
+
219
+ @extensible
220
+ async def login_handler(self):
221
+ error = None
222
+ if request.method == "POST":
223
+ user = dotenv.get_dotenv_value("AUTH_LOGIN")
224
+ password = dotenv.get_dotenv_value("AUTH_PASSWORD")
225
+
226
+ if request.form["username"] == user and request.form["password"] == password:
227
+ session["authentication"] = login.get_credentials_hash()
228
+ return redirect(url_for("serve_index"))
229
+ else:
230
+ await asyncio.sleep(1)
231
+ error = "Invalid Credentials. Please try again."
232
+
233
+ login_page_content = files.read_file("webui/login.html")
234
+ return render_template_string(login_page_content, error=error)
235
+
236
+ @extensible
237
+ async def logout_handler(self):
238
+ session.pop("authentication", None)
239
+ return redirect(url_for("login_handler"))
240
+
241
+ @requires_auth
242
+ @extensible
243
+ async def serve_index(self):
244
+ try:
245
+ gitinfo = git.get_git_info()
246
+ except Exception:
247
+ gitinfo = {
248
+ "version": "unknown",
249
+ "commit_time": "unknown",
250
+ }
251
+
252
+ index = files.read_file("webui/index.html")
253
+ return files.replace_placeholders_text(
254
+ _content=index,
255
+ version_no=gitinfo["version"],
256
+ version_time=gitinfo["commit_time"],
257
+ runtime_id=runtime.get_runtime_id(),
258
+ runtime_is_development=("true" if runtime.is_development() else "false"),
259
+ logged_in=("true" if login.get_credentials_hash() else "false"),
260
+ )
261
+
262
+ @requires_auth
263
+ async def serve_builtin_plugin_asset(self, plugin_name, asset_path):
264
+ return await self._serve_plugin_asset(plugin_name, asset_path)
265
+
266
+ @requires_auth
267
+ async def serve_plugin_asset(self, plugin_name, asset_path):
268
+ return await self._serve_plugin_asset(plugin_name, asset_path)
269
+
270
+ @requires_auth
271
+ async def serve_extension_asset(self, asset_path):
272
+ exts = files.get_abs_path("extensions/webui")
273
+ path = files.get_abs_path(exts, asset_path)
274
+ if not files.is_in_dir(path, exts):
275
+ return Response("Access denied", 403)
276
+ return send_file(path)
277
+
278
+ @extensible
279
+ async def _serve_plugin_asset(self, plugin_name, asset_path):
280
+ from helpers import plugins
281
+
282
+ plugin_dir = plugins.find_plugin_dir(plugin_name)
283
+ if not plugin_dir:
284
+ return Response("Plugin not found", 404)
285
+
286
+ try:
287
+ asset_file = files.get_abs_path(plugin_dir, asset_path)
288
+ webui_dir = files.get_abs_path(plugin_dir, "webui")
289
+ webui_extensions_dir = files.get_abs_path(plugin_dir, "extensions/webui")
290
+
291
+ if not files.is_in_dir(str(asset_file), str(webui_dir)) and not files.is_in_dir(
292
+ str(asset_file), str(webui_extensions_dir)
293
+ ):
294
+ return Response("Access denied", 403)
295
+
296
+ if not files.is_file(asset_file):
297
+ return Response("Asset not found", 404)
298
+
299
+ return send_file(str(asset_file))
300
+ except Exception as e:
301
+ PrintStyle.error(f"Error serving plugin asset: {e}")
302
+ return Response("Error serving asset", 500)
patches/run_ui.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import timedelta
2
+ import os
3
+ import secrets
4
+ import threading
5
+ from flask import Flask, request, Response, session
6
+ from werkzeug.middleware.proxy_fix import ProxyFix
7
+ import initialize
8
+ from python.helpers import files, git, mcp_server, fasta2a_server
9
+ from python.helpers.files import get_abs_path
10
+ from python.helpers import runtime, dotenv, process
11
+ from python.helpers.extract_tools import load_classes_from_folder
12
+ from python.helpers.api import ApiHandler
13
+ from python.helpers.print_style import PrintStyle
14
+
15
+ # initialize the internal Flask server
16
+ webapp = Flask("app", static_folder=get_abs_path("./webui"), static_url_path="/")
17
+ webapp.secret_key = os.getenv("FLASK_SECRET_KEY") or secrets.token_hex(32)
18
+
19
+ # HF Space reverse proxy support
20
+ if os.getenv("HF_SPACE") == "true":
21
+ webapp.wsgi_app = ProxyFix(webapp.wsgi_app, x_for=1, x_proto=1, x_host=1)
22
+
23
+ webapp.config.update(
24
+ JSON_SORT_KEYS=False,
25
+ SESSION_COOKIE_NAME="session_" + runtime.get_runtime_id(),
26
+ SESSION_COOKIE_SAMESITE="Lax",
27
+ SESSION_PERMANENT=True,
28
+ PERMANENT_SESSION_LIFETIME=timedelta(days=1)
29
+ )
30
+
31
+ lock = threading.Lock()
32
+
33
+ @webapp.route("/", methods=["GET"])
34
+ async def serve_index():
35
+ index = files.read_file("webui/index.html")
36
+ return index
37
+
38
+ @webapp.route("/health", methods=["GET"])
39
+ async def health():
40
+ return {"status": "ok", "service": "agent-zero"}
41
+
42
+ @webapp.route("/api-docs", methods=["GET"])
43
+ async def api_docs():
44
+ from python.api.api_docs import ApiDocs
45
+ handler = ApiDocs(webapp, lock)
46
+ result = await handler.process({}, request)
47
+ return result
48
+
49
+ def run():
50
+ from werkzeug.serving import make_server
51
+ from werkzeug.middleware.dispatcher import DispatcherMiddleware
52
+ from a2wsgi import ASGIMiddleware
53
+
54
+ port = runtime.get_web_ui_port()
55
+ host = "0.0.0.0"
56
+
57
+ # register API handlers
58
+ handlers = load_classes_from_folder("python/api", "*.py", ApiHandler)
59
+ for handler in handlers:
60
+ name = handler.__module__.split(".")[-1]
61
+ instance = handler(webapp, lock)
62
+ async def handler_wrap(h=instance): return await h.handle_request(request=request)
63
+ webapp.add_url_rule(f"/api/{name}", f"/{name}", handler_wrap, methods=handler.get_methods())
64
+
65
+ middleware_routes = {
66
+ "/mcp": ASGIMiddleware(app=mcp_server.DynamicMcpProxy.get_instance()),
67
+ "/a2a": ASGIMiddleware(app=fasta2a_server.DynamicA2AProxy.get_instance()),
68
+ }
69
+
70
+ app = DispatcherMiddleware(webapp, middleware_routes)
71
+ server = make_server(host=host, port=port, app=app, threaded=True)
72
+ process.set_server(server)
73
+
74
+ # Init tasks
75
+ initialize.initialize_chats().result_sync()
76
+ initialize.initialize_mcp()
77
+ initialize.initialize_job_loop()
78
+
79
+ PrintStyle().debug(f"Starting server at http://{host}:{port} ...")
80
+ server.serve_forever()
81
+
82
+ if __name__ == "__main__":
83
+ runtime.initialize()
84
+ dotenv.load_dotenv()
85
+ run()
start_hf.sh CHANGED
@@ -4,22 +4,31 @@ set -e
4
  REPO_URL="https://github.com/JsonLord/agent-zero.git"
5
  CLONE_DIR="/home/user/app/agent-zero-clone"
6
  APP_DIR="/home/user/app"
 
7
 
8
- echo "Cloning Agent-Zero (main branch)..."
9
  if [ -d "$CLONE_DIR" ]; then
10
  rm -rf "$CLONE_DIR"
11
  fi
12
 
13
  git clone "$REPO_URL" "$CLONE_DIR"
14
 
15
- echo "Applying adaptations..."
 
16
  cp -rn "$CLONE_DIR"/* "$APP_DIR/" 2>/dev/null || true
17
 
18
- cp -f /home/user/app/helpers/api.py "$APP_DIR/helpers/api.py"
19
- cp -f /home/user/app/helpers/runtime.py "$APP_DIR/helpers/runtime.py"
20
- cp -f /home/user/app/helpers/settings.py "$APP_DIR/helpers/settings.py"
21
- cp -f /home/user/app/helpers/ui_server.py "$APP_DIR/helpers/ui_server.py"
22
- cp -f /home/user/app/api/api_docs.py "$APP_DIR/api/api_docs.py"
 
 
 
 
 
 
 
23
 
24
  echo "Installing dependencies..."
25
  if command -v uv > /dev/null; then
@@ -33,4 +42,5 @@ export PORT=7860
33
  export HOST=0.0.0.0
34
 
35
  echo "Starting Agent-Zero..."
 
36
  python run_ui.py
 
4
  REPO_URL="https://github.com/JsonLord/agent-zero.git"
5
  CLONE_DIR="/home/user/app/agent-zero-clone"
6
  APP_DIR="/home/user/app"
7
+ PATCHES_DIR="/home/user/app/patches"
8
 
9
+ echo "Cloning Agent-Zero (main branch) into temporary directory..."
10
  if [ -d "$CLONE_DIR" ]; then
11
  rm -rf "$CLONE_DIR"
12
  fi
13
 
14
  git clone "$REPO_URL" "$CLONE_DIR"
15
 
16
+ echo "Applying Agent-Zero codebase to app directory..."
17
+ # Copy everything from the clone into the current directory, but don't overwrite local patches
18
  cp -rn "$CLONE_DIR"/* "$APP_DIR/" 2>/dev/null || true
19
 
20
+ echo "Applying patches from $PATCHES_DIR..."
21
+ # Forcefully apply our adaptations over the cloned files
22
+ if [ -d "$PATCHES_DIR/helpers" ]; then
23
+ cp -f "$PATCHES_DIR/helpers/"*.py "$APP_DIR/helpers/"
24
+ fi
25
+ if [ -f "$PATCHES_DIR/run_ui.py" ]; then
26
+ cp -f "$PATCHES_DIR/run_ui.py" "$APP_DIR/run_ui.py"
27
+ fi
28
+ if [ -d "$PATCHES_DIR/api" ]; then
29
+ mkdir -p "$APP_DIR/api"
30
+ cp -f "$PATCHES_DIR/api/"*.py "$APP_DIR/api/"
31
+ fi
32
 
33
  echo "Installing dependencies..."
34
  if command -v uv > /dev/null; then
 
42
  export HOST=0.0.0.0
43
 
44
  echo "Starting Agent-Zero..."
45
+ # The run_ui.py in the root has been patched with HF adaptations
46
  python run_ui.py