| """ |
| Cloud → registry sync (P0-1). |
| |
| The registry only becomes *real* when it is fed by OllaBridge Cloud: a paired |
| Colab/RunPod/home node advertises itself and its models to Cloud over the |
| outbound relay, and HomePilot must pull that inventory into its own |
| ``ComputeDevice`` / ``ModelManifest`` tables so the Resources panel and the |
| per-model selector show live nodes and route to them. |
| |
| This module maps Cloud's ``GET /v1/devices`` (paired devices + heartbeat/GPU) |
| and ``GET /ollama/v1/models`` (advertised models, tagged to a device) into the |
| registry. Capabilities come from what the node/device advertises — never from |
| guessing model names. |
| |
| The HTTP fetch is injected (``fetch(path) -> (status, json)``) so the mapping |
| and reconcile logic are unit-testable without a live Cloud; the route supplies a |
| real httpx-backed fetcher with the server-side cloud token. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import re |
| import time |
| from datetime import datetime, timezone |
| from typing import Any, Awaitable, Callable, Optional |
|
|
| from . import registry |
| from .schemas import ComputeDevice, ComputeSource, ModelManifest |
|
|
| |
| |
| CLOUD_SOURCE_ID = "ollabridge-cloud" |
|
|
| Fetch = Callable[[str], Awaitable[tuple[int, Any]]] |
|
|
| _RELAY_OWNER_RE = re.compile(r"^relay:(?P<dev>[^:]+)") |
| |
| _CHAT_CAPS = {"chat", "text", "completion"} |
| _VISION_CAPS = {"vision", "multimodal", "image-input"} |
|
|
|
|
| def ensure_cloud_source(base_url: Optional[str] = None) -> ComputeSource: |
| """Ensure the ollabridge Cloud source row exists (idempotent).""" |
| existing = registry.get_source(CLOUD_SOURCE_ID) |
| source = ComputeSource( |
| id=CLOUD_SOURCE_ID, |
| name="OllaBridge Cloud", |
| kind="ollabridge", |
| base_url=base_url or (existing.base_url if existing else None), |
| enabled=existing.enabled if existing else True, |
| execution_type="relay", |
| meta={"managed": True}, |
| ) |
| return registry.upsert_source(source) |
|
|
|
|
| def _parse_last_seen(value: Any) -> Optional[float]: |
| if value is None: |
| return None |
| if isinstance(value, (int, float)): |
| return float(value) |
| try: |
| s = str(value).replace("Z", "+00:00") |
| return datetime.fromisoformat(s).timestamp() |
| except Exception: |
| return None |
|
|
|
|
| def map_device(cloud_dev: dict, *, now: Optional[float] = None) -> ComputeDevice: |
| """Cloud device record → registry ComputeDevice (defensive about shape).""" |
| now = now or time.time() |
| gpu = cloud_dev.get("gpu") if isinstance(cloud_dev.get("gpu"), dict) else {} |
| gpu_name = gpu.get("name") or cloud_dev.get("gpu_name") |
| vram_mb = gpu.get("vram_mb") if gpu else cloud_dev.get("vram_mb") |
| online = bool(cloud_dev.get("online", False)) |
| last_seen = _parse_last_seen(cloud_dev.get("last_seen") or cloud_dev.get("last_heartbeat")) |
| return ComputeDevice( |
| id=str(cloud_dev.get("id")), |
| source_id=CLOUD_SOURCE_ID, |
| name=cloud_dev.get("name") or str(cloud_dev.get("id")), |
| online=online, |
| ephemeral=bool(cloud_dev.get("ephemeral", False)), |
| gpu_name=gpu_name, |
| vram_mb=int(vram_mb) if vram_mb is not None else None, |
| capacity=int(cloud_dev.get("capacity", 1)), |
| active_jobs=int(cloud_dev.get("active_jobs", 0)), |
| |
| |
| last_heartbeat=last_seen if last_seen is not None else (now if online else None), |
| ) |
|
|
|
|
| def _device_id_of(model: dict) -> str: |
| for key in ("device_id", "deviceId"): |
| val = model.get(key) |
| if val: |
| return str(val) |
| m = _RELAY_OWNER_RE.match(str(model.get("owned_by") or "")) |
| return m.group("dev") if m else "" |
|
|
|
|
| def _caps_for(device_caps: list[str], model: dict) -> list[str]: |
| """Manifest-driven capabilities: prefer the model's own advertised caps, |
| then the device's; fall back to chat. No name-guessing.""" |
| raw = model.get("capabilities") or device_caps or [] |
| caps: list[str] = [] |
| lowered = {str(c).lower() for c in raw} |
| if lowered & _VISION_CAPS: |
| caps.append("multimodal") |
| if not caps or (lowered & _CHAT_CAPS): |
| if "chat" not in caps: |
| caps.insert(0, "chat") |
| return caps or ["chat"] |
|
|
|
|
| def map_manifests( |
| models_payload: Any, device_caps: dict[str, list[str]] |
| ) -> list[ModelManifest]: |
| """Advertised models → manifests, grouped by owning device.""" |
| data = models_payload.get("data") if isinstance(models_payload, dict) else models_payload |
| if not isinstance(data, list): |
| return [] |
| by_id: dict[str, ModelManifest] = {} |
| for m in data: |
| if not isinstance(m, dict): |
| continue |
| source = m.get("x_source") or m.get("source") |
| if source not in (None, "shared_device"): |
| |
| continue |
| dev_id = _device_id_of(m) |
| mid = m.get("id") |
| if not (dev_id and mid): |
| continue |
| caps = _caps_for(device_caps.get(dev_id, []), m) |
| man = by_id.get(mid) |
| if man is None: |
| man = ModelManifest( |
| id=mid, |
| runtime=str(m.get("runtime") or "ollama"), |
| capabilities=caps, |
| device_ids=[dev_id], |
| digest=m.get("digest"), |
| ) |
| by_id[mid] = man |
| else: |
| if dev_id not in man.device_ids: |
| man.device_ids.append(dev_id) |
| for c in caps: |
| if c not in man.capabilities: |
| man.capabilities.append(c) |
| return list(by_id.values()) |
|
|
|
|
| async def sync_from_cloud(base_url: str, *, fetch: Fetch) -> dict: |
| """Pull devices + advertised models from Cloud into the registry. |
| |
| Returns a summary the UI shows: how many devices/models synced, and whether |
| the account is linked/reachable. Never raises for an unreachable Cloud — it |
| reports ``linked=False`` so the UI can prompt a re-link. |
| """ |
| ensure_cloud_source(base_url) |
|
|
| try: |
| dev_status, dev_payload = await fetch("/v1/devices") |
| mdl_status, mdl_payload = await fetch("/ollama/v1/models") |
| except Exception as exc: |
| return {"ok": False, "linked": False, "reason": f"Cloud unreachable: {exc}", |
| "devices": 0, "models": 0} |
|
|
| if dev_status == 401 or mdl_status == 401: |
| return {"ok": False, "linked": False, "reason": "Cloud rejected the token — re-link your account.", |
| "devices": 0, "models": 0} |
|
|
| cloud_devices = dev_payload if isinstance(dev_payload, list) else [] |
| device_caps: dict[str, list[str]] = {} |
| reported_ids: set[str] = set() |
|
|
| for cd in cloud_devices: |
| if not isinstance(cd, dict) or not cd.get("id"): |
| continue |
| dev = map_device(cd) |
| registry.upsert_device(dev) |
| reported_ids.add(dev.id) |
| caps = cd.get("capabilities") |
| if isinstance(caps, list): |
| device_caps[dev.id] = [str(c) for c in caps] |
|
|
| |
| |
| |
| for existing in registry.list_devices(CLOUD_SOURCE_ID): |
| if existing.id not in reported_ids and existing.online: |
| existing.online = False |
| registry.upsert_device(existing) |
|
|
| manifests = map_manifests(mdl_payload, device_caps) |
| for man in manifests: |
| registry.upsert_manifest(man) |
|
|
| return { |
| "ok": True, |
| "linked": True, |
| "devices": len(reported_ids), |
| "models": len(manifests), |
| "source_id": CLOUD_SOURCE_ID, |
| } |
|
|