@danhachuel/thunderbolt 0.4.15 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ from integrations.openai_model_discovery import (
14
14
  OpenAICompatibleAPIError,
15
15
  fetch_openai_compatible_models,
16
16
  validate_openai_compatible_api_key,
17
+ validate_openrouter_api_key,
17
18
  )
18
19
 
19
20
 
@@ -420,7 +421,10 @@ def test_llm_provider_card(card: Mapping[str, Any]) -> dict[str, Any]:
420
421
  if not model:
421
422
  return {"ok": False, "status": "error", "message": "Missing configuration — introduza o modelo antes do teste."}
422
423
  try:
423
- validate_openai_compatible_api_key(api_key, base_url, model)
424
+ if normalized["provider"] == "openrouter":
425
+ validate_openrouter_api_key(api_key, base_url, model)
426
+ else:
427
+ validate_openai_compatible_api_key(api_key, base_url, model)
424
428
  except OpenAICompatibleAPIError as exc:
425
429
  return {
426
430
  "ok": False,
@@ -53,6 +53,54 @@ def chat_completions_endpoint(base_url: str) -> str:
53
53
  return f"{value}/chat/completions"
54
54
 
55
55
 
56
+ def validate_openrouter_api_key(
57
+ api_key: str,
58
+ base_url: str,
59
+ model: str,
60
+ *,
61
+ timeout: float = DEFAULT_TIMEOUT_SECONDS,
62
+ ) -> None:
63
+ """Validate an OpenRouter key and model without spending credits on a chat call."""
64
+ token = str(api_key or "").strip()
65
+ model_id = str(model or "").strip()
66
+ if not token:
67
+ raise OpenAICompatibleAPIError("Informe a API key antes do teste.")
68
+ if not model_id:
69
+ raise OpenAICompatibleAPIError("Informe o modelo antes do teste.")
70
+
71
+ base = _normalise_http_base_url(base_url, error_type=OpenAICompatibleAPIError)
72
+ headers = {
73
+ "Accept": "application/json",
74
+ "Authorization": f"Bearer {token}",
75
+ }
76
+ key_endpoint = f"{base}/key"
77
+ try:
78
+ key_response = requests.get(key_endpoint, headers=headers, timeout=timeout)
79
+ except requests.RequestException as exc:
80
+ raise OpenAICompatibleAPIError("Não foi possível contactar o OpenRouter para validar a API key.") from exc
81
+
82
+ if key_response.status_code in (401, 403):
83
+ raise OpenAICompatibleAPIError(
84
+ f"A API key do OpenRouter foi recusada (HTTP {key_response.status_code}). Verifique a credencial."
85
+ )
86
+ if key_response.status_code >= 400:
87
+ raise OpenAICompatibleAPIError(
88
+ f"O diagnóstico da API key do OpenRouter devolveu HTTP {key_response.status_code}."
89
+ )
90
+
91
+ try:
92
+ available_models = fetch_openai_compatible_models(token, base, timeout=timeout)
93
+ except ModelDiscoveryError as exc:
94
+ raise OpenAICompatibleAPIError(
95
+ "A API key foi aceite, mas não foi possível consultar o catálogo de modelos OpenRouter."
96
+ ) from exc
97
+ if model_id not in available_models:
98
+ raise OpenAICompatibleAPIError(
99
+ f"A API key OpenRouter foi validada, mas o modelo '{model_id}' não está no catálogo actual. "
100
+ "Clique em Consultar modelos e seleccione um modelo disponível."
101
+ )
102
+
103
+
56
104
  def validate_openai_compatible_api_key(
57
105
  api_key: str,
58
106
  base_url: str,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",