Files
engine/modules/agent_api/agent_server.h
ozan 7fb933a4b9 fix(agent_api): wire up module lifecycle — auto-start, poll, port 4329
- Call check_cmdline_and_start() from register_types so --agent-api flag works
- Connect poll() to SceneTree::process_frame via deferred callable so
  screenshot/depth/command requests get serviced on the main thread
- Default port changed to 4329 to avoid conflict with C# AgentServer on 4328
- Clean disconnect on stop()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 05:01:38 +01:00

168 lines
5.2 KiB
C++

#pragma once
#include "core/object/class_db.h"
#include "core/object/object.h"
#include "core/os/mutex.h"
#include "core/os/thread.h"
#include "core/io/tcp_server.h"
#include "core/io/stream_peer_tcp.h"
#include "core/string/ustring.h"
#include "core/variant/dictionary.h"
#include "core/variant/variant.h"
#include "core/io/json.h"
class AgentServer : public Object {
GDCLASS(AgentServer, Object);
static AgentServer *singleton;
public:
static AgentServer *get_singleton() { return singleton; }
// HTTP request parsed structure.
struct HTTPRequest {
String method;
String path;
HashMap<String, String> query_params;
HashMap<String, String> headers;
String body;
};
// HTTP response.
struct HTTPResponse {
int status_code = 200;
String content_type = "application/json";
String body;
Vector<uint8_t> binary_body;
bool is_binary = false;
};
private:
Ref<TCPServer> tcp_server;
Thread server_thread;
bool running = false;
int port = 4329;
String auth_token;
// SSE clients for log/event streaming.
struct SSEClient {
Ref<StreamPeerTCP> peer;
String stream_type; // "logs" or "events"
uint64_t connected_at = 0;
};
Vector<SSEClient> sse_clients;
Mutex sse_mutex;
// Screenshot request coordination (main thread captures, server thread serves).
struct ScreenshotRequest {
bool pending = false;
bool ready = false;
int width = 0;
int height = 0;
String format = "png";
int quality = 90;
String camera_name;
bool annotate = false;
bool diff = false;
Vector<uint8_t> result_data;
String result_content_type;
};
ScreenshotRequest screenshot_request;
Mutex screenshot_mutex;
// Depth buffer request.
struct DepthRequest {
bool pending = false;
bool ready = false;
Vector<uint8_t> result_data;
};
DepthRequest depth_request;
Mutex depth_mutex;
// Command execution (must happen on main thread).
struct CommandRequest {
bool pending = false;
bool ready = false;
String expression;
String result;
bool success = false;
};
CommandRequest command_request;
Mutex command_mutex;
// Server thread function.
static void _server_thread_func(void *p_userdata);
void _server_loop();
void _connect_to_scene_tree();
void _handle_connection(Ref<StreamPeerTCP> p_peer);
// HTTP parsing.
HTTPRequest _parse_http_request(const String &p_raw);
void _send_response(Ref<StreamPeerTCP> p_peer, const HTTPResponse &p_response);
void _send_sse_headers(Ref<StreamPeerTCP> p_peer);
// Route handlers.
HTTPResponse _handle_request(const HTTPRequest &p_request);
// Endpoint handlers.
HTTPResponse _handle_health(const HTTPRequest &p_request);
HTTPResponse _handle_screenshot(const HTTPRequest &p_request);
HTTPResponse _handle_depth(const HTTPRequest &p_request);
HTTPResponse _handle_segmentation(const HTTPRequest &p_request);
HTTPResponse _handle_state(const HTTPRequest &p_request);
HTTPResponse _handle_nodes(const HTTPRequest &p_request, const String &p_node_path);
HTTPResponse _handle_command(const HTTPRequest &p_request);
HTTPResponse _handle_command_batch(const HTTPRequest &p_request);
HTTPResponse _handle_input(const HTTPRequest &p_request);
HTTPResponse _handle_logs(const HTTPRequest &p_request);
HTTPResponse _handle_logs_stream(const HTTPRequest &p_request, Ref<StreamPeerTCP> p_peer);
HTTPResponse _handle_perf(const HTTPRequest &p_request);
HTTPResponse _handle_events_stream(const HTTPRequest &p_request, Ref<StreamPeerTCP> p_peer);
HTTPResponse _handle_events_history(const HTTPRequest &p_request);
HTTPResponse _handle_world_raycast(const HTTPRequest &p_request);
HTTPResponse _handle_world_spawn(const HTTPRequest &p_request);
HTTPResponse _handle_world_destroy(const HTTPRequest &p_request);
HTTPResponse _handle_world_physics(const HTTPRequest &p_request);
HTTPResponse _handle_world_navmesh(const HTTPRequest &p_request);
HTTPResponse _handle_save(const HTTPRequest &p_request);
HTTPResponse _handle_load(const HTTPRequest &p_request);
HTTPResponse _handle_saves(const HTTPRequest &p_request);
HTTPResponse _handle_config(const HTTPRequest &p_request);
HTTPResponse _handle_console(const HTTPRequest &p_request);
HTTPResponse _handle_commands_list(const HTTPRequest &p_request);
HTTPResponse _handle_assets(const HTTPRequest &p_request);
HTTPResponse _handle_assets_reload(const HTTPRequest &p_request);
HTTPResponse _handle_vision_boxes(const HTTPRequest &p_request);
HTTPResponse _handle_vision_minimap(const HTTPRequest &p_request);
// Auth helpers.
bool _check_auth(const HTTPRequest &p_request);
// Utility.
static String _status_text(int p_code);
static String _json_response(const Dictionary &p_dict);
static String _json_error(int p_code, const String &p_message);
static Dictionary _node_to_dict(Node *p_node, int p_depth, int p_max_depth, const String &p_filter);
protected:
static void _bind_methods();
public:
AgentServer();
~AgentServer();
void start(int p_port = 4329, const String &p_token = "");
void stop();
bool is_running() const { return running; }
int get_port() const { return port; }
// Called from main thread each frame to service requests.
void poll();
// Push SSE data to connected clients.
void push_sse(const String &p_stream_type, const String &p_event, const String &p_data);
// Auto-start from command line.
static void check_cmdline_and_start();
};