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 runtime: Option<mj_core::harness_runtime::RuntimeReceipt>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub checkout: Option<mj_core::remote_git::ExactCheckout>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub expected_runtime_identity: Option<String>,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub background_work: Option<ApiBackgroundWork>,
42 pub id: String,
43 pub workspace_id: String,
44 pub title: String,
45 pub harness_kind: String,
46 pub profile_id: String,
47 pub target_id: String,
48 pub bundle_id: String,
49 pub state: String,
50 pub lifecycle: ViewerLifecycleCategory,
51 pub chat_phase: crate::server::ViewerChatPhase,
52 pub is_idle: bool,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub activity_state: Option<mj_core::activity::ActivityState>,
59 pub has_error: bool,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub error: Option<String>,
66 pub created_at: String,
67 pub updated_at: String,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
75 #[serde(default)]
76 pub config_options: Vec<crate::server::ViewerConfigOption>,
77 #[serde(default, skip_serializing_if = "Vec::is_empty")]
78 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
79}
80
81impl From<&ViewerSession> for ApiSession {
82 fn from(session: &ViewerSession) -> Self {
83 Self {
84 background_work: None,
85 checkout: session.checkout.clone(),
86 expected_runtime_identity: session.expected_runtime_identity.clone(),
87 runtime: None,
88 id: session.id.clone(),
89 workspace_id: session.workspace_id.clone(),
90 title: session.title.clone(),
91 harness_kind: session.harness_kind.clone(),
92 profile_id: session.profile_id.clone(),
93 target_id: session.target_id.clone(),
94 bundle_id: session.bundle_id.clone(),
95 state: session.state.clone(),
96 lifecycle: session.lifecycle,
97 chat_phase: session.chat_phase,
98 is_idle: session.is_idle,
99 activity_state: session.activity_state.clone(),
100 has_error: session.has_error,
101 error: session.launch_error.clone(),
102 created_at: session.created_at.clone(),
103 updated_at: session.updated_at.clone(),
104 last_turn_outcome: None,
105 last_turn_diagnostic: None,
106 config_options: session.config_options.clone(),
107 pending_elicitations: session.pending_elicitations.clone(),
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub struct SessionListResponse {
114 pub sessions: Vec<ApiSession>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct WorkspaceListResponse {
121 pub workspaces: Vec<mj_core::workspace::WorkspaceRecord>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(deny_unknown_fields)]
129pub struct CreateWorkspaceRequest {
130 pub name: String,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct CreateWorkspaceResponse {
135 pub workspace: mj_core::workspace::WorkspaceRecord,
136}
137
138#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
148#[serde(deny_unknown_fields)]
149pub struct StartSessionRequest {
150 #[serde(default)]
151 pub create_managed_worktree: Option<bool>,
152 #[serde(default)]
154 pub launch_base: Option<String>,
155 #[serde(default)]
157 pub launch_branch: Option<String>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub checkout: Option<mj_core::remote_git::ExactCheckout>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub expected_runtime_identity: Option<String>,
163 #[serde(default)]
165 pub mjolnir_subagents: Option<bool>,
166 #[serde(default)]
167 pub workspace_id: Option<String>,
168 #[serde(default)]
170 pub profile_id: Option<String>,
171 #[serde(default)]
173 pub target_id: Option<String>,
174 #[serde(default)]
175 pub bundle_id: Option<String>,
176 #[serde(default)]
177 pub project_directory: Option<PathBuf>,
178 #[serde(default)]
179 pub title: Option<String>,
180 #[serde(default)]
181 pub model: Option<String>,
182 #[serde(default)]
183 pub effort: Option<String>,
184 #[serde(default)]
185 pub prompt: Option<String>,
186}
187
188#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
193#[serde(deny_unknown_fields)]
194pub struct ResumeSessionRequest {
195 #[serde(default)]
197 pub profile_id: Option<String>,
198 #[serde(default)]
200 pub target_id: Option<String>,
201 #[serde(default)]
203 pub workspace_id: Option<String>,
204 #[serde(default)]
208 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214pub struct ResumeSessionResponse {
215 pub session_id: String,
216 pub workspace_id: String,
217 pub profile_id: String,
218 pub target_id: String,
219}
220
221#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
224pub struct SuspendSessionResponse {
225 #[serde(default)]
226 pub session_id: String,
227 #[serde(default)]
229 pub stopped_subagents: usize,
230 #[serde(default)]
232 pub subagents_not_handed_back: usize,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub warning: Option<String>,
237}
238
239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240pub struct StartSessionResponse {
241 pub session_id: String,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub turn_id: Option<u64>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249#[serde(deny_unknown_fields)]
250pub struct SubagentSourceRange {
251 pub file: PathBuf,
252 pub start: u64,
253 pub end: u64,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
257#[serde(deny_unknown_fields)]
258pub struct SpawnSubagentRequest {
259 pub task_name: String,
260 pub instructions: String,
261 #[serde(default)]
262 pub profile_id: Option<String>,
263 #[serde(default)]
264 pub model: Option<String>,
265 #[serde(default)]
266 pub effort: Option<String>,
267 #[serde(default)]
268 pub working_directory: Option<PathBuf>,
269 #[serde(default)]
270 pub context: Option<String>,
271 #[serde(default)]
272 pub files: Vec<SubagentSourceRange>,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
278pub struct SubagentCandidate {
279 pub profile_id: String,
280 pub harness: mj_core::config::HarnessKind,
281 pub choices: mj_core::worker_launch::ProfileConfig,
282 pub remaining_percent: Option<u8>,
286}
287
288#[derive(Debug, Clone, Default, PartialEq, Eq)]
291pub struct SubagentCandidates {
292 pub offered: Vec<SubagentCandidate>,
293 pub unavailable: Vec<(String, String)>,
294}
295
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297pub struct SubagentView {
298 pub parent_session_id: String,
299 pub task_name: String,
300 pub session: ApiSession,
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
304pub struct SubagentListResponse {
305 pub subagents: Vec<SubagentView>,
306}
307
308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
309#[serde(deny_unknown_fields)]
310pub struct PromptRequest {
311 pub text: String,
312}
313
314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
315pub struct PromptResponse {
316 pub turn_id: u64,
319}
320
321#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
322#[serde(deny_unknown_fields)]
323pub struct WaitRequest {
324 #[serde(default)]
326 pub return_on_input: bool,
327 #[serde(default)]
331 pub turn_id: Option<u64>,
332 #[serde(default)]
333 pub timeout_secs: Option<u64>,
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
338#[serde(rename_all = "snake_case")]
339pub enum WaitOutcome {
340 InputRequired,
342 Finished,
344 Error,
346 Cancelled,
348 QuotaLimit,
350 Timeout,
352 Stopped,
354}
355
356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
357pub struct WaitCapacityRetry {
358 pub attempt: u32,
359 pub retry_at_ms: i64,
360}
361
362impl From<&CapacityRetry> for WaitCapacityRetry {
363 fn from(retry: &CapacityRetry) -> Self {
364 Self {
365 attempt: retry.attempt,
366 retry_at_ms: retry.retry_at_ms,
367 }
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
377#[serde(rename_all = "snake_case")]
378pub enum RelayState {
379 Connected,
381 Disconnected,
383 Unreachable,
385 TargetMissing,
387 ProjectionIntegrity,
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
392pub struct RelayHealth {
393 pub state: RelayState,
394 #[serde(default, skip_serializing_if = "Option::is_none")]
396 pub detail: Option<String>,
397}
398
399impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
400 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
401 use mj_client::session::ViewError;
402 match &view.error {
405 Some(error) => Self {
406 state: match error {
407 ViewError::Unreachable(_) => RelayState::Unreachable,
408 ViewError::TargetMissing(_) => RelayState::TargetMissing,
409 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
410 },
411 detail: Some(error.detail().to_owned()),
412 },
413 None if view.connected => Self {
414 state: RelayState::Connected,
415 detail: None,
416 },
417 None => Self {
418 state: RelayState::Disconnected,
419 detail: None,
420 },
421 }
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
426pub struct WaitResponse {
427 #[serde(default, skip_serializing_if = "Option::is_none")]
428 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
429
430 #[serde(default, skip_serializing_if = "Vec::is_empty")]
431 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
432 #[serde(default, skip_serializing_if = "Option::is_none")]
433 pub usage: Option<mj_core::usage::TokenUsage>,
434 pub outcome: WaitOutcome,
435 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub stop_reason: Option<String>,
438 #[serde(default, skip_serializing_if = "Option::is_none")]
440 pub message: Option<String>,
441 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub final_message: Option<String>,
445 #[serde(default, skip_serializing_if = "Option::is_none")]
449 pub report_source: Option<String>,
450 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub turn_id: Option<u64>,
452 #[serde(default, skip_serializing_if = "Option::is_none")]
454 pub turn_number: Option<u64>,
455 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub elapsed_ms: Option<i64>,
457 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub capacity_retry: Option<WaitCapacityRetry>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
461 pub server_retry: Option<WaitCapacityRetry>,
462 #[serde(default)]
463 pub retry_assessment_pending: bool,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
466 #[serde(default, skip_serializing_if = "Option::is_none")]
470 pub relay: Option<RelayHealth>,
471 pub session: ApiSession,
472}
473
474pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
479pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
480
481#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
482pub struct TranscriptQuery {
483 #[serde(default)]
484 pub role: Option<mj_core::transcript::TranscriptRole>,
485 #[serde(default)]
487 pub after_seq: Option<u64>,
488 #[serde(default)]
489 pub limit: Option<usize>,
490}
491
492#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
493pub struct TranscriptItemView {
494 pub stable_id: String,
495 pub position: u64,
496 pub seq: u64,
499 pub role: String,
500 pub text: String,
502 pub created_at_ms: i64,
503 pub last_changed_at_ms: i64,
504 pub body: mj_core::transcript::TranscriptBody,
506}
507
508#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
509pub struct TranscriptResponse {
510 #[serde(default)]
511 pub next_after_seq: u64,
512 pub session_id: String,
513 pub latest_seq: u64,
516 pub execution: MaterializedExecutionState,
517 pub items: Vec<TranscriptItemView>,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq)]
527pub struct TurnState {
528 pub execution: MaterializedExecutionState,
529 pub active_turn: Option<MaterializedTurn>,
530 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
531}
532
533pub use crate::database::TurnSummary;
534
535#[derive(Debug, Clone, Default, PartialEq, Eq)]
538pub struct StartFollowup {
539 pub model: Option<String>,
540 pub effort: Option<String>,
541 pub prompt: Option<String>,
542 pub fast_mode: bool,
543}
544
545#[derive(Debug, Clone, PartialEq, Eq)]
547pub enum StartStatus {
548 Pending,
550 Submitted { turn_id: u64 },
552 Failed { message: String },
554}
555
556pub use crate::database::TranscriptPage;
558
559#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
561pub struct PushedBranch {
562 pub branch: String,
563 pub remote: String,
564}
565
566#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
568pub struct FileQuery {
569 pub path: String,
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
575#[serde(rename_all = "snake_case")]
576pub enum ExportKind {
577 Patch,
579 Branch,
581 Bundle,
583}
584
585#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
586pub struct ExportRequest {
587 pub kind: ExportKind,
588 #[serde(default, skip_serializing_if = "Option::is_none")]
590 pub branch: Option<String>,
591}
592
593#[derive(Debug, Clone)]
595pub struct BundleExport {
596 pub repository: String,
597 pub bytes: Vec<u8>,
598}
599
600#[derive(Debug)]
602pub enum ExportError {
603 Refused(String),
606 Failed(anyhow::Error),
608}
609
610impl From<ExportError> for ApiFailure {
611 fn from(error: ExportError) -> Self {
612 match error {
613 ExportError::Refused(message) => Self::conflict(message),
614 ExportError::Failed(error) => Self::from(error),
615 }
616 }
617}
618
619#[derive(Debug, Default, Deserialize)]
624#[serde(deny_unknown_fields)]
625pub struct WikiSearchQuery {
626 #[serde(default)]
627 pub q: Option<String>,
628 #[serde(default)]
629 pub limit: Option<usize>,
630}
631
632#[derive(Debug, Default, Deserialize)]
633#[serde(deny_unknown_fields)]
634pub struct WikiBriefQuery {
635 #[serde(default)]
636 pub max_chars: Option<usize>,
637}
638
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
640pub struct WikiBriefResponse {
641 pub markdown: String,
642}
643
644#[derive(Debug, Default, Deserialize)]
646#[serde(deny_unknown_fields)]
647pub struct WikiHitsQuery {
648 pub q: String,
649 #[serde(default)]
650 pub context_messages: Option<usize>,
651 #[serde(default)]
652 pub per_message_chars: Option<usize>,
653}
654
655#[derive(Debug, Clone, Default, Serialize, Deserialize)]
658#[serde(deny_unknown_fields)]
659pub struct WikiRestoreBody {
660 #[serde(default)]
661 pub workspace_id: Option<String>,
662 pub profile_id: String,
663 pub target_id: String,
664 #[serde(default)]
665 pub project_directory: Option<PathBuf>,
666 #[serde(default)]
667 pub model: Option<String>,
668 #[serde(default)]
669 pub effort: Option<String>,
670}
671
672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
686#[serde(deny_unknown_fields)]
687pub struct LaunchOptions {
688 pub revision: u64,
692 pub profiles: Vec<LaunchProfile>,
693 pub targets: Vec<LaunchTarget>,
694 pub bundles: Vec<LaunchBundle>,
695 #[serde(default, skip_serializing_if = "Vec::is_empty")]
698 pub hosts: Vec<LaunchHost>,
699 #[serde(default, skip_serializing_if = "Option::is_none")]
703 pub default: Option<LaunchDefault>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
708#[serde(deny_unknown_fields)]
709pub struct LaunchProfile {
710 pub id: String,
711 pub harness: String,
714}
715
716#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
718#[serde(deny_unknown_fields)]
719pub struct LaunchTarget {
720 pub id: String,
721 pub kind: String,
723 pub requires_project_directory: bool,
726 pub availability: LaunchAvailability,
727 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub unavailable_reason: Option<String>,
732 #[serde(default, skip_serializing_if = "Option::is_none")]
734 pub host: Option<String>,
735}
736
737#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
744#[serde(rename_all = "lowercase")]
745pub enum LaunchAvailability {
746 Ready,
748 Stale,
750 Unavailable,
752 Unknown,
754}
755
756#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
758#[serde(deny_unknown_fields)]
759pub struct LaunchBundle {
760 pub id: String,
761 pub primary_repository: String,
762 pub repositories: Vec<LaunchRepository>,
763}
764
765#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
766#[serde(deny_unknown_fields)]
767pub struct LaunchRepository {
768 pub id: String,
769 #[serde(default, skip_serializing_if = "Option::is_none")]
773 pub github: Option<String>,
774 pub destination: String,
775}
776
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
780#[serde(deny_unknown_fields)]
781pub struct LaunchHost {
782 pub id: String,
783 pub label: String,
784 pub targets: Vec<String>,
786 pub stale: bool,
787 pub refreshing: bool,
788 pub has_error: bool,
789}
790
791#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
793#[serde(deny_unknown_fields)]
794pub struct LaunchDefault {
795 pub profile_id: String,
796 pub target_id: String,
797}