Private
Public Access
0
0

feat(ai_client): mark send() @deprecated; rewire to call send_result()

This commit is contained in:
2026-06-12 19:22:27 -04:00
parent 9f86b2bee3
commit 73cf321cdf
4 changed files with 19 additions and 49 deletions
+3
View File
@@ -43,6 +43,9 @@ dev = [
] ]
[tool.pytest.ini_options] [tool.pytest.ini_options]
filterwarnings = [
"ignore:Use ai_client.send_result.*:DeprecationWarning",
]
markers = [ markers = [
"integration: marks tests as integration tests (requires live GUI)", "integration: marks tests as integration tests (requires live GUI)",
"clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)", "clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)",
+10 -43
View File
@@ -35,6 +35,7 @@ from collections import deque
from pathlib import Path as _P from pathlib import Path as _P
from pathlib import Path from pathlib import Path
from typing import Optional, Callable, Any, List, Union, cast, Iterable from typing import Optional, Callable, Any, List, Union, cast, Iterable
from typing_extensions import deprecated
from src import project_manager from src import project_manager
from src import file_cache 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) 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( def send(
md_content: str, md_content: str,
user_message: str, user_message: str,
@@ -2720,53 +2722,18 @@ def send(
rag_engine: Optional[Any] = None, rag_engine: Optional[Any] = None,
) -> str: ) -> 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] [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() result = send_result(
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, md_content, user_message, base_dir, file_items, discussion_history,
pre_tool_callback, qa_callback, enable_tools, stream_callback, patch_callback stream, pre_tool_callback, qa_callback, enable_tools, stream_callback, patch_callback, rag_engine,
) )
elif p == "gemini_cli": if not result.ok:
res = _send_gemini_cli( for err in result.errors:
md_content, user_message, base_dir, file_items, discussion_history, _append_comms("WARN", "deprecated_send_with_errors", {"error": err.ui_message()})
pre_tool_callback, qa_callback, stream_callback, patch_callback return result.data
)
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
def send_result( def send_result(
md_content: str, md_content: str,
+3 -3
View File
@@ -18,7 +18,7 @@ def test_send_deprecated_emits_warning() -> None:
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always") warnings.simplefilter("always")
with patch.object(ai_client, "set_provider"): 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") result = ai_client.send("system", "user")
assert result == "hi" assert result == "hi"
assert any(issubclass(x.category, DeprecationWarning) for x in w) 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: def test_send_extracts_data_from_result() -> None:
with patch.object(ai_client, "set_provider"): 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") result = ai_client.send("system", "user")
assert result == "result text" 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: def test_send_returns_empty_string_on_error_result() -> None:
err = ErrorInfo(kind=ErrorKind.AUTH, message="bad key", source="test") err = ErrorInfo(kind=ErrorKind.AUTH, message="bad key", source="test")
with patch.object(ai_client, "set_provider"): 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") result = ai_client.send("system", "user")
assert result == "" assert result == ""
+1 -1
View File
@@ -6,7 +6,7 @@ from src.result_types import Result
def test_send_deprecated_warning_emitted_once_per_site() -> None: def test_send_deprecated_warning_emitted_once_per_site() -> None:
with patch.object(ai_client, "set_provider"): 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: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always") warnings.simplefilter("always")
ai_client.send("s", "u") ai_client.send("s", "u")