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, PartialEq, Serialize, Deserialize)]
207pub struct StartSessionResponse {
208 pub session_id: String,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
212 pub turn_id: Option<u64>,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(deny_unknown_fields)]
217pub struct SubagentSourceRange {
218 pub file: PathBuf,
219 pub start: u64,
220 pub end: u64,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(deny_unknown_fields)]
225pub struct SpawnSubagentRequest {
226 pub task_name: String,
227 pub instructions: String,
228 #[serde(default)]
229 pub profile_id: Option<String>,
230 #[serde(default)]
231 pub model: Option<String>,
232 #[serde(default)]
233 pub effort: Option<String>,
234 #[serde(default)]
235 pub working_directory: Option<PathBuf>,
236 #[serde(default)]
237 pub context: Option<String>,
238 #[serde(default)]
239 pub files: Vec<SubagentSourceRange>,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct SubagentCandidate {
246 pub profile_id: String,
247 pub harness: mj_core::config::HarnessKind,
248 pub choices: mj_core::worker_launch::ProfileConfig,
249 pub remaining_percent: Option<u8>,
253}
254
255#[derive(Debug, Clone, Default, PartialEq, Eq)]
258pub struct SubagentCandidates {
259 pub offered: Vec<SubagentCandidate>,
260 pub unavailable: Vec<(String, String)>,
261}
262
263#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
264pub struct SubagentView {
265 pub parent_session_id: String,
266 pub task_name: String,
267 pub session: ApiSession,
268}
269
270#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
271pub struct SubagentListResponse {
272 pub subagents: Vec<SubagentView>,
273}
274
275#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
276#[serde(deny_unknown_fields)]
277pub struct PromptRequest {
278 pub text: String,
279}
280
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282pub struct PromptResponse {
283 pub turn_id: u64,
286}
287
288#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
289#[serde(deny_unknown_fields)]
290pub struct WaitRequest {
291 #[serde(default)]
293 pub return_on_input: bool,
294 #[serde(default)]
298 pub turn_id: Option<u64>,
299 #[serde(default)]
300 pub timeout_secs: Option<u64>,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
305#[serde(rename_all = "snake_case")]
306pub enum WaitOutcome {
307 InputRequired,
309 Finished,
311 Error,
313 Cancelled,
315 QuotaLimit,
317 Timeout,
319 Stopped,
321}
322
323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
324pub struct WaitCapacityRetry {
325 pub attempt: u32,
326 pub retry_at_ms: i64,
327}
328
329impl From<&CapacityRetry> for WaitCapacityRetry {
330 fn from(retry: &CapacityRetry) -> Self {
331 Self {
332 attempt: retry.attempt,
333 retry_at_ms: retry.retry_at_ms,
334 }
335 }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
344#[serde(rename_all = "snake_case")]
345pub enum RelayState {
346 Connected,
348 Disconnected,
350 Unreachable,
352 TargetMissing,
354 ProjectionIntegrity,
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub struct RelayHealth {
360 pub state: RelayState,
361 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub detail: Option<String>,
364}
365
366impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
367 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
368 use mj_client::session::ViewError;
369 match &view.error {
372 Some(error) => Self {
373 state: match error {
374 ViewError::Unreachable(_) => RelayState::Unreachable,
375 ViewError::TargetMissing(_) => RelayState::TargetMissing,
376 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
377 },
378 detail: Some(error.detail().to_owned()),
379 },
380 None if view.connected => Self {
381 state: RelayState::Connected,
382 detail: None,
383 },
384 None => Self {
385 state: RelayState::Disconnected,
386 detail: None,
387 },
388 }
389 }
390}
391
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393pub struct WaitResponse {
394 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
396
397 #[serde(default, skip_serializing_if = "Vec::is_empty")]
398 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
399 #[serde(default, skip_serializing_if = "Option::is_none")]
400 pub usage: Option<mj_core::usage::TokenUsage>,
401 pub outcome: WaitOutcome,
402 #[serde(default, skip_serializing_if = "Option::is_none")]
404 pub stop_reason: Option<String>,
405 #[serde(default, skip_serializing_if = "Option::is_none")]
407 pub message: Option<String>,
408 #[serde(default, skip_serializing_if = "Option::is_none")]
411 pub final_message: Option<String>,
412 #[serde(default, skip_serializing_if = "Option::is_none")]
416 pub report_source: Option<String>,
417 #[serde(default, skip_serializing_if = "Option::is_none")]
418 pub turn_id: Option<u64>,
419 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub turn_number: Option<u64>,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub elapsed_ms: Option<i64>,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub capacity_retry: Option<WaitCapacityRetry>,
427 #[serde(default, skip_serializing_if = "Option::is_none")]
428 pub server_retry: Option<WaitCapacityRetry>,
429 #[serde(default)]
430 pub retry_assessment_pending: bool,
431 #[serde(default, skip_serializing_if = "Option::is_none")]
432 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
433 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub relay: Option<RelayHealth>,
438 pub session: ApiSession,
439}
440
441pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
446pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
447
448#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
449pub struct TranscriptQuery {
450 #[serde(default)]
451 pub role: Option<mj_core::transcript::TranscriptRole>,
452 #[serde(default)]
454 pub after_seq: Option<u64>,
455 #[serde(default)]
456 pub limit: Option<usize>,
457}
458
459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
460pub struct TranscriptItemView {
461 pub stable_id: String,
462 pub position: u64,
463 pub seq: u64,
466 pub role: String,
467 pub text: String,
469 pub created_at_ms: i64,
470 pub last_changed_at_ms: i64,
471 pub body: mj_core::transcript::TranscriptBody,
473}
474
475#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
476pub struct TranscriptResponse {
477 #[serde(default)]
478 pub next_after_seq: u64,
479 pub session_id: String,
480 pub latest_seq: u64,
483 pub execution: MaterializedExecutionState,
484 pub items: Vec<TranscriptItemView>,
485}
486
487#[derive(Debug, Clone, PartialEq, Eq)]
494pub struct TurnState {
495 pub execution: MaterializedExecutionState,
496 pub active_turn: Option<MaterializedTurn>,
497 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
498}
499
500pub use crate::database::TurnSummary;
501
502#[derive(Debug, Clone, Default, PartialEq, Eq)]
505pub struct StartFollowup {
506 pub model: Option<String>,
507 pub effort: Option<String>,
508 pub prompt: Option<String>,
509 pub fast_mode: bool,
510}
511
512#[derive(Debug, Clone, PartialEq, Eq)]
514pub enum StartStatus {
515 Pending,
517 Submitted { turn_id: u64 },
519 Failed { message: String },
521}
522
523pub use crate::database::TranscriptPage;
525
526#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
528pub struct PushedBranch {
529 pub branch: String,
530 pub remote: String,
531}
532
533#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
535pub struct FileQuery {
536 pub path: String,
538}
539
540#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
542#[serde(rename_all = "snake_case")]
543pub enum ExportKind {
544 Patch,
546 Branch,
548 Bundle,
550}
551
552#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
553pub struct ExportRequest {
554 pub kind: ExportKind,
555 #[serde(default, skip_serializing_if = "Option::is_none")]
557 pub branch: Option<String>,
558}
559
560#[derive(Debug, Clone)]
562pub struct BundleExport {
563 pub repository: String,
564 pub bytes: Vec<u8>,
565}
566
567#[derive(Debug)]
569pub enum ExportError {
570 Refused(String),
573 Failed(anyhow::Error),
575}
576
577impl From<ExportError> for ApiFailure {
578 fn from(error: ExportError) -> Self {
579 match error {
580 ExportError::Refused(message) => Self::conflict(message),
581 ExportError::Failed(error) => Self::from(error),
582 }
583 }
584}
585
586#[derive(Debug, Default, Deserialize)]
591#[serde(deny_unknown_fields)]
592pub struct WikiSearchQuery {
593 #[serde(default)]
594 pub q: Option<String>,
595 #[serde(default)]
596 pub limit: Option<usize>,
597}
598
599#[derive(Debug, Default, Deserialize)]
600#[serde(deny_unknown_fields)]
601pub struct WikiBriefQuery {
602 #[serde(default)]
603 pub max_chars: Option<usize>,
604}
605
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
607pub struct WikiBriefResponse {
608 pub markdown: String,
609}
610
611#[derive(Debug, Default, Deserialize)]
613#[serde(deny_unknown_fields)]
614pub struct WikiHitsQuery {
615 pub q: String,
616 #[serde(default)]
617 pub context_messages: Option<usize>,
618 #[serde(default)]
619 pub per_message_chars: Option<usize>,
620}
621
622#[derive(Debug, Clone, Default, Serialize, Deserialize)]
625#[serde(deny_unknown_fields)]
626pub struct WikiRestoreBody {
627 #[serde(default)]
628 pub workspace_id: Option<String>,
629 pub profile_id: String,
630 pub target_id: String,
631 #[serde(default)]
632 pub project_directory: Option<PathBuf>,
633 #[serde(default)]
634 pub model: Option<String>,
635 #[serde(default)]
636 pub effort: Option<String>,
637}
638
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
653#[serde(deny_unknown_fields)]
654pub struct LaunchOptions {
655 pub revision: u64,
659 pub profiles: Vec<LaunchProfile>,
660 pub targets: Vec<LaunchTarget>,
661 pub bundles: Vec<LaunchBundle>,
662 #[serde(default, skip_serializing_if = "Vec::is_empty")]
665 pub hosts: Vec<LaunchHost>,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
670 pub default: Option<LaunchDefault>,
671}
672
673#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
675#[serde(deny_unknown_fields)]
676pub struct LaunchProfile {
677 pub id: String,
678 pub harness: String,
681}
682
683#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
685#[serde(deny_unknown_fields)]
686pub struct LaunchTarget {
687 pub id: String,
688 pub kind: String,
690 pub requires_project_directory: bool,
693 pub availability: LaunchAvailability,
694 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub unavailable_reason: Option<String>,
699 #[serde(default, skip_serializing_if = "Option::is_none")]
701 pub host: Option<String>,
702}
703
704#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
711#[serde(rename_all = "lowercase")]
712pub enum LaunchAvailability {
713 Ready,
715 Stale,
717 Unavailable,
719 Unknown,
721}
722
723#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
725#[serde(deny_unknown_fields)]
726pub struct LaunchBundle {
727 pub id: String,
728 pub primary_repository: String,
729 pub repositories: Vec<LaunchRepository>,
730}
731
732#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
733#[serde(deny_unknown_fields)]
734pub struct LaunchRepository {
735 pub id: String,
736 #[serde(default, skip_serializing_if = "Option::is_none")]
740 pub github: Option<String>,
741 pub destination: String,
742}
743
744#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
747#[serde(deny_unknown_fields)]
748pub struct LaunchHost {
749 pub id: String,
750 pub label: String,
751 pub targets: Vec<String>,
753 pub stale: bool,
754 pub refreshing: bool,
755 pub has_error: bool,
756}
757
758#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
760#[serde(deny_unknown_fields)]
761pub struct LaunchDefault {
762 pub profile_id: String,
763 pub target_id: String,
764}