1use super::*;
2
3#[derive(Debug, Default)]
5pub struct GithubTokenQuery {
6 pub owner: Option<String>,
7 pub repo: Vec<String>,
8}
9
10#[derive(Debug, Serialize, Deserialize)]
13#[serde(deny_unknown_fields)]
14pub struct GithubTokenResponse {
15 pub token: String,
16}
17
18#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct DiffOptions {
22 pub base: Option<String>,
23 #[serde(default)]
24 pub json: bool,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct ApiBackgroundWork {
30 pub known: Option<bool>,
31 pub tasks: Vec<mj_core::relay::BackgroundCommand>,
32}
33
34impl From<&mj_core::relay::RelayOperationalState> for ApiBackgroundWork {
35 fn from(state: &mj_core::relay::RelayOperationalState) -> Self {
36 Self {
37 known: state.background_work_known,
38 tasks: state.background_commands.clone(),
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct ApiSession {
48 #[serde(default)]
50 pub background_tasks: Vec<crate::server::ViewerBackgroundTask>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub assessment: Option<mj_core::assessment::Summary>,
53 #[serde(default)]
54 pub subagents: mj_core::subagent::SubagentPolicy,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub at: Option<String>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub branch: Option<String>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub base: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub background_work: Option<ApiBackgroundWork>,
68 pub id: String,
69 pub workspace_id: String,
70 pub title: String,
71 pub harness_kind: String,
72 pub profile_id: String,
73 pub target_id: String,
74 pub bundle_id: String,
75 pub state: String,
76 pub lifecycle: ViewerLifecycleCategory,
77 pub chat_phase: crate::server::ViewerChatPhase,
78 pub is_idle: bool,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub activity_state: Option<mj_core::activity::ActivityState>,
85 pub has_error: bool,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub error: Option<String>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub storage_problem: Option<String>,
95 pub created_at: String,
96 pub updated_at: String,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
104 #[serde(default)]
105 pub config_options: Vec<crate::server::ViewerConfigOption>,
106 #[serde(default, skip_serializing_if = "Vec::is_empty")]
107 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
108}
109
110impl From<&ViewerSession> for ApiSession {
111 fn from(session: &ViewerSession) -> Self {
112 Self {
113 background_tasks: session.background_tasks.clone(),
114 assessment: None,
115 subagents: session.subagents.clone(),
116 background_work: None,
117 at: session.at.clone(),
118 branch: session.branch.clone(),
119 base: session.base.clone(),
120 id: session.id.clone(),
121 workspace_id: session.workspace_id.clone(),
122 title: session.title.clone(),
123 harness_kind: session.harness_kind.clone(),
124 profile_id: session.profile_id.clone(),
125 target_id: session.target_id.clone(),
126 bundle_id: session.bundle_id.clone(),
127 state: session.state.clone(),
128 lifecycle: session.lifecycle,
129 chat_phase: session.chat_phase,
130 is_idle: session.is_idle,
131 activity_state: session.activity_state.clone(),
132 has_error: session.has_error,
133 error: session.launch_error.clone(),
134 storage_problem: session.storage_problem.clone(),
135 created_at: session.created_at.clone(),
136 updated_at: session.updated_at.clone(),
137 last_turn_outcome: None,
138 last_turn_diagnostic: None,
139 config_options: session.config_options.clone(),
140 pending_elicitations: session.pending_elicitations.clone(),
141 }
142 }
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
146#[serde(deny_unknown_fields)]
147pub struct StopBackgroundTaskRequest {
148 pub background_task_id: String,
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152pub struct SessionListResponse {
153 pub sessions: Vec<ApiSession>,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub struct WorkspaceListResponse {
160 pub workspaces: Vec<mj_core::workspace::WorkspaceRecord>,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(deny_unknown_fields)]
168pub struct CreateWorkspaceRequest {
169 pub name: String,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct CreateWorkspaceResponse {
174 pub workspace: mj_core::workspace::WorkspaceRecord,
175}
176
177#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
190#[serde(deny_unknown_fields)]
191pub struct StartSessionRequest {
192 #[serde(default)]
193 pub create_managed_worktree: Option<bool>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub at: Option<String>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub branch: Option<String>,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub base: Option<String>,
206 #[serde(default)]
208 pub subagents: Option<mj_core::subagent::SubagentPolicy>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub review: Option<mj_core::config::SessionReview>,
212 #[serde(default)]
213 pub workspace_id: Option<String>,
214 #[serde(default)]
217 pub profile_id: Option<String>,
218 #[serde(default)]
220 pub target_id: Option<String>,
221 #[serde(default)]
222 pub bundle_id: Option<String>,
223 #[serde(default)]
224 pub project_directory: Option<PathBuf>,
225 #[serde(default)]
226 pub title: Option<String>,
227 #[serde(default)]
228 pub model: Option<String>,
229 #[serde(default)]
230 pub effort: Option<String>,
231 #[serde(default)]
232 pub prompt: Option<String>,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub cpus: Option<u64>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
239 pub memory_bytes: Option<u64>,
240}
241
242#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
247#[serde(deny_unknown_fields)]
248pub struct ResumeSessionRequest {
249 #[serde(default)]
251 pub profile_id: Option<String>,
252 #[serde(default)]
254 pub target_id: Option<String>,
255 #[serde(default)]
257 pub workspace_id: Option<String>,
258 #[serde(default)]
262 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268pub struct ResumeSessionResponse {
269 pub session_id: String,
270 pub workspace_id: String,
271 pub profile_id: String,
272 pub target_id: String,
273}
274
275#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
278pub struct SuspendSessionResponse {
279 #[serde(default)]
280 pub session_id: String,
281 #[serde(default)]
283 pub stopped_subagents: usize,
284 #[serde(default)]
286 pub subagents_not_handed_back: usize,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub warning: Option<String>,
291}
292
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
294pub struct StartSessionResponse {
295 pub session_id: String,
296 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub turn_id: Option<u64>,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
303#[serde(deny_unknown_fields)]
304pub struct SpawnSubagentRequest {
305 pub task_name: String,
306 pub instructions: String,
307 #[serde(default)]
308 pub profile_id: Option<String>,
309 #[serde(default)]
310 pub model: Option<String>,
311 #[serde(default)]
312 pub effort: Option<String>,
313 #[serde(default)]
314 pub working_directory: Option<PathBuf>,
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct SubagentCandidate {
320 pub profile_id: String,
321 pub harness: mj_core::config::HarnessKind,
322 pub choices: mj_core::worker_launch::ProfileConfig,
323 pub remaining_percent: Option<u8>,
327}
328
329#[derive(Debug, Clone, Default, PartialEq, Eq)]
332pub struct SubagentCandidates {
333 pub offered: Vec<SubagentCandidate>,
334 pub unavailable: Vec<(String, String)>,
335}
336
337#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
338pub struct SubagentView {
339 pub parent_session_id: String,
340 pub task_name: String,
341 pub session: ApiSession,
342}
343
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
345pub struct SubagentListResponse {
346 pub subagents: Vec<SubagentView>,
347}
348
349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350#[serde(deny_unknown_fields)]
351pub struct PromptRequest {
352 pub text: String,
353}
354
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
356pub struct PromptResponse {
357 pub turn_id: u64,
360}
361
362#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
363#[serde(deny_unknown_fields)]
364pub struct WaitRequest {
365 #[serde(default)]
367 pub return_on_input: bool,
368 #[serde(default)]
372 pub turn_id: Option<u64>,
373 #[serde(default)]
374 pub timeout_secs: Option<u64>,
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
379#[serde(rename_all = "snake_case")]
380pub enum WaitOutcome {
381 InputRequired,
383 Finished,
385 Error,
387 Cancelled,
389 QuotaLimit,
391 Timeout,
393 Stopped,
395}
396
397#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
398pub struct WaitCapacityRetry {
399 pub attempt: u32,
400 pub retry_at_ms: i64,
401}
402
403impl From<&CapacityRetry> for WaitCapacityRetry {
404 fn from(retry: &CapacityRetry) -> Self {
405 Self {
406 attempt: retry.attempt,
407 retry_at_ms: retry.retry_at_ms,
408 }
409 }
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
418#[serde(rename_all = "snake_case")]
419pub enum RelayState {
420 Connected,
422 Disconnected,
424 Unreachable,
426 TargetMissing,
428 ProjectionIntegrity,
430}
431
432#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
433pub struct RelayHealth {
434 pub state: RelayState,
435 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub detail: Option<String>,
438}
439
440impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
441 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
442 use mj_client::session::ViewError;
443 match &view.error {
446 Some(error) => Self {
447 state: match error {
448 ViewError::Unreachable(_) => RelayState::Unreachable,
449 ViewError::TargetMissing(_) => RelayState::TargetMissing,
450 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
451 },
452 detail: Some(error.detail().to_owned()),
453 },
454 None if view.connected => Self {
455 state: RelayState::Connected,
456 detail: None,
457 },
458 None => Self {
459 state: RelayState::Disconnected,
460 detail: None,
461 },
462 }
463 }
464}
465
466#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
467pub struct WaitResponse {
468 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
470
471 #[serde(default, skip_serializing_if = "Vec::is_empty")]
472 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
474 pub usage: Option<mj_core::usage::TokenUsage>,
475 pub outcome: WaitOutcome,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub stop_reason: Option<String>,
479 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub message: Option<String>,
482 #[serde(default, skip_serializing_if = "Option::is_none")]
485 pub final_message: Option<String>,
486 #[serde(default, skip_serializing_if = "Option::is_none")]
490 pub report_source: Option<String>,
491 #[serde(default, skip_serializing_if = "Option::is_none")]
494 pub turn_id: Option<u64>,
495 #[serde(default, skip_serializing_if = "Option::is_none")]
498 pub requested_turn_id: Option<u64>,
499 #[serde(default, skip_serializing_if = "Option::is_none")]
501 pub turn_number: Option<u64>,
502 #[serde(default, skip_serializing_if = "Option::is_none")]
503 pub elapsed_ms: Option<i64>,
504 #[serde(default, skip_serializing_if = "Option::is_none")]
507 pub tool_calls: Option<u64>,
508 #[serde(default, skip_serializing_if = "Option::is_none")]
510 pub capacity_retry: Option<WaitCapacityRetry>,
511 #[serde(default, skip_serializing_if = "Option::is_none")]
512 pub server_retry: Option<WaitCapacityRetry>,
513 #[serde(default)]
514 pub retry_assessment_pending: bool,
515 #[serde(default, skip_serializing_if = "Option::is_none")]
516 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
517 #[serde(default, skip_serializing_if = "Option::is_none")]
521 pub relay: Option<RelayHealth>,
522 pub session: ApiSession,
523}
524
525pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
530pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
531
532#[derive(Debug, Clone, Default, PartialEq, Serialize)]
533pub struct TranscriptQuery {
534 pub role: Vec<mj_core::transcript::TranscriptRole>,
535 pub finished_only: bool,
537 pub after_seq: Option<u64>,
539 pub limit: Option<usize>,
540}
541
542impl<'de> serde::Deserialize<'de> for TranscriptQuery {
543 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
544 where
545 D: serde::Deserializer<'de>,
546 {
547 struct TranscriptQueryVisitor;
548
549 impl<'de> serde::de::Visitor<'de> for TranscriptQueryVisitor {
550 type Value = TranscriptQuery;
551
552 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 formatter.write_str("transcript query parameters")
554 }
555
556 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
557 where
558 M: serde::de::MapAccess<'de>,
559 {
560 let mut role = Vec::new();
561 let mut finished_only = None;
562 let mut after_seq = None;
563 let mut limit = None;
564
565 while let Some(key) = map.next_key::<String>()? {
566 match key.as_str() {
567 "role" => role.push(map.next_value()?),
568 "finished_only" => {
569 if finished_only.replace(map.next_value()?).is_some() {
570 return Err(serde::de::Error::duplicate_field("finished_only"));
571 }
572 }
573 "after_seq" => {
574 if after_seq.replace(map.next_value()?).is_some() {
575 return Err(serde::de::Error::duplicate_field("after_seq"));
576 }
577 }
578 "limit" => {
579 if limit.replace(map.next_value()?).is_some() {
580 return Err(serde::de::Error::duplicate_field("limit"));
581 }
582 }
583 _ => {
584 let _: serde::de::IgnoredAny = map.next_value()?;
585 }
586 }
587 }
588
589 Ok(TranscriptQuery {
590 role,
591 finished_only: finished_only.unwrap_or_default(),
592 after_seq,
593 limit,
594 })
595 }
596 }
597
598 deserializer.deserialize_map(TranscriptQueryVisitor)
599 }
600}
601
602#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
603pub struct TranscriptItemView {
604 pub stable_id: String,
605 pub position: u64,
606 pub seq: u64,
609 pub role: String,
610 pub text: String,
612 pub created_at_ms: i64,
613 pub last_changed_at_ms: i64,
614 pub body: mj_core::transcript::TranscriptBody,
616}
617
618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
619pub struct TranscriptResponse {
620 #[serde(default)]
621 pub next_after_seq: u64,
622 pub session_id: String,
623 pub latest_seq: u64,
626 pub execution: MaterializedExecutionState,
627 pub items: Vec<TranscriptItemView>,
628}
629
630#[derive(Debug, Clone, PartialEq, Eq)]
637pub struct TurnState {
638 pub execution: MaterializedExecutionState,
639 pub active_turn: Option<MaterializedTurn>,
640 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
641}
642
643pub use crate::database::TurnSummary;
644
645#[derive(Debug, Clone, Default, PartialEq, Eq)]
648pub struct StartFollowup {
649 pub model: Option<String>,
650 pub effort: Option<String>,
651 pub prompt: Option<String>,
652 pub fast_mode: bool,
653}
654
655#[derive(Debug, Clone, PartialEq, Eq)]
657pub enum StartStatus {
658 Pending,
660 Submitted { turn_id: u64 },
662 Failed { message: String },
664}
665
666pub use crate::database::TranscriptPage;
668
669#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
671pub struct PushedBranch {
672 pub branch: String,
673 pub remote: String,
674}
675
676#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
678pub struct FileQuery {
679 pub path: String,
681}
682
683#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
685#[serde(rename_all = "snake_case")]
686pub enum ExportKind {
687 Patch,
689 Branch,
691 Bundle,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
696pub struct ExportRequest {
697 pub kind: ExportKind,
698 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub branch: Option<String>,
701}
702
703#[derive(Debug, Clone)]
705pub struct BundleExport {
706 pub repository: String,
707 pub bytes: Vec<u8>,
708}
709
710#[derive(Debug)]
712pub enum ExportError {
713 Refused(String),
716 Failed(anyhow::Error),
718}
719
720impl From<ExportError> for ApiFailure {
721 fn from(error: ExportError) -> Self {
722 match error {
723 ExportError::Refused(message) => Self::conflict(message),
724 ExportError::Failed(error) => Self::from(error),
725 }
726 }
727}
728
729#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
732pub struct StartReviewResponse {
733 pub session_id: String,
734 pub started: bool,
735}
736
737#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
739pub struct ResolveReviewResponse {
740 pub session_id: String,
741 pub resolution: String,
743}
744
745#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
749pub struct ReviewStatusResponse {
750 pub session_id: String,
751 #[serde(default)]
752 pub review: Option<crate::server::ViewerTurnReview>,
753}
754
755#[derive(Debug, Default, Deserialize)]
760#[serde(deny_unknown_fields)]
761pub struct WikiSearchQuery {
762 #[serde(default)]
763 pub q: Option<String>,
764 #[serde(default)]
765 pub limit: Option<usize>,
766}
767
768#[derive(Debug, Default, Deserialize)]
769#[serde(deny_unknown_fields)]
770pub struct WikiBriefQuery {
771 #[serde(default)]
772 pub max_chars: Option<usize>,
773}
774
775#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
776pub struct WikiBriefResponse {
777 pub markdown: String,
778}
779
780#[derive(Debug, Default, Deserialize)]
782#[serde(deny_unknown_fields)]
783pub struct WikiHitsQuery {
784 pub q: String,
785 #[serde(default)]
786 pub context_messages: Option<usize>,
787 #[serde(default)]
788 pub per_message_chars: Option<usize>,
789}
790
791#[derive(Debug, Clone, Default, Serialize, Deserialize)]
794#[serde(deny_unknown_fields)]
795pub struct WikiRestoreBody {
796 #[serde(default)]
797 pub workspace_id: Option<String>,
798 pub profile_id: String,
799 pub target_id: String,
800 #[serde(default)]
801 pub project_directory: Option<PathBuf>,
802 #[serde(default)]
803 pub model: Option<String>,
804 #[serde(default)]
805 pub effort: Option<String>,
806}
807
808#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
822#[serde(deny_unknown_fields)]
823pub struct LaunchOptions {
824 pub revision: u64,
828 pub profiles: Vec<LaunchProfile>,
829 pub targets: Vec<LaunchTarget>,
830 pub bundles: Vec<LaunchBundle>,
831 #[serde(default, skip_serializing_if = "Vec::is_empty")]
834 pub hosts: Vec<LaunchHost>,
835 #[serde(default, skip_serializing_if = "Option::is_none")]
839 pub default: Option<LaunchDefault>,
840}
841
842#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
844#[serde(deny_unknown_fields)]
845pub struct LaunchProfile {
846 pub id: String,
847 pub harness: String,
850}
851
852#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
854#[serde(deny_unknown_fields)]
855pub struct LaunchTarget {
856 pub id: String,
857 pub kind: String,
859 pub requires_project_directory: bool,
862 pub availability: LaunchAvailability,
863 #[serde(default, skip_serializing_if = "Option::is_none")]
867 pub unavailable_reason: Option<String>,
868 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
873 pub runtime_missing: bool,
874 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
879 pub default_candidate: bool,
880 #[serde(default, skip_serializing_if = "Option::is_none")]
882 pub host: Option<String>,
883}
884
885#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
892#[serde(rename_all = "lowercase")]
893pub enum LaunchAvailability {
894 Ready,
896 Stale,
898 Unavailable,
900 Unknown,
902}
903
904#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
906#[serde(deny_unknown_fields)]
907pub struct LaunchBundle {
908 pub id: String,
909 pub primary_repository: String,
910 pub repositories: Vec<LaunchRepository>,
911}
912
913#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
914#[serde(deny_unknown_fields)]
915pub struct LaunchRepository {
916 pub id: String,
917 #[serde(default, skip_serializing_if = "Option::is_none")]
921 pub github: Option<String>,
922 pub destination: String,
923}
924
925#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
928#[serde(deny_unknown_fields)]
929pub struct LaunchHost {
930 pub id: String,
931 pub label: String,
932 pub targets: Vec<String>,
934 pub stale: bool,
935 pub refreshing: bool,
936 pub has_error: bool,
937}
938
939#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
941#[serde(deny_unknown_fields)]
942pub struct LaunchDefault {
943 pub profile_id: String,
944 pub target_id: String,
945}