feat(core): SearchOpts domain type for budget controls (fb-34)

3 optional knobs (max_tokens, snippet_chars, cursor); Default = all
None = no enforcement (backwards-compat existing search behavior).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-05-09 17:46:40 +09:00
parent dbb7b54d5d
commit e00418537f
2 changed files with 21 additions and 1 deletions

View File

@@ -51,7 +51,7 @@ pub use metadata::{
};
pub use search::{
DocFilter, DocSummary, RetrievalDetail, SearchFilters, SearchHit,
SearchMode, SearchQuery,
SearchMode, SearchOpts, SearchQuery,
};
pub use answer::{
Answer, AnswerCitation, AnswerRetrievalSummary, ModelRef, RefusalReason, TokenUsage,

View File

@@ -96,6 +96,18 @@ pub struct DocSummary {
pub chunker_version: ChunkerVersion,
}
/// p9-fb-34: caller-supplied output budget knobs for `App::search_with_opts`.
/// All `None` = no enforcement (existing behavior).
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct SearchOpts {
/// chars/4 approximation of wire JSON token cost. None = no cap.
pub max_tokens: Option<usize>,
/// Per-hit snippet character cap. None = use config default.
pub snippet_chars: Option<usize>,
/// Opaque base64 cursor from a previous response. None = first page.
pub cursor: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
@@ -135,4 +147,12 @@ mod tests {
assert_eq!(v["indexed_at"], "2026-05-09T12:00:00Z");
assert_eq!(v["stale"], true);
}
#[test]
fn search_opts_default_is_all_none() {
let opts = SearchOpts::default();
assert!(opts.max_tokens.is_none());
assert!(opts.snippet_chars.is_none());
assert!(opts.cursor.is_none());
}
}