From 79d0a563205f2d2a2e8376833ae26d8b75a75bb9 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 25 Jun 2026 12:14:04 -0400 Subject: [PATCH] refactor(ai_client): migrate _deepseek_history call sites to provider_state.get_history('deepseek') MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TIER-2 READ conductor/code_styleguides/error_handling.md before Phase 2 (deepseek migration; RLock re-entrance critical). Phase 2 of code_path_audit_phase_3_provider_state_20260624. 11 sites in _send_deepseek (lines 2186-2414) migrated from _deepseek_history/_deepseek_history_lock to local capture history = provider_state.get_history('deepseek'). The RLock re-entrance is critical here — this was the deadlock-prone site that prompted cc7993e5. The local capture pattern uses one acquisition per function instead of one per call site, minimizing lock acquisitions while preserving the same RLock instance that _deepseek_history_lock aliased to. 4 with-blocks migrated (lines 2195, 2215, 2347, 2412). 6 _deepseek_history alias references migrated to history (lines 2196, 2197, 2201, 2216, 2354, 2414). Verified: 30 tests pass across test_provider_state_migration (14) + test_deepseek_provider (7) + 5 ai_client test files. The test_lock_acquisition_no_deadlock regression test verifies RLock re-entrance works correctly inside the with history.lock: blocks. Conventions: 1-space indentation, CRLF preserved, no comments added. --- src/ai_client.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/ai_client.py b/src/ai_client.py index 21ccd42d..3b20a94f 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -2183,6 +2183,7 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str, if not api_key: if monitor.enabled: monitor.end_component("ai_client._send_deepseek") raise ValueError("DeepSeek API key not found in credentials.toml") + history = provider_state.get_history("deepseek") api_url = "https://api.deepseek.com/chat/completions" headers = { "Authorization": f"Bearer {api_key}", @@ -2192,13 +2193,13 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str, is_reasoner = _model in ("deepseek-reasoner", "deepseek-r1") # Update history following Anthropic pattern - with _deepseek_history_lock: - _repair_deepseek_history(_deepseek_history) - if discussion_history and not _deepseek_history: + with history.lock: + _repair_deepseek_history(history) + if discussion_history and not history: user_content = f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}" else: user_content = user_message - _deepseek_history.append({"role": "user", "content": user_content}) + history.append({"role": "user", "content": user_content}) all_text_parts: list[str] = [] _cumulative_tool_bytes = 0 @@ -2212,8 +2213,8 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str, sys_msg = {"role": "system", "content": f"{_get_combined_system_prompt()}\n\n\n{md_content}\n"} current_api_messages.append(sys_msg) - with _deepseek_history_lock: - for i, msg in enumerate(_deepseek_history): + with history.lock: + for i, msg in enumerate(history): # Create a clean copy of the message for the API role = msg.get("role") api_msg = {"role": role} @@ -2344,14 +2345,14 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str, thinking_tags = f"\n{reasoning_content}\n\n" full_assistant_text = thinking_tags + assistant_text - with _deepseek_history_lock: + with history.lock: # DeepSeek/OpenAI: If tool_calls are present, content can be null but should usually be present msg_to_store: Metadata = {"role": "assistant", "content": assistant_text or None} if reasoning_content: msg_to_store["reasoning_content"] = reasoning_content if tool_calls_raw: msg_to_store["tool_calls"] = tool_calls_raw - _deepseek_history.append(msg_to_store) + history.append(msg_to_store) if full_assistant_text: all_text_parts.append(full_assistant_text) @@ -2409,9 +2410,9 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str, }) _append_comms("OUT", "request", {"message": f"[TOOL OUTPUT BUDGET EXCEEDED: {_cumulative_tool_bytes} bytes]"}) - with _deepseek_history_lock: + with history.lock: for tr in tool_results_for_history: - _deepseek_history.append(tr) + history.append(tr) res = "\n\n".join(all_text_parts) if all_text_parts else "(No text returned)" if monitor.enabled: monitor.end_component("ai_client._send_deepseek")