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)]
35 pub background_tasks: Vec<crate::server::ViewerBackgroundTask>,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub assessment: Option<mj_core::assessment::Summary>,
38 #[serde(default)]
39 pub subagents: mj_core::subagent::SubagentPolicy,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub at: Option<String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub branch: Option<String>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub base: Option<String>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub background_work: Option<ApiBackgroundWork>,
53 pub id: String,
54 pub workspace_id: String,
55 pub title: String,
56 pub harness_kind: String,
57 pub profile_id: String,
58 pub target_id: String,
59 pub bundle_id: String,
60 pub state: String,
61 pub lifecycle: ViewerLifecycleCategory,
62 pub chat_phase: crate::server::ViewerChatPhase,
63 pub is_idle: bool,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub activity_state: Option<mj_core::activity::ActivityState>,
70 pub has_error: bool,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub error: Option<String>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub storage_problem: Option<String>,
80 pub created_at: String,
81 pub updated_at: String,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
89 #[serde(default)]
90 pub config_options: Vec<crate::server::ViewerConfigOption>,
91 #[serde(default, skip_serializing_if = "Vec::is_empty")]
92 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
93}
94
95impl From<&ViewerSession> for ApiSession {
96 fn from(session: &ViewerSession) -> Self {
97 Self {
98 background_tasks: session.background_tasks.clone(),
99 assessment: None,
100 subagents: session.subagents.clone(),
101 background_work: None,
102 at: session.at.clone(),
103 branch: session.branch.clone(),
104 base: session.base.clone(),
105 id: session.id.clone(),
106 workspace_id: session.workspace_id.clone(),
107 title: session.title.clone(),
108 harness_kind: session.harness_kind.clone(),
109 profile_id: session.profile_id.clone(),
110 target_id: session.target_id.clone(),
111 bundle_id: session.bundle_id.clone(),
112 state: session.state.clone(),
113 lifecycle: session.lifecycle,
114 chat_phase: session.chat_phase,
115 is_idle: session.is_idle,
116 activity_state: session.activity_state.clone(),
117 has_error: session.has_error,
118 error: session.launch_error.clone(),
119 storage_problem: session.storage_problem.clone(),
120 created_at: session.created_at.clone(),
121 updated_at: session.updated_at.clone(),
122 last_turn_outcome: None,
123 last_turn_diagnostic: None,
124 config_options: session.config_options.clone(),
125 pending_elicitations: session.pending_elicitations.clone(),
126 }
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(deny_unknown_fields)]
132pub struct StopBackgroundTaskRequest {
133 pub background_task_id: String,
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct SessionListResponse {
138 pub sessions: Vec<ApiSession>,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct WorkspaceListResponse {
145 pub workspaces: Vec<mj_core::workspace::WorkspaceRecord>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(deny_unknown_fields)]
153pub struct CreateWorkspaceRequest {
154 pub name: String,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158pub struct CreateWorkspaceResponse {
159 pub workspace: mj_core::workspace::WorkspaceRecord,
160}
161
162#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
175#[serde(deny_unknown_fields)]
176pub struct StartSessionRequest {
177 #[serde(default)]
178 pub create_managed_worktree: Option<bool>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub at: Option<String>,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub branch: Option<String>,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub base: Option<String>,
191 #[serde(default)]
193 pub subagents: Option<mj_core::subagent::SubagentPolicy>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub review: Option<mj_core::config::SessionReview>,
197 #[serde(default)]
198 pub workspace_id: Option<String>,
199 #[serde(default)]
202 pub profile_id: Option<String>,
203 #[serde(default)]
205 pub target_id: Option<String>,
206 #[serde(default)]
207 pub bundle_id: Option<String>,
208 #[serde(default)]
209 pub project_directory: Option<PathBuf>,
210 #[serde(default)]
211 pub title: Option<String>,
212 #[serde(default)]
213 pub model: Option<String>,
214 #[serde(default)]
215 pub effort: Option<String>,
216 #[serde(default)]
217 pub prompt: Option<String>,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub cpus: Option<u64>,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub memory_bytes: Option<u64>,
225}
226
227#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(deny_unknown_fields)]
233pub struct ResumeSessionRequest {
234 #[serde(default)]
236 pub profile_id: Option<String>,
237 #[serde(default)]
239 pub target_id: Option<String>,
240 #[serde(default)]
242 pub workspace_id: Option<String>,
243 #[serde(default)]
247 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253pub struct ResumeSessionResponse {
254 pub session_id: String,
255 pub workspace_id: String,
256 pub profile_id: String,
257 pub target_id: String,
258}
259
260#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
263pub struct SuspendSessionResponse {
264 #[serde(default)]
265 pub session_id: String,
266 #[serde(default)]
268 pub stopped_subagents: usize,
269 #[serde(default)]
271 pub subagents_not_handed_back: usize,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub warning: Option<String>,
276}
277
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct StartSessionResponse {
280 pub session_id: String,
281 #[serde(default, skip_serializing_if = "Option::is_none")]
284 pub turn_id: Option<u64>,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288#[serde(deny_unknown_fields)]
289pub struct SpawnSubagentRequest {
290 pub task_name: String,
291 pub instructions: String,
292 #[serde(default)]
293 pub profile_id: Option<String>,
294 #[serde(default)]
295 pub model: Option<String>,
296 #[serde(default)]
297 pub effort: Option<String>,
298 #[serde(default)]
299 pub working_directory: Option<PathBuf>,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
305pub struct SubagentCandidate {
306 pub profile_id: String,
307 pub harness: mj_core::config::HarnessKind,
308 pub choices: mj_core::worker_launch::ProfileConfig,
309 pub remaining_percent: Option<u8>,
313}
314
315#[derive(Debug, Clone, Default, PartialEq, Eq)]
318pub struct SubagentCandidates {
319 pub offered: Vec<SubagentCandidate>,
320 pub unavailable: Vec<(String, String)>,
321}
322
323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
324pub struct SubagentView {
325 pub parent_session_id: String,
326 pub task_name: String,
327 pub session: ApiSession,
328}
329
330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
331pub struct SubagentListResponse {
332 pub subagents: Vec<SubagentView>,
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336#[serde(deny_unknown_fields)]
337pub struct PromptRequest {
338 pub text: String,
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
342pub struct PromptResponse {
343 pub turn_id: u64,
346}
347
348#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
349#[serde(deny_unknown_fields)]
350pub struct WaitRequest {
351 #[serde(default)]
353 pub return_on_input: bool,
354 #[serde(default)]
358 pub turn_id: Option<u64>,
359 #[serde(default)]
360 pub timeout_secs: Option<u64>,
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
365#[serde(rename_all = "snake_case")]
366pub enum WaitOutcome {
367 InputRequired,
369 Finished,
371 Error,
373 Cancelled,
375 QuotaLimit,
377 Timeout,
379 Stopped,
381}
382
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
384pub struct WaitCapacityRetry {
385 pub attempt: u32,
386 pub retry_at_ms: i64,
387}
388
389impl From<&CapacityRetry> for WaitCapacityRetry {
390 fn from(retry: &CapacityRetry) -> Self {
391 Self {
392 attempt: retry.attempt,
393 retry_at_ms: retry.retry_at_ms,
394 }
395 }
396}
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
404#[serde(rename_all = "snake_case")]
405pub enum RelayState {
406 Connected,
408 Disconnected,
410 Unreachable,
412 TargetMissing,
414 ProjectionIntegrity,
416}
417
418#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
419pub struct RelayHealth {
420 pub state: RelayState,
421 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub detail: Option<String>,
424}
425
426impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
427 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
428 use mj_client::session::ViewError;
429 match &view.error {
432 Some(error) => Self {
433 state: match error {
434 ViewError::Unreachable(_) => RelayState::Unreachable,
435 ViewError::TargetMissing(_) => RelayState::TargetMissing,
436 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
437 },
438 detail: Some(error.detail().to_owned()),
439 },
440 None if view.connected => Self {
441 state: RelayState::Connected,
442 detail: None,
443 },
444 None => Self {
445 state: RelayState::Disconnected,
446 detail: None,
447 },
448 }
449 }
450}
451
452#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
453pub struct WaitResponse {
454 #[serde(default, skip_serializing_if = "Option::is_none")]
455 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
456
457 #[serde(default, skip_serializing_if = "Vec::is_empty")]
458 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
459 #[serde(default, skip_serializing_if = "Option::is_none")]
460 pub usage: Option<mj_core::usage::TokenUsage>,
461 pub outcome: WaitOutcome,
462 #[serde(default, skip_serializing_if = "Option::is_none")]
464 pub stop_reason: Option<String>,
465 #[serde(default, skip_serializing_if = "Option::is_none")]
467 pub message: Option<String>,
468 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub final_message: Option<String>,
472 #[serde(default, skip_serializing_if = "Option::is_none")]
476 pub report_source: Option<String>,
477 #[serde(default, skip_serializing_if = "Option::is_none")]
480 pub turn_id: Option<u64>,
481 #[serde(default, skip_serializing_if = "Option::is_none")]
484 pub requested_turn_id: Option<u64>,
485 #[serde(default, skip_serializing_if = "Option::is_none")]
487 pub turn_number: Option<u64>,
488 #[serde(default, skip_serializing_if = "Option::is_none")]
489 pub elapsed_ms: Option<i64>,
490 #[serde(default, skip_serializing_if = "Option::is_none")]
493 pub tool_calls: Option<u64>,
494 #[serde(default, skip_serializing_if = "Option::is_none")]
496 pub capacity_retry: Option<WaitCapacityRetry>,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
498 pub server_retry: Option<WaitCapacityRetry>,
499 #[serde(default)]
500 pub retry_assessment_pending: bool,
501 #[serde(default, skip_serializing_if = "Option::is_none")]
502 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
503 #[serde(default, skip_serializing_if = "Option::is_none")]
507 pub relay: Option<RelayHealth>,
508 pub session: ApiSession,
509}
510
511pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
516pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
517
518#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
519pub struct TranscriptQuery {
520 #[serde(default)]
521 pub role: Option<mj_core::transcript::TranscriptRole>,
522 #[serde(default)]
524 pub after_seq: Option<u64>,
525 #[serde(default)]
526 pub limit: Option<usize>,
527}
528
529#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
530pub struct TranscriptItemView {
531 pub stable_id: String,
532 pub position: u64,
533 pub seq: u64,
536 pub role: String,
537 pub text: String,
539 pub created_at_ms: i64,
540 pub last_changed_at_ms: i64,
541 pub body: mj_core::transcript::TranscriptBody,
543}
544
545#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
546pub struct TranscriptResponse {
547 #[serde(default)]
548 pub next_after_seq: u64,
549 pub session_id: String,
550 pub latest_seq: u64,
553 pub execution: MaterializedExecutionState,
554 pub items: Vec<TranscriptItemView>,
555}
556
557#[derive(Debug, Clone, PartialEq, Eq)]
564pub struct TurnState {
565 pub execution: MaterializedExecutionState,
566 pub active_turn: Option<MaterializedTurn>,
567 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
568}
569
570pub use crate::database::TurnSummary;
571
572#[derive(Debug, Clone, Default, PartialEq, Eq)]
575pub struct StartFollowup {
576 pub model: Option<String>,
577 pub effort: Option<String>,
578 pub prompt: Option<String>,
579 pub fast_mode: bool,
580}
581
582#[derive(Debug, Clone, PartialEq, Eq)]
584pub enum StartStatus {
585 Pending,
587 Submitted { turn_id: u64 },
589 Failed { message: String },
591}
592
593pub use crate::database::TranscriptPage;
595
596#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
598pub struct PushedBranch {
599 pub branch: String,
600 pub remote: String,
601}
602
603#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
605pub struct FileQuery {
606 pub path: String,
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
612#[serde(rename_all = "snake_case")]
613pub enum ExportKind {
614 Patch,
616 Branch,
618 Bundle,
620}
621
622#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
623pub struct ExportRequest {
624 pub kind: ExportKind,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
627 pub branch: Option<String>,
628}
629
630#[derive(Debug, Clone)]
632pub struct BundleExport {
633 pub repository: String,
634 pub bytes: Vec<u8>,
635}
636
637#[derive(Debug)]
639pub enum ExportError {
640 Refused(String),
643 Failed(anyhow::Error),
645}
646
647impl From<ExportError> for ApiFailure {
648 fn from(error: ExportError) -> Self {
649 match error {
650 ExportError::Refused(message) => Self::conflict(message),
651 ExportError::Failed(error) => Self::from(error),
652 }
653 }
654}
655
656#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
659pub struct StartReviewResponse {
660 pub session_id: String,
661 pub started: bool,
662}
663
664#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
666pub struct ResolveReviewResponse {
667 pub session_id: String,
668 pub resolution: String,
670}
671
672#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
676pub struct ReviewStatusResponse {
677 pub session_id: String,
678 #[serde(default)]
679 pub review: Option<crate::server::ViewerTurnReview>,
680}
681
682#[derive(Debug, Default, Deserialize)]
687#[serde(deny_unknown_fields)]
688pub struct WikiSearchQuery {
689 #[serde(default)]
690 pub q: Option<String>,
691 #[serde(default)]
692 pub limit: Option<usize>,
693}
694
695#[derive(Debug, Default, Deserialize)]
696#[serde(deny_unknown_fields)]
697pub struct WikiBriefQuery {
698 #[serde(default)]
699 pub max_chars: Option<usize>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
703pub struct WikiBriefResponse {
704 pub markdown: String,
705}
706
707#[derive(Debug, Default, Deserialize)]
709#[serde(deny_unknown_fields)]
710pub struct WikiHitsQuery {
711 pub q: String,
712 #[serde(default)]
713 pub context_messages: Option<usize>,
714 #[serde(default)]
715 pub per_message_chars: Option<usize>,
716}
717
718#[derive(Debug, Clone, Default, Serialize, Deserialize)]
721#[serde(deny_unknown_fields)]
722pub struct WikiRestoreBody {
723 #[serde(default)]
724 pub workspace_id: Option<String>,
725 pub profile_id: String,
726 pub target_id: String,
727 #[serde(default)]
728 pub project_directory: Option<PathBuf>,
729 #[serde(default)]
730 pub model: Option<String>,
731 #[serde(default)]
732 pub effort: Option<String>,
733}
734
735#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
749#[serde(deny_unknown_fields)]
750pub struct LaunchOptions {
751 pub revision: u64,
755 pub profiles: Vec<LaunchProfile>,
756 pub targets: Vec<LaunchTarget>,
757 pub bundles: Vec<LaunchBundle>,
758 #[serde(default, skip_serializing_if = "Vec::is_empty")]
761 pub hosts: Vec<LaunchHost>,
762 #[serde(default, skip_serializing_if = "Option::is_none")]
766 pub default: Option<LaunchDefault>,
767}
768
769#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
771#[serde(deny_unknown_fields)]
772pub struct LaunchProfile {
773 pub id: String,
774 pub harness: String,
777}
778
779#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
781#[serde(deny_unknown_fields)]
782pub struct LaunchTarget {
783 pub id: String,
784 pub kind: String,
786 pub requires_project_directory: bool,
789 pub availability: LaunchAvailability,
790 #[serde(default, skip_serializing_if = "Option::is_none")]
794 pub unavailable_reason: Option<String>,
795 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
800 pub runtime_missing: bool,
801 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
806 pub default_candidate: bool,
807 #[serde(default, skip_serializing_if = "Option::is_none")]
809 pub host: Option<String>,
810}
811
812#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
819#[serde(rename_all = "lowercase")]
820pub enum LaunchAvailability {
821 Ready,
823 Stale,
825 Unavailable,
827 Unknown,
829}
830
831#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
833#[serde(deny_unknown_fields)]
834pub struct LaunchBundle {
835 pub id: String,
836 pub primary_repository: String,
837 pub repositories: Vec<LaunchRepository>,
838}
839
840#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
841#[serde(deny_unknown_fields)]
842pub struct LaunchRepository {
843 pub id: String,
844 #[serde(default, skip_serializing_if = "Option::is_none")]
848 pub github: Option<String>,
849 pub destination: String,
850}
851
852#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
855#[serde(deny_unknown_fields)]
856pub struct LaunchHost {
857 pub id: String,
858 pub label: String,
859 pub targets: Vec<String>,
861 pub stale: bool,
862 pub refreshing: bool,
863 pub has_error: bool,
864}
865
866#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
868#[serde(deny_unknown_fields)]
869pub struct LaunchDefault {
870 pub profile_id: String,
871 pub target_id: String,
872}