Skip to main content

squigit_brain/
service.rs

1// Copyright 2026 a7mddra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::provider::gemini::attachments::{
5    GeminiFileRef, PrepareAttachmentRequest, PrepareAttachmentResult,
6    PrepareSubmissionAttachmentsRequest, PrepareSubmissionAttachmentsResult,
7};
8use crate::runtime::BrainRuntimeState;
9
10pub struct ImageThreadCredentialSnapshot {
11    credential: crate::provider::gemini::attachments::ActiveCredential,
12}
13
14impl std::fmt::Debug for ImageThreadCredentialSnapshot {
15    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        formatter.write_str("ImageThreadCredentialSnapshot([REDACTED])")
17    }
18}
19
20pub struct BrainService {
21    runtime: BrainRuntimeState,
22}
23
24impl BrainService {
25    pub fn new() -> Self {
26        Self {
27            runtime: BrainRuntimeState::new(),
28        }
29    }
30
31    pub async fn prepare_attachment(
32        &self,
33        request: PrepareAttachmentRequest,
34    ) -> PrepareAttachmentResult {
35        crate::provider::gemini::attachments::prepare_attachment(&self.runtime, request).await
36    }
37
38    pub async fn attachment_preparation_snapshot(
39        &self,
40        job_id: &str,
41    ) -> Option<PrepareAttachmentResult> {
42        crate::provider::gemini::attachments::attachment_preparation_snapshot(&self.runtime, job_id)
43            .await
44    }
45
46    pub async fn cancel_attachment(&self, job_id: String) -> Result<(), String> {
47        crate::provider::gemini::attachments::cancel_attachment(&self.runtime, &job_id).await;
48        Ok(())
49    }
50
51    pub async fn cancel_all_attachment_jobs(&self) -> Result<(), String> {
52        crate::provider::gemini::attachments::cancel_all_attachment_jobs(&self.runtime).await;
53        Ok(())
54    }
55
56    pub async fn prepare_submission_attachments(
57        &self,
58        request: PrepareSubmissionAttachmentsRequest,
59    ) -> PrepareSubmissionAttachmentsResult {
60        crate::provider::gemini::attachments::prepare_submission_attachments(&self.runtime, request)
61            .await
62    }
63
64    pub async fn suggest_thread_title(
65        &self,
66        thread_id: String,
67        model_candidates: Vec<String>,
68    ) -> Result<String, String> {
69        crate::provider::gemini::commands::generation::suggest_thread_title(
70            &self.runtime,
71            thread_id,
72            model_candidates,
73        )
74        .await
75    }
76
77    pub async fn suggest_thread_title_from_text(
78        &self,
79        text: String,
80        model_candidates: Vec<String>,
81    ) -> Result<String, String> {
82        crate::provider::gemini::commands::generation::generate_thread_title_from_text(
83            model_candidates,
84            text,
85        )
86        .await
87    }
88
89    /// Capture the active Google credential for a complete background lifecycle.
90    /// The secret remains opaque to callers and is zeroized with the snapshot.
91    pub async fn capture_image_thread_credential(
92        &self,
93    ) -> Result<Option<ImageThreadCredentialSnapshot>, String> {
94        Ok(
95            crate::provider::gemini::attachments::capture_image_thread_credential()
96                .await?
97                .map(|credential| ImageThreadCredentialSnapshot { credential }),
98        )
99    }
100
101    /// Ensure one canonical CAS image has a valid Gemini Files remote using
102    /// exactly the credential captured when the background lifecycle began.
103    pub async fn ensure_thread_image_uploaded_with_snapshot(
104        &self,
105        snapshot: &ImageThreadCredentialSnapshot,
106        image_path: String,
107    ) -> Result<GeminiFileRef, String> {
108        let cancel_token = tokio_util::sync::CancellationToken::new();
109        Ok(
110            crate::provider::gemini::attachments::ensure_file_uploaded_for_credential(
111                &self.runtime,
112                &snapshot.credential,
113                &image_path,
114                &cancel_token,
115            )
116            .await?
117            .file_ref,
118        )
119    }
120
121    /// Generate an initial title using the same credential snapshot that
122    /// resolved or uploaded the Gemini file.
123    pub async fn suggest_thread_title_from_file_with_snapshot(
124        &self,
125        snapshot: &ImageThreadCredentialSnapshot,
126        file: GeminiFileRef,
127        model_candidates: Vec<String>,
128    ) -> Result<String, String> {
129        crate::provider::gemini::commands::generation::generate_thread_title_from_image(
130            snapshot.credential.api_key(),
131            model_candidates,
132            file.file_uri,
133            file.mime_type,
134        )
135        .await
136    }
137
138    pub async fn build_model_attempt_plan(
139        &self,
140        model_id: String,
141        effort: String,
142    ) -> Result<Vec<String>, String> {
143        crate::provider::gemini::models::build_attempt_plan(&model_id, &effort)
144    }
145}
146
147impl Default for BrainService {
148    fn default() -> Self {
149        Self::new()
150    }
151}