1use super::*;
2
3#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct DiffOptions {
7 pub base: Option<String>,
8 #[serde(default)]
9 pub json: bool,
10}
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct ApiBackgroundWork {
15 pub known: Option<bool>,
16 pub tasks: Vec<mj_core::relay::BackgroundCommand>,
17}
18
19impl From<&mj_core::relay::RelayOperationalState> for ApiBackgroundWork {
20 fn from(state: &mj_core::relay::RelayOperationalState) -> Self {
21 Self {
22 known: state.background_work_known,
23 tasks: state.background_commands.clone(),
24 }
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct ApiSession {
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub background_work: Option<ApiBackgroundWork>,
35 pub id: String,
36 pub workspace_id: String,
37 pub title: String,
38 pub harness_kind: String,
39 pub profile_id: String,
40 pub target_id: String,
41 pub bundle_id: String,
42 pub state: String,
43 pub lifecycle: ViewerLifecycleCategory,
44 pub chat_phase: crate::server::ViewerChatPhase,
45 pub is_idle: bool,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub activity_state: Option<mj_core::activity::ActivityState>,
52 pub has_error: bool,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub error: Option<String>,
59 pub created_at: String,
60 pub updated_at: String,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
68 #[serde(default)]
69 pub config_options: Vec<crate::server::ViewerConfigOption>,
70 #[serde(default, skip_serializing_if = "Vec::is_empty")]
71 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
72}
73
74impl From<&ViewerSession> for ApiSession {
75 fn from(session: &ViewerSession) -> Self {
76 Self {
77 background_work: None,
78 id: session.id.clone(),
79 workspace_id: session.workspace_id.clone(),
80 title: session.title.clone(),
81 harness_kind: session.harness_kind.clone(),
82 profile_id: session.profile_id.clone(),
83 target_id: session.target_id.clone(),
84 bundle_id: session.bundle_id.clone(),
85 state: session.state.clone(),
86 lifecycle: session.lifecycle,
87 chat_phase: session.chat_phase,
88 is_idle: session.is_idle,
89 activity_state: session.activity_state.clone(),
90 has_error: session.has_error,
91 error: session.launch_error.clone(),
92 created_at: session.created_at.clone(),
93 updated_at: session.updated_at.clone(),
94 last_turn_outcome: None,
95 last_turn_diagnostic: None,
96 config_options: session.config_options.clone(),
97 pending_elicitations: session.pending_elicitations.clone(),
98 }
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub struct SessionListResponse {
104 pub sessions: Vec<ApiSession>,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct WorkspaceListResponse {
111 pub workspaces: Vec<mj_core::workspace::WorkspaceRecord>,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118#[serde(deny_unknown_fields)]
119pub struct CreateWorkspaceRequest {
120 pub name: String,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct CreateWorkspaceResponse {
125 pub workspace: mj_core::workspace::WorkspaceRecord,
126}
127
128#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
138#[serde(deny_unknown_fields)]
139pub struct StartSessionRequest {
140 #[serde(default)]
141 pub create_managed_worktree: Option<bool>,
142 #[serde(default)]
144 pub launch_base: Option<String>,
145 #[serde(default)]
147 pub launch_branch: Option<String>,
148 #[serde(default)]
150 pub mjolnir_subagents: Option<bool>,
151 #[serde(default)]
152 pub workspace_id: Option<String>,
153 #[serde(default)]
155 pub profile_id: Option<String>,
156 #[serde(default)]
158 pub target_id: Option<String>,
159 #[serde(default)]
160 pub bundle_id: Option<String>,
161 #[serde(default)]
162 pub project_directory: Option<PathBuf>,
163 #[serde(default)]
164 pub title: Option<String>,
165 #[serde(default)]
166 pub model: Option<String>,
167 #[serde(default)]
168 pub effort: Option<String>,
169 #[serde(default)]
170 pub prompt: Option<String>,
171}
172
173#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(deny_unknown_fields)]
179pub struct ResumeSessionRequest {
180 #[serde(default)]
182 pub profile_id: Option<String>,
183 #[serde(default)]
185 pub target_id: Option<String>,
186 #[serde(default)]
188 pub workspace_id: Option<String>,
189 #[serde(default)]
193 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199pub struct ResumeSessionResponse {
200 pub session_id: String,
201 pub workspace_id: String,
202 pub profile_id: String,
203 pub target_id: String,
204}
205
206#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
209pub struct SuspendSessionResponse {
210 #[serde(default)]
211 pub session_id: String,
212 #[serde(default)]
214 pub stopped_subagents: usize,
215 #[serde(default)]
217 pub subagents_not_handed_back: usize,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub warning: Option<String>,
222}
223
224#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
225pub struct StartSessionResponse {
226 pub session_id: String,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
230 pub turn_id: Option<u64>,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234#[serde(deny_unknown_fields)]
235pub struct SubagentSourceRange {
236 pub file: PathBuf,
237 pub start: u64,
238 pub end: u64,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242#[serde(deny_unknown_fields)]
243pub struct SpawnSubagentRequest {
244 pub task_name: String,
245 pub instructions: String,
246 #[serde(default)]
247 pub profile_id: Option<String>,
248 #[serde(default)]
249 pub model: Option<String>,
250 #[serde(default)]
251 pub effort: Option<String>,
252 #[serde(default)]
253 pub working_directory: Option<PathBuf>,
254 #[serde(default)]
255 pub context: Option<String>,
256 #[serde(default)]
257 pub files: Vec<SubagentSourceRange>,
258}
259
260#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct SubagentCandidate {
264 pub profile_id: String,
265 pub harness: mj_core::config::HarnessKind,
266 pub choices: mj_core::worker_launch::ProfileConfig,
267 pub remaining_percent: Option<u8>,
271}
272
273#[derive(Debug, Clone, Default, PartialEq, Eq)]
276pub struct SubagentCandidates {
277 pub offered: Vec<SubagentCandidate>,
278 pub unavailable: Vec<(String, String)>,
279}
280
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282pub struct SubagentView {
283 pub parent_session_id: String,
284 pub task_name: String,
285 pub session: ApiSession,
286}
287
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub struct SubagentListResponse {
290 pub subagents: Vec<SubagentView>,
291}
292
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
294#[serde(deny_unknown_fields)]
295pub struct PromptRequest {
296 pub text: String,
297}
298
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
300pub struct PromptResponse {
301 pub turn_id: u64,
304}
305
306#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
307#[serde(deny_unknown_fields)]
308pub struct WaitRequest {
309 #[serde(default)]
311 pub return_on_input: bool,
312 #[serde(default)]
316 pub turn_id: Option<u64>,
317 #[serde(default)]
318 pub timeout_secs: Option<u64>,
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
323#[serde(rename_all = "snake_case")]
324pub enum WaitOutcome {
325 InputRequired,
327 Finished,
329 Error,
331 Cancelled,
333 QuotaLimit,
335 Timeout,
337 Stopped,
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
342pub struct WaitCapacityRetry {
343 pub attempt: u32,
344 pub retry_at_ms: i64,
345}
346
347impl From<&CapacityRetry> for WaitCapacityRetry {
348 fn from(retry: &CapacityRetry) -> Self {
349 Self {
350 attempt: retry.attempt,
351 retry_at_ms: retry.retry_at_ms,
352 }
353 }
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
362#[serde(rename_all = "snake_case")]
363pub enum RelayState {
364 Connected,
366 Disconnected,
368 Unreachable,
370 TargetMissing,
372 ProjectionIntegrity,
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
377pub struct RelayHealth {
378 pub state: RelayState,
379 #[serde(default, skip_serializing_if = "Option::is_none")]
381 pub detail: Option<String>,
382}
383
384impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
385 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
386 use mj_client::session::ViewError;
387 match &view.error {
390 Some(error) => Self {
391 state: match error {
392 ViewError::Unreachable(_) => RelayState::Unreachable,
393 ViewError::TargetMissing(_) => RelayState::TargetMissing,
394 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
395 },
396 detail: Some(error.detail().to_owned()),
397 },
398 None if view.connected => Self {
399 state: RelayState::Connected,
400 detail: None,
401 },
402 None => Self {
403 state: RelayState::Disconnected,
404 detail: None,
405 },
406 }
407 }
408}
409
410#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
411pub struct WaitResponse {
412 #[serde(default, skip_serializing_if = "Option::is_none")]
413 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
414
415 #[serde(default, skip_serializing_if = "Vec::is_empty")]
416 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
417 #[serde(default, skip_serializing_if = "Option::is_none")]
418 pub usage: Option<mj_core::usage::TokenUsage>,
419 pub outcome: WaitOutcome,
420 #[serde(default, skip_serializing_if = "Option::is_none")]
422 pub stop_reason: Option<String>,
423 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub message: Option<String>,
426 #[serde(default, skip_serializing_if = "Option::is_none")]
429 pub final_message: Option<String>,
430 #[serde(default, skip_serializing_if = "Option::is_none")]
434 pub report_source: Option<String>,
435 #[serde(default, skip_serializing_if = "Option::is_none")]
436 pub turn_id: Option<u64>,
437 #[serde(default, skip_serializing_if = "Option::is_none")]
439 pub turn_number: Option<u64>,
440 #[serde(default, skip_serializing_if = "Option::is_none")]
441 pub elapsed_ms: Option<i64>,
442 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub capacity_retry: Option<WaitCapacityRetry>,
445 #[serde(default, skip_serializing_if = "Option::is_none")]
446 pub server_retry: Option<WaitCapacityRetry>,
447 #[serde(default)]
448 pub retry_assessment_pending: bool,
449 #[serde(default, skip_serializing_if = "Option::is_none")]
450 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
451 #[serde(default, skip_serializing_if = "Option::is_none")]
455 pub relay: Option<RelayHealth>,
456 pub session: ApiSession,
457}
458
459pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
464pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
465
466#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
467pub struct TranscriptQuery {
468 #[serde(default)]
469 pub role: Option<mj_core::transcript::TranscriptRole>,
470 #[serde(default)]
472 pub after_seq: Option<u64>,
473 #[serde(default)]
474 pub limit: Option<usize>,
475}
476
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478pub struct TranscriptItemView {
479 pub stable_id: String,
480 pub position: u64,
481 pub seq: u64,
484 pub role: String,
485 pub text: String,
487 pub created_at_ms: i64,
488 pub last_changed_at_ms: i64,
489 pub body: mj_core::transcript::TranscriptBody,
491}
492
493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
494pub struct TranscriptResponse {
495 #[serde(default)]
496 pub next_after_seq: u64,
497 pub session_id: String,
498 pub latest_seq: u64,
501 pub execution: MaterializedExecutionState,
502 pub items: Vec<TranscriptItemView>,
503}
504
505#[derive(Debug, Clone, PartialEq, Eq)]
512pub struct TurnState {
513 pub execution: MaterializedExecutionState,
514 pub active_turn: Option<MaterializedTurn>,
515 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
516}
517
518pub use crate::database::TurnSummary;
519
520#[derive(Debug, Clone, Default, PartialEq, Eq)]
523pub struct StartFollowup {
524 pub model: Option<String>,
525 pub effort: Option<String>,
526 pub prompt: Option<String>,
527 pub fast_mode: bool,
528}
529
530#[derive(Debug, Clone, PartialEq, Eq)]
532pub enum StartStatus {
533 Pending,
535 Submitted { turn_id: u64 },
537 Failed { message: String },
539}
540
541pub use crate::database::TranscriptPage;
543
544#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
546pub struct PushedBranch {
547 pub branch: String,
548 pub remote: String,
549}
550
551#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
553pub struct FileQuery {
554 pub path: String,
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
560#[serde(rename_all = "snake_case")]
561pub enum ExportKind {
562 Patch,
564 Branch,
566 Bundle,
568}
569
570#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
571pub struct ExportRequest {
572 pub kind: ExportKind,
573 #[serde(default, skip_serializing_if = "Option::is_none")]
575 pub branch: Option<String>,
576}
577
578#[derive(Debug, Clone)]
580pub struct BundleExport {
581 pub repository: String,
582 pub bytes: Vec<u8>,
583}
584
585#[derive(Debug)]
587pub enum ExportError {
588 Refused(String),
591 Failed(anyhow::Error),
593}
594
595impl From<ExportError> for ApiFailure {
596 fn from(error: ExportError) -> Self {
597 match error {
598 ExportError::Refused(message) => Self::conflict(message),
599 ExportError::Failed(error) => Self::from(error),
600 }
601 }
602}
603
604#[derive(Debug, Default, Deserialize)]
609#[serde(deny_unknown_fields)]
610pub struct WikiSearchQuery {
611 #[serde(default)]
612 pub q: Option<String>,
613 #[serde(default)]
614 pub limit: Option<usize>,
615}
616
617#[derive(Debug, Default, Deserialize)]
618#[serde(deny_unknown_fields)]
619pub struct WikiBriefQuery {
620 #[serde(default)]
621 pub max_chars: Option<usize>,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
625pub struct WikiBriefResponse {
626 pub markdown: String,
627}
628
629#[derive(Debug, Default, Deserialize)]
631#[serde(deny_unknown_fields)]
632pub struct WikiHitsQuery {
633 pub q: String,
634 #[serde(default)]
635 pub context_messages: Option<usize>,
636 #[serde(default)]
637 pub per_message_chars: Option<usize>,
638}
639
640#[derive(Debug, Clone, Default, Serialize, Deserialize)]
643#[serde(deny_unknown_fields)]
644pub struct WikiRestoreBody {
645 #[serde(default)]
646 pub workspace_id: Option<String>,
647 pub profile_id: String,
648 pub target_id: String,
649 #[serde(default)]
650 pub project_directory: Option<PathBuf>,
651 #[serde(default)]
652 pub model: Option<String>,
653 #[serde(default)]
654 pub effort: Option<String>,
655}
656
657#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
671#[serde(deny_unknown_fields)]
672pub struct LaunchOptions {
673 pub revision: u64,
677 pub profiles: Vec<LaunchProfile>,
678 pub targets: Vec<LaunchTarget>,
679 pub bundles: Vec<LaunchBundle>,
680 #[serde(default, skip_serializing_if = "Vec::is_empty")]
683 pub hosts: Vec<LaunchHost>,
684 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub default: Option<LaunchDefault>,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
693#[serde(deny_unknown_fields)]
694pub struct LaunchProfile {
695 pub id: String,
696 pub harness: String,
699}
700
701#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
703#[serde(deny_unknown_fields)]
704pub struct LaunchTarget {
705 pub id: String,
706 pub kind: String,
708 pub requires_project_directory: bool,
711 pub availability: LaunchAvailability,
712 #[serde(default, skip_serializing_if = "Option::is_none")]
716 pub unavailable_reason: Option<String>,
717 #[serde(default, skip_serializing_if = "Option::is_none")]
719 pub host: Option<String>,
720}
721
722#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
729#[serde(rename_all = "lowercase")]
730pub enum LaunchAvailability {
731 Ready,
733 Stale,
735 Unavailable,
737 Unknown,
739}
740
741#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
743#[serde(deny_unknown_fields)]
744pub struct LaunchBundle {
745 pub id: String,
746 pub primary_repository: String,
747 pub repositories: Vec<LaunchRepository>,
748}
749
750#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
751#[serde(deny_unknown_fields)]
752pub struct LaunchRepository {
753 pub id: String,
754 #[serde(default, skip_serializing_if = "Option::is_none")]
758 pub github: Option<String>,
759 pub destination: String,
760}
761
762#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
765#[serde(deny_unknown_fields)]
766pub struct LaunchHost {
767 pub id: String,
768 pub label: String,
769 pub targets: Vec<String>,
771 pub stale: bool,
772 pub refreshing: bool,
773 pub has_error: bool,
774}
775
776#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778#[serde(deny_unknown_fields)]
779pub struct LaunchDefault {
780 pub profile_id: String,
781 pub target_id: String,
782}