feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
This commit is contained in:
8
services/toolcore/executors/__init__.py
Normal file
8
services/toolcore/executors/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .http_executor import HTTPExecutor
|
||||
from .python_executor import PythonExecutor
|
||||
|
||||
__all__ = ['HTTPExecutor', 'PythonExecutor']
|
||||
|
||||
|
||||
|
||||
|
||||
104
services/toolcore/executors/http_executor.py
Normal file
104
services/toolcore/executors/http_executor.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import httpx
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
from models import ToolCallResult
|
||||
|
||||
class HTTPExecutor:
|
||||
"""Execute tools via HTTP calls"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = httpx.AsyncClient()
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
tool_id: str,
|
||||
target: str,
|
||||
args: Dict[str, Any],
|
||||
context: Dict[str, Any],
|
||||
timeout: int = 30
|
||||
) -> ToolCallResult:
|
||||
"""
|
||||
Execute tool via HTTP POST
|
||||
|
||||
Sends args + context to target URL
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
payload = {
|
||||
"args": args,
|
||||
"context": context
|
||||
}
|
||||
|
||||
try:
|
||||
response = await self.client.post(
|
||||
target,
|
||||
json=payload,
|
||||
timeout=timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
|
||||
return ToolCallResult(
|
||||
ok=True,
|
||||
result=result,
|
||||
tool_id=tool_id,
|
||||
latency_ms=latency_ms
|
||||
)
|
||||
|
||||
except httpx.ConnectError as e:
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
error_msg = f"Connection failed: {target} ({str(e)})"
|
||||
print(f"❌ {error_msg}")
|
||||
|
||||
return ToolCallResult(
|
||||
ok=False,
|
||||
error=error_msg,
|
||||
tool_id=tool_id,
|
||||
latency_ms=latency_ms
|
||||
)
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
error_msg = f"HTTP {e.response.status_code}: {e.response.text}"
|
||||
print(f"❌ Tool {tool_id} failed: {error_msg}")
|
||||
|
||||
return ToolCallResult(
|
||||
ok=False,
|
||||
error=error_msg,
|
||||
tool_id=tool_id,
|
||||
latency_ms=latency_ms
|
||||
)
|
||||
|
||||
except httpx.TimeoutException:
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
error_msg = f"Timeout after {timeout}s"
|
||||
print(f"⏱️ Tool {tool_id} timeout")
|
||||
|
||||
return ToolCallResult(
|
||||
ok=False,
|
||||
error=error_msg,
|
||||
tool_id=tool_id,
|
||||
latency_ms=latency_ms
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
error_msg = f"Unexpected error: {str(e)}"
|
||||
print(f"❌ Tool {tool_id} error: {error_msg}")
|
||||
|
||||
return ToolCallResult(
|
||||
ok=False,
|
||||
error=error_msg,
|
||||
tool_id=tool_id,
|
||||
latency_ms=latency_ms
|
||||
)
|
||||
|
||||
async def close(self):
|
||||
"""Close HTTP client"""
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
|
||||
|
||||
68
services/toolcore/executors/python_executor.py
Normal file
68
services/toolcore/executors/python_executor.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import time
|
||||
import importlib
|
||||
from typing import Dict, Any
|
||||
from models import ToolCallResult
|
||||
|
||||
class PythonExecutor:
|
||||
"""
|
||||
Execute tools via Python function calls
|
||||
|
||||
Phase 3: Stub implementation
|
||||
Phase 4: Full implementation with sandboxing
|
||||
"""
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
tool_id: str,
|
||||
target: str,
|
||||
args: Dict[str, Any],
|
||||
context: Dict[str, Any],
|
||||
timeout: int = 30
|
||||
) -> ToolCallResult:
|
||||
"""
|
||||
Execute Python function
|
||||
|
||||
target format: "module.path:function_name"
|
||||
Example: "tools.projects:list_projects"
|
||||
|
||||
Phase 3: Stub - returns placeholder
|
||||
Phase 4: Actually import and call function with proper sandboxing
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
print(f"ℹ️ Python executor (stub): {tool_id} → {target}")
|
||||
|
||||
# TODO Phase 4: Implement proper Python execution
|
||||
# 1. Parse target (module:function)
|
||||
# 2. Import module dynamically
|
||||
# 3. Call function with args
|
||||
# 4. Add sandboxing/security
|
||||
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
|
||||
return ToolCallResult(
|
||||
ok=False,
|
||||
error="Python executor not implemented in Phase 3",
|
||||
tool_id=tool_id,
|
||||
latency_ms=latency_ms
|
||||
)
|
||||
|
||||
# Stub for Phase 4 implementation:
|
||||
"""
|
||||
async def execute_real(self, tool_id, target, args, context, timeout):
|
||||
module_path, func_name = target.split(':')
|
||||
module = importlib.import_module(module_path)
|
||||
func = getattr(module, func_name)
|
||||
|
||||
# Call function with timeout
|
||||
result = await asyncio.wait_for(
|
||||
func(**args, context=context),
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
return ToolCallResult(ok=True, result=result, tool_id=tool_id)
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user