Skip to main content

roder_protocol/
lib.rs

1pub mod agent_node;
2pub mod chrome;
3pub mod hosted;
4pub mod methods;
5pub mod schema;
6pub mod speech;
7pub mod stats;
8pub mod workflows;
9
10use roder_api::artifacts::{
11    ArtifactGrepPage, ArtifactReadPage, ArtifactTailPage, ContextArtifactDescriptor,
12    ContextArtifactKind,
13};
14
15use roder_api::automations::{
16    AutomationConcurrencyPolicy, AutomationDefinition, AutomationId, AutomationProject,
17    AutomationRunId, AutomationRunState, AutomationRunSummary, AutomationSchedule, CatchUpPolicy,
18};
19use roder_api::capabilities::CapabilityStatus;
20use roder_api::code_index::{
21    CodeChunk, CodeIndexGenerationId, CodeIndexSearchResponse, CodeIndexStats, CodeIndexStatus,
22    ContentProof,
23};
24use roder_api::context::ContextBlock;
25use roder_api::discovery::{
26    DiscoveryCatalog, DiscoveryCatalogGroup, DiscoveryCatalogItem, DiscoveryPromotionRecord,
27};
28use roder_api::events::{ThreadId, TurnId};
29use roder_api::extension::{ExtensionId, ExtensionManifest};
30pub use roder_api::forks::WorkspaceFork;
31pub use roder_api::goals::{ThreadGoal, ThreadGoalStatus};
32use roder_api::inference::{
33    HostedWebSearchMode, InferenceCapabilities, ModelDescriptor, ModelSelection, ProviderAuthType,
34    TokenUsage,
35};
36use roder_api::inference_routing::{
37    InferenceRoutingCostDelta, InferenceRoutingDecision, InferenceRoutingOptionDescriptor,
38    InferenceRoutingOutcome, ModelSelectionMode,
39};
40use roder_api::knowledge::{
41    KnowledgeDocId, KnowledgeDocSummary, KnowledgeDocument, KnowledgeKind, KnowledgeLinkType,
42    KnowledgeRevisionInfo, KnowledgeSearchResult as KnowledgeSearchMatch, KnowledgeStatus,
43};
44use roder_api::marketplace::{
45    DedupedMarketplacePlugin, DefaultMarketplaceSelection, InstalledPluginRecord,
46    MarketplaceDescriptor, MarketplaceKind, MarketplacePluginEntry, MarketplaceSource,
47};
48use roder_api::media::{MediaArtifact, MediaArtifactId, MediaAttachment, MediaPreview};
49use roder_api::memory::{
50    MemoryId, MemoryProviderSelection, MemoryRecord, MemoryScope, MemorySearchResult,
51};
52use roder_api::packages::{PackageRecord, PackageResource, PackageResourceFilters, PackageScope};
53use roder_api::plan_review::{
54    HunkId, HunkRecord, PagedHunkDiff, PlanComment, PlanCommentAnchor, PlanReview, PlanReviewId,
55    PlanRewrite,
56};
57use roder_api::policy_mode::PolicyMode;
58use roder_api::processes::{ProcessDescriptor, ProcessId, ProcessOutput, ProcessStopResult};
59use roder_api::retrieval::{RetrievalMeasuredOutcome, RetrievalMode, RetrievalRoutePlan};
60use roder_api::skills::{Skill, SkillDescriptor, SkillExposure, SkillSelector};
61use roder_api::subagents::SubagentPermissionMode;
62use roder_api::tasks::{TaskHandle, TaskOutputStream};
63use roder_api::teams::{
64    AgentTeamDisplayMode, TeamId, TeamMailboxMessage, TeamMemberDescriptor, TeamMemberId,
65    TeamMemberStatus, TeamTaskDescriptor,
66};
67use roder_api::thread::ThreadUsageMetadata;
68use roder_api::tools::ToolSpec;
69use roder_api::trace::{SubagentTraceDelta, SubagentTraceId, SubagentTraceSummary};
70use roder_api::transcript::InputImage;
71use roder_api::version_control::VcsChangeArea;
72use roder_api::workflow::{
73    WorkflowImportDecision, WorkflowImportItem, WorkflowImportScan, WorkflowImportState,
74};
75use roder_api::workspace_changes::WorkspaceChangeObservation;
76use serde::{Deserialize, Serialize};
77use std::collections::BTreeMap;
78use std::collections::HashMap;
79use time::OffsetDateTime;
80
81pub use chrome::*;
82pub use speech::*;
83pub use workflows::*;
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct JsonRpcRequest {
87    #[serde(default = "default_jsonrpc_version")]
88    pub jsonrpc: String,
89    pub id: Option<serde_json::Value>,
90    pub method: String,
91    pub params: Option<serde_json::Value>,
92}
93
94fn default_jsonrpc_version() -> String {
95    "2.0".to_string()
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct JsonRpcResponse {
100    pub jsonrpc: String,
101    pub id: Option<serde_json::Value>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub result: Option<serde_json::Value>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub error: Option<JsonRpcError>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct JsonRpcNotification {
110    #[serde(default = "default_jsonrpc_version")]
111    pub jsonrpc: String,
112    pub method: String,
113    pub params: serde_json::Value,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct JsonRpcError {
118    pub code: i32,
119    pub message: String,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub data: Option<serde_json::Value>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct InitializeResult {
127    pub provider: String,
128    pub model: String,
129    pub cwd: Option<String>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct ThreadStatus {
135    #[serde(rename = "type")]
136    pub kind: String,
137    pub active_turn_id: Option<TurnId>,
138    pub active_flags: Vec<String>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct Thread {
144    pub id: ThreadId,
145    pub preview: String,
146    pub model_provider: String,
147    pub model: String,
148    #[serde(default, skip_serializing_if = "Option::is_none")]
149    pub selection_mode: Option<ModelSelectionMode>,
150    pub created_at: i64,
151    pub updated_at: i64,
152    pub status: ThreadStatus,
153    pub cwd: String,
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub workspace_id: Option<String>,
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub root_id: Option<String>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub name: Option<String>,
160    #[serde(default, skip_serializing_if = "Option::is_none")]
161    pub message_count: Option<u32>,
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub turns: Option<Vec<Turn>>,
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub usage: Option<ThreadUsageMetadata>,
166    /// Per-thread tool filter applied on top of the runtime allowlist. Empty = no filtering.
167    #[serde(default, skip_serializing_if = "Vec::is_empty")]
168    pub tool_allowlist: Vec<String>,
169    /// Host-supplied instructions added to the developer slot of every turn's inference request.
170    #[serde(default, skip_serializing_if = "Option::is_none")]
171    pub developer_instructions: Option<String>,
172    /**
173     * Host-executed tool specs advertised to the model on every turn of this thread. Calls pause
174     * on `thread/toolExecutionRequested` until the client answers with `tools/resolve`.
175     */
176    #[serde(default, skip_serializing_if = "Vec::is_empty")]
177    pub external_tools: Vec<ToolSpec>,
178    /**
179     * Remote-runner binding fixed at thread/start; absent = native coding tools execute locally.
180     * Lets hosts verify that a reused thread targets the intended runner workspace, since the
181     * binding never changes after creation. The destination config carries no secrets.
182     */
183    #[serde(default, skip_serializing_if = "Option::is_none")]
184    pub runner: Option<ThreadRunnerParams>,
185    /// Parent thread for conversation forks; absent for normal threads.
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub parent_thread_id: Option<ThreadId>,
188    /// Compact workspace-fork provenance for forked threads.
189    #[serde(default, skip_serializing_if = "Option::is_none")]
190    pub workspace_fork: Option<WorkspaceFork>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct Turn {
196    pub id: TurnId,
197    pub items: Vec<Item>,
198    pub items_view: String,
199    pub status: String,
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub error: Option<serde_json::Value>,
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub started_at: Option<i64>,
204    #[serde(default, skip_serializing_if = "Option::is_none")]
205    pub completed_at: Option<i64>,
206    #[serde(default, skip_serializing_if = "Option::is_none")]
207    pub duration_ms: Option<i64>,
208    #[serde(default, skip_serializing_if = "Option::is_none")]
209    pub usage: Option<TokenUsage>,
210    /**
211     * Normalized stop reason of the turn's terminal inference step ("stop",
212     * "length", "toolUse", "contentFilter", "refusal", or a provider-native
213     * value passed through). Present on `turn/completed` for completed turns.
214     */
215    #[serde(default, skip_serializing_if = "Option::is_none")]
216    pub finish_reason: Option<String>,
217}
218
219#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
220#[serde(rename_all = "camelCase")]
221pub enum ThreadItemStatus {
222    InProgress,
223    Completed,
224    Failed,
225}
226
227impl From<roder_api::thread::ThreadItemStatus> for ThreadItemStatus {
228    fn from(value: roder_api::thread::ThreadItemStatus) -> Self {
229        match value {
230            roder_api::thread::ThreadItemStatus::InProgress => Self::InProgress,
231            roder_api::thread::ThreadItemStatus::Completed => Self::Completed,
232            roder_api::thread::ThreadItemStatus::Failed => Self::Failed,
233        }
234    }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
238#[serde(tag = "type", rename_all = "camelCase")]
239pub enum Item {
240    UserMessage {
241        id: String,
242        text: String,
243        #[serde(default, skip_serializing_if = "Vec::is_empty")]
244        images: Vec<InputImage>,
245        #[serde(default, skip_serializing_if = "Option::is_none")]
246        status: Option<ThreadItemStatus>,
247    },
248    AgentMessage {
249        id: String,
250        text: String,
251        #[serde(default, skip_serializing_if = "Option::is_none")]
252        phase: Option<String>,
253        #[serde(default, skip_serializing_if = "Option::is_none")]
254        status: Option<ThreadItemStatus>,
255    },
256    Reasoning {
257        id: String,
258        #[serde(default, skip_serializing_if = "Vec::is_empty")]
259        summary: Vec<String>,
260        #[serde(default, skip_serializing_if = "Vec::is_empty")]
261        content: Vec<String>,
262        #[serde(default, skip_serializing_if = "Option::is_none")]
263        status: Option<ThreadItemStatus>,
264    },
265    ToolExecution {
266        id: String,
267        #[serde(rename = "toolCallId")]
268        tool_call_id: String,
269        #[serde(rename = "toolName")]
270        tool_name: String,
271        status: ThreadItemStatus,
272        #[serde(default, skip_serializing_if = "Option::is_none")]
273        input: Option<serde_json::Value>,
274        #[serde(default, skip_serializing_if = "Option::is_none")]
275        output: Option<String>,
276        #[serde(default, skip_serializing_if = "Option::is_none")]
277        error: Option<String>,
278    },
279    RoutingDecision {
280        id: String,
281        decision: InferenceRoutingDecisionEvent,
282        #[serde(default, skip_serializing_if = "Option::is_none")]
283        status: Option<ThreadItemStatus>,
284    },
285    Compaction {
286        id: String,
287        summary: String,
288        #[serde(default, skip_serializing_if = "Option::is_none")]
289        status: Option<ThreadItemStatus>,
290    },
291    Error {
292        id: String,
293        message: String,
294        #[serde(default, skip_serializing_if = "Option::is_none")]
295        status: Option<ThreadItemStatus>,
296    },
297    Raw {
298        id: String,
299        payload: serde_json::Value,
300        #[serde(default, skip_serializing_if = "Option::is_none")]
301        status: Option<ThreadItemStatus>,
302    },
303}
304
305impl From<roder_api::thread::ThreadItem> for Item {
306    fn from(value: roder_api::thread::ThreadItem) -> Self {
307        match value {
308            roder_api::thread::ThreadItem::UserMessage {
309                id,
310                text,
311                images,
312                status,
313            } => Self::UserMessage {
314                id,
315                text,
316                images,
317                status: status.map(Into::into),
318            },
319            roder_api::thread::ThreadItem::AgentMessage {
320                id,
321                text,
322                phase,
323                status,
324            } => Self::AgentMessage {
325                id,
326                text,
327                phase,
328                status: status.map(Into::into),
329            },
330            roder_api::thread::ThreadItem::Reasoning {
331                id,
332                summary,
333                content,
334                status,
335            } => Self::Reasoning {
336                id,
337                summary,
338                content,
339                status: status.map(Into::into),
340            },
341            roder_api::thread::ThreadItem::ToolExecution {
342                id,
343                tool_call_id,
344                tool_name,
345                status,
346                input,
347                output,
348                error,
349            } => Self::ToolExecution {
350                id,
351                tool_call_id,
352                tool_name,
353                status: status.into(),
354                input,
355                output,
356                error,
357            },
358            roder_api::thread::ThreadItem::RoutingDecision {
359                id,
360                decision,
361                status,
362            } => Self::RoutingDecision {
363                id,
364                decision: decision.into(),
365                status: status.map(Into::into),
366            },
367            roder_api::thread::ThreadItem::Compaction {
368                id,
369                summary,
370                status,
371            } => Self::Compaction {
372                id,
373                summary,
374                status: status.map(Into::into),
375            },
376            roder_api::thread::ThreadItem::Error {
377                id,
378                message,
379                status,
380            } => Self::Error {
381                id,
382                message,
383                status: status.map(Into::into),
384            },
385            roder_api::thread::ThreadItem::Raw {
386                id,
387                payload,
388                status,
389            } => Self::Raw {
390                id,
391                payload,
392                status: status.map(Into::into),
393            },
394        }
395    }
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
399#[serde(tag = "type", rename_all = "camelCase")]
400pub enum ThreadItemDelta {
401    AgentMessageText {
402        delta: String,
403        #[serde(default, skip_serializing_if = "Option::is_none")]
404        phase: Option<String>,
405    },
406    ReasoningText {
407        delta: String,
408        #[serde(rename = "contentIndex")]
409        content_index: usize,
410    },
411    ReasoningSummaryPartAdded {
412        #[serde(rename = "summaryIndex")]
413        summary_index: usize,
414    },
415    ReasoningSummaryText {
416        delta: String,
417        #[serde(rename = "summaryIndex")]
418        summary_index: usize,
419    },
420}
421
422impl From<roder_api::thread::ThreadItemDelta> for ThreadItemDelta {
423    fn from(value: roder_api::thread::ThreadItemDelta) -> Self {
424        match value {
425            roder_api::thread::ThreadItemDelta::AgentMessageText { delta, phase } => {
426                Self::AgentMessageText { delta, phase }
427            }
428            roder_api::thread::ThreadItemDelta::ReasoningText {
429                delta,
430                content_index,
431            } => Self::ReasoningText {
432                delta,
433                content_index,
434            },
435            roder_api::thread::ThreadItemDelta::ReasoningSummaryPartAdded { summary_index } => {
436                Self::ReasoningSummaryPartAdded { summary_index }
437            }
438            roder_api::thread::ThreadItemDelta::ReasoningSummaryText {
439                delta,
440                summary_index,
441            } => Self::ReasoningSummaryText {
442                delta,
443                summary_index,
444            },
445        }
446    }
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
450#[serde(tag = "type", rename_all = "camelCase")]
451pub enum ThreadItemEventKind {
452    ItemStarted {
453        item: Item,
454    },
455    ItemDelta {
456        #[serde(rename = "itemId")]
457        item_id: String,
458        delta: ThreadItemDelta,
459    },
460    ItemCompleted {
461        item: Item,
462    },
463}
464
465impl From<roder_api::thread::ThreadItemEventKind> for ThreadItemEventKind {
466    fn from(value: roder_api::thread::ThreadItemEventKind) -> Self {
467        match value {
468            roder_api::thread::ThreadItemEventKind::ItemStarted { item } => {
469                Self::ItemStarted { item: item.into() }
470            }
471            roder_api::thread::ThreadItemEventKind::ItemDelta { item_id, delta } => {
472                Self::ItemDelta {
473                    item_id,
474                    delta: delta.into(),
475                }
476            }
477            roder_api::thread::ThreadItemEventKind::ItemCompleted { item } => {
478                Self::ItemCompleted { item: item.into() }
479            }
480        }
481    }
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
485#[serde(rename_all = "camelCase")]
486pub struct ThreadItemEvent {
487    pub seq: u64,
488    pub event_id: String,
489    pub thread_id: ThreadId,
490    pub turn_id: TurnId,
491    #[serde(with = "time::serde::rfc3339")]
492    pub timestamp: OffsetDateTime,
493    pub event: ThreadItemEventKind,
494}
495
496impl From<roder_api::thread::ThreadItemEvent> for ThreadItemEvent {
497    fn from(value: roder_api::thread::ThreadItemEvent) -> Self {
498        Self {
499            seq: value.seq,
500            event_id: value.event_id,
501            thread_id: value.thread_id,
502            turn_id: value.turn_id,
503            timestamp: value.timestamp,
504            event: value.event.into(),
505        }
506    }
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
510#[serde(rename_all = "camelCase")]
511pub struct ThreadStartParams {
512    #[serde(default, skip_serializing_if = "Option::is_none")]
513    pub selection: Option<ModelSelectChoice>,
514    pub model: Option<String>,
515    pub model_provider: Option<String>,
516    pub reasoning: Option<String>,
517    pub workspace_id: String,
518    #[serde(default, skip_serializing_if = "Option::is_none")]
519    pub root_id: Option<String>,
520    #[serde(default, skip_serializing_if = "Option::is_none")]
521    pub cwd: Option<String>,
522    /**
523     * Per-thread tool filter applied on top of the runtime allowlist. Absent = no
524     * filtering; an explicit empty array is rejected with invalid params.
525     */
526    #[serde(default, skip_serializing_if = "Option::is_none")]
527    pub tool_allowlist: Option<Vec<String>>,
528    /// Host-supplied instructions added to the developer slot of every turn's inference request.
529    #[serde(default, skip_serializing_if = "Option::is_none")]
530    pub developer_instructions: Option<String>,
531    /**
532     * Host-executed tool specs advertised to the model on every turn of this thread. Calls pause
533     * on `thread/toolExecutionRequested` until the client answers with `tools/resolve`.
534     */
535    #[serde(default, skip_serializing_if = "Option::is_none")]
536    pub external_tools: Option<Vec<ToolSpec>>,
537    /**
538     * Binds the thread's native coding tools to a remote-runner workspace.
539     * Absent = local execution, even when a runtime-level runner destination
540     * is selected via `runners/select`.
541     */
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    pub runner: Option<ThreadRunnerParams>,
544    #[serde(default)]
545    pub ephemeral: bool,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
549#[serde(rename_all = "camelCase")]
550pub struct ThreadRunnerParams {
551    /// Installed remote-runner provider id (e.g. "e2b").
552    pub provider_id: String,
553    /**
554     * Provider-specific destination config. Persisted with the thread, so it
555     * must not carry secrets; providers read those from their environment.
556     */
557    #[serde(default, skip_serializing_if = "Option::is_none")]
558    pub config: Option<serde_json::Value>,
559    /// Absolute path on the runner used as the thread's coding-tool workspace root.
560    pub workspace: String,
561    /**
562     * Extra absolute runner paths file reads may resolve under, beyond
563     * `workspace`. Writes and the working directory stay confined to
564     * `workspace`.
565     */
566    #[serde(default, skip_serializing_if = "Vec::is_empty")]
567    pub read_roots: Vec<String>,
568}
569
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct ThreadStartResult {
573    pub thread: Thread,
574    pub model: String,
575    pub model_provider: String,
576    pub reasoning: String,
577    #[serde(default, skip_serializing_if = "Option::is_none")]
578    pub selection_mode: Option<ModelSelectionMode>,
579    pub cwd: String,
580    pub workspace_id: String,
581    pub root_id: String,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
585#[serde(rename_all = "camelCase")]
586pub struct WorkspaceRoot {
587    pub id: String,
588    pub path: String,
589    pub name: String,
590}
591
592#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
593#[serde(rename_all = "camelCase")]
594pub struct Workspace {
595    pub id: String,
596    pub name: String,
597    pub roots: Vec<WorkspaceRoot>,
598    pub default_root_id: String,
599    pub updated_at: i64,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
603#[serde(rename_all = "camelCase")]
604pub struct WorkspaceRootInput {
605    pub path: String,
606    #[serde(default, skip_serializing_if = "Option::is_none")]
607    pub name: Option<String>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
611#[serde(rename_all = "camelCase")]
612pub struct WorkspaceListParams {}
613
614#[derive(Debug, Clone, Serialize, Deserialize)]
615#[serde(rename_all = "camelCase")]
616pub struct WorkspaceListResult {
617    pub workspaces: Vec<Workspace>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize)]
621#[serde(rename_all = "camelCase")]
622pub struct WorkspaceCreateParams {
623    #[serde(default, skip_serializing_if = "Option::is_none")]
624    pub name: Option<String>,
625    pub roots: Vec<WorkspaceRootInput>,
626    #[serde(default, skip_serializing_if = "Option::is_none")]
627    pub default_root_path: Option<String>,
628}
629
630#[derive(Debug, Clone, Serialize, Deserialize)]
631#[serde(rename_all = "camelCase")]
632pub struct WorkspaceCreateResult {
633    pub workspace: Workspace,
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize)]
637#[serde(rename_all = "camelCase")]
638pub struct WorkspaceUpdateParams {
639    pub workspace_id: String,
640    #[serde(default, skip_serializing_if = "Option::is_none")]
641    pub name: Option<String>,
642    #[serde(default, skip_serializing_if = "Option::is_none")]
643    pub roots: Option<Vec<WorkspaceRootInput>>,
644    #[serde(default, skip_serializing_if = "Option::is_none")]
645    pub default_root_id: Option<String>,
646}
647
648#[derive(Debug, Clone, Serialize, Deserialize)]
649#[serde(rename_all = "camelCase")]
650pub struct WorkspaceUpdateResult {
651    pub workspace: Workspace,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize)]
655#[serde(rename_all = "camelCase")]
656pub struct WorkspaceForgetParams {
657    pub workspace_id: String,
658}
659
660#[derive(Debug, Clone, Serialize, Deserialize)]
661#[serde(rename_all = "camelCase")]
662pub struct WorkspaceForgetResult {
663    pub workspace_id: String,
664    pub forgotten: bool,
665}
666
667#[derive(Debug, Clone, Serialize, Deserialize)]
668#[serde(rename_all = "camelCase")]
669pub struct ThreadListParams {
670    pub limit: Option<usize>,
671    #[serde(default, skip_serializing_if = "Option::is_none")]
672    pub cursor: Option<String>,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize)]
676#[serde(rename_all = "camelCase")]
677pub struct ThreadListResult {
678    pub data: Vec<Thread>,
679    pub next_cursor: Option<String>,
680    pub backwards_cursor: Option<String>,
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize)]
684#[serde(rename_all = "camelCase")]
685pub struct ThreadReadParams {
686    pub thread_id: ThreadId,
687    #[serde(default)]
688    pub include_turns: bool,
689}
690
691#[derive(Debug, Clone, Serialize, Deserialize)]
692#[serde(rename_all = "camelCase")]
693pub struct ThreadReadResult {
694    pub thread: Option<Thread>,
695}
696
697#[derive(Debug, Clone, Serialize, Deserialize)]
698#[serde(rename_all = "camelCase")]
699pub struct ThreadArchiveParams {
700    pub thread_id: ThreadId,
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize)]
704#[serde(rename_all = "camelCase")]
705pub struct ThreadArchiveResult {
706    pub thread_id: ThreadId,
707    pub archived: bool,
708}
709
710#[derive(Debug, Clone, Serialize, Deserialize)]
711#[serde(rename_all = "camelCase")]
712pub struct ThreadForkParams {
713    /// Parent thread to fork.
714    pub thread_id: ThreadId,
715    /// Fork name; the provider sanitizes it into its naming scheme.
716    pub name: String,
717    /// Fork at a specific parent turn; absent = latest turn.
718    #[serde(default, skip_serializing_if = "Option::is_none")]
719    pub from_turn_id: Option<TurnId>,
720    /// Fork provider id; absent = `git-worktree`.
721    #[serde(default, skip_serializing_if = "Option::is_none")]
722    pub provider: Option<String>,
723    /// Provider-specific options (never secrets).
724    #[serde(default, skip_serializing_if = "Option::is_none")]
725    pub provider_config: Option<serde_json::Value>,
726}
727
728#[derive(Debug, Clone, Serialize, Deserialize)]
729#[serde(rename_all = "camelCase")]
730pub struct ThreadForkResult {
731    /// The new child thread (its `cwd` is the fork workspace).
732    pub thread: Thread,
733    pub fork: WorkspaceFork,
734    #[serde(default, skip_serializing_if = "Vec::is_empty")]
735    pub warnings: Vec<String>,
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
739#[serde(rename_all = "camelCase")]
740pub struct ThreadForkStatusParams {
741    pub thread_id: ThreadId,
742}
743
744#[derive(Debug, Clone, Serialize, Deserialize)]
745#[serde(rename_all = "camelCase")]
746pub struct ThreadForkStatusResult {
747    pub thread_id: ThreadId,
748    #[serde(default, skip_serializing_if = "Option::is_none")]
749    pub parent_thread_id: Option<ThreadId>,
750    #[serde(default, skip_serializing_if = "Option::is_none")]
751    pub forked_from_turn_id: Option<TurnId>,
752    #[serde(default, skip_serializing_if = "Option::is_none")]
753    pub fork: Option<WorkspaceFork>,
754    /// True when the fork is active but its workspace is missing.
755    pub workspace_missing: bool,
756}
757
758#[derive(Debug, Clone, Serialize, Deserialize)]
759#[serde(rename_all = "camelCase")]
760pub struct ThreadRemoveForkParams {
761    pub thread_id: ThreadId,
762    /// Exact fork workspace path; required as destructive confirmation.
763    pub confirm_path: String,
764}
765
766#[derive(Debug, Clone, Serialize, Deserialize)]
767#[serde(rename_all = "camelCase")]
768pub struct ThreadRemoveForkResult {
769    pub thread_id: ThreadId,
770    pub fork: WorkspaceFork,
771}
772
773#[derive(Debug, Clone, Serialize, Deserialize)]
774#[serde(rename_all = "camelCase")]
775pub struct ForksProvidersListResult {
776    pub providers: Vec<roder_api::forks::ForkProviderDescriptor>,
777}
778
779#[derive(Debug, Clone, Serialize, Deserialize)]
780#[serde(rename_all = "camelCase")]
781pub struct ForksListParams {
782    /// Source workspace to list forks of (absolute path).
783    pub source_workspace: String,
784    /// Provider id; absent = `git-worktree`.
785    #[serde(default, skip_serializing_if = "Option::is_none")]
786    pub provider: Option<String>,
787}
788
789#[derive(Debug, Clone, Serialize, Deserialize)]
790#[serde(rename_all = "camelCase")]
791pub struct ForksListResult {
792    pub forks: Vec<WorkspaceFork>,
793}
794
795#[derive(Debug, Clone, Serialize, Deserialize)]
796#[serde(rename_all = "camelCase")]
797pub struct ForksCreateParams {
798    pub source_workspace: String,
799    #[serde(default, skip_serializing_if = "Option::is_none")]
800    pub name: Option<String>,
801    #[serde(default, skip_serializing_if = "Option::is_none")]
802    pub provider: Option<String>,
803    #[serde(default, skip_serializing_if = "Option::is_none")]
804    pub provider_config: Option<serde_json::Value>,
805}
806
807#[derive(Debug, Clone, Serialize, Deserialize)]
808#[serde(rename_all = "camelCase")]
809pub struct ForksCreateResult {
810    pub fork: WorkspaceFork,
811}
812
813#[derive(Debug, Clone, Serialize, Deserialize)]
814#[serde(rename_all = "camelCase")]
815pub struct ForksRemoveParams {
816    pub fork_id: String,
817    #[serde(default, skip_serializing_if = "Option::is_none")]
818    pub provider: Option<String>,
819    /// Exact fork workspace path; required as destructive confirmation.
820    pub confirm_workspace: String,
821}
822
823#[derive(Debug, Clone, Serialize, Deserialize)]
824#[serde(rename_all = "camelCase")]
825pub struct ForksRemoveResult {
826    pub fork_id: String,
827    pub removed: bool,
828}
829
830#[derive(Debug, Clone, Serialize, Deserialize)]
831#[serde(rename_all = "camelCase")]
832pub struct ThreadGoalGetParams {
833    pub thread_id: ThreadId,
834}
835
836#[derive(Debug, Clone, Serialize, Deserialize)]
837#[serde(rename_all = "camelCase")]
838pub struct ThreadGoalGetResult {
839    pub goal: Option<ThreadGoal>,
840}
841
842#[derive(Debug, Clone, Serialize, Deserialize)]
843#[serde(rename_all = "camelCase")]
844pub struct ThreadGoalSetParams {
845    pub thread_id: ThreadId,
846    #[serde(default, skip_serializing_if = "Option::is_none")]
847    pub objective: Option<String>,
848    #[serde(default, skip_serializing_if = "Option::is_none")]
849    pub status: Option<ThreadGoalStatus>,
850    #[serde(
851        default,
852        skip_serializing_if = "Option::is_none",
853        deserialize_with = "deserialize_goal_token_budget_patch"
854    )]
855    pub token_budget: Option<Option<i64>>,
856}
857
858fn deserialize_goal_token_budget_patch<'de, D>(
859    deserializer: D,
860) -> Result<Option<Option<i64>>, D::Error>
861where
862    D: serde::Deserializer<'de>,
863{
864    let value = serde_json::Value::deserialize(deserializer)?;
865    match value {
866        serde_json::Value::Null => Ok(Some(None)),
867        value => i64::deserialize(value)
868            .map(Some)
869            .map(Some)
870            .map_err(serde::de::Error::custom),
871    }
872}
873
874#[derive(Debug, Clone, Serialize, Deserialize)]
875#[serde(rename_all = "camelCase")]
876pub struct ThreadGoalSetResult {
877    pub goal: Option<ThreadGoal>,
878}
879
880#[derive(Debug, Clone, Serialize, Deserialize)]
881#[serde(rename_all = "camelCase")]
882pub struct ThreadGoalClearParams {
883    pub thread_id: ThreadId,
884}
885
886#[derive(Debug, Clone, Serialize, Deserialize)]
887#[serde(rename_all = "camelCase")]
888pub struct ThreadGoalClearResult {
889    pub cleared: bool,
890}
891
892#[derive(Debug, Clone, Serialize, Deserialize)]
893#[serde(rename_all = "camelCase")]
894pub struct TurnInputItem {
895    #[serde(rename = "type")]
896    pub kind: String,
897    pub text: Option<String>,
898    pub path: Option<String>,
899    #[serde(default, alias = "image_url", skip_serializing_if = "Option::is_none")]
900    pub image_url: Option<String>,
901}
902
903#[derive(Debug, Clone, Serialize, Deserialize)]
904#[serde(rename_all = "camelCase")]
905pub struct TurnStartParams {
906    pub thread_id: ThreadId,
907    #[serde(default)]
908    pub input: Vec<TurnInputItem>,
909    pub prompt: Option<String>,
910    #[serde(default, skip_serializing_if = "Option::is_none")]
911    pub model_provider: Option<String>,
912    #[serde(default, skip_serializing_if = "Option::is_none")]
913    pub model: Option<String>,
914    #[serde(default, skip_serializing_if = "Option::is_none")]
915    pub reasoning: Option<String>,
916    /**
917     * Per-turn developer-authority context layered after the thread's
918     * developerInstructions for this turn only. Never persisted to thread
919     * metadata; absent means no per-turn context.
920     */
921    #[serde(default, skip_serializing_if = "Option::is_none")]
922    pub developer_context: Option<String>,
923    #[serde(default, skip_serializing_if = "Option::is_none")]
924    pub policy_mode: Option<PolicyMode>,
925    #[serde(default)]
926    pub task_ledger_required: bool,
927}
928
929#[derive(Debug, Clone, Serialize, Deserialize)]
930#[serde(rename_all = "camelCase")]
931pub struct TurnStartResult {
932    pub turn_id: TurnId,
933}
934
935#[derive(Debug, Clone, Serialize, Deserialize)]
936#[serde(rename_all = "camelCase")]
937pub struct TurnSteerParams {
938    pub thread_id: ThreadId,
939    pub expected_turn_id: TurnId,
940    #[serde(default)]
941    pub input: Vec<TurnInputItem>,
942    pub prompt: Option<String>,
943}
944
945#[derive(Debug, Clone, Serialize, Deserialize)]
946#[serde(rename_all = "camelCase")]
947pub struct TurnSteerResult {
948    pub turn_id: TurnId,
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize)]
952#[serde(rename_all = "camelCase")]
953pub struct TurnInterruptParams {
954    pub thread_id: ThreadId,
955    pub turn_id: Option<TurnId>,
956}
957
958#[derive(Debug, Clone, Serialize, Deserialize)]
959#[serde(rename_all = "camelCase")]
960pub struct TurnInterruptResult {
961    pub turn_id: Option<TurnId>,
962}
963
964#[derive(Debug, Clone, Serialize, Deserialize)]
965#[serde(rename_all = "camelCase")]
966pub struct ModelListResult {
967    pub models: Vec<Model>,
968}
969
970#[derive(Debug, Clone, Serialize, Deserialize)]
971#[serde(rename_all = "camelCase")]
972pub struct Model {
973    pub id: String,
974    pub name: String,
975    pub model_provider: String,
976    pub default_reasoning_effort: Option<String>,
977    pub reasoning_efforts: Vec<String>,
978    pub is_default: bool,
979}
980
981#[derive(Debug, Clone, Serialize, Deserialize)]
982#[serde(rename_all = "camelCase")]
983pub struct MemoryListParams {
984    pub scope: Option<MemoryScope>,
985    pub limit: Option<usize>,
986}
987
988#[derive(Debug, Clone, Serialize, Deserialize)]
989#[serde(rename_all = "camelCase")]
990pub struct MemoryListResult {
991    pub memories: Vec<MemoryRecord>,
992}
993
994#[derive(Debug, Clone, Serialize, Deserialize)]
995#[serde(rename_all = "camelCase")]
996pub struct MemoryReadParams {
997    pub memory_id: MemoryId,
998}
999
1000#[derive(Debug, Clone, Serialize, Deserialize)]
1001#[serde(rename_all = "camelCase")]
1002pub struct MemoryReadResult {
1003    pub memory: Option<MemoryRecord>,
1004}
1005
1006#[derive(Debug, Clone, Serialize, Deserialize)]
1007#[serde(rename_all = "camelCase")]
1008pub struct MemorySaveParams {
1009    pub scope: MemoryScope,
1010    pub text: String,
1011    #[serde(default)]
1012    pub metadata: serde_json::Value,
1013}
1014
1015#[derive(Debug, Clone, Serialize, Deserialize)]
1016#[serde(rename_all = "camelCase")]
1017pub struct MemorySaveResult {
1018    pub memory_id: MemoryId,
1019}
1020
1021#[derive(Debug, Clone, Serialize, Deserialize)]
1022#[serde(rename_all = "camelCase")]
1023pub struct MemoryUpdateParams {
1024    pub memory_id: MemoryId,
1025    pub text: String,
1026    #[serde(default)]
1027    pub metadata: serde_json::Value,
1028}
1029
1030#[derive(Debug, Clone, Serialize, Deserialize)]
1031#[serde(rename_all = "camelCase")]
1032pub struct MemoryDeleteParams {
1033    pub memory_id: MemoryId,
1034}
1035
1036#[derive(Debug, Clone, Serialize, Deserialize)]
1037#[serde(rename_all = "camelCase")]
1038pub struct MemoryDeleteResult {
1039    pub deleted: bool,
1040}
1041
1042#[derive(Debug, Clone, Serialize, Deserialize)]
1043#[serde(rename_all = "camelCase")]
1044pub struct MemoryQueryParams {
1045    pub scope: Option<MemoryScope>,
1046    pub text: String,
1047    pub limit: Option<usize>,
1048    #[serde(default)]
1049    pub include_global: bool,
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize)]
1053#[serde(rename_all = "camelCase")]
1054pub struct MemoryQueryResult {
1055    pub results: Vec<MemorySearchResult>,
1056}
1057
1058#[derive(Debug, Clone, Serialize, Deserialize)]
1059#[serde(rename_all = "camelCase")]
1060pub struct MemoryProviderListResult {
1061    pub providers: Vec<roder_api::embeddings::EmbeddingProviderDescriptor>,
1062    pub selected: MemoryProviderSelection,
1063}
1064
1065#[derive(Debug, Clone, Serialize, Deserialize)]
1066#[serde(rename_all = "camelCase")]
1067pub struct MemoryProviderSetParams {
1068    pub provider_id: String,
1069    pub model: String,
1070}
1071
1072#[derive(Debug, Clone, Serialize, Deserialize)]
1073#[serde(rename_all = "camelCase")]
1074pub struct MemoryRecallPreviewParams {
1075    pub thread_id: ThreadId,
1076    pub turn_id: TurnId,
1077    pub scope: Option<MemoryScope>,
1078    pub text: String,
1079    pub limit: Option<usize>,
1080    #[serde(default)]
1081    pub include_global: bool,
1082}
1083
1084#[derive(Debug, Clone, Serialize, Deserialize)]
1085#[serde(rename_all = "camelCase")]
1086pub struct MemoryRecallPreviewResult {
1087    pub citations: Vec<roder_api::memory::MemoryCitation>,
1088    pub results: Vec<MemorySearchResult>,
1089}
1090
1091#[derive(Debug, Clone, Serialize, Deserialize)]
1092#[serde(rename_all = "camelCase")]
1093pub struct KnowledgeListParams {
1094    pub scope: Option<MemoryScope>,
1095    #[serde(default)]
1096    pub kind: Option<KnowledgeKind>,
1097    #[serde(default)]
1098    pub tag: Option<String>,
1099    #[serde(default)]
1100    pub status: Option<KnowledgeStatus>,
1101    #[serde(default)]
1102    pub include_archived: bool,
1103    pub limit: Option<usize>,
1104}
1105
1106#[derive(Debug, Clone, Serialize, Deserialize)]
1107#[serde(rename_all = "camelCase")]
1108pub struct KnowledgeListResult {
1109    pub documents: Vec<KnowledgeDocSummary>,
1110}
1111
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113#[serde(rename_all = "camelCase")]
1114pub struct KnowledgeReadParams {
1115    pub doc_id: KnowledgeDocId,
1116    #[serde(default)]
1117    pub revision: Option<u32>,
1118}
1119
1120#[derive(Debug, Clone, Serialize, Deserialize)]
1121#[serde(rename_all = "camelCase")]
1122pub struct KnowledgeReadResult {
1123    pub document: Option<KnowledgeDocument>,
1124}
1125
1126#[derive(Debug, Clone, Serialize, Deserialize)]
1127#[serde(rename_all = "camelCase")]
1128pub struct KnowledgeSaveParams {
1129    pub scope: MemoryScope,
1130    pub kind: KnowledgeKind,
1131    pub title: String,
1132    #[serde(default)]
1133    pub tags: Vec<String>,
1134    pub body: String,
1135}
1136
1137#[derive(Debug, Clone, Serialize, Deserialize)]
1138#[serde(rename_all = "camelCase")]
1139pub struct KnowledgeSaveResult {
1140    pub document: KnowledgeDocument,
1141}
1142
1143#[derive(Debug, Clone, Serialize, Deserialize)]
1144#[serde(rename_all = "camelCase")]
1145pub struct KnowledgeUpdateParams {
1146    pub doc_id: KnowledgeDocId,
1147    #[serde(default)]
1148    pub title: Option<String>,
1149    #[serde(default)]
1150    pub body: Option<String>,
1151    #[serde(default)]
1152    pub status: Option<KnowledgeStatus>,
1153    #[serde(default)]
1154    pub tags: Option<Vec<String>>,
1155}
1156
1157#[derive(Debug, Clone, Serialize, Deserialize)]
1158#[serde(rename_all = "camelCase")]
1159pub struct KnowledgeDeleteParams {
1160    pub doc_id: KnowledgeDocId,
1161}
1162
1163#[derive(Debug, Clone, Serialize, Deserialize)]
1164#[serde(rename_all = "camelCase")]
1165pub struct KnowledgeDeleteResult {
1166    pub archived: bool,
1167}
1168
1169#[derive(Debug, Clone, Serialize, Deserialize)]
1170#[serde(rename_all = "camelCase")]
1171pub struct KnowledgeSearchParams {
1172    pub scope: Option<MemoryScope>,
1173    pub text: String,
1174    #[serde(default)]
1175    pub kind: Option<KnowledgeKind>,
1176    pub limit: Option<usize>,
1177    #[serde(default)]
1178    pub include_global: bool,
1179}
1180
1181#[derive(Debug, Clone, Serialize, Deserialize)]
1182#[serde(rename_all = "camelCase")]
1183pub struct KnowledgeSearchResults {
1184    pub results: Vec<KnowledgeSearchMatch>,
1185}
1186
1187#[derive(Debug, Clone, Serialize, Deserialize)]
1188#[serde(rename_all = "camelCase")]
1189pub struct KnowledgeLinkSetParams {
1190    pub from: KnowledgeDocId,
1191    pub to: KnowledgeDocId,
1192    #[serde(rename = "type")]
1193    pub link_type: KnowledgeLinkType,
1194    #[serde(default)]
1195    pub remove: bool,
1196}
1197
1198#[derive(Debug, Clone, Serialize, Deserialize)]
1199#[serde(rename_all = "camelCase")]
1200pub struct KnowledgeRevisionsParams {
1201    pub doc_id: KnowledgeDocId,
1202}
1203
1204#[derive(Debug, Clone, Serialize, Deserialize)]
1205#[serde(rename_all = "camelCase")]
1206pub struct KnowledgeRevisionsResult {
1207    pub revisions: Vec<KnowledgeRevisionInfo>,
1208}
1209
1210#[derive(Debug, Clone, Serialize, Deserialize)]
1211#[serde(rename_all = "camelCase")]
1212pub struct ThreadStartedNotification {
1213    pub thread: Thread,
1214}
1215
1216#[derive(Debug, Clone, Serialize, Deserialize)]
1217#[serde(rename_all = "camelCase")]
1218pub struct ThreadStatusChangedNotification {
1219    pub thread_id: ThreadId,
1220    pub status: ThreadStatus,
1221}
1222
1223#[derive(Debug, Clone, Serialize, Deserialize)]
1224#[serde(rename_all = "camelCase")]
1225pub struct ThreadGoalUpdatedNotification {
1226    pub thread_id: ThreadId,
1227    pub goal: ThreadGoal,
1228}
1229
1230#[derive(Debug, Clone, Serialize, Deserialize)]
1231#[serde(rename_all = "camelCase")]
1232pub struct ThreadGoalClearedNotification {
1233    pub thread_id: ThreadId,
1234}
1235
1236#[derive(Debug, Clone, Serialize, Deserialize)]
1237#[serde(rename_all = "camelCase")]
1238pub struct TurnStartedNotification {
1239    pub thread_id: ThreadId,
1240    pub turn: Turn,
1241}
1242
1243#[derive(Debug, Clone, Serialize, Deserialize)]
1244#[serde(rename_all = "camelCase")]
1245pub struct TurnCompletedNotification {
1246    pub thread_id: ThreadId,
1247    pub turn: Turn,
1248}
1249
1250#[derive(Debug, Clone, Serialize, Deserialize)]
1251#[serde(rename_all = "camelCase")]
1252pub struct TurnDeadlineExceededNotification {
1253    pub thread_id: ThreadId,
1254    pub turn_id: TurnId,
1255    pub deadline: time::OffsetDateTime,
1256    pub partial_result: String,
1257}
1258
1259#[derive(Debug, Clone, Serialize, Deserialize)]
1260#[serde(rename_all = "camelCase")]
1261pub struct TurnPartialResultNotification {
1262    pub thread_id: ThreadId,
1263    pub turn_id: TurnId,
1264    pub summary: String,
1265}
1266
1267#[derive(Debug, Clone, Serialize, Deserialize)]
1268#[serde(rename_all = "camelCase")]
1269pub struct ApprovalRequestedNotification {
1270    pub thread_id: ThreadId,
1271    pub turn_id: TurnId,
1272    pub approval_id: String,
1273    pub tool_id: String,
1274    pub tool_name: String,
1275    #[serde(default, skip_serializing_if = "Option::is_none")]
1276    pub reason: Option<String>,
1277}
1278
1279#[derive(Debug, Clone, Serialize, Deserialize)]
1280#[serde(rename_all = "camelCase")]
1281pub struct ApprovalResolvedNotification {
1282    pub thread_id: ThreadId,
1283    pub turn_id: TurnId,
1284    pub approval_id: String,
1285    pub tool_id: String,
1286    pub tool_name: String,
1287    pub approved: bool,
1288}
1289
1290/// Model-issued call to a host-executed external tool, embedded in `thread/toolExecutionRequested`.
1291#[derive(Debug, Clone, Serialize, Deserialize)]
1292#[serde(rename_all = "camelCase")]
1293pub struct ExternalToolCall {
1294    pub id: String,
1295    pub name: String,
1296    pub arguments: serde_json::Value,
1297}
1298
1299#[derive(Debug, Clone, Serialize, Deserialize)]
1300#[serde(rename_all = "camelCase")]
1301pub struct ToolExecutionRequestedNotification {
1302    pub thread_id: ThreadId,
1303    pub turn_id: TurnId,
1304    pub request_id: String,
1305    pub call: ExternalToolCall,
1306}
1307
1308#[derive(Debug, Clone, Serialize, Deserialize)]
1309#[serde(rename_all = "camelCase")]
1310pub struct ToolExecutionResolvedNotification {
1311    pub thread_id: ThreadId,
1312    pub turn_id: TurnId,
1313    pub request_id: String,
1314    pub tool_id: String,
1315    pub tool_name: String,
1316    /// "resolved", "timedOut", or "cancelled".
1317    pub outcome: roder_api::events::ExternalToolCallOutcome,
1318    pub is_error: bool,
1319}
1320
1321#[derive(Debug, Clone, Serialize, Deserialize)]
1322#[serde(rename_all = "camelCase")]
1323pub struct UserInputRequestedNotification {
1324    pub thread_id: ThreadId,
1325    pub turn_id: TurnId,
1326    pub request_id: String,
1327    pub questions: serde_json::Value,
1328}
1329
1330#[derive(Debug, Clone, Serialize, Deserialize)]
1331#[serde(rename_all = "camelCase")]
1332pub struct UserInputResolvedNotification {
1333    pub thread_id: ThreadId,
1334    pub turn_id: TurnId,
1335    pub request_id: String,
1336    pub answers: serde_json::Value,
1337}
1338
1339#[derive(Debug, Clone, Serialize, Deserialize)]
1340#[serde(rename_all = "camelCase")]
1341pub struct VerificationRequiredNotification {
1342    pub thread_id: ThreadId,
1343    pub turn_id: TurnId,
1344    pub reason: String,
1345    pub changed_files: Vec<String>,
1346    pub tool_evidence: Vec<String>,
1347    pub tests_run: Vec<String>,
1348    pub open_gaps: Vec<String>,
1349}
1350
1351#[derive(Debug, Clone, Serialize, Deserialize)]
1352#[serde(rename_all = "camelCase")]
1353pub struct VerificationCompletedNotification {
1354    pub thread_id: ThreadId,
1355    pub turn_id: TurnId,
1356    pub passed: bool,
1357    pub changed_files: Vec<String>,
1358    pub tool_evidence: Vec<String>,
1359    pub tests_run: Vec<String>,
1360    pub open_gaps: Vec<String>,
1361}
1362
1363#[derive(Debug, Clone, Serialize, Deserialize)]
1364#[serde(rename_all = "camelCase")]
1365pub struct VerificationSkippedNotification {
1366    pub thread_id: ThreadId,
1367    pub turn_id: TurnId,
1368    pub reason: String,
1369}
1370
1371#[derive(Debug, Clone, Serialize, Deserialize)]
1372#[serde(rename_all = "camelCase")]
1373pub struct AutomationRunNotification {
1374    pub run: AutomationRunSummary,
1375}
1376
1377#[derive(Debug, Clone, Serialize, Deserialize)]
1378#[serde(rename_all = "camelCase")]
1379pub struct AutomationRunFailedNotification {
1380    pub run: AutomationRunSummary,
1381    pub error: String,
1382}
1383
1384#[derive(Debug, Clone, Serialize, Deserialize)]
1385#[serde(rename_all = "camelCase")]
1386pub struct AutomationRunSkippedNotification {
1387    pub run: AutomationRunSummary,
1388    pub reason: String,
1389}
1390
1391#[derive(Debug, Clone, Serialize, Deserialize)]
1392#[serde(rename_all = "camelCase")]
1393pub struct PlanExitRequestedNotification {
1394    pub thread_id: ThreadId,
1395    pub turn_id: TurnId,
1396    pub request_id: String,
1397    pub target_mode: roder_api::policy_mode::PolicyMode,
1398    #[serde(default, skip_serializing_if = "Option::is_none")]
1399    pub plan_summary: Option<String>,
1400    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1401    pub next_steps: Vec<String>,
1402}
1403
1404#[derive(Debug, Clone, Serialize, Deserialize)]
1405#[serde(rename_all = "camelCase")]
1406pub struct PlanExitResolvedNotification {
1407    pub thread_id: ThreadId,
1408    pub turn_id: TurnId,
1409    pub request_id: String,
1410    pub approved: bool,
1411    pub target_mode: roder_api::policy_mode::PolicyMode,
1412    pub resolved_mode: roder_api::policy_mode::PolicyMode,
1413}
1414
1415#[derive(Debug, Clone, Serialize, Deserialize)]
1416#[serde(rename_all = "camelCase")]
1417pub struct FsReadFileParams {
1418    pub path: String,
1419}
1420
1421#[derive(Debug, Clone, Serialize, Deserialize)]
1422#[serde(rename_all = "camelCase")]
1423pub struct FsReadFileResponse {
1424    pub data_base64: String,
1425}
1426
1427#[derive(Debug, Clone, Serialize, Deserialize)]
1428#[serde(rename_all = "camelCase")]
1429pub struct FsReadDirectoryParams {
1430    pub path: String,
1431}
1432
1433#[derive(Debug, Clone, Serialize, Deserialize)]
1434#[serde(rename_all = "camelCase")]
1435pub struct FsReadDirectoryEntry {
1436    pub file_name: String,
1437    pub is_directory: bool,
1438    pub is_file: bool,
1439}
1440
1441#[derive(Debug, Clone, Serialize, Deserialize)]
1442#[serde(rename_all = "camelCase")]
1443pub struct FsReadDirectoryResponse {
1444    pub entries: Vec<FsReadDirectoryEntry>,
1445}
1446
1447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1448#[serde(rename_all = "camelCase")]
1449pub struct WorkspaceFilesStatusParams {
1450    pub workspace_id: String,
1451    #[serde(default, skip_serializing_if = "Option::is_none")]
1452    pub root_id: Option<String>,
1453}
1454
1455#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1456#[serde(rename_all = "camelCase")]
1457pub enum WorkspaceFilesIndexState {
1458    Missing,
1459    Building,
1460    Ready,
1461    /// Reserved: the index is built but known to be out of date. The server
1462    /// does not emit this yet (watcher-backed staleness is future work); clients
1463    /// should treat it like `Ready` and may offer a manual rebuild.
1464    Stale,
1465    Failed,
1466}
1467
1468#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1469#[serde(rename_all = "camelCase")]
1470pub struct WorkspaceFilesRootStatus {
1471    pub root_id: String,
1472    pub root_name: String,
1473    pub state: WorkspaceFilesIndexState,
1474    pub stale: bool,
1475    #[serde(default, skip_serializing_if = "Option::is_none")]
1476    pub file_count: Option<u64>,
1477    #[serde(default, skip_serializing_if = "Option::is_none")]
1478    pub directory_count: Option<u64>,
1479    #[serde(default, skip_serializing_if = "Option::is_none")]
1480    pub build_time_ms: Option<u64>,
1481    #[serde(default, skip_serializing_if = "Option::is_none")]
1482    pub indexed_at_ms: Option<i64>,
1483    #[serde(default, skip_serializing_if = "Option::is_none")]
1484    pub message: Option<String>,
1485}
1486
1487#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1488#[serde(rename_all = "camelCase")]
1489pub struct WorkspaceFilesStatus {
1490    pub workspace_id: String,
1491    pub state: WorkspaceFilesIndexState,
1492    pub stale: bool,
1493    pub roots: Vec<WorkspaceFilesRootStatus>,
1494    pub file_count: u64,
1495    pub directory_count: u64,
1496    #[serde(default, skip_serializing_if = "Option::is_none")]
1497    pub message: Option<String>,
1498}
1499
1500#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1501#[serde(rename_all = "camelCase")]
1502pub struct WorkspaceFilesStatusResult {
1503    pub status: WorkspaceFilesStatus,
1504}
1505
1506#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1507#[serde(rename_all = "camelCase")]
1508pub struct WorkspaceFilesRebuildParams {
1509    pub workspace_id: String,
1510    #[serde(default, skip_serializing_if = "Option::is_none")]
1511    pub root_id: Option<String>,
1512}
1513
1514#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1515#[serde(rename_all = "camelCase")]
1516pub struct WorkspaceFilesRebuildResult {
1517    pub status: WorkspaceFilesStatus,
1518}
1519
1520#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1521#[serde(rename_all = "camelCase")]
1522pub struct WorkspaceFilesChildrenParams {
1523    pub workspace_id: String,
1524    #[serde(default, skip_serializing_if = "Option::is_none")]
1525    pub root_id: Option<String>,
1526    #[serde(default, skip_serializing_if = "Option::is_none")]
1527    pub path: Option<String>,
1528}
1529
1530#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1531#[serde(rename_all = "camelCase")]
1532pub enum WorkspaceFileKind {
1533    Directory,
1534    File,
1535}
1536
1537#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1538#[serde(rename_all = "camelCase")]
1539pub struct WorkspaceFileEntry {
1540    pub root_id: String,
1541    pub root_name: String,
1542    pub path: String,
1543    pub name: String,
1544    pub kind: WorkspaceFileKind,
1545    pub has_children: bool,
1546    #[serde(default, skip_serializing_if = "Option::is_none")]
1547    pub size: Option<u64>,
1548    #[serde(default, skip_serializing_if = "Option::is_none")]
1549    pub modified_ms: Option<u64>,
1550}
1551
1552#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1553#[serde(rename_all = "camelCase")]
1554pub struct WorkspaceFilesChildrenResult {
1555    pub status: WorkspaceFilesStatus,
1556    pub entries: Vec<WorkspaceFileEntry>,
1557}
1558
1559#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1560#[serde(rename_all = "camelCase")]
1561pub struct WorkspaceFilesQueryParams {
1562    pub workspace_id: String,
1563    #[serde(default, skip_serializing_if = "Option::is_none")]
1564    pub root_id: Option<String>,
1565    pub query: String,
1566    #[serde(default, skip_serializing_if = "Option::is_none")]
1567    pub limit: Option<usize>,
1568}
1569
1570#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1571#[serde(rename_all = "camelCase")]
1572pub struct WorkspaceFileQueryMatch {
1573    pub entry: WorkspaceFileEntry,
1574    pub score: i64,
1575    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1576    pub match_positions: Vec<usize>,
1577}
1578
1579#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1580#[serde(rename_all = "camelCase")]
1581pub struct WorkspaceFilesQueryResult {
1582    pub status: WorkspaceFilesStatus,
1583    pub matches: Vec<WorkspaceFileQueryMatch>,
1584    pub indexed_file_count: u64,
1585}
1586
1587#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1588#[serde(rename_all = "camelCase")]
1589pub struct WorkspaceFilesReadParams {
1590    pub workspace_id: String,
1591    pub root_id: String,
1592    pub path: String,
1593    #[serde(default, skip_serializing_if = "Option::is_none")]
1594    pub offset: Option<usize>,
1595    #[serde(default, skip_serializing_if = "Option::is_none")]
1596    pub limit: Option<usize>,
1597}
1598
1599#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1600#[serde(rename_all = "camelCase")]
1601pub enum WorkspaceFilesReadEncoding {
1602    Utf8,
1603    Binary,
1604    Unsupported,
1605}
1606
1607#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1608#[serde(rename_all = "camelCase")]
1609pub struct WorkspaceFilesReadResult {
1610    pub entry: WorkspaceFileEntry,
1611    pub encoding: WorkspaceFilesReadEncoding,
1612    #[serde(default, skip_serializing_if = "Option::is_none")]
1613    pub text: Option<String>,
1614    pub offset: usize,
1615    pub limit: usize,
1616    pub total_bytes: u64,
1617    pub has_more: bool,
1618    pub truncated: bool,
1619}
1620
1621#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1622#[serde(rename_all = "camelCase")]
1623pub struct WorkspaceFilesStatusNotification {
1624    pub status: WorkspaceFilesStatus,
1625}
1626
1627#[derive(Debug, Clone, Serialize, Deserialize)]
1628#[serde(rename_all = "camelCase")]
1629pub struct DesignWorkspaceParams {
1630    pub workspace_id: String,
1631    #[serde(default, skip_serializing_if = "Option::is_none")]
1632    pub root_id: Option<String>,
1633}
1634
1635pub type DesignGetVariablesParams = DesignWorkspaceParams;
1636pub type DesignSnapshotLayoutParams = DesignWorkspaceParams;
1637pub type DesignGetGuidelinesParams = DesignWorkspaceParams;
1638
1639#[derive(Debug, Clone, Serialize, Deserialize)]
1640#[serde(rename_all = "camelCase")]
1641pub struct DesignSetSelectionParams {
1642    pub workspace_id: String,
1643    #[serde(default, skip_serializing_if = "Option::is_none")]
1644    pub root_id: Option<String>,
1645    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1646    pub selected_node_ids: Vec<String>,
1647}
1648
1649#[derive(Debug, Clone, Serialize, Deserialize)]
1650#[serde(rename_all = "camelCase")]
1651pub struct DesignSetVariablesParams {
1652    pub workspace_id: String,
1653    #[serde(default, skip_serializing_if = "Option::is_none")]
1654    pub root_id: Option<String>,
1655    pub variables: BTreeMap<String, serde_json::Value>,
1656    #[serde(default)]
1657    pub replace: bool,
1658}
1659
1660#[derive(Debug, Clone, Serialize, Deserialize)]
1661#[serde(rename_all = "camelCase")]
1662pub struct DesignSpawnAgentsParams {
1663    pub workspace_id: String,
1664    #[serde(default, skip_serializing_if = "Option::is_none")]
1665    pub root_id: Option<String>,
1666    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1667    pub scope_node_ids: Vec<String>,
1668    #[serde(default, skip_serializing_if = "Option::is_none")]
1669    pub prompt: Option<String>,
1670    #[serde(default)]
1671    pub allow_patch: bool,
1672    #[serde(default)]
1673    pub allow_export: bool,
1674    #[serde(default)]
1675    pub require_review: bool,
1676}
1677
1678#[derive(Debug, Clone, Serialize, Deserialize)]
1679#[serde(rename_all = "camelCase")]
1680pub struct DesignGetEditorStateParams {
1681    pub workspace_id: String,
1682    #[serde(default, skip_serializing_if = "Option::is_none")]
1683    pub root_id: Option<String>,
1684    #[serde(default)]
1685    pub include_schema: bool,
1686}
1687
1688#[derive(Debug, Clone, Serialize, Deserialize)]
1689#[serde(rename_all = "camelCase")]
1690pub struct RoderDesignDocument {
1691    pub version: String,
1692    pub document_id: String,
1693    pub title: String,
1694    pub created_at: String,
1695    pub updated_at: String,
1696    #[serde(default)]
1697    pub nodes: BTreeMap<String, RoderDesignNode>,
1698    #[serde(default)]
1699    pub root_ids: Vec<String>,
1700    #[serde(default)]
1701    pub variables: BTreeMap<String, serde_json::Value>,
1702    #[serde(default)]
1703    pub assets: BTreeMap<String, serde_json::Value>,
1704    pub metadata: RoderDesignMetadata,
1705}
1706
1707#[derive(Debug, Clone, Serialize, Deserialize)]
1708#[serde(rename_all = "camelCase")]
1709pub struct RoderDesignMetadata {
1710    #[serde(default, skip_serializing_if = "Option::is_none")]
1711    pub workspace_id: Option<String>,
1712    #[serde(default, skip_serializing_if = "Option::is_none")]
1713    pub root_id: Option<String>,
1714    #[serde(default, skip_serializing_if = "Option::is_none")]
1715    pub workspace_root: Option<String>,
1716    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1717    pub selected_node_ids: Vec<String>,
1718}
1719
1720#[derive(Debug, Clone, Serialize, Deserialize)]
1721#[serde(rename_all = "camelCase")]
1722pub struct RoderDesignNode {
1723    pub id: String,
1724    #[serde(rename = "type")]
1725    pub node_type: String,
1726    pub name: String,
1727    #[serde(default, skip_serializing_if = "Option::is_none")]
1728    pub parent_id: Option<String>,
1729    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1730    pub child_ids: Vec<String>,
1731    #[serde(default)]
1732    pub x: f64,
1733    #[serde(default)]
1734    pub y: f64,
1735    #[serde(default)]
1736    pub width: f64,
1737    #[serde(default)]
1738    pub height: f64,
1739    #[serde(default, skip_serializing_if = "Option::is_none")]
1740    pub rotation: Option<f64>,
1741    #[serde(default, skip_serializing_if = "Option::is_none")]
1742    pub opacity: Option<f64>,
1743    #[serde(default, skip_serializing_if = "Option::is_none")]
1744    pub visible: Option<bool>,
1745    #[serde(default, skip_serializing_if = "Option::is_none")]
1746    pub locked: Option<bool>,
1747    #[serde(default, skip_serializing_if = "Option::is_none")]
1748    pub fill: Option<serde_json::Value>,
1749    #[serde(default, skip_serializing_if = "Option::is_none")]
1750    pub stroke: Option<serde_json::Value>,
1751    #[serde(default, skip_serializing_if = "BTreeMap::is_empty", flatten)]
1752    pub extra: BTreeMap<String, serde_json::Value>,
1753}
1754
1755#[derive(Debug, Clone, Serialize, Deserialize)]
1756#[serde(rename_all = "camelCase")]
1757pub struct DesignDocumentResult {
1758    pub path: String,
1759    pub document: RoderDesignDocument,
1760}
1761
1762#[derive(Debug, Clone, Serialize, Deserialize)]
1763#[serde(rename_all = "camelCase")]
1764pub struct DesignNodeAlias {
1765    pub alias: String,
1766    pub node_id: String,
1767    pub name: String,
1768    #[serde(rename = "type")]
1769    pub node_type: String,
1770}
1771
1772#[derive(Debug, Clone, Serialize, Deserialize)]
1773#[serde(rename_all = "camelCase")]
1774pub struct DesignEditorStateResult {
1775    pub path: String,
1776    pub document: RoderDesignDocument,
1777    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1778    pub selected_node_ids: Vec<String>,
1779    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1780    pub node_aliases: Vec<DesignNodeAlias>,
1781    #[serde(default, skip_serializing_if = "Option::is_none")]
1782    pub schema: Option<serde_json::Value>,
1783    #[serde(default, skip_serializing_if = "Option::is_none")]
1784    pub rules: Option<String>,
1785}
1786
1787#[derive(Debug, Clone, Serialize, Deserialize)]
1788#[serde(rename_all = "camelCase")]
1789pub struct DesignBatchGetParams {
1790    pub workspace_id: String,
1791    #[serde(default, skip_serializing_if = "Option::is_none")]
1792    pub root_id: Option<String>,
1793    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1794    pub node_ids: Vec<String>,
1795    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1796    pub patterns: Vec<DesignNodeSearchPattern>,
1797    #[serde(default, skip_serializing_if = "Option::is_none")]
1798    pub parent_id: Option<String>,
1799    #[serde(default, skip_serializing_if = "Option::is_none")]
1800    pub read_depth: Option<u32>,
1801    #[serde(default, skip_serializing_if = "Option::is_none")]
1802    pub search_depth: Option<u32>,
1803}
1804
1805#[derive(Debug, Clone, Serialize, Deserialize)]
1806#[serde(rename_all = "camelCase")]
1807pub struct DesignNodeSearchPattern {
1808    #[serde(default, skip_serializing_if = "Option::is_none")]
1809    pub name: Option<String>,
1810    #[serde(default, skip_serializing_if = "Option::is_none")]
1811    #[serde(rename = "type")]
1812    pub node_type: Option<String>,
1813}
1814
1815#[derive(Debug, Clone, Serialize, Deserialize)]
1816#[serde(rename_all = "camelCase")]
1817pub struct DesignBatchGetResult {
1818    pub path: String,
1819    pub nodes: Vec<RoderDesignNode>,
1820    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1821    pub node_aliases: Vec<DesignNodeAlias>,
1822}
1823
1824#[derive(Debug, Clone, Serialize, Deserialize)]
1825#[serde(rename_all = "camelCase")]
1826pub struct DesignPatchParams {
1827    pub workspace_id: String,
1828    #[serde(default, skip_serializing_if = "Option::is_none")]
1829    pub root_id: Option<String>,
1830    pub operations: Vec<DesignPatchOperation>,
1831}
1832
1833#[derive(Debug, Clone, Serialize, Deserialize)]
1834#[serde(rename_all = "snake_case", tag = "op")]
1835pub enum DesignPatchOperation {
1836    InsertNode {
1837        #[serde(default, alias = "parentId", skip_serializing_if = "Option::is_none")]
1838        parent_id: Option<String>,
1839        #[serde(default, skip_serializing_if = "Option::is_none")]
1840        index: Option<usize>,
1841        node: RoderDesignNode,
1842    },
1843    UpdateNode {
1844        #[serde(alias = "nodeId")]
1845        node_id: String,
1846        patch: serde_json::Value,
1847    },
1848    DeleteNode {
1849        #[serde(alias = "nodeId")]
1850        node_id: String,
1851        #[serde(default)]
1852        recursive: bool,
1853    },
1854    ReorderNode {
1855        #[serde(alias = "nodeId")]
1856        node_id: String,
1857        index: usize,
1858    },
1859    SetVariables {
1860        variables: BTreeMap<String, serde_json::Value>,
1861        #[serde(default)]
1862        replace: bool,
1863    },
1864}
1865
1866#[derive(Debug, Clone, Serialize, Deserialize)]
1867#[serde(rename_all = "camelCase")]
1868pub struct DesignPatchResult {
1869    pub path: String,
1870    pub document: RoderDesignDocument,
1871    pub applied: usize,
1872}
1873
1874#[derive(Debug, Clone, Serialize, Deserialize)]
1875#[serde(rename_all = "camelCase")]
1876pub struct DesignVariablesResult {
1877    pub path: String,
1878    pub variables: BTreeMap<String, serde_json::Value>,
1879}
1880
1881#[derive(Debug, Clone, Serialize, Deserialize)]
1882#[serde(rename_all = "camelCase")]
1883pub struct DesignLayoutNode {
1884    pub id: String,
1885    #[serde(rename = "type")]
1886    pub node_type: String,
1887    pub name: String,
1888    pub x: f64,
1889    pub y: f64,
1890    pub width: f64,
1891    pub height: f64,
1892    #[serde(default, skip_serializing_if = "Option::is_none")]
1893    pub parent_id: Option<String>,
1894    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1895    pub child_ids: Vec<String>,
1896    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1897    pub problems: Vec<String>,
1898}
1899
1900#[derive(Debug, Clone, Serialize, Deserialize)]
1901#[serde(rename_all = "camelCase")]
1902pub struct DesignSnapshotLayoutResult {
1903    pub path: String,
1904    pub nodes: Vec<DesignLayoutNode>,
1905}
1906
1907#[derive(Debug, Clone, Serialize, Deserialize)]
1908#[serde(rename_all = "camelCase")]
1909pub struct DesignGuidelineCategory {
1910    pub name: String,
1911    pub description: String,
1912    pub guidelines: Vec<String>,
1913}
1914
1915#[derive(Debug, Clone, Serialize, Deserialize)]
1916#[serde(rename_all = "camelCase")]
1917pub struct DesignGuidelinesResult {
1918    pub categories: Vec<DesignGuidelineCategory>,
1919}
1920
1921#[derive(Debug, Clone, Serialize, Deserialize)]
1922#[serde(rename_all = "camelCase")]
1923pub struct DesignSpawnedAgentScope {
1924    pub alias: String,
1925    pub scope_node_id: String,
1926    pub scope_name: String,
1927    #[serde(rename = "type")]
1928    pub node_type: String,
1929    pub child_count: usize,
1930    pub prompt: String,
1931}
1932
1933#[derive(Debug, Clone, Serialize, Deserialize)]
1934#[serde(rename_all = "camelCase")]
1935pub struct DesignSpawnAgentsResult {
1936    pub path: String,
1937    pub planned: Vec<DesignSpawnedAgentScope>,
1938    pub allow_patch: bool,
1939    pub allow_export: bool,
1940    pub require_review: bool,
1941    pub instructions: String,
1942}
1943
1944#[derive(Debug, Clone, Serialize, Deserialize)]
1945#[serde(rename_all = "camelCase")]
1946pub struct DesignExportNodesParams {
1947    pub workspace_id: String,
1948    #[serde(default, skip_serializing_if = "Option::is_none")]
1949    pub root_id: Option<String>,
1950    pub node_ids: Vec<String>,
1951    #[serde(default, skip_serializing_if = "Option::is_none")]
1952    pub output_dir: Option<String>,
1953    #[serde(default, skip_serializing_if = "Option::is_none")]
1954    pub format: Option<String>,
1955}
1956
1957#[derive(Debug, Clone, Serialize, Deserialize)]
1958#[serde(rename_all = "camelCase")]
1959pub struct DesignGetScreenshotParams {
1960    pub workspace_id: String,
1961    #[serde(default, skip_serializing_if = "Option::is_none")]
1962    pub root_id: Option<String>,
1963    #[serde(default, skip_serializing_if = "Option::is_none")]
1964    pub node_id: Option<String>,
1965    #[serde(default)]
1966    pub format: Option<String>,
1967}
1968
1969#[derive(Debug, Clone, Serialize, Deserialize)]
1970#[serde(rename_all = "camelCase")]
1971pub struct DesignExportedNode {
1972    pub node_id: String,
1973    pub path: String,
1974}
1975
1976#[derive(Debug, Clone, Serialize, Deserialize)]
1977#[serde(rename_all = "camelCase")]
1978pub struct DesignExportNodesResult {
1979    pub exported: Vec<DesignExportedNode>,
1980}
1981
1982#[derive(Debug, Clone, Serialize, Deserialize)]
1983#[serde(rename_all = "camelCase")]
1984pub struct DesignScreenshotResult {
1985    pub path: String,
1986    #[serde(default, skip_serializing_if = "Option::is_none")]
1987    pub node_id: Option<String>,
1988    pub mime_type: String,
1989    pub data_url: String,
1990}
1991
1992#[derive(Debug, Clone, Serialize, Deserialize)]
1993#[serde(rename_all = "camelCase")]
1994pub struct CommandExecParams {
1995    pub command: Vec<String>,
1996    pub process_id: Option<String>,
1997    #[serde(default)]
1998    pub tty: bool,
1999    #[serde(default)]
2000    pub stream_stdin: bool,
2001    #[serde(default)]
2002    pub stream_stdout_stderr: bool,
2003    pub output_bytes_cap: Option<usize>,
2004    #[serde(default)]
2005    pub disable_output_cap: bool,
2006    #[serde(default)]
2007    pub disable_timeout: bool,
2008    pub timeout_ms: Option<u64>,
2009    pub cwd: Option<String>,
2010    pub env: Option<HashMap<String, Option<String>>>,
2011    #[serde(default)]
2012    pub size: Option<serde_json::Value>,
2013    #[serde(default)]
2014    pub sandbox_policy: Option<serde_json::Value>,
2015}
2016
2017#[derive(Debug, Clone, Serialize, Deserialize)]
2018#[serde(rename_all = "camelCase")]
2019pub struct CommandExecResponse {
2020    pub exit_code: i32,
2021    pub stdout: String,
2022    pub stderr: String,
2023    #[serde(default, skip_serializing_if = "Option::is_none")]
2024    pub stdout_artifact: Option<ContextArtifactDescriptor>,
2025    #[serde(default, skip_serializing_if = "Option::is_none")]
2026    pub stderr_artifact: Option<ContextArtifactDescriptor>,
2027}
2028
2029#[derive(Debug, Clone, Serialize, Deserialize)]
2030#[serde(rename_all = "camelCase")]
2031pub struct CommandExecOutputDeltaNotification {
2032    pub process_id: String,
2033    pub stream: String,
2034    pub delta_base64: String,
2035    pub cap_reached: bool,
2036}
2037
2038#[derive(Debug, Clone, Serialize, Deserialize)]
2039#[serde(rename_all = "camelCase")]
2040pub struct TeamDescriptor {
2041    pub id: TeamId,
2042    pub lead_thread_id: ThreadId,
2043    pub display_mode: AgentTeamDisplayMode,
2044    pub members: Vec<TeamMemberDescriptor>,
2045    pub tasks: Vec<TeamTaskDescriptor>,
2046}
2047
2048#[derive(Debug, Clone, Serialize, Deserialize)]
2049#[serde(rename_all = "camelCase")]
2050pub struct TeamStartMemberParams {
2051    pub name: String,
2052    pub model_provider: Option<String>,
2053    pub model: Option<String>,
2054}
2055
2056#[derive(Debug, Clone, Serialize, Deserialize)]
2057#[serde(rename_all = "camelCase")]
2058pub struct TeamStartParams {
2059    pub lead_thread_id: Option<ThreadId>,
2060    pub display_mode: Option<AgentTeamDisplayMode>,
2061    #[serde(default)]
2062    pub members: Vec<TeamStartMemberParams>,
2063}
2064
2065#[derive(Debug, Clone, Serialize, Deserialize)]
2066#[serde(rename_all = "camelCase")]
2067pub struct TeamStartResult {
2068    pub team: TeamDescriptor,
2069}
2070
2071#[derive(Debug, Clone, Serialize, Deserialize)]
2072#[serde(rename_all = "camelCase")]
2073pub struct TeamListParams {
2074    pub limit: Option<usize>,
2075}
2076
2077#[derive(Debug, Clone, Serialize, Deserialize)]
2078#[serde(rename_all = "camelCase")]
2079pub struct TeamListResult {
2080    pub data: Vec<TeamDescriptor>,
2081    pub next_cursor: Option<String>,
2082}
2083
2084#[derive(Debug, Clone, Serialize, Deserialize)]
2085#[serde(rename_all = "camelCase")]
2086pub struct TeamReadParams {
2087    pub team_id: TeamId,
2088}
2089
2090#[derive(Debug, Clone, Serialize, Deserialize)]
2091#[serde(rename_all = "camelCase")]
2092pub struct TeamReadResult {
2093    pub team: Option<TeamDescriptor>,
2094    pub messages: Vec<TeamMailboxMessage>,
2095}
2096
2097#[derive(Debug, Clone, Serialize, Deserialize)]
2098#[serde(rename_all = "camelCase")]
2099pub struct TeamMemberStartParams {
2100    pub team_id: TeamId,
2101    pub name: String,
2102    pub model_provider: Option<String>,
2103    pub model: Option<String>,
2104}
2105
2106#[derive(Debug, Clone, Serialize, Deserialize)]
2107#[serde(rename_all = "camelCase")]
2108pub struct TeamMemberStartResult {
2109    pub member: TeamMemberDescriptor,
2110}
2111
2112#[derive(Debug, Clone, Serialize, Deserialize)]
2113#[serde(rename_all = "camelCase")]
2114pub struct TeamMemberMessageParams {
2115    pub team_id: TeamId,
2116    pub member_id: TeamMemberId,
2117    pub text: String,
2118    pub expected_turn_id: Option<TurnId>,
2119}
2120
2121#[derive(Debug, Clone, Serialize, Deserialize)]
2122#[serde(rename_all = "camelCase")]
2123pub struct TeamMemberMessageResult {
2124    pub turn_id: TurnId,
2125}
2126
2127#[derive(Debug, Clone, Serialize, Deserialize)]
2128#[serde(rename_all = "camelCase")]
2129pub struct TeamMemberInterruptParams {
2130    pub team_id: TeamId,
2131    pub member_id: TeamMemberId,
2132    pub turn_id: Option<TurnId>,
2133}
2134
2135#[derive(Debug, Clone, Serialize, Deserialize)]
2136#[serde(rename_all = "camelCase")]
2137pub struct TeamMemberInterruptResult {
2138    pub interrupted: bool,
2139    pub turn_id: Option<TurnId>,
2140}
2141
2142#[derive(Debug, Clone, Serialize, Deserialize)]
2143#[serde(rename_all = "camelCase")]
2144pub struct TeamMemberFocusParams {
2145    pub team_id: TeamId,
2146    pub member_id: TeamMemberId,
2147}
2148
2149#[derive(Debug, Clone, Serialize, Deserialize)]
2150#[serde(rename_all = "camelCase")]
2151pub struct TeamMemberFocusResult {
2152    pub focused_member_id: TeamMemberId,
2153}
2154
2155#[derive(Debug, Clone, Serialize, Deserialize)]
2156#[serde(rename_all = "camelCase")]
2157pub struct TeamCleanupParams {
2158    pub team_id: TeamId,
2159    #[serde(default)]
2160    pub force: bool,
2161}
2162
2163#[derive(Debug, Clone, Serialize, Deserialize)]
2164#[serde(rename_all = "camelCase")]
2165pub struct TeamCleanupResult {
2166    pub cleaned: bool,
2167}
2168
2169#[derive(Debug, Clone, Serialize, Deserialize)]
2170#[serde(rename_all = "camelCase")]
2171pub struct TeamStartedNotification {
2172    pub team: TeamDescriptor,
2173}
2174
2175#[derive(Debug, Clone, Serialize, Deserialize)]
2176#[serde(rename_all = "camelCase")]
2177pub struct TeamMemberStartedNotification {
2178    pub team_id: TeamId,
2179    pub member: TeamMemberDescriptor,
2180}
2181
2182#[derive(Debug, Clone, Serialize, Deserialize)]
2183#[serde(rename_all = "camelCase")]
2184pub struct TeamMemberStatusChangedNotification {
2185    pub team_id: TeamId,
2186    pub member_id: TeamMemberId,
2187    pub status: TeamMemberStatus,
2188}
2189
2190#[derive(Debug, Clone, Serialize, Deserialize)]
2191#[serde(rename_all = "camelCase")]
2192pub struct TeamMemberMessageDeltaNotification {
2193    pub team_id: TeamId,
2194    pub member_id: TeamMemberId,
2195    pub turn_id: TurnId,
2196    pub delta: String,
2197}
2198
2199#[derive(Debug, Clone, Serialize, Deserialize)]
2200#[serde(rename_all = "camelCase")]
2201pub struct TeamMemberCompletedNotification {
2202    pub team_id: TeamId,
2203    pub member_id: TeamMemberId,
2204    pub turn_id: Option<TurnId>,
2205    pub status: TeamMemberStatus,
2206}
2207
2208#[derive(Debug, Clone, Serialize, Deserialize)]
2209#[serde(rename_all = "camelCase")]
2210pub struct TeamDisplayModeChangedNotification {
2211    pub team_id: TeamId,
2212    pub display_mode: AgentTeamDisplayMode,
2213}
2214
2215#[derive(Debug, Clone, Serialize, Deserialize)]
2216#[serde(rename_all = "camelCase")]
2217pub struct TeamTaskChangedNotification {
2218    pub team_id: TeamId,
2219    pub task: TeamTaskDescriptor,
2220}
2221
2222#[derive(Debug, Clone, Serialize, Deserialize)]
2223#[serde(rename_all = "camelCase")]
2224pub struct TeamCleanupCompletedNotification {
2225    pub team_id: TeamId,
2226    pub forced: bool,
2227}
2228
2229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2230pub struct RunnerStatus {
2231    pub destination_id: String,
2232    pub provider_id: String,
2233    pub state: String,
2234    pub session_id: Option<String>,
2235}
2236
2237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2238pub struct RunnerProviderDescriptor {
2239    pub provider_id: String,
2240    pub capabilities: roder_api::remote_runner::RunnerCapabilities,
2241    /// Setup guidance when the provider is installed but missing credentials
2242    /// (documented env-var names only; never secret values).
2243    #[serde(default, skip_serializing_if = "Option::is_none")]
2244    pub setup_hint: Option<String>,
2245}
2246
2247#[derive(Debug, Clone, Serialize, Deserialize)]
2248pub struct RunnersListResult {
2249    pub active: Option<RunnerStatus>,
2250    pub providers: Vec<RunnerProviderDescriptor>,
2251}
2252
2253#[derive(Debug, Clone, Serialize, Deserialize)]
2254pub struct RunnersSelectParams {
2255    pub destination_id: String,
2256    pub provider_id: Option<String>,
2257    #[serde(default)]
2258    pub config: serde_json::Value,
2259    #[serde(default)]
2260    pub manifest: roder_api::remote_runner::RunnerManifest,
2261}
2262
2263#[derive(Debug, Clone, Serialize, Deserialize)]
2264pub struct RunnersSelectResult {
2265    pub active: Option<RunnerStatus>,
2266}
2267
2268#[derive(Debug, Clone, Serialize, Deserialize)]
2269pub struct RunnersSessionResult {
2270    pub active: Option<RunnerStatus>,
2271}
2272
2273#[derive(Debug, Clone, Serialize, Deserialize)]
2274pub struct RunnersSnapshotResult {
2275    pub snapshot: Option<roder_api::remote_runner::RunnerSnapshotRef>,
2276}
2277
2278#[derive(Debug, Clone, Serialize, Deserialize)]
2279pub struct RunnersDeleteResult {
2280    pub deleted: bool,
2281}
2282
2283#[derive(Debug, Clone, Serialize, Deserialize)]
2284pub struct RunnersPortsResult {
2285    pub ports: Vec<roder_api::remote_runner::RunnerPortResult>,
2286}
2287
2288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2289pub struct WebSearchSettings {
2290    pub mode: HostedWebSearchMode,
2291}
2292
2293#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2294pub struct SearchIndexSettings {
2295    pub enabled: bool,
2296}
2297
2298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2299pub struct ShellSettings {
2300    pub shell: String,
2301    pub options: Vec<String>,
2302}
2303
2304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2305#[serde(rename_all = "camelCase")]
2306pub enum SearchIndexStatusState {
2307    Disabled,
2308    Missing,
2309    Building,
2310    Ready,
2311    Stale,
2312    Failed,
2313    Cleared,
2314}
2315
2316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2317#[serde(rename_all = "camelCase")]
2318pub struct SearchIndexStatus {
2319    pub state: SearchIndexStatusState,
2320    pub enabled: bool,
2321    pub workspace: String,
2322    pub store_dir: String,
2323    #[serde(default, skip_serializing_if = "Option::is_none")]
2324    pub index_version: Option<String>,
2325    #[serde(default, skip_serializing_if = "Option::is_none")]
2326    pub document_count: Option<u64>,
2327    #[serde(default, skip_serializing_if = "Option::is_none")]
2328    pub index_bytes: Option<u64>,
2329    #[serde(default, skip_serializing_if = "Option::is_none")]
2330    pub build_time_ms: Option<u64>,
2331    pub stale: bool,
2332    #[serde(default, skip_serializing_if = "Option::is_none")]
2333    pub message: Option<String>,
2334}
2335
2336#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2337#[serde(rename_all = "camelCase")]
2338pub struct SearchIndexStatusParams {
2339    pub workspace: Option<String>,
2340}
2341
2342#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2343#[serde(rename_all = "camelCase")]
2344pub struct SearchIndexWarmupParams {
2345    pub workspace: Option<String>,
2346}
2347
2348#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2349#[serde(rename_all = "camelCase")]
2350pub struct SearchIndexRebuildParams {
2351    pub workspace: Option<String>,
2352}
2353
2354#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2355#[serde(rename_all = "camelCase")]
2356pub struct SearchIndexClearParams {
2357    pub workspace: Option<String>,
2358}
2359
2360#[derive(Debug, Clone, Serialize, Deserialize)]
2361#[serde(rename_all = "camelCase")]
2362pub struct SearchIndexStatusResult {
2363    pub status: SearchIndexStatus,
2364}
2365
2366#[derive(Debug, Clone, Serialize, Deserialize)]
2367#[serde(rename_all = "camelCase")]
2368pub struct SearchIndexWarmupResult {
2369    pub status: SearchIndexStatus,
2370}
2371
2372#[derive(Debug, Clone, Serialize, Deserialize)]
2373#[serde(rename_all = "camelCase")]
2374pub struct SearchIndexRebuildResult {
2375    pub status: SearchIndexStatus,
2376}
2377
2378#[derive(Debug, Clone, Serialize, Deserialize)]
2379#[serde(rename_all = "camelCase")]
2380pub struct SearchIndexClearResult {
2381    pub status: SearchIndexStatus,
2382}
2383
2384#[derive(Debug, Clone, Serialize, Deserialize)]
2385#[serde(rename_all = "camelCase")]
2386pub struct SearchIndexStatusNotification {
2387    pub status: SearchIndexStatus,
2388}
2389
2390#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2391#[serde(rename_all = "camelCase")]
2392pub struct CodeIndexStatusView {
2393    pub status: CodeIndexStatus,
2394    pub workspace: String,
2395    pub store_path: String,
2396    pub generation_id: Option<CodeIndexGenerationId>,
2397    pub root_hash: Option<String>,
2398    pub stale: bool,
2399    pub stats: CodeIndexStats,
2400    #[serde(default, skip_serializing_if = "Option::is_none")]
2401    pub message: Option<String>,
2402}
2403
2404#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2405#[serde(rename_all = "camelCase")]
2406pub struct CodeIndexStatusParams {
2407    pub workspace: Option<String>,
2408}
2409
2410#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2411#[serde(rename_all = "camelCase")]
2412pub struct CodeIndexRebuildParams {
2413    pub workspace: Option<String>,
2414}
2415
2416#[derive(Debug, Clone, Serialize, Deserialize)]
2417#[serde(rename_all = "camelCase")]
2418pub struct CodeIndexSearchParams {
2419    pub query: String,
2420    #[serde(default, skip_serializing_if = "Option::is_none")]
2421    pub workspace: Option<String>,
2422    #[serde(default)]
2423    pub limit: Option<usize>,
2424}
2425
2426#[derive(Debug, Clone, Serialize, Deserialize)]
2427#[serde(rename_all = "camelCase")]
2428pub struct CodeIndexReadChunkParams {
2429    pub chunk_hash: String,
2430    #[serde(default, skip_serializing_if = "Option::is_none")]
2431    pub workspace: Option<String>,
2432    #[serde(default)]
2433    pub offset: Option<usize>,
2434    #[serde(default)]
2435    pub limit: Option<usize>,
2436    #[serde(default)]
2437    pub include_source: bool,
2438}
2439
2440#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2441#[serde(rename_all = "camelCase")]
2442pub struct CodeIndexProofsListParams {
2443    #[serde(default, skip_serializing_if = "Option::is_none")]
2444    pub workspace: Option<String>,
2445}
2446
2447#[derive(Debug, Clone, Serialize, Deserialize)]
2448#[serde(rename_all = "camelCase")]
2449pub struct CodeIndexStatusResult {
2450    pub status: CodeIndexStatusView,
2451}
2452
2453#[derive(Debug, Clone, Serialize, Deserialize)]
2454#[serde(rename_all = "camelCase")]
2455pub struct CodeIndexRebuildResult {
2456    pub status: CodeIndexStatusView,
2457}
2458
2459#[derive(Debug, Clone, Serialize, Deserialize)]
2460#[serde(rename_all = "camelCase")]
2461pub struct CodeIndexSearchResultEnvelope {
2462    pub status: CodeIndexStatusView,
2463    pub response: CodeIndexSearchResponse,
2464}
2465
2466#[derive(Debug, Clone, Serialize, Deserialize)]
2467#[serde(rename_all = "camelCase")]
2468pub struct CodeIndexChunkReadPage {
2469    pub chunk: CodeChunk,
2470    pub text: String,
2471    pub offset: usize,
2472    pub limit: usize,
2473    pub total_bytes: usize,
2474    pub has_more: bool,
2475}
2476
2477#[derive(Debug, Clone, Serialize, Deserialize)]
2478#[serde(rename_all = "camelCase")]
2479pub struct CodeIndexReadChunkResult {
2480    pub status: CodeIndexStatusView,
2481    pub page: CodeIndexChunkReadPage,
2482}
2483
2484#[derive(Debug, Clone, Serialize, Deserialize)]
2485#[serde(rename_all = "camelCase")]
2486pub struct CodeIndexProofsListResult {
2487    pub status: CodeIndexStatusView,
2488    pub proofs: Vec<ContentProof>,
2489}
2490
2491#[derive(Debug, Clone, Serialize, Deserialize)]
2492#[serde(rename_all = "camelCase")]
2493pub struct CodeIndexStatusNotification {
2494    pub status: CodeIndexStatusView,
2495}
2496
2497#[derive(Debug, Clone, Serialize, Deserialize)]
2498pub struct ExtensionsListResult {
2499    pub extensions: Vec<ExtensionManifest>,
2500    #[serde(default)]
2501    pub capability_statuses: std::collections::BTreeMap<ExtensionId, Vec<CapabilityStatus>>,
2502}
2503
2504#[derive(Debug, Clone, Serialize, Deserialize)]
2505pub struct ProviderDescriptor {
2506    pub id: String,
2507    pub name: String,
2508    pub description: Option<String>,
2509    pub auth_type: ProviderAuthType,
2510    pub auth_label: Option<String>,
2511    pub authenticated: bool,
2512    pub auth_detail: Option<String>,
2513    pub recommended: bool,
2514    pub sort_order: i32,
2515    pub capabilities: InferenceCapabilities,
2516    pub models: Vec<ModelDescriptor>,
2517}
2518
2519#[derive(Debug, Clone, Serialize, Deserialize)]
2520pub struct ProvidersListResult {
2521    pub active_provider: String,
2522    pub active_model: String,
2523    pub active_reasoning: String,
2524    #[serde(
2525        default,
2526        rename = "selectionMode",
2527        skip_serializing_if = "Option::is_none"
2528    )]
2529    pub selection_mode: Option<ModelSelectionMode>,
2530    #[serde(default, rename = "routingOptions")]
2531    pub routing_options: Vec<InferenceRoutingOptionDescriptor>,
2532    pub providers: Vec<ProviderDescriptor>,
2533}
2534
2535#[derive(Debug, Clone, Serialize, Deserialize)]
2536pub struct ProviderConfigureParams {
2537    pub provider: String,
2538    pub api_key: String,
2539}
2540
2541#[derive(Debug, Clone, Serialize, Deserialize)]
2542pub struct ProviderConfigureResult {
2543    pub provider: String,
2544    pub authenticated: bool,
2545}
2546
2547#[derive(Debug, Clone, Serialize, Deserialize)]
2548pub struct ProviderClearParams {
2549    pub provider: String,
2550}
2551
2552#[derive(Debug, Clone, Serialize, Deserialize)]
2553pub struct ProviderClearResult {
2554    pub provider: String,
2555}
2556
2557#[derive(Debug, Clone, Serialize, Deserialize)]
2558#[serde(rename_all = "camelCase")]
2559pub struct SubagentTracesListParams {
2560    pub thread_id: ThreadId,
2561    pub turn_id: TurnId,
2562}
2563
2564#[derive(Debug, Clone, Serialize, Deserialize)]
2565#[serde(rename_all = "camelCase")]
2566pub struct SubagentTracesListResult {
2567    pub traces: Vec<SubagentTraceSummary>,
2568}
2569
2570#[derive(Debug, Clone, Serialize, Deserialize)]
2571#[serde(rename_all = "camelCase")]
2572pub struct SubagentTraceReadParams {
2573    pub thread_id: ThreadId,
2574    pub trace_id: SubagentTraceId,
2575    #[serde(default)]
2576    pub offset: usize,
2577    #[serde(default, skip_serializing_if = "Option::is_none")]
2578    pub limit: Option<usize>,
2579}
2580
2581#[derive(Debug, Clone, Serialize, Deserialize)]
2582#[serde(rename_all = "camelCase")]
2583pub struct SubagentTraceReadResult {
2584    pub trace_id: SubagentTraceId,
2585    pub events: Vec<SubagentTraceDelta>,
2586    #[serde(default, skip_serializing_if = "Option::is_none")]
2587    pub next_offset: Option<usize>,
2588}
2589
2590#[derive(Debug, Clone, Serialize, Deserialize)]
2591#[serde(rename_all = "camelCase")]
2592pub struct PlanReviewReadParams {
2593    pub thread_id: ThreadId,
2594    pub review_id: PlanReviewId,
2595}
2596
2597#[derive(Debug, Clone, Serialize, Deserialize)]
2598#[serde(rename_all = "camelCase")]
2599pub struct PlanReviewReadResult {
2600    pub review: Option<PlanReview>,
2601}
2602
2603#[derive(Debug, Clone, Serialize, Deserialize)]
2604#[serde(rename_all = "camelCase")]
2605pub struct PlanReviewCommentParams {
2606    pub thread_id: ThreadId,
2607    pub review_id: PlanReviewId,
2608    pub anchor: PlanCommentAnchor,
2609    pub body: String,
2610}
2611
2612#[derive(Debug, Clone, Serialize, Deserialize)]
2613#[serde(rename_all = "camelCase")]
2614pub struct PlanReviewCommentResult {
2615    pub comment: PlanComment,
2616}
2617
2618#[derive(Debug, Clone, Serialize, Deserialize)]
2619#[serde(rename_all = "camelCase")]
2620pub struct PlanReviewRewriteParams {
2621    pub thread_id: ThreadId,
2622    pub review_id: PlanReviewId,
2623    pub replacement_markdown: String,
2624}
2625
2626#[derive(Debug, Clone, Serialize, Deserialize)]
2627#[serde(rename_all = "camelCase")]
2628pub struct PlanReviewRewriteResult {
2629    pub rewrite: PlanRewrite,
2630}
2631
2632#[derive(Debug, Clone, Serialize, Deserialize)]
2633#[serde(rename_all = "camelCase")]
2634pub struct PlanReviewApproveParams {
2635    pub thread_id: ThreadId,
2636    pub review_id: PlanReviewId,
2637}
2638
2639#[derive(Debug, Clone, Serialize, Deserialize)]
2640#[serde(rename_all = "camelCase")]
2641pub struct PlanReviewApproveResult {
2642    pub approved: bool,
2643}
2644
2645#[derive(Debug, Clone, Serialize, Deserialize)]
2646#[serde(rename_all = "camelCase")]
2647pub struct PlanReviewRejectParams {
2648    pub thread_id: ThreadId,
2649    pub review_id: PlanReviewId,
2650    #[serde(default, skip_serializing_if = "Option::is_none")]
2651    pub reason: Option<String>,
2652}
2653
2654#[derive(Debug, Clone, Serialize, Deserialize)]
2655#[serde(rename_all = "camelCase")]
2656pub struct PlanReviewRejectResult {
2657    pub rejected: bool,
2658}
2659
2660#[derive(Debug, Clone, Serialize, Deserialize)]
2661#[serde(rename_all = "camelCase")]
2662pub struct HunkListParams {
2663    pub thread_id: ThreadId,
2664    #[serde(default, skip_serializing_if = "Option::is_none")]
2665    pub turn_id: Option<TurnId>,
2666    #[serde(default, skip_serializing_if = "Option::is_none")]
2667    pub review_id: Option<PlanReviewId>,
2668}
2669
2670#[derive(Debug, Clone, Serialize, Deserialize)]
2671#[serde(rename_all = "camelCase")]
2672pub struct HunkListResult {
2673    pub hunks: Vec<HunkRecord>,
2674}
2675
2676#[derive(Debug, Clone, Serialize, Deserialize)]
2677#[serde(rename_all = "camelCase")]
2678pub struct HunkReadParams {
2679    pub thread_id: ThreadId,
2680    pub hunk_id: HunkId,
2681    #[serde(default)]
2682    pub offset: usize,
2683    #[serde(default, skip_serializing_if = "Option::is_none")]
2684    pub limit: Option<usize>,
2685}
2686
2687#[derive(Debug, Clone, Serialize, Deserialize)]
2688#[serde(rename_all = "camelCase")]
2689pub struct HunkReadResult {
2690    pub page: Option<PagedHunkDiff>,
2691}
2692
2693#[derive(Debug, Clone, Serialize, Deserialize)]
2694#[serde(rename_all = "camelCase")]
2695pub struct WorkspaceChangesListParams {
2696    pub thread_id: ThreadId,
2697    #[serde(default, skip_serializing_if = "Option::is_none")]
2698    pub turn_id: Option<TurnId>,
2699}
2700
2701#[derive(Debug, Clone, Serialize, Deserialize)]
2702#[serde(rename_all = "camelCase")]
2703pub struct WorkspaceChangesListResult {
2704    pub changes: Vec<WorkspaceChangeObservation>,
2705}
2706
2707#[derive(Debug, Clone, Serialize, Deserialize)]
2708#[serde(rename_all = "camelCase")]
2709pub struct HunkRollbackParams {
2710    pub thread_id: ThreadId,
2711    pub hunk_id: HunkId,
2712    #[serde(default)]
2713    pub confirmed: bool,
2714}
2715
2716#[derive(Debug, Clone, Serialize, Deserialize)]
2717#[serde(rename_all = "camelCase")]
2718pub struct HunkRollbackResult {
2719    pub rolled_back: bool,
2720    #[serde(default, skip_serializing_if = "Option::is_none")]
2721    pub error: Option<String>,
2722}
2723
2724#[derive(Debug, Clone, Serialize, Deserialize)]
2725#[serde(rename_all = "camelCase")]
2726pub struct VcsWorkspaceParams {
2727    pub workspace_id: String,
2728    #[serde(default, skip_serializing_if = "Option::is_none")]
2729    pub root_id: Option<String>,
2730    #[serde(default, skip_serializing_if = "Option::is_none")]
2731    pub provider_id: Option<String>,
2732}
2733
2734#[derive(Debug, Clone, Serialize, Deserialize)]
2735#[serde(rename_all = "camelCase")]
2736pub struct VcsChangesListParams {
2737    pub workspace_id: String,
2738    #[serde(default, skip_serializing_if = "Option::is_none")]
2739    pub root_id: Option<String>,
2740    #[serde(default, skip_serializing_if = "Option::is_none")]
2741    pub provider_id: Option<String>,
2742    #[serde(default, skip_serializing_if = "Option::is_none")]
2743    pub limit: Option<usize>,
2744}
2745
2746#[derive(Debug, Clone, Serialize, Deserialize)]
2747#[serde(rename_all = "camelCase")]
2748pub struct VcsChangesReadParams {
2749    pub workspace_id: String,
2750    #[serde(default, skip_serializing_if = "Option::is_none")]
2751    pub root_id: Option<String>,
2752    #[serde(default, skip_serializing_if = "Option::is_none")]
2753    pub provider_id: Option<String>,
2754    pub path: String,
2755    #[serde(default)]
2756    pub offset: usize,
2757    #[serde(default, skip_serializing_if = "Option::is_none")]
2758    pub limit: Option<usize>,
2759    #[serde(default, skip_serializing_if = "Option::is_none")]
2760    pub area: Option<VcsChangeArea>,
2761    #[serde(default)]
2762    pub ignore_whitespace: bool,
2763}
2764
2765#[derive(Debug, Clone, Serialize, Deserialize)]
2766#[serde(rename_all = "camelCase")]
2767pub struct VcsChangesTotals {
2768    pub files: u32,
2769    pub additions: u32,
2770    pub deletions: u32,
2771}
2772
2773#[derive(Debug, Clone, Serialize, Deserialize)]
2774#[serde(rename_all = "camelCase")]
2775pub struct VcsChangesListResult {
2776    pub status: roder_api::version_control::VcsStatus,
2777    pub files: Vec<roder_api::version_control::VcsChangedFile>,
2778    pub totals: VcsChangesTotals,
2779    pub truncated: bool,
2780}
2781
2782#[derive(Debug, Clone, Serialize, Deserialize)]
2783#[serde(rename_all = "camelCase")]
2784pub struct VcsSelectionParams {
2785    pub workspace_id: String,
2786    #[serde(default, skip_serializing_if = "Option::is_none")]
2787    pub root_id: Option<String>,
2788    #[serde(default, skip_serializing_if = "Option::is_none")]
2789    pub provider_id: Option<String>,
2790    pub paths: Vec<String>,
2791    pub granularity: roder_api::version_control::VcsSelectionGranularity,
2792}
2793
2794#[derive(Debug, Clone, Serialize, Deserialize)]
2795#[serde(rename_all = "camelCase")]
2796pub struct VcsSnapshotCreateParams {
2797    pub workspace_id: String,
2798    #[serde(default, skip_serializing_if = "Option::is_none")]
2799    pub root_id: Option<String>,
2800    #[serde(default, skip_serializing_if = "Option::is_none")]
2801    pub provider_id: Option<String>,
2802    pub message: String,
2803    #[serde(default)]
2804    pub paths: Vec<String>,
2805}
2806
2807#[derive(Debug, Clone, Serialize, Deserialize)]
2808#[serde(rename_all = "camelCase")]
2809pub struct VcsRestoreParams {
2810    pub workspace_id: String,
2811    #[serde(default, skip_serializing_if = "Option::is_none")]
2812    pub root_id: Option<String>,
2813    #[serde(default, skip_serializing_if = "Option::is_none")]
2814    pub provider_id: Option<String>,
2815    pub paths: Vec<String>,
2816}
2817
2818#[derive(Debug, Clone, Serialize, Deserialize)]
2819#[serde(rename_all = "camelCase")]
2820pub struct VcsLineSwitchParams {
2821    pub workspace_id: String,
2822    #[serde(default, skip_serializing_if = "Option::is_none")]
2823    pub root_id: Option<String>,
2824    #[serde(default, skip_serializing_if = "Option::is_none")]
2825    pub provider_id: Option<String>,
2826    pub line_id: String,
2827}
2828
2829#[derive(Debug, Clone, Serialize, Deserialize)]
2830#[serde(rename_all = "camelCase")]
2831pub struct VcsSyncParams {
2832    pub workspace_id: String,
2833    #[serde(default, skip_serializing_if = "Option::is_none")]
2834    pub root_id: Option<String>,
2835    #[serde(default, skip_serializing_if = "Option::is_none")]
2836    pub provider_id: Option<String>,
2837    pub operation: roder_api::version_control::VcsSyncOperation,
2838}
2839
2840#[derive(Debug, Clone, Serialize, Deserialize)]
2841#[serde(rename_all = "camelCase")]
2842pub struct MediaListParams {
2843    #[serde(default, skip_serializing_if = "Option::is_none")]
2844    pub thread_id: Option<ThreadId>,
2845    #[serde(default, skip_serializing_if = "Option::is_none")]
2846    pub kind: Option<roder_api::media::MediaKind>,
2847}
2848
2849#[derive(Debug, Clone, Serialize, Deserialize)]
2850#[serde(rename_all = "camelCase")]
2851pub struct MediaListResult {
2852    pub artifacts: Vec<MediaArtifact>,
2853}
2854
2855#[derive(Debug, Clone, Serialize, Deserialize)]
2856#[serde(rename_all = "camelCase")]
2857pub struct ArtifactListParams {
2858    pub thread_id: ThreadId,
2859    #[serde(default, skip_serializing_if = "Option::is_none")]
2860    pub kind: Option<ContextArtifactKind>,
2861    #[serde(default, skip_serializing_if = "Option::is_none")]
2862    pub limit: Option<usize>,
2863}
2864
2865#[derive(Debug, Clone, Serialize, Deserialize)]
2866#[serde(rename_all = "camelCase")]
2867pub struct ArtifactListResult {
2868    pub artifacts: Vec<ContextArtifactDescriptor>,
2869}
2870
2871#[derive(Debug, Clone, Serialize, Deserialize)]
2872#[serde(rename_all = "camelCase")]
2873pub struct ArtifactReadParams {
2874    pub thread_id: ThreadId,
2875    pub artifact_id: String,
2876    #[serde(default, skip_serializing_if = "Option::is_none")]
2877    pub start_line: Option<usize>,
2878    #[serde(default, skip_serializing_if = "Option::is_none")]
2879    pub limit: Option<usize>,
2880}
2881
2882#[derive(Debug, Clone, Serialize, Deserialize)]
2883#[serde(rename_all = "camelCase")]
2884pub struct ArtifactReadResult {
2885    pub page: ArtifactReadPage,
2886}
2887
2888#[derive(Debug, Clone, Serialize, Deserialize)]
2889#[serde(rename_all = "camelCase")]
2890pub struct ArtifactGrepParams {
2891    pub thread_id: ThreadId,
2892    pub artifact_id: String,
2893    pub query: String,
2894    #[serde(default, skip_serializing_if = "Option::is_none")]
2895    pub offset: Option<usize>,
2896    #[serde(default, skip_serializing_if = "Option::is_none")]
2897    pub limit: Option<usize>,
2898}
2899
2900#[derive(Debug, Clone, Serialize, Deserialize)]
2901#[serde(rename_all = "camelCase")]
2902pub struct ArtifactGrepResult {
2903    pub page: ArtifactGrepPage,
2904}
2905
2906#[derive(Debug, Clone, Serialize, Deserialize)]
2907#[serde(rename_all = "camelCase")]
2908pub struct ArtifactTailParams {
2909    pub thread_id: ThreadId,
2910    pub artifact_id: String,
2911    #[serde(default, skip_serializing_if = "Option::is_none")]
2912    pub lines: Option<usize>,
2913}
2914
2915#[derive(Debug, Clone, Serialize, Deserialize)]
2916#[serde(rename_all = "camelCase")]
2917pub struct ArtifactTailResult {
2918    pub page: ArtifactTailPage,
2919}
2920
2921#[derive(Debug, Clone, Serialize, Deserialize)]
2922#[serde(rename_all = "camelCase")]
2923pub struct ArtifactDeleteParams {
2924    pub thread_id: ThreadId,
2925    pub artifact_id: String,
2926}
2927
2928#[derive(Debug, Clone, Serialize, Deserialize)]
2929#[serde(rename_all = "camelCase")]
2930pub struct ArtifactDeleteResult {
2931    pub deleted: bool,
2932}
2933
2934#[derive(Debug, Clone, Serialize, Deserialize)]
2935#[serde(rename_all = "camelCase")]
2936pub struct DiscoveryGroupsParams {
2937    #[serde(default, skip_serializing_if = "Option::is_none")]
2938    pub refresh: Option<bool>,
2939    #[serde(default, skip_serializing_if = "Option::is_none")]
2940    pub limit: Option<usize>,
2941}
2942
2943#[derive(Debug, Clone, Serialize, Deserialize)]
2944#[serde(rename_all = "camelCase")]
2945pub struct DiscoveryGroupsResult {
2946    pub catalog_id: String,
2947    pub title: String,
2948    pub hidden_item_count: u64,
2949    pub groups: Vec<DiscoveryCatalogGroup>,
2950}
2951
2952#[derive(Debug, Clone, Serialize, Deserialize)]
2953#[serde(rename_all = "camelCase")]
2954pub struct DiscoverySearchParams {
2955    pub query: String,
2956    #[serde(default, skip_serializing_if = "Option::is_none")]
2957    pub refresh: Option<bool>,
2958    #[serde(default, skip_serializing_if = "Option::is_none")]
2959    pub limit: Option<usize>,
2960}
2961
2962#[derive(Debug, Clone, Serialize, Deserialize)]
2963#[serde(rename_all = "camelCase")]
2964pub struct DiscoverySearchResult {
2965    pub query: String,
2966    pub items: Vec<DiscoveryCatalogItem>,
2967}
2968
2969#[derive(Debug, Clone, Serialize, Deserialize)]
2970#[serde(rename_all = "camelCase")]
2971pub struct DiscoveryReadParams {
2972    pub item_id: String,
2973    #[serde(default, skip_serializing_if = "Option::is_none")]
2974    pub refresh: Option<bool>,
2975    #[serde(default, skip_serializing_if = "Option::is_none")]
2976    pub start_line: Option<usize>,
2977    #[serde(default, skip_serializing_if = "Option::is_none")]
2978    pub limit: Option<usize>,
2979    #[serde(default, skip_serializing_if = "Option::is_none")]
2980    pub promote: Option<bool>,
2981    #[serde(default, skip_serializing_if = "Option::is_none")]
2982    pub thread_id: Option<ThreadId>,
2983    #[serde(default, skip_serializing_if = "Option::is_none")]
2984    pub turn_id: Option<TurnId>,
2985}
2986
2987#[derive(Debug, Clone, Serialize, Deserialize)]
2988#[serde(rename_all = "camelCase")]
2989pub struct DiscoveryReadPage {
2990    pub text: String,
2991    pub start_line: usize,
2992    pub end_line: usize,
2993    pub total_lines: usize,
2994    pub truncated: bool,
2995}
2996
2997#[derive(Debug, Clone, Serialize, Deserialize)]
2998#[serde(rename_all = "camelCase")]
2999pub struct DiscoveryReadResult {
3000    pub item: DiscoveryCatalogItem,
3001    pub page: DiscoveryReadPage,
3002    pub promoted: bool,
3003}
3004
3005#[derive(Debug, Clone, Serialize, Deserialize)]
3006#[serde(rename_all = "camelCase")]
3007pub struct DiscoveryRefreshResult {
3008    pub catalog: DiscoveryCatalog,
3009    pub catalog_root: String,
3010    pub promotion_state_dir: String,
3011    pub written_files: Vec<String>,
3012}
3013
3014#[derive(Debug, Clone, Serialize, Deserialize)]
3015#[serde(rename_all = "camelCase")]
3016pub struct DiscoveryPromoteParams {
3017    pub item_id: String,
3018    pub thread_id: ThreadId,
3019    #[serde(default, skip_serializing_if = "Option::is_none")]
3020    pub turn_id: Option<TurnId>,
3021}
3022
3023#[derive(Debug, Clone, Serialize, Deserialize)]
3024#[serde(rename_all = "camelCase")]
3025pub struct DiscoveryPromoteResult {
3026    pub record: DiscoveryPromotionRecord,
3027}
3028
3029#[derive(Debug, Clone, Serialize, Deserialize)]
3030#[serde(rename_all = "camelCase")]
3031pub struct DiscoveryPromotedListParams {
3032    #[serde(default, skip_serializing_if = "Option::is_none")]
3033    pub thread_id: Option<ThreadId>,
3034}
3035
3036#[derive(Debug, Clone, Serialize, Deserialize)]
3037#[serde(rename_all = "camelCase")]
3038pub struct DiscoveryPromotedListResult {
3039    pub records: Vec<DiscoveryPromotionRecord>,
3040}
3041
3042#[derive(Debug, Clone, Serialize, Deserialize)]
3043#[serde(rename_all = "camelCase")]
3044pub struct DiscoveryPromotedClearParams {
3045    #[serde(default, skip_serializing_if = "Option::is_none")]
3046    pub thread_id: Option<ThreadId>,
3047    #[serde(default, skip_serializing_if = "Option::is_none")]
3048    pub item_id: Option<String>,
3049}
3050
3051#[derive(Debug, Clone, Serialize, Deserialize)]
3052#[serde(rename_all = "camelCase")]
3053pub struct DiscoveryPromotedClearResult {
3054    pub cleared: usize,
3055}
3056
3057#[derive(Debug, Clone, Serialize, Deserialize)]
3058#[serde(rename_all = "camelCase")]
3059pub struct RetrievalTurnParams {
3060    pub thread_id: ThreadId,
3061    pub turn_id: TurnId,
3062    #[serde(default, skip_serializing_if = "Option::is_none")]
3063    pub limit: Option<usize>,
3064}
3065
3066#[derive(Debug, Clone, Serialize, Deserialize)]
3067#[serde(rename_all = "camelCase")]
3068pub struct RetrievalDebugSummary {
3069    pub text: String,
3070    #[serde(default)]
3071    pub notes: Vec<String>,
3072    pub truncated: bool,
3073}
3074
3075#[derive(Debug, Clone, Serialize, Deserialize)]
3076#[serde(rename_all = "camelCase")]
3077pub struct RetrievalRecommendationsResult {
3078    pub thread_id: ThreadId,
3079    pub turn_id: TurnId,
3080    pub plans: Vec<RetrievalRoutePlan>,
3081    pub summary: RetrievalDebugSummary,
3082}
3083
3084#[derive(Debug, Clone, Serialize, Deserialize)]
3085#[serde(rename_all = "camelCase")]
3086pub struct RetrievalMetricsResult {
3087    pub thread_id: ThreadId,
3088    pub turn_id: TurnId,
3089    pub outcomes: Vec<RetrievalMeasuredOutcome>,
3090    pub accepted_count: u64,
3091    pub ignored_count: u64,
3092    pub failed_count: u64,
3093    pub outcome_counts: BTreeMap<String, u64>,
3094    pub mode_counts: BTreeMap<RetrievalMode, u64>,
3095    pub summary: RetrievalDebugSummary,
3096}
3097
3098#[derive(Debug, Clone, Serialize, Deserialize)]
3099#[serde(rename_all = "camelCase")]
3100pub struct InferenceRoutingMetricsParams {
3101    pub thread_id: ThreadId,
3102    pub turn_id: TurnId,
3103    #[serde(default, skip_serializing_if = "Option::is_none")]
3104    pub limit: Option<usize>,
3105}
3106
3107#[derive(Debug, Clone, Serialize, Deserialize)]
3108#[serde(rename_all = "camelCase")]
3109pub struct InferenceRoutingStatusParams {
3110    pub thread_id: ThreadId,
3111    #[serde(default, skip_serializing_if = "Option::is_none")]
3112    pub turn_id: Option<TurnId>,
3113}
3114
3115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3116#[serde(rename_all = "camelCase")]
3117pub struct InferenceRoutingDecisionEvent {
3118    pub thread_id: ThreadId,
3119    pub turn_id: TurnId,
3120    #[serde(default)]
3121    pub round_index: u32,
3122    pub default_selection: ModelSelection,
3123    pub selected_selection: ModelSelection,
3124    pub decision: InferenceRoutingDecision,
3125    #[serde(with = "time::serde::rfc3339")]
3126    pub timestamp: OffsetDateTime,
3127}
3128
3129impl From<roder_api::events::InferenceRoutingDecisionEvent> for InferenceRoutingDecisionEvent {
3130    fn from(event: roder_api::events::InferenceRoutingDecisionEvent) -> Self {
3131        Self {
3132            thread_id: event.thread_id,
3133            turn_id: event.turn_id,
3134            round_index: event.round_index,
3135            default_selection: event.default_selection,
3136            selected_selection: event.selected_selection,
3137            decision: event.decision,
3138            timestamp: event.timestamp,
3139        }
3140    }
3141}
3142
3143#[derive(Debug, Clone, Serialize, Deserialize)]
3144#[serde(rename_all = "camelCase")]
3145pub struct InferenceRoutingStatusResult {
3146    pub thread_id: ThreadId,
3147    #[serde(default, skip_serializing_if = "Option::is_none")]
3148    pub turn_id: Option<TurnId>,
3149    pub active: bool,
3150    pub decision_count: u64,
3151    #[serde(default, skip_serializing_if = "Option::is_none")]
3152    pub router_id: Option<String>,
3153    #[serde(default, skip_serializing_if = "Option::is_none")]
3154    pub latest_outcome: Option<InferenceRoutingOutcome>,
3155    #[serde(default, skip_serializing_if = "Option::is_none")]
3156    pub default_selection: Option<ModelSelection>,
3157    #[serde(default, skip_serializing_if = "Option::is_none")]
3158    pub selected_selection: Option<ModelSelection>,
3159    #[serde(default, skip_serializing_if = "Option::is_none")]
3160    pub latest_decision: Option<InferenceRoutingDecisionEvent>,
3161    pub summary: RetrievalDebugSummary,
3162}
3163
3164#[derive(Debug, Clone, Serialize, Deserialize)]
3165#[serde(rename_all = "camelCase")]
3166pub struct InferenceRoutingCostSummary {
3167    pub selected_estimated_cost_usd: f64,
3168    pub baseline_estimated_cost_usd: f64,
3169    pub estimated_savings_usd: f64,
3170    #[serde(default, skip_serializing_if = "Option::is_none")]
3171    pub classifier_overhead_usd: Option<f64>,
3172    pub incomplete_estimate_count: u64,
3173    pub priced_decision_count: u64,
3174}
3175
3176#[derive(Debug, Clone, Serialize, Deserialize)]
3177#[serde(rename_all = "camelCase")]
3178pub struct InferenceRoutingRegretSummary {
3179    pub retry_count: u64,
3180    pub failure_count: u64,
3181    pub turn_failed: bool,
3182    pub escalation_count: u64,
3183    pub fallback_count: u64,
3184}
3185
3186#[derive(Debug, Clone, Serialize, Deserialize)]
3187#[serde(rename_all = "camelCase")]
3188pub struct InferenceRoutingMetricsResult {
3189    pub thread_id: ThreadId,
3190    pub turn_id: TurnId,
3191    pub decisions: Vec<InferenceRoutingDecisionEvent>,
3192    pub decision_count: u64,
3193    pub outcome_counts: BTreeMap<String, u64>,
3194    pub cost: InferenceRoutingCostSummary,
3195    pub regret: InferenceRoutingRegretSummary,
3196    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3197    pub cost_deltas: Vec<InferenceRoutingCostDelta>,
3198    pub summary: RetrievalDebugSummary,
3199}
3200
3201#[derive(Debug, Clone, Serialize, Deserialize)]
3202#[serde(rename_all = "camelCase")]
3203pub struct RetrievalPromotedCapabilityState {
3204    pub item_id: String,
3205    #[serde(default, skip_serializing_if = "Option::is_none")]
3206    pub route_id: Option<String>,
3207    pub state: String,
3208    #[serde(default, skip_serializing_if = "Option::is_none")]
3209    pub cache_status: Option<String>,
3210    #[serde(default, skip_serializing_if = "Option::is_none")]
3211    pub reason: Option<String>,
3212    pub thread_id: ThreadId,
3213    #[serde(default, skip_serializing_if = "Option::is_none")]
3214    pub turn_id: Option<TurnId>,
3215    #[serde(with = "time::serde::rfc3339")]
3216    pub timestamp: OffsetDateTime,
3217}
3218
3219#[derive(Debug, Clone, Serialize, Deserialize)]
3220#[serde(rename_all = "camelCase")]
3221pub struct RetrievalPromotedResult {
3222    pub thread_id: ThreadId,
3223    pub turn_id: TurnId,
3224    pub states: Vec<RetrievalPromotedCapabilityState>,
3225    pub summary: RetrievalDebugSummary,
3226}
3227
3228#[derive(Debug, Clone, Serialize, Deserialize)]
3229#[serde(rename_all = "camelCase")]
3230pub struct MediaReadParams {
3231    pub artifact_id: MediaArtifactId,
3232    #[serde(default, skip_serializing_if = "Option::is_none")]
3233    pub max_bytes: Option<u64>,
3234}
3235
3236#[derive(Debug, Clone, Serialize, Deserialize)]
3237#[serde(rename_all = "camelCase")]
3238pub struct MediaReadResult {
3239    pub artifact: MediaArtifact,
3240    pub bytes_base64: String,
3241}
3242
3243#[derive(Debug, Clone, Serialize, Deserialize)]
3244#[serde(rename_all = "camelCase")]
3245pub struct MediaThumbnailParams {
3246    pub artifact_id: MediaArtifactId,
3247}
3248
3249#[derive(Debug, Clone, Serialize, Deserialize)]
3250#[serde(rename_all = "camelCase")]
3251pub struct MediaThumbnailResult {
3252    pub preview: MediaPreview,
3253}
3254
3255#[derive(Debug, Clone, Serialize, Deserialize)]
3256#[serde(rename_all = "camelCase")]
3257pub struct MediaDeleteParams {
3258    pub artifact_id: MediaArtifactId,
3259}
3260
3261#[derive(Debug, Clone, Serialize, Deserialize)]
3262#[serde(rename_all = "camelCase")]
3263pub struct MediaDeleteResult {
3264    pub deleted: bool,
3265}
3266
3267#[derive(Debug, Clone, Serialize, Deserialize)]
3268#[serde(rename_all = "camelCase")]
3269pub struct MediaAttachToTurnParams {
3270    pub artifact_id: MediaArtifactId,
3271}
3272
3273#[derive(Debug, Clone, Serialize, Deserialize)]
3274#[serde(rename_all = "camelCase")]
3275pub struct MediaAttachToTurnResult {
3276    pub attachment: MediaAttachment,
3277    pub image: Option<InputImage>,
3278}
3279
3280#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3281#[serde(rename_all = "camelCase")]
3282pub struct MediaImageProvidersListParams {}
3283
3284#[derive(Debug, Clone, Serialize, Deserialize)]
3285#[serde(rename_all = "camelCase")]
3286pub struct MediaImageProvidersListResult {
3287    pub default_provider: String,
3288    pub providers: Vec<roder_api::media::MediaProviderDescriptor>,
3289}
3290
3291#[derive(Debug, Clone, Serialize, Deserialize)]
3292#[serde(rename_all = "camelCase")]
3293pub struct MediaImageGenerateParams {
3294    /// Canonical provider-neutral image generation request.
3295    #[serde(flatten)]
3296    pub request: roder_api::media::MediaGenerationRequest,
3297    /// Optional thread to associate emitted media events with.
3298    #[serde(default, skip_serializing_if = "Option::is_none")]
3299    pub thread_id: Option<ThreadId>,
3300}
3301
3302#[derive(Debug, Clone, Serialize, Deserialize)]
3303#[serde(rename_all = "camelCase")]
3304pub struct MediaImageGenerateResult {
3305    pub response: roder_api::media::MediaGenerationResponse,
3306}
3307
3308#[derive(Debug, Clone, Serialize, Deserialize)]
3309#[serde(rename_all = "camelCase")]
3310pub struct WorkflowScanParams {
3311    pub workspace: Option<String>,
3312    #[serde(default)]
3313    pub include_user: bool,
3314}
3315
3316#[derive(Debug, Clone, Serialize, Deserialize)]
3317#[serde(rename_all = "camelCase")]
3318pub struct WorkflowScanResult {
3319    pub scan: WorkflowImportScan,
3320}
3321
3322#[derive(Debug, Clone, Serialize, Deserialize)]
3323#[serde(rename_all = "camelCase")]
3324pub struct WorkflowPreviewParams {
3325    pub workspace: Option<String>,
3326    #[serde(default, skip_serializing_if = "Option::is_none")]
3327    pub item_id: Option<String>,
3328}
3329
3330#[derive(Debug, Clone, Serialize, Deserialize)]
3331#[serde(rename_all = "camelCase")]
3332pub struct WorkflowPreviewResult {
3333    pub items: Vec<WorkflowImportItem>,
3334}
3335
3336#[derive(Debug, Clone, Serialize, Deserialize)]
3337#[serde(rename_all = "camelCase")]
3338pub struct WorkflowEnableParams {
3339    pub workspace: Option<String>,
3340    pub item_id: String,
3341    #[serde(default)]
3342    pub approve_side_effects: bool,
3343}
3344
3345#[derive(Debug, Clone, Serialize, Deserialize)]
3346#[serde(rename_all = "camelCase")]
3347pub struct WorkflowEnableResult {
3348    pub item: WorkflowImportItem,
3349    pub decision: WorkflowImportDecision,
3350}
3351
3352#[derive(Debug, Clone, Serialize, Deserialize)]
3353#[serde(rename_all = "camelCase")]
3354pub struct WorkflowIgnoreParams {
3355    pub workspace: Option<String>,
3356    pub item_id: String,
3357}
3358
3359#[derive(Debug, Clone, Serialize, Deserialize)]
3360#[serde(rename_all = "camelCase")]
3361pub struct WorkflowIgnoreResult {
3362    pub item_id: String,
3363    pub decision: WorkflowImportDecision,
3364}
3365
3366#[derive(Debug, Clone, Serialize, Deserialize)]
3367#[serde(rename_all = "camelCase")]
3368pub struct WorkflowRefreshParams {
3369    pub workspace: Option<String>,
3370}
3371
3372#[derive(Debug, Clone, Serialize, Deserialize)]
3373#[serde(rename_all = "camelCase")]
3374pub struct WorkflowRefreshResult {
3375    pub scan: WorkflowImportScan,
3376    pub stale: Vec<WorkflowImportItem>,
3377}
3378
3379#[derive(Debug, Clone, Serialize, Deserialize)]
3380#[serde(rename_all = "camelCase")]
3381pub struct WorkflowRemoveParams {
3382    pub workspace: Option<String>,
3383    pub item_id: String,
3384}
3385
3386#[derive(Debug, Clone, Serialize, Deserialize)]
3387#[serde(rename_all = "camelCase")]
3388pub struct WorkflowRemoveResult {
3389    pub item_id: String,
3390    pub state: WorkflowImportState,
3391    pub decision: WorkflowImportDecision,
3392}
3393
3394#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3395#[serde(rename_all = "camelCase")]
3396pub struct EvalReportsListParams {
3397    pub limit: Option<usize>,
3398}
3399
3400#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
3401#[serde(rename_all = "camelCase")]
3402pub struct EvalReliabilitySummary {
3403    #[serde(default)]
3404    pub error_class_counts: BTreeMap<String, u64>,
3405    pub retry_attempts: u64,
3406    pub retry_recoveries: u64,
3407    pub failure_limit_stops: u64,
3408    pub unknown_errors: u64,
3409}
3410
3411#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3412#[serde(rename_all = "camelCase")]
3413pub struct EvalReportSummary {
3414    pub id: String,
3415    pub suite_id: String,
3416    pub fixture_count: usize,
3417    pub passed: usize,
3418    pub failed: usize,
3419    #[serde(default)]
3420    pub reliability: EvalReliabilitySummary,
3421    #[serde(with = "time::serde::rfc3339")]
3422    pub generated_at: time::OffsetDateTime,
3423}
3424
3425#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3426#[serde(rename_all = "camelCase")]
3427pub struct EvalReportsListResult {
3428    pub reports: Vec<EvalReportSummary>,
3429}
3430
3431#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3432#[serde(rename_all = "camelCase")]
3433pub struct EvalReportReadParams {
3434    pub report_id: String,
3435    pub max_bytes: Option<usize>,
3436}
3437
3438#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3439#[serde(rename_all = "camelCase")]
3440pub struct EvalReportReadResult {
3441    pub summary: EvalReportSummary,
3442    pub markdown: String,
3443    pub truncated: bool,
3444}
3445
3446#[derive(Debug, Clone, Serialize, Deserialize)]
3447#[serde(rename_all = "camelCase")]
3448pub struct MarketplacesListResult {
3449    pub marketplaces: Vec<MarketplaceDescriptor>,
3450}
3451
3452#[derive(Debug, Clone, Serialize, Deserialize)]
3453#[serde(rename_all = "camelCase")]
3454pub struct MarketplacesInstallDefaultParams {
3455    pub selection: DefaultMarketplaceSelection,
3456}
3457
3458#[derive(Debug, Clone, Serialize, Deserialize)]
3459#[serde(rename_all = "camelCase")]
3460pub struct MarketplacesInstallDefaultResult {
3461    pub marketplaces: Vec<MarketplaceDescriptor>,
3462}
3463
3464#[derive(Debug, Clone, Serialize, Deserialize)]
3465#[serde(rename_all = "camelCase")]
3466pub struct MarketplacesAddParams {
3467    pub id: String,
3468    #[serde(default, skip_serializing_if = "Option::is_none")]
3469    pub kind: Option<MarketplaceKind>,
3470    pub display_name: String,
3471    pub source: MarketplaceSource,
3472}
3473
3474#[derive(Debug, Clone, Serialize, Deserialize)]
3475#[serde(rename_all = "camelCase")]
3476pub struct MarketplacesAddResult {
3477    pub marketplace: MarketplaceDescriptor,
3478}
3479
3480#[derive(Debug, Clone, Serialize, Deserialize)]
3481#[serde(rename_all = "camelCase")]
3482pub struct MarketplacesRemoveParams {
3483    pub marketplace_id: String,
3484}
3485
3486#[derive(Debug, Clone, Serialize, Deserialize)]
3487#[serde(rename_all = "camelCase")]
3488pub struct MarketplacesRemoveResult {
3489    pub removed: bool,
3490}
3491
3492#[derive(Debug, Clone, Serialize, Deserialize)]
3493#[serde(rename_all = "camelCase")]
3494pub struct MarketplacesRefreshParams {
3495    pub marketplace_id: String,
3496}
3497
3498#[derive(Debug, Clone, Serialize, Deserialize)]
3499#[serde(rename_all = "camelCase")]
3500pub struct MarketplacesRefreshResult {
3501    pub marketplace: MarketplaceDescriptor,
3502    pub plugins: Vec<MarketplacePluginEntry>,
3503}
3504
3505#[derive(Debug, Clone, Serialize, Deserialize)]
3506#[serde(rename_all = "camelCase")]
3507pub struct MarketplacesSearchParams {
3508    #[serde(default, skip_serializing_if = "Option::is_none")]
3509    pub query: Option<String>,
3510}
3511
3512#[derive(Debug, Clone, Serialize, Deserialize)]
3513#[serde(rename_all = "camelCase")]
3514pub struct MarketplacesSearchResult {
3515    pub plugins: Vec<DedupedMarketplacePlugin>,
3516}
3517
3518#[derive(Debug, Clone, Serialize, Deserialize)]
3519#[serde(rename_all = "camelCase")]
3520pub struct MarketplacePluginParams {
3521    pub marketplace_id: String,
3522    pub plugin_id: String,
3523}
3524
3525#[derive(Debug, Clone, Serialize, Deserialize)]
3526#[serde(rename_all = "camelCase")]
3527pub struct MarketplacePluginResult {
3528    pub plugin: Option<MarketplacePluginEntry>,
3529}
3530
3531#[derive(Debug, Clone, Serialize, Deserialize)]
3532#[serde(rename_all = "camelCase")]
3533pub struct PluginPreviewInstallParams {
3534    pub marketplace_id: String,
3535    pub plugin_id: String,
3536}
3537
3538#[derive(Debug, Clone, Serialize, Deserialize)]
3539#[serde(rename_all = "camelCase")]
3540pub struct PluginPreviewInstallResult {
3541    pub preview: serde_json::Value,
3542}
3543
3544#[derive(Debug, Clone, Serialize, Deserialize)]
3545#[serde(rename_all = "camelCase")]
3546pub struct PluginInstallParams {
3547    pub marketplace_id: String,
3548    pub plugin_id: String,
3549}
3550
3551#[derive(Debug, Clone, Serialize, Deserialize)]
3552#[serde(rename_all = "camelCase")]
3553pub struct PluginInstallResult {
3554    pub plugin: InstalledPluginRecord,
3555}
3556
3557#[derive(Debug, Clone, Serialize, Deserialize)]
3558#[serde(rename_all = "camelCase")]
3559pub struct PluginInstallAllVariantsParams {
3560    pub marketplace_id: String,
3561    pub plugin_id: String,
3562}
3563
3564#[derive(Debug, Clone, Serialize, Deserialize)]
3565#[serde(rename_all = "camelCase")]
3566pub struct PluginInstallAllVariantsResult {
3567    pub plugins: Vec<InstalledPluginRecord>,
3568}
3569
3570#[derive(Debug, Clone, Serialize, Deserialize)]
3571#[serde(rename_all = "camelCase")]
3572pub struct PluginListInstalledResult {
3573    pub plugins: Vec<InstalledPluginRecord>,
3574}
3575
3576#[derive(Debug, Clone, Serialize, Deserialize)]
3577#[serde(rename_all = "camelCase")]
3578pub struct PluginDisableParams {
3579    pub variant_key: String,
3580}
3581
3582#[derive(Debug, Clone, Serialize, Deserialize)]
3583#[serde(rename_all = "camelCase")]
3584pub struct PluginDisableResult {
3585    pub plugin: Option<InstalledPluginRecord>,
3586}
3587
3588#[derive(Debug, Clone, Serialize, Deserialize)]
3589#[serde(rename_all = "camelCase")]
3590pub struct PluginUninstallParams {
3591    pub variant_key: String,
3592}
3593
3594#[derive(Debug, Clone, Serialize, Deserialize)]
3595#[serde(rename_all = "camelCase")]
3596pub struct PluginUninstallResult {
3597    pub removed: bool,
3598}
3599
3600/// One resource of an installed package, with its registry-facing id
3601/// (`<package-id>:<kind>/<name>`).
3602#[derive(Debug, Clone, Serialize, Deserialize)]
3603#[serde(rename_all = "camelCase")]
3604pub struct PackageResourceDescriptor {
3605    #[serde(flatten)]
3606    pub resource: PackageResource,
3607    pub id: String,
3608}
3609
3610impl From<PackageResource> for PackageResourceDescriptor {
3611    fn from(resource: PackageResource) -> Self {
3612        let id = resource.id();
3613        Self { resource, id }
3614    }
3615}
3616
3617/// Installed package record plus its enumerated resources.
3618#[derive(Debug, Clone, Serialize, Deserialize)]
3619#[serde(rename_all = "camelCase")]
3620pub struct PackageDescriptor {
3621    #[serde(flatten)]
3622    pub record: PackageRecord,
3623    pub shadowed_by_project: bool,
3624    pub resources: Vec<PackageResourceDescriptor>,
3625}
3626
3627#[derive(Debug, Clone, Serialize, Deserialize)]
3628#[serde(rename_all = "camelCase")]
3629pub struct PackagesListResult {
3630    pub packages: Vec<PackageDescriptor>,
3631    pub diagnostics: Vec<String>,
3632}
3633
3634#[derive(Debug, Clone, Serialize, Deserialize)]
3635#[serde(rename_all = "camelCase")]
3636pub struct PackagesInstallParams {
3637    pub spec: String,
3638    pub scope: PackageScope,
3639    #[serde(default, skip_serializing_if = "Option::is_none")]
3640    pub allow_scripts: Option<bool>,
3641}
3642
3643#[derive(Debug, Clone, Serialize, Deserialize)]
3644#[serde(rename_all = "camelCase")]
3645pub struct PackagesInstallResult {
3646    pub package: PackageDescriptor,
3647    pub diagnostics: Vec<String>,
3648}
3649
3650#[derive(Debug, Clone, Serialize, Deserialize)]
3651#[serde(rename_all = "camelCase")]
3652pub struct PackagesRemoveParams {
3653    pub spec_or_id: String,
3654    #[serde(default, skip_serializing_if = "Option::is_none")]
3655    pub scope: Option<PackageScope>,
3656}
3657
3658#[derive(Debug, Clone, Serialize, Deserialize)]
3659#[serde(rename_all = "camelCase")]
3660pub struct PackagesRemoveResult {
3661    pub removed: PackageRecord,
3662}
3663
3664#[derive(Debug, Clone, Serialize, Deserialize)]
3665#[serde(rename_all = "camelCase")]
3666pub struct PackagesUpdateParams {
3667    #[serde(default, skip_serializing_if = "Option::is_none")]
3668    pub target: Option<String>,
3669}
3670
3671#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
3672#[serde(rename_all = "camelCase")]
3673pub enum PackageUpdateStatus {
3674    Updated,
3675    SkippedPinned,
3676    Failed,
3677}
3678
3679#[derive(Debug, Clone, Serialize, Deserialize)]
3680#[serde(rename_all = "camelCase")]
3681pub struct PackageUpdateOutcome {
3682    pub package_id: String,
3683    pub identity: String,
3684    pub scope: PackageScope,
3685    pub status: PackageUpdateStatus,
3686    #[serde(default, skip_serializing_if = "Option::is_none")]
3687    pub resolved: Option<String>,
3688    #[serde(default, skip_serializing_if = "Option::is_none")]
3689    pub message: Option<String>,
3690}
3691
3692#[derive(Debug, Clone, Serialize, Deserialize)]
3693#[serde(rename_all = "camelCase")]
3694pub struct PackagesUpdateResult {
3695    pub outcomes: Vec<PackageUpdateOutcome>,
3696}
3697
3698#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
3699#[serde(rename_all = "camelCase")]
3700pub enum PackageSyncStatus {
3701    Materialized,
3702    AlreadyPresent,
3703    Failed,
3704}
3705
3706#[derive(Debug, Clone, Serialize, Deserialize)]
3707#[serde(rename_all = "camelCase")]
3708pub struct PackageSyncOutcome {
3709    pub package_id: String,
3710    pub identity: String,
3711    pub status: PackageSyncStatus,
3712    #[serde(default, skip_serializing_if = "Option::is_none")]
3713    pub resolved: Option<String>,
3714    #[serde(default, skip_serializing_if = "Option::is_none")]
3715    pub message: Option<String>,
3716}
3717
3718#[derive(Debug, Clone, Serialize, Deserialize)]
3719#[serde(rename_all = "camelCase")]
3720pub struct PackagesSyncResult {
3721    pub outcomes: Vec<PackageSyncOutcome>,
3722}
3723
3724#[derive(Debug, Clone, Serialize, Deserialize)]
3725#[serde(rename_all = "camelCase")]
3726pub struct PackagesSetEnabledParams {
3727    /// Package id or resource id (`<package-id>:<kind>/<name>`).
3728    pub id: String,
3729    pub enabled: bool,
3730}
3731
3732#[derive(Debug, Clone, Serialize, Deserialize)]
3733#[serde(rename_all = "camelCase")]
3734pub struct PackagesSetEnabledResult {
3735    pub package: PackageDescriptor,
3736}
3737
3738#[derive(Debug, Clone, Serialize, Deserialize)]
3739#[serde(rename_all = "camelCase")]
3740pub struct PackagesApproveExtensionsParams {
3741    pub package_id: String,
3742    pub approved: bool,
3743}
3744
3745#[derive(Debug, Clone, Serialize, Deserialize)]
3746#[serde(rename_all = "camelCase")]
3747pub struct PackagesApproveExtensionsResult {
3748    pub package: PackageDescriptor,
3749}
3750
3751#[derive(Debug, Clone, Serialize, Deserialize)]
3752#[serde(rename_all = "camelCase")]
3753pub struct PackagesSetFiltersParams {
3754    pub package_id: String,
3755    pub filters: PackageResourceFilters,
3756}
3757
3758#[derive(Debug, Clone, Serialize, Deserialize)]
3759#[serde(rename_all = "camelCase")]
3760pub struct PackagesSetFiltersResult {
3761    pub package: PackageDescriptor,
3762}
3763
3764#[derive(Debug, Clone, Serialize, Deserialize)]
3765pub struct ProviderSelectParams {
3766    pub provider: String,
3767    pub model: Option<String>,
3768    pub reasoning: Option<String>,
3769    #[serde(default, skip_serializing_if = "Option::is_none")]
3770    pub thread_id: Option<ThreadId>,
3771}
3772
3773#[derive(Debug, Clone, Serialize, Deserialize)]
3774#[serde(rename_all = "camelCase")]
3775pub struct ProviderSelectResult {
3776    pub provider: String,
3777    pub model: String,
3778    pub reasoning: String,
3779    #[serde(default, skip_serializing_if = "Option::is_none")]
3780    pub model_profile: Option<String>,
3781    #[serde(default, skip_serializing_if = "Option::is_none")]
3782    pub model_switch_summary: Option<String>,
3783}
3784
3785#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3786#[serde(tag = "type", rename_all = "camelCase")]
3787pub enum ModelSelectChoice {
3788    Manual {
3789        provider: String,
3790        #[serde(default, skip_serializing_if = "Option::is_none")]
3791        model: Option<String>,
3792        #[serde(default, skip_serializing_if = "Option::is_none")]
3793        reasoning: Option<String>,
3794    },
3795    Auto {
3796        option_id: String,
3797    },
3798}
3799
3800#[derive(Debug, Clone, Serialize, Deserialize)]
3801#[serde(rename_all = "camelCase")]
3802pub struct ModelSelectParams {
3803    pub selection: ModelSelectChoice,
3804    #[serde(default, skip_serializing_if = "Option::is_none")]
3805    pub thread_id: Option<ThreadId>,
3806}
3807
3808#[derive(Debug, Clone, Serialize, Deserialize)]
3809#[serde(rename_all = "camelCase")]
3810pub struct ModelSelectResult {
3811    pub selection_mode: ModelSelectionMode,
3812    pub provider: String,
3813    pub model: String,
3814    pub reasoning: String,
3815    #[serde(default, skip_serializing_if = "Option::is_none")]
3816    pub model_profile: Option<String>,
3817    #[serde(default, skip_serializing_if = "Option::is_none")]
3818    pub model_switch_summary: Option<String>,
3819}
3820
3821#[derive(Debug, Clone, Serialize, Deserialize)]
3822pub struct SettingsGetResult {
3823    pub web_search: WebSearchSettings,
3824    pub search_index: SearchIndexSettings,
3825    pub shell: ShellSettings,
3826    pub default_provider: String,
3827    pub default_model: String,
3828    pub default_reasoning: String,
3829    pub default_mode: PolicyMode,
3830    pub file_backed_dynamic_context: bool,
3831}
3832
3833#[derive(Debug, Clone, Serialize, Deserialize)]
3834pub struct SettingsSetWebSearchParams {
3835    pub mode: HostedWebSearchMode,
3836}
3837
3838#[derive(Debug, Clone, Serialize, Deserialize)]
3839pub struct SettingsSetWebSearchResult {
3840    pub web_search: WebSearchSettings,
3841}
3842
3843#[derive(Debug, Clone, Serialize, Deserialize)]
3844pub struct SettingsSetSearchIndexParams {
3845    pub enabled: bool,
3846}
3847
3848#[derive(Debug, Clone, Serialize, Deserialize)]
3849pub struct SettingsSetSearchIndexResult {
3850    pub search_index: SearchIndexSettings,
3851}
3852
3853#[derive(Debug, Clone, Serialize, Deserialize)]
3854pub struct SettingsSetShellParams {
3855    pub shell: String,
3856}
3857
3858#[derive(Debug, Clone, Serialize, Deserialize)]
3859pub struct SettingsSetShellResult {
3860    pub shell: ShellSettings,
3861}
3862
3863#[derive(Debug, Clone, Serialize, Deserialize)]
3864pub struct SettingsSetDefaultModeParams {
3865    pub mode: PolicyMode,
3866}
3867
3868#[derive(Debug, Clone, Serialize, Deserialize)]
3869pub struct SettingsSetDefaultModeResult {
3870    pub default_mode: PolicyMode,
3871}
3872
3873#[derive(Debug, Clone, Serialize, Deserialize)]
3874pub struct SettingsSetFileBackedDynamicContextParams {
3875    pub enabled: bool,
3876}
3877
3878#[derive(Debug, Clone, Serialize, Deserialize)]
3879pub struct SettingsSetFileBackedDynamicContextResult {
3880    pub enabled: bool,
3881}
3882
3883#[derive(Debug, Clone, Serialize, Deserialize)]
3884pub struct ProviderAuthResult {
3885    pub signed_in: bool,
3886    pub account_id: Option<String>,
3887}
3888
3889#[derive(Debug, Clone, Serialize, Deserialize)]
3890#[serde(rename_all = "camelCase")]
3891pub struct ThreadStateResult {
3892    pub mode: PolicyMode,
3893    pub pending_plan_exit: Option<PendingPlanExitDescriptor>,
3894}
3895
3896#[derive(Debug, Clone, Serialize, Deserialize)]
3897#[serde(rename_all = "camelCase")]
3898pub struct PendingPlanExitDescriptor {
3899    pub thread_id: ThreadId,
3900    pub turn_id: TurnId,
3901    pub request_id: String,
3902    pub target_mode: PolicyMode,
3903    pub plan_summary: Option<String>,
3904    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3905    pub next_steps: Vec<String>,
3906    pub requested_at: OffsetDateTime,
3907    pub expires_at: Option<OffsetDateTime>,
3908}
3909
3910#[derive(Debug, Clone, Serialize, Deserialize)]
3911#[serde(rename_all = "camelCase")]
3912pub struct ThreadSetModeParams {
3913    pub mode: PolicyMode,
3914    pub reason: Option<String>,
3915}
3916
3917#[derive(Debug, Clone, Serialize, Deserialize)]
3918#[serde(rename_all = "camelCase")]
3919pub struct ThreadSetModeResult {
3920    pub mode: PolicyMode,
3921}
3922
3923#[derive(Debug, Clone, Serialize, Deserialize)]
3924#[serde(rename_all = "camelCase")]
3925pub struct ThreadExitPlanParams {
3926    pub request_id: String,
3927    pub approved: bool,
3928}
3929
3930#[derive(Debug, Clone, Serialize, Deserialize)]
3931#[serde(rename_all = "camelCase")]
3932pub struct ThreadExitPlanResult {
3933    pub resolved: bool,
3934    pub mode: PolicyMode,
3935}
3936
3937#[derive(Debug, Clone, Serialize, Deserialize)]
3938#[serde(rename_all = "camelCase")]
3939pub struct ThreadResolveApprovalParams {
3940    pub approval_id: String,
3941    pub approved: bool,
3942}
3943
3944#[derive(Debug, Clone, Serialize, Deserialize)]
3945#[serde(rename_all = "camelCase")]
3946pub struct ThreadResolveApprovalResult {
3947    pub resolved: bool,
3948}
3949
3950/// Completes a pending host-executed tool call published via `thread/toolExecutionRequested`.
3951#[derive(Debug, Clone, Serialize, Deserialize)]
3952#[serde(rename_all = "camelCase")]
3953pub struct ToolsResolveParams {
3954    pub request_id: String,
3955    pub output: String,
3956    #[serde(default)]
3957    pub is_error: bool,
3958}
3959
3960#[derive(Debug, Clone, Serialize, Deserialize)]
3961#[serde(rename_all = "camelCase")]
3962pub struct ToolsResolveResult {
3963    pub resolved: bool,
3964}
3965
3966#[derive(Debug, Clone, Serialize, Deserialize)]
3967#[serde(rename_all = "camelCase")]
3968pub struct ThreadResolveUserInputParams {
3969    pub request_id: String,
3970    pub answers: serde_json::Value,
3971}
3972
3973#[derive(Debug, Clone, Serialize, Deserialize)]
3974#[serde(rename_all = "camelCase")]
3975pub struct ThreadResolveUserInputResult {
3976    pub resolved: bool,
3977}
3978
3979#[derive(Debug, Clone, Serialize, Deserialize)]
3980pub struct CommandDescriptor {
3981    pub name: String,
3982    pub description: Option<String>,
3983    pub argument_hint: Option<String>,
3984    pub source: String,
3985    pub model: Option<String>,
3986    pub agent: Option<String>,
3987    pub has_shell_includes: bool,
3988    pub has_url_includes: bool,
3989}
3990
3991#[derive(Debug, Clone, Serialize, Deserialize)]
3992pub struct CommandsListResult {
3993    pub commands: Vec<CommandDescriptor>,
3994}
3995
3996#[derive(Debug, Clone, Serialize, Deserialize)]
3997pub struct CommandsExpandParams {
3998    pub name: String,
3999    #[serde(default)]
4000    pub arguments: String,
4001    pub workspace: Option<String>,
4002}
4003
4004#[derive(Debug, Clone, Serialize, Deserialize)]
4005pub struct CommandsExpandResult {
4006    pub command: CommandDescriptor,
4007    pub message: String,
4008    pub context_blocks: Vec<ContextBlock>,
4009    pub allowed_tools: Vec<String>,
4010    pub model: Option<String>,
4011    pub agent: Option<String>,
4012}
4013
4014#[derive(Debug, Clone, Serialize, Deserialize)]
4015pub struct CommandsRunParams {
4016    pub thread_id: ThreadId,
4017    pub name: String,
4018    #[serde(default)]
4019    pub arguments: String,
4020    pub workspace: Option<String>,
4021}
4022
4023#[derive(Debug, Clone, Serialize, Deserialize)]
4024pub struct CommandsRunResult {
4025    pub turn_id: TurnId,
4026    pub expanded: CommandsExpandResult,
4027}
4028
4029#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4030#[serde(rename_all = "camelCase")]
4031pub struct SkillsListParams {
4032    #[serde(default, skip_serializing_if = "Option::is_none")]
4033    pub workspace_id: Option<String>,
4034    #[serde(default, skip_serializing_if = "Option::is_none")]
4035    pub root_id: Option<String>,
4036    #[serde(default, skip_serializing_if = "Option::is_none")]
4037    pub cwd: Option<String>,
4038}
4039
4040#[derive(Debug, Clone, Serialize, Deserialize)]
4041#[serde(rename_all = "camelCase")]
4042pub struct SkillsListResult {
4043    pub skills: Vec<SkillDescriptor>,
4044    #[serde(default)]
4045    pub diagnostics: Vec<String>,
4046}
4047
4048#[derive(Debug, Clone, Serialize, Deserialize)]
4049#[serde(rename_all = "camelCase")]
4050pub struct SkillsReadParams {
4051    pub selector: SkillSelector,
4052}
4053
4054#[derive(Debug, Clone, Serialize, Deserialize)]
4055#[serde(rename_all = "camelCase")]
4056pub struct SkillsReadResult {
4057    pub skill: Option<Skill>,
4058}
4059
4060#[derive(Debug, Clone, Serialize, Deserialize)]
4061#[serde(rename_all = "camelCase")]
4062pub struct SkillsSetEnabledParams {
4063    pub selector: SkillSelector,
4064    pub enabled: bool,
4065}
4066
4067#[derive(Debug, Clone, Serialize, Deserialize)]
4068#[serde(rename_all = "camelCase")]
4069pub struct SkillsSetExposureParams {
4070    pub selector: SkillSelector,
4071    pub exposure: SkillExposure,
4072}
4073
4074#[derive(Debug, Clone, Serialize, Deserialize)]
4075#[serde(rename_all = "camelCase")]
4076pub struct SkillsUpdateResult {
4077    pub skills: Vec<SkillDescriptor>,
4078    #[serde(default)]
4079    pub diagnostics: Vec<String>,
4080}
4081
4082#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4083#[serde(rename_all = "camelCase")]
4084pub struct AutomationsListParams {
4085    #[serde(default, skip_serializing_if = "Option::is_none")]
4086    pub project_cwd: Option<String>,
4087    #[serde(default, skip_serializing_if = "Option::is_none")]
4088    pub include_disabled: Option<bool>,
4089}
4090
4091#[derive(Debug, Clone, Serialize, Deserialize)]
4092#[serde(rename_all = "camelCase")]
4093pub struct AutomationsListResult {
4094    pub automations: Vec<AutomationDefinition>,
4095}
4096
4097#[derive(Debug, Clone, Serialize, Deserialize)]
4098#[serde(rename_all = "camelCase")]
4099pub struct AutomationsCreateParams {
4100    pub name: String,
4101    pub project: AutomationProject,
4102    pub schedule: AutomationSchedule,
4103    pub prompt: String,
4104    #[serde(default = "default_true_bool")]
4105    pub enabled: bool,
4106    #[serde(default, skip_serializing_if = "Option::is_none")]
4107    pub model_provider: Option<String>,
4108    #[serde(default, skip_serializing_if = "Option::is_none")]
4109    pub model: Option<String>,
4110    #[serde(default, skip_serializing_if = "Option::is_none")]
4111    pub policy_mode: Option<roder_api::policy_mode::PolicyMode>,
4112    pub catch_up: CatchUpPolicy,
4113    pub concurrency: AutomationConcurrencyPolicy,
4114}
4115
4116fn default_true_bool() -> bool {
4117    true
4118}
4119
4120#[derive(Debug, Clone, Serialize, Deserialize)]
4121#[serde(rename_all = "camelCase")]
4122pub struct AutomationsCreateResult {
4123    pub automation: AutomationDefinition,
4124}
4125
4126#[derive(Debug, Clone, Serialize, Deserialize, Default)]
4127#[serde(rename_all = "camelCase")]
4128pub struct AutomationsUpdatePatch {
4129    #[serde(default, skip_serializing_if = "Option::is_none")]
4130    pub name: Option<String>,
4131    #[serde(default, skip_serializing_if = "Option::is_none")]
4132    pub project: Option<AutomationProject>,
4133    #[serde(default, skip_serializing_if = "Option::is_none")]
4134    pub schedule: Option<AutomationSchedule>,
4135    #[serde(default, skip_serializing_if = "Option::is_none")]
4136    pub prompt: Option<String>,
4137    #[serde(default, skip_serializing_if = "Option::is_none")]
4138    pub enabled: Option<bool>,
4139    #[serde(default, skip_serializing_if = "Option::is_none")]
4140    pub model_provider: Option<String>,
4141    #[serde(default, skip_serializing_if = "Option::is_none")]
4142    pub model: Option<String>,
4143    #[serde(default, skip_serializing_if = "Option::is_none")]
4144    pub policy_mode: Option<roder_api::policy_mode::PolicyMode>,
4145    #[serde(default, skip_serializing_if = "Option::is_none")]
4146    pub catch_up: Option<CatchUpPolicy>,
4147    #[serde(default, skip_serializing_if = "Option::is_none")]
4148    pub concurrency: Option<AutomationConcurrencyPolicy>,
4149}
4150
4151#[derive(Debug, Clone, Serialize, Deserialize)]
4152#[serde(rename_all = "camelCase")]
4153pub struct AutomationsUpdateParams {
4154    pub automation_id: AutomationId,
4155    pub patch: AutomationsUpdatePatch,
4156}
4157
4158#[derive(Debug, Clone, Serialize, Deserialize)]
4159#[serde(rename_all = "camelCase")]
4160pub struct AutomationsUpdateResult {
4161    pub automation: AutomationDefinition,
4162}
4163
4164#[derive(Debug, Clone, Serialize, Deserialize)]
4165#[serde(rename_all = "camelCase")]
4166pub struct AutomationsDeleteParams {
4167    pub automation_id: AutomationId,
4168}
4169
4170#[derive(Debug, Clone, Serialize, Deserialize)]
4171#[serde(rename_all = "camelCase")]
4172pub struct AutomationsDeleteResult {
4173    pub automation_id: AutomationId,
4174    pub deleted: bool,
4175}
4176
4177#[derive(Debug, Clone, Serialize, Deserialize)]
4178#[serde(rename_all = "camelCase")]
4179pub struct AutomationsRunNowParams {
4180    pub automation_id: AutomationId,
4181    #[serde(default, skip_serializing_if = "Option::is_none")]
4182    pub prompt_override: Option<String>,
4183}
4184
4185#[derive(Debug, Clone, Serialize, Deserialize)]
4186#[serde(rename_all = "camelCase")]
4187pub struct AutomationsRunNowResult {
4188    pub run: AutomationRunSummary,
4189}
4190
4191#[derive(Debug, Clone, Serialize, Deserialize)]
4192#[serde(rename_all = "camelCase")]
4193pub struct AutomationsRunsParams {
4194    pub automation_id: AutomationId,
4195    #[serde(default, skip_serializing_if = "Option::is_none")]
4196    pub state: Option<AutomationRunState>,
4197    #[serde(default, skip_serializing_if = "Option::is_none")]
4198    pub limit: Option<usize>,
4199}
4200
4201#[derive(Debug, Clone, Serialize, Deserialize)]
4202#[serde(rename_all = "camelCase")]
4203pub struct AutomationsRunsResult {
4204    pub runs: Vec<AutomationRunSummary>,
4205    #[serde(default, skip_serializing_if = "Option::is_none")]
4206    pub next_cursor: Option<String>,
4207}
4208
4209#[derive(Debug, Clone, Serialize, Deserialize)]
4210#[serde(rename_all = "camelCase")]
4211pub struct AutomationsCancelRunParams {
4212    pub run_id: AutomationRunId,
4213    #[serde(default, skip_serializing_if = "Option::is_none")]
4214    pub reason: Option<String>,
4215}
4216
4217#[derive(Debug, Clone, Serialize, Deserialize)]
4218#[serde(rename_all = "camelCase")]
4219pub struct AutomationsCancelRunResult {
4220    pub run_id: AutomationRunId,
4221    pub cancelled: bool,
4222}
4223
4224#[derive(Debug, Clone, Serialize, Deserialize)]
4225#[serde(rename_all = "camelCase")]
4226pub struct AutomationsStatusResult {
4227    pub scheduler_enabled: bool,
4228    pub read_api_enabled: bool,
4229    pub server_id: String,
4230    pub server_role: String,
4231    pub store_path: String,
4232    #[serde(default, skip_serializing_if = "Option::is_none")]
4233    pub last_tick_at: Option<OffsetDateTime>,
4234    #[serde(default, skip_serializing_if = "Option::is_none")]
4235    pub next_tick_at: Option<OffsetDateTime>,
4236    #[serde(default)]
4237    pub active_runs: usize,
4238    #[serde(default)]
4239    pub due_count: usize,
4240    #[serde(default)]
4241    pub leased_count: usize,
4242}
4243
4244#[derive(Debug, Clone, Serialize, Deserialize)]
4245pub struct ToolsListResult {
4246    pub tools: Vec<ToolSpec>,
4247}
4248
4249#[derive(Debug, Clone, Serialize, Deserialize)]
4250pub struct ToolCallParams {
4251    pub thread_id: ThreadId,
4252    pub tool_name: String,
4253    pub arguments: serde_json::Value,
4254}
4255
4256#[derive(Debug, Clone, Serialize, Deserialize)]
4257pub struct ToolCallResult {
4258    pub text: String,
4259    pub data: serde_json::Value,
4260    pub is_error: bool,
4261}
4262
4263#[derive(Debug, Clone, Serialize, Deserialize)]
4264pub struct AgentsListResult {
4265    pub agents: Vec<AgentDescriptor>,
4266}
4267
4268#[derive(Debug, Clone, Serialize, Deserialize)]
4269pub struct TasksSubmitParams {
4270    pub executor_id: String,
4271    #[serde(default)]
4272    pub input: serde_json::Value,
4273    pub thread_id: Option<ThreadId>,
4274    pub turn_id: Option<TurnId>,
4275    pub workspace: Option<String>,
4276}
4277
4278#[derive(Debug, Clone, Serialize, Deserialize)]
4279pub struct TasksSubmitResult {
4280    pub task: TaskHandle,
4281}
4282
4283#[derive(Debug, Clone, Serialize, Deserialize)]
4284pub struct TasksListResult {
4285    pub tasks: Vec<TaskHandle>,
4286}
4287
4288#[derive(Debug, Clone, Serialize, Deserialize)]
4289pub struct TasksGetParams {
4290    pub task_id: String,
4291}
4292
4293#[derive(Debug, Clone, Serialize, Deserialize)]
4294pub struct TaskLogDescriptor {
4295    pub stream: TaskOutputStream,
4296    pub chunk: String,
4297    pub timestamp: OffsetDateTime,
4298}
4299
4300#[derive(Debug, Clone, Serialize, Deserialize)]
4301pub struct TasksGetResult {
4302    pub task: TaskHandle,
4303    pub logs: Vec<TaskLogDescriptor>,
4304    pub dropped_bytes: u64,
4305}
4306
4307#[derive(Debug, Clone, Serialize, Deserialize)]
4308pub struct TasksCancelParams {
4309    pub task_id: String,
4310    pub reason: Option<String>,
4311}
4312
4313#[derive(Debug, Clone, Serialize, Deserialize)]
4314pub struct TasksCancelResult {
4315    pub cancelled: bool,
4316}
4317
4318#[derive(Debug, Clone, Serialize, Deserialize)]
4319pub struct TasksSubscribeResult {
4320    pub subscribed: bool,
4321    pub event_kinds: Vec<String>,
4322}
4323
4324#[derive(Debug, Clone, Serialize, Deserialize)]
4325#[serde(rename_all = "camelCase")]
4326pub struct WebwrightPrepareParams {
4327    pub task: String,
4328    #[serde(default)]
4329    pub mode: Option<String>,
4330    #[serde(default)]
4331    pub start_url: Option<String>,
4332    #[serde(default)]
4333    pub task_id: Option<String>,
4334    #[serde(default)]
4335    pub browser: Option<String>,
4336    #[serde(default)]
4337    pub headless: Option<bool>,
4338    #[serde(default)]
4339    pub output_dir: Option<String>,
4340    #[serde(default)]
4341    pub workspace: Option<String>,
4342}
4343
4344#[derive(Debug, Clone, Serialize, Deserialize)]
4345#[serde(rename_all = "camelCase")]
4346pub struct WebwrightWorkspaceParams {
4347    pub workspace: String,
4348    #[serde(default)]
4349    pub workspace_root: Option<String>,
4350}
4351
4352#[derive(Debug, Clone, Serialize, Deserialize)]
4353#[serde(rename_all = "camelCase")]
4354pub struct WebwrightSubmitParams {
4355    pub task: String,
4356    #[serde(default)]
4357    pub mode: Option<String>,
4358    #[serde(default)]
4359    pub start_url: Option<String>,
4360    #[serde(default)]
4361    pub task_id: Option<String>,
4362    #[serde(default)]
4363    pub browser: Option<String>,
4364    #[serde(default)]
4365    pub headless: Option<bool>,
4366    #[serde(default)]
4367    pub output_dir: Option<String>,
4368    #[serde(default)]
4369    pub timeout_seconds: Option<u64>,
4370    #[serde(default)]
4371    pub thread_id: Option<ThreadId>,
4372    #[serde(default)]
4373    pub turn_id: Option<TurnId>,
4374    #[serde(default)]
4375    pub workspace: Option<String>,
4376}
4377
4378#[derive(Debug, Clone, Serialize, Deserialize)]
4379#[serde(rename_all = "camelCase")]
4380pub struct WebwrightSetupParams {
4381    #[serde(default)]
4382    pub python: Option<String>,
4383    #[serde(default)]
4384    pub browser: Option<String>,
4385    #[serde(default)]
4386    pub dry_run: bool,
4387}
4388
4389#[derive(Debug, Clone, Serialize, Deserialize)]
4390#[serde(rename_all = "camelCase")]
4391pub struct WebwrightRerunParams {
4392    pub workspace: String,
4393    #[serde(default)]
4394    pub workspace_root: Option<String>,
4395    #[serde(default)]
4396    pub python: Option<String>,
4397    #[serde(default)]
4398    pub thread_id: Option<ThreadId>,
4399    #[serde(default)]
4400    pub turn_id: Option<TurnId>,
4401}
4402
4403#[derive(Debug, Clone, Serialize, Deserialize)]
4404#[serde(rename_all = "camelCase")]
4405pub struct WebwrightExportParams {
4406    pub workspace: String,
4407    #[serde(default)]
4408    pub workspace_root: Option<String>,
4409    pub output_dir: String,
4410}
4411
4412#[derive(Debug, Clone, Serialize, Deserialize)]
4413#[serde(rename_all = "camelCase")]
4414pub struct WebwrightVisualJudgeParams {
4415    pub workspace: String,
4416    #[serde(default)]
4417    pub workspace_root: Option<String>,
4418    #[serde(default)]
4419    pub run_id: Option<u32>,
4420    #[serde(default)]
4421    pub enabled: Option<bool>,
4422}
4423
4424#[derive(Debug, Clone, Serialize, Deserialize)]
4425#[serde(rename_all = "camelCase")]
4426pub struct WebwrightPrepareResult {
4427    pub task_id: String,
4428    pub workspace: serde_json::Value,
4429}
4430
4431#[derive(Debug, Clone, Serialize, Deserialize)]
4432#[serde(rename_all = "camelCase")]
4433pub struct WebwrightArtifactsResult {
4434    pub workspace: serde_json::Value,
4435}
4436
4437#[derive(Debug, Clone, Serialize, Deserialize)]
4438#[serde(rename_all = "camelCase")]
4439pub struct WebwrightLatestRunResult {
4440    pub latest_run: Option<u32>,
4441    pub run: Option<serde_json::Value>,
4442}
4443
4444#[derive(Debug, Clone, Serialize, Deserialize)]
4445#[serde(rename_all = "camelCase")]
4446pub struct WebwrightReportResult {
4447    pub task_definition: Option<serde_json::Value>,
4448    pub report: Option<serde_json::Value>,
4449    pub rendered_text: Option<String>,
4450}
4451
4452#[derive(Debug, Clone, Serialize, Deserialize)]
4453#[serde(rename_all = "camelCase")]
4454pub struct WebwrightVerifyResult {
4455    pub verification: serde_json::Value,
4456}
4457
4458#[derive(Debug, Clone, Serialize, Deserialize)]
4459#[serde(rename_all = "camelCase")]
4460pub struct WebwrightSubmitResult {
4461    pub task: TaskHandle,
4462}
4463
4464#[derive(Debug, Clone, Serialize, Deserialize)]
4465#[serde(rename_all = "camelCase")]
4466pub struct WebwrightSetupStepResult {
4467    pub label: String,
4468    pub command: Vec<String>,
4469    pub status: String,
4470    pub stdout_tail: String,
4471    pub stderr_tail: String,
4472}
4473
4474#[derive(Debug, Clone, Serialize, Deserialize)]
4475#[serde(rename_all = "camelCase")]
4476pub struct WebwrightSetupResult {
4477    pub roder_home: String,
4478    pub runtime_dir: String,
4479    pub python: String,
4480    pub browser: String,
4481    pub dry_run: bool,
4482    pub installed: bool,
4483    pub steps: Vec<WebwrightSetupStepResult>,
4484    pub message: String,
4485}
4486
4487#[derive(Debug, Clone, Serialize, Deserialize)]
4488#[serde(rename_all = "camelCase")]
4489pub struct WebwrightRerunResult {
4490    pub task: TaskHandle,
4491    pub run_id: u32,
4492    pub run_dir: String,
4493}
4494
4495#[derive(Debug, Clone, Serialize, Deserialize)]
4496#[serde(rename_all = "camelCase")]
4497pub struct WebwrightExportResult {
4498    pub export_dir: String,
4499    pub files: Vec<String>,
4500    pub excluded: Vec<String>,
4501}
4502
4503#[derive(Debug, Clone, Serialize, Deserialize)]
4504#[serde(rename_all = "camelCase")]
4505pub struct WebwrightVisualJudgeResult {
4506    pub visual_judge: serde_json::Value,
4507}
4508
4509#[derive(Debug, Clone, Serialize, Deserialize, Default)]
4510#[serde(rename_all = "camelCase")]
4511pub struct ProcessesListParams {
4512    #[serde(default)]
4513    pub include_completed: bool,
4514}
4515
4516#[derive(Debug, Clone, Serialize, Deserialize)]
4517#[serde(rename_all = "camelCase")]
4518pub struct ProcessesListResult {
4519    pub processes: Vec<ProcessDescriptor>,
4520}
4521
4522#[derive(Debug, Clone, Serialize, Deserialize)]
4523#[serde(rename_all = "camelCase")]
4524pub struct ProcessesGetParams {
4525    pub process_id: ProcessId,
4526    #[serde(default, skip_serializing_if = "Option::is_none")]
4527    pub output_bytes: Option<usize>,
4528}
4529
4530#[derive(Debug, Clone, Serialize, Deserialize)]
4531#[serde(rename_all = "camelCase")]
4532pub struct ProcessesGetResult {
4533    #[serde(default, skip_serializing_if = "Option::is_none")]
4534    pub process: Option<ProcessDescriptor>,
4535    #[serde(default)]
4536    pub output: Vec<ProcessOutput>,
4537}
4538
4539#[derive(Debug, Clone, Serialize, Deserialize)]
4540#[serde(rename_all = "camelCase")]
4541pub struct ProcessesStopParams {
4542    pub process_id: ProcessId,
4543    #[serde(default, skip_serializing_if = "Option::is_none")]
4544    pub reason: Option<String>,
4545}
4546
4547#[derive(Debug, Clone, Serialize, Deserialize)]
4548#[serde(rename_all = "camelCase")]
4549pub struct ProcessesStopResult {
4550    pub result: ProcessStopResult,
4551}
4552
4553#[derive(Debug, Clone, Serialize, Deserialize, Default)]
4554#[serde(rename_all = "camelCase")]
4555pub struct ProcessesStopAllParams {
4556    #[serde(default, skip_serializing_if = "Option::is_none")]
4557    pub reason: Option<String>,
4558}
4559
4560#[derive(Debug, Clone, Serialize, Deserialize)]
4561#[serde(rename_all = "camelCase")]
4562pub struct ProcessesStopAllResult {
4563    pub results: Vec<ProcessStopResult>,
4564}
4565
4566#[derive(Debug, Clone, Serialize, Deserialize)]
4567#[serde(rename_all = "camelCase")]
4568pub struct ProcessesSubscribeResult {
4569    pub subscribed: bool,
4570    pub event_kinds: Vec<String>,
4571}
4572
4573#[derive(Debug, Clone, Serialize, Deserialize)]
4574pub struct AgentDescriptor {
4575    pub agent_type: String,
4576    pub description: String,
4577    pub tools: Vec<String>,
4578    pub model: Option<String>,
4579    pub permission_mode: SubagentPermissionMode,
4580    pub max_turns: Option<u32>,
4581    pub max_result_chars: Option<usize>,
4582}
4583
4584#[cfg(test)]
4585mod tests {
4586    use super::*;
4587    use time::OffsetDateTime;
4588
4589    #[test]
4590    fn protocol_turn_start_params_accept_input_shape() {
4591        let params: TurnStartParams = serde_json::from_value(serde_json::json!({
4592            "threadId": "thread-1",
4593            "input": [
4594                { "type": "text", "text": "hello" },
4595                { "type": "image", "imageUrl": "data:image/png;base64,YWJj" }
4596            ]
4597        }))
4598        .unwrap();
4599
4600        assert_eq!(params.thread_id, "thread-1");
4601        assert_eq!(params.input[0].kind, "text");
4602        assert_eq!(params.input[0].text.as_deref(), Some("hello"));
4603        assert_eq!(params.input[1].kind, "image");
4604        assert_eq!(
4605            params.input[1].image_url.as_deref(),
4606            Some("data:image/png;base64,YWJj")
4607        );
4608        assert!(!params.task_ledger_required);
4609    }
4610
4611    #[test]
4612    fn protocol_turn_start_params_accept_task_ledger_required() {
4613        let params: TurnStartParams = serde_json::from_value(serde_json::json!({
4614            "threadId": "thread-1",
4615            "taskLedgerRequired": true
4616        }))
4617        .unwrap();
4618
4619        assert!(params.task_ledger_required);
4620    }
4621
4622    #[test]
4623    fn protocol_turn_start_params_round_trip_developer_context() {
4624        let params: TurnStartParams = serde_json::from_value(serde_json::json!({
4625            "threadId": "thread-1",
4626            "developerContext": "Connected accounts: example-service."
4627        }))
4628        .unwrap();
4629        assert_eq!(
4630            params.developer_context.as_deref(),
4631            Some("Connected accounts: example-service.")
4632        );
4633
4634        let encoded = serde_json::to_value(&params).unwrap();
4635        assert_eq!(
4636            encoded["developerContext"],
4637            "Connected accounts: example-service."
4638        );
4639
4640        // Absent developerContext stays absent on the wire.
4641        let without: TurnStartParams = serde_json::from_value(serde_json::json!({
4642            "threadId": "thread-1"
4643        }))
4644        .unwrap();
4645        assert!(without.developer_context.is_none());
4646        assert!(
4647            serde_json::to_value(&without)
4648                .unwrap()
4649                .get("developerContext")
4650                .is_none()
4651        );
4652    }
4653
4654    #[test]
4655    fn protocol_thread_start_params_round_trip_runner_selection() {
4656        let params: ThreadStartParams = serde_json::from_value(serde_json::json!({
4657            "workspaceId": "ws-1",
4658            "model": null,
4659            "modelProvider": null,
4660            "reasoning": null,
4661            "runner": {
4662                "providerId": "acme",
4663                "config": { "space_id": "space-1", "mode": "readwrite" },
4664                "workspace": "/workspace"
4665            }
4666        }))
4667        .unwrap();
4668
4669        let runner = params.runner.clone().expect("runner params");
4670        assert_eq!(runner.provider_id, "acme");
4671        assert_eq!(
4672            runner.config,
4673            Some(serde_json::json!({ "space_id": "space-1", "mode": "readwrite" }))
4674        );
4675        assert_eq!(runner.workspace, "/workspace");
4676
4677        let encoded = serde_json::to_value(&params).unwrap();
4678        assert_eq!(encoded["runner"]["providerId"], "acme");
4679        assert_eq!(encoded["runner"]["workspace"], "/workspace");
4680
4681        // Absent runner stays absent on the wire.
4682        let without: ThreadStartParams = serde_json::from_value(serde_json::json!({
4683            "workspaceId": "ws-1",
4684            "model": null,
4685            "modelProvider": null,
4686            "reasoning": null
4687        }))
4688        .unwrap();
4689        assert!(without.runner.is_none());
4690        assert!(
4691            serde_json::to_value(&without)
4692                .unwrap()
4693                .get("runner")
4694                .is_none()
4695        );
4696    }
4697
4698    #[test]
4699    fn protocol_turn_start_params_accept_selected_controls() {
4700        let params: TurnStartParams = serde_json::from_value(serde_json::json!({
4701            "threadId": "thread-1",
4702            "modelProvider": "mock",
4703            "model": "gpt-5.5",
4704            "reasoning": "high",
4705            "policyMode": "plan"
4706        }))
4707        .unwrap();
4708
4709        assert_eq!(params.model_provider.as_deref(), Some("mock"));
4710        assert_eq!(params.model.as_deref(), Some("gpt-5.5"));
4711        assert_eq!(params.reasoning.as_deref(), Some("high"));
4712        assert_eq!(params.policy_mode, Some(PolicyMode::Plan));
4713    }
4714
4715    #[test]
4716    fn protocol_notifications_serialize_with_json_rpc_method_and_camel_case_params() {
4717        let notification = JsonRpcNotification {
4718            jsonrpc: "2.0".to_string(),
4719            method: "item/agentMessage/delta".to_string(),
4720            params: serde_json::to_value(ThreadItemEvent {
4721                seq: 1,
4722                event_id: "event-1".to_string(),
4723                thread_id: "thread-1".to_string(),
4724                turn_id: "turn-1".to_string(),
4725                timestamp: OffsetDateTime::UNIX_EPOCH,
4726                event: ThreadItemEventKind::ItemDelta {
4727                    item_id: "turn-1-assistant".to_string(),
4728                    delta: ThreadItemDelta::AgentMessageText {
4729                        delta: "hello".to_string(),
4730                        phase: Some("final_answer".to_string()),
4731                    },
4732                },
4733            })
4734            .unwrap(),
4735        };
4736
4737        let value = serde_json::to_value(notification).unwrap();
4738        assert_eq!(value["jsonrpc"], "2.0");
4739        assert_eq!(value["method"], "item/agentMessage/delta");
4740        assert_eq!(value["params"]["seq"], 1);
4741        assert_eq!(value["params"]["eventId"], "event-1");
4742        assert_eq!(value["params"]["threadId"], "thread-1");
4743        assert_eq!(value["params"]["turnId"], "turn-1");
4744        assert_eq!(value["params"]["event"]["type"], "itemDelta");
4745        assert_eq!(value["params"]["event"]["itemId"], "turn-1-assistant");
4746        assert_eq!(
4747            value["params"]["event"]["delta"]["type"],
4748            "agentMessageText"
4749        );
4750        assert_eq!(value["params"]["event"]["delta"]["delta"], "hello");
4751        assert_eq!(value["params"]["event"]["delta"]["phase"], "final_answer");
4752    }
4753
4754    #[test]
4755    fn thread_item_reasoning_serializes_as_typed_public_item() {
4756        let value = serde_json::to_value(Item::Reasoning {
4757            id: "turn-1-agent-reasoning".to_string(),
4758            summary: vec!["Inspecting project".to_string()],
4759            content: vec!["I need to inspect files.".to_string()],
4760            status: Some(ThreadItemStatus::Completed),
4761        })
4762        .unwrap();
4763
4764        assert_eq!(
4765            value,
4766            serde_json::json!({
4767                "type": "reasoning",
4768                "id": "turn-1-agent-reasoning",
4769                "summary": ["Inspecting project"],
4770                "content": ["I need to inspect files."],
4771                "status": "completed"
4772            })
4773        );
4774    }
4775
4776    #[test]
4777    fn thread_item_routing_decision_serializes_as_typed_public_item() {
4778        let selected = roder_api::inference::ModelSelection {
4779            provider: "anthropic".to_string(),
4780            model: "claude-sonnet-5".to_string(),
4781        };
4782        let value = serde_json::to_value(Item::RoutingDecision {
4783            id: "turn-1-routing-decision-0".to_string(),
4784            decision: InferenceRoutingDecisionEvent {
4785                thread_id: "thread-1".to_string(),
4786                turn_id: "turn-1".to_string(),
4787                round_index: 0,
4788                default_selection: roder_api::inference::ModelSelection {
4789                    provider: "openai".to_string(),
4790                    model: "gpt-5.5".to_string(),
4791                },
4792                selected_selection: selected.clone(),
4793                decision: roder_api::inference_routing::InferenceRoutingDecision::selected(
4794                    "local",
4795                    selected,
4796                    "Large diff and failing tests",
4797                ),
4798                timestamp: OffsetDateTime::UNIX_EPOCH,
4799            },
4800            status: Some(ThreadItemStatus::Completed),
4801        })
4802        .unwrap();
4803
4804        assert_eq!(value["type"], "routingDecision");
4805        assert_eq!(value["id"], "turn-1-routing-decision-0");
4806        assert_eq!(
4807            value["decision"]["selectedSelection"]["model"],
4808            "claude-sonnet-5"
4809        );
4810        assert_eq!(
4811            value["decision"]["decision"]["reason"],
4812            "Large diff and failing tests"
4813        );
4814        assert_eq!(value["status"], "completed");
4815    }
4816
4817    #[test]
4818    fn reasoning_text_delta_notification_targets_reasoning_content_index() {
4819        let notification = JsonRpcNotification {
4820            jsonrpc: "2.0".to_string(),
4821            method: "item/reasoning/textDelta".to_string(),
4822            params: serde_json::to_value(ThreadItemEvent {
4823                seq: 1,
4824                event_id: "event-1".to_string(),
4825                thread_id: "thread-1".to_string(),
4826                turn_id: "turn-1".to_string(),
4827                timestamp: OffsetDateTime::UNIX_EPOCH,
4828                event: ThreadItemEventKind::ItemDelta {
4829                    item_id: "turn-1-agent-reasoning".to_string(),
4830                    delta: ThreadItemDelta::ReasoningText {
4831                        delta: "thinking".to_string(),
4832                        content_index: 0,
4833                    },
4834                },
4835            })
4836            .unwrap(),
4837        };
4838
4839        let value = serde_json::to_value(notification).unwrap();
4840        assert_eq!(value["method"], "item/reasoning/textDelta");
4841        assert_eq!(value["params"]["threadId"], "thread-1");
4842        assert_eq!(value["params"]["turnId"], "turn-1");
4843        assert_eq!(value["params"]["event"]["itemId"], "turn-1-agent-reasoning");
4844        assert_eq!(value["params"]["event"]["delta"]["delta"], "thinking");
4845        assert_eq!(value["params"]["event"]["delta"]["contentIndex"], 0);
4846    }
4847
4848    #[test]
4849    fn thread_status_serializes_required_activity_fields() {
4850        let idle = serde_json::to_value(ThreadStatus {
4851            kind: "idle".to_string(),
4852            active_turn_id: None,
4853            active_flags: Vec::new(),
4854        })
4855        .unwrap();
4856        assert_eq!(
4857            idle,
4858            serde_json::json!({
4859                "type": "idle",
4860                "activeTurnId": null,
4861                "activeFlags": []
4862            })
4863        );
4864
4865        let running = serde_json::to_value(ThreadStatus {
4866            kind: "running".to_string(),
4867            active_turn_id: Some("turn-1".to_string()),
4868            active_flags: vec!["approvalRequired".to_string()],
4869        })
4870        .unwrap();
4871        assert_eq!(
4872            running,
4873            serde_json::json!({
4874                "type": "running",
4875                "activeTurnId": "turn-1",
4876                "activeFlags": ["approvalRequired"]
4877            })
4878        );
4879    }
4880
4881    #[test]
4882    fn verification_notifications_use_camel_case_fields() {
4883        let value = serde_json::to_value(VerificationRequiredNotification {
4884            thread_id: "thread-1".to_string(),
4885            turn_id: "turn-1".to_string(),
4886            reason: "code_changes_without_verification".to_string(),
4887            changed_files: vec!["src/lib.rs".to_string()],
4888            tool_evidence: vec!["write_file: wrote src/lib.rs".to_string()],
4889            tests_run: vec!["cargo test".to_string()],
4890            open_gaps: Vec::new(),
4891        })
4892        .unwrap();
4893
4894        assert_eq!(value["threadId"], "thread-1");
4895        assert_eq!(value["turnId"], "turn-1");
4896        assert_eq!(value["changedFiles"][0], "src/lib.rs");
4897        assert_eq!(value["toolEvidence"][0], "write_file: wrote src/lib.rs");
4898        assert_eq!(value["testsRun"][0], "cargo test");
4899        assert!(value.get("changed_files").is_none());
4900    }
4901
4902    #[test]
4903    fn search_index_status_protocol_uses_camel_case_fields() {
4904        let value = serde_json::to_value(SearchIndexStatusNotification {
4905            status: SearchIndexStatus {
4906                state: SearchIndexStatusState::Ready,
4907                enabled: true,
4908                workspace: "/tmp/workspace".to_string(),
4909                store_dir: "/tmp/home/.roder/indexes/abc".to_string(),
4910                index_version: Some("fastregex-v1".to_string()),
4911                document_count: Some(7),
4912                index_bytes: Some(128),
4913                build_time_ms: Some(4),
4914                stale: false,
4915                message: None,
4916            },
4917        })
4918        .unwrap();
4919
4920        assert_eq!(value["status"]["state"], "ready");
4921        assert_eq!(value["status"]["storeDir"], "/tmp/home/.roder/indexes/abc");
4922        assert_eq!(value["status"]["indexVersion"], "fastregex-v1");
4923        assert_eq!(value["status"]["documentCount"], 7);
4924        assert_eq!(value["status"]["buildTimeMs"], 4);
4925        assert!(value["status"].get("store_dir").is_none());
4926        assert!(value["status"].get("document_count").is_none());
4927    }
4928
4929    #[test]
4930    fn code_index_status_protocol_uses_camel_case_fields() {
4931        let value = serde_json::to_value(CodeIndexStatusNotification {
4932            status: CodeIndexStatusView {
4933                status: CodeIndexStatus::Ready,
4934                workspace: "/tmp/workspace".to_string(),
4935                store_path: "/tmp/home/.roder/code-index/abc/code-index.sqlite3".to_string(),
4936                generation_id: Some("gen-1".to_string()),
4937                root_hash: Some("root-hash".to_string()),
4938                stale: false,
4939                stats: roder_api::code_index::CodeIndexStats {
4940                    file_count: 2,
4941                    chunk_count: 3,
4942                    embedded_chunk_count: 3,
4943                    cached_embedding_count: 1,
4944                    index_bytes: 256,
4945                },
4946                message: None,
4947            },
4948        })
4949        .unwrap();
4950
4951        assert_eq!(value["status"]["status"], "ready");
4952        assert_eq!(
4953            value["status"]["storePath"],
4954            "/tmp/home/.roder/code-index/abc/code-index.sqlite3"
4955        );
4956        assert_eq!(value["status"]["generationId"], "gen-1");
4957        assert_eq!(value["status"]["stats"]["chunkCount"], 3);
4958        assert_eq!(value["status"]["stats"]["cachedEmbeddingCount"], 1);
4959        assert!(value["status"].get("store_path").is_none());
4960        assert!(value["status"]["stats"].get("chunk_count").is_none());
4961    }
4962
4963    #[test]
4964    fn workspace_files_protocol_structs_use_workspace_scoped_locators() {
4965        let children: WorkspaceFilesChildrenParams = serde_json::from_value(serde_json::json!({
4966            "workspaceId": "ws-1",
4967            "rootId": "root-1",
4968            "path": "roadmap"
4969        }))
4970        .unwrap();
4971        assert_eq!(children.workspace_id, "ws-1");
4972        assert_eq!(children.root_id.as_deref(), Some("root-1"));
4973        assert_eq!(children.path.as_deref(), Some("roadmap"));
4974
4975        let status = WorkspaceFilesStatus {
4976            workspace_id: "ws-1".to_string(),
4977            state: WorkspaceFilesIndexState::Ready,
4978            stale: false,
4979            roots: vec![WorkspaceFilesRootStatus {
4980                root_id: "root-1".to_string(),
4981                root_name: "repo".to_string(),
4982                state: WorkspaceFilesIndexState::Ready,
4983                stale: false,
4984                file_count: Some(1),
4985                directory_count: Some(1),
4986                build_time_ms: Some(4),
4987                indexed_at_ms: Some(42),
4988                message: None,
4989            }],
4990            file_count: 1,
4991            directory_count: 1,
4992            message: None,
4993        };
4994        let entry = WorkspaceFileEntry {
4995            root_id: "root-1".to_string(),
4996            root_name: "repo".to_string(),
4997            path: "roadmap/status.md".to_string(),
4998            name: "status.md".to_string(),
4999            kind: WorkspaceFileKind::File,
5000            has_children: false,
5001            size: Some(12),
5002            modified_ms: Some(99),
5003        };
5004        let result = WorkspaceFilesQueryResult {
5005            status,
5006            matches: vec![WorkspaceFileQueryMatch {
5007                entry,
5008                score: 120,
5009                match_positions: vec![0, 8],
5010            }],
5011            indexed_file_count: 1,
5012        };
5013        let value = serde_json::to_value(result).unwrap();
5014
5015        assert_eq!(value["status"]["workspaceId"], "ws-1");
5016        assert_eq!(value["status"]["roots"][0]["rootId"], "root-1");
5017        assert_eq!(value["matches"][0]["entry"]["rootName"], "repo");
5018        assert_eq!(value["matches"][0]["entry"]["kind"], "file");
5019        assert_eq!(
5020            value["matches"][0]["matchPositions"],
5021            serde_json::json!([0, 8])
5022        );
5023        assert!(value["matches"][0]["entry"].get("root_id").is_none());
5024        assert!(value.get("indexed_file_count").is_none());
5025    }
5026
5027    #[test]
5028    fn team_start_params_round_trip_camel_case_display_mode() {
5029        let params: TeamStartParams = serde_json::from_value(serde_json::json!({
5030            "leadThreadId": "lead-thread",
5031            "displayMode": "tmux",
5032            "members": [{
5033                "name": "reviewer",
5034                "modelProvider": "mock",
5035                "model": "mock"
5036            }]
5037        }))
5038        .unwrap();
5039
5040        assert_eq!(params.lead_thread_id.as_deref(), Some("lead-thread"));
5041        assert_eq!(params.display_mode, Some(AgentTeamDisplayMode::Tmux));
5042        assert_eq!(params.members[0].model_provider.as_deref(), Some("mock"));
5043
5044        let value = serde_json::to_value(params).unwrap();
5045        assert_eq!(value["leadThreadId"], "lead-thread");
5046        assert_eq!(value["displayMode"], "tmux");
5047        assert_eq!(value["members"][0]["modelProvider"], "mock");
5048    }
5049
5050    #[test]
5051    fn subagent_trace_protocol_structs_use_camel_case_fields() {
5052        let list_params: SubagentTracesListParams = serde_json::from_value(serde_json::json!({
5053            "threadId": "thread-1",
5054            "turnId": "turn-1"
5055        }))
5056        .unwrap();
5057        assert_eq!(list_params.thread_id, "thread-1");
5058        assert_eq!(list_params.turn_id, "turn-1");
5059
5060        let read_params: SubagentTraceReadParams = serde_json::from_value(serde_json::json!({
5061            "threadId": "thread-1",
5062            "traceId": "trace-1",
5063            "offset": 10,
5064            "limit": 20
5065        }))
5066        .unwrap();
5067        assert_eq!(read_params.thread_id, "thread-1");
5068        assert_eq!(read_params.trace_id, "trace-1");
5069        assert_eq!(read_params.offset, 10);
5070        assert_eq!(read_params.limit, Some(20));
5071
5072        let result = SubagentTraceReadResult {
5073            trace_id: "trace-1".to_string(),
5074            events: Vec::new(),
5075            next_offset: Some(30),
5076        };
5077        let value = serde_json::to_value(result).unwrap();
5078        assert_eq!(value["traceId"], "trace-1");
5079        assert_eq!(value["nextOffset"], 30);
5080    }
5081
5082    #[test]
5083    fn artifacts_protocol_structs_use_camel_case_fields() {
5084        let params: ArtifactReadParams = serde_json::from_value(serde_json::json!({
5085            "threadId": "thread-1",
5086            "artifactId": "artifact-1",
5087            "startLine": 2,
5088            "limit": 10
5089        }))
5090        .unwrap();
5091
5092        assert_eq!(params.thread_id, "thread-1");
5093        assert_eq!(params.artifact_id, "artifact-1");
5094        assert_eq!(params.start_line, Some(2));
5095
5096        let command = serde_json::to_value(CommandExecResponse {
5097            exit_code: 0,
5098            stdout: "short".to_string(),
5099            stderr: String::new(),
5100            stdout_artifact: None,
5101            stderr_artifact: None,
5102        })
5103        .unwrap();
5104
5105        assert_eq!(command["exitCode"], 0);
5106        assert!(command.get("stdoutArtifact").is_none());
5107    }
5108
5109    #[test]
5110    fn discovery_protocol_structs_use_camel_case_fields() {
5111        let params: DiscoveryReadParams = serde_json::from_value(serde_json::json!({
5112            "itemId": "tool:builtin/grep",
5113            "startLine": 2,
5114            "limit": 10,
5115            "threadId": "thread-1",
5116            "turnId": "turn-1"
5117        }))
5118        .unwrap();
5119
5120        assert_eq!(params.item_id, "tool:builtin/grep");
5121        assert_eq!(params.start_line, Some(2));
5122        assert_eq!(params.thread_id.as_deref(), Some("thread-1"));
5123
5124        let clear = serde_json::to_value(DiscoveryPromotedClearResult { cleared: 2 }).unwrap();
5125        assert_eq!(clear["cleared"], 2);
5126    }
5127
5128    #[test]
5129    fn retrieval_protocol_structs_use_camel_case_fields() {
5130        let params: RetrievalTurnParams = serde_json::from_value(serde_json::json!({
5131            "threadId": "thread-1",
5132            "turnId": "turn-1",
5133            "limit": 5
5134        }))
5135        .unwrap();
5136        assert_eq!(params.thread_id, "thread-1");
5137        assert_eq!(params.turn_id, "turn-1");
5138        assert_eq!(params.limit, Some(5));
5139
5140        let result = RetrievalRecommendationsResult {
5141            thread_id: "thread-1".to_string(),
5142            turn_id: "turn-1".to_string(),
5143            plans: Vec::new(),
5144            summary: RetrievalDebugSummary {
5145                text: "no route recommendations recorded".to_string(),
5146                notes: vec!["router did not emit retrieval/routePlanned".to_string()],
5147                truncated: false,
5148            },
5149        };
5150        let value = serde_json::to_value(result).unwrap();
5151        assert_eq!(value["threadId"], "thread-1");
5152        assert_eq!(value["turnId"], "turn-1");
5153        assert_eq!(value["summary"]["truncated"], false);
5154
5155        let state = RetrievalPromotedCapabilityState {
5156            item_id: "tool:builtin/grep".to_string(),
5157            route_id: Some("route-1".to_string()),
5158            state: "skipped".to_string(),
5159            cache_status: None,
5160            reason: Some("already warm".to_string()),
5161            thread_id: "thread-1".to_string(),
5162            turn_id: Some("turn-1".to_string()),
5163            timestamp: OffsetDateTime::UNIX_EPOCH,
5164        };
5165        let value = serde_json::to_value(state).unwrap();
5166        assert_eq!(value["itemId"], "tool:builtin/grep");
5167        assert_eq!(value["routeId"], "route-1");
5168        assert!(value.get("cacheStatus").is_none());
5169    }
5170
5171    #[test]
5172    fn plan_review_and_hunk_protocol_structs_use_camel_case_fields() {
5173        let comment_params: PlanReviewCommentParams = serde_json::from_value(serde_json::json!({
5174            "threadId": "thread-1",
5175            "reviewId": "review-1",
5176            "anchor": { "step": { "stepId": "step-1" } },
5177            "body": "Use the smaller patch."
5178        }))
5179        .unwrap();
5180        assert_eq!(comment_params.thread_id, "thread-1");
5181        assert_eq!(comment_params.review_id, "review-1");
5182
5183        let hunk_params: HunkReadParams = serde_json::from_value(serde_json::json!({
5184            "threadId": "thread-1",
5185            "hunkId": "hunk-1",
5186            "offset": 4,
5187            "limit": 20
5188        }))
5189        .unwrap();
5190        assert_eq!(hunk_params.hunk_id, "hunk-1");
5191        assert_eq!(hunk_params.limit, Some(20));
5192
5193        let result = HunkRollbackResult {
5194            rolled_back: false,
5195            error: Some("checkpoint data is unavailable".to_string()),
5196        };
5197        let value = serde_json::to_value(result).unwrap();
5198        assert_eq!(value["rolledBack"], false);
5199        assert_eq!(value["error"], "checkpoint data is unavailable");
5200    }
5201
5202    #[test]
5203    fn workflow_import_protocol_structs_use_camel_case_fields() {
5204        let scan_params: WorkflowScanParams = serde_json::from_value(serde_json::json!({
5205            "workspace": "/tmp/repo",
5206            "includeUser": true
5207        }))
5208        .unwrap();
5209        assert_eq!(scan_params.workspace.as_deref(), Some("/tmp/repo"));
5210        assert!(scan_params.include_user);
5211
5212        let enable_params: WorkflowEnableParams = serde_json::from_value(serde_json::json!({
5213            "workspace": "/tmp/repo",
5214            "itemId": "workflow-1",
5215            "approveSideEffects": true
5216        }))
5217        .unwrap();
5218        assert_eq!(enable_params.item_id, "workflow-1");
5219        assert!(enable_params.approve_side_effects);
5220
5221        let remove = WorkflowRemoveResult {
5222            item_id: "workflow-1".to_string(),
5223            state: WorkflowImportState::Removed,
5224            decision: WorkflowImportDecision {
5225                item_id: "workflow-1".to_string(),
5226                decision: roder_api::workflow::WorkflowImportDecisionKind::Remove,
5227                source_hash: "hash".to_string(),
5228                approved_side_effects: false,
5229                decided_at: OffsetDateTime::UNIX_EPOCH,
5230            },
5231        };
5232        let value = serde_json::to_value(remove).unwrap();
5233        assert_eq!(value["itemId"], "workflow-1");
5234        assert_eq!(value["state"], "removed");
5235        assert_eq!(value["decision"]["sourceHash"], "hash");
5236    }
5237
5238    #[test]
5239    fn marketplace_protocol_structs_use_camel_case_fields() {
5240        let params: MarketplacesInstallDefaultParams = serde_json::from_value(serde_json::json!({
5241            "selection": "all"
5242        }))
5243        .unwrap();
5244        assert_eq!(params.selection, DefaultMarketplaceSelection::All);
5245
5246        let add: MarketplacesAddParams = serde_json::from_value(serde_json::json!({
5247            "id": "local-cursor",
5248            "kind": "cursor",
5249            "displayName": "Local Cursor",
5250            "source": {
5251                "kind": "localPath",
5252                "path": "/tmp/cursor"
5253            }
5254        }))
5255        .unwrap();
5256        assert_eq!(add.id, "local-cursor");
5257        assert_eq!(add.kind, Some(MarketplaceKind::Cursor));
5258        assert_eq!(
5259            add.source,
5260            MarketplaceSource::LocalPath {
5261                path: "/tmp/cursor".to_string()
5262            }
5263        );
5264
5265        let remove = MarketplacesRemoveParams {
5266            marketplace_id: "local-cursor".to_string(),
5267        };
5268        let value = serde_json::to_value(remove).unwrap();
5269        assert_eq!(value["marketplaceId"], "local-cursor");
5270
5271        let disable = PluginDisableParams {
5272            variant_key: "codex-plugins:superpowers".to_string(),
5273        };
5274        let value = serde_json::to_value(disable).unwrap();
5275        assert_eq!(value["variantKey"], "codex-plugins:superpowers");
5276
5277        let all = PluginInstallAllVariantsParams {
5278            marketplace_id: "codex-plugins".to_string(),
5279            plugin_id: "superpowers".to_string(),
5280        };
5281        let value = serde_json::to_value(all).unwrap();
5282        assert_eq!(value["marketplaceId"], "codex-plugins");
5283
5284        let uninstall = PluginUninstallParams {
5285            variant_key: "codex-plugins:superpowers".to_string(),
5286        };
5287        let value = serde_json::to_value(uninstall).unwrap();
5288        assert_eq!(value["variantKey"], "codex-plugins:superpowers");
5289    }
5290
5291    #[test]
5292    fn skills_protocol_structs_use_camel_case_fields() {
5293        let params: SkillsSetExposureParams = serde_json::from_value(serde_json::json!({
5294            "selector": { "name": { "name": "commit" } },
5295            "exposure": "direct_only"
5296        }))
5297        .unwrap();
5298        assert_eq!(
5299            params.selector,
5300            SkillSelector::Name {
5301                name: "commit".to_string()
5302            }
5303        );
5304        assert_eq!(params.exposure, SkillExposure::DirectOnly);
5305
5306        let enabled = SkillsSetEnabledParams {
5307            selector: SkillSelector::Path {
5308                path: "roder-builtin://commit/SKILL.md".to_string(),
5309            },
5310            enabled: false,
5311        };
5312        let value = serde_json::to_value(enabled).unwrap();
5313        assert_eq!(
5314            value["selector"]["path"]["path"],
5315            "roder-builtin://commit/SKILL.md"
5316        );
5317        assert_eq!(value["enabled"], false);
5318    }
5319
5320    #[test]
5321    fn automations_protocol_structs_use_camel_case_fields() {
5322        let create: AutomationsCreateParams = serde_json::from_value(serde_json::json!({
5323            "name": "Nightly cleanup",
5324            "project": { "cwd": "/repo", "displayName": "repo" },
5325            "schedule": { "interval": { "seconds": 300 } },
5326            "prompt": "summarize status",
5327            "modelProvider": "codex",
5328            "model": "gpt-5.5",
5329            "policyMode": "plan",
5330            "catchUp": { "runLatestOnly": null },
5331            "concurrency": "forbid"
5332        }))
5333        .unwrap();
5334
5335        assert_eq!(create.name, "Nightly cleanup");
5336        assert_eq!(create.project.display_name.as_deref(), Some("repo"));
5337        assert_eq!(create.model_provider.as_deref(), Some("codex"));
5338        assert_eq!(
5339            create.policy_mode,
5340            Some(roder_api::policy_mode::PolicyMode::Plan)
5341        );
5342        assert_eq!(create.catch_up, CatchUpPolicy::RunLatestOnly);
5343
5344        let update = AutomationsUpdateParams {
5345            automation_id: "automation-1".to_string(),
5346            patch: AutomationsUpdatePatch {
5347                model_provider: Some("codex".to_string()),
5348                catch_up: Some(CatchUpPolicy::SkipExpired { grace_seconds: 60 }),
5349                ..AutomationsUpdatePatch::default()
5350            },
5351        };
5352        let value = serde_json::to_value(update).unwrap();
5353        assert_eq!(value["automationId"], "automation-1");
5354        assert_eq!(value["patch"]["modelProvider"], "codex");
5355        assert_eq!(value["patch"]["catchUp"]["skipExpired"]["graceSeconds"], 60);
5356        assert!(value.get("automation_id").is_none());
5357    }
5358
5359    #[test]
5360    fn processes_protocol_structs_cover_list_detail_stop_and_subscribe() {
5361        let descriptor = ProcessDescriptor {
5362            process_id: "process-1".to_string(),
5363            origin: roder_api::processes::ProcessOrigin::CommandExec,
5364            state: roder_api::processes::ProcessState::Running,
5365            command: vec!["sleep".to_string(), "10".to_string()],
5366            command_summary: "sleep 10".to_string(),
5367            cwd: Some("/repo".to_string()),
5368            pid: Some(1234),
5369            task_id: Some("task-1".to_string()),
5370            thread_id: Some("thread-1".to_string()),
5371            turn_id: Some("turn-1".to_string()),
5372            runner_destination_id: None,
5373            runner_session_id: None,
5374            stoppable: true,
5375            started_at: OffsetDateTime::UNIX_EPOCH,
5376            updated_at: OffsetDateTime::UNIX_EPOCH,
5377            stdout_tail: Some("ready\n".to_string()),
5378            stderr_tail: None,
5379        };
5380
5381        let list_params: ProcessesListParams = serde_json::from_value(serde_json::json!({
5382            "includeCompleted": true
5383        }))
5384        .unwrap();
5385        assert!(list_params.include_completed);
5386
5387        let list = serde_json::to_value(ProcessesListResult {
5388            processes: vec![descriptor.clone()],
5389        })
5390        .unwrap();
5391        assert_eq!(list["processes"][0]["processId"], "process-1");
5392        assert_eq!(list["processes"][0]["pid"], 1234);
5393        assert!(list["processes"][0].get("process_id").is_none());
5394
5395        let get: ProcessesGetParams = serde_json::from_value(serde_json::json!({
5396            "processId": "process-1",
5397            "outputBytes": 1024
5398        }))
5399        .unwrap();
5400        assert_eq!(get.process_id, "process-1");
5401        assert_eq!(get.output_bytes, Some(1024));
5402
5403        let stop: ProcessesStopParams = serde_json::from_value(serde_json::json!({
5404            "processId": "process-1",
5405            "reason": "user requested stop"
5406        }))
5407        .unwrap();
5408        assert_eq!(stop.process_id, "process-1");
5409        assert_eq!(stop.reason.as_deref(), Some("user requested stop"));
5410
5411        let stop_all: ProcessesStopAllResult = serde_json::from_value(serde_json::json!({
5412            "results": [{
5413                "processId": "process-1",
5414                "stopped": true,
5415                "process": list["processes"][0].clone()
5416            }]
5417        }))
5418        .unwrap();
5419        assert_eq!(stop_all.results[0].process_id, "process-1");
5420        assert!(stop_all.results[0].stopped);
5421
5422        let subscribe = ProcessesSubscribeResult {
5423            subscribed: true,
5424            event_kinds: vec!["process.started".to_string(), "process.output".to_string()],
5425        };
5426        let value = serde_json::to_value(subscribe).unwrap();
5427        assert_eq!(value["subscribed"], true);
5428        assert_eq!(value["eventKinds"][0], "process.started");
5429        assert!(value.get("event_kinds").is_none());
5430    }
5431
5432    #[test]
5433    fn eval_report_protocol_structs_use_camel_case_fields() {
5434        let list: EvalReportsListParams = serde_json::from_value(serde_json::json!({
5435            "limit": 5
5436        }))
5437        .unwrap();
5438        assert_eq!(list.limit, Some(5));
5439
5440        let read: EvalReportReadParams = serde_json::from_value(serde_json::json!({
5441            "reportId": "eval-run",
5442            "maxBytes": 1024
5443        }))
5444        .unwrap();
5445        assert_eq!(read.report_id, "eval-run");
5446        assert_eq!(read.max_bytes, Some(1024));
5447
5448        let result = EvalReportReadResult {
5449            summary: EvalReportSummary {
5450                id: "eval-run".to_string(),
5451                suite_id: "tool-calls".to_string(),
5452                fixture_count: 2,
5453                passed: 1,
5454                failed: 1,
5455                reliability: EvalReliabilitySummary {
5456                    retry_attempts: 2,
5457                    retry_recoveries: 1,
5458                    ..EvalReliabilitySummary::default()
5459                },
5460                generated_at: OffsetDateTime::UNIX_EPOCH,
5461            },
5462            markdown: "# Report".to_string(),
5463            truncated: false,
5464        };
5465        let value = serde_json::to_value(result).unwrap();
5466        assert_eq!(value["summary"]["suiteId"], "tool-calls");
5467        assert_eq!(value["summary"]["fixtureCount"], 2);
5468        assert_eq!(value["summary"]["reliability"]["retryAttempts"], 2);
5469        assert_eq!(value["summary"]["reliability"]["retryRecoveries"], 1);
5470        assert_eq!(value["markdown"], "# Report");
5471    }
5472
5473    #[test]
5474    fn deadline_notifications_use_camel_case_fields() {
5475        let value = serde_json::to_value(TurnDeadlineExceededNotification {
5476            thread_id: "thread-a".to_string(),
5477            turn_id: "turn-a".to_string(),
5478            deadline: OffsetDateTime::UNIX_EPOCH,
5479            partial_result: "partial evidence".to_string(),
5480        })
5481        .unwrap();
5482        assert_eq!(value["threadId"], "thread-a");
5483        assert_eq!(value["turnId"], "turn-a");
5484        assert_eq!(value["partialResult"], "partial evidence");
5485
5486        let value = serde_json::to_value(TurnPartialResultNotification {
5487            thread_id: "thread-a".to_string(),
5488            turn_id: "turn-a".to_string(),
5489            summary: "partial evidence".to_string(),
5490        })
5491        .unwrap();
5492        assert_eq!(value["summary"], "partial evidence");
5493    }
5494
5495    #[test]
5496    fn thread_goal_set_params_preserve_null_budget_clear() {
5497        let params: ThreadGoalSetParams = serde_json::from_value(serde_json::json!({
5498            "threadId": "thread-a",
5499            "tokenBudget": null
5500        }))
5501        .unwrap();
5502        assert_eq!(params.thread_id, "thread-a");
5503        assert_eq!(params.token_budget, Some(None));
5504
5505        let params: ThreadGoalSetParams = serde_json::from_value(serde_json::json!({
5506            "threadId": "thread-a"
5507        }))
5508        .unwrap();
5509        assert_eq!(params.token_budget, None);
5510    }
5511
5512    #[test]
5513    fn media_protocol_structs_use_camel_case_fields() {
5514        let read: MediaReadParams = serde_json::from_value(serde_json::json!({
5515            "artifactId": "media-1",
5516            "maxBytes": 1024
5517        }))
5518        .unwrap();
5519        assert_eq!(read.artifact_id, "media-1");
5520        assert_eq!(read.max_bytes, Some(1024));
5521
5522        let attach = MediaAttachToTurnResult {
5523            attachment: MediaAttachment {
5524                artifact_id: "media-1".to_string(),
5525                mime_type: "image/png".to_string(),
5526                data_url: "data:image/png;base64,YWJj".to_string(),
5527            },
5528            image: Some(InputImage {
5529                image_url: "data:image/png;base64,YWJj".to_string(),
5530            }),
5531        };
5532        let value = serde_json::to_value(attach).unwrap();
5533        assert_eq!(value["attachment"]["artifactId"], "media-1");
5534        assert_eq!(value["attachment"]["mimeType"], "image/png");
5535        assert_eq!(value["image"]["image_url"], "data:image/png;base64,YWJj");
5536    }
5537
5538    #[test]
5539    fn media_image_generation_params_flatten_the_canonical_request() {
5540        let params: MediaImageGenerateParams = serde_json::from_value(serde_json::json!({
5541            "prompt": "a tiny test image",
5542            "provider": "openai",
5543            "model": "gpt-image-2",
5544            "count": 2,
5545            "size": "1024x1024",
5546            "threadId": "thread-1"
5547        }))
5548        .unwrap();
5549        assert_eq!(params.request.prompt, "a tiny test image");
5550        assert_eq!(params.request.provider.as_deref(), Some("openai"));
5551        assert_eq!(params.request.count, Some(2));
5552        assert_eq!(params.thread_id.as_deref(), Some("thread-1"));
5553
5554        let result = MediaImageProvidersListResult {
5555            default_provider: "fake".to_string(),
5556            providers: vec![roder_api::media::MediaProviderDescriptor {
5557                id: "fake".to_string(),
5558                display_name: "Fake Media (offline)".to_string(),
5559                supports_images: true,
5560                configured: true,
5561                ..roder_api::media::MediaProviderDescriptor::default()
5562            }],
5563        };
5564        let value = serde_json::to_value(result).unwrap();
5565        assert_eq!(value["defaultProvider"], "fake");
5566        assert_eq!(value["providers"][0]["supportsImages"], true);
5567    }
5568
5569    #[test]
5570    fn transcript_json_rpc_boundary_values_round_trip() {
5571        let request = JsonRpcRequest {
5572            jsonrpc: "2.0".to_string(),
5573            id: Some(serde_json::json!(7)),
5574            method: "processes/list".to_string(),
5575            params: Some(serde_json::json!({"includeCompleted": true})),
5576        };
5577        let response = JsonRpcResponse {
5578            jsonrpc: "2.0".to_string(),
5579            id: Some(serde_json::json!(7)),
5580            result: Some(serde_json::json!({"processes": []})),
5581            error: None,
5582        };
5583        let notification = JsonRpcNotification {
5584            jsonrpc: "2.0".to_string(),
5585            method: "processes/changed".to_string(),
5586            params: serde_json::json!({"processId": "proc-a"}),
5587        };
5588
5589        assert_eq!(
5590            serde_json::to_value(&request).unwrap()["method"],
5591            "processes/list"
5592        );
5593        assert_eq!(
5594            serde_json::to_value(&response).unwrap()["result"]["processes"],
5595            serde_json::json!([])
5596        );
5597        assert_eq!(
5598            serde_json::to_value(&notification).unwrap()["method"],
5599            "processes/changed"
5600        );
5601    }
5602}