mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 22:01:59 -06:00
Add GitHub integration service and endpoints
This commit is contained in:
@@ -19,6 +19,7 @@ from .enrollment import (
|
||||
PollingResult,
|
||||
)
|
||||
from .jobs.scheduler_service import SchedulerService
|
||||
from .github import GitHubService, GitHubTokenPayload
|
||||
from .realtime import AgentRealtimeService, AgentRecord
|
||||
|
||||
__all__ = [
|
||||
@@ -37,4 +38,6 @@ __all__ = [
|
||||
"AgentRealtimeService",
|
||||
"AgentRecord",
|
||||
"SchedulerService",
|
||||
"GitHubService",
|
||||
"GitHubTokenPayload",
|
||||
]
|
||||
|
||||
@@ -9,10 +9,12 @@ from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
from Data.Engine.config import EngineSettings
|
||||
from Data.Engine.integrations.github import GitHubArtifactProvider
|
||||
from Data.Engine.repositories.sqlite import (
|
||||
SQLiteConnectionFactory,
|
||||
SQLiteDeviceRepository,
|
||||
SQLiteEnrollmentRepository,
|
||||
SQLiteGitHubRepository,
|
||||
SQLiteJobRepository,
|
||||
SQLiteRefreshTokenRepository,
|
||||
)
|
||||
@@ -26,6 +28,7 @@ from Data.Engine.services.auth import (
|
||||
from Data.Engine.services.crypto.signing import ScriptSigner, load_signer
|
||||
from Data.Engine.services.enrollment import EnrollmentService
|
||||
from Data.Engine.services.enrollment.nonce_cache import NonceCache
|
||||
from Data.Engine.services.github import GitHubService
|
||||
from Data.Engine.services.jobs import SchedulerService
|
||||
from Data.Engine.services.rate_limit import SlidingWindowRateLimiter
|
||||
from Data.Engine.services.realtime import AgentRealtimeService
|
||||
@@ -42,6 +45,7 @@ class EngineServiceContainer:
|
||||
dpop_validator: DPoPValidator
|
||||
agent_realtime: AgentRealtimeService
|
||||
scheduler_service: SchedulerService
|
||||
github_service: GitHubService
|
||||
|
||||
|
||||
def build_service_container(
|
||||
@@ -56,6 +60,7 @@ def build_service_container(
|
||||
token_repo = SQLiteRefreshTokenRepository(db_factory, logger=log.getChild("tokens"))
|
||||
enrollment_repo = SQLiteEnrollmentRepository(db_factory, logger=log.getChild("enrollment"))
|
||||
job_repo = SQLiteJobRepository(db_factory, logger=log.getChild("jobs"))
|
||||
github_repo = SQLiteGitHubRepository(db_factory, logger=log.getChild("github_repo"))
|
||||
|
||||
jwt_service = load_jwt_service()
|
||||
dpop_validator = DPoPValidator()
|
||||
@@ -101,6 +106,20 @@ def build_service_container(
|
||||
logger=log.getChild("scheduler"),
|
||||
)
|
||||
|
||||
github_provider = GitHubArtifactProvider(
|
||||
cache_file=settings.github.cache_file,
|
||||
default_repo=settings.github.default_repo,
|
||||
default_branch=settings.github.default_branch,
|
||||
refresh_interval=settings.github.refresh_interval_seconds,
|
||||
logger=log.getChild("github.provider"),
|
||||
)
|
||||
github_service = GitHubService(
|
||||
repository=github_repo,
|
||||
provider=github_provider,
|
||||
logger=log.getChild("github"),
|
||||
)
|
||||
github_service.start_background_refresh()
|
||||
|
||||
return EngineServiceContainer(
|
||||
device_auth=device_auth,
|
||||
token_service=token_service,
|
||||
@@ -109,6 +128,7 @@ def build_service_container(
|
||||
dpop_validator=dpop_validator,
|
||||
agent_realtime=agent_realtime,
|
||||
scheduler_service=scheduler_service,
|
||||
github_service=github_service,
|
||||
)
|
||||
|
||||
|
||||
|
||||
8
Data/Engine/services/github/__init__.py
Normal file
8
Data/Engine/services/github/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""GitHub-oriented services for the Borealis Engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .github_service import GitHubService, GitHubTokenPayload
|
||||
|
||||
__all__ = ["GitHubService", "GitHubTokenPayload"]
|
||||
|
||||
106
Data/Engine/services/github/github_service.py
Normal file
106
Data/Engine/services/github/github_service.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""GitHub service layer bridging repositories and integrations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Optional
|
||||
|
||||
from Data.Engine.domain.github import GitHubRepoRef, GitHubTokenStatus, RepoHeadSnapshot
|
||||
from Data.Engine.integrations.github import GitHubArtifactProvider
|
||||
from Data.Engine.repositories.sqlite.github_repository import SQLiteGitHubRepository
|
||||
|
||||
__all__ = ["GitHubService", "GitHubTokenPayload"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class GitHubTokenPayload:
|
||||
token: Optional[str]
|
||||
status: GitHubTokenStatus
|
||||
checked_at: int
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
payload = self.status.to_dict()
|
||||
payload.update(
|
||||
{
|
||||
"token": self.token or "",
|
||||
"checked_at": self.checked_at,
|
||||
}
|
||||
)
|
||||
return payload
|
||||
|
||||
|
||||
class GitHubService:
|
||||
"""Coordinate GitHub caching, verification, and persistence."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
repository: SQLiteGitHubRepository,
|
||||
provider: GitHubArtifactProvider,
|
||||
logger: Optional[logging.Logger] = None,
|
||||
clock: Optional[Callable[[], float]] = None,
|
||||
) -> None:
|
||||
self._repository = repository
|
||||
self._provider = provider
|
||||
self._log = logger or logging.getLogger("borealis.engine.services.github")
|
||||
self._clock = clock or time.time
|
||||
self._token_cache: Optional[str] = None
|
||||
self._token_loaded_at: float = 0.0
|
||||
|
||||
initial_token = self._repository.load_token()
|
||||
self._apply_token(initial_token)
|
||||
|
||||
def get_repo_head(
|
||||
self,
|
||||
owner_repo: Optional[str],
|
||||
branch: Optional[str],
|
||||
*,
|
||||
ttl_seconds: int,
|
||||
force_refresh: bool = False,
|
||||
) -> RepoHeadSnapshot:
|
||||
repo_str = (owner_repo or self._provider.default_repo).strip()
|
||||
branch_name = (branch or self._provider.default_branch).strip()
|
||||
repo = GitHubRepoRef.parse(repo_str, branch_name)
|
||||
ttl = max(30, min(ttl_seconds, 3600))
|
||||
return self._provider.fetch_repo_head(repo, ttl_seconds=ttl, force_refresh=force_refresh)
|
||||
|
||||
def refresh_default_repo(self, *, force: bool = False) -> RepoHeadSnapshot:
|
||||
return self._provider.refresh_default_repo_head(force=force)
|
||||
|
||||
def get_token_status(self, *, force_refresh: bool = False) -> GitHubTokenPayload:
|
||||
token = self._load_token(force_refresh=force_refresh)
|
||||
status = self._provider.verify_token(token)
|
||||
return GitHubTokenPayload(token=token, status=status, checked_at=int(self._clock()))
|
||||
|
||||
def update_token(self, token: Optional[str]) -> GitHubTokenPayload:
|
||||
normalized = (token or "").strip()
|
||||
self._repository.store_token(normalized)
|
||||
self._apply_token(normalized)
|
||||
status = self._provider.verify_token(normalized)
|
||||
self._provider.start_background_refresh()
|
||||
self._log.info("github-token updated valid=%s", status.valid)
|
||||
return GitHubTokenPayload(token=normalized or None, status=status, checked_at=int(self._clock()))
|
||||
|
||||
def start_background_refresh(self) -> None:
|
||||
self._provider.start_background_refresh()
|
||||
|
||||
@property
|
||||
def default_refresh_interval(self) -> int:
|
||||
return self._provider.refresh_interval
|
||||
|
||||
def _load_token(self, *, force_refresh: bool = False) -> Optional[str]:
|
||||
now = self._clock()
|
||||
if not force_refresh and self._token_cache is not None and (now - self._token_loaded_at) < 15.0:
|
||||
return self._token_cache
|
||||
|
||||
token = self._repository.load_token()
|
||||
self._apply_token(token)
|
||||
return token
|
||||
|
||||
def _apply_token(self, token: Optional[str]) -> None:
|
||||
self._token_cache = (token or "").strip() or None
|
||||
self._token_loaded_at = self._clock()
|
||||
self._provider.set_token(self._token_cache)
|
||||
|
||||
Reference in New Issue
Block a user