From 2323b529ee002c724ebb62f1bbdee01b2a5fbff5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 25 Jun 2026 12:07:36 -0400 Subject: [PATCH] refactor(ai_client): migrate _anthropic_history call sites to provider_state.get_history('anthropic') TIER-2 READ conductor/code_styleguides/error_handling.md before Phase 1 (anthropic migration). Phase 1 of code_path_audit_phase_3_provider_state_20260624. 13 call sites in _send_anthropic (lines 1430-1575) migrated from the module-level _anthropic_history alias to a local capture history = provider_state.get_history('anthropic'). The local capture pattern is used (instead of repeated provider_state.get_history() calls) to minimize lock acquisitions and improve readability. The migration preserves behavior: ProviderHistory is the same singleton that _anthropic_history aliased to, so the migration is a pure refactor. The lock acquisition pattern is unchanged (this function does not acquire _anthropic_history_lock; thread-safety comes from _send_anthropic being called per-thread). Verified: 37 tests pass across test_provider_state_migration.py + 6 ai_client test files. Conventions: 1-space indentation, CRLF preserved, no comments added. --- src/ai_client.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/ai_client.py b/src/ai_client.py index a730a4c2..21ccd42d 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -1427,16 +1427,17 @@ def _send_anthropic( try: _ensure_anthropic_client() mcp_client.configure(file_items or [], [base_dir]) + history = provider_state.get_history("anthropic") stable_prompt = _get_combined_system_prompt() stable_blocks: list[Metadata] = [{"type": "text", "text": stable_prompt, "cache_control": {"type": "ephemeral"}}] context_text = f"\n\n\n{md_content}\n" context_blocks = _build_chunked_context_blocks(context_text) system_blocks = stable_blocks + context_blocks - if discussion_history and not _anthropic_history: + if discussion_history and not history: user_content: list[Metadata] = [{"type": "text", "text": f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"}] else: user_content = [{"type": "text", "text": user_message}] - for msg in _anthropic_history: + for msg in history: if msg.get("role") == "user" and isinstance(msg.get("content"), list): modified = False for block in cast(List[dict[str, Any]], msg["content"]): @@ -1446,10 +1447,10 @@ def _send_anthropic( block["content"] = t_content[:_history_trunc_limit] + "\n\n... [TRUNCATED BY SYSTEM TO SAVE TOKENS. Original output was too large.]" modified = True if modified: _invalidate_token_estimate(msg) - _strip_cache_controls(_anthropic_history) - _repair_anthropic_history(_anthropic_history) - _anthropic_history.append({"role": "user", "content": user_content}) - _add_history_cache_breakpoint(_anthropic_history) + _strip_cache_controls(history) + _repair_anthropic_history(history) + history.append({"role": "user", "content": user_content}) + _add_history_cache_breakpoint(history) all_text_parts: list[str] = [] _cumulative_tool_bytes = 0 @@ -1458,13 +1459,13 @@ def _send_anthropic( for round_idx in range(MAX_TOOL_ROUNDS + 2): response: Any = None - dropped = _trim_anthropic_history(system_blocks, _anthropic_history) + dropped = _trim_anthropic_history(system_blocks, history) if dropped > 0: - est_tokens = _estimate_prompt_tokens(system_blocks, _anthropic_history) + est_tokens = _estimate_prompt_tokens(system_blocks, history) _append_comms("OUT", "request", { "message": ( f"[HISTORY TRIMMED: dropped {dropped} old messages to fit token budget. " - f"Estimated {est_tokens} tokens remaining. {len(_anthropic_history)} messages in history.]" + f"Estimated {est_tokens} tokens remaining. {len(history)} messages in history.]" ), }) @@ -1478,7 +1479,7 @@ def _send_anthropic( top_p = _top_p, system = cast(Iterable[anthropic.types.TextBlockParam], system_blocks), tools = cast(Iterable[anthropic.types.ToolParam], _get_anthropic_tools()), - messages = cast(Iterable[anthropic.types.MessageParam], _strip_private_keys(_anthropic_history)), + messages = cast(Iterable[anthropic.types.MessageParam], _strip_private_keys(history)), ) as stream: for event in stream: if isinstance(event, anthropic.types.ContentBlockDeltaEvent) and event.delta.type == "text_delta": @@ -1492,10 +1493,10 @@ def _send_anthropic( top_p = _top_p, system = cast(Iterable[anthropic.types.TextBlockParam], system_blocks), tools = cast(Iterable[anthropic.types.ToolParam], _get_anthropic_tools()), - messages = cast(Iterable[anthropic.types.MessageParam], _strip_private_keys(_anthropic_history)), + messages = cast(Iterable[anthropic.types.MessageParam], _strip_private_keys(history)), ) serialised_content = [_content_block_to_dict(b) for b in response.content] - _anthropic_history.append({ + history.append({ "role": "assistant", "content": serialised_content, }) @@ -1571,7 +1572,7 @@ def _send_anthropic( "type": "text", "text": "SYSTEM WARNING: MAX TOOL ROUNDS REACHED. YOU MUST PROVIDE YOUR FINAL ANSWER NOW WITHOUT CALLING ANY MORE TOOLS." }) - _anthropic_history.append({ + history.append({ "role": "user", "content": tool_results, })