diff --git a/src/ai_client.py b/src/ai_client.py index 3de555cd..8b737193 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -1704,6 +1704,28 @@ def _create_gemini_cache_result(sys_instr: str, tools_decl: Any, file_items: lis errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=f"failed to create gemini cache: {type(e).__name__}: {e}", source="ai_client._create_gemini_cache_result", original=e)], ) +def _send_cli_round_result(r_idx: int, adapter: Any, payload: Any, safety_settings: list[Any], sys_instr: str, stream_callback: Optional[Callable[[str], None]]) -> Result[dict[str, Any]]: + """Call the Gemini CLI adapter for one round. Returns Result[resp_data]. + + On SDK failure, emits a response_received event with the error info + (preserving the original side-effect semantics) and returns + Result(errors=[ErrorInfo]). The caller (_send in _send_gemini_cli) + re-raises the original exception to preserve the outer catch flow. + """ + events.emit("request_start", payload={"provider": "gemini_cli", "model": _model, "round": r_idx}) + if r_idx > 0: + _append_comms("OUT", "request", {"message": f"[CLI] [round {r_idx}] [msg {len(payload)}]"}) + send_payload: Any = json.dumps(payload) if isinstance(payload, list) else payload + try: + resp_data = adapter.send(cast(str, send_payload), safety_settings=safety_settings, system_instruction=sys_instr, model=_model, stream_callback=stream_callback) + return Result(data=resp_data) + except Exception as e: + events.emit("response_received", payload={"provider": "gemini_cli", "model": _model, "usage": {}, "latency": 0, "round": r_idx, "error": str(e)}) + return Result( + data=None, + errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source="ai_client._send_cli_round_result", original=e)], + ) + def _extract_gemini_thoughts(resp: Any) -> str: """ Extracts concatenated thinking text from a Gemini response object's parts. @@ -1981,15 +2003,10 @@ def _send_gemini_cli(md_content: str, user_message: str, base_dir: str, def _send(r_idx: int) -> NormalizedResponse: if adapter is None: return NormalizedResponse(text="(adapter unavailable)", tool_calls=[], usage_input_tokens=0, usage_output_tokens=0, usage_cache_read_tokens=0, usage_cache_creation_tokens=0, raw_response=None) - events.emit("request_start", payload={"provider": "gemini_cli", "model": _model, "round": r_idx}) - if r_idx > 0: - _append_comms("OUT", "request", {"message": f"[CLI] [round {r_idx}] [msg {len(payload)}]"}) - send_payload: Any = json.dumps(payload) if isinstance(payload, list) else payload - try: - resp_data = adapter.send(cast(str, send_payload), safety_settings=safety_settings, system_instruction=sys_instr, model=_model, stream_callback=stream_callback) - except Exception as e: - events.emit("response_received", payload={"provider": "gemini_cli", "model": _model, "usage": {}, "latency": 0, "round": r_idx, "error": str(e)}) - raise + send_result = _send_cli_round_result(r_idx, adapter, payload, safety_settings, sys_instr, stream_callback) + if not send_result.ok: + raise cast(Exception, send_result.errors[0].original) + resp_data = send_result.data cli_stderr = resp_data.get("stderr", "") if cli_stderr: sys.stderr.write(f"\n--- Gemini CLI stderr ---\n{cli_stderr}\n-------------------------\n") diff --git a/tests/tier2/phase10_site6_test.py b/tests/tier2/phase10_site6_test.py new file mode 100644 index 00000000..ba8c991a --- /dev/null +++ b/tests/tier2/phase10_site6_test.py @@ -0,0 +1,27 @@ +"""Phase 10 site 6: _send_cli_round_result helper. + +Site L1990 (in _send_gemini_cli): + try: resp_data = adapter.send(...) + except Exception as e: events.emit('response_received', {'error': str(e)}); raise + +Re-Raise Pattern 2 (catch + emit + raise). Migration: extract Result helper. +The inner _send calls the helper; on error, re-raise original exception +(preserving outer _send_gemini_cli catch behavior). +""" +import sys +sys.path.insert(0, ".") + + +def test_phase10_site6_send_cli_round_result_exists(): + import src.ai_client + assert hasattr(src.ai_client, "_send_cli_round_result"), \ + "_send_cli_round_result helper missing" + + +def test_phase10_site6_send_cli_round_result_returns_result(): + import src.ai_client + import inspect + fn = src.ai_client._send_cli_round_result + sig = inspect.signature(fn) + assert "Result" in str(sig.return_annotation), \ + f"_send_cli_round_result return must be Result, got {sig.return_annotation}" \ No newline at end of file