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 assessment: Option<mj_core::assessment::Summary>,
35 #[serde(default)]
36 pub subagents: mj_core::subagent::SubagentPolicy,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub runtime: Option<mj_core::harness_runtime::RuntimeReceipt>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub at: Option<String>,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub branch: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub base: Option<String>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub expected_runtime_identity: Option<String>,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub background_work: Option<ApiBackgroundWork>,
54 pub id: String,
55 pub workspace_id: String,
56 pub title: String,
57 pub harness_kind: String,
58 pub profile_id: String,
59 pub target_id: String,
60 pub bundle_id: String,
61 pub state: String,
62 pub lifecycle: ViewerLifecycleCategory,
63 pub chat_phase: crate::server::ViewerChatPhase,
64 pub is_idle: bool,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub activity_state: Option<mj_core::activity::ActivityState>,
71 pub has_error: bool,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub error: Option<String>,
78 pub created_at: String,
79 pub updated_at: String,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
87 #[serde(default)]
88 pub config_options: Vec<crate::server::ViewerConfigOption>,
89 #[serde(default, skip_serializing_if = "Vec::is_empty")]
90 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
91}
92
93impl From<&ViewerSession> for ApiSession {
94 fn from(session: &ViewerSession) -> Self {
95 Self {
96 assessment: None,
97 subagents: session.subagents.clone(),
98 background_work: None,
99 at: session.at.clone(),
100 branch: session.branch.clone(),
101 base: session.base.clone(),
102 expected_runtime_identity: session.expected_runtime_identity.clone(),
103 runtime: None,
104 id: session.id.clone(),
105 workspace_id: session.workspace_id.clone(),
106 title: session.title.clone(),
107 harness_kind: session.harness_kind.clone(),
108 profile_id: session.profile_id.clone(),
109 target_id: session.target_id.clone(),
110 bundle_id: session.bundle_id.clone(),
111 state: session.state.clone(),
112 lifecycle: session.lifecycle,
113 chat_phase: session.chat_phase,
114 is_idle: session.is_idle,
115 activity_state: session.activity_state.clone(),
116 has_error: session.has_error,
117 error: session.launch_error.clone(),
118 created_at: session.created_at.clone(),
119 updated_at: session.updated_at.clone(),
120 last_turn_outcome: None,
121 last_turn_diagnostic: None,
122 config_options: session.config_options.clone(),
123 pending_elicitations: session.pending_elicitations.clone(),
124 }
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129pub struct SessionListResponse {
130 pub sessions: Vec<ApiSession>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct WorkspaceListResponse {
137 pub workspaces: Vec<mj_core::workspace::WorkspaceRecord>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144#[serde(deny_unknown_fields)]
145pub struct CreateWorkspaceRequest {
146 pub name: String,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub struct CreateWorkspaceResponse {
151 pub workspace: mj_core::workspace::WorkspaceRecord,
152}
153
154#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
164#[serde(deny_unknown_fields)]
165pub struct StartSessionRequest {
166 #[serde(default)]
167 pub create_managed_worktree: Option<bool>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub at: Option<String>,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub branch: Option<String>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub base: Option<String>,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub expected_runtime_identity: Option<String>,
182 #[serde(default)]
184 pub subagents: Option<mj_core::subagent::SubagentPolicy>,
185 #[serde(default)]
186 pub workspace_id: Option<String>,
187 #[serde(default)]
189 pub profile_id: Option<String>,
190 #[serde(default)]
192 pub target_id: Option<String>,
193 #[serde(default)]
194 pub bundle_id: Option<String>,
195 #[serde(default)]
196 pub project_directory: Option<PathBuf>,
197 #[serde(default)]
198 pub title: Option<String>,
199 #[serde(default)]
200 pub model: Option<String>,
201 #[serde(default)]
202 pub effort: Option<String>,
203 #[serde(default)]
204 pub prompt: Option<String>,
205}
206
207#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
212#[serde(deny_unknown_fields)]
213pub struct ResumeSessionRequest {
214 #[serde(default)]
216 pub profile_id: Option<String>,
217 #[serde(default)]
219 pub target_id: Option<String>,
220 #[serde(default)]
222 pub workspace_id: Option<String>,
223 #[serde(default)]
227 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub struct ResumeSessionResponse {
234 pub session_id: String,
235 pub workspace_id: String,
236 pub profile_id: String,
237 pub target_id: String,
238}
239
240#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
243pub struct SuspendSessionResponse {
244 #[serde(default)]
245 pub session_id: String,
246 #[serde(default)]
248 pub stopped_subagents: usize,
249 #[serde(default)]
251 pub subagents_not_handed_back: usize,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub warning: Option<String>,
256}
257
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259pub struct StartSessionResponse {
260 pub session_id: String,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub turn_id: Option<u64>,
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(deny_unknown_fields)]
269pub struct SubagentSourceRange {
270 pub file: PathBuf,
271 pub start: u64,
272 pub end: u64,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
276#[serde(deny_unknown_fields)]
277pub struct SpawnSubagentRequest {
278 pub task_name: String,
279 pub instructions: String,
280 #[serde(default)]
281 pub profile_id: Option<String>,
282 #[serde(default)]
283 pub model: Option<String>,
284 #[serde(default)]
285 pub effort: Option<String>,
286 #[serde(default)]
287 pub working_directory: Option<PathBuf>,
288 #[serde(default)]
289 pub context: Option<String>,
290 #[serde(default)]
291 pub files: Vec<SubagentSourceRange>,
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct SubagentCandidate {
298 pub profile_id: String,
299 pub harness: mj_core::config::HarnessKind,
300 pub choices: mj_core::worker_launch::ProfileConfig,
301 pub remaining_percent: Option<u8>,
305}
306
307#[derive(Debug, Clone, Default, PartialEq, Eq)]
310pub struct SubagentCandidates {
311 pub offered: Vec<SubagentCandidate>,
312 pub unavailable: Vec<(String, String)>,
313}
314
315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
316pub struct SubagentView {
317 pub parent_session_id: String,
318 pub task_name: String,
319 pub session: ApiSession,
320}
321
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct SubagentListResponse {
324 pub subagents: Vec<SubagentView>,
325}
326
327#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
328#[serde(deny_unknown_fields)]
329pub struct PromptRequest {
330 pub text: String,
331}
332
333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334pub struct PromptResponse {
335 pub turn_id: u64,
338}
339
340#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
341#[serde(deny_unknown_fields)]
342pub struct WaitRequest {
343 #[serde(default)]
345 pub return_on_input: bool,
346 #[serde(default)]
350 pub turn_id: Option<u64>,
351 #[serde(default)]
352 pub timeout_secs: Option<u64>,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
357#[serde(rename_all = "snake_case")]
358pub enum WaitOutcome {
359 InputRequired,
361 Finished,
363 Error,
365 Cancelled,
367 QuotaLimit,
369 Timeout,
371 Stopped,
373}
374
375#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
376pub struct WaitCapacityRetry {
377 pub attempt: u32,
378 pub retry_at_ms: i64,
379}
380
381impl From<&CapacityRetry> for WaitCapacityRetry {
382 fn from(retry: &CapacityRetry) -> Self {
383 Self {
384 attempt: retry.attempt,
385 retry_at_ms: retry.retry_at_ms,
386 }
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
396#[serde(rename_all = "snake_case")]
397pub enum RelayState {
398 Connected,
400 Disconnected,
402 Unreachable,
404 TargetMissing,
406 ProjectionIntegrity,
408}
409
410#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
411pub struct RelayHealth {
412 pub state: RelayState,
413 #[serde(default, skip_serializing_if = "Option::is_none")]
415 pub detail: Option<String>,
416}
417
418impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
419 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
420 use mj_client::session::ViewError;
421 match &view.error {
424 Some(error) => Self {
425 state: match error {
426 ViewError::Unreachable(_) => RelayState::Unreachable,
427 ViewError::TargetMissing(_) => RelayState::TargetMissing,
428 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
429 },
430 detail: Some(error.detail().to_owned()),
431 },
432 None if view.connected => Self {
433 state: RelayState::Connected,
434 detail: None,
435 },
436 None => Self {
437 state: RelayState::Disconnected,
438 detail: None,
439 },
440 }
441 }
442}
443
444#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
445pub struct WaitResponse {
446 #[serde(default, skip_serializing_if = "Option::is_none")]
447 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
448
449 #[serde(default, skip_serializing_if = "Vec::is_empty")]
450 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
451 #[serde(default, skip_serializing_if = "Option::is_none")]
452 pub usage: Option<mj_core::usage::TokenUsage>,
453 pub outcome: WaitOutcome,
454 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub stop_reason: Option<String>,
457 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub message: Option<String>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub final_message: Option<String>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
468 pub report_source: Option<String>,
469 #[serde(default, skip_serializing_if = "Option::is_none")]
472 pub turn_id: Option<u64>,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
476 pub requested_turn_id: Option<u64>,
477 #[serde(default, skip_serializing_if = "Option::is_none")]
479 pub turn_number: Option<u64>,
480 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub elapsed_ms: Option<i64>,
482 #[serde(default, skip_serializing_if = "Option::is_none")]
485 pub tool_calls: Option<u64>,
486 #[serde(default, skip_serializing_if = "Option::is_none")]
488 pub capacity_retry: Option<WaitCapacityRetry>,
489 #[serde(default, skip_serializing_if = "Option::is_none")]
490 pub server_retry: Option<WaitCapacityRetry>,
491 #[serde(default)]
492 pub retry_assessment_pending: bool,
493 #[serde(default, skip_serializing_if = "Option::is_none")]
494 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
495 #[serde(default, skip_serializing_if = "Option::is_none")]
499 pub relay: Option<RelayHealth>,
500 pub session: ApiSession,
501}
502
503pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
508pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
509
510#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
511pub struct TranscriptQuery {
512 #[serde(default)]
513 pub role: Option<mj_core::transcript::TranscriptRole>,
514 #[serde(default)]
516 pub after_seq: Option<u64>,
517 #[serde(default)]
518 pub limit: Option<usize>,
519}
520
521#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
522pub struct TranscriptItemView {
523 pub stable_id: String,
524 pub position: u64,
525 pub seq: u64,
528 pub role: String,
529 pub text: String,
531 pub created_at_ms: i64,
532 pub last_changed_at_ms: i64,
533 pub body: mj_core::transcript::TranscriptBody,
535}
536
537#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
538pub struct TranscriptResponse {
539 #[serde(default)]
540 pub next_after_seq: u64,
541 pub session_id: String,
542 pub latest_seq: u64,
545 pub execution: MaterializedExecutionState,
546 pub items: Vec<TranscriptItemView>,
547}
548
549#[derive(Debug, Clone, PartialEq, Eq)]
556pub struct TurnState {
557 pub execution: MaterializedExecutionState,
558 pub active_turn: Option<MaterializedTurn>,
559 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
560}
561
562pub use crate::database::TurnSummary;
563
564#[derive(Debug, Clone, Default, PartialEq, Eq)]
567pub struct StartFollowup {
568 pub model: Option<String>,
569 pub effort: Option<String>,
570 pub prompt: Option<String>,
571 pub fast_mode: bool,
572}
573
574#[derive(Debug, Clone, PartialEq, Eq)]
576pub enum StartStatus {
577 Pending,
579 Submitted { turn_id: u64 },
581 Failed { message: String },
583}
584
585pub use crate::database::TranscriptPage;
587
588#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
590pub struct PushedBranch {
591 pub branch: String,
592 pub remote: String,
593}
594
595#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
597pub struct FileQuery {
598 pub path: String,
600}
601
602#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
604#[serde(rename_all = "snake_case")]
605pub enum ExportKind {
606 Patch,
608 Branch,
610 Bundle,
612}
613
614#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
615pub struct ExportRequest {
616 pub kind: ExportKind,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
619 pub branch: Option<String>,
620}
621
622#[derive(Debug, Clone)]
624pub struct BundleExport {
625 pub repository: String,
626 pub bytes: Vec<u8>,
627}
628
629#[derive(Debug)]
631pub enum ExportError {
632 Refused(String),
635 Failed(anyhow::Error),
637}
638
639impl From<ExportError> for ApiFailure {
640 fn from(error: ExportError) -> Self {
641 match error {
642 ExportError::Refused(message) => Self::conflict(message),
643 ExportError::Failed(error) => Self::from(error),
644 }
645 }
646}
647
648#[derive(Debug, Default, Deserialize)]
653#[serde(deny_unknown_fields)]
654pub struct WikiSearchQuery {
655 #[serde(default)]
656 pub q: Option<String>,
657 #[serde(default)]
658 pub limit: Option<usize>,
659}
660
661#[derive(Debug, Default, Deserialize)]
662#[serde(deny_unknown_fields)]
663pub struct WikiBriefQuery {
664 #[serde(default)]
665 pub max_chars: Option<usize>,
666}
667
668#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
669pub struct WikiBriefResponse {
670 pub markdown: String,
671}
672
673#[derive(Debug, Default, Deserialize)]
675#[serde(deny_unknown_fields)]
676pub struct WikiHitsQuery {
677 pub q: String,
678 #[serde(default)]
679 pub context_messages: Option<usize>,
680 #[serde(default)]
681 pub per_message_chars: Option<usize>,
682}
683
684#[derive(Debug, Clone, Default, Serialize, Deserialize)]
687#[serde(deny_unknown_fields)]
688pub struct WikiRestoreBody {
689 #[serde(default)]
690 pub workspace_id: Option<String>,
691 pub profile_id: String,
692 pub target_id: String,
693 #[serde(default)]
694 pub project_directory: Option<PathBuf>,
695 #[serde(default)]
696 pub model: Option<String>,
697 #[serde(default)]
698 pub effort: Option<String>,
699}
700
701#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
715#[serde(deny_unknown_fields)]
716pub struct LaunchOptions {
717 pub revision: u64,
721 pub profiles: Vec<LaunchProfile>,
722 pub targets: Vec<LaunchTarget>,
723 pub bundles: Vec<LaunchBundle>,
724 #[serde(default, skip_serializing_if = "Vec::is_empty")]
727 pub hosts: Vec<LaunchHost>,
728 #[serde(default, skip_serializing_if = "Option::is_none")]
732 pub default: Option<LaunchDefault>,
733}
734
735#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
737#[serde(deny_unknown_fields)]
738pub struct LaunchProfile {
739 pub id: String,
740 pub harness: String,
743}
744
745#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
747#[serde(deny_unknown_fields)]
748pub struct LaunchTarget {
749 pub id: String,
750 pub kind: String,
752 pub requires_project_directory: bool,
755 pub availability: LaunchAvailability,
756 #[serde(default, skip_serializing_if = "Option::is_none")]
760 pub unavailable_reason: Option<String>,
761 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
766 pub runtime_missing: bool,
767 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
772 pub default_candidate: bool,
773 #[serde(default, skip_serializing_if = "Option::is_none")]
775 pub host: Option<String>,
776}
777
778#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
785#[serde(rename_all = "lowercase")]
786pub enum LaunchAvailability {
787 Ready,
789 Stale,
791 Unavailable,
793 Unknown,
795}
796
797#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
799#[serde(deny_unknown_fields)]
800pub struct LaunchBundle {
801 pub id: String,
802 pub primary_repository: String,
803 pub repositories: Vec<LaunchRepository>,
804}
805
806#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
807#[serde(deny_unknown_fields)]
808pub struct LaunchRepository {
809 pub id: String,
810 #[serde(default, skip_serializing_if = "Option::is_none")]
814 pub github: Option<String>,
815 pub destination: String,
816}
817
818#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
821#[serde(deny_unknown_fields)]
822pub struct LaunchHost {
823 pub id: String,
824 pub label: String,
825 pub targets: Vec<String>,
827 pub stale: bool,
828 pub refreshing: bool,
829 pub has_error: bool,
830}
831
832#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
834#[serde(deny_unknown_fields)]
835pub struct LaunchDefault {
836 pub profile_id: String,
837 pub target_id: String,
838}