Skip to main content

relay_knowledge/domain/code/context/
mod.rs

1//! Defines bounded code-graph context requests, evidence, and response contracts.
2
3use serde::{Deserialize, Serialize};
4
5use super::{
6    CodeQueryKind, CodeRepositorySelector, CodeRetrievalHit, CodeRetrievalLayer, DomainError,
7    FreshnessPolicy, RepositoryCodeRange, error::required_text,
8};
9
10pub const CODEGRAPH_CONTEXT_DEFAULT_LIMIT: usize = 8;
11pub const CODEGRAPH_CONTEXT_MAX_LIMIT: usize = 20;
12pub const CODEGRAPH_CONTEXT_MIN_BYTES: usize = 1024;
13pub const CODEGRAPH_CONTEXT_DEFAULT_MAX_BYTES: usize = 65_536;
14pub const CODEGRAPH_CONTEXT_MAX_BYTES: usize = 262_144;
15
16/// Agent-facing one-call code graph context request.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct CodeGraphContextRequest {
19    pub repository: CodeRepositorySelector,
20    pub query: String,
21    pub limit: usize,
22    pub freshness_policy: FreshnessPolicy,
23    pub max_context_bytes: usize,
24    #[serde(default = "default_include_code")]
25    pub include_code: bool,
26    #[serde(default)]
27    pub exclude_generated: bool,
28}
29
30impl CodeGraphContextRequest {
31    /// Validates text and hard bounds before context orchestration starts.
32    pub fn new(
33        repository: CodeRepositorySelector,
34        query: impl Into<String>,
35        limit: usize,
36        freshness_policy: FreshnessPolicy,
37        max_context_bytes: usize,
38        include_code: bool,
39        exclude_generated: bool,
40    ) -> Result<Self, DomainError> {
41        let limit = match limit {
42            1..=CODEGRAPH_CONTEXT_MAX_LIMIT => limit,
43            0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
44            _ => {
45                return Err(DomainError::invalid(
46                    "limit",
47                    "must be 20 or less for codegraph context",
48                ));
49            }
50        };
51        let max_context_bytes = match max_context_bytes {
52            0..CODEGRAPH_CONTEXT_MIN_BYTES => {
53                return Err(DomainError::invalid(
54                    "max_context_bytes",
55                    "must be at least 1024 for codegraph context",
56                ));
57            }
58            CODEGRAPH_CONTEXT_MIN_BYTES..=CODEGRAPH_CONTEXT_MAX_BYTES => max_context_bytes,
59            _ => {
60                return Err(DomainError::invalid(
61                    "max_context_bytes",
62                    "must be 262144 or less",
63                ));
64            }
65        };
66
67        Ok(Self {
68            repository,
69            query: required_text("query", query)?,
70            limit,
71            freshness_policy,
72            max_context_bytes,
73            include_code,
74            exclude_generated,
75        })
76    }
77}
78
79fn default_include_code() -> bool {
80    true
81}
82
83/// Context item provenance used by packed codegraph responses.
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub struct CodeGraphContextProvenance {
86    pub query_kind: CodeQueryKind,
87    pub retrieval_layers: Vec<CodeRetrievalLayer>,
88    pub score: f64,
89}
90
91/// Compact code excerpt retained inside the context byte budget.
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct CodeGraphCodeExcerpt {
94    pub path: String,
95    pub language_id: String,
96    pub line_range: RepositoryCodeRange,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub symbol_snapshot_id: Option<String>,
99    pub provenance: CodeGraphContextProvenance,
100    pub excerpt: String,
101}
102
103/// Structural risk hint around a seed symbol or file.
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct CodeGraphImpactHint {
106    pub path: String,
107    pub line_range: RepositoryCodeRange,
108    pub relationship: String,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub symbol_snapshot_id: Option<String>,
111    pub retrieval_layers: Vec<CodeRetrievalLayer>,
112    pub score: f64,
113}
114
115/// Budget consumed by one codegraph context orchestration.
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub struct CodeGraphContextBudget {
118    pub limit: usize,
119    pub max_context_bytes: usize,
120    pub candidate_count: usize,
121    pub returned_count: usize,
122    pub context_bytes: usize,
123    pub elapsed_ms: u64,
124}
125
126/// Internal context packing input grouped by structural role.
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct CodeGraphContextPack {
129    #[serde(default, skip_serializing_if = "Vec::is_empty")]
130    pub business_context: Vec<crate::domain::BusinessTerm>,
131    pub entry_points: Vec<CodeRetrievalHit>,
132    pub related_symbols: Vec<CodeRetrievalHit>,
133    pub graph_paths: Vec<CodeRetrievalHit>,
134    pub impact_hints: Vec<CodeGraphImpactHint>,
135    pub code_excerpts: Vec<CodeGraphCodeExcerpt>,
136}
137
138#[cfg(test)]
139mod mod_tests;