talon_cli/mcp/background/
embed.rs1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::mcp::state::McpServerState;
5
6const DEFAULT_INTERVAL_SECS: u64 = 1800; pub fn spawn_embed_ticker(state: Arc<McpServerState>) {
17 let _ = std::thread::Builder::new()
18 .name("talon-embed-ticker".to_owned())
19 .spawn(move || {
20 if let Err(e) = run_embed_ticker(&state) {
21 let mut err = state
22 .diagnostics
23 .last_embed_error
24 .lock()
25 .unwrap_or_else(std::sync::PoisonError::into_inner);
26 *err = Some(format!("embed ticker error: {e:#}"));
27 }
28 });
29}
30
31fn run_embed_ticker(state: &Arc<McpServerState>) -> color_eyre::eyre::Result<()> {
32 let interval = Duration::from_secs(DEFAULT_INTERVAL_SECS);
33 loop {
34 std::thread::sleep(interval);
35 if let Err(e) = run_embed_tick(state) {
36 let mut err = state
37 .diagnostics
38 .last_embed_error
39 .lock()
40 .unwrap_or_else(std::sync::PoisonError::into_inner);
41 *err = Some(format!("embed tick error: {e:#}"));
42 } else {
43 let mut err = state
44 .diagnostics
45 .last_embed_error
46 .lock()
47 .unwrap_or_else(std::sync::PoisonError::into_inner);
48 *err = None;
49 }
50 }
51}
52
53fn run_embed_tick(state: &Arc<McpServerState>) -> color_eyre::eyre::Result<()> {
54 use color_eyre::eyre::WrapErr as _;
55 use talon_core::{
56 EmbeddingClient, embed::EmbedPassOptions, open_database, vec_ext::register_sqlite_vec,
57 };
58
59 register_sqlite_vec().wrap_err("registering sqlite-vec extension")?;
60 let conn = open_database(&state.config.db_path)
61 .wrap_err_with(|| format!("opening index at {}", state.config.db_path.display()))?;
62
63 let opts = EmbedPassOptions {
64 force: false,
65 restrict_paths: Vec::new(),
66 chunk_embedding_model: state.config.config.embedding.model.clone(),
67 document_embedding_model: state.config.config.embedding.document_model().to_owned(),
68 };
69
70 let client = EmbeddingClient::from_config(
71 &state.config.config.embedding,
72 &state.config.config.credentials,
73 )
74 .wrap_err("building embedding client")?;
75
76 talon_core::embed::run_embed_pass(&conn, &client, &opts)
77 .map(|_| ())
78 .wrap_err("embedding pending chunks")
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn embed_ticker_interval_is_thirty_minutes() {
87 assert_eq!(DEFAULT_INTERVAL_SECS, 1800);
88 }
89}