use std::collections::BTreeMap;
use super::{MetadataObject, ReadPresentationMode, ResponseMode};
use crate::domain::{
ChannelHealthStatus, EvidenceAnchor, PathClass, SourceClass, model::SymbolMatch,
model::TextMatch,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SearchPatternType {
Literal,
Regex,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExploreOperation {
Probe,
Zoom,
Refine,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreAnchor {
pub start_line: usize,
pub start_column: usize,
pub end_line: usize,
pub end_column: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreCursor {
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreLineWindow {
pub start_line: usize,
pub end_line: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreWindow {
pub start_line: usize,
pub end_line: usize,
pub bytes: usize,
pub content: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreMatch {
pub match_id: String,
pub start_line: usize,
pub start_column: usize,
pub end_line: usize,
pub end_column: usize,
pub excerpt: String,
pub window: ExploreWindow,
pub anchor: ExploreAnchor,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreMetadata {
pub lossy_utf8: bool,
pub effective_context_lines: usize,
pub effective_max_matches: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_efficiency: Option<ContextEfficiencyMetadata>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreParams {
pub path: String,
pub repository_id: Option<String>,
pub operation: ExploreOperation,
pub query: Option<String>,
pub pattern_type: Option<SearchPatternType>,
pub anchor: Option<ExploreAnchor>,
pub context_lines: Option<usize>,
pub max_matches: Option<usize>,
pub resume_from: Option<ExploreCursor>,
pub presentation_mode: Option<ReadPresentationMode>,
pub include_context_efficiency: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ExploreResponse {
pub repository_id: String,
pub path: String,
pub operation: ExploreOperation,
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern_type: Option<SearchPatternType>,
pub total_lines: usize,
pub scan_scope: ExploreLineWindow,
#[serde(skip_serializing_if = "Option::is_none")]
pub window: Option<ExploreWindow>,
pub total_matches: usize,
pub matches: Vec<ExploreMatch>,
pub truncated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub resume_from: Option<ExploreCursor>,
pub metadata: ExploreMetadata,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct SearchTextParams {
pub query: String,
pub pattern_type: Option<SearchPatternType>,
pub repository_id: Option<String>,
pub path_regex: Option<String>,
pub limit: Option<usize>,
pub context_lines: Option<usize>,
pub case_sensitive: Option<bool>,
pub ignore_case: Option<bool>,
pub word: Option<bool>,
pub files_with_matches: Option<bool>,
pub count_only: Option<bool>,
pub glob: Option<String>,
pub exclude_glob: Option<String>,
pub include_hidden: Option<bool>,
pub max_count_per_file: Option<usize>,
pub collapse_by_file: Option<bool>,
pub response_mode: Option<ResponseMode>,
pub include_context_efficiency: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SearchTextResponse {
pub total_matches: usize,
pub matches: Vec<TextMatch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<SearchTextMetadata>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SearchLexicalBackendMetadata {
Native,
Ripgrep,
Mixed,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SearchTextMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub lexical_backend: Option<SearchLexicalBackendMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lexical_backend_note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_efficiency: Option<ContextEfficiencyMetadata>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ContextEfficiencyMetadata {
pub indexed_readable_files: usize,
pub indexed_readable_bytes: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub indexed_min_mtime_ns: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub indexed_max_mtime_ns: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub candidate_input_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub candidate_output_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub returned_match_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub returned_unique_paths: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub returned_unique_file_bytes: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub returned_source_bytes_estimate: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub matched_file_context_saved_bytes_estimate: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub matched_file_context_saved_percent_estimate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub corpus_context_saved_bytes_estimate: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub corpus_context_saved_percent_estimate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub corpus_narrowing_ratio_estimate: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_duration_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub narrowing_ratio_estimate: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stage_attribution: Option<ContextEfficiencyStageAttribution>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ContextEfficiencyStageAttribution {
pub candidate_input_count: usize,
pub candidate_output_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridChannelWeightsParams {
pub lexical: Option<f32>,
pub graph: Option<f32>,
pub semantic: Option<f32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridParams {
pub query: String,
pub repository_id: Option<String>,
pub language: Option<String>,
pub limit: Option<usize>,
pub weights: Option<SearchHybridChannelWeightsParams>,
pub semantic: Option<bool>,
pub response_mode: Option<ResponseMode>,
pub include_context_efficiency: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridMatch {
#[serde(skip_serializing_if = "Option::is_none")]
pub match_id: Option<String>,
pub repository_id: String,
pub path: String,
pub line: usize,
pub column: usize,
pub excerpt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub anchor: Option<EvidenceAnchor>,
pub blended_score: f32,
pub lexical_score: f32,
pub graph_score: f32,
pub semantic_score: f32,
pub lexical_sources: Vec<String>,
pub graph_sources: Vec<String>,
pub semantic_sources: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_class: Option<PathClass>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_class: Option<SourceClass>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub surface_families: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub navigation_hint: Option<SearchHybridNavigationHint>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rank_reasons: Vec<SearchHybridRankReason>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SearchHybridRankReason {
ExactSymbolMatch,
ExactTextMatch,
StrongLexicalAnchor,
GraphAdjacency,
SemanticContribution,
WitnessOnlyFallback,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridNavigationHint {
pub pivotable: bool,
pub document_symbols: bool,
pub go_to_definition: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridUtilitySummary {
pub pivotable_match_count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub best_pivot_rank: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub best_pivot_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub best_pivot_repository_id: Option<String>,
pub symbol_navigation_ready: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridChannelDiagnostic {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridChannelMetadata {
pub status: ChannelHealthStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
pub candidate_count: usize,
pub hit_count: usize,
pub match_count: usize,
pub diagnostic_count: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub diagnostics: Vec<SearchHybridChannelDiagnostic>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridDiagnosticsSummary {
pub walk: usize,
pub read: usize,
pub total: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridStageSample {
pub elapsed_us: u64,
pub input_count: usize,
pub output_count: usize,
}
impl From<&crate::searcher::SearchStageSample> for SearchHybridStageSample {
fn from(value: &crate::searcher::SearchStageSample) -> Self {
Self {
elapsed_us: value.elapsed_us,
input_count: value.input_count,
output_count: value.output_count,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridStageAttribution {
pub candidate_intake: SearchHybridStageSample,
pub freshness_validation: SearchHybridStageSample,
pub scan: SearchHybridStageSample,
pub witness_scoring: SearchHybridStageSample,
pub graph_expansion: SearchHybridStageSample,
pub semantic_retrieval: SearchHybridStageSample,
pub anchor_blending: SearchHybridStageSample,
pub document_aggregation: SearchHybridStageSample,
pub final_diversification: SearchHybridStageSample,
}
impl From<&crate::searcher::SearchStageAttribution> for SearchHybridStageAttribution {
fn from(value: &crate::searcher::SearchStageAttribution) -> Self {
Self {
candidate_intake: SearchHybridStageSample::from(&value.candidate_intake),
freshness_validation: SearchHybridStageSample::from(&value.freshness_validation),
scan: SearchHybridStageSample::from(&value.scan),
witness_scoring: SearchHybridStageSample::from(&value.witness_scoring),
graph_expansion: SearchHybridStageSample::from(&value.graph_expansion),
semantic_retrieval: SearchHybridStageSample::from(&value.semantic_retrieval),
anchor_blending: SearchHybridStageSample::from(&value.anchor_blending),
document_aggregation: SearchHybridStageSample::from(&value.document_aggregation),
final_diversification: SearchHybridStageSample::from(&value.final_diversification),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ResponseFreshnessRepositoryMetadata {
pub repository_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub snapshot_id: Option<String>,
pub manifest: String,
pub semantic: String,
pub dirty_root: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub cacheable_reason: Option<String>,
pub candidate_source: String,
pub using_live_walk: bool,
pub refresh_in_progress: bool,
#[serde(default)]
pub active_index_tasks: Vec<Value>,
pub recommended_client_behavior: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ResponseFreshnessBasisMetadata {
pub mode: String,
pub cacheable: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub repositories: Vec<ResponseFreshnessRepositoryMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub runtime_cache_contract: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridSemanticAcceleratorMetadata {
pub tier: String,
pub state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<ChannelHealthStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridLanguageCapabilityMetadata {
pub requested_language: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
pub semantic_chunking: String,
pub semantic_accelerator: SearchHybridSemanticAcceleratorMetadata,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub capabilities: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SearchHybridQueryShape {
BroadNaturalLanguage,
CodeShaped,
Neutral,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridExactPivotAssistance {
pub applied: bool,
pub exact_symbol_hit_count: usize,
pub exact_text_hit_count: usize,
pub boosted_match_count: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridMetadata {
pub channels: BTreeMap<String, SearchHybridChannelMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lexical_backend: Option<SearchLexicalBackendMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lexical_backend_note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_requested: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_status: Option<ChannelHealthStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_candidate_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_hit_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_match_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lexical_only_mode: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_shape: Option<SearchHybridQueryShape>,
#[serde(skip_serializing_if = "Option::is_none")]
pub warning: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exact_pivot_assistance: Option<SearchHybridExactPivotAssistance>,
#[serde(skip_serializing_if = "Option::is_none")]
pub witness_demotion_applied: Option<bool>,
pub diagnostics_count: usize,
pub diagnostics: SearchHybridDiagnosticsSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub stage_attribution: Option<SearchHybridStageAttribution>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_capability: Option<SearchHybridLanguageCapabilityMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub utility: Option<SearchHybridUtilitySummary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_efficiency: Option<ContextEfficiencyMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_debug: Option<ResponseFreshnessBasisMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SearchHybridResponse {
pub matches: Vec<SearchHybridMatch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<SearchHybridMetadata>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct SearchSymbolParams {
pub query: String,
pub repository_id: Option<String>,
pub path_class: Option<SearchSymbolPathClass>,
pub path_regex: Option<String>,
pub limit: Option<usize>,
pub response_mode: Option<ResponseMode>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SearchSymbolResponse {
pub matches: Vec<SymbolMatch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(schema_with = "super::metadata_object_field_schema")]
pub metadata: Option<MetadataObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SearchSymbolPathClass {
Runtime,
Project,
Support,
}
impl SearchSymbolPathClass {
pub fn as_str(self) -> &'static str {
match self {
Self::Runtime => "runtime",
Self::Project => "project",
Self::Support => "support",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn context_efficiency_metadata_omits_unknown_optional_fields() {
let value = serde_json::to_value(ContextEfficiencyMetadata {
indexed_readable_files: 3,
indexed_readable_bytes: 120,
indexed_min_mtime_ns: None,
indexed_max_mtime_ns: None,
candidate_input_count: None,
candidate_output_count: None,
returned_match_count: Some(2),
returned_unique_paths: Some(1),
returned_unique_file_bytes: Some(80),
returned_source_bytes_estimate: Some(20),
matched_file_context_saved_bytes_estimate: Some(60),
matched_file_context_saved_percent_estimate: Some(75.0),
corpus_context_saved_bytes_estimate: Some(100),
corpus_context_saved_percent_estimate: Some(83.33),
corpus_narrowing_ratio_estimate: Some(6),
query_duration_ms: Some(12),
narrowing_ratio_estimate: Some(4),
stage_attribution: None,
})
.expect("context-efficiency metadata should serialize");
assert_eq!(
value,
json!({
"indexed_readable_files": 3,
"indexed_readable_bytes": 120,
"returned_match_count": 2,
"returned_unique_paths": 1,
"returned_unique_file_bytes": 80,
"returned_source_bytes_estimate": 20,
"matched_file_context_saved_bytes_estimate": 60,
"matched_file_context_saved_percent_estimate": 75.0,
"corpus_context_saved_bytes_estimate": 100,
"corpus_context_saved_percent_estimate": 83.33,
"corpus_narrowing_ratio_estimate": 6,
"query_duration_ms": 12,
"narrowing_ratio_estimate": 4
})
);
}
#[test]
fn context_efficiency_stage_attribution_is_typed() {
let value = serde_json::to_value(ContextEfficiencyMetadata {
indexed_readable_files: 1,
indexed_readable_bytes: 10,
indexed_min_mtime_ns: Some(100),
indexed_max_mtime_ns: Some(200),
candidate_input_count: Some(8),
candidate_output_count: Some(3),
returned_match_count: None,
returned_unique_paths: None,
returned_unique_file_bytes: None,
returned_source_bytes_estimate: None,
matched_file_context_saved_bytes_estimate: None,
matched_file_context_saved_percent_estimate: None,
corpus_context_saved_bytes_estimate: None,
corpus_context_saved_percent_estimate: None,
corpus_narrowing_ratio_estimate: None,
query_duration_ms: None,
narrowing_ratio_estimate: None,
stage_attribution: Some(ContextEfficiencyStageAttribution {
candidate_input_count: 8,
candidate_output_count: 3,
}),
})
.expect("context-efficiency metadata should serialize");
assert_eq!(
value["stage_attribution"]["candidate_input_count"],
json!(8)
);
assert_eq!(
value["stage_attribution"]["candidate_output_count"],
json!(3)
);
}
#[test]
fn explore_params_accept_context_efficiency_opt_in() {
let params: ExploreParams = serde_json::from_value(json!({
"path": "src/lib.rs",
"operation": "probe",
"query": "needle",
"include_context_efficiency": true
}))
.expect("explore params should accept include_context_efficiency");
assert_eq!(params.include_context_efficiency, Some(true));
}
#[test]
fn explore_metadata_omits_context_efficiency_by_default() {
let value = serde_json::to_value(ExploreMetadata {
lossy_utf8: false,
effective_context_lines: 3,
effective_max_matches: 8,
context_efficiency: None,
})
.expect("explore metadata should serialize");
assert!(value.get("context_efficiency").is_none());
}
#[test]
fn response_freshness_basis_round_trips_runtime_metadata() {
let value = json!({
"mode": "manifest_only",
"cacheable": false,
"repositories": [{
"repository_id": "repo-001",
"snapshot_id": "snapshot-abc",
"manifest": "ready",
"semantic": "ready",
"dirty_root": false,
"cacheable_reason": "refresh in progress",
"candidate_source": "manifest",
"using_live_walk": false,
"refresh_in_progress": true,
"active_index_tasks": [{
"kind": "manifest",
"status": "running"
}],
"recommended_client_behavior": "prefer_current_response",
"provider": "openai",
"model": "text-embedding-3-small"
}],
"runtime_cache_contract": {
"cacheable": false,
"invalidation_basis": "snapshot"
}
});
let metadata: ResponseFreshnessBasisMetadata =
serde_json::from_value(value.clone()).expect("freshness metadata should deserialize");
let serialized =
serde_json::to_value(metadata).expect("freshness metadata should serialize");
assert_eq!(serialized, value);
}
#[test]
fn response_freshness_repository_keeps_empty_active_index_tasks() {
let value = json!({
"repository_id": "repo-001",
"snapshot_id": "snapshot-abc",
"manifest": "ready",
"semantic": "ready",
"dirty_root": false,
"candidate_source": "manifest_snapshot",
"using_live_walk": false,
"refresh_in_progress": false,
"active_index_tasks": [],
"recommended_client_behavior": "use_cached_frigg_results"
});
let metadata: ResponseFreshnessRepositoryMetadata = serde_json::from_value(value.clone())
.expect("repository freshness metadata should deserialize");
let serialized =
serde_json::to_value(metadata).expect("repository freshness metadata should serialize");
assert_eq!(serialized, value);
}
}