"""Shared provider auth status formatting.""" from __future__ import annotations from typing import TYPE_CHECKING, assert_never from textual.content import Content from deepagents_code.model_config import ( CODEX_PROVIDER, ProviderAuthSource, ProviderAuthState, ProviderAuthStatus, resolved_env_var_name, ) if TYPE_CHECKING: from deepagents_code.config import Glyphs def format_auth_badge(status: ProviderAuthStatus) -> Content: """Format an auth manager badge for a provider. Used by the `/auth` manager, where each provider renders a bracketed, styled badge (e.g. `[stored]`, `[env set: ANTHROPIC_API_KEY]`, `[missing]`). Args: status: Provider auth/readiness status. Returns: A styled badge `Content` for the auth manager surface. """ # The ChatGPT-OAuth codex provider has no API key, so its credential is a # file-backed OAuth token rather than a stored key. Give it distinctive # badges (`[chatgpt]` / `[sign in to chatgpt]`) so users don't read the # generic `[stored]`/`[missing]` as a literal API key on disk. if status.provider == CODEX_PROVIDER: return _format_codex_badge(status) state = status.state match state: case ProviderAuthState.CONFIGURED: return _format_configured_badge(status) case ProviderAuthState.MISSING: return Content.styled("[missing]", "bold $warning") case ProviderAuthState.NOT_REQUIRED: return _auth_badge(status.detail or "no API key required") case ProviderAuthState.IMPLICIT: return _auth_badge(status.detail or "implicit auth") case ProviderAuthState.MANAGED: return _auth_badge(status.detail or "custom auth") case ProviderAuthState.UNKNOWN: return _auth_badge(status.detail or "credentials unknown", prefix="? ") case _: assert_never(state) def format_auth_indicator(status: ProviderAuthStatus, glyphs: Glyphs) -> str: """Format a model selector provider-header indicator. Used by `/model`, where the indicator is plain text shown next to the provider name. Returns an empty string for `CONFIGURED` providers, which need no indicator. Args: status: Provider auth/readiness status. glyphs: Glyph table for the active terminal mode. Returns: Text shown next to the provider name, or an empty string when no indicator should be rendered (e.g., `CONFIGURED`). """ state = status.state match state: case ProviderAuthState.CONFIGURED: return "" case ProviderAuthState.MISSING: return f"{glyphs.warning} missing credentials" case ProviderAuthState.NOT_REQUIRED: return status.detail or "no API key required" case ProviderAuthState.IMPLICIT: return status.detail or "implicit auth" case ProviderAuthState.MANAGED: return status.detail or "custom auth" case ProviderAuthState.UNKNOWN: detail = status.detail or "credentials unknown" return f"{glyphs.question} {detail}" case _: assert_never(state) def _auth_badge(detail: str, *, prefix: str = "") -> Content: """Format a muted auth manager badge. Args: detail: Badge text inside the brackets. prefix: Text prepended verbatim before `detail` inside the brackets. Callers include any separator themselves (e.g. `"? "`); this helper does not insert one. Returns: Formatted auth manager badge content. """ return Content.assemble( ("[", "$text-muted"), (prefix, "$text-muted"), Content.styled(detail, "$text-muted"), ("]", "$text-muted"), ) def _format_codex_badge(status: ProviderAuthStatus) -> Content: """Format the auth manager badge for the ChatGPT-OAuth codex provider. Codex signs in through ChatGPT OAuth rather than an API key, so a `CONFIGURED` status renders as `[chatgpt]` (carrying the plan name when the status detail includes one) and any other state renders as a `[sign in to chatgpt]` prompt. Args: status: The codex provider's auth status. Returns: A styled badge reflecting ChatGPT sign-in state. """ if status.state is ProviderAuthState.CONFIGURED: badge_text = "[chatgpt]" # `_get_codex_auth_status` encodes the plan as a trailing "(plan)" in # the detail string (e.g. "signed in to ChatGPT (pro)"). if status.detail and (plan := _codex_plan_from_detail(status.detail)): badge_text = f"[chatgpt: {plan}]" return Content.styled(badge_text, "bold $success") return Content.styled("[sign in to chatgpt]", "bold $warning") def _codex_plan_from_detail(detail: str) -> str | None: """Extract the ChatGPT plan from a codex auth detail string. Args: detail: Human-readable codex auth status detail. Returns: The ChatGPT plan text, or `None` when no bounded plan is present. """ _, marker, plan_tail = detail.partition("(") if not marker: return None plan, marker, _ = plan_tail.partition(")") if not marker or not plan: return None return plan def _format_configured_badge(status: ProviderAuthStatus) -> Content: """Format the auth manager badge for a `CONFIGURED` provider. Args: status: A `CONFIGURED` provider auth status. Returns: A styled badge naming the credential source (`[stored]` or `[env set: …]`). Raises: ValueError: If the status carries no source. `ProviderAuthStatus` guarantees `CONFIGURED` implies a source, so this guards against that invariant being violated rather than a normal input. """ match status.source: case ProviderAuthSource.STORED: return Content.styled("[stored]", "bold $success") case ProviderAuthSource.ENV: if status.env_var: return Content.assemble( ("[env set: ", "$text-muted"), Content.styled( resolved_env_var_name(status.env_var), "$text-muted" ), ("]", "$text-muted"), ) return Content.styled("[env]", "$text-muted") case None: msg = f"CONFIGURED auth status has no source: {status!r}" raise ValueError(msg) case _: assert_never(status.source)