1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex};
3
4use serde::{ser::SerializeStruct, Deserialize, Serialize};
5
6#[derive(Clone, Copy, Default)]
7pub struct DbState;
8
9#[derive(Clone, Default)]
10pub(super) struct StatusCache {
11 pub(super) entry: Arc<Mutex<Option<StatusCacheEntry>>>,
12}
13
14#[derive(Clone)]
15pub(super) struct StatusCacheEntry {
16 pub(super) generated_at_epoch: i64,
17 pub(super) payload: serde_json::Value,
18}
19
20#[derive(Deserialize, Default)]
21pub(super) struct StatusParams {
22 pub refresh: Option<bool>,
23}
24
25#[derive(Deserialize)]
26pub(super) struct SearchParams {
27 pub query: Option<String>,
28 pub project: Option<String>,
29 #[serde(rename = "type")]
30 pub memory_type: Option<String>,
31 pub limit: Option<i64>,
32 pub offset: Option<i64>,
33 pub include_stale: Option<bool>,
34 pub include_suppressed: Option<bool>,
35 pub branch: Option<String>,
36 pub multi_hop: Option<bool>,
37 pub explain: Option<bool>,
38}
39
40#[derive(Serialize)]
41pub(super) struct SearchResponse {
42 pub data: Vec<MemoryItem>,
43 pub meta: Meta,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub multi_hop: Option<MultiHopInfo>,
46 #[serde(skip_serializing_if = "Vec::is_empty")]
49 pub raw_hits: Vec<RawHitItem>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub raw_hits_error: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub explain: Option<crate::retrieval::search::SearchExplain>,
54}
55
56#[derive(Serialize)]
57pub(super) struct RawHitItem {
58 pub id: i64,
59 pub session_id: String,
60 pub project: String,
61 pub role: String,
62 pub preview: String,
63 pub source: String,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub branch: Option<String>,
66 pub created_at_epoch: i64,
67}
68
69#[derive(Serialize)]
70pub(super) struct MultiHopInfo {
71 pub hops: u8,
72 pub entities_discovered: Vec<String>,
73}
74
75#[derive(Serialize)]
76pub(super) struct MemoryItem {
77 pub id: i64,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub version: Option<i64>,
80 pub title: String,
81 pub content: String,
82 pub memory_type: String,
83 pub project: String,
84 pub scope: String,
85 pub status: String,
86 pub staleness: crate::memory::MemoryStalenessLabel,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub topic_key: Option<String>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub branch: Option<String>,
91 pub created_at_epoch: i64,
92 pub updated_at_epoch: i64,
93}
94
95#[derive(Serialize)]
96pub(super) struct Meta {
97 pub count: usize,
98 pub has_more: bool,
99 pub limit: i64,
100 pub offset: i64,
101}
102
103#[derive(Serialize)]
104pub(super) struct ErrorResponse {
105 pub error: ErrorDetail,
106}
107
108#[derive(Serialize)]
109pub(super) struct ErrorDetail {
110 pub code: String,
111 pub message: String,
112}
113
114#[derive(Serialize)]
115pub(super) struct CapabilitiesResponse {
116 pub version: &'static str,
117 pub schema_version: i64,
118 pub api_version: u16,
119 pub features: CapabilitiesFeatures,
120 pub endpoints: BTreeMap<&'static str, &'static str>,
121}
122
123#[derive(Serialize)]
124pub(super) struct CapabilitiesFeatures {
125 pub health: bool,
126 pub status: bool,
127 pub stats: bool,
128 pub search: bool,
129 pub search_explain: bool,
130 pub memory_list: bool,
131 pub memory_detail: bool,
132 pub save_memory: bool,
133 pub memory_archive: bool,
134 pub memory_restore: bool,
135 pub memory_delete: bool,
136 pub candidate_rows: bool,
137 pub candidate_filters: bool,
138 pub candidate_review: bool,
139 pub candidate_detail: bool,
140 pub candidate_evidence: bool,
141 pub candidate_review_safe: bool,
142 pub observations: bool,
143 pub sessions: bool,
144 pub workstreams: bool,
145 pub events: bool,
146 pub tasks: bool,
147 pub graph: bool,
148 pub user_recall: bool,
149 pub user_recall_usage_policy: bool,
150}
151
152#[derive(Serialize)]
153pub(super) struct HealthResponse {
154 pub ok: bool,
155 pub version: &'static str,
156 pub api_version: u16,
157 pub schema_version: i64,
158}
159
160#[derive(Deserialize)]
161pub(super) struct SaveMemoryRequest {
162 pub text: String,
163 #[serde(default)]
164 pub title: Option<String>,
165 #[serde(default)]
166 pub project: Option<String>,
167 #[serde(default)]
168 pub session_id: Option<String>,
169 #[serde(default)]
170 pub host: Option<String>,
171 #[serde(default)]
172 pub topic_key: Option<String>,
173 #[serde(default)]
174 pub memory_type: Option<String>,
175 #[serde(default)]
176 pub files: Option<Vec<String>>,
177 #[serde(default)]
178 pub scope: Option<String>,
179 #[serde(default)]
180 pub reference_time_epoch: Option<i64>,
181 #[serde(default)]
182 pub created_at_epoch: Option<i64>,
183 #[serde(default)]
184 pub branch: Option<String>,
185 #[serde(default)]
186 pub local_path: Option<String>,
187 #[serde(default)]
188 pub local_copy_enabled: Option<bool>,
189 #[serde(default)]
190 pub claim_enabled: Option<bool>,
191 #[serde(default)]
192 pub claim_source: Option<String>,
193 #[serde(default)]
194 pub acknowledge_pattern: Option<String>,
195}
196
197#[derive(Deserialize)]
198pub(super) struct UserRecallRequest {
199 pub query: String,
200 #[serde(default)]
201 pub project: Option<String>,
202 #[serde(default)]
203 pub cwd: Option<String>,
204 #[serde(default)]
205 pub task_intent: Option<String>,
206 #[serde(default)]
207 pub current_files: Vec<String>,
208 #[serde(default)]
209 pub host: Option<String>,
210 #[serde(default)]
211 pub owner_scope: Option<String>,
212 #[serde(default)]
213 pub owner_key: Option<String>,
214 #[serde(default)]
215 pub state_keys: Vec<String>,
216 #[serde(default)]
217 pub include_sensitive: bool,
218 #[serde(default)]
219 pub include_suppressed: bool,
220 #[serde(default)]
221 pub limit: Option<i64>,
222 #[serde(default)]
223 pub budget_chars: Option<usize>,
224}
225
226#[derive(Serialize)]
227pub(super) struct SaveMemoryResponse {
228 pub id: i64,
229 pub status: String,
230 pub memory_type: String,
231 pub project: String,
232 pub scope: String,
233 pub topic_key: Option<String>,
234 pub branch: Option<String>,
235 pub operation: String,
236 pub created_at_epoch: i64,
237 pub reference_time_epoch: i64,
238 pub updated_at_epoch: i64,
239 pub upserted: bool,
240 pub local_copy: LocalCopyResponse,
241 pub local_status: String,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub local_path: Option<String>,
244 pub claim_status: String,
245 pub claim_id: Option<i64>,
246 pub claim_error: Option<String>,
247 pub next_step: SaveMemoryNextStepResponse,
248}
249
250#[derive(Serialize)]
251pub(super) struct LocalCopyResponse {
252 pub status: String,
253 pub path: Option<String>,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub reason: Option<String>,
256}
257
258#[derive(Serialize)]
259pub(super) struct SaveMemoryNextStepResponse {
260 pub tool: String,
261 pub ids: Vec<i64>,
262 pub source: String,
263 pub reason: String,
264}
265
266#[derive(Deserialize)]
267pub(super) struct ShowParams {
268 pub id: i64,
269 pub include_suppressed: Option<bool>,
270}
271
272#[derive(Deserialize)]
273pub(super) struct MemoryDetailParams {
274 pub include_suppressed: Option<bool>,
275}
276#[derive(Deserialize)]
279pub(super) struct ListParams {
280 pub project: Option<String>,
281 #[serde(rename = "type")]
282 pub memory_type: Option<String>,
283 pub scope: Option<String>,
284 pub status: Option<String>,
285 pub branch: Option<String>,
286 pub q: Option<String>,
287 pub include_suppressed: Option<bool>,
288 pub limit: Option<i64>,
289 pub offset: Option<i64>,
290}
291
292pub(super) struct ListMeta {
293 pub count: usize,
294 pub total: i64,
295 pub limit: i64,
296 pub offset: i64,
297}
298
299impl Serialize for ListMeta {
300 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
301 where
302 S: serde::Serializer,
303 {
304 let count = i64::try_from(self.count).unwrap_or(i64::MAX);
305 let consumed = self.offset.saturating_add(count);
306 let has_more = consumed < self.total;
307 let next_offset = has_more.then_some(consumed);
308
309 let mut state = serializer.serialize_struct("ListMeta", 6)?;
310 state.serialize_field("count", &self.count)?;
311 state.serialize_field("total", &self.total)?;
312 state.serialize_field("limit", &self.limit)?;
313 state.serialize_field("offset", &self.offset)?;
314 state.serialize_field("has_more", &has_more)?;
315 state.serialize_field("next_offset", &next_offset)?;
316 state.end()
317 }
318}
319
320#[derive(Serialize)]
321pub(super) struct ListResponse<T: Serialize> {
322 pub data: Vec<T>,
323 pub meta: ListMeta,
324}
325
326#[derive(Deserialize)]
327pub(super) struct CandidateParams {
328 pub project: Option<String>,
329 pub status: Option<String>,
330 #[serde(rename = "type")]
331 pub memory_type: Option<String>,
332 pub block_reason: Option<String>,
333 pub topic_key: Option<String>,
334 pub contains: Option<String>,
335 pub min_confidence: Option<f64>,
336 pub older_than_days: Option<i64>,
337 pub limit: Option<i64>,
338 pub offset: Option<i64>,
339}
340
341#[derive(Deserialize)]
342pub(super) struct BlockedParams {
343 pub project: Option<String>,
344}
345
346#[derive(Serialize)]
347pub(super) struct BlockedReasonItem {
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub reason: Option<String>,
350 pub pending: i64,
351 pub example_ids: Vec<i64>,
352}
353
354#[derive(Serialize)]
355pub(super) struct CandidateItem {
356 pub id: i64,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub project: Option<String>,
359 pub memory_type: String,
360 pub text: String,
361 pub scope: String,
362 pub confidence: f64,
363 pub risk_class: String,
364 pub review_status: String,
365 pub evidence_count: i64,
366 pub created_at_epoch: i64,
367}
368
369#[derive(Deserialize)]
370pub(super) struct CandidateEditRequest {
371 #[serde(default)]
372 pub scope: Option<String>,
373 #[serde(default, rename = "memory_type")]
374 pub memory_type: Option<String>,
375 #[serde(default)]
376 pub topic_key: Option<String>,
377 #[serde(default)]
378 pub text: Option<String>,
379}
380
381#[derive(Deserialize, Default)]
382pub(super) struct CandidateApproveRequest {
383 #[serde(default)]
384 pub acknowledge_pattern: Option<String>,
385}
386
387#[derive(Serialize)]
388pub(super) struct CandidateReviewResponse {
389 pub candidate_id: i64,
390 pub status: String,
391 #[serde(skip_serializing_if = "Option::is_none")]
392 pub memory_id: Option<i64>,
393}
394
395#[derive(Serialize)]
396pub(super) struct CandidateDetailResponse {
397 pub data: CandidateDetailItem,
398 pub evidence: Vec<CandidateEvidenceItem>,
399 pub decision: CandidateReviewDecision,
400}
401
402#[derive(Serialize)]
403pub(super) struct CandidateDetailItem {
404 pub id: i64,
405 pub project: Option<String>,
406 pub scope: String,
407 pub memory_type: String,
408 pub topic_key: String,
409 pub text: String,
410 pub source_kind: Option<String>,
411 pub source_project: Option<String>,
412 pub target_project: Option<String>,
413 pub owner_scope: Option<String>,
414 pub owner_key: Option<String>,
415 pub topic_domain: Option<String>,
416 pub routing_confidence: Option<f64>,
417 pub routing_reason: Option<String>,
418 pub context_class: Option<String>,
419 pub confidence: f64,
420 pub risk_class: String,
421 pub review_status: String,
422 pub auto_promote_block_reason: Option<String>,
423 pub source_trust_class: String,
424 pub quarantine_pattern_id: Option<String>,
425 pub quarantine_pattern_version: Option<i64>,
426 pub version: i64,
427 pub created_at_epoch: i64,
428 pub updated_at_epoch: i64,
429}
430
431#[derive(Serialize)]
432pub(super) struct CandidateEvidenceItem {
433 pub source_kind: &'static str,
434 pub source_id: i64,
435 pub event_type: Option<String>,
436 pub role: Option<String>,
437 pub tool_name: Option<String>,
438 pub created_at_epoch: Option<i64>,
439 pub summary: String,
440 pub preview: String,
441 pub provenance_status: String,
442 pub redacted: bool,
443}
444
445#[derive(Serialize)]
446pub(super) struct CandidateReviewDecision {
447 pub can_review: bool,
448 pub blocked_reasons: Vec<String>,
449}
450
451#[derive(Debug, Clone, Deserialize)]
452#[serde(deny_unknown_fields)]
453pub(super) struct CandidateSafeApproveRequest {
454 pub reason: String,
455 pub expected_version: i64,
456 pub idempotency_key: String,
457 #[serde(default)]
458 pub acknowledge_pattern: Option<String>,
459}
460
461#[derive(Debug, Clone, Deserialize)]
462#[serde(deny_unknown_fields)]
463pub(super) struct CandidateSafeRejectRequest {
464 pub reason: String,
465 pub expected_version: i64,
466 pub idempotency_key: String,
467}
468
469#[derive(Debug, Clone, Deserialize)]
470#[serde(deny_unknown_fields)]
471pub(super) struct CandidateSafeEditRequest {
472 pub reason: String,
473 pub expected_version: i64,
474 pub idempotency_key: String,
475 #[serde(default)]
476 pub scope: Option<String>,
477 #[serde(default, rename = "memory_type")]
478 pub memory_type: Option<String>,
479 #[serde(default)]
480 pub topic_key: Option<String>,
481 #[serde(default)]
482 pub text: Option<String>,
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
486pub(super) struct CandidateSafeReviewResponse {
487 pub response_schema_version: i64,
488 pub operation_id: String,
489 pub audit_id: i64,
490 pub candidate_id: i64,
491 #[serde(skip_serializing_if = "Option::is_none")]
492 pub memory_id: Option<i64>,
493 pub action: String,
494 pub before_status: String,
495 pub after_status: String,
496 pub version: i64,
497 pub occurred_at_epoch: i64,
498 pub replayed: bool,
499}
500
501#[derive(Debug, Clone, Deserialize)]
502#[serde(deny_unknown_fields)]
503pub(super) struct MemorySafeGovernanceRequest {
504 pub reason: String,
505 pub expected_version: i64,
506 pub idempotency_key: String,
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
510pub(super) struct MemorySafeGovernanceResponse {
511 pub response_schema_version: i64,
512 pub operation_id: String,
513 pub audit_id: i64,
514 pub memory_id: i64,
515 pub action: String,
516 pub before_status: String,
517 pub after_status: String,
518 pub version: i64,
519 pub occurred_at_epoch: i64,
520 pub replayed: bool,
521}
522
523#[derive(Serialize)]
524pub(super) struct SafeMutationErrorResponse {
525 pub error: SafeMutationErrorDetail,
526}
527
528#[derive(Serialize)]
529pub(super) struct SafeMutationErrorDetail {
530 pub code: String,
531 pub message: String,
532 #[serde(skip_serializing_if = "Option::is_none")]
533 pub operation_id: Option<String>,
534}
535
536#[derive(Deserialize)]
537pub(super) struct GraphParams {
538 pub project: Option<String>,
539 pub include_suppressed: Option<bool>,
540 pub limit: Option<i64>,
541}
542
543#[derive(Serialize)]
544pub(super) struct GraphNodeItem {
545 pub id: i64,
546 pub name: String,
547 pub entity_type: Option<String>,
548 pub mention_count: i64,
549 pub mems: Vec<i64>,
550}
551
552#[derive(Serialize)]
553pub(super) struct GraphEdgeItem {
554 pub a: i64,
555 pub b: i64,
556 pub w: i64,
557}
558
559#[derive(Serialize)]
560pub(super) struct GraphResponse {
561 pub nodes: Vec<GraphNodeItem>,
562 pub edges: Vec<GraphEdgeItem>,
563}
564
565#[derive(Serialize)]
566pub(super) struct MemoryEdgeItem {
567 pub id: i64,
568 pub edge_type: String,
569 pub from_memory_id: Option<i64>,
570 pub to_memory_id: Option<i64>,
571 pub confidence: Option<f64>,
572}
573
574#[derive(Serialize)]
575pub(super) struct MemoryDetailResponse {
576 #[serde(flatten)]
577 pub memory: MemoryItem,
578 pub entities: Vec<String>,
579 pub edges: Vec<MemoryEdgeItem>,
580}
581
582#[derive(Serialize)]
583pub(super) struct TypeCount {
584 pub memory_type: String,
585 pub count: i64,
586}
587
588#[derive(Serialize)]
589pub(super) struct StatsResponse {
590 pub active_memories: i64,
591 pub total_memories: i64,
592 pub pending_candidates: i64,
593 pub captured_events: i64,
594 pub pending_extraction_tasks: i64,
595 pub ai_calls: i64,
596 pub ai_cost_usd: f64,
597 pub ai_total_tokens: i64,
598 pub type_distribution: Vec<TypeCount>,
599}