Private
Public Access
0
0
Files
manual_slop/src/provider_state.py
T
ed 5bd416c3ca feat(provider): add src/provider_state.py + tests (t3_2, t3_3)
Phase 3 of any_type_componentization_20260621 (PARTIAL). Adds the
ProviderHistory abstraction and 6-provider registry.

NEW src/provider_state.py (60 lines):
- ProviderHistory dataclass (messages: list[HistoryMessage], lock: Lock,
  append / get_all / replace_all / clear methods)
- _PROVIDER_HISTORIES: dict[str, ProviderHistory] for anthropic / deepseek /
  minimax / qwen / grok / llama
- get_history(provider) factory + clear_all() + providers()
- SDK client holders (_gemini_chat, _anthropic_client, etc.) NOT touched
  per Pattern 3 (heterogeneous SDK types)

NEW tests/test_provider_state.py (12 tests, all pass):
- test_six_providers_registered
- test_get_history_returns_singleton_per_provider
- test_get_history_raises_for_unknown
- test_provider_history_starts_empty
- test_provider_history_append / get_all_returns_copy / replace_all /
  replace_all_takes_copy / clear
- test_clear_all_resets_every_provider
- test_provider_history_thread_safety (10 threads x 100 messages)
- test_independent_locks_per_provider (lock on one doesn't block another)

DEFERRED:
- t3_4 (Remove 14 globals from ai_client.py:111-133)
- t3_5 through t3_13 (Update call sites in _send_<provider> functions)
- t3_14 (Run full regression suite on test_ai_client*.py)

These call-site updates require careful per-function refactoring of the
~27 sites in _send_anthropic, _send_deepseek, _send_minimax, _send_qwen,
_send_grok, _send_llama. The ai_client.py file is 3432 lines; a single
regex pass risks subtle indentation regressions in nested constructs
(see the 7
ot : orphan lines from a previous attempt).

The provider_state module is independently usable and tested. Future
track: provider_state_migration_2026MMDD to wire up the call sites
mechanically, OR integrate into a Phase 3 retry pass.

Verified:
  uv run pytest tests/test_provider_state.py --timeout=30
    12 passed in 2.99s
2026-06-22 00:59:50 -04:00

69 lines
1.9 KiB
Python

"""Per-provider history state for the AI client layer.
Promotes 14 module globals in src/ai_client.py:
- 7x `_<provider>_history: list[Metadata]` (anthropic/deepseek/minimax/qwen/grok/llama)
- 7x `_<provider>_history_lock: threading.Lock`
To a single `_PROVIDER_HISTORIES: dict[str, ProviderHistory]` keyed by
provider name. Each `ProviderHistory` owns its own lock and message list;
the cross-provider pattern is encapsulated behind a 4-method interface.
SDK client holders (`_gemini_chat`, `_deepseek_client`, etc.) stay as
module-level `Any` variables per Pattern 3 (heterogeneous SDK types,
lazy-initialized). Only the homogeneous history aspect is unified.
CONVENTION: 1-space indentation. NO COMMENTS.
"""
from __future__ import annotations
import threading
from dataclasses import dataclass, field
from src.type_aliases import HistoryMessage, Metadata
@dataclass
class ProviderHistory:
messages: list[HistoryMessage] = field(default_factory=list)
lock: threading.Lock = field(default_factory=threading.Lock)
def append(self, message: HistoryMessage) -> None:
with self.lock:
self.messages.append(message)
def get_all(self) -> list[HistoryMessage]:
with self.lock:
return list(self.messages)
def replace_all(self, messages: list[HistoryMessage]) -> None:
with self.lock:
self.messages = list(messages)
def clear(self) -> None:
with self.lock:
self.messages = []
_PROVIDER_HISTORIES: dict[str, ProviderHistory] = {
"anthropic": ProviderHistory(),
"deepseek": ProviderHistory(),
"minimax": ProviderHistory(),
"qwen": ProviderHistory(),
"grok": ProviderHistory(),
"llama": ProviderHistory(),
}
def get_history(provider: str) -> ProviderHistory:
if provider not in _PROVIDER_HISTORIES:
raise KeyError(f"Unknown provider: {provider!r}")
return _PROVIDER_HISTORIES[provider]
def clear_all() -> None:
for h in _PROVIDER_HISTORIES.values():
h.clear()
def providers() -> tuple[str, ...]:
return tuple(_PROVIDER_HISTORIES.keys())