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