Skip to main content

sqlite_graphrag/i18n/validation/
messages_embedding.rs

1//! Embedding error message catalog (GAP-SG-132).
2//!
3//! EN+PT user-facing strings for `AppError::Embedding(String)`.
4//!
5//! `EmbeddingErrorKind::classify` matches English substrings case-insensitively:
6//! `oauth`, `quota`, `slot exhausted`, `backend mismatch`, and (`dim` + `zero`).
7//! Portuguese templates MUST retain those English markers so classification
8//! continues to work under either locale.
9
10use crate::i18n::{current, Language};
11use std::fmt::Display;
12
13// ── generic wrapper ──────────────────────────────────────────────────────────
14
15/// Wrap an external / dynamic error detail as an embedding payload.
16pub fn embedding_detail(err: impl Display) -> String {
17    match current() {
18        Language::English => format!("{err}"),
19        Language::Portuguese => format!("{err}"),
20    }
21}
22
23// ── OpenRouter HTTP / client ─────────────────────────────────────────────────
24
25/// Failed to build the reqwest HTTP client for OpenRouter embeddings.
26pub fn embedding_http_client_build_failed(err: impl Display) -> String {
27    match current() {
28        Language::English => format!("failed to build HTTP client: {err}"),
29        Language::Portuguese => format!("falha ao construir cliente HTTP: {err}"),
30    }
31}
32
33/// Empty `data` array in a successful OpenRouter embedding response.
34pub fn embedding_empty_response_from_openrouter() -> String {
35    match current() {
36        Language::English => "empty response from OpenRouter".to_string(),
37        Language::Portuguese => "resposta vazia do OpenRouter".to_string(),
38    }
39}
40
41/// Batch response length mismatch.
42pub fn embedding_expected_count(expected: usize, got: usize) -> String {
43    match current() {
44        Language::English => format!("expected {expected} embeddings, got {got}"),
45        Language::Portuguese => format!("esperados {expected} embeddings, obtidos {got}"),
46    }
47}
48
49/// Provider returned fewer dimensions than requested.
50pub fn embedding_dimension_less_than_requested(got: usize, requested: usize) -> String {
51    match current() {
52        Language::English => format!("embedding dimension {got} < requested {requested}"),
53        Language::Portuguese => {
54            format!("dimensão de embedding {got} < solicitada {requested}")
55        }
56    }
57}
58
59/// OpenRouter embedding request timed out.
60pub fn embedding_openrouter_request_timed_out() -> String {
61    match current() {
62        Language::English => "OpenRouter request timed out".to_string(),
63        Language::Portuguese => "requisição OpenRouter expirou (timeout)".to_string(),
64    }
65}
66
67/// Transient network failure during embedding HTTP.
68pub fn embedding_http_request_failed(err: impl Display) -> String {
69    match current() {
70        Language::English => format!("HTTP request failed: {err}"),
71        Language::Portuguese => format!("requisição HTTP falhou: {err}"),
72    }
73}
74
75/// Failed to read embedding response body.
76pub fn embedding_failed_to_read_response_body(err: impl Display) -> String {
77    match current() {
78        Language::English => format!("failed to read response body: {err}"),
79        Language::Portuguese => format!("falha ao ler corpo da resposta: {err}"),
80    }
81}
82
83/// HTTP 200 body had neither `data` nor `error`.
84pub fn embedding_openrouter_200_neither_data_nor_error() -> String {
85    match current() {
86        Language::English => "OpenRouter 200 response had neither data nor error".to_string(),
87        Language::Portuguese => "resposta OpenRouter 200 sem data nem error".to_string(),
88    }
89}
90
91/// Failed to parse embedding JSON body.
92pub fn embedding_failed_to_parse_response(err: impl Display) -> String {
93    match current() {
94        Language::English => format!("failed to parse embedding response: {err}"),
95        Language::Portuguese => format!("falha ao analisar resposta de embedding: {err}"),
96    }
97}
98
99/// Invalid OpenRouter API key (HTTP 401).
100pub fn embedding_openrouter_invalid_api_key_401() -> String {
101    match current() {
102        Language::English => "invalid OpenRouter API key (HTTP 401)".to_string(),
103        Language::Portuguese => "chave de API OpenRouter inválida (HTTP 401)".to_string(),
104    }
105}
106
107/// OpenRouter hard-failure status with body.
108pub fn embedding_openrouter_returned(status: impl Display, body: &str) -> String {
109    match current() {
110        Language::English => format!("OpenRouter returned {status}: {body}"),
111        Language::Portuguese => format!("OpenRouter retornou {status}: {body}"),
112    }
113}
114
115/// OpenRouter 5xx during embedding.
116pub fn embedding_openrouter_server_error(status: impl Display) -> String {
117    match current() {
118        Language::English => format!("OpenRouter server error: {status}"),
119        Language::Portuguese => format!("erro de servidor OpenRouter: {status}"),
120    }
121}
122
123/// Unexpected HTTP status with body snippet.
124pub fn embedding_unexpected_http(status: impl Display, body: &str) -> String {
125    match current() {
126        Language::English => format!("unexpected HTTP {status}: {body}"),
127        Language::Portuguese => format!("HTTP inesperado {status}: {body}"),
128    }
129}
130
131/// Max retries exhausted for OpenRouter embedding request.
132pub fn embedding_openrouter_max_retries() -> String {
133    match current() {
134        Language::English => "max retries exceeded for OpenRouter request".to_string(),
135        Language::Portuguese => {
136            "número máximo de tentativas excedido para requisição OpenRouter".to_string()
137        }
138    }
139}
140
141// ── runtime / singleton getters ──────────────────────────────────────────────
142
143/// Tokio multi-thread runtime failed to initialise.
144pub fn embedding_tokio_runtime_init_failed(err: impl Display) -> String {
145    match current() {
146        Language::English => format!("tokio runtime init failed: {err}"),
147        Language::Portuguese => format!("falha na inicialização do runtime tokio: {err}"),
148    }
149}
150
151/// Tokio runtime missing after set.
152pub fn embedding_tokio_runtime_unavailable() -> String {
153    match current() {
154        Language::English => "tokio runtime unavailable after initialisation".to_string(),
155        Language::Portuguese => "runtime tokio indisponível após inicialização".to_string(),
156    }
157}
158
159/// Default embedder singleton missing after set.
160pub fn embedding_embedder_unavailable() -> String {
161    match current() {
162        Language::English => "embedder unavailable after initialisation".to_string(),
163        Language::Portuguese => "embedder indisponível após inicialização".to_string(),
164    }
165}
166
167/// Claude embedder singleton missing after set.
168pub fn embedding_claude_embedder_unavailable() -> String {
169    match current() {
170        Language::English => "claude embedder unavailable after initialisation".to_string(),
171        Language::Portuguese => "embedder claude indisponível após inicialização".to_string(),
172    }
173}
174
175/// OpenCode embedder singleton missing after set.
176pub fn embedding_opencode_embedder_unavailable() -> String {
177    match current() {
178        Language::English => "opencode embedder unavailable after initialisation".to_string(),
179        Language::Portuguese => "embedder opencode indisponível após inicialização".to_string(),
180    }
181}
182
183/// OpenRouter embed client singleton missing after set.
184pub fn embedding_openrouter_client_unavailable() -> String {
185    match current() {
186        Language::English => "openrouter client unavailable after initialisation".to_string(),
187        Language::Portuguese => "cliente openrouter indisponível após inicialização".to_string(),
188    }
189}
190
191/// OpenRouter chat client singleton missing after set.
192pub fn embedding_openrouter_chat_client_unavailable() -> String {
193    match current() {
194        Language::English => "openrouter chat client unavailable after initialisation".to_string(),
195        Language::Portuguese => {
196            "cliente de chat openrouter indisponível após inicialização".to_string()
197        }
198    }
199}
200
201// ── backend probes ───────────────────────────────────────────────────────────
202
203/// OpenRouter probe: client not initialised.
204pub fn embedding_openrouter_probe_not_initialised() -> String {
205    match current() {
206        Language::English => "openrouter probe: client not initialised (skip)".to_string(),
207        Language::Portuguese => "probe openrouter: cliente não inicializado (skip)".to_string(),
208    }
209}
210
211/// Codex probe: binary missing from PATH.
212pub fn embedding_codex_probe_binary_not_on_path() -> String {
213    match current() {
214        Language::English => "codex probe: binary not on PATH (skip)".to_string(),
215        Language::Portuguese => "probe codex: binário não está no PATH (skip)".to_string(),
216    }
217}
218
219/// Codex probe: auth.json missing.
220pub fn embedding_codex_probe_auth_missing() -> String {
221    match current() {
222        Language::English => {
223            "codex probe: auth.json missing (skip; use --llm-backend none or login)".to_string()
224        }
225        Language::Portuguese => {
226            "probe codex: auth.json ausente (skip; use --llm-backend none ou login)".to_string()
227        }
228    }
229}
230
231/// Claude probe: binary missing from PATH.
232pub fn embedding_claude_probe_binary_not_on_path() -> String {
233    match current() {
234        Language::English => "claude probe: binary not on PATH (skip)".to_string(),
235        Language::Portuguese => "probe claude: binário não está no PATH (skip)".to_string(),
236    }
237}
238
239/// OpenCode probe: binary missing from PATH.
240pub fn embedding_opencode_probe_binary_not_on_path() -> String {
241    match current() {
242        Language::English => "opencode probe: binary not on PATH (skip)".to_string(),
243        Language::Portuguese => "probe opencode: binário não está no PATH (skip)".to_string(),
244    }
245}
246
247/// OpenRouter client not initialised for embed path.
248pub fn embedding_openrouter_client_not_initialised() -> String {
249    match current() {
250        Language::English => {
251            "OpenRouter client not initialised; call get_openrouter_embedder first".to_string()
252        }
253        Language::Portuguese => {
254            "cliente OpenRouter não inicializado; chame get_openrouter_embedder primeiro"
255                .to_string()
256        }
257    }
258}
259
260// ── dimension / validation ───────────────────────────────────────────────────
261
262/// Vector length diverges from configured embedding dim (G42/C5).
263pub fn embedding_has_dims_expected(got: usize, expected: usize) -> String {
264    match current() {
265        Language::English => format!(
266            "embedding has {got} dims, expected {expected}; \
267             refusing to truncate or pad silently (G42/C5)"
268        ),
269        Language::Portuguese => format!(
270            "embedding tem {got} dims, esperado {expected}; \
271             recusando truncar ou preencher silenciosamente (G42/C5)"
272        ),
273    }
274}
275
276/// LLM returned wrong dim for a specific fan-out item.
277pub fn embedding_llm_item_dims(got: usize, idx: usize, expected: usize) -> String {
278    match current() {
279        Language::English => format!(
280            "LLM returned {got} dims for item {idx}, expected {expected}; \
281             refusing to truncate or pad silently (G42/C5)"
282        ),
283        Language::Portuguese => format!(
284            "LLM retornou {got} dims para item {idx}, esperado {expected}; \
285             recusando truncar ou preencher silenciosamente (G42/C5)"
286        ),
287    }
288}
289
290/// KNN search dim mismatch (memories / entities).
291pub fn embedding_knn_search_dim_mismatch(got: usize, expected: usize) -> String {
292    match current() {
293        Language::English => {
294            format!("knn_search embedding has {got} dims, expected {expected}")
295        }
296        Language::Portuguese => {
297            format!("embedding knn_search tem {got} dims, esperado {expected}")
298        }
299    }
300}
301
302/// KNN search dim mismatch (chunks).
303pub fn embedding_knn_search_chunks_dim_mismatch(got: usize, expected: usize) -> String {
304    match current() {
305        Language::English => {
306            format!("knn_search_chunks embedding has {got} dims, expected {expected}")
307        }
308        Language::Portuguese => {
309            format!("embedding knn_search_chunks tem {got} dims, esperado {expected}")
310        }
311    }
312}
313
314// ── slot / fan-out / batch ───────────────────────────────────────────────────
315
316/// LLM slot semaphore exhausted (marker: `slot exhausted`).
317pub fn embedding_slot_exhausted(err: impl Display) -> String {
318    match current() {
319        Language::English => format!("slot exhausted: {err} (fall back to FTS5)"),
320        // Keep English marker "slot exhausted" for EmbeddingErrorKind::classify.
321        Language::Portuguese => {
322            format!("slot exhausted: {err} (fall back para FTS5)")
323        }
324    }
325}
326
327/// JoinSet join error while embedding.
328pub fn embedding_task_join_error(err: impl Display) -> String {
329    match current() {
330        Language::English => format!("embedding task join error: {err}"),
331        Language::Portuguese => format!("erro de join na tarefa de embedding: {err}"),
332    }
333}
334
335/// Entity embed cache produced a null slot.
336pub fn embedding_entity_cache_null() -> String {
337    match current() {
338        Language::English => "entity embed cache produced null result".to_string(),
339        Language::Portuguese => "cache de embed de entidade produziu resultado nulo".to_string(),
340    }
341}
342
343/// Fan-out lost an item index.
344pub fn embedding_fanout_lost_index(idx: usize) -> String {
345    match current() {
346        Language::English => format!("embedding fan-out lost item index {idx}"),
347        Language::Portuguese => {
348            format!("fan-out de embedding perdeu índice do item {idx}")
349        }
350    }
351}
352
353/// Semaphore closed mid fan-out.
354pub fn embedding_semaphore_closed() -> String {
355    match current() {
356        Language::English => "semaphore closed".to_string(),
357        Language::Portuguese => "semáforo fechado".to_string(),
358    }
359}
360
361/// Embedding cancelled by shutdown signal (keep "cancelled" for batch matcher).
362pub fn embedding_cancelled_by_shutdown() -> String {
363    match current() {
364        Language::English => "embedding cancelled by shutdown signal".to_string(),
365        // Keep English "cancelled" so batch collector still matches.
366        Language::Portuguese => "embedding cancelled por sinal de shutdown".to_string(),
367    }
368}
369
370/// Embedding JoinSet task panicked.
371pub fn embedding_task_panicked(err: impl Display) -> String {
372    match current() {
373        Language::English => format!("embedding task panicked: {err}"),
374        Language::Portuguese => format!("tarefa de embedding entrou em pânico: {err}"),
375    }
376}
377
378// ── LLM subprocess embedding ─────────────────────────────────────────────────
379
380/// No codex/claude/opencode on PATH.
381pub fn embedding_no_llm_cli_on_path() -> String {
382    match current() {
383        Language::English => {
384            "no LLM CLI found on PATH: install `codex` (0.130+), `claude` (Claude Code 2.1+), or `opencode` (1.17+)"
385                .to_string()
386        }
387        Language::Portuguese => {
388            "nenhum CLI de LLM encontrado no PATH: instale `codex` (0.130+), `claude` (Claude Code 2.1+) ou `opencode` (1.17+)"
389                .to_string()
390        }
391    }
392}
393
394/// Named binary not found on PATH.
395pub fn embedding_binary_not_found_on_path(which_name: &str) -> String {
396    match current() {
397        Language::English => format!("`{which_name}` not found on PATH"),
398        Language::Portuguese => format!("`{which_name}` não encontrado no PATH"),
399    }
400}
401
402/// LLM batch embedding JSON parse failed.
403pub fn embedding_llm_batch_parse_failed(err: impl Display, raw: &str) -> String {
404    match current() {
405        Language::English => {
406            format!("LLM batch embedding response parse failed: {err}; raw={raw}")
407        }
408        Language::Portuguese => {
409            format!("falha ao analisar resposta de embedding em lote LLM: {err}; raw={raw}")
410        }
411    }
412}
413
414/// LLM batch returned wrong item count.
415pub fn embedding_llm_batch_item_count(got: usize, expected: usize) -> String {
416    match current() {
417        Language::English => {
418            format!("LLM batch returned {got} items, expected {expected} (G42/S2 coverage check)")
419        }
420        Language::Portuguese => format!(
421            "lote LLM retornou {got} itens, esperados {expected} (verificação de cobertura G42/S2)"
422        ),
423    }
424}
425
426/// LLM batch item index out of range.
427pub fn embedding_llm_batch_index_out_of_range(i: usize, max: usize) -> String {
428    match current() {
429        Language::English => {
430            format!("LLM batch item index {i} out of range 1..={max}")
431        }
432        Language::Portuguese => {
433            format!("índice de item do lote LLM {i} fora do intervalo 1..={max}")
434        }
435    }
436}
437
438/// LLM batch item dimension mismatch (G42/C5).
439pub fn embedding_llm_batch_item_dims(i: usize, got: usize, expected: usize) -> String {
440    match current() {
441        Language::English => format!(
442            "LLM batch item {i} returned {got} dims, expected {expected}; \
443             refusing to truncate or pad silently (G42/C5)"
444        ),
445        Language::Portuguese => format!(
446            "item {i} do lote LLM retornou {got} dims, esperado {expected}; \
447             recusando truncar ou preencher silenciosamente (G42/C5)"
448        ),
449    }
450}
451
452/// LLM batch response missing an item index.
453pub fn embedding_llm_batch_missing_item(index: usize) -> String {
454    match current() {
455        Language::English => {
456            format!("LLM batch response is missing item index {index} (G42/S2 coverage check)")
457        }
458        Language::Portuguese => format!(
459            "resposta do lote LLM sem índice de item {index} (verificação de cobertura G42/S2)"
460        ),
461    }
462}
463
464/// Single LLM embedding JSON parse failed.
465pub fn embedding_llm_parse_failed(err: impl Display, raw: &str) -> String {
466    match current() {
467        Language::English => {
468            format!("LLM embedding response parse failed: {err}; raw={raw}")
469        }
470        Language::Portuguese => {
471            format!("falha ao analisar resposta de embedding LLM: {err}; raw={raw}")
472        }
473    }
474}
475
476/// Single LLM embedding dimension mismatch (G42/C5).
477pub fn embedding_llm_returned_dims(got: usize, expected: usize) -> String {
478    match current() {
479        Language::English => format!(
480            "LLM returned {got} dims, expected {expected}; \
481             refusing to truncate or pad silently (G42/C5)"
482        ),
483        Language::Portuguese => format!(
484            "LLM retornou {got} dims, esperado {expected}; \
485             recusando truncar ou preencher silenciosamente (G42/C5)"
486        ),
487    }
488}
489
490/// Schema tempfile create failed.
491pub fn embedding_schema_tempfile_create_failed(err: impl Display) -> String {
492    match current() {
493        Language::English => format!("schema tempfile create failed: {err}"),
494        Language::Portuguese => {
495            format!("falha ao criar tempfile de schema: {err}")
496        }
497    }
498}
499
500/// Schema tempfile write failed.
501pub fn embedding_schema_tempfile_write_failed(err: impl Display) -> String {
502    match current() {
503        Language::English => format!("schema tempfile write failed: {err}"),
504        Language::Portuguese => {
505            format!("falha ao escrever tempfile de schema: {err}")
506        }
507    }
508}
509
510/// Claude OAuth usage quota exhausted (markers: `OAuth`, `quota`).
511pub fn embedding_oauth_usage_quota_exhausted_claude(snippet: &str) -> String {
512    match current() {
513        // Keep English "OAuth" + "quota" markers for EmbeddingErrorKind::classify.
514        Language::English => {
515            format!("OAuth usage quota exhausted: claude rate_limit detected in stdout: {snippet}")
516        }
517        Language::Portuguese => format!(
518            "OAuth usage quota exhausted: rate_limit do claude detectado no stdout: {snippet}"
519        ),
520    }
521}
522
523/// Codex stdin write failed.
524pub fn embedding_codex_stdin_write_failed(err: impl Display) -> String {
525    match current() {
526        Language::English => format!("codex stdin write failed: {err}"),
527        Language::Portuguese => format!("falha ao escrever no stdin do codex: {err}"),
528    }
529}
530
531// ── dry-run backend guards ───────────────────────────────────────────────────
532
533/// `--llm-backend codex` but codex missing (claude detected).
534pub fn embedding_dry_run_codex_not_on_path() -> String {
535    match current() {
536        Language::English => "`--llm-backend codex` requested but `codex` was not found on PATH \
537             (a `claude` binary was detected; refusing silent fallback per ADR-0042). \
538             Install `codex` (>= 0.130) or pass `--llm-backend claude` explicitly."
539            .to_string(),
540        Language::Portuguese => {
541            "`--llm-backend codex` solicitado mas `codex` não encontrado no PATH \
542             (binário `claude` detectado; recusando fallback silencioso por ADR-0042). \
543             Instale `codex` (>= 0.130) ou passe `--llm-backend claude` explicitamente."
544                .to_string()
545        }
546    }
547}
548
549/// `--llm-backend claude` but claude missing (codex detected).
550pub fn embedding_dry_run_claude_not_on_path() -> String {
551    match current() {
552        Language::English => "`--llm-backend claude` requested but `claude` was not found on PATH \
553             (a `codex` binary was detected; refusing silent fallback per ADR-0042). \
554             Install `claude` (Claude Code >= 2.1) or pass `--llm-backend codex` explicitly."
555            .to_string(),
556        Language::Portuguese => {
557            "`--llm-backend claude` solicitado mas `claude` não encontrado no PATH \
558             (binário `codex` detectado; recusando fallback silencioso por ADR-0042). \
559             Instale `claude` (Claude Code >= 2.1) ou passe `--llm-backend codex` explicitamente."
560                .to_string()
561        }
562    }
563}
564
565/// `--llm-backend opencode` but auto-detect resolved another flavour.
566pub fn embedding_dry_run_opencode_resolved_other(flavour: &str) -> String {
567    match current() {
568        Language::English => format!(
569            "`--llm-backend opencode` requested but auto-detect resolved `{flavour}` \
570             (opencode has lower priority than codex/claude in detect_available). \
571             Pass `--llm-backend auto`, `--opencode-binary <path>`, or \
572             `config set llm.opencode_binary <path>`."
573        ),
574        Language::Portuguese => format!(
575            "`--llm-backend opencode` solicitado mas auto-detect resolveu `{flavour}` \
576             (opencode tem prioridade menor que codex/claude em detect_available). \
577             Passe `--llm-backend auto`, `--opencode-binary <path>`, ou \
578             `config set llm.opencode_binary <path>`."
579        ),
580    }
581}
582
583/// `--llm-backend opencode` but opencode not on PATH.
584pub fn embedding_dry_run_opencode_not_on_path() -> String {
585    match current() {
586        Language::English => {
587            "`--llm-backend opencode` requested but `opencode` was not found on PATH. \
588             Install `opencode` (>= 1.17) or pass `--llm-backend auto` to auto-detect."
589                .to_string()
590        }
591        Language::Portuguese => {
592            "`--llm-backend opencode` solicitado mas `opencode` não encontrado no PATH. \
593             Instale `opencode` (>= 1.17) ou passe `--llm-backend auto` para auto-detect."
594                .to_string()
595        }
596    }
597}
598
599// ── opencode runner ──────────────────────────────────────────────────────────
600
601/// OpenCode NDJSON had no text events.
602pub fn embedding_opencode_no_text_events() -> String {
603    match current() {
604        Language::English => "opencode returned no text events in NDJSON output".to_string(),
605        Language::Portuguese => {
606            "opencode não retornou eventos de texto na saída NDJSON".to_string()
607        }
608    }
609}
610
611/// OpenCode timed out.
612pub fn embedding_opencode_timed_out(timeout_secs: u64) -> String {
613    match current() {
614        Language::English => format!("opencode timed out after {timeout_secs}s"),
615        Language::Portuguese => {
616            format!("opencode expirou (timeout) após {timeout_secs}s")
617        }
618    }
619}
620
621/// Failed to spawn opencode.
622pub fn embedding_failed_to_spawn_opencode(err: impl Display) -> String {
623    match current() {
624        Language::English => format!("failed to spawn opencode: {err}"),
625        Language::Portuguese => format!("falha ao iniciar opencode: {err}"),
626    }
627}
628
629/// OpenCode non-zero exit with tails.
630pub fn embedding_opencode_exited(status: impl Display, stderr: &str, stdout: &str) -> String {
631    match current() {
632        Language::English => {
633            format!("opencode exited with {status}: stderr={stderr}, stdout={stdout}")
634        }
635        Language::Portuguese => {
636            format!("opencode saiu com {status}: stderr={stderr}, stdout={stdout}")
637        }
638    }
639}
640
641/// OpenCode JSON parse failed.
642pub fn embedding_opencode_json_parse_failed(err: impl Display) -> String {
643    match current() {
644        Language::English => format!("opencode JSON parse failed: {err}"),
645        Language::Portuguese => format!("falha ao analisar JSON do opencode: {err}"),
646    }
647}