Skip to main content

sqlite_graphrag/extract/llm_embedding/
builder.rs

1//! Builder for [`super::LlmEmbedding`] (ADR-0042 / GAP-002).
2
3use super::binary::resolve_real_binary;
4use super::models::{claude_embed_model, codex_embed_model, opencode_embed_model};
5use super::types::EmbeddingFlavour;
6use super::LlmEmbedding;
7use crate::errors::AppError;
8
9/// ADR-0042 / GAP-002: builder for [`LlmEmbedding`] that lets callers
10/// override the binary path and model without having to remember the
11/// env-var names per flavour. Replaces the duplicated `with_codex` /
12/// `with_claude` bodies that diverged in v1.0.82 (GAP-002: the Claude
13/// arm of `embed_via_backend` re-did the PATH probe via
14/// `LlmEmbedding::detect_available` and could silently pick `codex`).
15#[derive(Clone, Debug)]
16pub struct LlmEmbeddingBuilder {
17    /// Selected backend flavour.
18    pub(crate) flavour: EmbeddingFlavour,
19    /// Optional binary path override.
20    pub(crate) binary_override: Option<std::path::PathBuf>,
21    /// Optional model name override.
22    pub(crate) model_override: Option<String>,
23    /// Optional per-call timeout override.
24    pub(crate) timeout_override: Option<std::time::Duration>,
25}
26
27impl LlmEmbeddingBuilder {
28    /// Convenience: produce a Claude-backed builder pre-configured with
29    /// the canonical default binary + model.
30    /// Convenience: produce a Claude-backed builder pre-configured with
31    /// the canonical default binary + model.
32    pub fn claude_default() -> Self {
33        Self {
34            flavour: EmbeddingFlavour::Claude,
35            binary_override: None,
36            model_override: None,
37            timeout_override: None,
38        }
39    }
40
41    /// Convenience: produce a Codex-backed builder pre-configured with
42    /// the canonical default binary + model.
43    pub fn codex_default() -> Self {
44        Self {
45            flavour: EmbeddingFlavour::Codex,
46            binary_override: None,
47            model_override: None,
48            timeout_override: None,
49        }
50    }
51
52    /// Convenience: produce an OpenCode-backed builder pre-configured with
53    /// the canonical default binary + model.
54    pub fn opencode_default() -> Self {
55        Self {
56            flavour: EmbeddingFlavour::Opencode,
57            binary_override: None,
58            model_override: None,
59            timeout_override: None,
60        }
61    }
62    /// Override the binary path (skips the `which::which` PATH probe).
63    pub fn override_binary(mut self, binary: std::path::PathBuf) -> Self {
64        self.binary_override = Some(binary);
65        self
66    }
67
68    /// Override the model name (skips the env-var lookup).
69    pub fn override_model(mut self, model: String) -> Self {
70        self.model_override = Some(model);
71        self
72    }
73
74    /// Override the per-call embedding timeout (skips env-var lookup).
75    /// Minimum 1s enables fail-fast Auto chain probes (GAP-E2E-06).
76    pub fn override_timeout(mut self, secs: u64) -> Self {
77        let clamped = secs.clamp(1, 3_600);
78        self.timeout_override = Some(std::time::Duration::from_secs(clamped));
79        self
80    }
81
82    /// Build the [`LlmEmbedding`]. Enforces OAuth-only and resolves the
83    /// binary/model via the override or the env-var defaults.
84    pub fn build(self) -> Result<LlmEmbedding, AppError> {
85        LlmEmbedding::oauth_only_enforce()?;
86        let binary = match self.binary_override {
87            Some(path) => resolve_real_binary(&path),
88            None => {
89                // Wave 4: flag/XDG via runtime_config — no product env reads.
90                let (xdg_bin, which_name) = match self.flavour {
91                    EmbeddingFlavour::Codex => (crate::runtime_config::codex_binary(), "codex"),
92                    EmbeddingFlavour::Claude => (crate::runtime_config::claude_binary(), "claude"),
93                    EmbeddingFlavour::Opencode => {
94                        (crate::runtime_config::opencode_binary(), "opencode")
95                    }
96                };
97                let path = xdg_bin
98                    .map(std::path::PathBuf::from)
99                    .or_else(|| which::which(which_name).ok())
100                    .ok_or_else(|| {
101                        AppError::Embedding(
102                            crate::i18n::validation::embedding_binary_not_found_on_path(which_name),
103                        )
104                    })?;
105                resolve_real_binary(&path)
106            }
107        };
108        let model = match self.model_override {
109            Some(m) => m,
110            None => match self.flavour {
111                EmbeddingFlavour::Codex => codex_embed_model(),
112                EmbeddingFlavour::Claude => claude_embed_model(),
113                EmbeddingFlavour::Opencode => opencode_embed_model(),
114            },
115        };
116        Ok(LlmEmbedding::from_parts(
117            self.flavour,
118            binary,
119            model,
120            self.timeout_override,
121        ))
122    }
123}