Skip to main content

mj_controller/server/
api.rs

1//! The documented HTTP API an orchestrating agent drives sessions with.
2//!
3//! The web viewer's own `/api/...` routes exist for the browser: they are
4//! undocumented, cookie-only, and shaped around what a phone renders. These
5//! `/api/v1/...` routes are the stable surface instead. They authenticate with
6//! a bearer token from a file the same user can read, answer with a version
7//! header so a client can tell which contract it reached, and — the point of
8//! the whole module — let a caller block until one specific prompt finishes and
9//! read a structured outcome for it.
10//!
11//! Everything that needs the daemon's live session actors or its SQLite store
12//! reaches them through [`SubagentBackend`]. The daemon implements it in
13//! `server_runtime::api`; the route tests implement it with a hand-written fake,
14//! so the HTTP contract is tested without a running daemon.
15
16mod events;
17
18use std::path::{Component, PathBuf};
19use std::sync::Arc;
20use std::time::Duration;
21
22use anyhow::{Context, Result as AnyResult};
23use axum::extract::{Path, Query, State};
24use axum::http::header::{
25    AUTHORIZATION, CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_TYPE, COOKIE, HeaderValue,
26};
27use axum::http::{Request as HttpRequest, StatusCode};
28use axum::middleware::Next;
29use axum::response::{IntoResponse, Response};
30use axum::routing::{get, post};
31use axum::{Json, Router};
32use serde::{Deserialize, Serialize};
33
34use mj_core::state::{
35    MaterializedExecutionState, MaterializedTurn, MaterializedTurnOutcome, TurnOutcomeKind,
36};
37
38use mj_core::relay::{CapacityRetry, is_capacity_stop_reason};
39
40use mj_client::session::{BoxFuture, SessionHandle};
41
42use super::{
43    ActionOutcome, ApiError, COOKIE_NAME, ControllerAction, ControllerRequest, ServerState,
44    ViewerLifecycleCategory, ViewerSession, ViewerSnapshot, constant_time_eq, cookie_value,
45    create_quick_bundle, now_unix, require_session_record, session_cookie_valid, validate_action,
46    validate_prompt_text,
47};
48
49/// Response header naming the contract version this server speaks. A client
50/// that understands only version 1 can refuse anything else without parsing a
51/// body it may not recognize.
52pub const API_VERSION_HEADER: &str = "mj-api-version";
53pub const API_VERSION: &str = "1";
54
55/// How long a wait blocks when the caller names no timeout, and the ceiling it
56/// may ask for. Both are generous: a turn routinely runs for minutes, and the
57/// caller is a program that reconnects rather than a person holding a page.
58pub const DEFAULT_WAIT_SECS: u64 = 600;
59pub use mj_core::subagent::MAX_WAIT_SECONDS as MAX_WAIT_SECS;
60
61/// How often a wait re-reads durable state for a session with no live actor.
62const STOPPED_POLL_INTERVAL: Duration = Duration::from_millis(500);
63
64const API_TOKEN_FILE: &str = "api-token";
65const API_TOKEN_BYTES: usize = 32;
66
67mod token;
68pub use token::*;
69mod failure;
70pub use failure::*;
71mod types;
72pub use types::*;
73mod subagent_backend;
74pub use subagent_backend::*;
75mod wait_policy;
76pub use wait_policy::*;
77mod routes;
78pub use routes::*;
79mod config;
80pub(crate) use config::*;
81mod sessions;
82use sessions::*;
83mod subagents;
84pub(crate) use subagents::*;
85mod turns;
86pub use turns::*;
87mod files;
88pub use files::*;
89mod wait;
90use wait::*;
91
92#[cfg(test)]
93mod tests;