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)]
130#[serde(deny_unknown_fields)]
131pub struct StartSessionRequest {
132 #[serde(default)]
133 pub create_managed_worktree: Option<bool>,
134 #[serde(default)]
136 pub mjolnir_subagents: Option<bool>,
137 #[serde(default)]
138 pub workspace_id: Option<String>,
139 pub profile_id: String,
140 pub target_id: String,
141 #[serde(default)]
142 pub bundle_id: Option<String>,
143 #[serde(default)]
144 pub project_directory: Option<PathBuf>,
145 #[serde(default)]
146 pub title: Option<String>,
147 #[serde(default)]
148 pub model: Option<String>,
149 #[serde(default)]
150 pub effort: Option<String>,
151 #[serde(default)]
152 pub prompt: Option<String>,
153}
154
155#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(deny_unknown_fields)]
161pub struct ResumeSessionRequest {
162 #[serde(default)]
164 pub profile_id: Option<String>,
165 #[serde(default)]
167 pub target_id: Option<String>,
168 #[serde(default)]
170 pub workspace_id: Option<String>,
171 #[serde(default)]
175 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct ResumeSessionResponse {
182 pub session_id: String,
183 pub workspace_id: String,
184 pub profile_id: String,
185 pub target_id: String,
186}
187
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
189pub struct StartSessionResponse {
190 pub session_id: String,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub turn_id: Option<u64>,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
198#[serde(deny_unknown_fields)]
199pub struct SubagentSourceRange {
200 pub file: PathBuf,
201 pub start: u64,
202 pub end: u64,
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(deny_unknown_fields)]
207pub struct SpawnSubagentRequest {
208 pub task_name: String,
209 pub instructions: String,
210 #[serde(default)]
211 pub profile_id: Option<String>,
212 #[serde(default)]
213 pub model: Option<String>,
214 #[serde(default)]
215 pub effort: Option<String>,
216 #[serde(default)]
217 pub working_directory: Option<PathBuf>,
218 #[serde(default)]
219 pub context: Option<String>,
220 #[serde(default)]
221 pub files: Vec<SubagentSourceRange>,
222 pub request_key: String,
223}
224
225#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
226pub struct SubagentView {
227 pub parent_session_id: String,
228 pub task_name: String,
229 pub request_key: String,
230 pub session: ApiSession,
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct SubagentListResponse {
235 pub subagents: Vec<SubagentView>,
236}
237
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239#[serde(deny_unknown_fields)]
240pub struct PromptRequest {
241 pub text: String,
242}
243
244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub struct PromptResponse {
246 pub turn_id: u64,
249}
250
251#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
252#[serde(deny_unknown_fields)]
253pub struct WaitRequest {
254 #[serde(default)]
256 pub return_on_input: bool,
257 #[serde(default)]
261 pub turn_id: Option<u64>,
262 #[serde(default)]
263 pub timeout_secs: Option<u64>,
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(rename_all = "snake_case")]
269pub enum WaitOutcome {
270 InputRequired,
272 Finished,
274 Error,
276 Cancelled,
278 QuotaLimit,
280 Timeout,
282 Stopped,
284}
285
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
287pub struct WaitCapacityRetry {
288 pub attempt: u32,
289 pub retry_at_ms: i64,
290}
291
292impl From<&CapacityRetry> for WaitCapacityRetry {
293 fn from(retry: &CapacityRetry) -> Self {
294 Self {
295 attempt: retry.attempt,
296 retry_at_ms: retry.retry_at_ms,
297 }
298 }
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
307#[serde(rename_all = "snake_case")]
308pub enum RelayState {
309 Connected,
311 Disconnected,
313 Unreachable,
315 TargetMissing,
317 ProjectionIntegrity,
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322pub struct RelayHealth {
323 pub state: RelayState,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
326 pub detail: Option<String>,
327}
328
329impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
330 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
331 use mj_client::session::ViewError;
332 match &view.error {
335 Some(error) => Self {
336 state: match error {
337 ViewError::Unreachable(_) => RelayState::Unreachable,
338 ViewError::TargetMissing(_) => RelayState::TargetMissing,
339 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
340 },
341 detail: Some(error.detail().to_owned()),
342 },
343 None if view.connected => Self {
344 state: RelayState::Connected,
345 detail: None,
346 },
347 None => Self {
348 state: RelayState::Disconnected,
349 detail: None,
350 },
351 }
352 }
353}
354
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
356pub struct WaitResponse {
357 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
359
360 #[serde(default, skip_serializing_if = "Vec::is_empty")]
361 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub usage: Option<mj_core::usage::TokenUsage>,
364 pub outcome: WaitOutcome,
365 #[serde(default, skip_serializing_if = "Option::is_none")]
367 pub stop_reason: Option<String>,
368 #[serde(default, skip_serializing_if = "Option::is_none")]
370 pub message: Option<String>,
371 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub final_message: Option<String>,
374 #[serde(default, skip_serializing_if = "Option::is_none")]
375 pub turn_id: Option<u64>,
376 #[serde(default, skip_serializing_if = "Option::is_none")]
378 pub turn_number: Option<u64>,
379 #[serde(default, skip_serializing_if = "Option::is_none")]
380 pub elapsed_ms: Option<i64>,
381 #[serde(default, skip_serializing_if = "Option::is_none")]
384 pub capacity_retry: Option<WaitCapacityRetry>,
385 #[serde(default, skip_serializing_if = "Option::is_none")]
386 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
387 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub relay: Option<RelayHealth>,
392 pub session: ApiSession,
393}
394
395pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
400pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
401
402#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
403pub struct TranscriptQuery {
404 #[serde(default)]
405 pub role: Option<mj_core::transcript::TranscriptRole>,
406 #[serde(default)]
408 pub after_seq: Option<u64>,
409 #[serde(default)]
410 pub limit: Option<usize>,
411}
412
413#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
414pub struct TranscriptItemView {
415 pub stable_id: String,
416 pub position: u64,
417 pub seq: u64,
420 pub role: String,
421 pub text: String,
423 pub created_at_ms: i64,
424 pub last_changed_at_ms: i64,
425 pub body: mj_core::transcript::TranscriptBody,
427}
428
429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
430pub struct TranscriptResponse {
431 #[serde(default)]
432 pub next_after_seq: u64,
433 pub session_id: String,
434 pub latest_seq: u64,
437 pub execution: MaterializedExecutionState,
438 pub items: Vec<TranscriptItemView>,
439}
440
441#[derive(Debug, Clone, PartialEq, Eq)]
448pub struct TurnState {
449 pub execution: MaterializedExecutionState,
450 pub active_turn: Option<MaterializedTurn>,
451 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
452}
453
454pub use crate::database::TurnSummary;
455
456#[derive(Debug, Clone, Default, PartialEq, Eq)]
459pub struct StartFollowup {
460 pub model: Option<String>,
461 pub effort: Option<String>,
462 pub prompt: Option<String>,
463}
464
465#[derive(Debug, Clone, PartialEq, Eq)]
467pub enum StartStatus {
468 Pending,
470 Submitted { turn_id: u64 },
472 Failed { message: String },
474}
475
476pub use crate::database::TranscriptPage;
478
479#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
481pub struct PushedBranch {
482 pub branch: String,
483 pub remote: String,
484}
485
486#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
488pub struct FileQuery {
489 pub path: String,
491}
492
493#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
495#[serde(rename_all = "snake_case")]
496pub enum ExportKind {
497 Patch,
499 Branch,
501 Bundle,
503}
504
505#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
506pub struct ExportRequest {
507 pub kind: ExportKind,
508 #[serde(default, skip_serializing_if = "Option::is_none")]
510 pub branch: Option<String>,
511}
512
513#[derive(Debug, Clone)]
515pub struct BundleExport {
516 pub repository: String,
517 pub bytes: Vec<u8>,
518}
519
520#[derive(Debug)]
522pub enum ExportError {
523 Refused(String),
526 Failed(anyhow::Error),
528}
529
530impl From<ExportError> for ApiFailure {
531 fn from(error: ExportError) -> Self {
532 match error {
533 ExportError::Refused(message) => Self::conflict(message),
534 ExportError::Failed(error) => Self::from(error),
535 }
536 }
537}
538
539#[derive(Debug, Default, Deserialize)]
544#[serde(deny_unknown_fields)]
545pub struct WikiSearchQuery {
546 #[serde(default)]
547 pub q: Option<String>,
548 #[serde(default)]
549 pub limit: Option<usize>,
550}
551
552#[derive(Debug, Default, Deserialize)]
553#[serde(deny_unknown_fields)]
554pub struct WikiBriefQuery {
555 #[serde(default)]
556 pub max_chars: Option<usize>,
557}
558
559#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
560pub struct WikiBriefResponse {
561 pub markdown: String,
562}
563
564#[derive(Debug, Default, Deserialize)]
566#[serde(deny_unknown_fields)]
567pub struct WikiHitsQuery {
568 pub q: String,
569 #[serde(default)]
570 pub context_messages: Option<usize>,
571 #[serde(default)]
572 pub per_message_chars: Option<usize>,
573}
574
575#[derive(Debug, Clone, Default, Serialize, Deserialize)]
578#[serde(deny_unknown_fields)]
579pub struct WikiRestoreBody {
580 #[serde(default)]
581 pub workspace_id: Option<String>,
582 pub profile_id: String,
583 pub target_id: String,
584 #[serde(default)]
585 pub project_directory: Option<PathBuf>,
586 #[serde(default)]
587 pub model: Option<String>,
588 #[serde(default)]
589 pub effort: Option<String>,
590}