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