|
NeoGraph 0.16.0
A C++20 Graph Agent Engine Library — LangGraph for C++
|
HTTP-less, stdio-first ACP server hosting a NeoGraph engine. More...
#include <server.h>
Public Member Functions | |
| ACPServer (neograph::graph::GraphExecution execution, neograph::json info, std::shared_ptr< ACPGraphAdapter > adapter={}) | |
| Preferred constructor: restrict the host to graph execution. | |
| ACPServer (std::shared_ptr< neograph::graph::GraphEngine > engine, neograph::json info, std::shared_ptr< ACPGraphAdapter > adapter={}) | |
| void | attach_client (std::shared_ptr< ACPClient > c) |
| Install a pre-constructed client and bind it to this server. Useful when the engine's nodes captured a client at compile() time before the server existed: pass that same shared_ptr here to make it usable. Replaces any previously cached client. | |
| neograph::json | call_client (std::string method, neograph::json params, std::chrono::milliseconds timeout=std::chrono::seconds(30)) |
Issue a JSON-RPC request to the connected client and block until a response arrives. Used internally by ACPClient; exposed publicly so user code can issue protocol extensions. fs/*, terminal/* and elicitation/* require the matching client capability. When params.sessionId names a session with a prompt turn in flight, cancelling that turn ends the wait (after the grace described at stop) with neograph::graph::CancelledException. timeout of std::chrono::milliseconds::max() waits without a deadline. Throws ACPClientError on an error envelope, and std::runtime_error on transport not yet connected, on a missing capability and on timeout. | |
| AgentCapabilities & | capabilities () |
| Mutable access to the capabilities advertised in initialize. Defaults: session.resume=true, load_session=false, no prompt extras, no MCP transports (stdio is required by spec — its presence is implicit). | |
| std::shared_ptr< ACPClient > | client () |
| Handle for issuing agent→client requests (fs/*, etc.). On first call lazily creates a client bound to this server and caches it. Subsequent calls return the same instance. Nodes in the graph capture this handle to talk back to the editor. | |
| neograph::json | handle_message (const neograph::json &envelope) |
| Process exactly one parsed JSON-RPC envelope. Returns: | |
| bool | is_running () const |
True while run() is actively reading from its input stream. Set on entry, cleared on exit. Mirrors neograph::a2a::A2AServer::is_running so a generic supervisor can poll either without protocol-specific casing. | |
| void | run () |
| Convenience overload — uses std::cin / std::cout. | |
| void | run (std::istream &in, std::ostream &out) |
Drive the server loop reading newline-delimited JSON-RPC envelopes from in and writing them to out. Returns when in reaches EOF or stop() is called from another thread. | |
| std::vector< std::shared_ptr< neograph::Tool > > | session_tools (std::string_view session_id) const |
| The tools of a session's client-supplied MCP servers; see ACPClient::session_tools. | |
| void | set_eof_grace (std::chrono::milliseconds grace) |
| How long run() lets in-flight prompts finish after its input reaches EOF (the client closed the connection) before it cancels them. Defaults to one second. | |
| void | set_max_inflight_prompts (std::size_t limit) |
| Bound concurrently executing sessions; reject excess prompts before any graph node runs (JSON-RPC -32603 with data.reason "overloaded"). Defaults to 32. | |
| void | set_notification_sink (NotificationSink sink) |
| Where session/update notifications go. Default: dropped on the floor (test-friendly). When run() is driving, the sink is set to write to the output stream automatically. A sink retained by a shared_ptr-owned server must capture that server weakly to avoid an ownership cycle. Objects captured by reference must outlive the server (or clear the sink before they are destroyed); the server drops late worker notifications once its destructor begins. | |
| void | set_pre_armed_cancel (bool enabled) |
| Opt in to the pre-armed cancel: a session/cancel received after session/new but before the session's first prompt cancels that prompt. Off by default — on an ordered transport such as stdio a cancel with no prompt running is stale and must not cancel a later turn. | |
| void | stop () |
| Signal a running run() loop to exit at the next message boundary. | |
HTTP-less, stdio-first ACP server hosting a NeoGraph engine.
Lifecycle:
ACPServer server(engine, info); server.run(); // blocks: reads std::cin, writes std::cout
The single-shot handle_message form is provided for testing and for embedding inside a custom transport (HTTP, WebSocket, in-process pipe). It takes one parsed JSON-RPC envelope and returns the response envelope (or a null json for notifications); side-effect notifications (session/update) are delivered through the NotificationSink set on the server.
| neograph::acp::ACPServer::ACPServer | ( | std::shared_ptr< neograph::graph::GraphEngine > | engine, |
| neograph::json | info, | ||
| std::shared_ptr< ACPGraphAdapter > | adapter = {} |
||
| ) |
| engine | The graph engine that handles incoming prompts. |
| info | agentInfo returned in initialize: an ACP Implementation {"name":"my-agent","version":"0.1.0"} with an optional string title and object _meta. A missing or non-string name/version is filled with "neograph-agent" / the NeoGraph version, and any other key is dropped, so the reply always validates. |
| adapter | Optional input/output mapping override. |
| neograph::json neograph::acp::ACPServer::handle_message | ( | const neograph::json & | envelope | ) |
Process exactly one parsed JSON-RPC envelope. Returns:
{ "jsonrpc","id","result"|"error" })| void neograph::acp::ACPServer::run | ( | std::istream & | in, |
| std::ostream & | out | ||
| ) |
Drive the server loop reading newline-delimited JSON-RPC envelopes from in and writing them to out. Returns when in reaches EOF or stop() is called from another thread.
Lifecycle parallel with neograph::a2a::A2AServer: run() here is the synchronous read-loop equivalent of A2A's start() (also synchronous, blocks until shutdown). Neither server exposes a built-in async-spawn helper because the host embedder usually picks the threading model; spawn run() on a std::thread if you want it to run in the background.
| void neograph::acp::ACPServer::stop | ( | ) |
Signal a running run() loop to exit at the next message boundary.
Cancellation semantics: ACP session/cancel signals the prompt's CancelToken, so engine checkpoints and cancellation-aware providers/tools stop cooperatively. A node that neither polls its token nor awaits a cancellation-aware operation still runs to its next engine checkpoint. The terminal session/prompt response then carries StopReason::Cancelled.
session/cancel cancels the session's ongoing prompt turn; a cancel with no prompt running is ignored. A $/cancel_request that names an in-flight session/prompt by its id does the same for that turn (its response is the cancelled stop reason); one that names an unknown or finished request, or any other request, is ignored. An embedder whose transport can reorder messages may opt into set_pre_armed_cancel, under which a cancel that arrives before a session's first prompt cancels it.
While a turn waits for an agent→client request (fs/*, terminal/*, session/request_permission), session/cancel gives the client a short grace to answer on its own (ACP requires a client to answer pending permission requests with cancelled); after it the server sends $/cancel_request for the request and stops waiting.
Destructor semantics: ~ACPServer joins all in-flight session/prompt worker threads before returning. Each worker blocks until its engine->run() completes, so destroying the server during a 60-second LLM call blocks the destroying thread for the full duration. Drain or cancel before destruction if you need a bounded-latency teardown.