feat(api): Add lightweight HTTP server for API hooks

This commit is contained in:
2026-02-23 12:11:01 -05:00
parent bd7ccf3a07
commit 44c2585f95
3 changed files with 68 additions and 0 deletions

41
api_hooks.py Normal file
View File

@@ -0,0 +1,41 @@
import json
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
import logging
class HookHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/status':
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({'status': 'ok'}).encode('utf-8'))
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
logging.info("Hook API: " + format % args)
class HookServer:
def __init__(self, app, port=8999):
self.app = app
self.port = port
self.server = None
self.thread = None
def start(self):
if not getattr(self.app, 'test_hooks_enabled', False):
return
self.server = HTTPServer(('127.0.0.1', self.port), HookHandler)
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
self.thread.start()
logging.info(f"Hook server started on port {self.port}")
def stop(self):
if self.server:
self.server.shutdown()
self.server.server_close()
if self.thread:
self.thread.join()
logging.info("Hook server stopped")

5
gui.py
View File

@@ -25,6 +25,7 @@ import shell_runner
import session_logger
import project_manager
import theme
import api_hooks
CONFIG_PATH = Path("config.toml")
PROVIDERS = ["gemini", "anthropic"]
@@ -396,6 +397,7 @@ class App:
def __init__(self):
self.config = load_config()
self.test_hooks_enabled = '--enable-test-hooks' in sys.argv or os.environ.get('SLOP_TEST_HOOKS') == '1'
self.hook_server = api_hooks.HookServer(self)
# ---- global settings from config.toml ----
ai_cfg = self.config.get("ai", {})
@@ -2058,6 +2060,8 @@ class App:
self._rebuild_discussion_selector()
self._fetch_models(self.current_provider)
self.hook_server.start()
while dpg.is_dearpygui_running():
# Show any pending confirmation dialog on the main thread safely
with self._pending_dialog_lock:
@@ -2184,6 +2188,7 @@ class App:
dpg.save_init_file("dpg_layout.ini")
session_logger.close_session()
ai_client.cleanup() # Destroy active API caches to stop billing
self.hook_server.stop()
dpg.destroy_context()

View File

@@ -4,6 +4,11 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
import pytest
from unittest.mock import patch
import gui
import api_hooks
import urllib.request
import json
import threading
import time
def test_hooks_enabled_via_cli():
with patch.object(sys, 'argv', ['gui.py', '--enable-test-hooks']):
@@ -22,3 +27,20 @@ def test_hooks_enabled_via_env():
with patch.dict(os.environ, {'SLOP_TEST_HOOKS': '1'}):
app = gui.App()
assert app.test_hooks_enabled is True
def test_ipc_server_starts_and_responds():
app_mock = gui.App()
app_mock.test_hooks_enabled = True
server = api_hooks.HookServer(app_mock, port=8999)
server.start()
# Wait for server to start
time.sleep(0.5)
try:
req = urllib.request.Request("http://127.0.0.1:8999/status")
with urllib.request.urlopen(req) as response:
assert response.status == 200
data = json.loads(response.read().decode())
assert data.get("status") == "ok"
finally:
server.stop()