Synchronous embed using a throwaway tokio runtime (for use in non-async contexts). When called from within an existing Tokio runtime (e.g., from hook handlers that call `vault_store` → `vault_auto_index` → `embed_sync`), spawns a separate thread to avoid the "cannot start a runtime from within a runtime" panic.
(&self, texts: &[String])
| 161 | /// separate thread to avoid the "cannot start a runtime from within a runtime" |
| 162 | /// panic. |
| 163 | pub fn embed_sync(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, AiError> { |
| 164 | match self.provider { |
| 165 | AiProvider::Local | AiProvider::None => Ok(texts |
| 166 | .iter() |
| 167 | .map(|t| crate::hash_embed(t, self.dimensions)) |
| 168 | .collect()), |
| 169 | _ => { |
| 170 | if tokio::runtime::Handle::try_current().is_ok() { |
| 171 | // Already inside an async runtime — run the API call on a |
| 172 | // separate thread with its own runtime to avoid nesting. |
| 173 | let provider = self.clone(); |
| 174 | let texts = texts.to_vec(); |
| 175 | std::thread::spawn(move || { |
| 176 | let rt = tokio::runtime::Builder::new_current_thread() |
| 177 | .enable_all() |
| 178 | .build() |
| 179 | .map_err(|e| AiError::Runtime(e.to_string()))?; |
| 180 | rt.block_on(provider.embed(&texts)) |
| 181 | }) |
| 182 | .join() |
| 183 | .unwrap_or_else(|_| Err(AiError::Runtime("Embedding thread panicked".into()))) |
| 184 | } else { |
| 185 | // Not inside a runtime — create a throwaway one directly. |
| 186 | let rt = tokio::runtime::Builder::new_current_thread() |
| 187 | .enable_all() |
| 188 | .build() |
| 189 | .map_err(|e| AiError::Runtime(e.to_string()))?; |
| 190 | rt.block_on(self.embed(texts)) |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | async fn embed_voyage(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, AiError> { |
| 197 | let api_key = self.api_key.as_ref().ok_or(AiError::NoApiKey)?; |