1use std::sync::Arc;
5
6use tokio_util::sync::CancellationToken;
7
8use crate::events::{EventSink, Reporter};
9use crate::llm::LlmClient;
10use crate::team::TeamSettings;
11use crate::worktree::{Credentials, NoCredentials};
12
13#[derive(Debug, Clone)]
16pub struct Settings {
17 pub llm_model: String,
18 pub task_models: std::collections::BTreeMap<crate::prompts::Task, String>,
20 pub llm_base_url: Option<String>,
21 pub embed_model: String,
22 pub embed: bool,
23 pub enrich: bool,
24 pub flows: bool,
25 pub guide: bool,
26 pub max_enrich: usize,
27 pub max_flows: usize,
28 pub llm_concurrency: usize,
29 pub prices: Option<(f64, f64)>,
31 pub format: String,
33 pub scip: crate::scip::Mode,
34 pub auto_index: bool,
35 pub auto_index_llm: bool,
36 pub auto_index_every: std::time::Duration,
38 pub rewrite: std::collections::BTreeSet<crate::prompts::Task>,
40}
41
42pub const AUTO_INDEX_MINUTES: usize = 5;
44pub const MAX_AUTO_INDEX_MINUTES: usize = 1440;
45
46impl Settings {
47 pub fn resolve(team: &TeamSettings, env: impl Fn(&str) -> Option<String>) -> Settings {
49 let env = |k: &str| env(k).filter(|v| !v.is_empty());
50 Settings {
51 llm_model: team
52 .llm_model
53 .clone()
54 .or_else(|| env("COMINDLY_LLM_MODEL"))
55 .unwrap_or_else(|| crate::llm::DEFAULT_MODEL.to_string()),
56 task_models: [
57 (crate::prompts::Task::Summary, &team.llm_model_summary),
58 (crate::prompts::Task::Guide, &team.llm_model_guide),
59 (crate::prompts::Task::Flow, &team.llm_model_flow),
60 ]
61 .into_iter()
62 .filter_map(|(t, m)| Some((t, m.clone()?)))
63 .collect(),
64 llm_base_url: team
65 .llm_base_url
66 .clone()
67 .or_else(|| env("COMINDLY_LLM_BASE_URL")),
68 embed_model: team
69 .embed_model
70 .clone()
71 .or_else(|| env("COMINDLY_EMBED_MODEL"))
72 .unwrap_or_else(|| crate::embed::DEFAULT_MODEL.to_string()),
73 embed: team.embed.unwrap_or(true),
74 enrich: team.enrich.unwrap_or(true),
75 flows: team.flows.unwrap_or(true),
76 guide: team.guide.unwrap_or(true),
77 max_enrich: team.max_enrich.unwrap_or(usize::MAX),
78 max_flows: team.max_flows.unwrap_or(usize::MAX),
79 llm_concurrency: team.llm_concurrency.unwrap_or(8).clamp(1, 64),
80 prices: (team.llm_price_in.is_some() || team.llm_price_out.is_some()).then(|| {
81 (
82 team.llm_price_in.unwrap_or(0.0),
83 team.llm_price_out.unwrap_or(0.0),
84 )
85 }),
86 format: env("COMINDLY_FORMAT")
87 .or_else(|| team.format.clone())
88 .unwrap_or_else(|| "md".to_string()),
89 scip: env("COMINDLY_SCIP")
90 .or_else(|| team.scip.clone())
91 .and_then(|s| crate::scip::Mode::parse(&s))
92 .unwrap_or(crate::scip::Mode::Auto),
93 auto_index: team.auto_index.unwrap_or(false),
94 auto_index_llm: team.auto_index_llm.unwrap_or(false),
95 auto_index_every: std::time::Duration::from_secs(
96 60 * team
97 .auto_index_minutes
98 .unwrap_or(AUTO_INDEX_MINUTES)
99 .clamp(1, MAX_AUTO_INDEX_MINUTES) as u64,
100 ),
101 rewrite: Default::default(),
102 }
103 }
104}
105
106#[derive(Clone)]
108pub struct IndexContext {
109 pub root: String,
111 pub settings: Settings,
112 pub llm: Option<Arc<LlmClient>>,
114 pub git: Arc<dyn Credentials>,
116 pub report: Reporter,
117 pub cancel: CancellationToken,
118 pub cache: std::path::PathBuf,
120}
121
122impl IndexContext {
123 pub fn new(
124 root: impl Into<String>,
125 settings: Settings,
126 events: Arc<dyn EventSink>,
127 cache: std::path::PathBuf,
128 ) -> Self {
129 IndexContext {
130 root: root.into(),
131 settings,
132 llm: None,
133 git: Arc::new(NoCredentials),
134 report: Reporter::new(events),
135 cancel: CancellationToken::new(),
136 cache,
137 }
138 }
139
140 pub fn with_llm(mut self, llm: Option<LlmClient>) -> Self {
141 self.llm = llm.map(Arc::new);
142 self
143 }
144
145 pub fn with_git(mut self, git: Arc<dyn Credentials>) -> Self {
146 self.git = git;
147 self
148 }
149
150 pub fn graph(&self) -> String {
152 format!("{}/_graph", self.root.trim_end_matches('/'))
153 }
154
155 pub fn check(&self) -> anyhow::Result<()> {
157 anyhow::ensure!(!self.cancel.is_cancelled(), Cancelled);
158 Ok(())
159 }
160}
161
162#[derive(Debug, Clone, Copy)]
164pub struct Cancelled;
165
166impl std::fmt::Display for Cancelled {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.write_str("cancelled")
169 }
170}
171
172impl std::error::Error for Cancelled {}