···414414415415 def walk(value: Any, key: str | None = None) -> Any:
416416 if isinstance(value, dict):
417417- return {
417417+ result = {
418418 item_key: (
419419 0
420420 if item_key in {"mtime", "created_at", "file_mtime"}
···433433 )
434434 for item_key, item_value in value.items()
435435 }
436436+ if key == "provider_status":
437437+ for _name, status in result.items():
438438+ if isinstance(status, dict) and "cogitate_cli" in status:
439439+ status["cogitate_cli_found"] = False
440440+ status["cogitate_ready"] = False
441441+ cli = status.get("cogitate_cli", "")
442442+ issues = [
443443+ i
444444+ for i in status.get("issues", [])
445445+ if "CLI not found" not in i
446446+ ]
447447+ if cli:
448448+ issues.append(f"{cli} CLI not found on PATH")
449449+ status["issues"] = sorted(issues)
450450+ return result
436451437452 if isinstance(value, list):
438453 walked = [walk(item, key) for item in value]
+1-10
think/agents.py
···5555# Minimum content length for transcript-based generation
5656MIN_INPUT_CHARS = 50
57575858-# CLI binary names for cogitate interface, per provider
5959-COGITATE_BINARIES: dict[str, str] = {
6060- "google": "gemini",
6161- "openai": "codex",
6262- "anthropic": "claude",
6363- "ollama": "opencode",
6464-}
6565-6666-6758def setup_logging(verbose: bool = False) -> logging.Logger:
6859 """Configure logging for agent CLI."""
6960 level = logging.DEBUG if verbose else logging.INFO
···12471238 return "skip", f"Ollama not reachable ({result.get('error', 'unreachable')})"
1248123912491240 # Pre-flight: check cogitate CLI binary is installed
12501250- binary = COGITATE_BINARIES.get(provider_name)
12411241+ binary = PROVIDER_METADATA[provider_name].get("cogitate_cli", "")
12511242 if binary and not shutil.which(binary):
12521243 return "skip", f"{binary} CLI not installed"
12531244
+105-9
think/providers/__init__.py
···2020- ollama: Ollama local models
2121"""
22222323+import os
2424+import shutil
2325from importlib import import_module
2426from types import ModuleType
2527from typing import Any, Dict, List
···4446# ---------------------------------------------------------------------------
4547# Provider Metadata
4648# ---------------------------------------------------------------------------
4747-# Display labels and environment variable names for each provider.
4848-# Used by settings UI to dynamically build provider dropdowns.
4949+# Display labels, environment variable names, and cogitate CLI binary names
5050+# for each provider. Used by settings UI, provider status, and agent health
5151+# checks.
4952# ---------------------------------------------------------------------------
50535154PROVIDER_METADATA: Dict[str, Dict[str, Any]] = {
5255 "google": {
5356 "label": "Google (Gemini)",
5457 "env_key": "GOOGLE_API_KEY",
5858+ "cogitate_cli": "gemini",
5559 "vertex_env_keys": [
5660 "GOOGLE_GENAI_USE_VERTEXAI",
5761 "GOOGLE_APPLICATION_CREDENTIALS",
5862 ],
5963 },
6060- "openai": {"label": "OpenAI (GPT)", "env_key": "OPENAI_API_KEY"},
6161- "anthropic": {"label": "Anthropic (Claude)", "env_key": "ANTHROPIC_API_KEY"},
6262- "ollama": {"label": "Ollama (Local)", "env_key": ""},
6464+ "openai": {
6565+ "label": "OpenAI (GPT)",
6666+ "env_key": "OPENAI_API_KEY",
6767+ "cogitate_cli": "codex",
6868+ },
6969+ "anthropic": {
7070+ "label": "Anthropic (Claude)",
7171+ "env_key": "ANTHROPIC_API_KEY",
7272+ "cogitate_cli": "claude",
7373+ },
7474+ "ollama": {
7575+ "label": "Ollama (Local)",
7676+ "env_key": "",
7777+ "cogitate_cli": "opencode",
7878+ },
6379}
64806581···99115 - label: Display label (e.g., "Google (Gemini)")
100116 - env_key: Environment variable for API key
101117 """
102102- return [
103103- {"name": name, **PROVIDER_METADATA.get(name, {"label": name, "env_key": ""})}
104104- for name in PROVIDER_REGISTRY
105105- ]
118118+ providers = []
119119+ for name in PROVIDER_REGISTRY:
120120+ meta = PROVIDER_METADATA.get(name, {"label": name, "env_key": ""})
121121+ provider = {
122122+ "name": name,
123123+ "label": meta.get("label", name),
124124+ "env_key": meta.get("env_key", ""),
125125+ }
126126+ if "vertex_env_keys" in meta:
127127+ provider["vertex_env_keys"] = meta["vertex_env_keys"]
128128+ providers.append(provider)
129129+ return providers
130130+131131+132132+def build_provider_status(
133133+ providers_list: List[Dict[str, Any]],
134134+ vertex_creds_configured: bool = False,
135135+) -> Dict[str, Dict[str, Any]]:
136136+ """Build per-provider readiness status.
137137+138138+ Parameters
139139+ ----------
140140+ providers_list
141141+ Output of get_provider_list().
142142+ vertex_creds_configured
143143+ Whether Vertex AI credentials are configured (for Google).
144144+145145+ Returns
146146+ -------
147147+ Dict[str, Dict[str, Any]]
148148+ Keyed by provider name. Each entry has: configured, generate_ready,
149149+ cogitate_ready, cogitate_cli, cogitate_cli_found, issues.
150150+ """
151151+ status = {}
152152+ for provider in providers_list:
153153+ name = provider["name"]
154154+ env_key = provider.get("env_key", "")
155155+ meta = PROVIDER_METADATA.get(name, {})
156156+ cogitate_cli = meta.get("cogitate_cli", "")
157157+ issues: list[str] = []
158158+159159+ if name == "ollama":
160160+ try:
161161+ import httpx
162162+163163+ base_url = os.getenv(
164164+ "OLLAMA_BASE_URL", "http://localhost:11434"
165165+ ).rstrip("/")
166166+ resp = httpx.get(f"{base_url}/api/version", timeout=2)
167167+ resp.raise_for_status()
168168+ configured = True
169169+ except Exception:
170170+ configured = False
171171+ base_url = os.getenv(
172172+ "OLLAMA_BASE_URL", "http://localhost:11434"
173173+ ).rstrip("/")
174174+ issues.append(f"Ollama not reachable at {base_url}")
175175+ elif name == "google":
176176+ has_key = bool(os.getenv(env_key))
177177+ configured = has_key or vertex_creds_configured
178178+ if not configured:
179179+ issues.append(f"{env_key} not set")
180180+ else:
181181+ configured = bool(os.getenv(env_key)) if env_key else False
182182+ if not configured and env_key:
183183+ issues.append(f"{env_key} not set")
184184+185185+ cogitate_cli_found = bool(shutil.which(cogitate_cli)) if cogitate_cli else False
186186+ if cogitate_cli and not cogitate_cli_found:
187187+ issues.append(f"{cogitate_cli} CLI not found on PATH")
188188+189189+ generate_ready = configured
190190+ cogitate_ready = configured and cogitate_cli_found
191191+192192+ status[name] = {
193193+ "configured": configured,
194194+ "generate_ready": generate_ready,
195195+ "cogitate_ready": cogitate_ready,
196196+ "cogitate_cli": cogitate_cli,
197197+ "cogitate_cli_found": cogitate_cli_found,
198198+ "issues": issues,
199199+ }
200200+ return status
106201107202108203def get_provider_models(provider: str) -> list[dict]:
···156251 "PROVIDER_METADATA",
157252 "get_provider_module",
158253 "get_provider_list",
254254+ "build_provider_status",
159255 "get_provider_models",
160256 "validate_key",
161257]