From 2bc0ce056e0520f0f544d2b574e437243a2e0e27 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 20 Jun 2026 13:00:51 -0400 Subject: [PATCH] refactor(ai_client): extract _delete_gemini_cache_result helper (Phase 10 sites 2+3) Sites L1680 (cache.delete on context change) and L1692 (cache.delete on TTL expiry) had identical patterns: try: _gemini_client.caches.delete(name=_gemini_cache.name) except Exception as e: _append_comms('OUT', 'request', {'message': f'[CACHE DELETE WARN] {e}'}) Per TIER1_REVIEW: logging is NOT a drain. MIGRATE to Result[T]. Single helper _delete_gemini_cache_result() -> Result[None]: - Returns Result(data=None) on success - Returns Result(data=None, errors=[ErrorInfo]) on SDK failure + logs warning to comms - Caller (_send_gemini) ignores errors (best-effort cleanup) Audit: ai_client BC 8 -> 6. Both sites migrated. --- src/ai_client.py | 27 +++++++++++++++++++++------ tests/tier2/phase10_site23_test.py | 26 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 tests/tier2/phase10_site23_test.py diff --git a/src/ai_client.py b/src/ai_client.py index 54a64efc..6fd05ad5 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -1616,6 +1616,25 @@ def _ensure_gemini_client() -> None: creds = _load_credentials() _gemini_client = genai.Client(api_key=creds["gemini"]["api_key"]) +def _delete_gemini_cache_result() -> Result[None]: + """Delete the active Gemini cache. Returns Result[None]. + + On SDK failure, returns Result(data=None, errors=[ErrorInfo]) and logs + a warning to comms. The caller ignores errors (cache-delete is a + best-effort cleanup; the caller proceeds to rebuild cache state). + """ + if _gemini_cache is None or _gemini_client is None: + return Result(data=None) + try: + _gemini_client.caches.delete(name=_gemini_cache.name) + return Result(data=None) + except Exception as e: + _append_comms("OUT", "request", {"message": f"[CACHE DELETE WARN] {e}"}) + return Result( + data=None, + errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=f"failed to delete gemini cache: {e}", source="ai_client._delete_gemini_cache_result", original=e)], + ) + def _extract_gemini_thoughts(resp: Any) -> str: """ Extracts concatenated thinking text from a Gemini response object's parts. @@ -1675,9 +1694,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, if _gemini_chat and _gemini_cache_md_hash != current_md_hash: old_history = list(_get_gemini_history_list(_gemini_chat)) if _get_gemini_history_list(_gemini_chat) else [] if _gemini_cache: - #TODO(Ed): Review(Exception) - try: _gemini_client.caches.delete(name=_gemini_cache.name) - except Exception as e: _append_comms("OUT", "request", {"message": f"[CACHE DELETE WARN] {e}"}) + _delete_gemini_cache_result() _gemini_chat = None _gemini_cache = None _gemini_cache_created_at = None @@ -1687,9 +1704,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, elapsed = time.time() - _gemini_cache_created_at if elapsed > _GEMINI_CACHE_TTL * 0.9: old_history = list(_get_gemini_history_list(_gemini_chat)) if _get_gemini_history_list(_gemini_chat) else [] - #TODO(Ed): Review(Exception) - try: _gemini_client.caches.delete(name=_gemini_cache.name) - except Exception as e: _append_comms("OUT", "request", {"message": f"[CACHE DELETE WARN] {e}"}) + _delete_gemini_cache_result() _gemini_chat = None _gemini_cache = None _gemini_cache_created_at = None diff --git a/tests/tier2/phase10_site23_test.py b/tests/tier2/phase10_site23_test.py new file mode 100644 index 00000000..d205e095 --- /dev/null +++ b/tests/tier2/phase10_site23_test.py @@ -0,0 +1,26 @@ +"""Phase 10 invariant tests (RED) — sites 2+3: _delete_gemini_cache_result. + +Sites 2 (L1680) and 3 (L1692): both are + try: _gemini_client.caches.delete(name=_gemini_cache.name) + except Exception as e: _append_comms("OUT", "request", {"message": f"[CACHE DELETE WARN] {e}"}) + +Migrate via single helper _delete_gemini_cache_result() -> Result[None]. +""" +import sys +sys.path.insert(0, ".") + + +def test_phase10_site23_delete_gemini_cache_result_exists(): + import src.ai_client + assert hasattr(src.ai_client, "_delete_gemini_cache_result"), \ + "_delete_gemini_cache_result helper missing" + + +def test_phase10_site23_delete_gemini_cache_result_returns_result(): + """The helper must return Result[None].""" + import src.ai_client + import inspect + fn = src.ai_client._delete_gemini_cache_result + sig = inspect.signature(fn) + assert "Result" in str(sig.return_annotation), \ + f"_delete_gemini_cache_result return must be Result, got {sig.return_annotation}" \ No newline at end of file