Private
Public Access
working on ai client
This commit is contained in:
+57
-69
@@ -46,13 +46,13 @@ from src import project_manager
|
||||
from src.vendor_capabilities import VendorCapabilities, get_capabilities
|
||||
|
||||
# TODO(Ed): Eliminate these?
|
||||
from src.events import EventEmitter
|
||||
from src.events import EventEmitter
|
||||
from src.gemini_cli_adapter import GeminiCliAdapter
|
||||
from src.models import ToolPreset, BiasProfile, Tool
|
||||
from src.paths import get_credentials_path
|
||||
from src.tool_bias import ToolBiasEngine
|
||||
from src.tool_presets import ToolPresetManager
|
||||
from src.tool_presets import ToolPresetManager
|
||||
from src.models import ToolPreset, BiasProfile, Tool
|
||||
from src.paths import get_credentials_path
|
||||
from src.tool_bias import ToolBiasEngine
|
||||
from src.tool_presets import ToolPresetManager
|
||||
from src.tool_presets import ToolPresetManager
|
||||
|
||||
PROVIDERS: List[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax", "qwen", "grok", "llama"]
|
||||
|
||||
@@ -62,8 +62,7 @@ PROVIDERS: List[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minima
|
||||
# existing call sites and the T3.1 test (which asserts
|
||||
# hasattr(src.ai_client, '_require_warmed')) continue to work.
|
||||
from src.module_loader import _require_warmed # noqa: E402,F401
|
||||
from src.result_types import ErrorInfo, ErrorKind, Result # noqa: E402,F401
|
||||
|
||||
from src.result_types import ErrorInfo, ErrorKind, Result # noqa: E402,F401
|
||||
|
||||
_provider: str = "gemini"
|
||||
_model: str = "gemini-2.5-flash-lite"
|
||||
@@ -87,42 +86,42 @@ def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000, top_p:
|
||||
_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_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_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_history: list[dict[str, Any]] = []
|
||||
_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()
|
||||
|
||||
_qwen_client: Any = None
|
||||
_qwen_client: Any = None
|
||||
_qwen_history: list[dict[str, Any]] = []
|
||||
_qwen_history_lock: threading.Lock = threading.Lock()
|
||||
_qwen_region: str = "china"
|
||||
_qwen_region: str = "china"
|
||||
|
||||
_grok_client: Any = None
|
||||
_grok_client: Any = None
|
||||
_grok_history: list[dict[str, Any]] = []
|
||||
_grok_history_lock: threading.Lock = threading.Lock()
|
||||
|
||||
_llama_client: Any = None
|
||||
_llama_history: list[dict[str, Any]] = []
|
||||
_llama_history_lock: threading.Lock = threading.Lock()
|
||||
_llama_base_url: str = "http://localhost:11434/v1"
|
||||
_llama_api_key: str = "ollama"
|
||||
_llama_client: Any = None
|
||||
_llama_history: list[dict[str, Any]] = []
|
||||
_llama_history_lock: threading.Lock = threading.Lock()
|
||||
_llama_base_url: str = "http://localhost:11434/v1"
|
||||
_llama_api_key: str = "ollama"
|
||||
|
||||
_send_lock: threading.Lock = threading.Lock()
|
||||
|
||||
@@ -271,6 +270,7 @@ def get_credentials_path() -> Path:
|
||||
|
||||
def _load_credentials() -> dict[str, Any]:
|
||||
cred_path = get_credentials_path()
|
||||
#TODO(Ed): Exception(Review)
|
||||
try:
|
||||
with open(cred_path, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
@@ -407,8 +407,9 @@ def get_provider() -> str:
|
||||
|
||||
def cleanup() -> None:
|
||||
"""Performs cleanup operations like deleting server-side Gemini caches."""
|
||||
global _gemini_client, _gemini_cache, _gemini_cached_file_paths
|
||||
if _gemini_client and _gemini_cache:
|
||||
global _gemini_client, _gemini_cache, _gemini_cached_file_paths
|
||||
if _gemini_client and _gemini_cache:
|
||||
#TODO(Ed): Exception(Review)
|
||||
try:
|
||||
_gemini_client.caches.delete(name=_gemini_cache.name)
|
||||
except Exception:
|
||||
@@ -417,15 +418,16 @@ def cleanup() -> None:
|
||||
|
||||
def reset_session() -> None:
|
||||
"""Clears conversation history and resets provider-specific session state."""
|
||||
global _gemini_client, _gemini_chat, _gemini_cache
|
||||
global _gemini_client, _gemini_chat, _gemini_cache
|
||||
global _gemini_cache_md_hash, _gemini_cache_created_at, _gemini_cached_file_paths
|
||||
global _anthropic_client, _anthropic_history
|
||||
global _deepseek_client, _deepseek_history
|
||||
global _minimax_client, _minimax_history
|
||||
global _qwen_client, _qwen_history
|
||||
global _anthropic_client, _anthropic_history
|
||||
global _deepseek_client, _deepseek_history
|
||||
global _minimax_client, _minimax_history
|
||||
global _qwen_client, _qwen_history
|
||||
global _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS
|
||||
global _gemini_cli_adapter
|
||||
if _gemini_client and _gemini_cache:
|
||||
#TODO(Ed): Review(Exception)
|
||||
try:
|
||||
_gemini_client.caches.delete(name=_gemini_cache.name)
|
||||
except Exception:
|
||||
@@ -438,7 +440,7 @@ def reset_session() -> None:
|
||||
_gemini_cached_file_paths = []
|
||||
|
||||
# Preserve binary_path if adapter exists
|
||||
old_path = _gemini_cli_adapter.binary_path if _gemini_cli_adapter else "gemini"
|
||||
old_path = _gemini_cli_adapter.binary_path if _gemini_cli_adapter else "gemini"
|
||||
_gemini_cli_adapter = GeminiCliAdapter(binary_path=old_path)
|
||||
|
||||
_anthropic_client = None
|
||||
@@ -456,28 +458,25 @@ def reset_session() -> None:
|
||||
_grok_client = None
|
||||
with _grok_history_lock:
|
||||
_grok_history = []
|
||||
_llama_client = None
|
||||
_llama_client = None
|
||||
with _llama_history_lock:
|
||||
_llama_history = []
|
||||
_llama_base_url = "http://localhost:11434/v1"
|
||||
_llama_api_key = "ollama"
|
||||
_llama_api_key = "ollama"
|
||||
_CACHED_ANTHROPIC_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"])
|
||||
elif provider == "qwen": return _list_qwen_models()
|
||||
elif provider == "grok": return _list_grok_models()
|
||||
elif provider == "llama": return _list_llama_models()
|
||||
elif provider == "qwen": return _list_qwen_models()
|
||||
elif provider == "grok": return _list_grok_models()
|
||||
elif provider == "llama": return _list_llama_models()
|
||||
return []
|
||||
|
||||
#endregion: Comms Log
|
||||
@@ -489,20 +488,14 @@ _agent_tools: dict[str, bool] = {}
|
||||
#region: Tool Configuration
|
||||
|
||||
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]
|
||||
"""
|
||||
"""Configures which tools are enabled for the AI agent."""
|
||||
global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS
|
||||
_agent_tools = tools
|
||||
_CACHED_ANTHROPIC_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]
|
||||
"""
|
||||
"""Loads a tool preset and applies it via set_agent_tools."""
|
||||
global _agent_tools, _CACHED_ANTHROPIC_TOOLS, _CACHED_DEEPSEEK_TOOLS, _tool_approval_modes, _active_tool_preset
|
||||
_tool_approval_modes = {}
|
||||
if not preset_name or preset_name == "None":
|
||||
@@ -532,11 +525,7 @@ def set_tool_preset(preset_name: Optional[str]) -> None:
|
||||
_CACHED_DEEPSEEK_TOOLS = None
|
||||
|
||||
def set_bias_profile(profile_name: Optional[str]) -> None:
|
||||
"""
|
||||
|
||||
Sets the active tool bias profile for tuning model behavior.
|
||||
[C: src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_agent_tools_panel, src/gui_2.py:App._render_persona_selector_panel, src/multi_agent_conductor.py:run_worker_lifecycle]
|
||||
"""
|
||||
"""Sets the active tool bias profile for tuning model behavior."""
|
||||
global _active_bias_profile
|
||||
if not profile_name or profile_name == "None":
|
||||
_active_bias_profile = None
|
||||
@@ -1191,7 +1180,7 @@ def _repair_anthropic_history(history: list[dict[str, Any]]) -> None:
|
||||
],
|
||||
})
|
||||
|
||||
def _send_anthropic_result(md_content: str, user_message: str, base_dir: str, file_items: list[dict[str, Any]] | None = None, discussion_history: str = "", pre_tool_callback: Optional[Callable[[str, str, Optional[Callable[[str], str]]], Optional[str]]] = None, qa_callback: Optional[Callable[[str], str]] = None, stream_callback: Optional[Callable[[str], None]] = None, patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> Result[str]:
|
||||
def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_items: list[dict[str, Any]] | None = None, discussion_history: str = "", pre_tool_callback: Optional[Callable[[str, str, Optional[Callable[[str], str]]], Optional[str]]] = None, qa_callback: Optional[Callable[[str], str]] = None, stream_callback: Optional[Callable[[str], None]] = None, patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> Result[str]:
|
||||
"""
|
||||
[C: src/ai_server.py:_handle_send]
|
||||
"""
|
||||
@@ -1434,7 +1423,7 @@ def _get_gemini_history_list(chat: Any | None) -> list[Any]:
|
||||
return cast(list[Any], chat.get_history())
|
||||
return []
|
||||
|
||||
def _send_gemini_result(md_content: str, user_message: str, base_dir: str,
|
||||
def _send_gemini(md_content: str, user_message: str, base_dir: str,
|
||||
file_items: list[dict[str, Any]] | None = None,
|
||||
discussion_history: str = "",
|
||||
pre_tool_callback: Optional[Callable[[str, str, Optional[Callable[[str], str]]], Optional[str]]] = None,
|
||||
@@ -1670,7 +1659,7 @@ def _send_gemini_result(md_content: str, user_message: str, base_dir: str,
|
||||
if monitor.enabled: monitor.end_component("ai_client._send_gemini")
|
||||
return Result(data="", errors=[_classify_gemini_error(e, source="ai_client.gemini")])
|
||||
|
||||
def _send_gemini_cli_result(md_content: str, user_message: str, base_dir: str,
|
||||
def _send_gemini_cli(md_content: str, user_message: str, base_dir: str,
|
||||
file_items: list[dict[str, Any]] | None = None,
|
||||
discussion_history: str = "",
|
||||
pre_tool_callback: Optional[Callable[[str, str, Optional[Callable[[str], str]]], Optional[str]]] = None,
|
||||
@@ -1815,7 +1804,7 @@ def _ensure_deepseek_client() -> None:
|
||||
_load_credentials()
|
||||
pass
|
||||
|
||||
def _send_deepseek_result(md_content: str, user_message: str, base_dir: str,
|
||||
def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
file_items: list[dict[str, Any]] | None = None,
|
||||
discussion_history: str = "",
|
||||
stream: bool = False,
|
||||
@@ -2076,6 +2065,7 @@ def _send_deepseek_result(md_content: str, user_message: str, base_dir: str,
|
||||
|
||||
#region: MiniMax Provider
|
||||
|
||||
#TODO(Ed): This causes a pause on gui thread, this should be cached.
|
||||
def _list_minimax_models(api_key: str) -> list[str]:
|
||||
try:
|
||||
openai = _require_warmed("openai")
|
||||
@@ -2090,17 +2080,14 @@ def _list_minimax_models(api_key: str) -> list[str]:
|
||||
return ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"]
|
||||
|
||||
def _repair_minimax_history(history: list[dict[str, Any]]) -> None:
|
||||
if not history:
|
||||
return
|
||||
if not history: return
|
||||
last = history[-1]
|
||||
if last.get("role") != "assistant":
|
||||
return
|
||||
if last.get("role") != "assistant": return
|
||||
tool_calls = last.get("tool_calls", [])
|
||||
if not tool_calls:
|
||||
return
|
||||
if not tool_calls: return
|
||||
call_ids = []
|
||||
for tc in tool_calls:
|
||||
if hasattr(tc, "id"): call_ids.append(tc.id)
|
||||
if hasattr(tc, "id"): call_ids.append(tc.id)
|
||||
elif isinstance(tc, dict) and tc.get("id"): call_ids.append(tc["id"])
|
||||
|
||||
for cid in call_ids:
|
||||
@@ -2332,7 +2319,7 @@ def _list_qwen_models() -> list[str]:
|
||||
from src.vendor_capabilities import list_models_for_vendor
|
||||
return list_models_for_vendor("qwen")
|
||||
|
||||
def _send_qwen_result(md_content: str, user_message: str, base_dir: str,
|
||||
def _send_qwen(md_content: str, user_message: str, base_dir: str,
|
||||
file_items: list[dict[str, Any]] | None = None,
|
||||
discussion_history: str = "",
|
||||
stream: bool = False,
|
||||
@@ -2481,6 +2468,7 @@ def _send_llama_native(md_content: str, user_message: str, base_dir: str,
|
||||
return Result(data=(f"<thinking>\n{thinking}\n</thinking>\n" if thinking else "") + text)
|
||||
except Exception as exc:
|
||||
return Result(data="", errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(exc), source="ai_client.llama_native", original=exc)])
|
||||
|
||||
def _list_llama_models() -> list[str]:
|
||||
from src.vendor_capabilities import list_models_for_vendor
|
||||
return list_models_for_vendor("llama")
|
||||
@@ -2682,22 +2670,22 @@ def send_result(
|
||||
p = str(_provider).lower().strip()
|
||||
try:
|
||||
if p == "gemini":
|
||||
res = _send_gemini_result(
|
||||
res = _send_gemini(
|
||||
md_content, user_message, base_dir, file_items, discussion_history,
|
||||
pre_tool_callback, qa_callback, enable_tools, stream_callback, patch_callback
|
||||
)
|
||||
elif p == "gemini_cli":
|
||||
res = _send_gemini_cli_result(
|
||||
res = _send_gemini_cli(
|
||||
md_content, user_message, base_dir, file_items, discussion_history,
|
||||
pre_tool_callback, qa_callback, stream_callback, patch_callback
|
||||
)
|
||||
elif p == "anthropic":
|
||||
res = _send_anthropic_result(
|
||||
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_result(
|
||||
res = _send_deepseek(
|
||||
md_content, user_message, base_dir, file_items, discussion_history,
|
||||
stream, pre_tool_callback, qa_callback, stream_callback, patch_callback
|
||||
)
|
||||
@@ -2707,7 +2695,7 @@ def send_result(
|
||||
stream, pre_tool_callback, qa_callback, stream_callback, patch_callback
|
||||
)
|
||||
elif p == "qwen":
|
||||
res = _send_qwen_result(
|
||||
res = _send_qwen(
|
||||
md_content, user_message, base_dir, file_items, discussion_history,
|
||||
stream, pre_tool_callback, qa_callback, stream_callback, patch_callback
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user