"""Model inventory tools — Ollama and MLX model listing.""" from __future__ import annotations import json from datetime import datetime, timezone from ..config import validate_host, HOSTS from ..schemas import ModelsResult, ModelInfo from .ssh import run_command, SSHError def _now() -> str: return datetime.now(timezone.utc).isoformat() def _parse_ollama_list(output: str) -> list[ModelInfo]: """Parse `ollama list` output.""" models = [] for line in output.strip().splitlines()[1:]: # skip header parts = line.split() if len(parts) < 2: continue model_id = parts[0] # Remaining fields vary: ID, SIZE, MODIFIED size = parts[2] + " " + parts[3] if len(parts) > 3 else "" modified = " ".join(parts[4:]) if len(parts) > 4 else "" models.append(ModelInfo(id=model_id, size=size, modified=modified)) return models async def ollama_models(host: str) -> ModelsResult: """List Ollama models on a host.""" try: cfg = validate_host("ollama_models", host) except ValueError as e: return ModelsResult( ok=False, checked_at=_now(), host=host, source="ollama", error_type="parse_error", error=str(e), ) try: stdout, _ = await run_command(cfg, "ollama list") except SSHError as e: return ModelsResult( ok=False, checked_at=_now(), host=host, source="ollama", error_type=e.error_type, error=str(e), ) models = _parse_ollama_list(stdout) return ModelsResult( ok=True, checked_at=_now(), host=host, source="ollama", models=models, raw=stdout.strip(), ) async def mlx_models() -> ModelsResult: """List MLX models loaded on Mac mini.""" cfg = HOSTS["macmini"] try: stdout, _ = await run_command(cfg, "curl -sf http://localhost:8800/v1/models") except SSHError as e: return ModelsResult( ok=False, checked_at=_now(), host="macmini", source="mlx", error_type=e.error_type, error=str(e), ) try: data = json.loads(stdout) model_list = data.get("data", []) models = [ ModelInfo( id=m.get("id", "unknown"), size=str(m.get("size", "")), modified=str(m.get("created", "")), ) for m in model_list ] except (json.JSONDecodeError, KeyError) as e: return ModelsResult( ok=False, checked_at=_now(), host="macmini", source="mlx", error_type="parse_error", error=f"JSON 파싱 실패: {e}", raw=stdout.strip(), ) return ModelsResult( ok=True, checked_at=_now(), host="macmini", source="mlx", models=models, raw=stdout.strip(), )