BIG PYTHON REFACTOR

Python scripts moved out of python/ folder to root to be unified with plugins + frontend extension around api calls

frdel committed Mar 5, 2026 at 17:28 UTC d02dda3667de4cd092afa657c42b15f3346c0c4e
326 files changed +1096 -862
AGENTS.md
+4 -4
@@ -105,8 +105,8 @@ Key Files:
105 ## Development Patterns & Conventions
106
107 ### Backend (Python)
108 -- Context Access: Use from agent import AgentContext, AgentContextType (not python.helpers.context).
109 -- Communication: Use mq from python.helpers.messages to log proactive UI messages:
108 +- Context Access: Use from agent import AgentContext, AgentContextType (not helpers.context).
109 +- Communication: Use mq from helpers.messages to log proactive UI messages:
110 mq.log_user_message(context.id, "Message", source="Plugin")
111 - API Handlers: Derive from ApiHandler in python/helpers/api.py.
112 - Extensions: Use the extension framework in python/helpers/extension.py for lifecycle hooks.
@@ -164,7 +164,7 @@ Key Files:
164
165 ### API Handler (Good)
166 ```python
167 -from python.helpers.api import ApiHandler, Request, Response
167 +from helpers.api import ApiHandler, Request, Response
168
169 class MyHandler(ApiHandler):
170 async def process(self, input: dict, request: Request) -> dict | Response:
@@ -186,7 +186,7 @@ export const store = createStore("myStore", {
186
187 ### Tool Definition (Good)
188 ```python
189 -from python.helpers.tool import Tool, ToolResult
189 +from helpers.tool import Tool, ToolResult
190
191 class MyTool(Tool):
192 async def execute(self, arg1: str):
agent.py
+15 -16
@@ -7,7 +7,7 @@ from typing import Any, Awaitable, Coroutine, Dict, Literal
7 from enum import Enum
8 import models
9
10 -from python.helpers import (
10 +from helpers import (
11 extract_tools,
12 files,
13 errors,
@@ -17,22 +17,21 @@ from python.helpers import (
17 dirty_json,
18 subagents,
19 )
20 -from python.helpers import extension
21 -from python.helpers.print_style import PrintStyle
20 +from helpers import extension
21 +from helpers.print_style import PrintStyle
22
23 from langchain_core.prompts import (
24 ChatPromptTemplate,
25 )
26 from langchain_core.messages import SystemMessage, BaseMessage
27
28 -import python.helpers.log as Log
29 -from python.helpers.dirty_json import DirtyJson
30 -from python.helpers.defer import DeferredTask
28 +import helpers.log as Log
29 +from helpers.dirty_json import DirtyJson
30 +from helpers.defer import DeferredTask
31 from typing import Callable
32 -from python.helpers.localization import Localization
33 -from python.helpers.extension import call_extensions, extensible
34 -from python.helpers.errors import RepairableException, InterventionException, HandledException
35 -
32 +from helpers.localization import Localization
33 +from helpers.extension import call_extensions, extensible
34 +from helpers.errors import RepairableException, InterventionException, HandledException
35
36 class AgentContextType(Enum):
37 USER = "user"
@@ -147,7 +146,7 @@ class AgentContext:
146 @classmethod
147 def get_notification_manager(cls):
148 if cls._notification_manager is None:
150 - from python.helpers.notification import NotificationManager # type: ignore
149 + from helpers.notification import NotificationManager # type: ignore
150
151 cls._notification_manager = NotificationManager()
152 return cls._notification_manager
@@ -177,7 +176,7 @@ class AgentContext:
176 # recursive is not used now, prepared for context hierarchy
177 self.output_data[key] = value
178
180 - @extensible
179 + # @extensible
180 def output(self):
181 return {
182 "id": self.id,
@@ -859,7 +858,7 @@ class Agent:
858
859 # Try getting tool from MCP first
860 try:
862 - import python.helpers.mcp_handler as mcp_helper
861 + import helpers.mcp_handler as mcp_helper
862
863 mcp_tool_candidate = mcp_helper.MCPConfig.get_instance().get_tool(
864 self, tool_name
@@ -969,13 +968,13 @@ class Agent:
968 loop_data: LoopData | None,
969 **kwargs,
970 ):
972 - from python.tools.unknown import Unknown
973 - from python.helpers.tool import Tool
971 + from tools.unknown import Unknown
972 + from helpers.tool import Tool
973
974 classes = []
975
976 # search for tools in agent's folder hierarchy
978 - paths = subagents.get_paths(self, "tools", name + ".py", default_root="python")
977 + paths = subagents.get_paths(self, "tools", name + ".py")
978
979 for path in paths:
980 try:
agents/_example/extensions/agent_init/_10_example_extension.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2
3 # this is an example extension that renames the current agent when initialized
4 # see /extensions folder for all available extension points
agents/_example/tools/example_tool.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2
3 # this is an example tool class
4 # don't forget to include instructions in the system prompt by creating
agents/_example/tools/response.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2
3 # example of a tool redefinition
4 # the original response tool is in python/tools/response.py
api/agents.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers import subagents
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers import subagents
3
4
5 class Agents(ApiHandler):
api/api_files_get.py renamed
+3 -3
@@ -1,8 +1,8 @@
1 import base64
2 import os
3 -from python.helpers.api import ApiHandler, Request, Response
4 -from python.helpers import files
5 -from python.helpers.print_style import PrintStyle
3 +from helpers.api import ApiHandler, Request, Response
4 +from helpers import files
5 +from helpers.print_style import PrintStyle
6 import json
7
8
api/api_log_get.py renamed
+1 -1
@@ -1,5 +1,5 @@
1 from agent import AgentContext
2 -from python.helpers.api import ApiHandler, Request, Response
2 +from helpers.api import ApiHandler, Request, Response
3
4
5 class ApiLogGet(ApiHandler):
api/api_message.py renamed
+5 -5
@@ -2,11 +2,11 @@ import base64
2 import os
3 from datetime import datetime, timedelta
4 from agent import AgentContext, UserMessage, AgentContextType
5 -from python.helpers.api import ApiHandler, Request, Response
6 -from python.helpers import files, projects
7 -from python.helpers.print_style import PrintStyle
8 -from python.helpers.projects import activate_project
9 -from python.helpers.security import safe_filename
5 +from helpers.api import ApiHandler, Request, Response
6 +from helpers import files, projects
7 +from helpers.print_style import PrintStyle
8 +from helpers.projects import activate_project
9 +from helpers.security import safe_filename
10 from initialize import initialize_agent
11 import threading
12
api/api_reset_chat.py renamed
+3 -3
@@ -1,7 +1,7 @@
1 from agent import AgentContext
2 -from python.helpers.api import ApiHandler, Request, Response
3 -from python.helpers.print_style import PrintStyle
4 -from python.helpers import persist_chat
2 +from helpers.api import ApiHandler, Request, Response
3 +from helpers.print_style import PrintStyle
4 +from helpers import persist_chat
5 import json
6
7
api/api_terminate_chat.py renamed
+3 -3
@@ -1,7 +1,7 @@
1 from agent import AgentContext
2 -from python.helpers.api import ApiHandler, Request, Response
3 -from python.helpers.persist_chat import remove_chat
4 -from python.helpers.print_style import PrintStyle
2 +from helpers.api import ApiHandler, Request, Response
3 +from helpers.persist_chat import remove_chat
4 +from helpers.print_style import PrintStyle
5 import json
6
7
api/backup_create.py renamed
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response, send_file
2 -from python.helpers.backup import BackupService
3 -from python.helpers.persist_chat import save_tmp_chats
1 +from helpers.api import ApiHandler, Request, Response, send_file
2 +from helpers.backup import BackupService
3 +from helpers.persist_chat import save_tmp_chats
4
5
6 class BackupCreate(ApiHandler):
api/backup_get_defaults.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers.backup import BackupService
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers.backup import BackupService
3
4
5 class BackupGetDefaults(ApiHandler):
api/backup_inspect.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers.backup import BackupService
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers.backup import BackupService
3 from werkzeug.datastructures import FileStorage
4
5
api/backup_preview_grouped.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers.backup import BackupService
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers.backup import BackupService
3 from typing import Dict, Any
4
5
api/backup_restore.py renamed
+3 -3
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2 from werkzeug.datastructures import FileStorage
3 -from python.helpers.backup import BackupService
4 -from python.helpers.persist_chat import load_tmp_chats
3 +from helpers.backup import BackupService
4 +from helpers.persist_chat import load_tmp_chats
5 import json
6
7
api/backup_restore_preview.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2 from werkzeug.datastructures import FileStorage
3 -from python.helpers.backup import BackupService
3 +from helpers.backup import BackupService
4 import json
5
6
api/backup_test.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers.backup import BackupService
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers.backup import BackupService
3
4
5 class BackupTest(ApiHandler):
api/banners.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers.extension import call_extensions
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers.extension import call_extensions
3
4
5 class GetBanners(ApiHandler):
api/cache_reset.py new
+35
@@ -0,0 +1,35 @@
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import cache
3 +
4 +
5 +class CacheReset(ApiHandler):
6 + @classmethod
7 + def requires_auth(cls) -> bool:
8 + return False
9 +
10 + @classmethod
11 + def requires_csrf(cls) -> bool:
12 + return False
13 +
14 + @classmethod
15 + def requires_api_key(cls) -> bool:
16 + return False
17 +
18 + @classmethod
19 + def requires_loopback(cls) -> bool:
20 + return True
21 +
22 + @classmethod
23 + def get_methods(cls) -> list[str]:
24 + return ["GET", "POST"]
25 +
26 + async def process(self, input: dict, request: Request) -> dict | Response:
27 + areas = input.get("areas", [])
28 +
29 + if not areas:
30 + cache.clear_all()
31 + else:
32 + for area in areas:
33 + cache.clear(area)
34 +
35 + return {"ok": True}
api/chat_create.py renamed
+3 -3
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4 -from python.helpers import settings, projects, guids
4 +from helpers import settings, projects, guids
5 from agent import AgentContext
6
7
@@ -26,7 +26,7 @@ class CreateChat(ApiHandler):
26 new_context.set_output_data(projects.CONTEXT_DATA_KEY_PROJECT, current_data_2)
27
28 # New context should appear in other tabs' chat lists via state_push.
29 - from python.helpers.state_monitor_integration import mark_dirty_all
29 + from helpers.state_monitor_integration import mark_dirty_all
30 mark_dirty_all(reason="api.chat_create.CreateChat")
31
32 return {
api/chat_export.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2
3 -from python.helpers import persist_chat
3 +from helpers import persist_chat
4
5 class ExportChat(ApiHandler):
6 async def process(self, input: Input, request: Request) -> Output:
api/chat_files_path_get.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import files, projects, settings
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import files, projects, settings
3
4
5 class GetChatFilesPath(ApiHandler):
api/chat_load.py renamed
+2 -2
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4 -from python.helpers import persist_chat
4 +from helpers import persist_chat
5
6 class LoadChats(ApiHandler):
7 async def process(self, input: Input, request: Request) -> Output:
api/chat_remove.py renamed
+4 -4
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2 from agent import AgentContext
3 -from python.helpers import persist_chat
4 -from python.helpers.task_scheduler import TaskScheduler
3 +from helpers import persist_chat
4 +from helpers.task_scheduler import TaskScheduler
5
6
7 class RemoveChat(ApiHandler):
@@ -26,7 +26,7 @@ class RemoveChat(ApiHandler):
26 await scheduler.remove_task_by_uuid(task.uuid)
27
28 # Context removal affects global chat/task lists in all tabs.
29 - from python.helpers.state_monitor_integration import mark_dirty_all
29 + from helpers.state_monitor_integration import mark_dirty_all
30 mark_dirty_all(reason="api.chat_remove.RemoveChat")
31
32 return {
api/chat_reset.py renamed
+4 -4
@@ -1,8 +1,8 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4 -from python.helpers import persist_chat
5 -from python.helpers.task_scheduler import TaskScheduler
4 +from helpers import persist_chat
5 +from helpers.task_scheduler import TaskScheduler
6
7
8 class Reset(ApiHandler):
@@ -19,7 +19,7 @@ class Reset(ApiHandler):
19 persist_chat.remove_msg_files(ctxid)
20
21 # Reset updates context metadata (log guid/version) and must refresh other tabs' lists.
22 - from python.helpers.state_monitor_integration import mark_dirty_all
22 + from helpers.state_monitor_integration import mark_dirty_all
23 mark_dirty_all(reason="api.chat_reset.Reset")
24
25 return {
api/csrf_token.py renamed
+3 -3
@@ -1,6 +1,6 @@
1 import secrets
2 from urllib.parse import urlparse
3 -from python.helpers.api import (
3 +from helpers.api import (
4 ApiHandler,
5 Input,
6 Output,
@@ -8,7 +8,7 @@ from python.helpers.api import (
8 Response,
9 session,
10 )
11 -from python.helpers import runtime, dotenv, login
11 +from helpers import runtime, dotenv, login
12 import fnmatch
13
14 ALLOWED_ORIGINS_KEY = "ALLOWED_ORIGINS"
@@ -104,7 +104,7 @@ class GetCsrfToken(ApiHandler):
104
105 # always allow tunnel url if running
106 try:
107 - from python.api.tunnel_proxy import process as tunnel_api_process
107 + from api.tunnel_proxy import process as tunnel_api_process
108
109 tunnel = await tunnel_api_process({"action": "get"})
110 if tunnel and isinstance(tunnel, dict) and tunnel["success"]:
api/ctx_window_get.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2
3 -from python.helpers import tokens
3 +from helpers import tokens
4
5
6 class GetCtxWindow(ApiHandler):
api/delete_work_dir_file.py renamed
+4 -4
@@ -1,9 +1,9 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4 -from python.helpers.file_browser import FileBrowser
5 -from python.helpers import files, runtime
6 -from python.api import get_work_dir_files
4 +from helpers.file_browser import FileBrowser
5 +from helpers import files, runtime
6 +from api import get_work_dir_files
7
8
9 class DeleteWorkDirFile(ApiHandler):
api/download_work_dir_file.py renamed
+3 -3
@@ -4,9 +4,9 @@ import mimetypes
4 import os
5
6 from flask import Response
7 -from python.helpers.api import ApiHandler, Input, Output, Request
8 -from python.helpers import files, runtime
9 -from python.api import file_info
7 +from helpers.api import ApiHandler, Input, Output, Request
8 +from helpers import files, runtime
9 +from api import file_info
10 from urllib.parse import quote
11
12
api/edit_work_dir_file.py renamed
+3 -3
@@ -1,9 +1,9 @@
1 import mimetypes
2 import os
3
4 -from python.helpers.api import ApiHandler, Input, Output, Request
5 -from python.helpers.file_browser import FileBrowser
6 -from python.helpers import runtime, files
4 +from helpers.api import ApiHandler, Input, Output, Request
5 +from helpers.file_browser import FileBrowser
6 +from helpers import runtime, files
7
8 MAX_EDIT_FILE_SIZE = 1024 * 1024
9 BINARY_SAMPLE_SIZE = 10 * 1024
api/file_info.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 import os
2 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
3 -from python.helpers import files, runtime
2 +from helpers.api import ApiHandler, Input, Output, Request, Response
3 +from helpers import files, runtime
4 from typing import TypedDict
5
6 class FileInfoApi(ApiHandler):
api/get_work_dir_files.py renamed
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers.file_browser import FileBrowser
3 -from python.helpers import runtime, files
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers.file_browser import FileBrowser
3 +from helpers import runtime, files
4
5 class GetWorkDirFiles(ApiHandler):
6
api/health.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import errors, git
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import errors, git
3
4 class HealthCheck(ApiHandler):
5
api/history_get.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3
4 class GetHistory(ApiHandler):
api/image_get.py renamed
+2 -2
@@ -1,7 +1,7 @@
1 import base64
2 import os
3 -from python.helpers.api import ApiHandler, Request, Response, send_file
4 -from python.helpers import files, runtime
3 +from helpers.api import ApiHandler, Request, Response, send_file
4 +from helpers import files, runtime
5 import io
6 from mimetypes import guess_type
7
api/load_webui_extensions.py renamed
+3 -3
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import plugins
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import extension
3
4
5 class LoadWebuiExtensions(ApiHandler):
@@ -15,6 +15,6 @@ class LoadWebuiExtensions(ApiHandler):
15 if not extension_point:
16 return Response(status=400, response="Missing extension_point")
17
18 - exts = plugins.get_webui_extensions(agent=None, extension_point=extension_point, filters=filters)
18 + exts = extension.get_webui_extensions(agent=None, extension_point=extension_point, filters=filters)
19
20 return {"extensions": exts or []}
api/logout.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler, Request, session
1 +from helpers.api import ApiHandler, Request, session
2
3
4 class ApiLogout(ApiHandler):
api/mcp_server_get_detail.py renamed
+2 -2
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2 from typing import Any
3
4 -from python.helpers.mcp_handler import MCPConfig
4 +from helpers.mcp_handler import MCPConfig
5
6
7 class McpServerGetDetail(ApiHandler):
api/mcp_server_get_log.py renamed
+2 -2
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2 from typing import Any
3
4 -from python.helpers.mcp_handler import MCPConfig
4 +from helpers.mcp_handler import MCPConfig
5
6
7 class McpServerGetLog(ApiHandler):
api/mcp_servers_apply.py renamed
+3 -3
@@ -1,10 +1,10 @@
1 import time
2 -from python.helpers.api import ApiHandler, Request, Response
2 +from helpers.api import ApiHandler, Request, Response
3
4 from typing import Any
5
6 -from python.helpers.mcp_handler import MCPConfig
7 -from python.helpers.settings import set_settings_delta
6 +from helpers.mcp_handler import MCPConfig
7 +from helpers.settings import set_settings_delta
8
9
10 class McpServersApply(ApiHandler):
api/mcp_servers_status.py renamed
+2 -2
@@ -1,8 +1,8 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 from typing import Any
4
5 -from python.helpers.mcp_handler import MCPConfig
5 +from helpers.mcp_handler import MCPConfig
6
7
8 class McpServersStatuss(ApiHandler):
api/message.py renamed
+4 -4
@@ -1,10 +1,10 @@
1 from agent import AgentContext, UserMessage
2 -from python.helpers.api import ApiHandler, Request, Response
2 +from helpers.api import ApiHandler, Request, Response
3
4 -from python.helpers import files, extension, message_queue as mq
4 +from helpers import files, extension, message_queue as mq
5 import os
6 -from python.helpers.security import safe_filename
7 -from python.helpers.defer import DeferredTask
6 +from helpers.security import safe_filename
7 +from helpers.defer import DeferredTask
8
9
10 class Message(ApiHandler):
api/message_async.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 from agent import AgentContext
2 -from python.helpers.defer import DeferredTask
3 -from python.api.message import Message
2 +from helpers.defer import DeferredTask
3 +from api.message import Message
4
5
6 class MessageAsync(Message):
api/message_queue_add.py renamed
+3 -3
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import message_queue as mq
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import message_queue as mq
3 from agent import AgentContext
4 -from python.helpers.state_monitor_integration import mark_dirty_for_context
4 +from helpers.state_monitor_integration import mark_dirty_for_context
5
6
7 class MessageQueueAdd(ApiHandler):
api/message_queue_remove.py renamed
+3 -3
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import message_queue as mq
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import message_queue as mq
3 from agent import AgentContext
4 -from python.helpers.state_monitor_integration import mark_dirty_for_context
4 +from helpers.state_monitor_integration import mark_dirty_for_context
5
6 class MessageQueueRemove(ApiHandler):
7 """Remove message(s) from queue."""
api/message_queue_send.py renamed
+3 -3
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import message_queue as mq
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import message_queue as mq
3 from agent import AgentContext
4 -from python.helpers.state_monitor_integration import mark_dirty_for_context
4 +from helpers.state_monitor_integration import mark_dirty_for_context
5
6 class MessageQueueSend(ApiHandler):
7 """Send queued message(s) immediately."""
api/notification_create.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler
1 +from helpers.api import ApiHandler
2 from flask import Request, Response
3 -from python.helpers.notification import NotificationManager, NotificationPriority, NotificationType
3 +from helpers.notification import NotificationManager, NotificationPriority, NotificationType
4
5
6 class NotificationCreate(ApiHandler):
api/notifications_clear.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler
1 +from helpers.api import ApiHandler
2 from flask import Request, Response
3 from agent import AgentContext
4
api/notifications_history.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler
1 +from helpers.api import ApiHandler
2 from flask import Request, Response
3 from agent import AgentContext
4
api/notifications_mark_read.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler
1 +from helpers.api import ApiHandler
2 from flask import Request, Response
3 from agent import AgentContext
4
api/nudge.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 class Nudge(ApiHandler):
4 async def process(self, input: dict, request: Request) -> dict | Response:
api/pause.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3
4 class Pause(ApiHandler):
api/plugins.py renamed
+2 -2
@@ -4,8 +4,8 @@ import subprocess
4 import sys
5 from datetime import datetime, timezone
6
7 -from python.helpers.api import ApiHandler, Request, Response
8 -from python.helpers import plugins, files
7 +from helpers.api import ApiHandler, Request, Response
8 +from helpers import plugins, files
9
10
11 class Plugins(ApiHandler):
api/plugins_list.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers import plugins
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers import plugins
3
4 class PluginsList(ApiHandler):
5 async def process(self, input: Input, request: Request) -> Output:
api/poll.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 -from python.helpers.state_snapshot import build_snapshot
3 +from helpers.state_snapshot import build_snapshot
4
5
6 class Poll(ApiHandler):
api/projects.py renamed
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
2 -from python.helpers import projects
3 -from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2 +from helpers import projects
3 +from helpers.notification import NotificationManager, NotificationType, NotificationPriority
4
5
6 class Projects(ApiHandler):
api/rename_work_dir_file.py renamed
+4 -4
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers.file_browser import FileBrowser
3 -from python.helpers import runtime
4 -from python.api import get_work_dir_files
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers.file_browser import FileBrowser
3 +from helpers import runtime
4 +from api import get_work_dir_files
5
6
7 class RenameWorkDirFile(ApiHandler):
api/restart.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 -from python.helpers import process
3 +from helpers import process
4
5 class Restart(ApiHandler):
6 async def process(self, input: dict, request: Request) -> dict | Response:
api/rfc.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 -from python.helpers import runtime
3 +from helpers import runtime
4
5 class RFC(ApiHandler):
6
api/scheduler_task_create.py renamed
+5 -5
@@ -1,11 +1,11 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers.task_scheduler import (
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers.task_scheduler import (
3 TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskSchedule,
4 serialize_task, parse_task_schedule, parse_task_plan, TaskType
5 )
6 -from python.helpers.projects import load_basic_project_data
7 -from python.helpers.localization import Localization
8 -from python.helpers.print_style import PrintStyle
6 +from helpers.projects import load_basic_project_data
7 +from helpers.localization import Localization
8 +from helpers.print_style import PrintStyle
9 import random
10
11
api/scheduler_task_delete.py renamed
+4 -4
@@ -1,8 +1,8 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers.task_scheduler import TaskScheduler, TaskState
3 -from python.helpers.localization import Localization
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers.task_scheduler import TaskScheduler, TaskState
3 +from helpers.localization import Localization
4 from agent import AgentContext
5 -from python.helpers import persist_chat
5 +from helpers import persist_chat
6
7
8 class SchedulerTaskDelete(ApiHandler):
api/scheduler_task_run.py renamed
+4 -4
@@ -1,7 +1,7 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers.task_scheduler import TaskScheduler, TaskState
3 -from python.helpers.print_style import PrintStyle
4 -from python.helpers.localization import Localization
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers.task_scheduler import TaskScheduler, TaskState
3 +from helpers.print_style import PrintStyle
4 +from helpers.localization import Localization
5
6
7 class SchedulerTaskRun(ApiHandler):
api/scheduler_task_update.py renamed
+3 -3
@@ -1,9 +1,9 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers.task_scheduler import (
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers.task_scheduler import (
3 TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskState,
4 serialize_task, parse_task_schedule, parse_task_plan
5 )
6 -from python.helpers.localization import Localization
6 +from helpers.localization import Localization
7
8
9 class SchedulerTaskUpdate(ApiHandler):
api/scheduler_tasks_list.py renamed
+4 -4
@@ -1,8 +1,8 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request
2 -from python.helpers.task_scheduler import TaskScheduler
1 +from helpers.api import ApiHandler, Input, Output, Request
2 +from helpers.task_scheduler import TaskScheduler
3 import traceback
4 -from python.helpers.print_style import PrintStyle
5 -from python.helpers.localization import Localization
4 +from helpers.print_style import PrintStyle
5 +from helpers.localization import Localization
6
7
8 class SchedulerTasksList(ApiHandler):
api/scheduler_tick.py renamed
+4 -4
@@ -1,9 +1,9 @@
1 from datetime import datetime
2
3 -from python.helpers.api import ApiHandler, Input, Output, Request
4 -from python.helpers.print_style import PrintStyle
5 -from python.helpers.task_scheduler import TaskScheduler
6 -from python.helpers.localization import Localization
3 +from helpers.api import ApiHandler, Input, Output, Request
4 +from helpers.print_style import PrintStyle
5 +from helpers.task_scheduler import TaskScheduler
6 +from helpers.localization import Localization
7
8
9 class SchedulerTick(ApiHandler):
api/settings_get.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 -from python.helpers import settings
3 +from helpers import settings
4
5 class GetSettings(ApiHandler):
6 async def process(self, input: dict, request: Request) -> dict | Response:
api/settings_set.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 -from python.helpers import settings
3 +from helpers import settings
4
5 from typing import Any
6
api/settings_workdir_file_structure.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 -from python.helpers import file_tree, files
3 +from helpers import file_tree, files
4
5
6 class SettingsWorkdirFileStructure(ApiHandler):
api/skills.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
2 -from python.helpers import runtime, skills, projects, files
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2 +from helpers import runtime, skills, projects, files
3
4
5 class Skills(ApiHandler):
api/skills_import.py renamed
+3 -3
@@ -5,9 +5,9 @@ import time
5 import uuid
6 from pathlib import Path
7
8 -from python.helpers.api import ApiHandler, Request, Response
9 -from python.helpers import files
10 -from python.helpers.skills_import import import_skills
8 +from helpers.api import ApiHandler, Request, Response
9 +from helpers import files
10 +from helpers.skills_import import import_skills
11 from werkzeug.datastructures import FileStorage
12 from werkzeug.utils import secure_filename
13
api/skills_import_preview.py renamed
+3 -3
@@ -5,9 +5,9 @@ import time
5 import uuid
6 from pathlib import Path
7
8 -from python.helpers.api import ApiHandler, Request, Response
9 -from python.helpers import files
10 -from python.helpers.skills_import import import_skills
8 +from helpers.api import ApiHandler, Request, Response
9 +from helpers import files
10 +from helpers.skills_import import import_skills
11 from werkzeug.datastructures import FileStorage
12 from werkzeug.utils import secure_filename
13
api/subagents.py renamed
+3 -3
@@ -1,9 +1,9 @@
1 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
2 -from python.helpers import subagents
1 +from helpers.api import ApiHandler, Input, Output, Request, Response
2 +from helpers import subagents
3 from typing import TYPE_CHECKING
4
5 if TYPE_CHECKING:
6 - from python.helpers import projects
6 + from helpers import projects
7
8 class Subagents(ApiHandler):
9 async def process(self, input: Input, request: Request) -> Output:
api/synthesize.py renamed
+2 -2
@@ -1,8 +1,8 @@
1 # api/synthesize.py
2
3 -from python.helpers.api import ApiHandler, Request, Response
3 +from helpers.api import ApiHandler, Request, Response
4
5 -from python.helpers import runtime, settings, kokoro_tts
5 +from helpers import runtime, settings, kokoro_tts
6
7 class Synthesize(ApiHandler):
8 async def process(self, input: dict, request: Request) -> dict | Response:
api/transcribe.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2
3 -from python.helpers import runtime, settings, whisper
3 +from helpers import runtime, settings, whisper
4
5 class Transcribe(ApiHandler):
6 async def process(self, input: dict, request: Request) -> dict | Response:
api/tunnel.py renamed
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import runtime
3 -from python.helpers.tunnel_manager import TunnelManager
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import runtime
3 +from helpers.tunnel_manager import TunnelManager
4
5 class Tunnel(ApiHandler):
6 async def process(self, input: dict, request: Request) -> dict | Response:
api/tunnel_proxy.py renamed
+4 -4
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import dotenv, runtime
3 -from python.helpers.tunnel_manager import TunnelManager
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import dotenv, runtime
3 +from helpers.tunnel_manager import TunnelManager
4 import requests
5
6
@@ -34,5 +34,5 @@ async def process(input: dict) -> dict | Response:
34 return {"error": str(e)}
35 else:
36 # forward to API handler directly
37 - from python.api.tunnel import process as local_process
37 + from api.tunnel import process as local_process
38 return await local_process(input)
api/upload.py renamed
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import files
3 -from python.helpers.security import safe_filename
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import files
3 +from helpers.security import safe_filename
4
5
6 class UploadFile(ApiHandler):
api/upload_work_dir_files.py renamed
+4 -4
@@ -1,9 +1,9 @@
1 import base64
2 from werkzeug.datastructures import FileStorage
3 -from python.helpers.api import ApiHandler, Request, Response
4 -from python.helpers.file_browser import FileBrowser
5 -from python.helpers import files, runtime
6 -from python.api import get_work_dir_files
3 +from helpers.api import ApiHandler, Request, Response
4 +from helpers.file_browser import FileBrowser
5 +from helpers import files, runtime
6 +from api import get_work_dir_files
7 import os
8
9
docs/developer/contributing-skills.md
+8 -8
@@ -37,16 +37,16 @@ Skills you create here can be used in any of these platforms!
37
38 ```bash
39 # Create a new skill interactively
40 -python -m python.helpers.skills_cli create my-skill-name
40 +python -m helpers.skills_cli create my-skill-name
41
42 # List all available skills
43 -python -m python.helpers.skills_cli list
43 +python -m helpers.skills_cli list
44
45 # Validate a skill
46 -python -m python.helpers.skills_cli validate my-skill-name
46 +python -m helpers.skills_cli validate my-skill-name
47
48 # Search skills
49 -python -m python.helpers.skills_cli search "keyword"
49 +python -m helpers.skills_cli search "keyword"
50 ```
51
52 ### Manual Creation
@@ -161,7 +161,7 @@ Ask yourself:
161
162 ```bash
163 # Using CLI
164 -python -m python.helpers.skills_cli create my-awesome-skill
164 +python -m helpers.skills_cli create my-awesome-skill
165
166 # Or manually
167 mkdir -p usr/skills/my-awesome-skill
@@ -225,7 +225,7 @@ This skill includes helper scripts:
225
226 ```bash
227 # Validate the skill
228 -python -m python.helpers.skills_cli validate my-awesome-skill
228 +python -m helpers.skills_cli validate my-awesome-skill
229
230 # Test in Agent Zero
231 # Start the agent and ask it to perform the task your skill handles
@@ -281,7 +281,7 @@ One skill = one expertise area. If your skill is getting too long, split it:
281
282 1. **Validate Structure:**
283 ```bash
284 - python -m python.helpers.skills_cli validate my-skill
284 + python -m helpers.skills_cli validate my-skill
285 ```
286
287 2. **Test Semantic Recall:**
@@ -304,7 +304,7 @@ One skill = one expertise area. If your skill is getting too long, split it:
304
305 2. **Create Your Skill:**
306 ```bash
307 - python -m python.helpers.skills_cli create my-skill
307 + python -m helpers.skills_cli create my-skill
308 # Edit usr/skills/my-skill/SKILL.md
309 ```
310
docs/developer/extensions.md
+4 -4
@@ -48,7 +48,7 @@ To create a custom extension:
48
49 ```python
50 # File: /agents/_example/extensions/agent_init/_10_example_extension.py
51 -from python.helpers.extension import Extension
51 +from helpers.extension import Extension
52
53 class ExampleExtension(Extension):
54 async def execute(self, **kwargs):
@@ -80,7 +80,7 @@ When a tool with the same name is requested, Agent Zero first checks for its exi
80
81 ```python
82 # File: /agents/_example/tools/response.py
83 -from python.helpers.tool import Tool, Response
83 +from helpers.tool import Tool, Response
84
85 # example of a tool redefinition
86 # the original response tool is in python/tools/response.py
@@ -145,8 +145,8 @@ When a prompt file is processed, Agent Zero automatically looks for a correspond
145 If you have a prompt file `agent.system.tools.md`, you can create `agent.system.tools.py` alongside it:
146
147 ```python
148 -from python.helpers.files import VariablesPlugin
149 -from python.helpers import files
148 +from helpers.files import VariablesPlugin
149 +from helpers import files
150
151 class Tools(VariablesPlugin):
152 def get_variables(self, file: str, backup_dirs: list[str] | None = None) -> dict[str, Any]:
docs/developer/notifications.md
+1 -1
@@ -10,7 +10,7 @@ Quick guide for using the notification system in Agent Zero.
10 Use `AgentNotification` helper methods anywhere in your Python code:
11
12 ```python
13 -from python.helpers.notification import AgentNotification
13 +from helpers.notification import AgentNotification
14
15 # Basic notifications
16 AgentNotification.info("Operation completed")
docs/developer/websockets.md
+1 -1
@@ -146,7 +146,7 @@ Handlers are discovered deterministically from `python/websocket_handlers/`:
146 Create handler modules under the appropriate namespace entry and inherit from `WebSocketHandler`.
147
148 ```python
149 -from python.helpers.websocket import WebSocketHandler
149 +from helpers.websocket import WebSocketHandler
150
151 class DashboardHandler(WebSocketHandler):
152 @classmethod
extensions/python/.gitkeep renamed
extensions/python/agent_Agent_handle_exception_end/_40_handle_intervention_exception.py renamed
+5 -5
@@ -1,10 +1,10 @@
1 from datetime import datetime, timezone
2 -from python.helpers.extension import Extension
2 +from helpers.extension import Extension
3 from agent import LoopData
4 -from python.helpers.localization import Localization
5 -from python.helpers.errors import InterventionException
6 -from python.helpers import errors
7 -from python.helpers.print_style import PrintStyle
4 +from helpers.localization import Localization
5 +from helpers.errors import InterventionException
6 +from helpers import errors
7 +from helpers.print_style import PrintStyle
8
9
10 class HandleInterventionException(Extension):
extensions/python/agent_Agent_handle_exception_end/_50_handle_repairable_exception.py renamed
+5 -5
@@ -1,10 +1,10 @@
1 from datetime import datetime, timezone
2 -from python.helpers.extension import Extension
2 +from helpers.extension import Extension
3 from agent import LoopData
4 -from python.helpers.localization import Localization
5 -from python.helpers.errors import RepairableException
6 -from python.helpers import errors
7 -from python.helpers.print_style import PrintStyle
4 +from helpers.localization import Localization
5 +from helpers.errors import RepairableException
6 +from helpers import errors
7 +from helpers.print_style import PrintStyle
8
9
10 class HandleRepairableException(Extension):
extensions/python/agent_Agent_handle_exception_end/_90_handle_critical_exception.py renamed
+4 -4
@@ -1,10 +1,10 @@
1 import asyncio
2
3 -from python.helpers.extension import Extension
4 -from python.helpers.print_style import PrintStyle
5 -from python.helpers import errors
3 +from helpers.extension import Extension
4 +from helpers.print_style import PrintStyle
5 +from helpers import errors
6
7 -from python.helpers.errors import HandledException
7 +from helpers.errors import HandledException
8
9
10 class HandleCriticalException(Extension):
extensions/python/agent_init/.gitkeep renamed
extensions/python/agent_init/_10_initial_message.py renamed
+3 -1
@@ -1,6 +1,6 @@
1 import json
2 from agent import LoopData
3 -from python.helpers.extension import Extension
3 +from helpers.extension import Extension
4
5
6 class InitialMessage(Extension):
@@ -10,6 +10,8 @@ class InitialMessage(Extension):
10 Add an initial greeting message when first user message is processed.
11 Called only once per session via _process_chain method.
12 """
13 + if not self.agent:
14 + return
15
16 # Only add initial message for main agent (A0), not subordinate agents
17 if self.agent.number != 0:
extensions/python/agent_init/_15_load_profile_settings.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 from initialize import initialize_agent
2 -from python.helpers import dirty_json, files, subagents, projects
3 -from python.helpers.extension import Extension
2 +from helpers import dirty_json, files, subagents, projects
3 +from helpers.extension import Extension
4
5
6 class LoadProfileSettings(Extension):
extensions/python/banners/.gitkeep renamed
extensions/python/banners/_10_unsecured_connection.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers import dotenv
1 +from helpers.extension import Extension
2 +from helpers import dotenv
3 import re
4
5
extensions/python/banners/_20_missing_api_key.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers import settings as settings_helper
1 +from helpers.extension import Extension
2 +from helpers import settings as settings_helper
3 import models
4
5
extensions/python/banners/_30_system_resources.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 import os
3 import psutil
4
extensions/python/before_main_llm_call/.gitkeep renamed
extensions/python/before_main_llm_call/_10_log_for_stream.py renamed
+7 -4
@@ -1,15 +1,18 @@
1 -from python.helpers import persist_chat, tokens
2 -from python.helpers.extension import Extension
1 +from helpers import persist_chat, tokens
2 +from helpers.extension import Extension
3 from agent import LoopData
4 import asyncio
5 -from python.helpers.log import LogItem
6 -from python.helpers import log
5 +from helpers.log import LogItem
6 +from helpers import log
7 import math
8
9
10 class LogForStream(Extension):
11
12 async def execute(self, loop_data: LoopData = LoopData(), text: str = "", **kwargs):
13 + if not self.agent:
14 + return
15 +
16 # create log message and store it in loop data temporary params
17 if "log_item_generating" not in loop_data.params_temporary:
18 loop_data.params_temporary["log_item_generating"] = (
extensions/python/error_format/.gitkeep renamed
extensions/python/error_format/_10_mask_errors.py renamed
+5 -2
@@ -1,10 +1,13 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers.secrets import get_secrets_manager
1 +from helpers.extension import Extension
2 +from helpers.secrets import get_secrets_manager
3
4
5 class MaskErrorSecrets(Extension):
6
7 async def execute(self, **kwargs):
8 + if not self.agent:
9 + return
10 +
11 # Get error data from kwargs
12 msg = kwargs.get("msg")
13 if not msg:
extensions/python/hist_add_before/.gitkeep renamed
extensions/python/hist_add_before/_10_mask_content.py renamed
+5 -2
@@ -1,10 +1,13 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers.secrets import get_secrets_manager
1 +from helpers.extension import Extension
2 +from helpers.secrets import get_secrets_manager
3
4
5 class MaskHistoryContent(Extension):
6
7 async def execute(self, **kwargs):
8 + if not self.agent:
9 + return
10 +
11 # Get content data from kwargs
12 content_data = kwargs.get("content_data")
13 if not content_data:
extensions/python/hist_add_tool_result/.gitkeep renamed
extensions/python/hist_add_tool_result/_90_save_tool_call_file.py renamed
+5 -2
@@ -1,12 +1,15 @@
1 from typing import Any
2 -from python.helpers.extension import Extension
3 -from python.helpers import files, persist_chat
2 +from helpers.extension import Extension
3 +from helpers import files, persist_chat
4 import os, re
5
6 LEN_MIN = 500
7
8 class SaveToolCallFile(Extension):
9 async def execute(self, data: dict[str, Any] | None = None, **kwargs):
10 + if not self.agent:
11 + return
12 +
13 if not data:
14 return
15
extensions/python/job_loop/.gitkeep renamed
extensions/python/message_loop_end/.gitkeep renamed
extensions/python/message_loop_end/_10_organize_history.py renamed
+5 -2
@@ -1,12 +1,15 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData
3 -from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
3 +from helpers.defer import DeferredTask, THREAD_BACKGROUND
4
5 DATA_NAME_TASK = "_organize_history_task"
6
7
8 class OrganizeHistory(Extension):
9 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
10 + if not self.agent:
11 + return
12 +
13 # is there a running task? if yes, skip this round, the wait extension will double check the context size
14 task: DeferredTask|None = self.agent.get_data(DATA_NAME_TASK)
15 if task and not task.is_ready():
extensions/python/message_loop_end/_90_save_chat.py renamed
+5 -2
@@ -1,10 +1,13 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData, AgentContextType
3 -from python.helpers import persist_chat
3 +from helpers import persist_chat
4
5
6 class SaveChat(Extension):
7 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
8 + if not self.agent:
9 + return
10 +
11 # Skip saving BACKGROUND contexts as they should be ephemeral
12 if self.agent.context.type == AgentContextType.BACKGROUND:
13 return
extensions/python/message_loop_prompts_after/.gitkeep renamed
extensions/python/message_loop_prompts_after/_60_include_current_datetime.py renamed
+5 -2
@@ -1,11 +1,14 @@
1 from datetime import datetime, timezone
2 -from python.helpers.extension import Extension
2 +from helpers.extension import Extension
3 from agent import LoopData
4 -from python.helpers.localization import Localization
4 +from helpers.localization import Localization
5
6
7 class IncludeCurrentDatetime(Extension):
8 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9 + if not self.agent:
10 + return
11 +
12 # get current datetime
13 current_datetime = Localization.get().utc_dt_to_localtime_str(
14 datetime.now(timezone.utc), sep=" ", timespec="seconds"
extensions/python/message_loop_prompts_after/_65_include_loaded_skills.py renamed
+6 -3
@@ -1,11 +1,14 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers import skills
3 -from python.tools.skills_tool import DATA_NAME_LOADED_SKILLS
1 +from helpers.extension import Extension
2 +from helpers import skills
3 +from tools.skills_tool import DATA_NAME_LOADED_SKILLS
4 from agent import LoopData
5
6
7 class IncludeLoadedSkills(Extension):
8 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9 + if not self.agent:
10 + return
11 +
12 extras = loop_data.extras_persistent
13
14 # Get loaded skills names
extensions/python/message_loop_prompts_after/_70_include_agent_info.py renamed
+3 -1
@@ -1,9 +1,11 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData
3
4
5 class IncludeAgentInfo(Extension):
6 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
7 + if not self.agent:
8 + return
9
10 # read prompt
11 agent_info_prompt = self.agent.read_prompt(
extensions/python/message_loop_prompts_after/_75_include_workdir_extras.py renamed
+8 -6
@@ -1,13 +1,15 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData
3 -from python.helpers import projects
4 -from python.helpers import settings
5 -from python.helpers import runtime
6 -from python.helpers import file_tree
7 -from python.helpers import files
3 +from helpers import projects
4 +from helpers import settings
5 +from helpers import runtime
6 +from helpers import file_tree
7 +from helpers import files
8
9 class IncludeWorkdirExtras(Extension):
10 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
11 + if not self.agent:
12 + return
13
14 project_name = projects.get_context_project_name(self.agent.context)
15
extensions/python/message_loop_prompts_before/.gitkeep
extensions/python/message_loop_prompts_before/_90_organize_history_wait.py renamed
+5 -3
@@ -1,11 +1,13 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData
3 -from python.extensions.message_loop_end._10_organize_history import DATA_NAME_TASK
4 -from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
3 +from extensions.python.message_loop_end._10_organize_history import DATA_NAME_TASK
4 +from helpers.defer import DeferredTask, THREAD_BACKGROUND
5
6
7 class OrganizeHistoryWait(Extension):
8 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9 + if not self.agent:
10 + return
11
12 # sync action only required if the history is too large, otherwise leave it in background
13 while self.agent.history.is_over_limit():
extensions/python/message_loop_start/.gitkeep
extensions/python/message_loop_start/_10_iteration_no.py renamed
+4 -1
@@ -1,10 +1,13 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import Agent, LoopData
3
4 DATA_NAME_ITER_NO = "iteration_no"
5
6 class IterationNo(Extension):
7 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
8 + if not self.agent:
9 + return
10 +
11 # total iteration number
12 no = self.agent.get_data(DATA_NAME_ITER_NO) or 0
13 self.agent.set_data(DATA_NAME_ITER_NO, no + 1)
extensions/python/monologue_end/.gitkeep
extensions/python/monologue_end/_90_waiting_for_input_msg.py renamed
+4 -1
@@ -1,9 +1,12 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData
3
4 class WaitingForInputMsg(Extension):
5
6 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
7 + if not self.agent:
8 + return
9 +
10 # show temp info message
11 if self.agent.number == 0:
12 self.agent.context.log.set_initial_progress()
extensions/python/monologue_start/.gitkeep
extensions/python/monologue_start/_60_rename_chat.py renamed
+5 -2
@@ -1,5 +1,5 @@
1 -from python.helpers import persist_chat, tokens
2 -from python.helpers.extension import Extension
1 +from helpers import persist_chat, tokens
2 +from helpers.extension import Extension
3 from agent import LoopData
4 import asyncio
5
@@ -10,6 +10,9 @@ class RenameChat(Extension):
10 asyncio.create_task(self.change_name())
11
12 async def change_name(self):
13 + if not self.agent:
14 + return
15 +
16 try:
17 # prepare history
18 history_text = self.agent.history.output_text()
extensions/python/process_chain_end/.gitkeep
extensions/python/process_chain_end/_50_process_queue.py renamed
+5 -2
@@ -1,6 +1,6 @@
1 import asyncio
2 -from python.helpers.extension import Extension
3 -from python.helpers import message_queue as mq
2 +from helpers.extension import Extension
3 +from helpers import message_queue as mq
4 from agent import AgentContext, Agent, LoopData
5
6
@@ -8,6 +8,9 @@ class ProcessQueue(Extension):
8 """Process queued messages after monologue ends."""
9
10 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
11 + if not self.agent:
12 + return
13 +
14 # Only process for agent0 (main agent)
15 if self.agent.number != 0:
16 return
extensions/python/reasoning_stream/.gitkeep
extensions/python/reasoning_stream/_10_log_from_stream.py renamed
+7 -5
@@ -1,15 +1,17 @@
1 -from python.helpers import persist_chat, tokens
2 -from python.helpers.extension import Extension
1 +from helpers import persist_chat, tokens
2 +from helpers.extension import Extension
3 from agent import LoopData
4 import asyncio
5 -from python.helpers.log import LogItem
6 -from python.helpers import log
5 +from helpers.log import LogItem
6 +from helpers import log
7 import math
8 -from python.extensions.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
8 +from extensions.python.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
9
10 class LogFromStream(Extension):
11
12 async def execute(self, loop_data: LoopData = LoopData(), text: str = "", **kwargs):
13 + if not self.agent:
14 + return
15
16 # thought length indicator
17 length = f"({len(text)})" if text else ""
extensions/python/reasoning_stream_chunk/.gitkeep
extensions/python/reasoning_stream_chunk/_10_mask_stream.py renamed
+6 -3
@@ -1,9 +1,12 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers.secrets import get_secrets_manager
1 +from helpers.extension import Extension
2 +from helpers.secrets import get_secrets_manager
3
4
5 class MaskReasoningStreamChunk(Extension):
6 async def execute(self, **kwargs):
7 + if not self.agent:
8 + return
9 +
10 # Get stream data and agent from kwargs
11 stream_data = kwargs.get("stream_data")
12 agent = kwargs.get("agent")
@@ -31,7 +34,7 @@ class MaskReasoningStreamChunk(Extension):
34
35 # Print the processed chunk (this is where printing should happen)
36 if processed_chunk:
34 - from python.helpers.print_style import PrintStyle
37 + from helpers.print_style import PrintStyle
38 PrintStyle().stream(processed_chunk)
39 except Exception as e:
40 # If masking fails, proceed without masking
extensions/python/reasoning_stream_end/.gitkeep
extensions/python/reasoning_stream_end/_10_mask_end.py renamed
+2 -2
@@ -1,4 +1,4 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2
3
4 class MaskReasoningStreamEnd(Extension):
@@ -17,7 +17,7 @@ class MaskReasoningStreamEnd(Extension):
17
18 # Print any remaining masked content
19 if tail:
20 - from python.helpers.print_style import PrintStyle
20 + from helpers.print_style import PrintStyle
21 PrintStyle().stream(tail)
22
23 # Clean up the filter
extensions/python/response_stream/.gitkeep
extensions/python/response_stream/_10_log_from_stream.py renamed
+7 -5
@@ -1,11 +1,11 @@
1 -from python.helpers import persist_chat, tokens
2 -from python.helpers.extension import Extension
1 +from helpers import persist_chat, tokens
2 +from helpers.extension import Extension
3 from agent import LoopData
4 import asyncio
5 -from python.helpers.log import LogItem
6 -from python.helpers import log
5 +from helpers.log import LogItem
6 +from helpers import log
7 import math
8 -from python.extensions.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
8 +from extensions.python.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
9
10
11 class LogFromStream(Extension):
@@ -17,6 +17,8 @@ class LogFromStream(Extension):
17 parsed: dict = {},
18 **kwargs,
19 ):
20 + if not self.agent:
21 + return
22
23 heading = build_default_heading(self.agent)
24 if "headline" in parsed:
extensions/python/response_stream/_15_replace_include_alias.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 from typing import Any
2 -from python.helpers.extension import Extension
3 -from python.helpers.strings import replace_file_includes
2 +from helpers.extension import Extension
3 +from helpers.strings import replace_file_includes
4
5
6 class ReplaceIncludeAlias(Extension):
extensions/python/response_stream/_20_live_response.py renamed
+7 -4
@@ -1,9 +1,9 @@
1 -from python.helpers import persist_chat, tokens
2 -from python.helpers.extension import Extension
1 +from helpers import persist_chat, tokens
2 +from helpers.extension import Extension
3 from agent import LoopData
4 import asyncio
5 -from python.helpers.log import LogItem
6 -from python.helpers import log
5 +from helpers.log import LogItem
6 +from helpers import log
7
8
9 class LiveResponse(Extension):
@@ -15,6 +15,9 @@ class LiveResponse(Extension):
15 parsed: dict = {},
16 **kwargs,
17 ):
18 + if not self.agent:
19 + return
20 +
21 try:
22 if (
23 not "tool_name" in parsed
extensions/python/response_stream_chunk/.gitkeep
extensions/python/response_stream_chunk/_10_mask_stream.py renamed
+6 -3
@@ -1,11 +1,14 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import Agent, LoopData
3 -from python.helpers.secrets import get_secrets_manager
3 +from helpers.secrets import get_secrets_manager
4
5
6 class MaskResponseStreamChunk(Extension):
7
8 async def execute(self, **kwargs):
9 + if not self.agent:
10 + return
11 +
12 # Get stream data and agent from kwargs
13 stream_data = kwargs.get("stream_data")
14 agent = kwargs.get("agent")
@@ -33,7 +36,7 @@ class MaskResponseStreamChunk(Extension):
36
37 # Print the processed chunk (this is where printing should happen)
38 if processed_chunk:
36 - from python.helpers.print_style import PrintStyle
39 + from helpers.print_style import PrintStyle
40 PrintStyle().stream(processed_chunk)
41 except Exception as e:
42 # If masking fails, proceed without masking
extensions/python/response_stream_end/.gitkeep
extensions/python/response_stream_end/_10_mask_end.py renamed
+3 -3
@@ -1,5 +1,5 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers.secrets import SecretsManager
1 +from helpers.extension import Extension
2 +from helpers.secrets import SecretsManager
3
4
5 class MaskResponseStreamEnd(Extension):
@@ -18,7 +18,7 @@ class MaskResponseStreamEnd(Extension):
18
19 # Print any remaining masked content
20 if tail:
21 - from python.helpers.print_style import PrintStyle
21 + from helpers.print_style import PrintStyle
22 PrintStyle().stream(tail)
23
24 # Clean up the filter
extensions/python/response_stream_end/_15_log_from_stream_end.py renamed
+5 -5
@@ -1,11 +1,11 @@
1 -from python.helpers import persist_chat, tokens
2 -from python.helpers.extension import Extension
1 +from helpers import persist_chat, tokens
2 +from helpers.extension import Extension
3 from agent import LoopData
4 import asyncio
5 -from python.helpers.log import LogItem
6 -from python.helpers import log
5 +from helpers.log import LogItem
6 +from helpers import log
7 import math
8 -from python.extensions.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
8 +from extensions.python.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
9
10
11 class LogFromStream(Extension):
extensions/python/system_prompt/.gitkeep
extensions/python/system_prompt/_10_system_prompt.py renamed
+8 -5
@@ -1,9 +1,9 @@
1 from typing import Any
2 -from python.helpers.extension import Extension
3 -from python.helpers.mcp_handler import MCPConfig
2 +from helpers.extension import Extension
3 +from helpers.mcp_handler import MCPConfig
4 from agent import Agent, LoopData
5 -from python.helpers.settings import get_settings
6 -from python.helpers import projects, skills
5 +from helpers.settings import get_settings
6 +from helpers import projects, skills
7
8
9 class SystemPrompt(Extension):
@@ -14,6 +14,9 @@ class SystemPrompt(Extension):
14 loop_data: LoopData = LoopData(),
15 **kwargs: Any
16 ):
17 + if not self.agent:
18 + return
19 +
20 # append main system prompt and tools
21 main = get_main_prompt(self.agent)
22 tools = get_tools_prompt(self.agent)
@@ -61,7 +64,7 @@ def get_mcp_tools_prompt(agent: Agent):
64 def get_secrets_prompt(agent: Agent):
65 try:
66 # Use lazy import to avoid circular dependencies
64 - from python.helpers.secrets import get_secrets_manager
67 + from helpers.secrets import get_secrets_manager
68
69 secrets_manager = get_secrets_manager(agent.context)
70 secrets = secrets_manager.get_secrets_for_prompt()
extensions/python/tool_execute_after/.gitkeep
extensions/python/tool_execute_after/_10_mask_secrets.py renamed
+6 -3
@@ -1,11 +1,14 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers.secrets import get_secrets_manager
3 -from python.helpers.tool import Response
1 +from helpers.extension import Extension
2 +from helpers.secrets import get_secrets_manager
3 +from helpers.tool import Response
4
5
6 class MaskToolSecrets(Extension):
7
8 async def execute(self, response: Response | None = None, **kwargs):
9 + if not self.agent:
10 + return
11 +
12 if not response:
13 return
14 secrets_mgr = get_secrets_manager(self.agent.context)
extensions/python/tool_execute_before/.gitkeep
extensions/python/tool_execute_before/_10_replace_last_tool_output.py renamed
+4 -1
@@ -1,9 +1,12 @@
1 from typing import Any
2 -from python.helpers.extension import Extension
2 +from helpers.extension import Extension
3
4
5 class ReplaceLastToolOutput(Extension):
6 async def execute(self, tool_args: dict[str, Any] | None = None, tool_name: str = "", **kwargs):
7 + if not self.agent:
8 + return
9 +
10 if not tool_args:
11 return
12
extensions/python/tool_execute_before/_10_unmask_secrets.py renamed
+5 -2
@@ -1,10 +1,13 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers.secrets import get_secrets_manager
1 +from helpers.extension import Extension
2 +from helpers.secrets import get_secrets_manager
3
4
5 class UnmaskToolSecrets(Extension):
6
7 async def execute(self, **kwargs):
8 + if not self.agent:
9 + return
10 +
11 # Get tool args from kwargs
12 tool_args = kwargs.get("tool_args")
13 if not tool_args:
extensions/python/user_message_ui/.gitkeep
extensions/python/user_message_ui/_10_update_check.py renamed
+9 -3
@@ -1,7 +1,7 @@
1 -from python.helpers import notification
2 -from python.helpers.extension import Extension
1 +from helpers import notification
2 +from helpers.extension import Extension
3 from agent import LoopData
4 -from python.helpers import settings, update_check
4 +from helpers import settings, update_check
5 import datetime
6
7
@@ -19,6 +19,9 @@ notification_cooldown_seconds = 60 * 60 * 24
19 class UpdateCheck(Extension):
20
21 async def execute(self, loop_data: LoopData = LoopData(), text: str = "", **kwargs):
22 + if not self.agent:
23 + return
24 +
25 try:
26 global last_check, last_notification_id, last_notification_time
27
@@ -46,6 +49,9 @@ class UpdateCheck(Extension):
49
50
51 def send_notification(self, notif):
52 + if not self.agent:
53 + return
54 +
55 notifs = self.agent.context.get_notification_manager()
56 notifs.send_notification(
57 title=notif.get("title", "Newer version available"),
extensions/python/util_model_call_before/.gitkeep
extensions/python/util_model_call_before/_10_mask_secrets.py renamed
+5 -2
@@ -1,10 +1,13 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers.secrets import get_secrets_manager
1 +from helpers.extension import Extension
2 +from helpers.secrets import get_secrets_manager
3
4
5 class MaskToolSecrets(Extension):
6
7 async def execute(self, **kwargs):
8 + if not self.agent:
9 + return
10 +
11 # model call data
12 call_data:dict = kwargs.get("call_data", {})
13
extensions/webui/.gitkeep
extensions/webui/api_call_after/.gitkeep
extensions/webui/api_call_before/.gitkeep
extensions/webui/get_message_handler/.gitkeep
extensions/webui/set_messages_after_loop/.gitkeep
extensions/webui/set_messages_before_loop/.gitkeep
helpers/api.py renamed
+10 -10
@@ -10,9 +10,9 @@ from flask import Request, Response, jsonify, Flask, session, request, send_file
10 from werkzeug.wrappers.response import Response as BaseResponse
11 from agent import AgentContext
12 from initialize import initialize_agent
13 -from python.helpers.print_style import PrintStyle
14 -from python.helpers.errors import format_error
15 -from python.helpers import files, cache
13 +from helpers.print_style import PrintStyle
14 +from helpers.errors import format_error
15 +from helpers import files, cache
16
17 ThreadLockType = Union[threading.Lock, threading.RLock]
18
@@ -147,7 +147,7 @@ def is_loopback_address(address: str) -> bool:
147 def requires_api_key(f):
148 @wraps(f)
149 async def decorated(*args, **kwargs):
150 - from python.helpers.settings import get_settings
150 + from helpers.settings import get_settings
151 valid_api_key = get_settings()["mcp_server_token"]
152
153 if api_key := request.headers.get("X-API-KEY"):
@@ -177,7 +177,7 @@ def requires_loopback(f):
177 def requires_auth(f):
178 @wraps(f)
179 async def decorated(*args, **kwargs):
180 - from python.helpers import login
180 + from helpers import login
181 user_pass_hash = login.get_credentials_hash()
182 if not user_pass_hash:
183 return await f(*args, **kwargs)
@@ -191,7 +191,7 @@ def requires_auth(f):
191 def csrf_protect(f):
192 @wraps(f)
193 async def decorated(*args, **kwargs):
194 - from python.helpers import runtime
194 + from helpers import runtime
195 token = session.get("csrf_token")
196 header = request.headers.get("X-CSRF-Token")
197 cookie = request.cookies.get("csrf_token_" + runtime.get_runtime_id())
@@ -204,8 +204,8 @@ def csrf_protect(f):
204
205
206 def register_api_route(app: Flask, lock: ThreadLockType) -> None:
207 - from python.helpers.extract_tools import load_classes_from_file
208 - from python.helpers import plugins
207 + from helpers.extract_tools import load_classes_from_file
208 + from helpers import plugins
209
210 async def _dispatch(path: str) -> BaseResponse:
211 # Return cached wrapped handler if available
@@ -218,8 +218,8 @@ def register_api_route(app: Flask, lock: ThreadLockType) -> None:
218 handler_cls: type[ApiHandler] | None = None
219
220 # Check built-in python/api/<path>.py
221 - builtin_file = files.get_abs_path(f"python/api/{path}.py")
222 - if files.is_in_dir(builtin_file, files.get_abs_path("python/api")) and files.exists(builtin_file):
221 + builtin_file = files.get_abs_path(f"api/{path}.py")
222 + if files.is_in_dir(builtin_file, files.get_abs_path("api")) and files.exists(builtin_file):
223 classes = load_classes_from_file(builtin_file, ApiHandler)
224 if classes:
225 handler_cls = classes[0]
helpers/attachment_manager.py renamed
+2 -2
@@ -3,10 +3,10 @@ import io
3 import base64
4 from PIL import Image
5 from typing import Dict, List, Optional, Tuple
6 -from python.helpers.security import safe_filename
6 +from helpers.security import safe_filename
7 from werkzeug.datastructures import FileStorage
8
9 -from python.helpers.print_style import PrintStyle
9 +from helpers.print_style import PrintStyle
10
11 class AttachmentManager:
12 ALLOWED_EXTENSIONS = {
helpers/backup.py renamed
+2 -2
@@ -9,8 +9,8 @@ from typing import List, Dict, Any, Optional
9 from pathspec import PathSpec
10 from pathspec.patterns.gitwildmatch import GitWildMatchPattern
11
12 -from python.helpers import files, runtime, git
13 -from python.helpers.print_style import PrintStyle
12 +from helpers import files, runtime, git
13 +from helpers.print_style import PrintStyle
14
15
16 class BackupService:
helpers/browser.py renamed
+1 -1
@@ -9,7 +9,7 @@
9 # BrowserContext,
10 # )
11
12 -# from python.helpers import files
12 +# from helpers import files
13
14
15 # class NoPageError(Exception):
helpers/browser_use.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers import dotenv
1 +from helpers import dotenv
2 dotenv.save_dotenv_value("ANONYMIZED_TELEMETRY", "false")
3 import browser_use
4 import browser_use.utils
\ No newline at end of file
helpers/browser_use_monkeypatch.py renamed
+1 -1
@@ -1,6 +1,6 @@
1 from typing import Any
2 from browser_use.llm import ChatGoogle
3 -from python.helpers import dirty_json
3 +from helpers import dirty_json
4
5
6 # ------------------------------------------------------------------------------
helpers/cache.py renamed
helpers/call_llm.py renamed
helpers/cloudflare_tunnel._py renamed
+2 -2
@@ -3,8 +3,8 @@ import platform
3 import requests
4 import subprocess
5 import threading
6 -from python.helpers import files
7 -from python.helpers.print_style import PrintStyle
6 +from helpers import files
7 +from helpers.print_style import PrintStyle
8
9 class CloudflareTunnel:
10 def __init__(self, port: int):
helpers/context.py renamed
helpers/crypto.py renamed
helpers/defer.py renamed
helpers/dirty_json.py renamed
helpers/docker.py renamed
+4 -4
@@ -2,10 +2,10 @@ import time
2 import docker
3 import atexit
4 from typing import Optional
5 -from python.helpers.files import get_abs_path
6 -from python.helpers.errors import format_error
7 -from python.helpers.print_style import PrintStyle
8 -from python.helpers.log import Log
5 +from helpers.files import get_abs_path
6 +from helpers.errors import format_error
7 +from helpers.print_style import PrintStyle
8 +from helpers.log import Log
9
10 class DockerContainerManager:
11 def __init__(self, image: str, name: str, ports: Optional[dict[str, int]] = None, volumes: Optional[dict[str, dict[str, str]]] = None,logger: Log|None=None):
helpers/document_query.py renamed
+3 -3
@@ -4,7 +4,7 @@ import asyncio
4 import aiohttp
5 import json
6
7 -from python.helpers.vector_db import VectorDB
7 +from helpers.vector_db import VectorDB
8
9 os.environ["USER_AGENT"] = "@mixedbread-ai/unstructured" # noqa E402
10 from langchain_unstructured import UnstructuredLoader # noqa E402
@@ -22,8 +22,8 @@ from langchain_community.document_loaders.parsers.images import TesseractBlobPar
22 from langchain_core.documents import Document
23 from langchain.schema import SystemMessage, HumanMessage
24
25 -from python.helpers.print_style import PrintStyle
26 -from python.helpers import files, errors
25 +from helpers.print_style import PrintStyle
26 +from helpers import files, errors
27 from agent import Agent
28
29 from langchain.text_splitter import RecursiveCharacterTextSplitter
helpers/dotenv.py renamed
helpers/duckduckgo_search.py renamed
helpers/email_client.py renamed
+4 -4
@@ -13,9 +13,9 @@ import html2text
13 from bs4 import BeautifulSoup
14 from imapclient import IMAPClient
15
16 -from python.helpers import files
17 -from python.helpers.errors import RepairableException, format_error
18 -from python.helpers.print_style import PrintStyle
16 +from helpers import files
17 +from helpers.errors import RepairableException, format_error
18 +from helpers.print_style import PrintStyle
19
20
21 @dataclass
@@ -560,7 +560,7 @@ async def read_messages(
560 List of Message objects
561
562 Example:
563 - from python.helpers.email_client import read_messages
563 + from helpers.email_client import read_messages
564 messages = await read_messages(
565 server="imap.gmail.com",
566 port=993,
helpers/errors.py renamed
+11 -6
@@ -14,9 +14,14 @@ def error_text(e: Exception):
14 return str(e)
15
16
17 -def format_error(e: Exception, start_entries=20, end_entries=15, error_message_position:Literal["top", "bottom", "none"] = "top"):
17 +def format_error(
18 + e: Exception,
19 + start_entries=20,
20 + end_entries=15,
21 + error_message_position: Literal["top", "bottom", "none"] = "top",
22 +):
23 # format traceback from the provided exception instead of the most recent one
19 - traceback_text = ''.join(traceback.format_exception(type(e), e, e.__traceback__))
24 + traceback_text = "".join(traceback.format_exception(type(e), e, e.__traceback__))
25 # Split the traceback into lines
26 lines = traceback_text.split("\n")
27
@@ -77,15 +82,15 @@ def format_error(e: Exception, start_entries=20, end_entries=15, error_message_p
82
83 class RepairableException(Exception):
84 """An exception type indicating errors that can be surfaced to the LLM for potential self-repair."""
80 - pass
85
82 -class InterventionException(Exception):
83 - """An exception type raised on user intervention, skipping rest of message loop iteration."""
86 pass
87 +
88 +
89 class InterventionException(Exception):
90 """An exception type raised on user intervention, skipping rest of message loop iteration."""
91 +
92 pass
93
94
95 class HandledException(Exception):
91 - pass
\ No newline at end of file
96 + pass
helpers/extension.py renamed
+40 -14
@@ -1,7 +1,7 @@
1 from abc import abstractmethod
2 from typing import Any
3 -from python.helpers import extract_tools, files
4 -from python.helpers import cache
3 +from helpers import extract_tools, files
4 +from helpers import cache, plugins, subagents
5 from typing import TYPE_CHECKING
6 from functools import wraps
7 import asyncio
@@ -15,7 +15,7 @@ DEFAULT_EXTENSIONS_FOLDER = "python/extensions"
15 USER_EXTENSIONS_FOLDER = "usr/extensions"
16
17 _CACHE_AREA = "extension_folder_classes(extensions)(plugins)"
18 -cache.toggle_area(_CACHE_AREA, False) # cache off for now
18 +cache.toggle_area(_CACHE_AREA, True) # cache off for now
19
20
21 class _Unset:
@@ -58,6 +58,7 @@ def extensible(func):
58 Finally, if ``data["exception"]`` contains an exception it is raised;
59 otherwise ``data["result"]`` is returned.
60 """
61 +
62 @wraps(func)
63 async def _inner_async(*args, **kwargs):
64 from agent import Agent
@@ -75,7 +76,9 @@ def extensible(func):
76
77 def _get_agent() -> "Agent|None":
78 candidate = kwargs.get("agent")
78 - if isinstance(candidate, Agent) and bool(getattr(candidate, "__dict__", None)):
79 + if isinstance(candidate, Agent) and bool(
80 + getattr(candidate, "__dict__", None)
81 + ):
82 return candidate
83
84 for a in args:
@@ -149,18 +152,14 @@ class Extension:
152 async def call_extensions(
153 extension_point: str, agent: "Agent|None" = None, **kwargs
154 ) -> Any:
152 - from python.helpers import projects, subagents, plugins
153 -
155 # search for extension folders in all agent's paths
155 - paths = subagents.get_paths(
156 - agent, "extensions", extension_point, default_root="python"
157 - )
156 + paths = subagents.get_paths(agent, "extensions/python", extension_point)
157
159 - # Add plugin backend extension paths (plugins/*/extensions/python/{extension_point})
160 - plugin_paths = plugins.get_enabled_plugin_paths(
161 - agent, "extensions", "python", extension_point
162 - )
163 - paths.extend(p for p in plugin_paths if p not in paths)
158 + # # Add plugin backend extension paths (plugins/*/extensions/python/{extension_point})
159 + # plugin_paths = plugins.get_enabled_plugin_paths(
160 + # agent, "extensions", "python", extension_point
161 + # )
162 + # paths.extend(p for p in plugin_paths if p not in paths)
163
164 all_exts = [cls for path in paths for cls in _get_extensions(path)]
165
@@ -179,6 +178,33 @@ async def call_extensions(
178 await cls(agent=agent).execute(**kwargs)
179
180
181 +def get_webui_extensions(
182 + agent: "Agent | None", extension_point: str, filters: list[str] | None = None
183 +):
184 + entries: list[str] = []
185 + effective_filters = filters or ["*"]
186 +
187 + # search for extension folders in all agent's paths
188 + folders = subagents.get_paths(
189 + agent,
190 + "extensions/webui",
191 + extension_point,
192 + )
193 +
194 + extensions = []
195 +
196 + for folder in folders:
197 + for filter in effective_filters:
198 + pattern = files.get_abs_path(folder, filter)
199 + extensions.extend(files.find_existing_paths_by_pattern(pattern))
200 +
201 + for extension in extensions:
202 + rel_path = files.deabsolute_path(extension)
203 + entries.append(rel_path)
204 +
205 + return entries
206 +
207 +
208 def _get_file_from_module(module_name: str) -> str:
209 return module_name.split(".")[-1]
210
helpers/extract_tools.py renamed
helpers/faiss_monkey_patch.py renamed
helpers/fasta2a_client.py renamed
+1 -1
@@ -1,6 +1,6 @@
1 import uuid
2 from typing import Any, Dict, List, Optional
3 -from python.helpers.print_style import PrintStyle
3 +from helpers.print_style import PrintStyle
4
5 try:
6 from fasta2a.client import A2AClient # type: ignore
helpers/fasta2a_server.py renamed
+4 -4
@@ -6,14 +6,14 @@ from typing import Any, List
6 import contextlib
7 import threading
8
9 -from python.helpers import settings, projects
9 +from helpers import settings, projects
10 from starlette.requests import Request
11
12 # Local imports
13 -from python.helpers.print_style import PrintStyle
13 +from helpers.print_style import PrintStyle
14 from agent import AgentContext, UserMessage, AgentContextType
15 from initialize import initialize_agent
16 -from python.helpers.persist_chat import remove_chat
16 +from helpers.persist_chat import remove_chat
17
18 # Import FastA2A
19 try:
@@ -358,7 +358,7 @@ class DynamicA2AProxy:
358 })
359 return
360
361 - from python.helpers import settings
361 + from helpers import settings
362 cfg = settings.get_settings()
363 if not cfg["a2a_server_enabled"]:
364 response = b'HTTP/1.1 403 Forbidden\r\n\r\nA2A server is disabled'
helpers/file_browser.py renamed
+3 -3
@@ -4,11 +4,11 @@ import shutil
4 import base64
5 import subprocess
6 from typing import Dict, List, Tuple, Any
7 -from python.helpers.security import safe_filename
7 +from helpers.security import safe_filename
8 from datetime import datetime
9
10 -from python.helpers import files
11 -from python.helpers.print_style import PrintStyle
10 +from helpers import files
11 +from helpers.print_style import PrintStyle
12
13
14 class FileBrowser:
helpers/file_tree.py renamed
+1 -1
@@ -8,7 +8,7 @@ from typing import Any, Callable, Iterable, Literal, Optional, Sequence
8
9 from pathspec import PathSpec
10
11 -from python.helpers import files as files_helper
11 +from helpers import files as files_helper
12
13 SORT_BY_NAME = "name"
14 SORT_BY_CREATED = "created"
helpers/files.py renamed
+6 -6
@@ -12,7 +12,7 @@ import zipfile
12 import glob
13 import mimetypes
14 from simpleeval import simple_eval
15 -from python.helpers import yaml
15 +from helpers import yaml
16
17 AGENTS_DIR = "agents"
18 PLUGINS_DIR = "plugins"
@@ -45,7 +45,7 @@ def load_plugin_variables(
45
46 if plugin_file and exists(plugin_file):
47
48 - from python.helpers import extract_tools
48 + from helpers import extract_tools
49
50 classes = extract_tools.load_classes_from_file(
51 plugin_file, VariablesPlugin, one_per_file=False
@@ -80,7 +80,7 @@ def load_plugin_variables(
80 return {}
81
82
83 -from python.helpers.strings import sanitize_string
83 +from helpers.strings import sanitize_string
84
85
86 def parse_file(
@@ -536,7 +536,7 @@ def get_abs_path(*relative_paths):
536 def get_abs_path_dockerized(*relative_paths):
537 "Ensures the abs path is dockerized (i.e. /a0/... path)"
538 abs = get_abs_path(*relative_paths)
539 - from python.helpers import runtime
539 + from helpers import runtime
540
541 if runtime.is_dockerized():
542 return abs
@@ -556,7 +556,7 @@ def deabsolute_path(path: str):
556
557 def fix_dev_path(path: str):
558 "On dev environment, convert /a0/... paths to local absolute paths"
559 - from python.helpers.runtime import is_development
559 + from helpers.runtime import is_development
560
561 if is_development():
562 if path.startswith("/a0/"):
@@ -589,7 +589,7 @@ def is_dir(*relative_paths):
589
590 def get_base_dir():
591 # Get the base directory from the current file path
592 - base_dir = os.path.dirname(os.path.abspath(os.path.join(__file__, "../../")))
592 + base_dir = os.path.dirname(os.path.abspath(os.path.join(__file__, "../")))
593 return base_dir
594
595
helpers/git.py renamed
+2 -2
@@ -4,7 +4,7 @@ import os
4 import subprocess
5 import base64
6 from urllib.parse import urlparse, urlunparse
7 -from python.helpers import files
7 +from helpers import files
8
9
10 def strip_auth_from_url(url: str) -> str:
@@ -167,7 +167,7 @@ def get_repo_status(repo_path: str) -> dict:
167 commit = repo.head.commit
168 last_commit = {
169 "hash": commit.hexsha[:7],
170 - "message": commit.message.split('\n')[0][:80],
170 + "message": str(commit.message).split("\n")[0][:80],
171 "author": str(commit.author),
172 "date": datetime.fromtimestamp(commit.committed_date).strftime('%Y-%m-%d %H:%M')
173 }
helpers/guids.py renamed
helpers/history.py renamed
+1 -1
@@ -5,7 +5,7 @@ from collections.abc import Mapping
5 import json
6 import math
7 from typing import Coroutine, Literal, TypedDict, cast, Union, Dict, List, Any
8 -from python.helpers import messages, tokens, settings, call_llm
8 +from helpers import messages, tokens, settings, call_llm
9 from enum import Enum
10 from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage
11
helpers/images.py renamed
helpers/job_loop.py renamed
+4 -4
@@ -1,10 +1,10 @@
1 import asyncio
2 from datetime import datetime
3 import time
4 -from python.helpers.task_scheduler import TaskScheduler
5 -from python.helpers.print_style import PrintStyle
6 -from python.helpers import errors
7 -from python.helpers import runtime
4 +from helpers.task_scheduler import TaskScheduler
5 +from helpers.print_style import PrintStyle
6 +from helpers import errors
7 +from helpers import runtime
8
9
10 SLEEP_TIME = 60
helpers/kokoro_tts.py renamed
+3 -3
@@ -5,9 +5,9 @@ import io
5 import warnings
6 import asyncio
7 import soundfile as sf
8 -from python.helpers import runtime
9 -from python.helpers.print_style import PrintStyle
10 -from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
8 +from helpers import runtime
9 +from helpers.print_style import PrintStyle
10 +from helpers.notification import NotificationManager, NotificationType, NotificationPriority
11
12 warnings.filterwarnings("ignore", category=FutureWarning)
13 warnings.filterwarnings("ignore", category=UserWarning)
helpers/kvp.py renamed
+1 -1
@@ -6,7 +6,7 @@ import tempfile
6 import threading
7 from typing import Any
8
9 -from python.helpers.files import get_abs_path
9 +from helpers.files import get_abs_path
10
11 _runtime_lock = threading.RLock()
12 _runtime_store: dict[str, Any] = {}
helpers/localization.py renamed
+2 -2
@@ -1,8 +1,8 @@
1 from datetime import datetime, timezone as dt_timezone, timedelta
2 import pytz # type: ignore
3
4 -from python.helpers.print_style import PrintStyle
5 -from python.helpers.dotenv import get_dotenv_value, save_dotenv_value
4 +from helpers.print_style import PrintStyle
5 +from helpers.dotenv import get_dotenv_value, save_dotenv_value
6
7
8
helpers/log.py renamed
+4 -4
@@ -7,8 +7,8 @@ from collections import OrderedDict
7 from dataclasses import dataclass
8 from typing import Any, Literal, Optional, TYPE_CHECKING, TypeVar, cast
9
10 -from python.helpers.secrets import get_secrets_manager
11 -from python.helpers.strings import truncate_text_by_ratio
10 +from helpers.secrets import get_secrets_manager
11 +from helpers.strings import truncate_text_by_ratio
12
13
14 if TYPE_CHECKING:
@@ -23,7 +23,7 @@ def _lazy_mark_dirty_all(*, reason: str | None = None) -> None:
23 # Lazy import to avoid circular import at module load time (AgentContext -> Log).
24 global _MARK_DIRTY_ALL
25 if _MARK_DIRTY_ALL is None:
26 - from python.helpers.state_monitor_integration import mark_dirty_all
26 + from helpers.state_monitor_integration import mark_dirty_all
27
28 _MARK_DIRTY_ALL = mark_dirty_all
29 _MARK_DIRTY_ALL(reason=reason)
@@ -33,7 +33,7 @@ def _lazy_mark_dirty_for_context(context_id: str, *, reason: str | None = None)
33 # Lazy import to avoid circular import at module load time (AgentContext -> Log).
34 global _MARK_DIRTY_FOR_CONTEXT
35 if _MARK_DIRTY_FOR_CONTEXT is None:
36 - from python.helpers.state_monitor_integration import mark_dirty_for_context
36 + from helpers.state_monitor_integration import mark_dirty_for_context
37
38 _MARK_DIRTY_FOR_CONTEXT = mark_dirty_for_context
39 _MARK_DIRTY_FOR_CONTEXT(context_id, reason=reason)
helpers/login.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers import dotenv
1 +from helpers import dotenv
2 import hashlib
3
4
helpers/mcp_handler.py renamed
+6 -6
@@ -21,9 +21,9 @@ from contextlib import AsyncExitStack
21 from shutil import which
22 from datetime import timedelta
23 import json
24 -from python.helpers import errors
25 -from python.helpers import settings
26 -from python.helpers.log import LogItem
24 +from helpers import errors
25 +from helpers import settings
26 +from helpers.log import LogItem
27
28 import httpx
29
@@ -39,9 +39,9 @@ from anyio.streams.memory import (
39 )
40
41 from pydantic import BaseModel, Field, Discriminator, Tag, PrivateAttr
42 -from python.helpers import dirty_json
43 -from python.helpers.print_style import PrintStyle
44 -from python.helpers.tool import Tool, Response
42 +from helpers import dirty_json
43 +from helpers.print_style import PrintStyle
44 +from helpers.tool import Tool, Response
45
46
47 def normalize_name(name: str) -> str:
helpers/mcp_server.py renamed
+3 -3
@@ -9,10 +9,10 @@ from fastmcp import FastMCP
9 import contextvars
10
11 from agent import AgentContext, AgentContextType, UserMessage
12 -from python.helpers.persist_chat import remove_chat
12 +from helpers.persist_chat import remove_chat
13 from initialize import initialize_agent
14 -from python.helpers.print_style import PrintStyle
15 -from python.helpers import settings, projects
14 +from helpers.print_style import PrintStyle
15 +from helpers import settings, projects
16 from starlette.middleware import Middleware
17 from starlette.middleware.base import BaseHTTPMiddleware
18 from starlette.exceptions import HTTPException as StarletteHTTPException
helpers/message_queue.py renamed
+2 -2
@@ -1,12 +1,12 @@
1 import os
2 import uuid
3 from typing import TYPE_CHECKING
4 -from python.helpers import guids
4 +from helpers import guids
5
6 if TYPE_CHECKING:
7 from agent import AgentContext
8
9 -from python.helpers.print_style import PrintStyle
9 +from helpers.print_style import PrintStyle
10
11 QUEUE_KEY = "message_queue"
12 QUEUE_SEQ_KEY = "message_queue_seq"
helpers/messages.py renamed
helpers/migration.py renamed
+4 -4
@@ -1,9 +1,9 @@
1 import os
2 import json
3 -from python.helpers import files
4 -from python.helpers import subagents
5 -from python.helpers import yaml as yaml_helper
6 -from python.helpers.print_style import PrintStyle
3 +from helpers import files
4 +from helpers import subagents
5 +from helpers import yaml as yaml_helper
6 +from helpers.print_style import PrintStyle
7
8
9 def startup_migration() -> None:
helpers/notification.py renamed
+5 -5
@@ -115,7 +115,7 @@ class NotificationManager:
115 # Enforce limit
116 self._enforce_limit()
117
118 - from python.helpers.state_monitor_integration import mark_dirty_all
118 + from helpers.state_monitor_integration import mark_dirty_all
119 mark_dirty_all(reason="notification.NotificationManager.add_notification")
120 return item
121
@@ -175,7 +175,7 @@ class NotificationManager:
175 if not changed_nos:
176 return 0
177
178 - from python.helpers.state_monitor_integration import mark_dirty_all
178 + from helpers.state_monitor_integration import mark_dirty_all
179 mark_dirty_all(reason="notification.NotificationManager.mark_read_by_ids")
180 return len(changed_nos)
181
@@ -196,7 +196,7 @@ class NotificationManager:
196 if not changed:
197 return
198
199 - from python.helpers.state_monitor_integration import mark_dirty_all
199 + from helpers.state_monitor_integration import mark_dirty_all
200 mark_dirty_all(reason="notification.NotificationManager._update_item")
201
202 def mark_all_read(self):
@@ -212,7 +212,7 @@ class NotificationManager:
212 if not changed_nos:
213 return
214
215 - from python.helpers.state_monitor_integration import mark_dirty_all
215 + from helpers.state_monitor_integration import mark_dirty_all
216 mark_dirty_all(reason="notification.NotificationManager.mark_all_read")
217
218 def clear_all(self):
@@ -220,7 +220,7 @@ class NotificationManager:
220 self.notifications = []
221 self.updates = []
222 self.guid = str(uuid.uuid4())
223 - from python.helpers.state_monitor_integration import mark_dirty_all
223 + from helpers.state_monitor_integration import mark_dirty_all
224 mark_dirty_all(reason="notification.NotificationManager.clear_all")
225
226 def get_notifications_by_type(self, type: NotificationType) -> list[NotificationItem]:
helpers/performance.py new
+51
@@ -0,0 +1,51 @@
1 +import functools
2 +import inspect
3 +from pyinstrument import Profiler
4 +
5 +def trace_performance(*, show_all=False, color=True, unicode=True):
6 + """
7 + Decorator that profiles a function and prints a call tree when it finishes.
8 +
9 + Works with both synchronous and asynchronous functions.
10 + """
11 +
12 + def decorator(func):
13 + is_coro = inspect.iscoroutinefunction(func)
14 +
15 + @functools.wraps(func)
16 + async def async_wrapper(*args, **kwargs):
17 + profiler = Profiler()
18 + profiler.start()
19 + try:
20 + return await func(*args, **kwargs)
21 + finally:
22 + profiler.stop()
23 + print(f"\n=== Performance trace: {func.__module__}.{func.__qualname__} (async) ===")
24 + print(
25 + profiler.output_text(
26 + color=color,
27 + unicode=unicode,
28 + show_all=show_all,
29 + )
30 + )
31 +
32 + @functools.wraps(func)
33 + def sync_wrapper(*args, **kwargs):
34 + profiler = Profiler()
35 + profiler.start()
36 + try:
37 + return func(*args, **kwargs)
38 + finally:
39 + profiler.stop()
40 + print(f"\n=== Performance trace: {func.__module__}.{func.__qualname__} ===")
41 + print(
42 + profiler.output_text(
43 + color=color,
44 + unicode=unicode,
45 + show_all=show_all,
46 + )
47 + )
48 +
49 + return async_wrapper if is_coro else sync_wrapper
50 +
51 + return decorator
\ No newline at end of file
helpers/perplexity_search.py renamed
helpers/persist_chat.py renamed
+2 -2
@@ -3,11 +3,11 @@ from datetime import datetime
3 from typing import Any
4 import uuid
5 from agent import Agent, AgentConfig, AgentContext, AgentContextType
6 -from python.helpers import files, history
6 +from helpers import files, history
7 import json
8 from initialize import initialize_agent
9
10 -from python.helpers.log import Log, LogItem
10 +from helpers.log import Log, LogItem
11
12 CHATS_FOLDER = "usr/chats"
13 LOG_SIZE = 1000
helpers/playwright.py renamed
+1 -1
@@ -3,7 +3,7 @@ import os
3 import sys
4 from pathlib import Path
5 import subprocess
6 -from python.helpers import files
6 +from helpers import files
7
8
9 # this helper ensures that playwright is installed in /lib/playwright
helpers/plugins.py renamed
+5 -18
@@ -13,7 +13,7 @@ from typing import (
13 TypedDict,
14 )
15
16 -from python.helpers import files, print_style, yaml as yaml_helper, cache
16 +from helpers import files, print_style, yaml as yaml_helper, cache
17 from pydantic import BaseModel, Field
18
19 if TYPE_CHECKING:
@@ -235,7 +235,7 @@ def get_enabled_plugins(agent: Agent | None):
235
236 # + agent paths
237 if agent:
238 - from python.helpers import subagents
238 + from helpers import subagents
239
240 agent_paths = subagents.get_paths(
241 agent,
@@ -349,19 +349,6 @@ def toggle_plugin(
349 files.write_file(disabled_file, "")
350
351
352 -def get_webui_extensions(agent: Agent | None, extension_point: str, filters: List[str] | None = None):
353 - entries: List[str] = []
354 - effective_filters = filters or ["*"]
355 -
356 - for filter in effective_filters:
357 - extensions = get_enabled_plugin_paths(agent, "extensions", "webui", extension_point, filter)
358 - for extension in extensions:
359 - rel_path = files.deabsolute_path(extension)
360 - entries.append(rel_path)
361 -
362 - return entries
363 -
364 -
352 def get_plugin_config(
353 plugin_name: str,
354 agent: Agent | None = None,
@@ -370,7 +357,7 @@ def get_plugin_config(
357 ):
358
359 if project_name is None and agent is not None:
373 - from python.helpers import projects
360 + from helpers import projects
361
362 project_name = projects.get_context_project_name(agent.context)
363 if agent_profile is None and agent is not None:
@@ -436,7 +423,7 @@ def find_plugin_assets(
423 agent_profile: str = "*",
424 only_first: bool = False,
425 ) -> list[PluginAssetFile]:
439 - from python.helpers import projects, subagents
426 + from helpers import projects, subagents
427
428 results: list[PluginAssetFile] = []
429
@@ -550,7 +537,7 @@ def determine_plugin_asset_path(
537 base_path = files.get_abs_path(files.USER_DIR)
538
539 if project_name:
553 - from python.helpers import projects
540 + from helpers import projects
541
542 base_path = projects.get_project_meta(project_name)
543
helpers/print_catch.py renamed
helpers/print_style.py renamed
+1 -1
@@ -138,7 +138,7 @@ class PrintStyle:
138 # Automatically mask secrets in all print output
139 try:
140 if not hasattr(self, "secrets_mgr"):
141 - from python.helpers.secrets import get_secrets_manager
141 + from helpers.secrets import get_secrets_manager
142 self.secrets_mgr = get_secrets_manager()
143 text = self.secrets_mgr.mask_values(text)
144 except Exception:
helpers/process.py renamed
+2 -2
@@ -1,7 +1,7 @@
1 import os
2 import sys
3 -from python.helpers import runtime
4 -from python.helpers.print_style import PrintStyle
3 +from helpers import runtime
4 +from helpers.print_style import PrintStyle
5
6 _server = None
7
helpers/projects.py renamed
+11 -11
@@ -1,8 +1,8 @@
1 import os
2 from typing import Literal, TypedDict, TYPE_CHECKING, cast
3
4 -from python.helpers import files, dirty_json, persist_chat, file_tree
5 -from python.helpers.print_style import PrintStyle
4 +from helpers import files, dirty_json, persist_chat, file_tree
5 +from helpers.print_style import PrintStyle
6
7
8 if TYPE_CHECKING:
@@ -87,7 +87,7 @@ def create_project(name: str, data: BasicProjectData):
87
88 def clone_git_project(name: str, git_url: str, git_token: str, data: BasicProjectData):
89 """Clone a git repository as a new A0 project. Token is used only for cloning via http header."""
90 - from python.helpers import git
90 + from helpers import git
91
92 abs_path = files.create_dir_safe(
93 files.get_abs_path(PROJECTS_PARENT_DIR, name), rename_format="{name}_{number}"
@@ -231,7 +231,7 @@ def load_basic_project_data(name: str) -> BasicProjectData:
231
232
233 def load_edit_project_data(name: str) -> EditProjectData:
234 - from python.helpers import git
234 + from helpers import git
235
236 data = load_basic_project_data(name)
237 additional_instructions = get_additional_instructions_files(name)
@@ -316,7 +316,7 @@ def activate_project(context_id: str, name: str, *, mark_dirty: bool = True):
316 persist_chat.save_tmp_chat(context)
317
318 if mark_dirty:
319 - from python.helpers.state_monitor_integration import mark_dirty_all
319 + from helpers.state_monitor_integration import mark_dirty_all
320 mark_dirty_all(reason="projects.activate_project")
321
322
@@ -333,7 +333,7 @@ def deactivate_project(context_id: str, *, mark_dirty: bool = True):
333 persist_chat.save_tmp_chat(context)
334
335 if mark_dirty:
336 - from python.helpers.state_monitor_integration import mark_dirty_all
336 + from helpers.state_monitor_integration import mark_dirty_all
337 mark_dirty_all(reason="projects.deactivate_project")
338
339
@@ -345,7 +345,7 @@ def reactivate_project_in_chats(name: str):
345 activate_project(context.id, name, mark_dirty=False)
346 persist_chat.save_tmp_chat(context)
347
348 - from python.helpers.state_monitor_integration import mark_dirty_all
348 + from helpers.state_monitor_integration import mark_dirty_all
349 mark_dirty_all(reason="projects.reactivate_project_in_chats")
350
351
@@ -357,7 +357,7 @@ def deactivate_project_in_chats(name: str):
357 deactivate_project(context.id, mark_dirty=False)
358 persist_chat.save_tmp_chat(context)
359
360 - from python.helpers.state_monitor_integration import mark_dirty_all
360 + from helpers.state_monitor_integration import mark_dirty_all
361 mark_dirty_all(reason="projects.deactivate_project_in_chats")
362
363
@@ -425,7 +425,7 @@ def save_project_subagents(name: str, subagents_data: dict[str, SubAgentSettings
425 def _normalize_subagents(
426 subagents_data: dict[str, SubAgentSettings]
427 ) -> dict[str, SubAgentSettings]:
428 - from python.helpers import subagents
428 + from helpers import subagents
429
430 agents_dict = subagents.get_agents_dict()
431
@@ -445,14 +445,14 @@ def _normalize_subagents(
445
446
447 def load_project_secrets_masked(name: str, merge_with_global=False):
448 - from python.helpers import secrets
448 + from helpers import secrets
449
450 mgr = secrets.get_project_secrets_manager(name, merge_with_global)
451 return mgr.get_masked_secrets()
452
453
454 def save_project_secrets(name: str, secrets: str):
455 - from python.helpers.secrets import get_project_secrets_manager
455 + from helpers.secrets import get_project_secrets_manager
456
457 secrets_manager = get_project_secrets_manager(name)
458 secrets_manager.save_secrets_with_merge(secrets)
helpers/providers.py renamed
+1 -1
@@ -1,5 +1,5 @@
1 import yaml
2 -from python.helpers import files
2 +from helpers import files
3 from typing import List, Dict, Optional, TypedDict, Literal
4
5 ModelType = Literal["chat", "embedding"]
helpers/rate_limiter.py renamed
helpers/rfc.py renamed
+2 -2
@@ -3,9 +3,9 @@ import inspect
3 import json
4 from typing import Any, TypedDict
5 import aiohttp
6 -from python.helpers import crypto
6 +from helpers import crypto
7
8 -from python.helpers import dotenv
8 +from helpers import dotenv
9
10
11 # Remote Function Call library
helpers/rfc_exchange.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers import runtime, crypto, dotenv
1 +from helpers import runtime, crypto, dotenv
2
3 async def get_root_password():
4 if runtime.is_dockerized():
helpers/rfc_files.py renamed
+1 -1
@@ -4,7 +4,7 @@ import fnmatch
4 import base64
5 import tempfile
6 import zipfile
7 -from python.helpers import runtime
7 +from helpers import runtime
8
9
10 def get_abs_path(*relative_paths):
helpers/runtime.py renamed
+1 -1
@@ -3,7 +3,7 @@ import inspect
3 import secrets
4 from pathlib import Path
5 from typing import TypeVar, Callable, Awaitable, Union, overload, cast
6 -from python.helpers import dotenv, rfc, settings, files
6 +from helpers import dotenv, rfc, settings, files
7 import asyncio
8 import threading
9 import queue
helpers/searxng.py renamed
+1 -1
@@ -1,5 +1,5 @@
1 import aiohttp
2 -from python.helpers import runtime
2 +from helpers import runtime
3
4 URL = "http://localhost:55510/search"
5
helpers/secrets.py renamed
+4 -4
@@ -6,8 +6,8 @@ from io import StringIO
6 from dataclasses import dataclass
7 from typing import Dict, Optional, List, Literal, Set, Callable, Tuple, TYPE_CHECKING
8 from dotenv.parser import parse_stream
9 -from python.helpers.errors import RepairableException
10 -from python.helpers import files
9 +from helpers.errors import RepairableException
10 +from helpers import files
11
12 if TYPE_CHECKING:
13 from agent import AgentContext
@@ -505,7 +505,7 @@ class SecretsManager:
505
506
507 def get_secrets_manager(context: "AgentContext|None" = None) -> SecretsManager:
508 - from python.helpers import projects
508 + from helpers import projects
509
510 # default secrets file
511 secret_files = [DEFAULT_SECRETS_FILE]
@@ -524,7 +524,7 @@ def get_secrets_manager(context: "AgentContext|None" = None) -> SecretsManager:
524 return SecretsManager.get_instance(*secret_files)
525
526 def get_project_secrets_manager(project_name: str, merge_with_global: bool = False) -> SecretsManager:
527 - from python.helpers import projects
527 + from helpers import projects
528
529 # default secrets file
530 secret_files = []
helpers/security.py renamed
helpers/settings.py renamed
+10 -10
@@ -7,13 +7,13 @@ import subprocess
7 from typing import Any, Literal, TypedDict, cast, TypeVar
8
9 import models
10 -from python.helpers import runtime, whisper, defer, git, subagents
10 +from helpers import runtime, whisper, defer, git, subagents
11 from . import files, dotenv
12 -from python.helpers.print_style import PrintStyle
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 -from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
12 +from helpers.print_style import PrintStyle
13 +from helpers.providers import get_providers, FieldOption as ProvidersFO
14 +from helpers.secrets import get_default_secrets_manager
15 +from helpers import dirty_json
16 +from helpers.notification import NotificationManager, NotificationType, NotificationPriority
17
18
19 T = TypeVar('T')
@@ -600,7 +600,7 @@ def _apply_settings(previous: Settings | None):
600 or _settings["embed_model_provider"] != previous["embed_model_provider"]
601 or _settings["embed_model_kwargs"] != previous["embed_model_kwargs"]
602 ):
603 - from python.helpers.extension import call_extensions
603 + from helpers.extension import call_extensions
604
605 defer.DeferredTask().start_task(
606 call_extensions, "embedding_model_changed"
@@ -608,7 +608,7 @@ def _apply_settings(previous: Settings | None):
608
609 # update mcp settings if necessary
610 if not previous or _settings["mcp_servers"] != previous["mcp_servers"]:
611 - from python.helpers.mcp_handler import MCPConfig
611 + from helpers.mcp_handler import MCPConfig
612
613 async def update_mcp_settings(mcp_servers: str):
614 PrintStyle(
@@ -670,7 +670,7 @@ def _apply_settings(previous: Settings | None):
670 if not previous or current_token != previous["mcp_server_token"]:
671
672 async def update_mcp_token(token: str):
673 - from python.helpers.mcp_server import DynamicMcpProxy
673 + from helpers.mcp_server import DynamicMcpProxy
674
675 DynamicMcpProxy.get_instance().reconfigure(token=token)
676
@@ -682,7 +682,7 @@ def _apply_settings(previous: Settings | None):
682 if not previous or current_token != previous["mcp_server_token"]:
683
684 async def update_a2a_token(token: str):
685 - from python.helpers.fasta2a_server import DynamicA2AProxy
685 + from helpers.fasta2a_server import DynamicA2AProxy
686
687 DynamicA2AProxy.get_instance().reconfigure(token=token)
688
helpers/shell_local.py renamed
+2 -2
@@ -4,8 +4,8 @@ import subprocess
4 import time
5 import sys
6 from typing import Optional, Tuple
7 -from python.helpers import tty_session, runtime
8 -from python.helpers.shell_ssh import clean_string
7 +from helpers import tty_session, runtime
8 +from helpers.shell_ssh import clean_string
9
10 class LocalInteractiveSession:
11 def __init__(self, cwd: str|None = None):
helpers/shell_ssh.py renamed
+3 -3
@@ -3,9 +3,9 @@ import paramiko
3 import time
4 import re
5 from typing import Tuple
6 -from python.helpers.log import Log
7 -from python.helpers.print_style import PrintStyle
8 -# from python.helpers.strings import calculate_valid_match_lengths
6 +from helpers.log import Log
7 +from helpers.print_style import PrintStyle
8 +# from helpers.strings import calculate_valid_match_lengths
9
10
11 class SSHInteractiveSession:
helpers/skills.py renamed
+1 -1
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
6 from pathlib import Path
7 from typing import Any, Dict, Iterable, List, Literal, Optional, Tuple, TYPE_CHECKING
8
9 -from python.helpers import files, subagents, projects, file_tree, runtime
9 +from helpers import files, subagents, projects, file_tree, runtime
10
11 if TYPE_CHECKING:
12 from agent import Agent
helpers/skills_cli.py renamed
+7 -7
@@ -3,11 +3,11 @@
3 Skills CLI - Easy skill management for Agent Zero
4
5 Usage:
6 - python -m python.helpers.skills_cli list List all skills
7 - python -m python.helpers.skills_cli create <name> Create a new skill
8 - python -m python.helpers.skills_cli show <name> Show skill details
9 - python -m python.helpers.skills_cli validate <name> Validate a skill
10 - python -m python.helpers.skills_cli search <query> Search skills
6 + python -m helpers.skills_cli list List all skills
7 + python -m helpers.skills_cli create <name> Create a new skill
8 + python -m helpers.skills_cli show <name> Show skill details
9 + python -m helpers.skills_cli validate <name> Validate a skill
10 + python -m helpers.skills_cli search <query> Search skills
11 """
12
13 import argparse
@@ -23,7 +23,7 @@ from datetime import datetime
23 # Add parent directory to path for imports
24 sys.path.insert(0, str(Path(__file__).parent.parent.parent))
25
26 -from python.helpers import files
26 +from helpers import files
27
28
29 @dataclass
@@ -306,7 +306,7 @@ Examples:
306 print(f"\nNext steps:")
307 print(f" 1. Edit {skill_dir / 'SKILL.md'} to add your instructions")
308 print(f" 2. Add any helper scripts to {skill_dir / 'scripts'}/")
309 - print(f" 3. Run: python -m python.helpers.skills_cli validate {args.name}")
309 + print(f" 3. Run: python -m helpers.skills_cli validate {args.name}")
310 except ValueError as e:
311 print(f"\n❌ Error: {e}")
312 sys.exit(1)
helpers/skills_import.py renamed
+4 -4
@@ -9,8 +9,8 @@ from dataclasses import dataclass
9 from pathlib import Path
10 from typing import Iterable, List, Literal, Optional, Tuple
11
12 -from python.helpers import files
13 -from python.helpers.skills import discover_skill_md_files
12 +from helpers import files
13 +from helpers.skills import discover_skill_md_files
14
15
16 ConflictPolicy = Literal["skip", "overwrite", "rename"]
@@ -172,7 +172,7 @@ def _resolve_conflict(dest: Path, policy: ConflictPolicy) -> Tuple[Path, bool]:
172
173 def get_project_skills_folder(project_name: str) -> Path:
174 """Get the skills folder path for a project."""
175 - from python.helpers.projects import get_project_meta
175 + from helpers.projects import get_project_meta
176 return Path(get_project_meta(project_name, PROJECT_SKILLS_DIR))
177
178
@@ -181,7 +181,7 @@ def get_agent_profile_skills_folder(profile_name: str) -> Path:
181
182
183 def get_project_agent_profile_skills_folder(project_name: str, profile_name: str) -> Path:
184 - from python.helpers.projects import get_project_meta
184 + from helpers.projects import get_project_meta
185 return Path(get_project_meta(project_name, "agents", profile_name, "skills"))
186
187
helpers/state_monitor.py renamed
+5 -5
@@ -7,17 +7,17 @@ import time
7 from dataclasses import dataclass, field
8 from typing import Any, TYPE_CHECKING
9
10 -from python.helpers import runtime
11 -from python.helpers.print_style import PrintStyle
12 -from python.helpers.state_snapshot import (
10 +from helpers import runtime
11 +from helpers.print_style import PrintStyle
12 +from helpers.state_snapshot import (
13 StateRequestV1,
14 advance_state_request_after_snapshot,
15 build_snapshot_from_request,
16 )
17 -from python.helpers.websocket import ConnectionNotFoundError
17 +from helpers.websocket import ConnectionNotFoundError
18
19 if TYPE_CHECKING: # pragma: no cover - hints only
20 - from python.helpers.websocket_manager import WebSocketManager
20 + from helpers.websocket_manager import WebSocketManager
21
22
23 ConnectionIdentity = tuple[str, str] # (namespace, sid)
helpers/state_monitor_integration.py renamed
+2 -2
@@ -2,12 +2,12 @@ from __future__ import annotations
2
3
4 def mark_dirty_all(*, reason: str | None = None) -> None:
5 - from python.helpers.state_monitor import get_state_monitor
5 + from helpers.state_monitor import get_state_monitor
6
7 get_state_monitor().mark_dirty_all(reason=reason)
8
9
10 def mark_dirty_for_context(context_id: str, *, reason: str | None = None) -> None:
11 - from python.helpers.state_monitor import get_state_monitor
11 + from helpers.state_monitor import get_state_monitor
12
13 get_state_monitor().mark_dirty_for_context(context_id, reason=reason)
helpers/state_snapshot.py renamed
+3 -3
@@ -9,9 +9,9 @@ import pytz # type: ignore[import-untyped]
9
10 from agent import AgentContext, AgentContextType
11
12 -from python.helpers.dotenv import get_dotenv_value
13 -from python.helpers.localization import Localization
14 -from python.helpers.task_scheduler import TaskScheduler
12 +from helpers.dotenv import get_dotenv_value
13 +from helpers.localization import Localization
14 +from helpers.task_scheduler import TaskScheduler
15
16
17 class SnapshotV1(TypedDict):
helpers/strings.py renamed
+1 -1
@@ -1,7 +1,7 @@
1 import re
2 import sys
3 import time
4 -from python.helpers import files
4 +from helpers import files
5
6 def sanitize_string(s: str, encoding: str = "utf-8") -> str:
7 # Replace surrogates and invalid unicode with replacement character
helpers/subagents.py renamed
+11 -11
@@ -1,5 +1,5 @@
1 -from python.helpers import files
2 -from python.helpers import yaml as yaml_helper
1 +from helpers import files
2 +from helpers import yaml as yaml_helper
3 from typing import TypedDict, TYPE_CHECKING
4 from pydantic import BaseModel, model_validator
5 import json
@@ -58,7 +58,7 @@ def get_agents_dict(
58 )
59 return merged
60
61 - from python.helpers import plugins
61 + from helpers import plugins
62
63 # load default, plugin, and custom agents and merge
64 default_agents = _get_agents_list_from_dir(DEFAULT_AGENTS_DIR, origin="default")
@@ -74,7 +74,7 @@ def get_agents_dict(
74
75 # merge with project agents if possible
76 if project_name:
77 - from python.helpers import projects
77 + from helpers import projects
78
79 project_agents_dir = projects.get_project_meta(project_name, "agents")
80 project_agents = _get_agents_list_from_dir(project_agents_dir, origin="project")
@@ -117,7 +117,7 @@ def load_agent_data(name: str, project_name: str | None = None) -> SubAgent:
117 return original
118 return override
119
120 - from python.helpers import plugins
120 + from helpers import plugins
121
122 # load default, plugin, and user agents and merge
123 default_agent = _load_agent_data_from_dir(
@@ -136,7 +136,7 @@ def load_agent_data(name: str, project_name: str | None = None) -> SubAgent:
136
137 # merge with project agent if possible
138 if project_name:
139 - from python.helpers import projects
139 + from helpers import projects
140
141 project_agents_dir = projects.get_project_meta(project_name, "agents")
142 project_agent = _load_agent_data_from_dir(
@@ -252,7 +252,7 @@ def _merge_agent_list_items(
252
253
254 def get_agents_roots() -> list[str]:
255 - from python.helpers import plugins
255 + from helpers import plugins
256
257 plugin_agents = plugins.get_enabled_plugin_paths(None, "agents")
258 project_agents = files.find_existing_paths_by_pattern("usr/projects/*/.a0proj/agents")
@@ -318,7 +318,7 @@ def get_available_agents_dict(
318 # all available agents
319 all_agents = get_agents_dict()
320 # filter by project settings
321 - from python.helpers import projects
321 + from helpers import projects
322
323 project_settings = (
324 projects.load_project_subagents(project_name) if project_name else {}
@@ -351,7 +351,7 @@ def get_paths(
351 project_name = ""
352
353 if include_project and agent:
354 - from python.helpers import projects
354 + from helpers import projects
355
356 project_name = projects.get_context_project_name(agent.context) or ""
357
@@ -378,7 +378,7 @@ def get_paths(
378
379 # plugin agents/<profile>/...
380 if include_plugins:
381 - from python.helpers import plugins
381 + from helpers import plugins
382 for plugin_dir in plugins.get_enabled_plugin_paths(agent, "agents", profile_name):
383 path = files.get_abs_path(plugin_dir, *subpaths)
384 if (not must_exist_completely) or files.exists(files.get_abs_path(plugin_dir, *check_subpaths)):
@@ -397,7 +397,7 @@ def get_paths(
397
398 if include_plugins:
399 # plugins/*/subpaths...
400 - from python.helpers import plugins
400 + from helpers import plugins
401
402 for plugin_dir in plugins.get_enabled_plugin_paths(agent):
403 path = files.get_abs_path(plugin_dir, *subpaths)
helpers/task_scheduler.py renamed
+10 -10
@@ -17,12 +17,12 @@ from pydantic import BaseModel, Field, PrivateAttr
17
18 from agent import Agent, AgentContext, UserMessage
19 from initialize import initialize_agent
20 -from python.helpers.persist_chat import save_tmp_chat
21 -from python.helpers.print_style import PrintStyle
22 -from python.helpers.defer import DeferredTask
23 -from python.helpers.files import get_abs_path, make_dirs, read_file, write_file
24 -from python.helpers.localization import Localization
25 -from python.helpers import projects, guids
20 +from helpers.persist_chat import save_tmp_chat
21 +from helpers.print_style import PrintStyle
22 +from helpers.defer import DeferredTask
23 +from helpers.files import get_abs_path, make_dirs, read_file, write_file
24 +from helpers.localization import Localization
25 +from helpers import projects, guids
26 import pytz
27 from typing import Annotated
28
@@ -677,19 +677,19 @@ class TaskScheduler:
677 async def add_task(self, task: Union[ScheduledTask, AdHocTask, PlannedTask]) -> "TaskScheduler":
678 await self._tasks.add_task(task)
679 ctx = await self._get_chat_context(task) # invoke context creation
680 - from python.helpers.state_monitor_integration import mark_dirty_all
680 + from helpers.state_monitor_integration import mark_dirty_all
681 mark_dirty_all(reason="task_scheduler.TaskScheduler.add_task")
682 return self
683
684 async def remove_task_by_uuid(self, task_uuid: str) -> "TaskScheduler":
685 await self._tasks.remove_task_by_uuid(task_uuid)
686 - from python.helpers.state_monitor_integration import mark_dirty_all
686 + from helpers.state_monitor_integration import mark_dirty_all
687 mark_dirty_all(reason="task_scheduler.TaskScheduler.remove_task_by_uuid")
688 return self
689
690 async def remove_task_by_name(self, name: str) -> "TaskScheduler":
691 await self._tasks.remove_task_by_name(name)
692 - from python.helpers.state_monitor_integration import mark_dirty_all
692 + from helpers.state_monitor_integration import mark_dirty_all
693 mark_dirty_all(reason="task_scheduler.TaskScheduler.remove_task_by_name")
694 return self
695
@@ -762,7 +762,7 @@ class TaskScheduler:
762
763 updated = await self._tasks.update_task_by_uuid(task_uuid, _update_task, verify_func)
764 if updated is not None:
765 - from python.helpers.state_monitor_integration import mark_dirty_all
765 + from helpers.state_monitor_integration import mark_dirty_all
766 mark_dirty_all(reason="task_scheduler.TaskScheduler.update_task_checked")
767 return updated
768
helpers/timed_input.py renamed
helpers/tokens.py renamed
helpers/tool.py renamed
+2 -2
@@ -3,8 +3,8 @@ from dataclasses import dataclass
3 from typing import Any
4
5 from agent import Agent, LoopData
6 -from python.helpers.print_style import PrintStyle
7 -from python.helpers.strings import sanitize_string
6 +from helpers.print_style import PrintStyle
7 +from helpers.strings import sanitize_string
8
9
10 @dataclass
helpers/tty_session.py renamed
helpers/tunnel_manager.py renamed
+1 -1
@@ -7,7 +7,7 @@ from flaredantic import (
7 import threading
8 from collections import deque
9
10 -from python.helpers.print_style import PrintStyle
10 +from helpers.print_style import PrintStyle
11
12 # Singleton to manage the tunnel instance
13 class TunnelManager:
helpers/update_check.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers import git, runtime
1 +from helpers import git, runtime
2 import hashlib
3
4
helpers/vector_db.py renamed
+2 -2
@@ -2,7 +2,7 @@ from typing import Any, List, Sequence
2 from langchain_community.vectorstores import FAISS
3
4 # faiss needs to be patched for python 3.12 on arm #TODO remove once not needed
5 -from python.helpers import faiss_monkey_patch
5 +from helpers import faiss_monkey_patch
6 import faiss
7
8
@@ -16,7 +16,7 @@ from langchain.embeddings import CacheBackedEmbeddings
16 from simpleeval import simple_eval
17
18 from agent import Agent
19 -from python.helpers import guids
19 +from helpers import guids
20
21
22 class MyFaiss(FAISS):
helpers/wait.py renamed
+1 -1
@@ -1,7 +1,7 @@
1 import asyncio
2 from datetime import datetime, timezone
3
4 -from python.helpers.print_style import PrintStyle
4 +from helpers.print_style import PrintStyle
5
6
7 def format_remaining_time(total_seconds: float) -> str:
helpers/websocket.py renamed
+2 -2
@@ -9,7 +9,7 @@ from typing import Any, Iterable, Optional, TYPE_CHECKING
9 import socketio
10
11 if TYPE_CHECKING: # pragma: no cover - hints only
12 - from python.helpers.websocket_manager import WebSocketManager
12 + from helpers.websocket_manager import WebSocketManager
13
14 _EVENT_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9_]*$")
15 _RESERVED_EVENT_NAMES: set[str] = {
@@ -272,7 +272,7 @@ class WebSocketResult:
272 class WebSocketHandler(ABC):
273 """Base class for WebSocket event handlers.
274
275 - The interface mirrors :class:`python.helpers.api.ApiHandler` with declarative
275 + The interface mirrors :class:`helpers.api.ApiHandler` with declarative
276 security configuration and lifecycle hooks while enforcing event-naming
277 conventions.
278 """
helpers/websocket_manager.py renamed
+5 -5
@@ -11,11 +11,11 @@ from typing import Any, Callable, Deque, Dict, Iterable, List, Optional, Set
11 import socketio
12 import uuid
13
14 -from python.helpers.defer import DeferredTask
15 -from python.helpers.print_style import PrintStyle
16 -from python.helpers import runtime
17 -from python.helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
18 -from python.helpers.state_monitor import _ws_debug_enabled
14 +from helpers.defer import DeferredTask
15 +from helpers.print_style import PrintStyle
16 +from helpers import runtime
17 +from helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
18 +from helpers.state_monitor import _ws_debug_enabled
19
20 BUFFER_MAX_SIZE = 100
21 BUFFER_TTL = timedelta(hours=1)
helpers/websocket_namespace_discovery.py renamed
+3 -3
@@ -7,9 +7,9 @@ from dataclasses import dataclass
7 from types import ModuleType
8 from typing import Iterable
9
10 -from python.helpers.files import get_abs_path
11 -from python.helpers.print_style import PrintStyle
12 -from python.helpers.websocket import WebSocketHandler
10 +from helpers.files import get_abs_path
11 +from helpers.print_style import PrintStyle
12 +from helpers.websocket import WebSocketHandler
13
14
15 @dataclass(frozen=True)
helpers/whisper.py renamed
+3 -3
@@ -3,9 +3,9 @@ import warnings
3 import whisper
4 import tempfile
5 import asyncio
6 -from python.helpers import runtime, rfc, settings, files
7 -from python.helpers.print_style import PrintStyle
8 -from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
6 +from helpers import runtime, rfc, settings, files
7 +from helpers.print_style import PrintStyle
8 +from helpers.notification import NotificationManager, NotificationType, NotificationPriority
9
10 # Suppress FutureWarning from torch.load
11 warnings.filterwarnings("ignore", category=FutureWarning)
helpers/yaml.py renamed
initialize.py
+8 -8
@@ -1,7 +1,7 @@
1 from agent import AgentConfig
2 import models
3 -from python.helpers import runtime, settings, defer, extension
4 -from python.helpers.print_style import PrintStyle
3 +from helpers import runtime, settings, defer, extension
4 +from helpers.print_style import PrintStyle
5
6
7 @extension.extensible
@@ -98,9 +98,9 @@ def initialize_agent(override_settings: dict | None = None):
98 # defer.DeferredTask(thread_name="mcp-initializer").start_task(initialize_mcp_async, config.mcp_servers)
99 # initialize_mcp(config.mcp_servers)
100
101 - # import python.helpers.mcp_handler as mcp_helper
101 + # import helpers.mcp_handler as mcp_helper
102 # import agent as agent_helper
103 - # import python.helpers.print_style as print_style_helper
103 + # import helpers.print_style as print_style_helper
104 # if not mcp_helper.MCPConfig.get_instance().is_initialized():
105 # try:
106 # mcp_helper.MCPConfig.update(config.mcp_servers)
@@ -121,7 +121,7 @@ def initialize_agent(override_settings: dict | None = None):
121
122 @extension.extensible
123 def initialize_chats():
124 - from python.helpers import persist_chat
124 + from helpers import persist_chat
125 async def initialize_chats_async():
126 persist_chat.load_tmp_chats()
127 return defer.DeferredTask().start_task(initialize_chats_async)
@@ -130,13 +130,13 @@ def initialize_chats():
130 def initialize_mcp():
131 set = settings.get_settings()
132 async def initialize_mcp_async():
133 - from python.helpers.mcp_handler import initialize_mcp as _initialize_mcp
133 + from helpers.mcp_handler import initialize_mcp as _initialize_mcp
134 return _initialize_mcp(set["mcp_servers"])
135 return defer.DeferredTask().start_task(initialize_mcp_async)
136
137 @extension.extensible
138 def initialize_job_loop():
139 - from python.helpers.job_loop import run_loop
139 + from helpers.job_loop import run_loop
140 return defer.DeferredTask("JobLoop").start_task(run_loop)
141
142 @extension.extensible
@@ -146,7 +146,7 @@ def initialize_preload():
146
147 @extension.extensible
148 def initialize_migration():
149 - from python.helpers import migration, dotenv
149 + from helpers import migration, dotenv
150 # run migration
151 migration.startup_migration()
152 # reload .env as it might have been moved
models.py
+7 -7
@@ -19,13 +19,13 @@ import litellm
19 import openai
20 from litellm.types.utils import ModelResponse
21
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 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
22 +from helpers import dotenv
23 +from helpers import settings, dirty_json
24 +from helpers.dotenv import load_dotenv
25 +from helpers.providers import ModelType as ProviderModelType, get_provider_config
26 +from helpers.rate_limiter import RateLimiter
27 +from helpers.tokens import approximate_tokens
28 +from helpers import dirty_json, browser_use_monkeypatch
29
30 from langchain_core.language_models.chat_models import SimpleChatModel
31 from langchain_core.outputs.chat_generation import ChatGenerationChunk
plugins/chat_branching/api/branch_chat.py
+3 -3
@@ -1,7 +1,7 @@
1 from datetime import datetime
2
3 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
4 -from python.helpers.persist_chat import (
3 +from helpers.api import ApiHandler, Input, Output, Request, Response
4 +from helpers.persist_chat import (
5 _serialize_context,
6 _deserialize_context,
7 save_tmp_chat,
@@ -65,7 +65,7 @@ class BranchChat(ApiHandler):
65 save_tmp_chat(new_context)
66
67 # Notify all tabs
68 - from python.helpers.state_monitor_integration import mark_dirty_all
68 + from helpers.state_monitor_integration import mark_dirty_all
69 mark_dirty_all(reason="plugins.chat_branching.BranchChat")
70
71 return {
plugins/error_retry/extensions/python/agent_Agent_handle_exception_end/_80_retry_critical_exception.py
+5 -5
@@ -1,11 +1,11 @@
1 import asyncio
2 from datetime import datetime, timezone
3 -from python.helpers.extension import Extension
3 +from helpers.extension import Extension
4 from agent import LoopData
5 -from python.helpers.localization import Localization
6 -from python.helpers.errors import RepairableException, HandledException
7 -from python.helpers import errors
8 -from python.helpers.print_style import PrintStyle
5 +from helpers.localization import Localization
6 +from helpers.errors import RepairableException, HandledException
7 +from helpers import errors
8 +from helpers.print_style import PrintStyle
9
10 from plugins.error_retry.extensions.python.agent_Agent_monologue_start._10_reset_critical_exception_counter import DATA_NAME_COUNTER
11
plugins/error_retry/extensions/python/agent_Agent_monologue_start/_10_reset_critical_exception_counter.py
+5 -5
@@ -1,10 +1,10 @@
1 from datetime import datetime, timezone
2 -from python.helpers.extension import Extension
2 +from helpers.extension import Extension
3 from agent import LoopData
4 -from python.helpers.localization import Localization
5 -from python.helpers.errors import RepairableException
6 -from python.helpers import errors
7 -from python.helpers.print_style import PrintStyle
4 +from helpers.localization import Localization
5 +from helpers.errors import RepairableException
6 +from helpers import errors
7 +from helpers.print_style import PrintStyle
8
9 DATA_NAME_COUNTER = "_plugin.error_retry.critical_exception_counter"
10
plugins/memory/api/import_knowledge.py
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import files
3 -from python.helpers.security import safe_filename
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import files
3 +from helpers.security import safe_filename
4 from plugins.memory.helpers.memory import Memory, get_custom_knowledge_subdir_abs
5 import os
6
plugins/memory/api/knowledge_path_get.py
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import files, projects
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import files, projects
3 from plugins.memory.helpers.memory import get_custom_knowledge_subdir_abs
4
5
plugins/memory/api/knowledge_reindex.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.api import ApiHandler, Request, Response
1 +from helpers.api import ApiHandler, Request, Response
2 from plugins.memory.helpers.memory import Memory
3
4
plugins/memory/api/memory_dashboard.py
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.api import ApiHandler, Request, Response
2 -from python.helpers import files
1 +from helpers.api import ApiHandler, Request, Response
2 +from helpers import files
3 from models import ModelConfig, ModelType
4 from langchain_core.documents import Document
5 from agent import AgentContext
plugins/memory/extensions/python/embedding_model_changed/_10_memory_reload.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2
3 # Direct import - this extension lives inside the memory plugin
4 from plugins.memory.helpers.memory import reload as memory_reload
plugins/memory/extensions/python/message_loop_prompts_after/_50_recall_memories.py
+2 -2
@@ -1,7 +1,7 @@
1 import asyncio
2 -from python.helpers.extension import Extension
2 +from helpers.extension import Extension
3 from agent import LoopData
4 -from python.helpers import dirty_json, errors, log, plugins
4 +from helpers import dirty_json, errors, log, plugins
5
6 # Direct import - this extension lives inside the memory plugin
7 from plugins.memory.helpers.memory import Memory
plugins/memory/extensions/python/message_loop_prompts_after/_91_recall_wait.py
+2 -2
@@ -1,7 +1,7 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData
3 from plugins.memory.extensions.python.message_loop_prompts_after._50_recall_memories import DATA_NAME_TASK as DATA_NAME_TASK_MEMORIES, DATA_NAME_ITER as DATA_NAME_ITER_MEMORIES
4 -from python.helpers import plugins
4 +from helpers import plugins
5
6 class RecallWait(Extension):
7 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
plugins/memory/extensions/python/monologue_end/_50_memorize_fragments.py
+5 -5
@@ -1,10 +1,10 @@
1 import asyncio
2 -from python.helpers import errors, plugins
3 -from python.helpers.extension import Extension
4 -from python.helpers.dirty_json import DirtyJson
2 +from helpers import errors, plugins
3 +from helpers.extension import Extension
4 +from helpers.dirty_json import DirtyJson
5 from agent import LoopData
6 -from python.helpers.log import LogItem
7 -from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
6 +from helpers.log import LogItem
7 +from helpers.defer import DeferredTask, THREAD_BACKGROUND
8
9 # Direct import - this extension lives inside the memory plugin
10 from plugins.memory.helpers.memory import Memory
plugins/memory/extensions/python/monologue_end/_51_memorize_solutions.py
+5 -5
@@ -1,10 +1,10 @@
1 import asyncio
2 -from python.helpers import errors, plugins
3 -from python.helpers.extension import Extension
4 -from python.helpers.dirty_json import DirtyJson
2 +from helpers import errors, plugins
3 +from helpers.extension import Extension
4 +from helpers.dirty_json import DirtyJson
5 from agent import LoopData
6 -from python.helpers.log import LogItem
7 -from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
6 +from helpers.log import LogItem
7 +from helpers.defer import DeferredTask, THREAD_BACKGROUND
8
9 # Direct import - this extension lives inside the memory plugin
10 from plugins.memory.helpers.memory import Memory
plugins/memory/extensions/python/monologue_start/_10_memory_init.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import LoopData
3
4 # Direct import - this extension lives inside the memory plugin
plugins/memory/extensions/python/system_prompt/_20_behaviour_prompt.py
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.extension import Extension
1 +from helpers.extension import Extension
2 from agent import Agent, LoopData
3 -from python.helpers import files
3 +from helpers import files
4
5 # Direct import - this extension lives inside the memory plugin
6 from plugins.memory.helpers import memory
plugins/memory/helpers/knowledge_import.py
+2 -2
@@ -8,8 +8,8 @@ from langchain_community.document_loaders import (
8 TextLoader,
9 UnstructuredHTMLLoader,
10 )
11 -from python.helpers.log import LogItem
12 -from python.helpers.print_style import PrintStyle
11 +from helpers.log import LogItem
12 +from helpers.print_style import PrintStyle
13
14 text_loader_kwargs = {"autodetect_encoding": True}
15
plugins/memory/helpers/memory.py
+9 -9
@@ -2,13 +2,13 @@ from datetime import datetime
2 from typing import Any, List, Sequence
3 from langchain.storage import InMemoryByteStore, LocalFileStore
4 from langchain.embeddings import CacheBackedEmbeddings
5 -from python.helpers import guids
5 +from helpers import guids
6
7 # from langchain_chroma import Chroma
8 from langchain_community.vectorstores import FAISS
9
10 # faiss needs to be patched for python 3.12 on arm #TODO remove once not needed
11 -from python.helpers import faiss_monkey_patch
11 +from helpers import faiss_monkey_patch
12 import faiss
13
14
@@ -22,11 +22,11 @@ import os, json
22
23 import numpy as np
24
25 -from python.helpers.print_style import PrintStyle
26 -from python.helpers import files, plugins, projects
25 +from helpers.print_style import PrintStyle
26 +from helpers import files, plugins, projects
27 from langchain_core.documents import Document
28 from . import knowledge_import
29 -from python.helpers.log import Log, LogItem
29 +from helpers.log import Log, LogItem
30 from enum import Enum
31 from agent import Agent, AgentContext
32 import models
@@ -486,7 +486,7 @@ def reload():
486 def abs_db_dir(memory_subdir: str) -> str:
487 # patch for projects, this way we don't need to re-work the structure of memory subdirs
488 if memory_subdir.startswith("projects/"):
489 - from python.helpers.projects import get_project_meta
489 + from helpers.projects import get_project_meta
490
491 return files.get_abs_path(get_project_meta(memory_subdir[9:]), "memory")
492 # standard subdirs
@@ -496,7 +496,7 @@ def abs_db_dir(memory_subdir: str) -> str:
496 def abs_knowledge_dir(knowledge_subdir: str, *sub_dirs: str) -> str:
497 # patch for projects, this way we don't need to re-work the structure of knowledge subdirs
498 if knowledge_subdir.startswith("projects/"):
499 - from python.helpers.projects import get_project_meta
499 + from helpers.projects import get_project_meta
500
501 return files.get_abs_path(
502 get_project_meta(knowledge_subdir[9:]), "knowledge", *sub_dirs
@@ -537,7 +537,7 @@ def get_context_memory_subdir(context: AgentContext) -> str:
537
538 def get_existing_memory_subdirs() -> list[str]:
539 try:
540 - from python.helpers.projects import (
540 + from helpers.projects import (
541 get_project_meta,
542 get_projects_parent_folder,
543 )
@@ -566,7 +566,7 @@ def get_knowledge_subdirs_by_memory_subdir(
566 memory_subdir: str, default: list[str]
567 ) -> list[str]:
568 if memory_subdir.startswith("projects/"):
569 - from python.helpers.projects import get_project_meta
569 + from helpers.projects import get_project_meta
570
571 default.append(get_project_meta(memory_subdir[9:], "knowledge"))
572 return default
plugins/memory/helpers/memory_consolidation.py
+3 -3
@@ -8,9 +8,9 @@ from enum import Enum
8 from langchain_core.documents import Document
9
10 from plugins.memory.helpers.memory import Memory
11 -from python.helpers.dirty_json import DirtyJson
12 -from python.helpers.log import LogItem
13 -from python.helpers.print_style import PrintStyle
11 +from helpers.dirty_json import DirtyJson
12 +from helpers.log import LogItem
13 +from helpers.print_style import PrintStyle
14 from agent import Agent
15 from plugins.memory.tools.memory_load import DEFAULT_THRESHOLD as DEFAULT_MEMORY_THRESHOLD
16
plugins/memory/tools/behaviour_adjustment.py
+3 -3
@@ -1,7 +1,7 @@
1 -from python.helpers import files
2 -from python.helpers.tool import Tool, Response
1 +from helpers import files
2 +from helpers.tool import Tool, Response
3 from agent import Agent
4 -from python.helpers.log import LogItem
4 +from helpers.log import LogItem
5 from plugins.memory.helpers import memory
6
7
plugins/memory/tools/memory_delete.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2 from plugins.memory.helpers.memory import Memory
3
4
plugins/memory/tools/memory_forget.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2 from plugins.memory.helpers.memory import Memory
3
4 from tools.memory_load import DEFAULT_THRESHOLD
plugins/memory/tools/memory_load.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2 from plugins.memory.helpers.memory import Memory
3
4 DEFAULT_THRESHOLD = 0.7
plugins/memory/tools/memory_save.py
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2 from plugins.memory.helpers.memory import Memory
3
4
plugins/plugin_installer/api/plugin_install.py
+3 -3
@@ -4,8 +4,8 @@ import time
4 import uuid
5 from pathlib import Path
6
7 -from python.helpers.api import ApiHandler, Input, Request, Output
8 -from python.helpers import files
7 +from helpers.api import ApiHandler, Input, Request, Output
8 +from helpers import files
9 from werkzeug.datastructures import FileStorage
10 from werkzeug.utils import secure_filename
11
@@ -63,7 +63,7 @@ class PluginInstall(ApiHandler):
63
64 def _fetch_index(self, input: dict) -> dict:
65 from plugins.plugin_installer.helpers.install import fetch_plugin_index
66 - from python.helpers.plugins import get_plugins_list
66 + from helpers.plugins import get_plugins_list
67 index_data = fetch_plugin_index()
68 installed = get_plugins_list()
69 return {
plugins/plugin_installer/helpers/install.py
+4 -4
@@ -8,13 +8,13 @@ import zipfile
8 from pathlib import Path
9 from typing import Optional
10
11 -from python.helpers import files
12 -from python.helpers.plugins import (
11 +from helpers import files
12 +from helpers.plugins import (
13 META_FILE_NAME,
14 PluginMetadata,
15 invalidate_plugin_cache,
16 )
17 -from python.helpers import yaml as yaml_helper
17 +from helpers import yaml as yaml_helper
18
19 _SAFE_NAME_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_]*$")
20
@@ -127,7 +127,7 @@ def install_from_zip(zip_path: str) -> dict:
127 def install_from_git(url: str, token: Optional[str] = None) -> dict:
128 """Clone git repo into usr/plugins/, validate plugin.yaml.
129 Returns dict with plugin name and metadata."""
130 - from python.helpers.git import clone_repo
130 + from helpers.git import clone_repo
131
132 # Derive plugin name from URL
133 repo_name = url.rstrip("/").split("/")[-1]
plugins/plugin_scan/api/plugin_scan_queue.py
+2 -2
@@ -1,6 +1,6 @@
1 from agent import AgentContext
2 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
3 -from python.helpers import message_queue as mq
2 +from helpers.api import ApiHandler, Input, Output, Request, Response
3 +from helpers import message_queue as mq
4
5
6 class PluginScanQueue(ApiHandler):
plugins/plugin_scan/api/plugin_scan_start.py
+1 -1
@@ -1,5 +1,5 @@
1 from agent import AgentContext, UserMessage
2 -from python.helpers.api import ApiHandler, Input, Output, Request, Response
2 +from helpers.api import ApiHandler, Input, Output, Request, Response
3
4
5 class PluginScanStart(ApiHandler):
plugins/text_editor/extensions/python/system_prompt/_15_text_editor_prompt.py
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.extension import Extension
2 -from python.helpers import plugins
1 +from helpers.extension import Extension
2 +from helpers import plugins
3 from agent import Agent, LoopData
4
5
plugins/text_editor/helpers/file_ops.py
+1 -1
@@ -9,7 +9,7 @@ import shutil
9 import tempfile
10 from typing import TypedDict
11
12 -from python.helpers import tokens
12 +from helpers import tokens
13
14 _BINARY_PEEK = 8192
15
plugins/text_editor/tools/text_editor.py
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.tool import Tool, Response
2 -from python.helpers.extension import call_extensions
3 -from python.helpers import plugins, runtime
1 +from helpers.tool import Tool, Response
2 +from helpers.extension import call_extensions
3 +from helpers import plugins, runtime
4 from plugins.text_editor.helpers.file_ops import (
5 FileInfo,
6 read_file,
preload.py
+3 -3
@@ -1,7 +1,7 @@
1 import asyncio
2 -from python.helpers import runtime, whisper, settings
3 -from python.helpers.print_style import PrintStyle
4 -from python.helpers import kokoro_tts
2 +from helpers import runtime, whisper, settings
3 +from helpers.print_style import PrintStyle
4 +from helpers import kokoro_tts
5 import models
6
7
prepare.py
+2 -2
@@ -1,7 +1,7 @@
1 -from python.helpers import dotenv, runtime, settings
1 +from helpers import dotenv, runtime, settings
2 import string
3 import random
4 -from python.helpers.print_style import PrintStyle
4 +from helpers.print_style import PrintStyle
5
6
7 PrintStyle.standard("Preparing environment...")
prompts/agent.system.main.tips.py
+5 -5
@@ -1,8 +1,8 @@
1 -from python.helpers.files import VariablesPlugin
2 -from python.helpers import settings
3 -from python.helpers import projects
4 -from python.helpers import runtime
5 -from python.helpers import files
1 +from helpers.files import VariablesPlugin
2 +from helpers import settings
3 +from helpers import projects
4 +from helpers import runtime
5 +from helpers import files
6 from typing import Any
7
8 class WorkdirPath(VariablesPlugin):
prompts/agent.system.tool.call_sub.py
+3 -3
@@ -1,8 +1,8 @@
1 import json
2 from typing import Any, TYPE_CHECKING
3 -from python.helpers.files import VariablesPlugin
4 -from python.helpers import files, projects, subagents
5 -from python.helpers.print_style import PrintStyle
3 +from helpers.files import VariablesPlugin
4 +from helpers import files, projects, subagents
5 +from helpers.print_style import PrintStyle
6
7 if TYPE_CHECKING:
8 from agent import Agent
prompts/agent.system.tools.py
+3 -3
@@ -1,8 +1,8 @@
1 import os
2 from typing import Any
3 -from python.helpers.files import VariablesPlugin
4 -from python.helpers import files
5 -from python.helpers.print_style import PrintStyle
3 +from helpers.files import VariablesPlugin
4 +from helpers import files
5 +from helpers.print_style import PrintStyle
6
7
8 class BuidToolsPrompt(VariablesPlugin):
python/websocket_handlers/_default.py
+1 -1
@@ -2,7 +2,7 @@ from __future__ import annotations
2
3 from typing import Any
4
5 -from python.helpers.websocket import WebSocketHandler, WebSocketResult
5 +from helpers.websocket import WebSocketHandler, WebSocketResult
6
7
8 class RootDefaultHandler(WebSocketHandler):
python/websocket_handlers/dev_websocket_test_handler.py
+3 -3
@@ -3,9 +3,9 @@ from __future__ import annotations
3 import asyncio
4 from typing import Any, Dict
5
6 -from python.helpers.print_style import PrintStyle
7 -from python.helpers import runtime
8 -from python.helpers.websocket import WebSocketHandler, WebSocketResult
6 +from helpers.print_style import PrintStyle
7 +from helpers import runtime
8 +from helpers.websocket import WebSocketHandler, WebSocketResult
9
10
11 class DevWebsocketTestHandler(WebSocketHandler):
python/websocket_handlers/hello_handler.py
+2 -2
@@ -1,7 +1,7 @@
1 from __future__ import annotations
2
3 -from python.helpers.print_style import PrintStyle
4 -from python.helpers.websocket import WebSocketHandler
3 +from helpers.print_style import PrintStyle
4 +from helpers.websocket import WebSocketHandler
5
6
7 class HelloHandler(WebSocketHandler):
python/websocket_handlers/state_sync_handler.py
+5 -5
@@ -1,10 +1,10 @@
1 from __future__ import annotations
2
3 -from python.helpers import runtime
4 -from python.helpers.print_style import PrintStyle
5 -from python.helpers.websocket import WebSocketHandler, WebSocketResult
6 -from python.helpers.state_monitor import get_state_monitor, _ws_debug_enabled
7 -from python.helpers.state_snapshot import (
3 +from helpers import runtime
4 +from helpers.print_style import PrintStyle
5 +from helpers.websocket import WebSocketHandler, WebSocketResult
6 +from helpers.state_monitor import get_state_monitor, _ws_debug_enabled
7 +from helpers.state_snapshot import (
8 StateRequestValidationError,
9 parse_state_request_payload,
10 )
requirements.dev.txt
+2 -1
@@ -1,3 +1,4 @@
1 pytest>=8.4.2
2 pytest-asyncio>=1.2.0
3 -pytest-mock>=3.15.1
\ No newline at end of file
3 +pytest-mock>=3.15.1
4 +pyinstrument==5.1.2
\ No newline at end of file
run_tunnel.py
+3 -3
@@ -1,9 +1,9 @@
1 import threading
2 from flask import Flask, request
3 -from python.helpers import runtime, dotenv, process
4 -from python.helpers.print_style import PrintStyle
3 +from helpers import runtime, dotenv, process
4 +from helpers.print_style import PrintStyle
5
6 -from python.api.tunnel import Tunnel
6 +from api.tunnel import Tunnel
7
8 # initialize the internal Flask server
9 app = Flask("app")
run_ui.py
+20 -11
@@ -11,20 +11,21 @@ from flask import Flask, request, Response, session, redirect, url_for, render_t
11 from werkzeug.wrappers.request import Request as WerkzeugRequest
12
13 import initialize
14 -from python.helpers import files, git, mcp_server, fasta2a_server, settings as settings_helper, extension
15 -from python.helpers.files import get_abs_path
16 -from python.helpers import runtime, dotenv, process
17 -from python.helpers.websocket import WebSocketHandler, validate_ws_origin
18 -from python.helpers.api import register_api_route, requires_auth
19 -from python.helpers.print_style import PrintStyle
20 -from python.helpers import login
14 +from helpers import files, git, mcp_server, fasta2a_server, settings as settings_helper, extension
15 +from helpers.files import get_abs_path
16 +from helpers import runtime, dotenv, process
17 +from helpers.websocket import WebSocketHandler, validate_ws_origin
18 +from helpers.api import register_api_route, requires_auth
19 +from helpers.print_style import PrintStyle
20 +from helpers import login
21 import socketio # type: ignore[import-untyped]
22 from socketio import ASGIApp, packet
23 from starlette.applications import Starlette
24 from starlette.routing import Mount
25 from uvicorn.middleware.wsgi import WSGIMiddleware
26 -from python.helpers.websocket_manager import WebSocketManager
27 -from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
26 +from helpers.websocket_manager import WebSocketManager
27 +from helpers.websocket_namespace_discovery import discover_websocket_namespaces
28 +from flask import send_file
29
30 # disable logging
31 import logging
@@ -144,6 +145,14 @@ async def serve_builtin_plugin_asset(plugin_name, asset_path):
145 async def serve_plugin_asset(plugin_name, asset_path):
146 return await _serve_plugin_asset(plugin_name, asset_path)
147
148 +@webapp.route("/extensions/webui/<path:asset_path>", methods=["GET"])
149 +@requires_auth
150 +async def serve_extension_asset(asset_path):
151 + path = files.get_abs_path("extensions/webui", asset_path)
152 + if not files.is_in_dir(path, "extensions/webui"):
153 + return Response("Access denied", 403)
154 + return send_file(path)
155 +
156
157 @extension.extensible
158 async def _serve_plugin_asset(plugin_name, asset_path):
@@ -151,8 +160,8 @@ async def _serve_plugin_asset(plugin_name, asset_path):
160 Serve static assets from plugin directories.
161 Resolves using the plugin system (with overrides).
162 """
154 - from python.helpers import plugins
155 - from flask import send_file
163 + from helpers import plugins
164 +
165
166 # Use the new find_plugin helper
167 plugin_dir = plugins.find_plugin_dir(plugin_name)
skills/a0-create-plugin/SKILL.md
+2 -2
@@ -139,7 +139,7 @@ If your plugin exposes existing core settings rather than plugin-specific ones,
139 ### Sending Messages Proactively
140 ```python
141 from agent import AgentContext
142 -from python.helpers.messages import UserMessage
142 +from helpers.messages import UserMessage
143
144 context = AgentContext.use(context_id)
145 task = context.communicate(UserMessage("Message text"))
@@ -148,7 +148,7 @@ response = await task.result()
148
149 ### Reading Plugin Settings (backend)
150 ```python
151 -from python.helpers.plugins import get_plugin_config, save_plugin_config
151 +from helpers.plugins import get_plugin_config, save_plugin_config
152
153 # Runtime (with running agent - resolves project/profile from context)
154 settings = get_plugin_config("my-plugin", agent=agent) or {}
tests/email_parser_test.py
+2 -2
@@ -4,8 +4,8 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
4
5 import asyncio
6 import pytest
7 -from python.helpers.email_client import read_messages
8 -from python.helpers.dotenv import get_dotenv_value, load_dotenv
7 +from helpers.email_client import read_messages
8 +from helpers.dotenv import get_dotenv_value, load_dotenv
9
10
11 @pytest.mark.skip(reason="This test is disabled as it has eternal dependencies and tests nothing automatically, please move it to a script or a manual test")
tests/test_fasta2a_client.py
+1 -1
@@ -9,7 +9,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
10 import asyncio
11 import pytest
12 -from python.helpers import settings
12 +from helpers import settings
13
14
15 def get_test_urls():
tests/test_file_tree_visualize.py
+2 -2
@@ -23,7 +23,7 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
23 if str(REPO_ROOT) not in sys.path:
24 sys.path.insert(0, str(REPO_ROOT))
25
26 -from python.helpers.file_tree import (
26 +from helpers.file_tree import (
27 OUTPUT_MODE_FLAT,
28 OUTPUT_MODE_NESTED,
29 OUTPUT_MODE_STRING,
@@ -34,7 +34,7 @@ from python.helpers.file_tree import (
34 SORT_DESC,
35 file_tree,
36 )
37 -from python.helpers.files import create_dir, delete_dir, get_abs_path, write_file
37 +from helpers.files import create_dir, delete_dir, get_abs_path, write_file
38
39
40 BASE_TEMP_ROOT = "tmp/tests/file_tree/visualize"
tests/test_http_auth_csrf.py
+6 -6
@@ -4,7 +4,7 @@ from flask import Flask, Response
4
5 import pytest
6
7 -from python.helpers import runtime
7 +from helpers import runtime
8
9
10 def _make_app() -> Flask:
@@ -32,7 +32,7 @@ def _set_csrf_cookie(client, token: str) -> None:
32 def test_http_auth_enforced_when_configured(monkeypatch) -> None:
33 from run_ui import csrf_protect, requires_auth
34
35 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
35 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
36
37 app = _make_app()
38
@@ -50,7 +50,7 @@ def test_http_auth_enforced_when_configured(monkeypatch) -> None:
50 def test_http_csrf_required_even_when_auth_not_configured(monkeypatch) -> None:
51 from run_ui import csrf_protect, requires_auth
52
53 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: None)
53 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: None)
54
55 app = _make_app()
56
@@ -69,7 +69,7 @@ def test_http_csrf_required_even_when_auth_not_configured(monkeypatch) -> None:
69 def test_http_csrf_rejects_missing_token(monkeypatch) -> None:
70 from run_ui import csrf_protect, requires_auth
71
72 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
72 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
73
74 app = _make_app()
75
@@ -88,7 +88,7 @@ def test_http_csrf_rejects_missing_token(monkeypatch) -> None:
88 def test_http_csrf_accepts_valid_header_without_cookie(monkeypatch) -> None:
89 from run_ui import csrf_protect, requires_auth
90
91 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
91 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
92
93 app = _make_app()
94
@@ -107,7 +107,7 @@ def test_http_csrf_accepts_valid_header_without_cookie(monkeypatch) -> None:
107 def test_http_csrf_accepts_valid_cookie(monkeypatch) -> None:
108 from run_ui import csrf_protect, requires_auth
109
110 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
110 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
111
112 app = _make_app()
113
tests/test_multi_tab_isolation.py
+5 -5
@@ -11,9 +11,9 @@ if str(PROJECT_ROOT) not in sys.path:
11
12 @pytest.mark.asyncio
13 async def test_state_monitor_per_sid_isolation_independent_snapshots_seq_and_cursors(monkeypatch):
14 - import python.helpers.state_monitor as state_monitor_module
15 - from python.helpers.state_monitor import StateMonitor
16 - from python.helpers.state_snapshot import StateRequestV1
14 + import helpers.state_monitor as state_monitor_module
15 + from helpers.state_monitor import StateMonitor
16 + from helpers.state_snapshot import StateRequestV1
17
18 snapshot_calls: list[dict[str, object]] = []
19 emitted: list[dict[str, object]] = []
@@ -129,8 +129,8 @@ async def test_state_monitor_per_sid_isolation_independent_snapshots_seq_and_cur
129
130 @pytest.mark.asyncio
131 async def test_state_monitor_mark_dirty_for_context_scopes_to_active_context():
132 - from python.helpers.state_monitor import StateMonitor
133 - from python.helpers.state_snapshot import StateRequestV1
132 + from helpers.state_monitor import StateMonitor
133 + from helpers.state_snapshot import StateRequestV1
134
135 monitor = StateMonitor(debounce_seconds=60.0)
136 namespace = "/state_sync"
tests/test_persist_chat_log_ids.py
+2 -2
@@ -2,8 +2,8 @@ from __future__ import annotations
2
3
4 def test_deserialize_log_preserves_item_id() -> None:
5 - from python.helpers.log import Log
6 - from python.helpers.persist_chat import _deserialize_log, _serialize_log
5 + from helpers.log import Log
6 + from helpers.persist_chat import _deserialize_log, _serialize_log
7
8 log = Log()
9 log.log(type="user", heading="User message", content="hello", id="msg-123")
tests/test_snapshot_parity.py
+3 -3
@@ -11,7 +11,7 @@ if str(PROJECT_ROOT) not in sys.path:
11
12 from agent import AgentContext
13 from initialize import initialize_agent
14 -from python.api.poll import Poll
14 +from api.poll import Poll
15
16
17 @pytest.mark.asyncio
@@ -31,7 +31,7 @@ async def test_snapshot_builder_matches_poll_output_for_null_context():
31 None, # Poll.process does not access the flask Request object.
32 )
33
34 - from python.helpers import state_snapshot as snapshot
34 + from helpers import state_snapshot as snapshot
35
36 builder_payload = await snapshot.build_snapshot(
37 context=None,
@@ -62,7 +62,7 @@ async def test_snapshot_builder_active_context_includes_incremental_logs():
62 assert first["logs"]
63 assert first["log_version"] == len(ctx.log.updates)
64
65 - from python.helpers import state_snapshot as snapshot
65 + from helpers import state_snapshot as snapshot
66
67 second = await snapshot.build_snapshot(
68 context=ctxid,
tests/test_snapshot_schema_v1.py
+3 -3
@@ -9,7 +9,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
9 if str(PROJECT_ROOT) not in sys.path:
10 sys.path.insert(0, str(PROJECT_ROOT))
11
12 -from python.api.poll import Poll
12 +from api.poll import Poll
13
14
15 EXPECTED_SNAPSHOT_KEYS = {
@@ -59,7 +59,7 @@ async def test_poll_snapshot_matches_contract_schema_key_set_null_context():
59
60 @pytest.mark.asyncio
61 async def test_snapshot_builder_produces_contract_schema_key_set_and_defaults():
62 - from python.helpers import state_snapshot as snapshot
62 + from helpers import state_snapshot as snapshot
63
64 payload = await snapshot.build_snapshot(
65 context=None,
@@ -87,7 +87,7 @@ async def test_snapshot_builder_produces_contract_schema_key_set_and_defaults():
87
88
89 def test_snapshot_schema_rejects_unexpected_top_level_keys():
90 - from python.helpers import state_snapshot as snapshot
90 + from helpers import state_snapshot as snapshot
91
92 payload = {
93 "deselect_chat": False,
tests/test_state_monitor.py
+5 -5
@@ -10,8 +10,8 @@ if str(PROJECT_ROOT) not in sys.path:
10
11 @pytest.mark.asyncio
12 async def test_state_monitor_debounce_coalesces_without_postponing_and_cleanup_cancels_pending():
13 - from python.helpers.state_monitor import StateMonitor
14 - from python.helpers.state_snapshot import StateRequestV1
13 + from helpers.state_monitor import StateMonitor
14 + from helpers.state_snapshot import StateRequestV1
15
16 namespace = "/state_sync"
17 monitor = StateMonitor(debounce_seconds=10.0)
@@ -44,8 +44,8 @@ async def test_state_monitor_namespace_identity_prevents_cross_namespace_state_p
44 import asyncio
45 from unittest.mock import AsyncMock
46
47 - from python.helpers.state_monitor import StateMonitor
48 - from python.helpers.state_snapshot import StateRequestV1
47 + from helpers.state_monitor import StateMonitor
48 + from helpers.state_snapshot import StateRequestV1
49
50 loop = asyncio.get_running_loop()
51 push_ready = asyncio.Event()
@@ -94,7 +94,7 @@ async def test_state_monitor_namespace_identity_prevents_cross_namespace_state_p
94 }
95
96 # Patch build_snapshot used by StateMonitor so this test stays lightweight.
97 - monkeypatch.setattr("python.helpers.state_monitor.build_snapshot_from_request", _fake_snapshot)
97 + monkeypatch.setattr("helpers.state_monitor.build_snapshot_from_request", _fake_snapshot)
98
99 monitor.mark_dirty(ns_a, sid, reason="test")
100 await asyncio.wait_for(push_ready.wait(), timeout=1.0)
tests/test_state_sync_handler.py
+5 -5
@@ -10,7 +10,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
10 if str(PROJECT_ROOT) not in sys.path:
11 sys.path.insert(0, str(PROJECT_ROOT))
12
13 -from python.helpers.websocket_manager import WebSocketManager
13 +from helpers.websocket_manager import WebSocketManager
14
15 NAMESPACE = "/state_sync"
16
@@ -28,7 +28,7 @@ async def _create_manager() -> WebSocketManager:
28 manager = WebSocketManager(socketio, threading.RLock())
29
30 from python.websocket_handlers.state_sync_handler import StateSyncHandler
31 - from python.helpers.state_monitor import _reset_state_monitor_for_testing
31 + from helpers.state_monitor import _reset_state_monitor_for_testing
32
33 _reset_state_monitor_for_testing()
34 StateSyncHandler._reset_instance_for_testing()
@@ -43,7 +43,7 @@ async def _create_manager_with_socketio() -> tuple[WebSocketManager, FakeSocketI
43 manager = WebSocketManager(socketio, threading.RLock())
44
45 from python.websocket_handlers.state_sync_handler import StateSyncHandler
46 - from python.helpers.state_monitor import _reset_state_monitor_for_testing
46 + from helpers.state_monitor import _reset_state_monitor_for_testing
47
48 _reset_state_monitor_for_testing()
49 StateSyncHandler._reset_instance_for_testing()
@@ -115,8 +115,8 @@ async def test_state_request_invalid_payload_returns_invalid_request_error():
115
116 @pytest.mark.asyncio
117 async def test_state_push_gating_and_initial_snapshot_delivery():
118 - from python.helpers.state_monitor import get_state_monitor
119 - from python.helpers.state_snapshot import validate_snapshot_schema_v1
118 + from helpers.state_monitor import get_state_monitor
119 + from helpers.state_snapshot import validate_snapshot_schema_v1
120
121 manager, socketio = await _create_manager_with_socketio()
122
tests/test_state_sync_welcome_screen.py
+3 -3
@@ -10,7 +10,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
10 if str(PROJECT_ROOT) not in sys.path:
11 sys.path.insert(0, str(PROJECT_ROOT))
12
13 -from python.helpers.websocket_manager import WebSocketManager
13 +from helpers.websocket_manager import WebSocketManager
14
15 NAMESPACE = "/state_sync"
16
@@ -30,8 +30,8 @@ async def test_state_sync_handshake_and_initial_snapshot_work_with_no_selected_c
30 is null. We must still handshake and receive an initial `state_push` quickly (no hang).
31 """
32
33 - from python.helpers.state_snapshot import validate_snapshot_schema_v1
34 - from python.helpers.state_monitor import _reset_state_monitor_for_testing
33 + from helpers.state_snapshot import validate_snapshot_schema_v1
34 + from helpers.state_monitor import _reset_state_monitor_for_testing
35 from python.websocket_handlers.state_sync_handler import StateSyncHandler
36
37 socketio = FakeSocketIOServer()
tests/test_websocket_csrf.py
+1 -1
@@ -5,7 +5,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
5 if str(PROJECT_ROOT) not in sys.path:
6 sys.path.insert(0, str(PROJECT_ROOT))
7
8 -from python.helpers.websocket import validate_ws_origin
8 +from helpers.websocket import validate_ws_origin
9
10
11 def test_validate_ws_origin_allows_same_origin_with_explicit_port():
tests/test_websocket_handlers.py
+3 -3
@@ -8,7 +8,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
8 if str(PROJECT_ROOT) not in sys.path:
9 sys.path.insert(0, str(PROJECT_ROOT))
10
11 -from python.helpers.websocket import (
11 +from helpers.websocket import (
12 WebSocketHandler,
13 WebSocketResult,
14 SingletonInstantiationError,
@@ -140,9 +140,9 @@ def test_get_instance_returns_singleton():
140
141 @pytest.mark.asyncio
142 async def test_state_sync_handler_registers_and_routes_state_request():
143 - from python.helpers.websocket_manager import WebSocketManager
143 + from helpers.websocket_manager import WebSocketManager
144 from python.websocket_handlers.state_sync_handler import StateSyncHandler
145 - from python.helpers.state_monitor import _reset_state_monitor_for_testing
145 + from helpers.state_monitor import _reset_state_monitor_for_testing
146
147 _reset_state_monitor_for_testing()
148 StateSyncHandler._reset_instance_for_testing()
tests/test_websocket_harness.py
+1 -1
@@ -9,7 +9,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
9 if str(PROJECT_ROOT) not in sys.path:
10 sys.path.insert(0, str(PROJECT_ROOT))
11
12 -from python.helpers.websocket_manager import WebSocketManager
12 +from helpers.websocket_manager import WebSocketManager
13 from python.websocket_handlers.dev_websocket_test_handler import (
14 DevWebsocketTestHandler,
15 )
tests/test_websocket_manager.py
+9 -9
@@ -12,8 +12,8 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
12 if str(PROJECT_ROOT) not in sys.path:
13 sys.path.insert(0, str(PROJECT_ROOT))
14
15 -from python.helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
16 -from python.helpers.websocket_manager import (
15 +from helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
16 +from helpers.websocket_manager import (
17 WebSocketManager,
18 BUFFER_TTL,
19 DIAGNOSTIC_EVENT,
@@ -354,7 +354,7 @@ async def test_buffer_overflow_drops_oldest(monkeypatch):
354 await manager.handle_connect(NAMESPACE, "offline")
355 await manager.handle_disconnect(NAMESPACE, "offline")
356
357 - monkeypatch.setattr("python.helpers.websocket_manager.BUFFER_MAX_SIZE", 2)
357 + monkeypatch.setattr("helpers.websocket_manager.BUFFER_MAX_SIZE", 2)
358
359 await manager.emit_to(NAMESPACE, "offline", "event", {"idx": 0})
360 await manager.emit_to(NAMESPACE, "offline", "event", {"idx": 1})
@@ -385,7 +385,7 @@ async def test_expired_buffer_entries_are_discarded(monkeypatch):
385 socketio.emit.reset_mock()
386
387 monkeypatch.setattr(
388 - "python.helpers.websocket_manager._utcnow",
388 + "helpers.websocket_manager._utcnow",
389 lambda: future,
390 )
391 await manager.handle_connect(NAMESPACE, "sid-expired")
@@ -479,7 +479,7 @@ async def test_timestamps_are_timezone_aware():
479 assert info.connected_at.tzinfo is not None
480 assert info.last_activity.tzinfo is not None
481
482 - with patch("python.helpers.websocket_manager._utcnow") as mocked_now:
482 + with patch("helpers.websocket_manager._utcnow") as mocked_now:
483 mocked_now.return_value = info.last_activity
484 await manager.route_event(NAMESPACE, "unknown", {}, "sid-utc")
485 assert info.last_activity.tzinfo is not None
@@ -512,7 +512,7 @@ def test_register_handlers_warns_on_duplicates(monkeypatch):
512 warnings.append(message)
513
514 monkeypatch.setattr(
515 - "python.helpers.print_style.PrintStyle.warning", staticmethod(capture_warning)
515 + "helpers.print_style.PrintStyle.warning", staticmethod(capture_warning)
516 )
517
518 DuplicateHandler._reset_instance_for_testing()
@@ -804,13 +804,13 @@ def test_debug_logging_respects_runtime_flag(monkeypatch):
804 def capture(message: str) -> None:
805 logs.append(message)
806
807 - monkeypatch.setattr("python.helpers.print_style.PrintStyle.debug", staticmethod(capture))
808 - monkeypatch.setattr("python.helpers.websocket_manager.runtime.is_development", lambda: False)
807 + monkeypatch.setattr("helpers.print_style.PrintStyle.debug", staticmethod(capture))
808 + monkeypatch.setattr("helpers.websocket_manager.runtime.is_development", lambda: False)
809
810 manager._debug("should-not-log") # noqa: SLF001
811 assert logs == []
812
813 - monkeypatch.setattr("python.helpers.websocket_manager.runtime.is_development", lambda: True)
813 + monkeypatch.setattr("helpers.websocket_manager.runtime.is_development", lambda: True)
814 manager._debug("should-log") # noqa: SLF001
815 assert logs == ["should-log"]
816
tests/test_websocket_namespace_discovery.py
+8 -8
@@ -55,7 +55,7 @@ def _write_handler_module(path: Path, class_name: str, event_type: str) -> None:
55 "",
56 "from typing import Any",
57 "",
58 - "from python.helpers.websocket import WebSocketHandler",
58 + "from helpers.websocket import WebSocketHandler",
59 "",
60 f"class {class_name}(WebSocketHandler):",
61 " @classmethod",
@@ -80,7 +80,7 @@ def _write_handler_module(path: Path, class_name: str, event_type: str) -> None:
80
81
82 def test_discovery_supports_folder_entries_and_ignores_deeper_nesting(tmp_path: Path) -> None:
83 - from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
83 + from helpers.websocket_namespace_discovery import discover_websocket_namespaces
84
85 folder = tmp_path / "orders"
86 folder.mkdir()
@@ -100,7 +100,7 @@ def test_discovery_supports_folder_entries_and_ignores_deeper_nesting(tmp_path:
100
101
102 def test_discovery_folder_suffix_handler_stripped(tmp_path: Path) -> None:
103 - from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
103 + from helpers.websocket_namespace_discovery import discover_websocket_namespaces
104
105 folder = tmp_path / "sales_handler"
106 folder.mkdir()
@@ -115,8 +115,8 @@ def test_discovery_empty_folder_warns_and_treats_namespace_unregistered(tmp_path
115 from flask import Flask
116 import socketio
117
118 - from python.helpers.websocket_manager import WebSocketManager
119 - from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
118 + from helpers.websocket_manager import WebSocketManager
119 + from helpers.websocket_namespace_discovery import discover_websocket_namespaces
120 from run_ui import configure_websocket_namespaces
121
122 empty = tmp_path / "empty"
@@ -128,7 +128,7 @@ def test_discovery_empty_folder_warns_and_treats_namespace_unregistered(tmp_path
128 def _warn(message: str) -> None:
129 warnings.append(message)
130
131 - monkeypatch.setattr("python.helpers.print_style.PrintStyle.warning", staticmethod(_warn))
131 + monkeypatch.setattr("helpers.print_style.PrintStyle.warning", staticmethod(_warn))
132
133 discoveries = discover_websocket_namespaces(handlers_folder=str(tmp_path), include_root_default=False)
134 assert "/empty" not in {d.namespace for d in discoveries}
@@ -181,7 +181,7 @@ def test_discovery_empty_folder_warns_and_treats_namespace_unregistered(tmp_path
181
182
183 def test_discovery_invalid_modules_fail_fast_with_descriptive_errors(tmp_path: Path) -> None:
184 - from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
184 + from helpers.websocket_namespace_discovery import discover_websocket_namespaces
185
186 # 0 handlers in a *_handler.py module
187 (tmp_path / "bad_handler.py").write_text(
@@ -196,7 +196,7 @@ def test_discovery_invalid_modules_fail_fast_with_descriptive_errors(tmp_path: P
196 (tmp_path / "two_handler.py").write_text(
197 "\n".join(
198 [
199 - "from python.helpers.websocket import WebSocketHandler",
199 + "from helpers.websocket import WebSocketHandler",
200 "class A(WebSocketHandler):",
201 " @classmethod",
202 " def requires_auth(cls): return False",
tests/test_websocket_namespace_security.py
+18 -18
@@ -61,9 +61,9 @@ async def test_connect_security_is_computed_per_namespace_and_enforced(monkeypat
61 from flask import Flask
62 import socketio
63
64 - from python.helpers.websocket import WebSocketHandler
65 - from python.helpers.websocket_manager import WebSocketManager
66 - from python.helpers import runtime
64 + from helpers.websocket import WebSocketHandler
65 + from helpers.websocket_manager import WebSocketManager
66 + from helpers import runtime
67 from run_ui import configure_websocket_namespaces
68
69 class OpenHandler(WebSocketHandler):
@@ -93,7 +93,7 @@ async def test_connect_security_is_computed_per_namespace_and_enforced(monkeypat
93 OpenHandler._reset_instance_for_testing()
94 SecureHandler._reset_instance_for_testing()
95
96 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
96 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
97
98 webapp = Flask("test_websocket_namespace_security")
99 webapp.secret_key = "test-secret"
@@ -181,8 +181,8 @@ async def test_unknown_namespace_rejected_with_deterministic_connect_error_paylo
181 from flask import Flask
182 import socketio
183
184 - from python.helpers.websocket import WebSocketHandler
185 - from python.helpers.websocket_manager import WebSocketManager
184 + from helpers.websocket import WebSocketHandler
185 + from helpers.websocket_manager import WebSocketManager
186 from run_ui import configure_websocket_namespaces
187
188 class OpenHandler(WebSocketHandler):
@@ -248,9 +248,9 @@ async def test_secure_namespace_rejects_missing_auth_even_with_valid_csrf(monkey
248 from flask import Flask
249 import socketio
250
251 - from python.helpers.websocket import WebSocketHandler
252 - from python.helpers.websocket_manager import WebSocketManager
253 - from python.helpers import runtime
251 + from helpers.websocket import WebSocketHandler
252 + from helpers.websocket_manager import WebSocketManager
253 + from helpers import runtime
254 from run_ui import configure_websocket_namespaces
255
256 class SecureHandler(WebSocketHandler):
@@ -263,7 +263,7 @@ async def test_secure_namespace_rejects_missing_auth_even_with_valid_csrf(monkey
263
264 SecureHandler._reset_instance_for_testing()
265
266 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
266 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
267
268 webapp = Flask("test_ws_secure_missing_auth")
269 webapp.secret_key = "test-secret"
@@ -314,9 +314,9 @@ async def test_secure_namespace_rejects_invalid_csrf_cookie(monkeypatch) -> None
314 from flask import Flask
315 import socketio
316
317 - from python.helpers.websocket import WebSocketHandler
318 - from python.helpers.websocket_manager import WebSocketManager
319 - from python.helpers import runtime
317 + from helpers.websocket import WebSocketHandler
318 + from helpers.websocket_manager import WebSocketManager
319 + from helpers import runtime
320 from run_ui import configure_websocket_namespaces
321
322 class SecureHandler(WebSocketHandler):
@@ -329,7 +329,7 @@ async def test_secure_namespace_rejects_invalid_csrf_cookie(monkeypatch) -> None
329
330 SecureHandler._reset_instance_for_testing()
331
332 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
332 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
333
334 webapp = Flask("test_ws_secure_invalid_csrf")
335 webapp.secret_key = "test-secret"
@@ -381,9 +381,9 @@ async def test_csrf_required_without_auth_is_enforced(monkeypatch) -> None:
381 from flask import Flask
382 import socketio
383
384 - from python.helpers.websocket import WebSocketHandler
385 - from python.helpers.websocket_manager import WebSocketManager
386 - from python.helpers import runtime
384 + from helpers.websocket import WebSocketHandler
385 + from helpers.websocket_manager import WebSocketManager
386 + from helpers import runtime
387 from run_ui import configure_websocket_namespaces
388
389 class CsrfOnlyHandler(WebSocketHandler):
@@ -404,7 +404,7 @@ async def test_csrf_required_without_auth_is_enforced(monkeypatch) -> None:
404
405 CsrfOnlyHandler._reset_instance_for_testing()
406
407 - monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: None)
407 + monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: None)
408
409 webapp = Flask("test_ws_csrf_only")
410 webapp.secret_key = "test-secret"
tests/test_websocket_namespaces.py
+10 -10
@@ -13,8 +13,8 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
13 if str(PROJECT_ROOT) not in sys.path:
14 sys.path.insert(0, str(PROJECT_ROOT))
15
16 -from python.helpers.state_monitor import StateMonitor
17 -from python.helpers.websocket_manager import WebSocketManager
16 +from helpers.state_monitor import StateMonitor
17 +from helpers.websocket_manager import WebSocketManager
18
19
20 class FakeSocketIOServer:
@@ -108,8 +108,8 @@ async def test_namespace_isolation_state_sync_vs_dev_websocket_test() -> None:
108 from flask import Flask
109 import socketio
110
111 - from python.helpers.websocket import WebSocketHandler
112 - from python.helpers.websocket_manager import WebSocketManager
111 + from helpers.websocket import WebSocketHandler
112 + from helpers.websocket_manager import WebSocketManager
113 from run_ui import configure_websocket_namespaces
114
115 class StateHandler(WebSocketHandler):
@@ -220,8 +220,8 @@ async def test_diagnostics_include_source_namespace_and_deliver_on_dev_namespace
220 but must include `sourceNamespace` identifying the origin namespace.
221 """
222
223 - from python.helpers.websocket import WebSocketHandler
224 - from python.helpers.websocket_manager import DIAGNOSTIC_EVENT, WebSocketManager
223 + from helpers.websocket import WebSocketHandler
224 + from helpers.websocket_manager import DIAGNOSTIC_EVENT, WebSocketManager
225
226 class DummyHandler(WebSocketHandler):
227 @classmethod
@@ -267,7 +267,7 @@ def test_namespace_discovery_maps_core_handlers_to_expected_namespaces() -> None
267 (no cross-registration).
268 """
269
270 - from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
270 + from helpers.websocket_namespace_discovery import discover_websocket_namespaces
271
272 discoveries = discover_websocket_namespaces(
273 handlers_folder="python/websocket_handlers",
@@ -309,7 +309,7 @@ async def test_route_event_dispatches_only_within_connected_namespace_and_result
309 CONTRACT.NS.ROUTING: inbound routing is restricted to handlers in the connected namespace.
310 """
311
312 - from python.helpers.websocket import WebSocketHandler
312 + from helpers.websocket import WebSocketHandler
313
314 socketio = FakeSocketIOServer()
315 manager = WebSocketManager(socketio, threading.RLock())
@@ -365,7 +365,7 @@ async def test_lifecycle_broadcasts_deliver_only_within_the_namespace() -> None:
365 CONTRACT.NS.DELIVERY: lifecycle broadcasts are namespace-scoped.
366 """
367
368 - from python.helpers.websocket_manager import (
368 + from helpers.websocket_manager import (
369 LIFECYCLE_CONNECT_EVENT,
370 LIFECYCLE_DISCONNECT_EVENT,
371 )
@@ -422,7 +422,7 @@ async def test_request_semantics_no_handlers_and_timeouts_are_namespace_scoped_a
422 CONTRACT.REQUEST.RESULTS + CONTRACT.REQUEST.RESULTS.ORDERING + CONTRACT.NS.ROUTING.
423 """
424
425 - from python.helpers.websocket import WebSocketHandler
425 + from helpers.websocket import WebSocketHandler
426
427 socketio = FakeSocketIOServer()
428 manager = WebSocketManager(socketio, threading.RLock())
tests/test_websocket_namespaces_integration.py
+2 -2
@@ -51,8 +51,8 @@ async def test_unregistered_namespace_connection_fails_with_unknown_namespace_co
51 from flask import Flask
52 import socketio
53
54 - from python.helpers.websocket import WebSocketHandler
55 - from python.helpers.websocket_manager import WebSocketManager
54 + from helpers.websocket import WebSocketHandler
55 + from helpers.websocket_manager import WebSocketManager
56 from run_ui import configure_websocket_namespaces
57
58 class OpenHandler(WebSocketHandler):
tests/test_websocket_root_namespace.py
+4 -4
@@ -51,8 +51,8 @@ async def test_root_namespace_request_style_calls_resolve_with_no_handlers() ->
51 from flask import Flask
52 import socketio
53
54 - from python.helpers.websocket import WebSocketHandler
55 - from python.helpers.websocket_manager import WebSocketManager
54 + from helpers.websocket import WebSocketHandler
55 + from helpers.websocket_manager import WebSocketManager
56 from run_ui import configure_websocket_namespaces
57
58 app = Flask("test_ws_root_namespace")
@@ -124,8 +124,8 @@ async def test_root_namespace_fire_and_forget_does_not_invoke_application_handle
124 from flask import Flask
125 import socketio
126
127 - from python.helpers.websocket import WebSocketHandler
128 - from python.helpers.websocket_manager import WebSocketManager
127 + from helpers.websocket import WebSocketHandler
128 + from helpers.websocket_manager import WebSocketManager
129 from run_ui import configure_websocket_namespaces
130
131 app = Flask("test_ws_root_fire_and_forget")
tests/test_webui_extension_surfaces.py
+1 -1
@@ -14,7 +14,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[2]
14 if str(PROJECT_ROOT) not in sys.path:
15 sys.path.insert(0, str(PROJECT_ROOT))
16
17 -from python.api.load_webui_extensions import LoadWebuiExtensions
17 +from api.load_webui_extensions import LoadWebuiExtensions
18
19
20 SURFACE_SCENARIOS: list[tuple[str, str]] = [
tools/a2a_chat.py renamed
+3 -3
@@ -1,6 +1,6 @@
1 -from python.helpers.tool import Tool, Response
2 -from python.helpers.print_style import PrintStyle
3 -from python.helpers.fasta2a_client import connect_to_agent, is_client_available
1 +from helpers.tool import Tool, Response
2 +from helpers.print_style import PrintStyle
3 +from helpers.fasta2a_client import connect_to_agent, is_client_available
4
5
6 class A2AChatTool(Tool):
tools/browser._py renamed
+4 -4
@@ -1,10 +1,10 @@
1 # import asyncio
2 # from dataclasses import dataclass
3 # import time
4 -# from python.helpers.tool import Tool, Response
5 -# from python.helpers import files, rfc_exchange
6 -# from python.helpers.print_style import PrintStyle
7 -# from python.helpers.browser import Browser as BrowserManager
4 +# from helpers.tool import Tool, Response
5 +# from helpers import files, rfc_exchange
6 +# from helpers.print_style import PrintStyle
7 +# from helpers.browser import Browser as BrowserManager
8 # import uuid
9
10
tools/browser_agent.py renamed
+8 -8
@@ -4,16 +4,16 @@ from typing import Optional, cast
4 from agent import Agent, InterventionException
5 from pathlib import Path
6
7 -from python.helpers.tool import Tool, Response
8 -from python.helpers import files, defer, persist_chat, strings
9 -from python.helpers.browser_use import browser_use # type: ignore[attr-defined]
10 -from python.helpers.print_style import PrintStyle
11 -from python.helpers.playwright import ensure_playwright_binary
12 -from python.helpers.secrets import get_secrets_manager
13 -from python.extensions.message_loop_start._10_iteration_no import get_iter_no
7 +from helpers.tool import Tool, Response
8 +from helpers import files, defer, persist_chat, strings
9 +from helpers.browser_use import browser_use # type: ignore[attr-defined]
10 +from helpers.print_style import PrintStyle
11 +from helpers.playwright import ensure_playwright_binary
12 +from helpers.secrets import get_secrets_manager
13 +from extensions.python.message_loop_start._10_iteration_no import get_iter_no
14 from pydantic import BaseModel
15 import uuid
16 -from python.helpers.dirty_json import DirtyJson
16 +from helpers.dirty_json import DirtyJson
17
18
19 class State:
tools/browser_do._py renamed
+3 -3
@@ -1,7 +1,7 @@
1 # import asyncio
2 -# from python.helpers.tool import Tool, Response
3 -# from python.tools.browser import Browser
4 -# from python.helpers.browser import NoPageError
2 +# from helpers.tool import Tool, Response
3 +# from tools.browser import Browser
4 +# from helpers.browser import NoPageError
5 # import asyncio
6
7
tools/browser_open._py renamed
+3 -3
@@ -1,7 +1,7 @@
1 # import asyncio
2 -# from python.helpers.tool import Tool, Response
3 -# from python.tools import browser
4 -# from python.tools.browser import Browser
2 +# from helpers.tool import Tool, Response
3 +# from tools import browser
4 +# from tools.browser import Browser
5
6
7 # class BrowserOpen(Browser):
tools/call_subordinate.py renamed
+2 -2
@@ -1,7 +1,7 @@
1 from agent import Agent, UserMessage
2 -from python.helpers.tool import Tool, Response
2 +from helpers.tool import Tool, Response
3 from initialize import initialize_agent
4 -from python.extensions.hist_add_tool_result import _90_save_tool_call_file as save_tool_call_file
4 +from extensions.python.hist_add_tool_result import _90_save_tool_call_file as save_tool_call_file
5
6
7 class Delegation(Tool):
tools/code_execution_tool.py renamed
+7 -7
@@ -2,13 +2,13 @@ import asyncio
2 from dataclasses import dataclass
3 import shlex
4 import time
5 -from python.helpers.tool import Tool, Response
6 -from python.helpers import files, rfc_exchange, projects, runtime, settings
7 -from python.helpers.print_style import PrintStyle
8 -from python.helpers.shell_local import LocalInteractiveSession
9 -from python.helpers.shell_ssh import SSHInteractiveSession
10 -from python.helpers.strings import truncate_text as truncate_text_string
11 -from python.helpers.messages import truncate_text as truncate_text_agent
5 +from helpers.tool import Tool, Response
6 +from helpers import files, rfc_exchange, projects, runtime, settings
7 +from helpers.print_style import PrintStyle
8 +from helpers.shell_local import LocalInteractiveSession
9 +from helpers.shell_ssh import SSHInteractiveSession
10 +from helpers.strings import truncate_text as truncate_text_string
11 +from helpers.messages import truncate_text as truncate_text_agent
12 import re
13
14 # Timeouts for python, nodejs, and terminal runtimes.
tools/document_query.py renamed
+2 -2
@@ -1,7 +1,7 @@
1 import asyncio
2
3 -from python.helpers.tool import Tool, Response
4 -from python.helpers.document_query import DocumentQueryHelper
3 +from helpers.tool import Tool, Response
4 +from helpers.document_query import DocumentQueryHelper
5
6
7 class DocumentQueryTool(Tool):
tools/input.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 from agent import Agent, UserMessage
2 -from python.helpers.tool import Tool, Response
3 -from python.tools.code_execution_tool import CodeExecution
2 +from helpers.tool import Tool, Response
3 +from tools.code_execution_tool import CodeExecution
4
5
6 class Input(Tool):
tools/knowledge_tool._py renamed
+3 -3
@@ -1,10 +1,10 @@
1 import asyncio
2 -from python.helpers import dotenv, perplexity_search, duckduckgo_search
2 +from helpers import dotenv, perplexity_search, duckduckgo_search
3 from plugins.memory.helpers.memory import Memory
4 from plugins.memory.tools.memory_load import DEFAULT_THRESHOLD as DEFAULT_MEMORY_THRESHOLD
5
6 -from python.helpers.tool import Tool, Response
7 -from python.helpers.document_query import DocumentQueryHelper
6 +from helpers.tool import Tool, Response
7 +from helpers.document_query import DocumentQueryHelper
8
9 SEARCH_ENGINE_RESULTS = 10
10
tools/notify_user.py renamed
+2 -2
@@ -1,6 +1,6 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2 from agent import AgentContext
3 -from python.helpers.notification import NotificationPriority, NotificationType
3 +from helpers.notification import NotificationPriority, NotificationType
4
5 class NotifyUserTool(Tool):
6
tools/response.py renamed
+1 -1
@@ -1,4 +1,4 @@
1 -from python.helpers.tool import Tool, Response
1 +from helpers.tool import Tool, Response
2
3
4 class ResponseTool(Tool):
tools/scheduler.py renamed
+4 -4
@@ -3,14 +3,14 @@ from datetime import datetime
3 import json
4 import random
5 import re
6 -from python.helpers.tool import Tool, Response
7 -from python.helpers.task_scheduler import (
6 +from helpers.tool import Tool, Response
7 +from helpers.task_scheduler import (
8 TaskScheduler, ScheduledTask, AdHocTask, PlannedTask,
9 serialize_task, TaskState, TaskSchedule, TaskPlan, parse_datetime, serialize_datetime
10 )
11 from agent import AgentContext
12 -from python.helpers import persist_chat
13 -from python.helpers.projects import get_context_project_name, load_basic_project_data
12 +from helpers import persist_chat
13 +from helpers.projects import get_context_project_name, load_basic_project_data
14
15 DEFAULT_WAIT_TIMEOUT = 300
16
tools/search_engine.py renamed
+5 -5
@@ -1,10 +1,10 @@
1 import os
2 import asyncio
3 -from python.helpers import dotenv, perplexity_search, duckduckgo_search
4 -from python.helpers.tool import Tool, Response
5 -from python.helpers.print_style import PrintStyle
6 -from python.helpers.errors import handle_error
7 -from python.helpers.searxng import search as searxng
3 +from helpers import dotenv, perplexity_search, duckduckgo_search
4 +from helpers.tool import Tool, Response
5 +from helpers.print_style import PrintStyle
6 +from helpers.errors import handle_error
7 +from helpers.searxng import search as searxng
8
9 SEARCH_ENGINE_RESULTS = 10
10
tools/skills_tool.py renamed
+3 -3
@@ -3,9 +3,9 @@ from __future__ import annotations
3 from pathlib import Path
4 from typing import List
5
6 -from python.helpers.tool import Tool, Response
7 -from python.helpers import projects, files, file_tree
8 -from python.helpers import skills as skills_helper, runtime
6 +from helpers.tool import Tool, Response
7 +from helpers import projects, files, file_tree
8 +from helpers import skills as skills_helper, runtime
9
10
11 DATA_NAME_LOADED_SKILLS = "loaded_skills"
tools/unknown.py renamed
+2 -2
@@ -1,5 +1,5 @@
1 -from python.helpers.tool import Tool, Response
2 -from python.extensions.system_prompt._10_system_prompt import (
1 +from helpers.tool import Tool, Response
2 +from extensions.python.system_prompt._10_system_prompt import (
3 get_tools_prompt,
4 )
5
tools/vision_load.py renamed
+4 -4
@@ -1,9 +1,9 @@
1 import base64
2 -from python.helpers.print_style import PrintStyle
3 -from python.helpers.tool import Tool, Response
4 -from python.helpers import runtime, files, images
2 +from helpers.print_style import PrintStyle
3 +from helpers.tool import Tool, Response
4 +from helpers import runtime, files, images
5 from mimetypes import guess_type
6 -from python.helpers import history
6 +from helpers import history
7
8 # image optimization and token estimation for context window
9 MAX_PIXELS = 768_000
tools/wait.py renamed
+4 -4
@@ -1,9 +1,9 @@
1 import asyncio
2 from datetime import datetime, timedelta, timezone
3 -from python.helpers.tool import Tool, Response
4 -from python.helpers.print_style import PrintStyle
5 -from python.helpers.wait import managed_wait
6 -from python.helpers.localization import Localization
3 +from helpers.tool import Tool, Response
4 +from helpers.print_style import PrintStyle
5 +from helpers.wait import managed_wait
6 +from helpers.localization import Localization
7
8 class WaitTool(Tool):
9
webui/components/sidebar/chats/chats-store.js
-7
@@ -98,8 +98,6 @@ const model = {
98 return;
99 }
100
101 - console.log("Deleting chat with ID:", id);
102 -
101 try {
102 // Switch to another context if deleting current
103 if (this.selected === id) {
@@ -111,11 +109,6 @@ const model = {
109
110 // Update the UI - remove from contexts
111 const updatedContexts = this.contexts.filter((ctx) => ctx.id !== id);
114 - console.log(
115 - "Updated contexts after deletion:",
116 - JSON.stringify(updatedContexts.map((c) => ({ id: c.id, name: c.name })))
117 - );
118 -
112 // Force UI update by creating a new array
113 this.contexts = [...updatedContexts];
114
webui/js/api.js
+51 -5
@@ -31,6 +31,25 @@ export async function callJsonApi(endpoint, data) {
31 * @returns {Promise<Response>} The fetch response
32 */
33 export async function fetchApi(url, request) {
34 + async function _getExtensions() {
35 + try {
36 + return await import("./extensions.js");
37 + } catch {
38 + return null;
39 + }
40 + }
41 +
42 + /**
43 + * @param {string} apiUrl
44 + * @returns {Promise<boolean>}
45 + */
46 + async function _shouldCallApiExtensions(apiUrl) {
47 + const extensions = await _getExtensions();
48 + if (!extensions) return false;
49 + const excluded = extensions.API_EXTENSION_EXCLUDED_ENDPOINTS;
50 + return !(excluded instanceof Set && excluded.has(apiUrl));
51 + }
52 +
53 async function _wrap(retry) {
54 // get the CSRF token
55 const token = await getCsrfToken();
@@ -46,21 +65,48 @@ export async function fetchApi(url, request) {
65
66 // perform the fetch with the updated request
67 const apiUrl = url.startsWith('/api/') || url.startsWith('api/') ? `/${url.replace(/^\/+/, '')}` : `/api/${url.replace(/^\/+/, '')}`;
49 - const response = await fetch(apiUrl, finalRequest);
68 +
69 + /** @type {{ url: string, apiUrl: string, request: any, response: Response | null, retry: boolean }} */
70 + const ctx = {
71 + url,
72 + apiUrl,
73 + request: finalRequest,
74 + response: null,
75 + retry,
76 + };
77 +
78 + if (await _shouldCallApiExtensions(apiUrl)) {
79 + const extensions = await _getExtensions();
80 + if (extensions) {
81 + await extensions.callJsExtensions("api_call_before", ctx);
82 + }
83 + }
84 +
85 + const response = ctx.response || await fetch(ctx.apiUrl, ctx.request);
86 + ctx.response = response;
87 +
88 + if (await _shouldCallApiExtensions(apiUrl)) {
89 + const extensions = await _getExtensions();
90 + if (extensions) {
91 + await extensions.callJsExtensions("api_call_after", ctx);
92 + }
93 + }
94 +
95 + const finalResponse = ctx.response;
96
97 // check if there was an CSRF error
52 - if (response.status === 403 && retry) {
98 + if (finalResponse.status === 403 && retry) {
99 // retry the request with new token
100 csrfToken = null;
101 return await _wrap(false);
56 - } else if (response.redirected && response.url.endsWith("/login")) {
102 + } else if (finalResponse.redirected && finalResponse.url.endsWith("/login")) {
103 // redirect to login
58 - window.location.href = response.url;
104 + window.location.href = finalResponse.url;
105 return;
106 }
107
108 // return the response
63 - return response;
109 + return finalResponse;
110 }
111
112 // perform the request
webui/js/extensions.js
+4
@@ -21,6 +21,10 @@ import * as cache from "./cache.js";
21 const JS_CACHE_AREA = "frontend_extensions_js(extensions)(plugins)";
22 const HTML_CACHE_AREA = "frontend_extensions_html(extensions)(plugins)";
23
24 +export const API_EXTENSION_EXCLUDED_ENDPOINTS = new Set([
25 + "/api/load_webui_extensions",
26 +]);
27 +
28 export function invalidateCache() {
29 cache.clear(JS_CACHE_AREA);
30 cache.clear(HTML_CACHE_AREA);
webui/js/messages.js
+1 -1
@@ -122,7 +122,7 @@ export async function getMessageHandler(type) {
122
123 async function getHandlerFromExtensions(type){
124 const extData = { type: type, handler: undefined }
125 - await callJsExtensions("getMessageHandler", extData);
125 + await callJsExtensions("get_message_handler", extData);
126 // return handler from extensions
127 if(typeof extData.handler == "function") return extData.handler;
128 //not set by extensions, return default