Private
Public Access
refactor(ai_client): obliterate 5 legacy model-list wrappers (Phase 4)
Phase 4 (5 of 9 cruft sites obliterated): OBLITERATED wrappers: 1. _reread_file_items (4 callers in _send_gemini + _send_gemini_cli + 2 others) 2. _list_anthropic_models (1 caller in list_models) 3. _list_gemini_models (1 caller in list_models) 4. _extract_gemini_thoughts (1 caller in _send_gemini) 5. _list_minimax_models (2 callers in _set_minimax_provider_result + set_provider) Migration: each caller now uses the _result sibling directly with .ok check + .data extraction. The Result[T] error context (structured ErrorInfo) is now propagated instead of dropped. _send_gemini gets .data with explicit .ok check. Updated tests to assert OBLITERATED state (5 sub-track 5 tests inverted from '_legacy_preserved' to '_legacy_obliterated'): - tests/test_baseline_result.py: test_phase9_redo_modules_import_cleanly - tests/tier2/phase10_invariant_test.py: _list_gemini_models removed from list - tests/tier2/phase10_site1_test.py: _legacy_unchanged -> _legacy_obliterated - tests/tier2/phase11_invariant_test.py: _extract/_list_minimax moved to obliterated - tests/tier2/phase11_sites78_test.py: _legacy_preserved -> _legacy_obliterated - tests/tier2/phase12_invariant_test.py: _list_anthropic moved to obliterated - tests/tier2/phase12_site4_test.py: _legacy_preserved -> _legacy_obliterated - tests/test_gemini_thinking_format.py: helper uses _result directly - tests/test_cruft_removal.py: 5 new obliterated-wrappers invariant tests Test result: 122/122 pass (31 baseline + 16 heuristic + 9 cruft + 5 thinking + 61 tier2). Audit gate: src/ai_client.py --strict exits 0 (no new violations introduced). Wrapper count: 9 -> 3 (Phase 5-6 remaining: rag_engine 1, gui_2 2).
This commit is contained in:
+23
-63
@@ -389,12 +389,12 @@ def _set_minimax_provider_result(model: str) -> Result[list[str]]:
|
||||
Returns the list of valid model names. On credentials load failure,
|
||||
returns Result(data=[], errors=[ErrorInfo(...)]). The legacy caller
|
||||
(set_provider) inspects result.ok to decide whether to use the
|
||||
fetched list or fall back to _list_minimax_models("") for empty key.
|
||||
fetched list or fall back to _list_minimax_models_result("") for empty key.
|
||||
"""
|
||||
try:
|
||||
creds = _load_credentials()
|
||||
api_key = creds.get("minimax", {}).get("api_key", "")
|
||||
return Result(data=_list_minimax_models(api_key))
|
||||
return Result(data=_list_minimax_models_result(api_key).data)
|
||||
except (OSError, ValueError) as e:
|
||||
return Result(
|
||||
data=[],
|
||||
@@ -424,7 +424,8 @@ def set_provider(provider: str, model: str, validate: bool = True) -> None:
|
||||
_model = model
|
||||
elif provider == "minimax":
|
||||
result = _set_minimax_provider_result(model)
|
||||
valid_models = result.data if result.ok else _list_minimax_models("")
|
||||
fallback_result = _list_minimax_models_result("")
|
||||
valid_models = result.data if result.ok else fallback_result.data
|
||||
if model not in valid_models:
|
||||
_model = "MiniMax-M2.5"
|
||||
else:
|
||||
@@ -492,11 +493,17 @@ def reset_session() -> None:
|
||||
|
||||
def list_models(provider: str) -> list[str]:
|
||||
creds = _load_credentials()
|
||||
if provider == "gemini": return _list_gemini_models(creds["gemini"]["api_key"])
|
||||
elif provider == "anthropic": return _list_anthropic_models()
|
||||
if provider == "gemini":
|
||||
result = _list_gemini_models_result(creds["gemini"]["api_key"])
|
||||
return result.data if result.ok else []
|
||||
elif provider == "anthropic":
|
||||
result = _list_anthropic_models_result()
|
||||
return result.data if result.ok else []
|
||||
elif provider == "deepseek": return _list_deepseek_models(creds["deepseek"]["api_key"])
|
||||
elif provider == "gemini_cli": return _list_gemini_cli_models()
|
||||
elif provider == "minimax": return _list_minimax_models(creds["minimax"]["api_key"])
|
||||
elif provider == "minimax":
|
||||
result = _list_minimax_models_result(creds["minimax"]["api_key"])
|
||||
return result.data if result.ok else []
|
||||
elif provider == "qwen": return _list_qwen_models()
|
||||
elif provider == "grok": return _list_grok_models()
|
||||
elif provider == "llama": return _list_llama_models()
|
||||
@@ -1070,40 +1077,6 @@ def _reread_file_items_result(file_items: list[dict[str, Any]]) -> Result[tuple[
|
||||
return Result(data=(refreshed, changed), errors=errors)
|
||||
|
||||
|
||||
def _reread_file_items(file_items: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
||||
"""
|
||||
Re-reads file items from the filesystem if their modification times have changed.
|
||||
Functional Purpose:
|
||||
Iterates through context files, compares current filesystem mtime against cached mtime,
|
||||
and reads file contents if changes are detected, returning both the full refreshed set
|
||||
and the subset of changed items.
|
||||
|
||||
Parameters & Inputs: file_items (list[dict[str, Any]]): List of file dictionaries containing keys "path" and optionally "mtime", "content".
|
||||
|
||||
Returns: tuple[list[dict[str, Any]], list[dict[str, Any]]]: A tuple containing (refreshed_items, changed_items).
|
||||
|
||||
Immediate-Mode DAG / Thread Context:
|
||||
Called by: _send_gemini
|
||||
Calls: pathlib.Path.stat, pathlib.Path.read_text
|
||||
|
||||
SSDL: `o-> [I:get_mtime] -> [B:changed?] -> [I:read_file] -> [T:diff_text]`
|
||||
|
||||
Thread Boundaries: Runs synchronously in the caller thread. Does synchronous blocking file system I/O.
|
||||
|
||||
Thin wrapper over _reread_file_items_result; the legacy tuple shape is
|
||||
preserved for backward compatibility, but the try/except Exception lives
|
||||
in the Result variant (where it can capture structured ErrorInfo).
|
||||
Per-file read errors are logged to stderr as warnings (operator-visible
|
||||
drain) and included in err_item[\"error\"] = True for in-band flag checks.
|
||||
"""
|
||||
result = _reread_file_items_result(file_items)
|
||||
if result.errors:
|
||||
for err in result.errors:
|
||||
sys.stderr.write(f"[AI_CLIENT] {err.ui_message()}\n")
|
||||
sys.stderr.flush()
|
||||
refreshed, changed = result.data
|
||||
return refreshed, changed
|
||||
|
||||
def _build_file_context_text(file_items: list[dict[str, Any]]) -> str:
|
||||
if not file_items:
|
||||
return ""
|
||||
@@ -1355,9 +1328,6 @@ def _list_anthropic_models_result() -> Result[list[str]]:
|
||||
)
|
||||
|
||||
|
||||
def _list_anthropic_models() -> list[str]:
|
||||
return _list_anthropic_models_result().data
|
||||
|
||||
def _ensure_anthropic_client() -> None:
|
||||
global _anthropic_client
|
||||
anthropic = _require_warmed("anthropic")
|
||||
@@ -1581,7 +1551,8 @@ def _send_anthropic(
|
||||
})
|
||||
_append_comms("OUT", "request", {"message": f"[TOOL OUTPUT BUDGET EXCEEDED: {_cumulative_tool_bytes} bytes]"})
|
||||
if file_items:
|
||||
file_items, changed = _reread_file_items(file_items)
|
||||
_reread_result = _reread_file_items_result(file_items)
|
||||
file_items, changed = _reread_result.data
|
||||
refreshed_ctx = _build_file_diff_text(changed)
|
||||
if refreshed_ctx:
|
||||
tool_results.append({
|
||||
@@ -1665,9 +1636,6 @@ def _list_gemini_models_result(api_key: str) -> Result[list[str]]:
|
||||
)
|
||||
|
||||
|
||||
def _list_gemini_models(api_key: str) -> list[str]:
|
||||
return _list_gemini_models_result(api_key).data
|
||||
|
||||
def _ensure_gemini_client() -> None:
|
||||
global _gemini_client
|
||||
genai = _require_warmed("google.genai")
|
||||
@@ -1812,15 +1780,6 @@ def _extract_gemini_thoughts_result(resp: Any) -> Result[str]:
|
||||
)
|
||||
|
||||
|
||||
def _extract_gemini_thoughts(resp: Any) -> str:
|
||||
"""
|
||||
Extracts concatenated thinking text from a Gemini response object's parts.
|
||||
Parts with thought=True are thinking segments; parts with thought=False or unset are visible text.
|
||||
The google-genai SDK filters thoughts out of resp.text, so we must scan parts directly.
|
||||
Returns "" if no thoughts are present.
|
||||
"""
|
||||
return _extract_gemini_thoughts_result(resp).data
|
||||
|
||||
def _get_gemini_history_list(chat: Any | None) -> list[Any]:
|
||||
if not chat: return []
|
||||
if hasattr(chat, "_history"): return cast(list[Any], chat._history)
|
||||
@@ -2014,7 +1973,8 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str,
|
||||
# Check if this is the last tool to trigger file refresh
|
||||
if i == len(results) - 1:
|
||||
if file_items:
|
||||
file_items, changed = _reread_file_items(file_items)
|
||||
_reread_result = _reread_file_items_result(file_items)
|
||||
file_items, changed = _reread_result.data
|
||||
ctx = _build_file_diff_text(changed)
|
||||
if ctx:
|
||||
out += f"\n\n{_get_context_marker()}\n\n{ctx}"
|
||||
@@ -2034,7 +1994,8 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str,
|
||||
_append_comms("OUT", "tool_result_send", {"results": log})
|
||||
payload = f_resps
|
||||
res = "\n\n".join(all_text) if all_text else "(No text returned)"
|
||||
thought_text = _extract_gemini_thoughts(final_resp if stream_callback else resp)
|
||||
thought_text_result = _extract_gemini_thoughts_result(final_resp if stream_callback else resp)
|
||||
thought_text = thought_text_result.data if thought_text_result.ok else ""
|
||||
if thought_text:
|
||||
res = f"<thinking>\n{thought_text}\n</thinking>\n\n{res}"
|
||||
if monitor.enabled: monitor.end_component("ai_client._send_gemini")
|
||||
@@ -2126,7 +2087,8 @@ def _send_gemini_cli(md_content: str, user_message: str, base_dir: str,
|
||||
for i, (name, call_id, out, _) in enumerate(results_iter):
|
||||
if i == len(results_iter) - 1:
|
||||
if file_items:
|
||||
file_items, changed = _reread_file_items(file_items)
|
||||
_reread_result = _reread_file_items_result(file_items)
|
||||
file_items, changed = _reread_result.data
|
||||
ctx = _build_file_diff_text(changed)
|
||||
if ctx:
|
||||
out += f"\n\n{_get_context_marker()}\n\n{ctx}"
|
||||
@@ -2416,7 +2378,8 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
for i, (name, call_id, out, _) in enumerate(results):
|
||||
if i == len(results) - 1:
|
||||
if file_items:
|
||||
file_items, changed = _reread_file_items(file_items)
|
||||
_reread_result = _reread_file_items_result(file_items)
|
||||
file_items, changed = _reread_result.data
|
||||
ctx = _build_file_diff_text(changed)
|
||||
if ctx:
|
||||
out += f"\n\n{_get_context_marker()}\n\n{ctx}"
|
||||
@@ -2484,9 +2447,6 @@ def _list_minimax_models_result(api_key: str) -> Result[list[str]]:
|
||||
)
|
||||
|
||||
|
||||
def _list_minimax_models(api_key: str) -> list[str]:
|
||||
return _list_minimax_models_result(api_key).data
|
||||
|
||||
def _repair_minimax_history(history: list[dict[str, Any]]) -> None:
|
||||
if not history: return
|
||||
last = history[-1]
|
||||
|
||||
Reference in New Issue
Block a user