diff --git a/agent/copilot_acp_client.py b/agent/copilot_acp_client.py index 452a96701a..5b2d2e46a6 100644 --- a/agent/copilot_acp_client.py +++ b/agent/copilot_acp_client.py @@ -6,6 +6,7 @@ as one prompt, collects text chunks, and returns the minimal OpenAI-client shape from __future__ import annotations +import contextlib import json import logging import os @@ -82,13 +83,10 @@ def _resolve_args() -> list[str]: def _acp_supported(command: str, args: list[str]) -> bool | None: - """Tri-state probe: does ``command`` accept ``--acp``? A CLI without the flag - (older releases, Claude Code v2.x) exits 1 with ``unknown option '--acp'`` and the - parent would wait the full child timeout for stdout that never arrives. - True = help advertises --acp; False = help ran cleanly without it (caller - fast-fails); None = inconclusive (binary missing / --help failed → normal spawn - error). Only probes when ``--acp`` is among ``args`` — a custom transport is the - operator's business.""" + """Tri-state ``--acp`` probe (a CLI without the flag exits 1 and the parent would wait the + full child timeout for stdout that never arrives). True = help advertises --acp; False = + help ran cleanly without it (caller fast-fails); None = inconclusive (binary missing / + --help failed → normal spawn error). Skipped when ``--acp`` is not in ``args`` (custom transport).""" if "--acp" not in args: return True if (cached := _ACP_PROBE_CACHE.get(command)) is not None: @@ -112,8 +110,7 @@ def _resolve_home_dir() -> str: """Stable HOME for child ACP processes; /tmp as a last resort so the child never starts HOME-less.""" if home := os.environ.get("HOME", "").strip(): return home - expanded = os.path.expanduser("~") - if expanded and expanded != "~": + if (expanded := os.path.expanduser("~")) and expanded != "~": return expanded try: import pwd @@ -276,8 +273,8 @@ class CopilotACPClient: def __init__( self, *, api_key: str | None = None, base_url: str | None = None, default_headers: dict[str, str] | None = None, - acp_command: str | None = None, acp_args: list[str] | None = None, acp_cwd: str | None = None, - command: str | None = None, args: list[str] | None = None, **_: Any, + acp_command: str | None = None, acp_args: list[str] | None = None, acp_cwd: str | None = None, command: str | None = None, + args: list[str] | None = None, **_: Any, ): self.api_key = api_key or "copilot-acp" self.base_url = base_url or ACP_MARKER_BASE_URL @@ -300,10 +297,8 @@ class CopilotACPClient: proc.terminate() proc.wait(timeout=2) except Exception: - try: + with contextlib.suppress(Exception): proc.kill() - except Exception: - pass def _create_chat_completion( self, *, model: str | None = None, messages: list[dict[str, Any]] | None = None, timeout: float | None = None, diff --git a/agent/credits_tracker.py b/agent/credits_tracker.py index 493ea8a9e1..3b125a521b 100644 --- a/agent/credits_tracker.py +++ b/agent/credits_tracker.py @@ -237,10 +237,9 @@ def evaluate_credits_notices( return (to_show, to_clear) -# Header contract: (field, header, kind[, default]). Each *-micros balance has a *-usd -# twin holding the server's formatted USD string. micros: required int >= 0 ("signed": -# may be negative). usd: required ^-?\d+\.\d{2}$. bool: optional "true"/"false" STRING. -# Not in the table: subscription-limit-* (PAIRED/optional), tool-pool-micros (optional), +# Header contract: (field, header, kind[, default]). micros: required int >= 0 ("signed": may be +# negative); usd: the server's formatted string, ^-?\d+\.\d{2}$; bool: optional "true"/"false" STRING. +# Handled inline below: subscription-limit-* (PAIRED/optional), tool-pool-micros (optional), # denominator-kind ("subscription_cap" | "none"), disabled-reason (omitted when null). _HEADER_FIELDS: tuple[tuple, ...] = ( ("remaining_micros", "x-nous-credits-remaining-micros", "micros"), diff --git a/agent/gemini_native_adapter.py b/agent/gemini_native_adapter.py index 37b0659825..1b1d71b7f2 100644 --- a/agent/gemini_native_adapter.py +++ b/agent/gemini_native_adapter.py @@ -1,11 +1,8 @@ -"""OpenAI-compatible facade over Google AI Studio's native Gemini API. - -The ``gemini`` provider keeps ``api_mode='chat_completions'`` so the agent loop -stays OpenAI-shaped; this shim converts ``messages[]``/``tools[]`` requests into -``models/{model}:generateContent`` payloads and converts responses back. Google's -OpenAI-compat endpoint is brittle for the multi-turn tool loop (auth churn, -tool-call replay quirks, thought-signature requirements); native is canonical. -""" +"""OpenAI-compatible facade over Google AI Studio's native Gemini API: the ``gemini`` +provider keeps ``api_mode='chat_completions'`` so the agent loop stays OpenAI-shaped, +and this shim converts ``messages[]``/``tools[]`` into ``models/{model}:generateContent`` +payloads and responses back. Google's OpenAI-compat endpoint is brittle for the +multi-turn tool loop (auth churn, tool-call replay, thought signatures); native is canonical.""" from __future__ import annotations @@ -149,8 +146,7 @@ class GeminiAPIError(Exception): def __init__( self, message: str, *, code: str = "gemini_api_error", status_code: Optional[int] = None, - response: Optional[httpx.Response] = None, retry_after: Optional[float] = None, - details: Optional[Dict[str, Any]] = None, + response: Optional[httpx.Response] = None, retry_after: Optional[float] = None, details: Optional[Dict[str, Any]] = None, ) -> None: super().__init__(message) self.code, self.status_code, self.response = code, status_code, response @@ -220,8 +216,8 @@ def _translate_tool_call_to_gemini(tool_call: Dict[str, Any], include_ids: bool except json.JSONDecodeError: args = {"_raw": args_raw} call: Dict[str, Any] = {"name": str(fn.get("name") or ""), "args": args if isinstance(args, dict) else {"_value": args}} - if include_ids and _tool_call_id(tool_call): - call["id"] = _tool_call_id(tool_call) + if include_ids and (call_id := _tool_call_id(tool_call)): + call["id"] = call_id return {"functionCall": call, "thoughtSignature": _tool_call_extra_signature(tool_call) or _SKIP_SIGNATURE} @@ -629,8 +625,8 @@ class GeminiNativeClient: """Minimal OpenAI-SDK-compatible facade (``client.chat.completions.create(**kwargs)``) over Gemini's native REST API.""" - # Declared for agent/auxiliary_client.py: already a complete client, never re-dispatched through - # a wire adapter. (No HERMES_SKIP_ASYNC_WRAP — the async path has a real conversion, AsyncGeminiNativeClient.) + # For agent/auxiliary_client.py: a complete client, never re-dispatched through a wire adapter. + # (No HERMES_SKIP_ASYNC_WRAP — the async path has a real conversion, AsyncGeminiNativeClient.) HERMES_SKIP_TRANSPORT_WRAP = True def __init__( @@ -658,10 +654,8 @@ class GeminiNativeClient: pass def _headers(self) -> Dict[str, str]: - return { - "Content-Type": "application/json", "Accept": "application/json", "x-goog-api-key": self.api_key, - "User-Agent": f"{_API_CLIENT} (gemini-native)", "X-Goog-Api-Client": _API_CLIENT, **self._default_headers, - } + return {"Content-Type": "application/json", "Accept": "application/json", "x-goog-api-key": self.api_key, + "User-Agent": f"{_API_CLIENT} (gemini-native)", "X-Goog-Api-Client": _API_CLIENT, **self._default_headers} @staticmethod def _advance_stream_iterator(iterator: Iterator[_GeminiStreamChunk]) -> tuple[bool, Optional[_GeminiStreamChunk]]: @@ -721,17 +715,14 @@ class AsyncGeminiNativeClient: async def _create_chat_completion(self, **kwargs: Any) -> Any: result = await asyncio.to_thread(self._sync.chat.completions.create, **kwargs) - if not kwargs.get("stream"): - return result + return self._async_stream(result) if kwargs.get("stream") else result - async def _async_stream() -> Any: - while True: - done, chunk = await asyncio.to_thread(self._sync._advance_stream_iterator, result) - if done: - return - yield chunk - - return _async_stream() + async def _async_stream(self, iterator: Iterator[_GeminiStreamChunk]) -> Any: + while True: + done, chunk = await asyncio.to_thread(self._sync._advance_stream_iterator, iterator) + if done: + return + yield chunk async def close(self) -> None: await asyncio.to_thread(self._sync.close)