Skip to main content

beam_core/
api.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    ipc::{CliUsageLimitState, ScreenStatus},
6    session::{AdoptedFrom, AgentAttention, PendingResponseCardState, Session, SessionStatus},
7};
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub struct ApiHealth {
11    pub status: String,
12    pub pid: u32,
13    pub started_at: DateTime<Utc>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17pub struct DaemonRuntimeState {
18    pub pid: u32,
19    pub api_addr: String,
20    pub started_at: DateTime<Utc>,
21    pub log_path: String,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct CreateSessionRequest {
26    pub title: String,
27    pub cli_id: String,
28    pub cli_bin: String,
29    #[serde(default)]
30    pub cli_args: Vec<String>,
31    pub working_dir: String,
32    #[serde(default)]
33    pub prompt: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37pub struct SessionInputRequest {
38    pub content: String,
39    #[serde(default)]
40    pub raw: bool,
41}
42
43/// A mention target: an open_id with an optional human-readable name.
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45pub struct MentionTarget {
46    pub open_id: String,
47    #[serde(default)]
48    pub name: Option<String>,
49}
50
51/// Structured request for beam send.
52///
53/// Only `content` is required; all other fields are optional with sensible defaults,
54/// providing backward compatibility with old `{ "content": "..." }` JSON payloads.
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
56pub struct FinalOutputRequest {
57    pub content: String,
58
59    // ---- mention / attention policy ----
60    /// Explicit mention targets (--mention <open_id[:name]>, repeatable).
61    #[serde(default)]
62    pub mentions: Vec<MentionTarget>,
63    /// Mention the sender who triggered the session (--mention-back).
64    #[serde(default)]
65    pub mention_back: bool,
66    /// Suppress all @-mentions in body and footer (--no-mention).
67    #[serde(default)]
68    pub no_mention: bool,
69
70    // ---- media attachments ----
71    /// Local file paths to upload and send as file messages (--files / --file, repeatable).
72    #[serde(default)]
73    pub files: Vec<String>,
74    /// Local image paths to upload and inline in an interactive card (--images / --image, repeatable).
75    #[serde(default)]
76    pub images: Vec<String>,
77
78    // ---- targeting / quoting ----
79    /// Force a top-level chat message instead of a reply (--top-level).
80    #[serde(default)]
81    pub top_level: bool,
82    /// Override target chat (--chat-id <oc_xxx>).
83    #[serde(default)]
84    pub chat_id: Option<String>,
85    /// Send into a specific thread (--into <message_id>).
86    #[serde(default)]
87    pub into: Option<String>,
88    /// Explicit quote target (--quote <message_id>).
89    #[serde(default)]
90    pub quote: Option<String>,
91    /// Disable automatic quoting (--no-quote).
92    #[serde(default)]
93    pub no_quote: bool,
94
95    // ---- voice ----
96    /// Request TTS/voice delivery (--voice).
97    #[serde(default)]
98    pub voice: bool,
99
100    // ---- attention (workflow) ----
101    /// Requesting attention with a specific kind (--attention[=kind]).
102    /// Valid: authz | decision | blocked | help. Default: blocked.
103    #[serde(default)]
104    pub attention: Option<String>,
105
106    // ---- compat flags ----
107    /// No-op (--card).
108    #[serde(default)]
109    pub card: bool,
110    /// No-op (--text).
111    #[serde(default)]
112    pub text: bool,
113    /// Passthrough (--anyway).
114    #[serde(default)]
115    pub anyway: bool,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119pub struct RestartSessionRequest {
120    #[serde(default)]
121    pub prompt: String,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
125pub struct ResumeSessionRequest {
126    #[serde(default)]
127    pub prompt: String,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
131pub struct AttemptResumeRequest {
132    #[serde(default)]
133    pub reason: Option<String>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
137pub struct AttemptResumeStartResponse {
138    pub ok: bool,
139    pub resume_id: String,
140    pub run_id: String,
141    pub activity_id: String,
142    pub attempt_id: String,
143    pub session_id: String,
144    pub original_session_id: String,
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub cli_session_id: Option<String>,
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub web_port: Option<u16>,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub write_token: Option<String>,
151    #[serde(default, skip_serializing_if = "Option::is_none")]
152    pub url: Option<String>,
153    pub already_running: bool,
154    pub started_at: u64,
155    pub log_path: String,
156    pub sidecar_path: String,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
160pub struct AttemptResumeEndResponse {
161    pub ok: bool,
162    pub resume_id: String,
163    pub status: String,
164    pub close_reason: String,
165    pub closed_at: u64,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
169pub struct TerminalInfo {
170    pub url: String,
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub read_only_token: Option<String>,
173    #[serde(default, skip_serializing_if = "Option::is_none")]
174    pub write_token: Option<String>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
178pub struct SessionSummary {
179    pub session_id: String,
180    pub title: String,
181    pub status: SessionStatus,
182    pub chat_type: Option<String>,
183    pub quote_target_id: Option<String>,
184    pub cli_id: Option<String>,
185    pub cli_bin: Option<String>,
186    pub cli_args: Vec<String>,
187    pub working_dir: Option<String>,
188    pub worker_pid: Option<u32>,
189    pub terminal_url: Option<String>,
190    #[serde(default, skip_serializing_if = "Option::is_none")]
191    pub read_only_token: Option<String>,
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub write_token: Option<String>,
194    pub created_at: DateTime<Utc>,
195    pub stream_card_nonce: Option<String>,
196    pub current_screen: Option<String>,
197    pub last_screen_status: Option<ScreenStatus>,
198    pub usage_limit: Option<CliUsageLimitState>,
199    pub current_image_key: Option<String>,
200    pub tui_prompt_card_id: Option<String>,
201    pub tui_prompt_options: Vec<crate::ipc::TuiPromptOption>,
202    pub tui_prompt_multi_select: Option<bool>,
203    pub tui_toggled_indices: Vec<usize>,
204    pub pending_response_card_id: Option<String>,
205    pub pending_response_card_state: Option<PendingResponseCardState>,
206    pub last_patched_response_card_id: Option<String>,
207    pub last_final_output_turn_id: Option<String>,
208    pub last_final_output: Option<String>,
209    pub adopted_from: Option<AdoptedFrom>,
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub agent_attention: Option<AgentAttention>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
215pub struct BotSummary {
216    pub lark_app_id: String,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub name: Option<String>,
219    pub cli_id: String,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub model: Option<String>,
222    pub allowed_users: Vec<String>,
223    #[serde(skip_serializing_if = "Vec::is_empty")]
224    pub allowed_chat_groups: Vec<String>,
225    #[serde(skip_serializing_if = "Vec::is_empty")]
226    pub oncall_chats: Vec<String>,
227    pub private_card: bool,
228    pub active_sessions: usize,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
232pub struct SessionGroup {
233    pub chat_id: String,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub title: Option<String>,
236    pub sessions: Vec<SessionSummary>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
240pub struct DaemonOverview {
241    pub pid: u32,
242    pub started_at: DateTime<Utc>,
243    pub session_count: usize,
244    pub active_session_count: usize,
245    pub closed_session_count: usize,
246    pub bot_count: usize,
247    pub worker_count: usize,
248    pub config_path: String,
249    pub data_dir: String,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
253pub struct SessionLocateInfo {
254    pub session_id: String,
255    pub terminal_url: Option<String>,
256    pub worker_pid: Option<u32>,
257}
258
259impl From<&Session> for SessionSummary {
260    fn from(value: &Session) -> Self {
261        Self {
262            session_id: value.session_id.clone(),
263            title: value.title.clone(),
264            status: value.status,
265            chat_type: value.chat_type.clone(),
266            quote_target_id: value.quote_target_id.clone(),
267            cli_id: value.cli_id.clone(),
268            cli_bin: value.cli_bin.clone(),
269            cli_args: value.cli_args.clone(),
270            working_dir: value.working_dir.clone(),
271            worker_pid: value.worker_pid,
272            terminal_url: value.terminal_url.clone(),
273            read_only_token: None,
274            write_token: None,
275            created_at: value.created_at,
276            stream_card_nonce: value.stream_card_nonce.clone(),
277            current_screen: value.current_screen.clone(),
278            last_screen_status: value.last_screen_status,
279            usage_limit: value.usage_limit.clone(),
280            current_image_key: value.current_image_key.clone(),
281            tui_prompt_card_id: value.tui_prompt_card_id.clone(),
282            tui_prompt_options: value.tui_prompt_options.clone(),
283            tui_prompt_multi_select: value.tui_prompt_multi_select,
284            tui_toggled_indices: value.tui_toggled_indices.clone(),
285            pending_response_card_id: value.pending_response_card_id.clone(),
286            pending_response_card_state: value.pending_response_card_state,
287            last_patched_response_card_id: value.last_patched_response_card_id.clone(),
288            last_final_output_turn_id: value.last_final_output_turn_id.clone(),
289            last_final_output: value.last_final_output.clone(),
290            adopted_from: value.adopted_from.clone(),
291            agent_attention: value.agent_attention.clone(),
292        }
293    }
294}
295
296/// Request body for `POST /api/attention` — aligned with botmux parity.
297/// Accepts both camelCase (sessionId) and snake_case (session_id).
298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
299pub struct AttentionRequest {
300    #[serde(alias = "sessionId")]
301    pub session_id: String,
302    pub kind: String,
303    pub reason: String,
304}