mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-26 14:32:22 +02:00
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015q1TVSLNNsXAoyM7sJuVKP
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""Lay down the backend's data dir before it boots, so the run is ready on the first tick.
|
|
|
|
Everything here is written pre-boot on purpose: the workflow store and the settings
|
|
store both load from disk once at startup, so seeding files is cheaper and more
|
|
deterministic than replaying create/PATCH calls over HTTP (no aux LLM naming call,
|
|
no schedule normalization, no chance of the container inventing a second workflow).
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from typing import Any, Dict
|
|
|
|
from typeguard import typechecked
|
|
|
|
from backend.apps.dashboards.models import Dashboard
|
|
from backend.apps.settings.models import AppSettings
|
|
from runner.run_spec import CLOUD_RUN_DASHBOARD_ID, RunSpec
|
|
|
|
# 9Router provider id -> the AppSettings field the backend reads a raw key from.
|
|
API_KEY_SETTINGS_FIELD: Dict[str, str] = {
|
|
"anthropic": "anthropic_api_key",
|
|
"openai": "openai_api_key",
|
|
"gemini": "google_api_key",
|
|
"google": "google_api_key",
|
|
"openrouter": "openrouter_api_key",
|
|
}
|
|
|
|
# One synthetic id for every cloud run. Without it each ephemeral container mints a fresh uuid and analytics sees a brand-new "install" per run.
|
|
CLOUD_RUNNER_INSTALLATION_ID = "openswarm-cloud-runner"
|
|
|
|
|
|
@typechecked
|
|
def p_write_json(path: str, payload: Any) -> None:
|
|
"""Atomic, owner-only write; these files hold API keys."""
|
|
directory = os.path.dirname(path)
|
|
os.makedirs(directory, mode=0o700, exist_ok=True)
|
|
handle, temp_path = tempfile.mkstemp(dir=directory, prefix=".seed-", suffix=".json")
|
|
try:
|
|
with os.fdopen(handle, "w", encoding="utf-8") as stream:
|
|
json.dump(payload, stream, indent=2)
|
|
os.chmod(temp_path, 0o600)
|
|
os.replace(temp_path, path)
|
|
except BaseException:
|
|
if os.path.exists(temp_path):
|
|
os.unlink(temp_path)
|
|
raise
|
|
|
|
|
|
@typechecked
|
|
def settings_for_run(spec: RunSpec) -> AppSettings:
|
|
"""The AppSettings a cloud run needs: this workflow's model, this run's keys, no telemetry."""
|
|
settings = AppSettings()
|
|
settings.default_model = spec.workflow.model
|
|
settings.connection_mode = "own_key"
|
|
settings.analytics_opt_in = False
|
|
settings.installation_id = CLOUD_RUNNER_INSTALLATION_ID
|
|
for credential in spec.credentials:
|
|
if credential.auth_type != "api_key":
|
|
continue
|
|
field = API_KEY_SETTINGS_FIELD.get(credential.provider)
|
|
if field is None:
|
|
raise ValueError(
|
|
f"no settings field for api_key provider {credential.provider!r}; "
|
|
f"supported: {', '.join(sorted(set(API_KEY_SETTINGS_FIELD)))}"
|
|
)
|
|
setattr(settings, field, credential.api_key)
|
|
return settings
|
|
|
|
|
|
@typechecked
|
|
def seed_data_root(data_root: str, spec: RunSpec) -> None:
|
|
"""Write the workflow, settings and dashboard records the backend will read at boot.
|
|
|
|
The dashboard exists so the Electron window has somewhere to land and browser cards have
|
|
somewhere to render. Writing it here rather than letting the backend's first-boot migration
|
|
invent one keeps its id knowable before anything has started.
|
|
"""
|
|
workflow = spec.workflow_for_disk()
|
|
p_write_json(
|
|
os.path.join(data_root, "workflows", f"{workflow.id}.json"),
|
|
workflow.model_dump(mode="json"),
|
|
)
|
|
p_write_json(
|
|
os.path.join(data_root, "settings", "settings.json"),
|
|
settings_for_run(spec).model_dump(mode="json"),
|
|
)
|
|
dashboard = Dashboard(id=CLOUD_RUN_DASHBOARD_ID, name=spec.workflow.title or "Cloud run")
|
|
p_write_json(
|
|
os.path.join(data_root, "dashboards", f"{dashboard.id}.json"),
|
|
dashboard.model_dump(mode="json"),
|
|
)
|