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 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub activity_state: Option<mj_core::activity::ActivityState>,
43 pub has_error: bool,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub error: Option<String>,
50 pub created_at: String,
51 pub updated_at: String,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
59 #[serde(default)]
60 pub config_options: Vec<crate::server::ViewerConfigOption>,
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
62 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
63}
64
65impl From<&ViewerSession> for ApiSession {
66 fn from(session: &ViewerSession) -> Self {
67 Self {
68 background_work: None,
69 id: session.id.clone(),
70 workspace_id: session.workspace_id.clone(),
71 title: session.title.clone(),
72 harness_kind: session.harness_kind.clone(),
73 profile_id: session.profile_id.clone(),
74 target_id: session.target_id.clone(),
75 bundle_id: session.bundle_id.clone(),
76 state: session.state.clone(),
77 lifecycle: session.lifecycle,
78 chat_phase: session.chat_phase,
79 is_idle: session.is_idle,
80 activity_state: session.activity_state.clone(),
81 has_error: session.has_error,
82 error: session.launch_error.clone(),
83 created_at: session.created_at.clone(),
84 updated_at: session.updated_at.clone(),
85 last_turn_outcome: None,
86 last_turn_diagnostic: None,
87 config_options: session.config_options.clone(),
88 pending_elicitations: session.pending_elicitations.clone(),
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct SessionListResponse {
95 pub sessions: Vec<ApiSession>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct WorkspaceListResponse {
102 pub workspaces: Vec<mj_core::workspace::WorkspaceRecord>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(deny_unknown_fields)]
110pub struct CreateWorkspaceRequest {
111 pub name: String,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115pub struct CreateWorkspaceResponse {
116 pub workspace: mj_core::workspace::WorkspaceRecord,
117}
118
119#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
121#[serde(deny_unknown_fields)]
122pub struct StartSessionRequest {
123 #[serde(default)]
124 pub create_managed_worktree: Option<bool>,
125 #[serde(default)]
127 pub mjolnir_subagents: Option<bool>,
128 #[serde(default)]
129 pub workspace_id: Option<String>,
130 pub profile_id: String,
131 pub target_id: String,
132 #[serde(default)]
133 pub bundle_id: Option<String>,
134 #[serde(default)]
135 pub project_directory: Option<PathBuf>,
136 #[serde(default)]
137 pub title: Option<String>,
138 #[serde(default)]
139 pub model: Option<String>,
140 #[serde(default)]
141 pub effort: Option<String>,
142 #[serde(default)]
143 pub prompt: Option<String>,
144}
145
146#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
151#[serde(deny_unknown_fields)]
152pub struct ResumeSessionRequest {
153 #[serde(default)]
155 pub profile_id: Option<String>,
156 #[serde(default)]
158 pub target_id: Option<String>,
159 #[serde(default)]
161 pub workspace_id: Option<String>,
162 #[serde(default)]
166 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172pub struct ResumeSessionResponse {
173 pub session_id: String,
174 pub workspace_id: String,
175 pub profile_id: String,
176 pub target_id: String,
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
180pub struct StartSessionResponse {
181 pub session_id: String,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub turn_id: Option<u64>,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189#[serde(deny_unknown_fields)]
190pub struct SubagentSourceRange {
191 pub file: PathBuf,
192 pub start: u64,
193 pub end: u64,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
197#[serde(deny_unknown_fields)]
198pub struct SpawnSubagentRequest {
199 pub task_name: String,
200 pub instructions: String,
201 #[serde(default)]
202 pub profile_id: Option<String>,
203 #[serde(default)]
204 pub model: Option<String>,
205 #[serde(default)]
206 pub effort: Option<String>,
207 #[serde(default)]
208 pub working_directory: Option<PathBuf>,
209 #[serde(default)]
210 pub context: Option<String>,
211 #[serde(default)]
212 pub files: Vec<SubagentSourceRange>,
213 pub request_key: String,
214}
215
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub struct SubagentView {
218 pub parent_session_id: String,
219 pub task_name: String,
220 pub request_key: String,
221 pub session: ApiSession,
222}
223
224#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
225pub struct SubagentListResponse {
226 pub subagents: Vec<SubagentView>,
227}
228
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
230#[serde(deny_unknown_fields)]
231pub struct PromptRequest {
232 pub text: String,
233}
234
235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236pub struct PromptResponse {
237 pub turn_id: u64,
240}
241
242#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
243#[serde(deny_unknown_fields)]
244pub struct WaitRequest {
245 #[serde(default)]
247 pub return_on_input: bool,
248 #[serde(default)]
252 pub turn_id: Option<u64>,
253 #[serde(default)]
254 pub timeout_secs: Option<u64>,
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
259#[serde(rename_all = "snake_case")]
260pub enum WaitOutcome {
261 InputRequired,
263 Finished,
265 Error,
267 Cancelled,
269 QuotaLimit,
271 Timeout,
273 Stopped,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub struct WaitCapacityRetry {
279 pub attempt: u32,
280 pub retry_at_ms: i64,
281}
282
283impl From<&CapacityRetry> for WaitCapacityRetry {
284 fn from(retry: &CapacityRetry) -> Self {
285 Self {
286 attempt: retry.attempt,
287 retry_at_ms: retry.retry_at_ms,
288 }
289 }
290}
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
298#[serde(rename_all = "snake_case")]
299pub enum RelayState {
300 Connected,
302 Disconnected,
304 Unreachable,
306 TargetMissing,
308 ProjectionIntegrity,
310}
311
312#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
313pub struct RelayHealth {
314 pub state: RelayState,
315 #[serde(default, skip_serializing_if = "Option::is_none")]
317 pub detail: Option<String>,
318}
319
320impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
321 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
322 use mj_client::session::ViewError;
323 match &view.error {
326 Some(error) => Self {
327 state: match error {
328 ViewError::Unreachable(_) => RelayState::Unreachable,
329 ViewError::TargetMissing(_) => RelayState::TargetMissing,
330 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
331 },
332 detail: Some(error.detail().to_owned()),
333 },
334 None if view.connected => Self {
335 state: RelayState::Connected,
336 detail: None,
337 },
338 None => Self {
339 state: RelayState::Disconnected,
340 detail: None,
341 },
342 }
343 }
344}
345
346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
347pub struct WaitResponse {
348 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
350
351 #[serde(default, skip_serializing_if = "Vec::is_empty")]
352 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub usage: Option<mj_core::usage::TokenUsage>,
355 pub outcome: WaitOutcome,
356 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub stop_reason: Option<String>,
359 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub message: Option<String>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub final_message: Option<String>,
365 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub turn_id: Option<u64>,
367 #[serde(default, skip_serializing_if = "Option::is_none")]
369 pub turn_number: Option<u64>,
370 #[serde(default, skip_serializing_if = "Option::is_none")]
371 pub elapsed_ms: Option<i64>,
372 #[serde(default, skip_serializing_if = "Option::is_none")]
375 pub capacity_retry: Option<WaitCapacityRetry>,
376 #[serde(default, skip_serializing_if = "Option::is_none")]
380 pub relay: Option<RelayHealth>,
381 pub session: ApiSession,
382}
383
384pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
389pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
390
391#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
392pub struct TranscriptQuery {
393 #[serde(default)]
394 pub role: Option<mj_core::transcript::TranscriptRole>,
395 #[serde(default)]
397 pub after_seq: Option<u64>,
398 #[serde(default)]
399 pub limit: Option<usize>,
400}
401
402#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
403pub struct TranscriptItemView {
404 pub stable_id: String,
405 pub position: u64,
406 pub seq: u64,
409 pub role: String,
410 pub text: String,
412 pub created_at_ms: i64,
413 pub last_changed_at_ms: i64,
414 pub body: mj_core::transcript::TranscriptBody,
416}
417
418#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
419pub struct TranscriptResponse {
420 #[serde(default)]
421 pub next_after_seq: u64,
422 pub session_id: String,
423 pub latest_seq: u64,
426 pub execution: MaterializedExecutionState,
427 pub items: Vec<TranscriptItemView>,
428}
429
430#[derive(Debug, Clone, PartialEq, Eq)]
437pub struct TurnState {
438 pub execution: MaterializedExecutionState,
439 pub active_turn: Option<MaterializedTurn>,
440 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
441}
442
443pub use crate::database::TurnSummary;
444
445#[derive(Debug, Clone, Default, PartialEq, Eq)]
448pub struct StartFollowup {
449 pub model: Option<String>,
450 pub effort: Option<String>,
451 pub prompt: Option<String>,
452}
453
454#[derive(Debug, Clone, PartialEq, Eq)]
456pub enum StartStatus {
457 Pending,
459 Submitted { turn_id: u64 },
461 Failed { message: String },
463}
464
465pub use crate::database::TranscriptPage;
467
468#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
470pub struct PushedBranch {
471 pub branch: String,
472 pub remote: String,
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477pub struct FileQuery {
478 pub path: String,
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
484#[serde(rename_all = "snake_case")]
485pub enum ExportKind {
486 Patch,
488 Branch,
490 Bundle,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
495pub struct ExportRequest {
496 pub kind: ExportKind,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
499 pub branch: Option<String>,
500}
501
502#[derive(Debug, Clone)]
504pub struct BundleExport {
505 pub repository: String,
506 pub bytes: Vec<u8>,
507}
508
509#[derive(Debug)]
511pub enum ExportError {
512 Refused(String),
515 Failed(anyhow::Error),
517}
518
519impl From<ExportError> for ApiFailure {
520 fn from(error: ExportError) -> Self {
521 match error {
522 ExportError::Refused(message) => Self::conflict(message),
523 ExportError::Failed(error) => Self::from(error),
524 }
525 }
526}
527
528#[derive(Debug, Default, Deserialize)]
533#[serde(deny_unknown_fields)]
534pub struct WikiSearchQuery {
535 #[serde(default)]
536 pub q: Option<String>,
537 #[serde(default)]
538 pub limit: Option<usize>,
539}
540
541#[derive(Debug, Default, Deserialize)]
542#[serde(deny_unknown_fields)]
543pub struct WikiBriefQuery {
544 #[serde(default)]
545 pub max_chars: Option<usize>,
546}
547
548#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
549pub struct WikiBriefResponse {
550 pub markdown: String,
551}
552
553#[derive(Debug, Default, Deserialize)]
555#[serde(deny_unknown_fields)]
556pub struct WikiHitsQuery {
557 pub q: String,
558 #[serde(default)]
559 pub context_messages: Option<usize>,
560 #[serde(default)]
561 pub per_message_chars: Option<usize>,
562}
563
564#[derive(Debug, Clone, Default, Deserialize)]
567#[serde(deny_unknown_fields)]
568pub struct WikiRestoreBody {
569 #[serde(default)]
570 pub workspace_id: Option<String>,
571 pub profile_id: String,
572 pub target_id: String,
573 #[serde(default)]
574 pub project_directory: Option<PathBuf>,
575 #[serde(default)]
576 pub model: Option<String>,
577 #[serde(default)]
578 pub effort: Option<String>,
579}