1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub struct ApiBackgroundWork {
6 pub known: Option<bool>,
7 pub tasks: Vec<mj_core::relay::BackgroundCommand>,
8}
9
10impl From<&mj_core::relay::RelayOperationalState> for ApiBackgroundWork {
11 fn from(state: &mj_core::relay::RelayOperationalState) -> Self {
12 Self {
13 known: state.background_work_known,
14 tasks: state.background_commands.clone(),
15 }
16 }
17}
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct ApiSession {
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub background_work: Option<ApiBackgroundWork>,
26 pub id: String,
27 pub workspace_id: String,
28 pub title: String,
29 pub harness_kind: String,
30 pub profile_id: String,
31 pub target_id: String,
32 pub bundle_id: String,
33 pub state: String,
34 pub lifecycle: ViewerLifecycleCategory,
35 pub chat_phase: crate::server::ViewerChatPhase,
36 pub is_idle: bool,
37 pub has_error: bool,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub error: Option<String>,
41 pub created_at: String,
42 pub updated_at: String,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
50 #[serde(default)]
51 pub config_options: Vec<crate::server::ViewerConfigOption>,
52 #[serde(default, skip_serializing_if = "Vec::is_empty")]
53 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
54}
55
56impl From<&ViewerSession> for ApiSession {
57 fn from(session: &ViewerSession) -> Self {
58 Self {
59 background_work: None,
60 id: session.id.clone(),
61 workspace_id: session.workspace_id.clone(),
62 title: session.title.clone(),
63 harness_kind: session.harness_kind.clone(),
64 profile_id: session.profile_id.clone(),
65 target_id: session.target_id.clone(),
66 bundle_id: session.bundle_id.clone(),
67 state: session.state.clone(),
68 lifecycle: session.lifecycle,
69 chat_phase: session.chat_phase,
70 is_idle: session.is_idle,
71 has_error: session.has_error,
72 error: session.launch_error.clone(),
73 created_at: session.created_at.clone(),
74 updated_at: session.updated_at.clone(),
75 last_turn_outcome: None,
76 last_turn_diagnostic: None,
77 config_options: session.config_options.clone(),
78 pending_elicitations: session.pending_elicitations.clone(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct SessionListResponse {
85 pub sessions: Vec<ApiSession>,
86}
87
88#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
90#[serde(deny_unknown_fields)]
91pub struct StartSessionRequest {
92 #[serde(default)]
93 pub create_managed_worktree: Option<bool>,
94 #[serde(default)]
96 pub mjolnir_subagents: Option<bool>,
97 #[serde(default)]
98 pub workspace_id: Option<String>,
99 pub profile_id: String,
100 pub target_id: String,
101 #[serde(default)]
102 pub bundle_id: Option<String>,
103 #[serde(default)]
104 pub project_directory: Option<PathBuf>,
105 #[serde(default)]
106 pub title: Option<String>,
107 #[serde(default)]
108 pub model: Option<String>,
109 #[serde(default)]
110 pub effort: Option<String>,
111 #[serde(default)]
112 pub prompt: Option<String>,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct StartSessionResponse {
117 pub session_id: String,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub turn_id: Option<u64>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(deny_unknown_fields)]
126pub struct SubagentSourceRange {
127 pub file: PathBuf,
128 pub start: u64,
129 pub end: u64,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133#[serde(deny_unknown_fields)]
134pub struct SpawnSubagentRequest {
135 pub task_name: String,
136 pub instructions: String,
137 #[serde(default)]
138 pub profile_id: Option<String>,
139 #[serde(default)]
140 pub model: Option<String>,
141 #[serde(default)]
142 pub effort: Option<String>,
143 #[serde(default)]
144 pub working_directory: Option<PathBuf>,
145 #[serde(default)]
146 pub context: Option<String>,
147 #[serde(default)]
148 pub files: Vec<SubagentSourceRange>,
149 pub request_key: String,
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153pub struct SubagentView {
154 pub parent_session_id: String,
155 pub task_name: String,
156 pub request_key: String,
157 pub session: ApiSession,
158}
159
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161pub struct SubagentListResponse {
162 pub subagents: Vec<SubagentView>,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166#[serde(deny_unknown_fields)]
167pub struct PromptRequest {
168 pub text: String,
169}
170
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
172pub struct PromptResponse {
173 pub turn_id: u64,
176}
177
178#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
179#[serde(deny_unknown_fields)]
180pub struct WaitRequest {
181 #[serde(default)]
183 pub return_on_input: bool,
184 #[serde(default)]
188 pub turn_id: Option<u64>,
189 #[serde(default)]
190 pub timeout_secs: Option<u64>,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
195#[serde(rename_all = "snake_case")]
196pub enum WaitOutcome {
197 InputRequired,
199 Finished,
201 Error,
203 Cancelled,
205 QuotaLimit,
207 Timeout,
209 Stopped,
211}
212
213#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
214pub struct WaitCapacityRetry {
215 pub attempt: u32,
216 pub retry_at_ms: i64,
217}
218
219impl From<&CapacityRetry> for WaitCapacityRetry {
220 fn from(retry: &CapacityRetry) -> Self {
221 Self {
222 attempt: retry.attempt,
223 retry_at_ms: retry.retry_at_ms,
224 }
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
234#[serde(rename_all = "snake_case")]
235pub enum RelayState {
236 Connected,
238 Disconnected,
240 Unreachable,
242 TargetMissing,
244 ProjectionIntegrity,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249pub struct RelayHealth {
250 pub state: RelayState,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub detail: Option<String>,
254}
255
256impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
257 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
258 use mj_client::session::ViewError;
259 match &view.error {
262 Some(error) => Self {
263 state: match error {
264 ViewError::Unreachable(_) => RelayState::Unreachable,
265 ViewError::TargetMissing(_) => RelayState::TargetMissing,
266 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
267 },
268 detail: Some(error.detail().to_owned()),
269 },
270 None if view.connected => Self {
271 state: RelayState::Connected,
272 detail: None,
273 },
274 None => Self {
275 state: RelayState::Disconnected,
276 detail: None,
277 },
278 }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
283pub struct WaitResponse {
284 #[serde(default, skip_serializing_if = "Option::is_none")]
285 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
286
287 #[serde(default, skip_serializing_if = "Vec::is_empty")]
288 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub usage: Option<mj_core::usage::TokenUsage>,
291 pub outcome: WaitOutcome,
292 #[serde(default, skip_serializing_if = "Option::is_none")]
294 pub stop_reason: Option<String>,
295 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub message: Option<String>,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
300 pub final_message: Option<String>,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub turn_id: Option<u64>,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub turn_number: Option<u64>,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub elapsed_ms: Option<i64>,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub capacity_retry: Option<WaitCapacityRetry>,
312 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub relay: Option<RelayHealth>,
317 pub session: ApiSession,
318}
319
320pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
325pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
326
327#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
328pub struct TranscriptQuery {
329 #[serde(default)]
330 pub role: Option<mj_core::transcript::TranscriptRole>,
331 #[serde(default)]
333 pub after_seq: Option<u64>,
334 #[serde(default)]
335 pub limit: Option<usize>,
336}
337
338#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
339pub struct TranscriptItemView {
340 pub stable_id: String,
341 pub position: u64,
342 pub seq: u64,
345 pub role: String,
346 pub text: String,
348 pub created_at_ms: i64,
349 pub last_changed_at_ms: i64,
350 pub body: mj_core::transcript::TranscriptBody,
352}
353
354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
355pub struct TranscriptResponse {
356 #[serde(default)]
357 pub next_after_seq: u64,
358 pub session_id: String,
359 pub latest_seq: u64,
362 pub execution: MaterializedExecutionState,
363 pub items: Vec<TranscriptItemView>,
364}
365
366#[derive(Debug, Clone, PartialEq, Eq)]
373pub struct TurnState {
374 pub execution: MaterializedExecutionState,
375 pub active_turn: Option<MaterializedTurn>,
376 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
377}
378
379pub use crate::database::TurnSummary;
380
381#[derive(Debug, Clone, Default, PartialEq, Eq)]
384pub struct StartFollowup {
385 pub model: Option<String>,
386 pub effort: Option<String>,
387 pub prompt: Option<String>,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
392pub enum StartStatus {
393 Pending,
395 Submitted { turn_id: u64 },
397 Failed { message: String },
399}
400
401pub use crate::database::TranscriptPage;
403
404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
406pub struct PushedBranch {
407 pub branch: String,
408 pub remote: String,
409}
410
411#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
413pub struct FileQuery {
414 pub path: String,
416}
417
418#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
420#[serde(rename_all = "snake_case")]
421pub enum ExportKind {
422 Patch,
424 Branch,
426 Bundle,
428}
429
430#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
431pub struct ExportRequest {
432 pub kind: ExportKind,
433 #[serde(default, skip_serializing_if = "Option::is_none")]
435 pub branch: Option<String>,
436}
437
438#[derive(Debug, Clone)]
440pub struct BundleExport {
441 pub repository: String,
442 pub bytes: Vec<u8>,
443}
444
445#[derive(Debug)]
447pub enum ExportError {
448 Refused(String),
451 Failed(anyhow::Error),
453}
454
455impl From<ExportError> for ApiFailure {
456 fn from(error: ExportError) -> Self {
457 match error {
458 ExportError::Refused(message) => Self::conflict(message),
459 ExportError::Failed(error) => Self::from(error),
460 }
461 }
462}
463
464#[derive(Debug, Default, Deserialize)]
469#[serde(deny_unknown_fields)]
470pub struct WikiSearchQuery {
471 #[serde(default)]
472 pub q: Option<String>,
473 #[serde(default)]
474 pub limit: Option<usize>,
475}
476
477#[derive(Debug, Default, Deserialize)]
478#[serde(deny_unknown_fields)]
479pub struct WikiBriefQuery {
480 #[serde(default)]
481 pub max_chars: Option<usize>,
482}
483
484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485pub struct WikiBriefResponse {
486 pub markdown: String,
487}
488
489#[derive(Debug, Clone, Default, Deserialize)]
492#[serde(deny_unknown_fields)]
493pub struct WikiRestoreBody {
494 #[serde(default)]
495 pub workspace_id: Option<String>,
496 pub profile_id: String,
497 pub target_id: String,
498 #[serde(default)]
499 pub project_directory: Option<PathBuf>,
500 #[serde(default)]
501 pub model: Option<String>,
502 #[serde(default)]
503 pub effort: Option<String>,
504}