Source code for taimoe.platform.registry

"""Runtime registry exposed by customer services."""

from __future__ import annotations

from collections.abc import Iterable
from threading import RLock
from typing import Any

from ._sync_types import SyncErrorHandler
from .agent_sync import AgentSyncer
from .cache import RuntimeConfigCache
from .client import TaimoeClient
from .manifest import ManifestBuilder
from .sync import RuntimeSyncer
from .types.manifest import AgentsManifest, HealthStatus, RuntimeManifest


[docs] class TaimoeRegistry: """Coordinates discovery, sync, and local config access for a runtime service. Construction does not touch global SDK state. To wire this registry into the global ``taimoe`` handle (so user code can call ``taimoe.agent(name)`` and resolve against this registry's cache), do it explicitly:: from taimoe.platform.adapters.adk import taimoe registry = TaimoeRegistry(...) taimoe.bind_to(registry) This keeps tests and multi-tenant setups cleanly separable. """ def __init__( self, *, runtime_name: str | None, platform_url: str, platform_token: str | None = None, runtime_id: str | None = None, poll_interval_seconds: float = 300, framework: str | None = None, framework_version: str | None = None, on_sync_error: SyncErrorHandler | None = None, ) -> None: # runtime_name is optional: when present we run the runtime-scoped # batch sync (one HTTP call returns every agent under that runtime). # When absent we run in *runtime-less* mode and only pull individual # agents via the per-agent AgentSyncer below. self.runtime_name = runtime_name self.runtime_id = runtime_id or runtime_name or "" self.platform_url = platform_url self.platform_token = platform_token self.cache = RuntimeConfigCache() self.client = TaimoeClient(base_url=platform_url, api_key=platform_token) self._agents: list[Any] = [] self._agents_lock = RLock() self._manifest_builder = ManifestBuilder( runtime_name=runtime_name or "runtime-less", framework=framework, framework_version=framework_version, ) self._syncer: RuntimeSyncer | None = None if runtime_name: self._syncer = RuntimeSyncer( client=self.client, runtime_id=self.runtime_id, cache=self.cache, poll_interval_seconds=poll_interval_seconds, on_error=on_sync_error, ) self.agent_syncer = AgentSyncer( client=self.client, cache=self.cache, poll_interval_seconds=poll_interval_seconds, on_error=on_sync_error, )
[docs] def register(self, agent: Any) -> None: with self._agents_lock: self._agents.append(agent)
[docs] def register_many(self, agents: Iterable[Any]) -> None: for agent in agents: self.register(agent)
@property def registered_agents(self) -> tuple[Any, ...]: with self._agents_lock: return tuple(self._agents)
[docs] def runtime_manifest(self) -> RuntimeManifest: return self._manifest_builder.runtime()
[docs] def agents_manifest(self) -> AgentsManifest: return self._manifest_builder.agents(self.registered_agents)
[docs] def health(self) -> HealthStatus: return self._manifest_builder.health()
[docs] def sync_once(self, *, ignore_errors: bool = True) -> None: """Run one synchronous sync pass on both syncers. ``ignore_errors`` defaults to True so a single call site can do cold-start init without each branch having to choose: failure logs a warning, the SDK enters degraded mode, and the next background tick retries. Pass ``ignore_errors=False`` if a caller needs the exception to bubble up (e.g. CI / tests). """ if self._syncer is not None: self._syncer.sync_once(ignore_errors=ignore_errors) self.agent_syncer.sync_once(ignore_errors=ignore_errors)
[docs] def start_sync(self) -> None: if self._syncer is not None: self._syncer.start() self.agent_syncer.start()
[docs] def stop_sync(self, timeout: float | None = None) -> None: if self._syncer is not None: self._syncer.stop(timeout) self.agent_syncer.stop(timeout)
[docs] def fastapi_router(self) -> Any: """Return a single combined FastAPI router for discovery endpoints. Convenient but must be mounted at the FastAPI app root to keep the well-known URI RFC 8615-compliant. Prefer :meth:`install_routes` for new code — it wires both routers with the correct mounts. """ from .routes import create_fastapi_router return create_fastapi_router(self)
[docs] def install_routes(self, app: Any) -> None: """Wire well-known and discovery routes into a FastAPI app. Equivalent to calling :func:`taimoe.platform.routes.install_routes` with this registry, just spelled as a method for ergonomics. """ from .routes import install_routes install_routes(self, app)