Skip to main content

gglib_core/contracts/http/
daemon.rs

1//! Daemon API route constants — the paths the CLI sends to `gglib daemon`.
2//!
3//! These live here, in shared vocabulary, rather than inside the CLI, so the
4//! daemon's own test suite can walk them and fail when it stops serving one.
5//! #834 deleted a route the CLI's download poller was calling and the whole
6//! suite stayed green, because nothing tied the client's paths to the router's.
7//! `gglib-axum/tests/daemon_route_contract.rs` is what ties them now.
8
9/// Daemon identity probe.
10pub const HEALTH_PATH: &str = "/health";
11
12/// Which build of gglib the daemon is running.
13///
14/// Not in [`CLI_ROUTE_CONTRACT`]: the CLI reads its own compiled-in constants
15/// and takes the daemon's from [`HEALTH_PATH`]. This route exists for the
16/// dashboard, which has no compiled-in version of its own.
17pub const VERSION_PATH: &str = "/api/version";
18
19/// Start the proxy.
20pub const PROXY_START_PATH: &str = "/api/proxy/start";
21
22/// Stop the proxy.
23pub const PROXY_STOP_PATH: &str = "/api/proxy/stop";
24
25/// Current proxy status.
26pub const PROXY_STATUS_PATH: &str = "/api/proxy/status";
27
28/// The loop guard's log, a day per row (#1052).
29///
30/// Not in [`CLI_ROUTE_CONTRACT`]: `gglib proxy trips` reads the log from this
31/// machine's database directly, daemon or not. This route exists for the
32/// GUI's settings panel.
33pub const PROXY_LOOP_GUARD_TRIPS_PATH: &str = "/api/proxy/loop-guard-trips";
34
35/// Start (or reuse) a llama-server for a model.
36pub const SERVERS_START_PATH: &str = "/api/servers/start";
37
38/// Ask the daemon to shut down.
39pub const DAEMON_SHUTDOWN_PATH: &str = "/api/daemon/shutdown";
40
41/// Bring the remote tunnel up and arm a pairing (ADR 0012).
42pub const REMOTE_ENABLE_PATH: &str = "/api/remote/enable";
43
44/// Take the remote tunnel down.
45pub const REMOTE_DISABLE_PATH: &str = "/api/remote/disable";
46
47/// The remote tunnel's status.
48pub const REMOTE_STATUS_PATH: &str = "/api/remote/status";
49
50/// Reach another machine's proxy over the tunnel: bind a loopback port here.
51pub const REMOTE_CONNECT_PATH: &str = "/api/remote/connect";
52
53/// Close that loopback port.
54pub const REMOTE_DISCONNECT_PATH: &str = "/api/remote/disconnect";
55
56/// Stop the far daemon through the tunnel, then disconnect.
57pub const REMOTE_KILL_PATH: &str = "/api/remote/kill";
58
59/// Mint a key for one new device and offer a code that hands it over.
60///
61/// Needs the tunnel already up; `POST /api/remote/enable` with `invite`
62/// still does both in one call, for a first run.
63pub const REMOTE_INVITE_PATH: &str = "/api/remote/invite";
64
65/// Every device this machine has issued a key to.
66pub const REMOTE_DEVICES_PATH: &str = "/api/remote/devices";
67
68/// Download queue: `POST` enqueues, `GET` returns the snapshot.
69///
70/// One path for both verbs. The snapshot handler was once double-mounted at
71/// `/api/models/downloads` as well; when that mount was retired the CLI was
72/// still polling it, and the bare path fell through to `/api/models/{id}`,
73/// whose `i64` extractor answers `400 text/plain`.
74pub const DOWNLOADS_QUEUE_PATH: &str = "/api/models/downloads/queue";
75
76/// Model list. `gglib model list` reaches this on the *detected* daemon port
77/// rather than the compile-time one, so it builds its own base — the path is
78/// still the daemon's.
79pub const MODELS_LIST_PATH: &str = "/api/models";
80
81/// Benchmark comparison run (SSE).
82pub const BENCHMARK_COMPARE_PATH: &str = "/api/benchmark/compare";
83
84/// Benchmark performance run (SSE).
85pub const BENCHMARK_PERF_PATH: &str = "/api/benchmark/perf";
86
87/// Benchmark tuning run (SSE).
88pub const BENCHMARK_TUNE_PATH: &str = "/api/benchmark/tune";
89
90/// Agentic evaluation run (SSE).
91pub const BENCHMARK_AGENTIC_PATH: &str = "/api/benchmark/agentic";
92
93/// Setup status, used for the hardware snapshot on benchmark reports.
94pub const SETUP_STATUS_PATH: &str = "/api/config/system/setup-status";
95
96/// Apply a gated tune run, interpolating `run_id` into [`BENCHMARK_TUNE_PATH`].
97#[must_use]
98pub fn benchmark_tune_apply_path(run_id: i64) -> String {
99    format!("{BENCHMARK_TUNE_PATH}/{run_id}/apply")
100}
101
102/// Every fixed path above, paired with the verbs the CLI sends to it.
103///
104/// The verb is half the contract: a deleted route often still *matches* some
105/// parameterized sibling, and only the method it allows gives that away.
106pub const CLI_ROUTE_CONTRACT: &[(&[&str], &str)] = &[
107    (&["GET"], HEALTH_PATH),
108    (&["POST"], PROXY_START_PATH),
109    (&["POST"], PROXY_STOP_PATH),
110    (&["GET"], PROXY_STATUS_PATH),
111    (&["POST"], SERVERS_START_PATH),
112    (&["POST"], DAEMON_SHUTDOWN_PATH),
113    (&["POST"], REMOTE_ENABLE_PATH),
114    (&["POST"], REMOTE_DISABLE_PATH),
115    (&["GET"], REMOTE_STATUS_PATH),
116    (&["POST"], REMOTE_CONNECT_PATH),
117    (&["POST"], REMOTE_DISCONNECT_PATH),
118    (&["POST"], REMOTE_KILL_PATH),
119    (&["POST"], REMOTE_INVITE_PATH),
120    (&["GET"], REMOTE_DEVICES_PATH),
121    (&["GET", "POST"], DOWNLOADS_QUEUE_PATH),
122    (&["GET"], MODELS_LIST_PATH),
123    (&["POST"], BENCHMARK_COMPARE_PATH),
124    (&["POST"], BENCHMARK_PERF_PATH),
125    (&["POST"], BENCHMARK_TUNE_PATH),
126    (&["POST"], BENCHMARK_AGENTIC_PATH),
127    (&["GET"], SETUP_STATUS_PATH),
128];
129
130/// The verbs [`benchmark_tune_apply_path`] is called with.
131pub const BENCHMARK_TUNE_APPLY_METHODS: &[&str] = &["POST"];
132
133/// Retire one device, interpolating `device` into [`REMOTE_DEVICES_PATH`].
134///
135/// The id is a path segment rather than a body, because `DELETE` with one is
136/// poorly served by enough of the stack to be worth avoiding, and because
137/// the daemon already names a resource this way at `/api/mcp/servers/{id}`.
138/// **Not escaped, and the caller owes the charset.** Every id this crate
139/// mints is `[A-Za-z0-9._-]` and the edge holds tokens under nothing else,
140/// so an id that came from the roster is safe to interpolate. One that came
141/// from a person's shell is not: an HTTP client resolves dot-segments the
142/// way a browser does, so `../../models/7` here is a `DELETE` of a different
143/// route. `gglib remote forget` checks the shape before it calls this; any
144/// new caller taking an id from outside the roster must do the same.
145#[must_use]
146pub fn remote_forget_path(device: &str) -> String {
147    format!("{REMOTE_DEVICES_PATH}/{device}")
148}
149
150/// The verbs [`remote_forget_path`] is called with.
151pub const REMOTE_FORGET_METHODS: &[&str] = &["DELETE"];
152
153/// Every key the CLI puts in a `POST /api/proxy/start` body.
154///
155/// The two ends of that body cannot meet in one test. `StartProxyBody` is
156/// `pub(crate)` inside `gglib-cli`'s `pub(crate) mod daemon_client`, and
157/// `StartProxyConfig` is `pub(crate)` inside `gglib-axum`'s `pub(crate) mod
158/// handlers`; both crates deny `unreachable_pub`, and gglib-axum may not depend
159/// on gglib-cli. So each side pins itself against this list instead — the same
160/// trick [`CLI_ROUTE_CONTRACT`] uses for paths.
161pub const PROXY_START_CLI_FIELDS: &[&str] = &[
162    "host",
163    "port",
164    "default_context",
165    "cache",
166    "slot_dir",
167    "pinned",
168    "cache_disk_gb",
169    "inference_override",
170    "default_profile",
171    "api_key",
172    "allowed_hosts",
173];
174
175/// Keys the daemon accepts on that body which the CLI never sends.
176///
177/// `llama_base_port` is read only by `POST /api/proxy/start-pinned`, which
178/// routes it through the launch cascade. `/api/proxy/start` deserializes it and
179/// never looks at it, so it is daemon-only by function rather than by omission.
180pub const PROXY_START_DAEMON_ONLY_FIELDS: &[&str] = &["llama_base_port"];