relay_knowledge/api/contracts/
file_index.rs1use serde::{Deserialize, Serialize};
2
3use crate::domain::{
4 FileContentReadModelCursor, FileContentSearchHit, FileIndexScanSummary, FileSearchHit,
5 FreshnessPolicy,
6};
7
8use super::ApiMetadata;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum FileIndexFreshnessState {
14 Fresh,
15 Pending,
16 Paused,
17 Stale,
18 Degraded,
19 Overflow,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct FileIndexFreshnessCursor {
25 pub source_scope: String,
26 pub root_id: String,
27 pub root_path: String,
28 pub backend: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub scan_watermark_ms: Option<u64>,
31 pub indexed_file_count: usize,
32 pub missing_file_count: usize,
33 pub scan_error_count: usize,
34 pub overflow: bool,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub last_error: Option<String>,
37}
38
39#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
41pub struct FileIndexLag {
42 pub configured_root_count: usize,
43 pub indexed_root_count: usize,
44 pub pending_root_count: usize,
45 pub stale_root_count: usize,
46 pub overflow_root_count: usize,
47 pub missing_file_count: usize,
48 pub pending_task_count: usize,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct FileIndexFreshnessDiagnostics {
54 pub state: FileIndexFreshnessState,
55 pub freshness_policy: FreshnessPolicy,
56 pub graph_version: u64,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub source_scope: Option<String>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub root_id: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub stale_reason: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub degraded_reason: Option<String>,
65 pub index_lag: FileIndexLag,
66 pub cursors: Vec<FileIndexFreshnessCursor>,
67 pub direct_source_read_required: bool,
68 pub bounded_rescan_required: bool,
69 #[serde(default, skip_serializing_if = "Vec::is_empty")]
70 pub direct_source_read_paths: Vec<String>,
71 #[serde(default, skip_serializing_if = "Vec::is_empty")]
72 pub agent_instructions: Vec<String>,
73 #[serde(default, skip_serializing_if = "Vec::is_empty")]
74 pub content_read_model_cursors: Vec<FileContentReadModelCursor>,
75}
76
77impl FileIndexFreshnessDiagnostics {
78 pub fn legacy_unknown() -> Self {
79 Self {
80 state: FileIndexFreshnessState::Degraded,
81 freshness_policy: FreshnessPolicy::AllowStale,
82 graph_version: 0,
83 source_scope: None,
84 root_id: None,
85 stale_reason: None,
86 degraded_reason: Some("response did not include file freshness diagnostics".to_owned()),
87 index_lag: FileIndexLag::default(),
88 cursors: Vec::new(),
89 direct_source_read_required: false,
90 bounded_rescan_required: false,
91 direct_source_read_paths: Vec::new(),
92 agent_instructions: Vec::new(),
93 content_read_model_cursors: Vec::new(),
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct FileIndexRequest {
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub source_scope: Option<String>,
103 #[serde(default)]
104 pub roots: Vec<String>,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109pub struct FileIndexResponse {
110 pub metadata: ApiMetadata,
111 pub summary: FileIndexScanSummary,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct FileQueryRequest {
117 pub query: String,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub source_scope: Option<String>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub root_id: Option<String>,
122 pub limit: usize,
123 #[serde(default)]
124 pub freshness_policy: FreshnessPolicy,
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129pub struct FileQueryResponse {
130 pub metadata: ApiMetadata,
131 pub query: String,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub source_scope: Option<String>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub root_id: Option<String>,
136 #[serde(default = "FileIndexFreshnessDiagnostics::legacy_unknown")]
137 pub freshness: FileIndexFreshnessDiagnostics,
138 pub results: Vec<FileSearchHit>,
139 pub truncated: bool,
140 pub duration_ms: u64,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub degraded_reason: Option<String>,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub struct FileContentQueryRequest {
148 pub query: String,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub source_scope: Option<String>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub root_id: Option<String>,
153 pub limit: usize,
154 #[serde(default)]
155 pub freshness_policy: FreshnessPolicy,
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct FileContentQueryResponse {
161 pub metadata: ApiMetadata,
162 pub query: String,
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub source_scope: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub root_id: Option<String>,
167 #[serde(default = "FileIndexFreshnessDiagnostics::legacy_unknown")]
168 pub freshness: FileIndexFreshnessDiagnostics,
169 pub results: Vec<FileContentSearchHit>,
170 pub truncated: bool,
171 pub duration_ms: u64,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub degraded_reason: Option<String>,
174}