diff --git a/pyproject.toml b/pyproject.toml index d558927f..d6d4e336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,9 @@ dev = [ ] [tool.pytest.ini_options] +filterwarnings = [ + "ignore:Use ai_client.send_result.*:DeprecationWarning", +] markers = [ "integration: marks tests as integration tests (requires live GUI)", "clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)", diff --git a/src/ai_client.py b/src/ai_client.py index db978763..9684f243 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -35,6 +35,7 @@ from collections import deque from pathlib import Path as _P from pathlib import Path from typing import Optional, Callable, Any, List, Union, cast, Iterable +from typing_extensions import deprecated from src import project_manager from src import file_cache @@ -2705,6 +2706,7 @@ def get_token_stats(md_content: str) -> dict[str, Any]: } return _add_bleed_derived(stats, sys_tok=total_tokens) +@deprecated("Use ai_client.send_result() instead. The deprecated send() will be removed in the public_api_migration_20260606 track.") def send( md_content: str, user_message: str, @@ -2720,53 +2722,18 @@ def send( rag_engine: Optional[Any] = None, ) -> str: """ + [DEPRECATED] Use send_result() instead. Returns str (the response text). Errors are logged to the comms log but not returned. + The full Result[str, ErrorInfo] is available via send_result() (no deprecation). [C: simulation/user_agent.py:UserSimAgent.generate_response, src/api_hooks.py:WebSocketServer._handler, src/api_hooks.py:WebSocketServer.broadcast, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_completion_logic, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoning_logic, tests/test_deepseek_provider.py:test_deepseek_streaming, tests/test_deepseek_provider.py:test_deepseek_tool_calling, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_full_flow_integration, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_captures_usage_metadata, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_handles_tool_use_events, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_parses_jsonl_output, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_starts_subprocess_with_correct_args, tests/test_gemini_cli_adapter_parity.py:TestGeminiCliAdapterParity.test_send_parses_tool_calls_from_streaming_json, tests/test_gemini_cli_adapter_parity.py:TestGeminiCliAdapterParity.test_send_starts_subprocess_with_model, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_context_bleed_prevention, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_loop_termination, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_cli_parity_regression.py:test_send_invokes_adapter_send, tests/test_gui2_mcp.py:test_mcp_tool_call_is_dispatched, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_token_usage.py:test_token_usage_tracking, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast] """ - monitor = performance_monitor.get_monitor() - if monitor.enabled: monitor.start_component("ai_client.send") - - if rag_engine and getattr(rag_engine.config, "enabled", False) and "## Retrieved Context" not in user_message: - chunks = rag_engine.search(user_message) - if chunks: - context_block = "## Retrieved Context\n\n" - for i, chunk in enumerate(chunks): - path = chunk.get("metadata", {}).get("path", "unknown") - context_block += f"### Chunk {i+1} (Source: {path})\n{chunk.get('document', '')}\n\n" - user_message = context_block + user_message - - _append_comms("OUT", "request", {"message": user_message, "system": _get_combined_system_prompt(_active_tool_preset, _active_bias_profile)}) - with _send_lock: - p = str(_provider).lower().strip() - if p == "gemini": - res = _send_gemini( - md_content, user_message, base_dir, file_items, discussion_history, - pre_tool_callback, qa_callback, enable_tools, stream_callback, patch_callback - ) - elif p == "gemini_cli": - res = _send_gemini_cli( - md_content, user_message, base_dir, file_items, discussion_history, - pre_tool_callback, qa_callback, stream_callback, patch_callback - ) - elif p == "anthropic": - res = _send_anthropic( - md_content, user_message, base_dir, file_items, discussion_history, - pre_tool_callback, qa_callback, stream_callback=stream_callback, patch_callback=patch_callback - ) - elif p == "deepseek": - res = _send_deepseek( - md_content, user_message, base_dir, file_items, discussion_history, - stream, pre_tool_callback, qa_callback, stream_callback, patch_callback - ) - elif p == "minimax": - res = _send_minimax( - md_content, user_message, base_dir, file_items, discussion_history, - stream, pre_tool_callback, qa_callback, stream_callback, patch_callback - ) - else: - if monitor.enabled: monitor.end_component("ai_client.send") - raise ValueError(f"Unknown provider: {_provider}") - if monitor.enabled: monitor.end_component("ai_client.send") - return res + result = send_result( + md_content, user_message, base_dir, file_items, discussion_history, + stream, pre_tool_callback, qa_callback, enable_tools, stream_callback, patch_callback, rag_engine, + ) + if not result.ok: + for err in result.errors: + _append_comms("WARN", "deprecated_send_with_errors", {"error": err.ui_message()}) + return result.data def send_result( md_content: str, diff --git a/tests/test_ai_client_result.py b/tests/test_ai_client_result.py index 525e5d86..4153fd2a 100644 --- a/tests/test_ai_client_result.py +++ b/tests/test_ai_client_result.py @@ -18,7 +18,7 @@ def test_send_deprecated_emits_warning() -> None: with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") with patch.object(ai_client, "set_provider"): - with patch.object(ai_client, "_send_anthropic_result", return_value=Result(data="hi")): + with patch.object(ai_client, "_send_gemini_result", return_value=Result(data="hi")): result = ai_client.send("system", "user") assert result == "hi" assert any(issubclass(x.category, DeprecationWarning) for x in w) @@ -35,7 +35,7 @@ def test_send_result_preserves_errors() -> None: def test_send_extracts_data_from_result() -> None: with patch.object(ai_client, "set_provider"): - with patch.object(ai_client, "_send_anthropic_result", return_value=Result(data="result text")): + with patch.object(ai_client, "_send_gemini_result", return_value=Result(data="result text")): result = ai_client.send("system", "user") assert result == "result text" @@ -43,7 +43,7 @@ def test_send_extracts_data_from_result() -> None: def test_send_returns_empty_string_on_error_result() -> None: err = ErrorInfo(kind=ErrorKind.AUTH, message="bad key", source="test") with patch.object(ai_client, "set_provider"): - with patch.object(ai_client, "_send_anthropic_result", return_value=Result(data="", errors=[err])): + with patch.object(ai_client, "_send_gemini_result", return_value=Result(data="", errors=[err])): result = ai_client.send("system", "user") assert result == "" diff --git a/tests/test_deprecation_warnings.py b/tests/test_deprecation_warnings.py index c274633f..e306af6e 100644 --- a/tests/test_deprecation_warnings.py +++ b/tests/test_deprecation_warnings.py @@ -6,7 +6,7 @@ from src.result_types import Result def test_send_deprecated_warning_emitted_once_per_site() -> None: with patch.object(ai_client, "set_provider"): - with patch.object(ai_client, "_send_anthropic_result", return_value=Result(data="x")): + with patch.object(ai_client, "_send_gemini_result", return_value=Result(data="x")): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") ai_client.send("s", "u")