diff --git a/agent/usage_pricing.py b/agent/usage_pricing.py index ebeb06fc07..2a4fa028a0 100644 --- a/agent/usage_pricing.py +++ b/agent/usage_pricing.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging import re -from dataclasses import dataclass +from dataclasses import dataclass, fields from datetime import datetime, timezone from decimal import Decimal from typing import Any, Dict, Literal, Optional @@ -25,13 +25,9 @@ _INCLUDED_NOTE = "subscription-included; no provider invoice for usage" def format_cost_label(amount: Decimal) -> str: - """Format a cost as a display label, scaling precision to magnitude. - - Zero → "$0.00"; sub-cent → "~$0.0046" (4 dp, or "~$<0.0001" when the - amount rounds to 0.0000 so the label never reads as zero); else "~$1.23". - Shared by per-response cost labels and the insights cost-bucket - formatters so sub-cent honesty cannot regress on one surface. - """ + """Cost display label: zero → "$0.00"; sub-cent → "~$0.0046" (4 dp, or + "~$<0.0001" when it rounds to 0.0000 so the label never reads as zero); + else "~$1.23". Shared by per-response labels and insights cost buckets.""" if amount == _ZERO: return "$0.00" if amount < _SUBCENT_THRESHOLD: @@ -72,14 +68,10 @@ class CanonicalUsage: combined figure covers.""" if not isinstance(other, CanonicalUsage): return NotImplemented - return CanonicalUsage( - input_tokens=self.input_tokens + other.input_tokens, - output_tokens=self.output_tokens + other.output_tokens, - cache_read_tokens=self.cache_read_tokens + other.cache_read_tokens, - cache_write_tokens=self.cache_write_tokens + other.cache_write_tokens, - reasoning_tokens=self.reasoning_tokens + other.reasoning_tokens, - request_count=self.request_count + other.request_count, raw_usage=None, - ) + return CanonicalUsage(**{ + f.name: getattr(self, f.name) + getattr(other, f.name) + for f in fields(CanonicalUsage) if f.name != "raw_usage" + }) @dataclass(frozen=True) diff --git a/agent/verification_evidence.py b/agent/verification_evidence.py index a73f0801fa..d7a7147d20 100644 --- a/agent/verification_evidence.py +++ b/agent/verification_evidence.py @@ -1,9 +1,6 @@ -"""Coding verification evidence ledger. - -Records what the agent actually proved while working in a code workspace. It is -deliberately passive: it never decides to run a suite, never blocks completion, -and never upgrades targeted checks into "repo green". -""" +"""Coding verification evidence ledger: records what the agent actually proved in +a code workspace. Deliberately passive — it never runs a suite, never blocks +completion, and never upgrades targeted checks into "repo green".""" from __future__ import annotations diff --git a/agent/verification_stop.py b/agent/verification_stop.py index 9ebc000a09..9ad54f7f87 100644 --- a/agent/verification_stop.py +++ b/agent/verification_stop.py @@ -1,9 +1,6 @@ -"""Turn-end verification guard for coding edits. - -Policy-only: it never runs checks itself. It turns the passive verification -ledger into a bounded follow-up when the model tries to finish right after -editing code without fresh evidence. -""" +"""Turn-end verification guard for coding edits. Policy-only: it never runs +checks itself, it turns the passive verification ledger into a bounded follow-up +when the model tries to finish right after editing code without fresh evidence.""" from __future__ import annotations @@ -98,10 +95,8 @@ def _candidate_cwds(paths: Iterable[str]) -> list[Path]: def _verification_snapshot( *, session_id: str | None, changed_paths: list[str] ) -> tuple[dict[str, Any], dict[str, Any]] | None: - """Return ``(status, facts)`` for the first edited workspace needing proof. - - Falls back to the first recognized workspace when every one is ``passed``. - """ + """``(status, facts)`` for the first edited workspace needing proof, else the + first recognized workspace when every one is ``passed``.""" try: from agent.coding_context import project_facts_for from agent.verification_evidence import verification_status diff --git a/agent/verify/recipes.py b/agent/verify/recipes.py index d7f90440ba..52d0b42353 100644 --- a/agent/verify/recipes.py +++ b/agent/verify/recipes.py @@ -25,12 +25,9 @@ def _as_strings(value: Any) -> list[str]: @dataclass class Recipe: - """A runnable verification recipe for a project. - - Mirrors grok-cli's ``VerifyRecipe`` (scoped): ``name`` is the human label - (``appLabel``), ``kind`` the detector id (``appKind``); command lists are - shell strings executed in the project root. - """ + """A runnable verification recipe (grok-cli's ``VerifyRecipe``): ``name`` is the + human label (``appLabel``), ``kind`` the detector id (``appKind``); command + lists are shell strings executed in the project root.""" name: str kind: str = "unknown" @@ -287,12 +284,8 @@ def _detect_compose_recipe(root: Path) -> Recipe | None: def detect_recipe(root: Path) -> Recipe | None: - """Detect a verification recipe for the project at ``root``. - - Order mirrors grok-cli's ``inferFallbackRecipe``: package.json wins, then - Python, Go, Rust, Java, then Makefile / docker-compose fallbacks. ``None`` - when nothing recognizable is found. - """ + """Detect a recipe for ``root`` (grok-cli's ``inferFallbackRecipe`` order): package.json + wins, then Python, Go, Rust, Java, then Makefile / docker-compose; ``None`` if nothing matches.""" root = Path(root) pkg = _read_package_json(root) if pkg is not None: diff --git a/agent/verify/runner.py b/agent/verify/runner.py index bf2963e5f8..054f573bd6 100644 --- a/agent/verify/runner.py +++ b/agent/verify/runner.py @@ -1,12 +1,9 @@ -"""Verification runner: execute a Recipe's phases and smoke-test the app. +"""Verification runner: bootstrap -> build -> test -> start in background -> +readiness loop -> teardown (scoped port of grok-cli's verify flow). -Scoped port of grok-cli's verify sub-agent flow (bootstrap -> build -> test -> -start in background -> readiness loop -> teardown) as a plain subprocess runner. - -Commands come from the project's own recipe (package.json scripts, Makefile -targets, ...) and run with ``shell=True`` on purpose: this is a developer tool -running the project's own build commands in its own checkout — the same trust -level as the terminal tool. +Commands come from the project's own recipe and run with ``shell=True`` on +purpose: a developer tool running the project's own build commands in its own +checkout — the same trust level as the terminal tool. """ from __future__ import annotations @@ -128,12 +125,8 @@ def _poll_readiness(url: str, timeout: float, interval: float = 1.0) -> tuple[bo def _terminate_process_group(proc: subprocess.Popen) -> None: - """Terminate the started app and its whole process group cleanly. - - On POSIX the child is spawned with ``start_new_session=True`` so we can - signal the whole group; on Windows (no ``os.killpg``) we fall back to - terminating just the direct child. SIGTERM first, SIGKILL after 10s. - """ + """SIGTERM the app's process group (``start_new_session=True`` on POSIX; just the + direct child on Windows, which lacks ``os.killpg``), SIGKILL after 10s.""" if proc.poll() is not None: return killpg = getattr(os, "killpg", None) @@ -195,12 +188,8 @@ def run_verify( skip_start: bool = False, port_override: int | None = None, stop_on_failure: bool = True, on_output: Callable[[str], None] | None = None, ) -> VerifyResult: - """Run a verify pass for ``recipe`` at project ``root``. - - Executes the selected command phases sequentially, then (unless - ``skip_start`` or a phase failed) launches ``recipe.start`` in the - background, polls the readiness URL, and tears the process group down. - """ + """Run the selected command phases sequentially, then (unless ``skip_start`` or a + phase failed) boot ``recipe.start``, poll readiness, and tear the process group down.""" root = Path(root) selected = tuple(phases) if phases else PHASE_ORDER + ("start",) result = VerifyResult(recipe_name=recipe.name) diff --git a/agent/video_gen_provider.py b/agent/video_gen_provider.py index 94d3880a25..14b0092286 100644 --- a/agent/video_gen_provider.py +++ b/agent/video_gen_provider.py @@ -38,23 +38,14 @@ DEFAULT_RESOLUTION = "720p" class VideoGenProvider(CatalogProviderBase): - """Abstract base class for a video generation backend. - - Subclasses must implement :attr:`name` and :meth:`generate`; everything else - has defaults. ``list_models`` entries are **model families** and may add - ``speed`` / ``strengths`` / ``price`` / advisory ``modalities``. - """ + """Abstract base class for a video generation backend: implement :attr:`name` + and :meth:`generate`. ``list_models`` entries are **model families** and may + add ``speed`` / ``strengths`` / ``price`` / advisory ``modalities``.""" def capabilities(self) -> Dict[str, Any]: - """What this provider supports (all keys optional): ``modalities``, - ``aspect_ratios``, ``resolutions``, ``max_duration`` / ``min_duration``, - ``supports_audio`` / ``supports_negative_prompt`` / ``supports_seed`` / - ``supports_upscale``, ``max_reference_images``. - - Used for soft validation, capability-gated params in the dynamic - ``video_generate`` schema (args a backend can't honor aren't advertised), - and the picker. Default fails closed: text-only, no optional features. - """ + """Supported features (keys below, all optional) used for soft validation, + capability-gated params in the dynamic ``video_generate`` schema, and the + picker. Default fails closed: text-only, no optional features.""" return { "modalities": ["text"], "aspect_ratios": list(COMMON_ASPECT_RATIOS), "resolutions": list(COMMON_RESOLUTIONS), "max_duration": 10, "min_duration": 1, @@ -70,13 +61,10 @@ class VideoGenProvider(CatalogProviderBase): negative_prompt: Optional[str] = None, audio: Optional[bool] = None, seed: Optional[int] = None, **kwargs: Any, ) -> Dict[str, Any]: - """Generate a video from a prompt, or animate ``image_url`` when given. - - Return :func:`success_response` / :func:`error_response`. Unknown - ``kwargs`` MUST be ignored (forward compat). Known optional kwarg: - ``upscale`` (bool) — a post-generation high-res pass; providers that - honor it report ``upscaled: True`` in ``extra``. - """ + """Generate a video from a prompt, or animate ``image_url`` when given; return + :func:`success_response` / :func:`error_response`. Unknown ``kwargs`` MUST be + ignored. Known optional kwarg ``upscale`` (bool): a post-generation high-res + pass; providers that honor it report ``upscaled: True`` in ``extra``.""" def save_b64_video(b64_data: str,*, prefix: str="video", extension: str="mp4") -> Path: @@ -97,11 +85,8 @@ _URL_VIDEO_CONTENT_TYPES = { def save_url_video( url: str, *, prefix: str = "video", timeout: float = 180.0, max_bytes: int = 200 * 1024 * 1024 ) -> Path: - """Download an (often ephemeral) video URL into ``$HERMES_HOME/cache/videos/``. - - Raises on network / HTTP / oversize / empty errors so callers can fall back - to returning the bare URL. See :mod:`agent.provider_media`. - """ + """Download an (often ephemeral) video URL into ``$HERMES_HOME/cache/videos/``; + raises on network / HTTP / oversize / empty errors so callers can fall back to the URL.""" return provider_media.save_url( "videos", url, prefix=prefix, timeout=timeout, max_bytes=max_bytes, chunk_size=256 * 1024, content_types=_URL_VIDEO_CONTENT_TYPES, @@ -139,19 +124,10 @@ class OpenAICompatibleVideoGenProvider(VideoGenProvider): """Generic text/image-to-video over the OpenAI ``client.videos`` API. DeepInfra, OpenAI/Sora, and OpenRouter share the ``POST /videos`` async-job - shape (``create`` → poll → ``download_content``), so the SDK call lives here - once; a concrete backend declares identity and credentials:: - - class FooVideoGenProvider(OpenAICompatibleVideoGenProvider): - name = "foo" - _env_key = "FOO_API_KEY" - _default_base_url = "https://api.foo.com/v1/openai" - def list_models(self): - return [...] # entries with an "id" key; default_model() uses [0] - - ``image_url`` routes to image-to-video; its absence routes to text-to-video. - Provider-specific fields (``image_url``/``negative_prompt``/``seed``) ride - in ``extra_body`` so they pass through the SDK unchanged. + shape (``create`` → poll → ``download_content``); a concrete backend sets + ``name``, ``_env_key``, ``_default_base_url`` and ``list_models()`` (entries + with an ``id`` key; ``default_model()`` uses ``[0]``). Provider-specific + fields (``image_url``/``negative_prompt``/``seed``) ride in ``extra_body``. """ _env_key: str = "OPENAI_API_KEY" diff --git a/agent/vision_message_prep.py b/agent/vision_message_prep.py index ac437eff36..c109efe443 100644 --- a/agent/vision_message_prep.py +++ b/agent/vision_message_prep.py @@ -115,12 +115,8 @@ class VisionMessagePrepMixin: return note def _model_supports_vision(self) -> bool: - """Return True if the active provider+model reports native vision. - - Resolution: ``model.supports_vision`` > ``providers.
.models.