Files
2026-07-13 13:12:00 +08:00

153 lines
6.1 KiB
Python

"""Resolve the runtime environment used by harness bench processes."""
from __future__ import annotations
import os
from dataclasses import dataclass
@dataclass(frozen=True)
class BenchRuntimeEnv:
"""The environment a bench server/runner/host-daemon is spawned with.
:param base_env: The full env dict (``os.environ`` plus any derived
``OPENAI_*`` / ``DATABRICKS_CONFIG_PROFILE``).
:param db_profile: The resolved Databricks profile name (for
``spec.executor.profile`` / skip-gating), or ``None`` when auth came
from the ambient env or the SDK default.
"""
base_env: dict[str, str]
db_profile: str | None
def _profile_from_config() -> str | None:
"""Return the Databricks profile ``omni run`` would use, or ``None``.
Mirrors ``omni run``'s profile sources, in the same precedence:
1. the user-level ``auth:`` block (``omni setup`` writes this);
2. a top-level ``profile:`` key;
3. the default ``providers:`` entry of ``kind: databricks`` — the common
case for a machine configured through the provider wizard rather than
``auth:`` (e.g. ``providers.databricks {default: true, profile: DEFAULT}``).
``omni run`` resolves creds from this entry's ``profile`` (see
``runtime/workflow.py`` ``DATABRICKS_KIND`` branch), so the bench must too
or a no-flag run would go offline where ``omni run`` goes live.
All imports are lazy so importing this module never drags in ``omnigent.cli``
at load time.
"""
from omnigent.config import load_effective_config, load_global_config
try:
global_config = load_global_config()
except Exception:
global_config = {}
raw_auth = global_config.get("auth")
if isinstance(raw_auth, dict) and raw_auth.get("type") == "databricks":
profile = raw_auth.get("profile")
if profile:
return str(profile)
try:
config = load_effective_config()
except Exception:
return None
cfg_profile = config.get("profile")
if cfg_profile:
return str(cfg_profile)
# Reuse the runtime resolver so bench and normal launches select identically.
try:
from omnigent.onboarding.provider_config import (
DATABRICKS_KIND,
default_provider_for_harness,
)
# claude-sdk is a representative gateway harness; the databricks
# provider is auth-only (serves every family), so the harness choice
# only decides which family's default is consulted, not the profile.
provider = default_provider_for_harness(config, "claude-sdk")
except Exception:
provider = None
if provider is not None and provider.kind == DATABRICKS_KIND and provider.profile:
return str(provider.profile)
return None
def resolve_bench_env(explicit_profile: str | None) -> BenchRuntimeEnv:
"""Build the bench runtime env, mirroring ``omni run``'s credential layering.
Precedence:
1. **Ambient wins.** If both ``OPENAI_BASE_URL`` and ``OPENAI_API_KEY`` are
already in the environment, use them and skip credential resolution
entirely — the same short-circuit ``omni run`` has
(``inner/databricks_executor.py``). This also lets a run with an
exported gateway token work with no profile configured.
2. **Profile.** ``explicit_profile`` (the ``--profile`` flag) wins; else the
config-derived profile (``auth:``/``profile`` in
``~/.omnigent/config.yaml``). May be ``None`` (the resolver then uses the
SDK / ``[DEFAULT]`` path, as ``omni run`` does).
3. **Compose** ``OPENAI_*`` from
:func:`resolve_databricks_workspace` — OAuth-profile aware, fails loud on
a typo'd named profile — filling in only the vars not already ambient.
:param explicit_profile: The ``--profile`` value, or ``None`` to derive.
:returns: A :class:`BenchRuntimeEnv`.
:raises OSError: When credentials cannot be resolved and no ambient
``OPENAI_*`` covers auth (surfaced by the caller as a clean skip).
"""
base = dict(os.environ)
have_ambient = bool(base.get("OPENAI_BASE_URL")) and bool(base.get("OPENAI_API_KEY"))
profile = explicit_profile or _profile_from_config()
if have_ambient:
# Ambient OPENAI_* already routes the gateway; don't mint. Still stamp
# the profile so the runner's DATABRICKS_CONFIG_PROFILE matches.
if profile:
base["DATABRICKS_CONFIG_PROFILE"] = profile
return BenchRuntimeEnv(base_env=base, db_profile=profile)
from omnigent.runtime.credentials.databricks import resolve_databricks_workspace
creds = resolve_databricks_workspace(profile)
base["OPENAI_BASE_URL"] = f"{creds.host}/serving-endpoints"
base["OPENAI_API_KEY"] = creds.token
if profile:
base["DATABRICKS_CONFIG_PROFILE"] = profile
return BenchRuntimeEnv(base_env=base, db_profile=profile)
def bench_creds_skip_reason(explicit_profile: str | None) -> str | None:
"""Cheap gate: why the bench cannot get gateway creds, or ``None`` if it can.
Mirrors :func:`resolve_bench_env`'s precedence without minting a token, so a
driver's ``unavailable()`` can skip a live run cleanly (no creds) rather than
fail mid-provision. A ``--profile`` is no longer required: an ambient
``OPENAI_*`` or a configured ``~/.omnigent`` profile is enough, matching
``omni run``.
:param explicit_profile: The ``--profile`` value, or ``None`` to derive.
:returns: A skip reason, or ``None`` when creds are resolvable.
"""
if os.environ.get("OPENAI_BASE_URL") and os.environ.get("OPENAI_API_KEY"):
return None
profile = explicit_profile or _profile_from_config()
if not profile:
return (
"no gateway creds: pass --profile, configure a profile in "
"~/.omnigent/config.yaml (like `omni run`), or export OPENAI_API_KEY + "
"OPENAI_BASE_URL"
)
from tests.e2e.helpers import lookup_databricks_host
if lookup_databricks_host(profile) is None:
return f"databricks profile {profile!r} missing/hostless in ~/.databrickscfg"
return None
__all__ = ["BenchRuntimeEnv", "bench_creds_skip_reason", "resolve_bench_env"]