mj_controller/server/api/
routes.rs1use super::*;
2
3pub(in crate::server) fn router(state: ServerState) -> Router<ServerState> {
4 Router::new()
5 .route(
6 "/sessions/{session_id}/native-agents/{child_id}/history",
7 get(native_agent_history),
8 )
9 .route("/events", get(events::events))
10 .route("/profiles/{profile_id}/config", get(profile_config))
11 .route(
12 "/profiles/{profile_id}/subagent-options",
13 get(subagent_options),
14 )
15 .route("/options", get(options))
16 .route(
17 "/sessions/{session_id}/config",
18 axum::routing::patch(set_config),
19 )
20 .route("/workspaces", get(list_workspaces).post(create_workspace))
21 .route("/sessions", get(list_sessions).post(start_session))
22 .route("/sessions/{session_id}", get(get_session))
23 .route(
24 "/sessions/{session_id}/subagents",
25 get(list_subagents).post(spawn_subagent),
26 )
27 .route("/sessions/{session_id}/prompt", post(prompt))
28 .route("/sessions/{session_id}/transcript", get(transcript))
29 .route("/sessions/{session_id}/history", get(transcript_history))
30 .route("/sessions/{session_id}/usage", get(usage))
31 .route("/sessions/{session_id}/usage/tree", get(usage_tree))
32 .route("/sessions/{session_id}/wait", post(wait))
33 .route("/sessions/{session_id}/suspend", post(suspend))
34 .route("/sessions/{session_id}/destroy", post(destroy))
35 .route("/sessions/{session_id}/resume", post(resume))
36 .route(
37 "/sessions/{session_id}/background-tasks/stop",
38 post(stop_background_task),
39 )
40 .route(
41 "/sessions/{session_id}/interrupt-turn",
42 post(interrupt_turn),
43 )
44 .route("/sessions/{session_id}/diff", get(diff))
45 .route(
46 "/sessions/{session_id}/files",
47 get(read_file)
48 .put(write_file)
49 .layer(axum::extract::DefaultBodyLimit::max(
50 mj_checkpoint::archive::MAX_SESSION_FILE_BYTES as usize,
51 )),
52 )
53 .route("/sessions/{session_id}/elicitations", get(elicitations))
54 .route(
55 "/sessions/{session_id}/elicitations/{elicitation_id}",
56 post(respond_elicitation),
57 )
58 .route("/sessions/{session_id}/export", post(export))
59 .route("/wiki/search", get(wiki_search))
60 .route("/wiki/sessions/{wiki_id}", get(wiki_session))
61 .route("/wiki/sessions/{wiki_id}/brief", get(wiki_brief))
62 .route("/wiki/sessions/{wiki_id}/hits", get(wiki_hits))
63 .route("/wiki/sessions/{wiki_id}/restore", post(wiki_restore))
64 .route_layer(axum::middleware::from_fn_with_state(
65 state,
66 require_api_auth,
67 ))
68 .layer(axum::middleware::from_fn(api_response_headers))
71}
72
73pub(super) async fn require_api_auth(
79 State(state): State<ServerState>,
80 request: HttpRequest<axum::body::Body>,
81 next: Next,
82) -> Result<Response, ApiFailure> {
83 let bearer = request
84 .headers()
85 .get(AUTHORIZATION)
86 .and_then(|value| value.to_str().ok())
87 .and_then(|value| value.strip_prefix("Bearer "))
88 .map(str::trim);
89 if bearer.is_some_and(|token| {
90 constant_time_eq(state.api_token.as_bytes(), token.as_bytes()) && !token.is_empty()
91 }) {
92 return Ok(next.run(request).await);
93 }
94 let cookie =
95 super::super::authenticated_viewer(&state, request.headers()).map_err(|error| {
96 if error.status == StatusCode::UNAUTHORIZED {
97 ApiFailure::new(
98 StatusCode::UNAUTHORIZED,
99 "supply the API token from the api-token file as a bearer token",
100 )
101 } else {
102 ApiFailure::from(error)
103 }
104 })?;
105 let mut response = next.run(request).await;
106 super::super::renew_viewer_response(&state, &cookie, &mut response)?;
107 Ok(response)
108}
109
110pub(super) async fn api_response_headers(
113 request: HttpRequest<axum::body::Body>,
114 next: Next,
115) -> Response {
116 let mut response = next.run(request).await;
117 let headers = response.headers_mut();
118 headers.insert(API_VERSION_HEADER, HeaderValue::from_static(API_VERSION));
119 headers.insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
120 response
121}
122
123#[derive(Debug, Default, Clone, Serialize, Deserialize)]
128#[serde(deny_unknown_fields)]
129pub struct SessionListQuery {
130 pub workspace_id: Option<String>,
131}
132
133#[derive(Debug, Default, Deserialize)]
134#[serde(deny_unknown_fields)]
135pub(super) struct ProfileConfigQuery {
136 pub(super) model: Option<String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(deny_unknown_fields)]
141pub struct SetConfigRequest {
142 pub key: String,
143 pub value: String,
144}