diff --git a/src/ai_client.py b/src/ai_client.py index b3baac2c..5b29d64f 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -14,7 +14,8 @@ during chat creation to avoid massive history bloat. # ai_client.py import anthropic from google import genai -from google.genai import types +from google.api_core import exceptions as gac +from google.genai import types from openai import OpenAI import asyncio @@ -91,9 +92,8 @@ class ProviderError(Exception): def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000, top_p: float = 1.0) -> None: """ - - Sets global generation parameters like temperature and max tokens. - [C: src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate] + Sets global generation parameters like temperature and max tokens. + [C: src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate] """ global _temperature, _max_tokens, _history_trunc_limit, _top_p _temperature = temp @@ -101,33 +101,33 @@ def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000, top_p: _history_trunc_limit = trunc_limit _top_p = top_p -_gemini_client: Optional[genai.Client] = None -_gemini_chat: Any = None -_gemini_cache: Any = None -_gemini_cache_md_hash: Optional[str] = None -_gemini_cache_created_at: Optional[float] = None +_gemini_client: Optional[genai.Client] = None +_gemini_chat: Any = None +_gemini_cache: Any = None +_gemini_cache_md_hash: Optional[str] = None +_gemini_cache_created_at: Optional[float] = None _gemini_cached_file_paths: list[str] = [] # Gemini cache TTL in seconds. Caches are created with this TTL and # proactively rebuilt at 90% of this value to avoid stale-reference errors. _GEMINI_CACHE_TTL: int = 3600 -_anthropic_client: Optional[anthropic.Anthropic] = None +_anthropic_client: Optional[anthropic.Anthropic] = None _anthropic_history: list[dict[str, Any]] = [] _anthropic_history_lock: threading.Lock = threading.Lock() -_deepseek_client: Any = None +_deepseek_client: Any = None _deepseek_history: list[dict[str, Any]] = [] _deepseek_history_lock: threading.Lock = threading.Lock() -_minimax_client: Any = None +_minimax_client: Any = None _minimax_history: list[dict[str, Any]] = [] _minimax_history_lock: threading.Lock = threading.Lock() _send_lock: threading.Lock = threading.Lock() _BIAS_ENGINE = ToolBiasEngine() -_active_tool_preset: Optional[ToolPreset] = None +_active_tool_preset: Optional[ToolPreset] = None _active_bias_profile: Optional[BiasProfile] = None _gemini_cli_adapter: Optional[GeminiCliAdapter] = None @@ -148,17 +148,15 @@ _tool_approval_modes: dict[str, str] = {} def get_current_tier() -> Optional[str]: """ - - Returns the current tier from thread-local storage. - [C: src/app_controller.py:AppController._on_tool_log, tests/test_ai_client_concurrency.py:intercepted_append] + Returns the current tier from thread-local storage. + [C: src/app_controller.py:AppController._on_tool_log, tests/test_ai_client_concurrency.py:intercepted_append] """ return getattr(_local_storage, "current_tier", None) def set_current_tier(tier: Optional[str]) -> None: """ - - Sets the current tier in thread-local storage. - [C: src/app_controller.py:AppController._handle_request_event, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, tests/test_mma_agent_focus_phase1.py:reset_tier, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2] + Sets the current tier in thread-local storage. + [C: src/app_controller.py:AppController._handle_request_event, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, tests/test_mma_agent_focus_phase1.py:reset_tier, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2] """ _local_storage.current_tier = tier @@ -187,10 +185,10 @@ _SYSTEM_PROMPT: str = ( "need to re-read files that are already provided in the block." ) -_custom_system_prompt: str = "" -_base_system_prompt_override: str = "" +_custom_system_prompt: str = "" +_base_system_prompt_override: str = "" _use_default_base_system_prompt: bool = True -_project_context_marker: str = "" +_project_context_marker: str = "" #endregion: Provider Configuration @@ -198,30 +196,29 @@ _project_context_marker: str = "" def set_custom_system_prompt(prompt: str) -> None: """ - - Sets a custom system prompt to be combined with the default instructions. - [C: simulation/user_agent.py:UserSimAgent.generate_response, src/app_controller.py:AppController._do_generate, 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_system_prompt_exposure.py:TestSystemPromptExposure.setUp] + Sets a custom system prompt to be combined with the default instructions. + [C: simulation/user_agent.py:UserSimAgent.generate_response, src/app_controller.py:AppController._do_generate, 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_system_prompt_exposure.py:TestSystemPromptExposure.setUp] """ global _custom_system_prompt _custom_system_prompt = prompt def set_base_system_prompt(prompt: str) -> None: """ - [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false] + [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false] """ global _base_system_prompt_override _base_system_prompt_override = prompt def set_use_default_base_prompt(use_default: bool) -> None: """ - [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false] + [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false] """ global _use_default_base_system_prompt _use_default_base_system_prompt = use_default def set_project_context_marker(marker: str) -> None: """ - [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate] + [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate] """ global _project_context_marker _project_context_marker = marker @@ -231,10 +228,10 @@ def _get_context_marker() -> str: def _get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optional[BiasProfile] = None) -> str: """ - [C: tests/test_bias_efficacy.py:test_bias_efficacy_prompt_generation, tests/test_bias_integration.py:test_system_prompt_biasing, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false] + [C: tests/test_bias_efficacy.py:test_bias_efficacy_prompt_generation, tests/test_bias_integration.py:test_system_prompt_biasing, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_get_combined_respects_use_default, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.test_ai_client_set_base_overrides_when_default_false] """ if preset is None: preset = _active_tool_preset - if bias is None: bias = _active_bias_profile + if bias is None: bias = _active_bias_profile if _use_default_base_system_prompt: base = _SYSTEM_PROMPT else: @@ -249,7 +246,7 @@ def _get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optio def get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optional[BiasProfile] = None) -> str: """ - [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event] + [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event] """ return _get_combined_system_prompt(preset, bias) @@ -263,9 +260,8 @@ COMMS_CLAMP_CHARS: int = 300 def get_comms_log_callback() -> Optional[Callable[[dict[str, Any]], None]]: """ - - Returns the comms log callback (thread-local with global fallback). - [C: src/multi_agent_conductor.py:run_worker_lifecycle] + Returns the comms log callback (thread-local with global fallback). + [C: src/multi_agent_conductor.py:run_worker_lifecycle] """ tl_cb = getattr(_local_storage, "comms_log_callback", None) if tl_cb: return tl_cb @@ -273,9 +269,8 @@ def get_comms_log_callback() -> Optional[Callable[[dict[str, Any]], None]]: def set_comms_log_callback(cb: Optional[Callable[[dict[str, Any]], None]]) -> None: """ - - Sets the comms log callback (both global and thread-local). - [C: src/app_controller.py:AppController._init_ai_and_hooks, src/multi_agent_conductor.py:run_worker_lifecycle] + Sets the comms log callback (both global and thread-local). + [C: src/app_controller.py:AppController._init_ai_and_hooks, src/multi_agent_conductor.py:run_worker_lifecycle] """ global comms_log_callback comms_log_callback = cb @@ -283,7 +278,7 @@ def set_comms_log_callback(cb: Optional[Callable[[dict[str, Any]], None]]) -> No def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None: """ - [C: tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2] + [C: tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2] """ entry: dict[str, Any] = { "ts": datetime.datetime.now().strftime("%H:%M:%S"), @@ -302,13 +297,13 @@ def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None: def get_comms_log() -> list[dict[str, Any]]: """ - [C: src/app_controller.py:AppController._bg_task, src/app_controller.py:AppController._recalculate_session_usage, src/app_controller.py:AppController._start_track_logic, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_token_usage.py:test_token_usage_tracking] + [C: src/app_controller.py:AppController._bg_task, src/app_controller.py:AppController._recalculate_session_usage, src/app_controller.py:AppController._start_track_logic, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_token_usage.py:test_token_usage_tracking] """ return list(_comms_log) def clear_comms_log() -> None: """ - [C: src/app_controller.py:AppController._handle_reset_session, src/gui_2.py:App._render_comms_history_panel, src/gui_2.py:App._show_menus, tests/test_ai_client_concurrency.py:test_ai_client_tier_isolation, tests/test_token_usage.py:test_token_usage_tracking] + [C: src/app_controller.py:AppController._handle_reset_session, src/gui_2.py:App._render_comms_history_panel, src/gui_2.py:App._show_menus, tests/test_ai_client_concurrency.py:test_ai_client_tier_isolation, tests/test_token_usage.py:test_token_usage_tracking] """ _comms_log.clear() @@ -339,27 +334,18 @@ def _load_credentials() -> dict[str, Any]: def _classify_anthropic_error(exc: Exception) -> ProviderError: try: - if isinstance(exc, anthropic.RateLimitError): - return ProviderError("rate_limit", "anthropic", exc) - if isinstance(exc, anthropic.AuthenticationError): - return ProviderError("auth", "anthropic", exc) - if isinstance(exc, anthropic.PermissionDeniedError): - return ProviderError("auth", "anthropic", exc) - if isinstance(exc, anthropic.APIConnectionError): - return ProviderError("network", "anthropic", exc) - if isinstance(exc, anthropic.APIStatusError): + if isinstance(exc, anthropic.RateLimitError): return ProviderError("rate_limit", "anthropic", exc) + if isinstance(exc, anthropic.AuthenticationError): return ProviderError("auth", "anthropic", exc) + if isinstance(exc, anthropic.PermissionDeniedError): return ProviderError("auth", "anthropic", exc) + if isinstance(exc, anthropic.APIConnectionError): return ProviderError("network", "anthropic", exc) + if isinstance(exc, anthropic.APIStatusError): status = getattr(exc, "status_code", 0) body = str(exc).lower() - if status == 429: - return ProviderError("rate_limit", "anthropic", exc) - if status in (401, 403): - return ProviderError("auth", "anthropic", exc) - if status == 402: - return ProviderError("balance", "anthropic", exc) - if "credit" in body or "balance" in body or "billing" in body: - return ProviderError("balance", "anthropic", exc) - if "quota" in body or "limit" in body or "exceeded" in body: - return ProviderError("quota", "anthropic", exc) + if status == 429: return ProviderError("rate_limit", "anthropic", exc) + if status in (401, 403): return ProviderError("auth", "anthropic", exc) + if status == 402: return ProviderError("balance", "anthropic", exc) + if "credit" in body or "balance" in body or "billing" in body: return ProviderError("balance", "anthropic", exc) + if "quota" in body or "limit" in body or "exceeded" in body: return ProviderError("quota", "anthropic", exc) except ImportError: pass return ProviderError("unknown", "anthropic", exc) @@ -367,27 +353,17 @@ def _classify_anthropic_error(exc: Exception) -> ProviderError: def _classify_gemini_error(exc: Exception) -> ProviderError: body = str(exc).lower() try: - from google.api_core import exceptions as gac - if isinstance(exc, gac.ResourceExhausted): - return ProviderError("quota", "gemini", exc) - if isinstance(exc, gac.TooManyRequests): - return ProviderError("rate_limit", "gemini", exc) - if isinstance(exc, (gac.Unauthenticated, gac.PermissionDenied)): - return ProviderError("auth", "gemini", exc) - if isinstance(exc, gac.ServiceUnavailable): - return ProviderError("network", "gemini", exc) + if isinstance(exc, gac.ResourceExhausted): return ProviderError("quota", "gemini", exc) + if isinstance(exc, gac.TooManyRequests): return ProviderError("rate_limit", "gemini", exc) + if isinstance(exc, (gac.Unauthenticated, gac.PermissionDenied)): return ProviderError("auth", "gemini", exc) + if isinstance(exc, gac.ServiceUnavailable): return ProviderError("network", "gemini", exc) except ImportError: pass - if "429" in body or "quota" in body or "resource exhausted" in body: - return ProviderError("quota", "gemini", exc) - if "rate" in body and "limit" in body: - return ProviderError("rate_limit", "gemini", exc) - if "401" in body or "403" in body or "api key" in body or "unauthenticated" in body: - return ProviderError("auth", "gemini", exc) - if "402" in body or "billing" in body or "balance" in body or "payment" in body: - return ProviderError("balance", "gemini", exc) - if "connection" in body or "timeout" in body or "unreachable" in body: - return ProviderError("network", "gemini", exc) + if "429" in body or "quota" in body or "resource exhausted" in body: return ProviderError("quota", "gemini", exc) + if "rate" in body and "limit" in body: return ProviderError("rate_limit", "gemini", exc) + if "401" in body or "403" in body or "api key" in body or "unauthenticated" in body: return ProviderError("auth", "gemini", exc) + if "402" in body or "billing" in body or "balance" in body or "payment" in body: return ProviderError("balance", "gemini", exc) + if "connection" in body or "timeout" in body or "unreachable" in body: return ProviderError("network", "gemini", exc) return ProviderError("unknown", "gemini", exc) def _classify_deepseek_error(exc: Exception) -> ProviderError: @@ -396,31 +372,21 @@ def _classify_deepseek_error(exc: Exception) -> ProviderError: try: # Try to get the detailed error from DeepSeek's JSON response err_data = exc.response.json() - if "error" in err_data: - body = str(err_data["error"].get("message", exc.response.text)) - else: - body = exc.response.text + if "error" in err_data: body = str(err_data["error"].get("message", exc.response.text)) + else: body = exc.response.text except: body = exc.response.text else: body = str(exc) body_l = body.lower() - if "429" in body_l or "rate" in body_l: - return ProviderError("rate_limit", "deepseek", Exception(body)) - if "401" in body_l or "403" in body_l or "auth" in body_l or "api key" in body_l: - return ProviderError("auth", "deepseek", Exception(body)) - if "402" in body_l or "balance" in body_l or "billing" in body_l: - return ProviderError("balance", "deepseek", Exception(body)) - if "quota" in body_l or "limit exceeded" in body_l: - return ProviderError("quota", "deepseek", Exception(body)) - if "connection" in body_l or "timeout" in body_l or "network" in body_l: - return ProviderError("network", "deepseek", Exception(body)) - + if "429" in body_l or "rate" in body_l: return ProviderError("rate_limit", "deepseek", Exception(body)) + if "401" in body_l or "403" in body_l or "auth" in body_l or "api key" in body_l: return ProviderError("auth", "deepseek", Exception(body)) + if "402" in body_l or "balance" in body_l or "billing" in body_l: return ProviderError("balance", "deepseek", Exception(body)) + if "quota" in body_l or "limit exceeded" in body_l: return ProviderError("quota", "deepseek", Exception(body)) + if "connection" in body_l or "timeout" in body_l or "network" in body_l: return ProviderError("network", "deepseek", Exception(body)) # If we have a body for a 400 error, wrap it - if "400" in body_l or "bad request" in body_l: - return ProviderError("unknown", "deepseek", Exception(f"DeepSeek Bad Request: {body}")) - + if "400" in body_l or "bad request" in body_l: return ProviderError("unknown", "deepseek", Exception(f"DeepSeek Bad Request: {body}")) return ProviderError("unknown", "deepseek", Exception(body)) def _classify_minimax_error(exc: Exception) -> ProviderError: @@ -428,37 +394,27 @@ def _classify_minimax_error(exc: Exception) -> ProviderError: if isinstance(exc, requests.exceptions.HTTPError) and exc.response is not None: try: err_data = exc.response.json() - if "error" in err_data: - body = str(err_data["error"].get("message", exc.response.text)) - else: - body = exc.response.text + if "error" in err_data: body = str(err_data["error"].get("message", exc.response.text)) + else: body = exc.response.text except: body = exc.response.text else: body = str(exc) body_l = body.lower() - if "429" in body_l or "rate" in body_l: - return ProviderError("rate_limit", "minimax", Exception(body)) - if "401" in body_l or "403" in body_l or "auth" in body_l or "api key" in body_l: - return ProviderError("auth", "minimax", Exception(body)) - if "402" in body_l or "balance" in body_l or "billing" in body_l: - return ProviderError("balance", "minimax", Exception(body)) - if "quota" in body_l or "limit exceeded" in body_l: - return ProviderError("quota", "minimax", Exception(body)) - if "connection" in body_l or "timeout" in body_l or "network" in body_l: - return ProviderError("network", "minimax", Exception(body)) + if "429" in body_l or "rate" in body_l: return ProviderError("rate_limit", "minimax", Exception(body)) + if "401" in body_l or "403" in body_l or "auth" in body_l or "api key" in body_l: return ProviderError("auth", "minimax", Exception(body)) + if "402" in body_l or "balance" in body_l or "billing" in body_l: return ProviderError("balance", "minimax", Exception(body)) + if "quota" in body_l or "limit exceeded" in body_l: return ProviderError("quota", "minimax", Exception(body)) + if "connection" in body_l or "timeout" in body_l or "network" in body_l: return ProviderError("network", "minimax", Exception(body)) - if "400" in body_l or "bad request" in body_l: - return ProviderError("unknown", "minimax", Exception(f"MiniMax Bad Request: {body}")) - + if "400" in body_l or "bad request" in body_l: return ProviderError("unknown", "minimax", Exception(f"MiniMax Bad Request: {body}")) return ProviderError("unknown", "minimax", Exception(body)) def set_provider(provider: str, model: str) -> None: """ - - Updates the active LLM provider and model name. - [C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController._init_ai_and_hooks, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.do_fetch, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/conftest.py:reset_ai_client, 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_model_selection, 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_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_minimax_provider.py:test_minimax_default_model, tests/test_minimax_provider.py:test_minimax_model_selection, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_rag_integration.py:test_rag_integration, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_tier4_interceptor.py:test_gemini_provider_passes_qa_callback_to_run_script, tests/test_token_usage.py:test_token_usage_tracking] + Updates the active LLM provider and model name. + [C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController._init_ai_and_hooks, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.do_fetch, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/conftest.py:reset_ai_client, 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_model_selection, 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_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_minimax_provider.py:test_minimax_default_model, tests/test_minimax_provider.py:test_minimax_model_selection, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_rag_integration.py:test_rag_integration, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_tier4_interceptor.py:test_gemini_provider_passes_qa_callback_to_run_script, tests/test_token_usage.py:test_token_usage_tracking] """ global _provider, _model _provider = provider @@ -483,7 +439,6 @@ def set_provider(provider: str, model: str) -> None: def get_provider() -> str: """ - Returns the current active provider name. [C: src/multi_agent_conductor.py:run_worker_lifecycle] """ @@ -491,7 +446,6 @@ def get_provider() -> str: def cleanup() -> None: """ - Performs cleanup operations like deleting server-side Gemini caches. [C: src/app_controller.py:AppController.clear_cache, src/app_controller.py:AppController.shutdown, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking_cleanup, tests/test_log_registry.py:TestLogRegistry.tearDown, tests/test_project_serialization.py:TestProjectSerialization.tearDown] """ @@ -505,7 +459,6 @@ def cleanup() -> None: def reset_session() -> None: """ - Clears conversation history and resets provider-specific session state. [C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_provider_panel, src/gui_2.py:App._show_menus, src/multi_agent_conductor.py:run_worker_lifecycle, tests/conftest.py:live_gui, tests/conftest.py:reset_ai_client, 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_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, 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_metrics.py:test_get_gemini_cache_stats_with_mock_client, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_session_logger_reset.py:test_reset_session, tests/test_token_usage.py:test_token_usage_tracking] """ @@ -521,11 +474,11 @@ def reset_session() -> None: _gemini_client.caches.delete(name=_gemini_cache.name) except Exception: pass - _gemini_client = None - _gemini_chat = None - _gemini_cache = None - _gemini_cache_md_hash = None - _gemini_cache_created_at = None + _gemini_client = None + _gemini_chat = None + _gemini_cache = None + _gemini_cache_md_hash = None + _gemini_cache_created_at = None _gemini_cached_file_paths = [] # Preserve binary_path if adapter exists @@ -533,35 +486,29 @@ def reset_session() -> None: _gemini_cli_adapter = GeminiCliAdapter(binary_path=old_path) _anthropic_client = None - with _anthropic_history_lock: _anthropic_history = [] - _deepseek_client = None + _deepseek_client = None with _deepseek_history_lock: _deepseek_history = [] - _minimax_client = None + _minimax_client = None with _minimax_history_lock: - _minimax_history = [] + _minimax_history = [] _CACHED_ANTHROPIC_TOOLS = None - _CACHED_DEEPSEEK_TOOLS = None + _CACHED_DEEPSEEK_TOOLS = None file_cache.reset_client() def list_models(provider: str) -> list[str]: - """ - [C: src/app_controller.py:AppController.do_fetch, tests/test_agent_capabilities.py:test_agent_capabilities_listing, tests/test_ai_client_list_models.py:test_list_models_gemini_cli, tests/test_deepseek_infra.py:test_deepseek_model_listing, tests/test_minimax_provider.py:test_minimax_list_models] - """ - creds = _load_credentials() - if provider == "gemini": - return _list_gemini_models(creds["gemini"]["api_key"]) - elif provider == "anthropic": - return _list_anthropic_models() - elif provider == "deepseek": - return _list_deepseek_models(creds["deepseek"]["api_key"]) - elif provider == "gemini_cli": - return _list_gemini_cli_models() - elif provider == "minimax": - return _list_minimax_models(creds["minimax"]["api_key"]) - return [] + """ + [C: src/app_controller.py:AppController.do_fetch, tests/test_agent_capabilities.py:test_agent_capabilities_listing, tests/test_ai_client_list_models.py:test_list_models_gemini_cli, tests/test_deepseek_infra.py:test_deepseek_model_listing, tests/test_minimax_provider.py:test_minimax_list_models] + """ + creds = _load_credentials() + if provider == "gemini": return _list_gemini_models(creds["gemini"]["api_key"]) + elif provider == "anthropic": return _list_anthropic_models() + elif provider == "deepseek": return _list_deepseek_models(creds["deepseek"]["api_key"]) + elif provider == "gemini_cli": return _list_gemini_cli_models() + elif provider == "minimax": return _list_minimax_models(creds["minimax"]["api_key"]) + return [] #endregion: Comms Log @@ -573,18 +520,16 @@ _agent_tools: dict[str, bool] = {} def set_agent_tools(tools: dict[str, bool]) -> None: """ - Configures which tools are enabled for the AI agent. [C: src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate, tests/test_agent_tools_wiring.py:test_build_anthropic_tools_conversion, tests/test_agent_tools_wiring.py:test_set_agent_tools, tests/test_tool_access_exclusion.py:test_build_anthropic_tools_excludes_disabled, tests/test_tool_access_exclusion.py:test_build_deepseek_tools_excludes_disabled, tests/test_tool_access_exclusion.py:test_gemini_tool_declaration_excludes_disabled, tests/test_tool_access_exclusion.py:test_set_agent_tools_clears_caches] """ global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS - _agent_tools = tools + _agent_tools = tools _CACHED_ANTHROPIC_TOOLS = None - _CACHED_DEEPSEEK_TOOLS = None + _CACHED_DEEPSEEK_TOOLS = None def set_tool_preset(preset_name: Optional[str]) -> None: """ - Loads a tool preset and applies it via set_agent_tools. [C: src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_persona_selector_panel, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_bias_integration.py:test_set_tool_preset_with_objects, tests/test_tool_preset_env.py:test_tool_preset_env_loading, tests/test_tool_preset_env.py:test_tool_preset_env_no_var, tests/test_tool_presets_execution.py:test_tool_ask_approval, tests/test_tool_presets_execution.py:test_tool_auto_approval, tests/test_tool_presets_execution.py:test_tool_rejection] """