Skip to main content

agentmux_srv/server/
http_self.rs

1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Presets, identity accounts and self.
5//! Split out of server/mod.rs unchanged (SPEC_LARGE_FILE_MODULE_ANALYSIS_2026_09_30.md §4.1).
6
7use super::*;
8
9/// `GET /api/v1/agent/preset/list` — list all presets (shared catalog, summary
10/// fields only). Backs the `PresetList` MCP tool.
11pub(super) async fn handle_agent_preset_list(State(state): State<AppState>) -> impl IntoResponse {
12    app_api_response(app_api::bundle_list_impl(&state).await)
13}
14
15#[derive(serde::Deserialize)]
16pub(super) struct AgentPresetGetQuery {
17    #[serde(default)]
18    pub(super) agent_id: String,
19    #[serde(default)]
20    pub(super) id: String,
21    #[serde(default)]
22    pub(super) name: String,
23}
24
25/// `GET /api/v1/agent/preset/get?agent_id=<slug>&id=&name=` — fetch a preset by
26/// id or name; with neither, return the agent's own bound preset (self).
27/// Backs the `PresetGet` MCP tool.
28pub(super) async fn handle_agent_preset_get(
29    State(state): State<AppState>,
30    caller: Option<Extension<caller::Caller>>,
31    Query(q): Query<AgentPresetGetQuery>,
32) -> impl IntoResponse {
33    if q.id.is_empty() && q.name.is_empty() {
34        // Self mode: `agent_id` is the actor (owner = actor). By id or name
35        // it is only an optional hint, so it is not checked there.
36        actor::check_actor(
37            &state,
38            caller.as_deref(),
39            actor::ActorSite::PresetGet,
40            Some(&q.agent_id),
41        );
42        if q.agent_id.is_empty() {
43            return (
44                StatusCode::BAD_REQUEST,
45                Json(json!({ "error": "preset.get: provide id, name, or agent_id (for self)" })),
46            )
47                .into_response();
48        }
49        return app_api_response(
50            app_api::bundle_self_get_impl(
51                &state,
52                app_api::SelfOwner::of(caller.as_deref(), &q.agent_id, "m4c.preset_owner_by_name"),
53            )
54            .await,
55        );
56    }
57    app_api_response(app_api::bundle_get_impl(&state, &q.id, &q.name).await)
58}
59
60#[derive(serde::Deserialize)]
61pub(super) struct AgentIdentityAccountsQuery {
62    pub(super) agent_id: String,
63}
64
65/// `GET /api/v1/agent/identity/accounts?agent_id=<slug>` — list the agent's own
66/// linked credential accounts (masked tails only, never secrets). Backs the
67/// `IdentityAccounts` MCP tool.
68pub(super) async fn handle_agent_identity_accounts(
69    State(state): State<AppState>,
70    caller: Option<Extension<caller::Caller>>,
71    Query(q): Query<AgentIdentityAccountsQuery>,
72) -> impl IntoResponse {
73    actor::check_actor(
74        &state,
75        caller.as_deref(),
76        actor::ActorSite::IdentityAccounts,
77        Some(&q.agent_id),
78    );
79    app_api_response(
80        app_api::identity_self_accounts_impl(
81            &state,
82            app_api::SelfOwner::of(caller.as_deref(), &q.agent_id, "m4c.identity_owner_by_name"),
83        )
84        .await,
85    )
86}
87
88#[derive(serde::Deserialize)]
89pub(super) struct AgentIdentityValidateRequest {
90    pub(super) agent_id: String,
91    pub(super) account_id: String,
92}
93
94/// `POST /api/v1/agent/identity/validate` — live-probe one of the agent's own
95/// linked accounts using its stored keychain secret (the agent never supplies a
96/// secret). Backs the `IdentityValidate` MCP tool.
97pub(super) async fn handle_agent_identity_validate(
98    State(state): State<AppState>,
99    caller: Option<Extension<caller::Caller>>,
100    Json(req): Json<AgentIdentityValidateRequest>,
101) -> impl IntoResponse {
102    actor::check_actor(
103        &state,
104        caller.as_deref(),
105        actor::ActorSite::IdentityValidate,
106        Some(&req.agent_id),
107    );
108    app_api_response(
109        app_api::identity_account_validate_stored_impl(
110            &state,
111            app_api::SelfOwner::of(caller.as_deref(), &req.agent_id, "m4c.identity_owner_by_name"),
112            &req.account_id,
113        )
114        .await,
115    )
116}
117
118#[derive(serde::Deserialize)]
119pub(super) struct SelfQuery {
120    /// Block UUID of the calling agent pane (its `AGENTMUX_BLOCKID`).
121    pub(super) block_id: Option<String>,
122}
123
124/// `GET /api/v1/self?block_id=<id>` — resolve the calling agent's place in the
125/// object tree (block → tab → window → workspace, with their names). The
126/// sidecar serves many agents, so the caller identifies itself by its block id
127/// (the MCP `WhoAmI` tool passes `AGENTMUX_BLOCKID`). Naming verbs reuse the
128/// same resolver to default their target to the agent's own context.
129pub(super) async fn handle_self(
130    State(state): State<AppState>,
131    Query(q): Query<SelfQuery>,
132) -> impl IntoResponse {
133    let block_id = q.block_id.unwrap_or_default();
134    if block_id.is_empty() {
135        return (StatusCode::BAD_REQUEST, Json(json!({ "error": "missing block_id" }))).into_response();
136    }
137    match service::resolve_agent_context(&state.mstore, &block_id) {
138        Ok(ctx) => Json(serde_json::to_value(&ctx).unwrap_or_default()).into_response(),
139        Err(e) => (StatusCode::NOT_FOUND, Json(json!({ "error": e }))).into_response(),
140    }
141}