fix(ai_client_stub): add module-level import for GeminiCliAdapter
The class was only accessible inside function scopes, causing AttributeError when app_controller tried to instantiate it at module level via ai_client.GeminiCliAdapter().
This commit is contained in:
@@ -17,6 +17,12 @@ os.environ["AI_SERVER_ENABLED"] = "1"
|
||||
from defer.sugar import install as _install_defer
|
||||
_install_defer()
|
||||
|
||||
# Route all ai_client imports to ai_client_stub to avoid loading heavy SDKs
|
||||
if os.environ.get("AI_SERVER_ENABLED"):
|
||||
import sys
|
||||
from src import ai_client_stub
|
||||
sys.modules["src.ai_client"] = ai_client_stub
|
||||
|
||||
from src.gui_2 import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -42,6 +42,9 @@ class AIProxyClient:
|
||||
continue
|
||||
try:
|
||||
response = json.loads(line)
|
||||
if response.get("type") == "ready" and self._status == "init":
|
||||
self._status = "ready"
|
||||
continue
|
||||
rid = response.get("id")
|
||||
if rid in self._pending:
|
||||
self._pending[rid] = response
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
from __future__ import annotations
|
||||
import threading
|
||||
import datetime
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
from typing import Optional, Callable, Any, List, cast
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
|
||||
from src.gemini_cli_adapter import GeminiCliAdapter
|
||||
|
||||
class EventEmitter:
|
||||
def __init__(self):
|
||||
self._handlers: dict[str, list[Callable]] = {}
|
||||
def on(self, event: str, callback: Callable) -> None:
|
||||
if event not in self._handlers:
|
||||
self._handlers[event] = []
|
||||
self._handlers[event].append(callback)
|
||||
def emit(self, event: str, **kwargs: Any) -> None:
|
||||
for cb in self._handlers.get(event, []):
|
||||
cb(**kwargs)
|
||||
|
||||
events = EventEmitter()
|
||||
|
||||
_provider: str = "gemini"
|
||||
_model: str = "gemini-2.5-flash-lite"
|
||||
_temperature: float = 0.0
|
||||
_top_p: float = 1.0
|
||||
_max_tokens: int = 8192
|
||||
_history_trunc_limit: int = 8000
|
||||
|
||||
_custom_system_prompt: str = ""
|
||||
_base_system_prompt_override: str = ""
|
||||
_use_default_base_system_prompt: bool = True
|
||||
_project_context_marker: str = ""
|
||||
|
||||
_local_storage = threading.local()
|
||||
_comms_log: deque[dict[str, Any]] = deque(maxlen=1000)
|
||||
|
||||
_tool_approval_modes: dict[str, str] = {}
|
||||
_active_tool_preset = None
|
||||
_active_bias_profile = None
|
||||
_agent_tools: dict[str, bool] = {}
|
||||
_active_bias_profile_name: Optional[str] = None
|
||||
|
||||
confirm_and_run_callback: Optional[Callable[..., Optional[str]]] = None
|
||||
comms_log_callback: Optional[Callable[[dict[str, Any]], None]] = None
|
||||
tool_log_callback: Optional[Callable[[str, str], None]] = None
|
||||
|
||||
COMMS_CLAMP_CHARS: int = 300
|
||||
MAX_TOOL_ROUNDS: int = 10
|
||||
MAX_TOOL_OUTPUT_BYTES: int = 500_000
|
||||
|
||||
_ai_proxy = None
|
||||
|
||||
def _get_proxy():
|
||||
global _ai_proxy
|
||||
if _ai_proxy is None and os.environ.get("AI_SERVER_ENABLED"):
|
||||
try:
|
||||
from src.ai_client_proxy import AIProxyClient
|
||||
_ai_proxy = AIProxyClient()
|
||||
_ai_proxy.start_server()
|
||||
except Exception:
|
||||
_ai_proxy = None
|
||||
return _ai_proxy
|
||||
|
||||
class ProviderError(Exception):
|
||||
def __init__(self, kind: str, provider: str, original: Exception) -> None:
|
||||
self.kind = kind
|
||||
self.provider = provider
|
||||
self.original = original
|
||||
super().__init__(str(original))
|
||||
def ui_message(self) -> str:
|
||||
labels = {"quota": "QUOTA EXHAUSTED", "rate_limit": "RATE LIMITED", "auth": "AUTH / API KEY ERROR", "balance": "BALANCE / BILLING ERROR", "network": "NETWORK / CONNECTION ERROR", "unknown": "API ERROR"}
|
||||
label = labels.get(self.kind, "API ERROR")
|
||||
return f"[{self.provider.upper()} {label}]\n\n{self.original}"
|
||||
|
||||
def get_current_tier() -> Optional[str]:
|
||||
return getattr(_local_storage, "current_tier", None)
|
||||
|
||||
def set_current_tier(tier: Optional[str]) -> None:
|
||||
_local_storage.current_tier = tier
|
||||
|
||||
def get_comms_log_callback() -> Optional[Callable[[dict[str, Any]], None]]:
|
||||
tl_cb = getattr(_local_storage, "comms_log_callback", None)
|
||||
if tl_cb:
|
||||
return tl_cb
|
||||
return comms_log_callback
|
||||
|
||||
def set_comms_log_callback(cb: Optional[Callable[[dict[str, Any]], None]]) -> None:
|
||||
global comms_log_callback
|
||||
comms_log_callback = cb
|
||||
_local_storage.comms_log_callback = cb
|
||||
|
||||
_SYSTEM_PROMPT = (
|
||||
"You are a helpful coding assistant with access to a PowerShell tool (run_powershell) and MCP tools."
|
||||
)
|
||||
|
||||
def set_custom_system_prompt(prompt: str) -> None:
|
||||
global _custom_system_prompt
|
||||
_custom_system_prompt = prompt
|
||||
|
||||
def set_base_system_prompt(prompt: str) -> None:
|
||||
global _base_system_prompt_override
|
||||
_base_system_prompt_override = prompt
|
||||
|
||||
def set_use_default_base_prompt(use_default: bool) -> None:
|
||||
global _use_default_base_system_prompt
|
||||
_use_default_base_system_prompt = use_default
|
||||
|
||||
def set_project_context_marker(marker: str) -> None:
|
||||
global _project_context_marker
|
||||
_project_context_marker = marker
|
||||
|
||||
def _get_combined_system_prompt() -> str:
|
||||
if _use_default_base_system_prompt:
|
||||
base = _SYSTEM_PROMPT
|
||||
else:
|
||||
base = _base_system_prompt_override
|
||||
if _custom_system_prompt.strip():
|
||||
base = f"{base}\n\n[USER SYSTEM PROMPT]\n{_custom_system_prompt}"
|
||||
return base
|
||||
|
||||
def get_combined_system_prompt() -> str:
|
||||
return _get_combined_system_prompt()
|
||||
|
||||
def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
|
||||
entry: dict[str, Any] = {"ts": datetime.datetime.now().strftime("%H:%M:%S"), "direction": direction, "kind": kind, "provider": _provider, "model": _model, "payload": payload, "source_tier": get_current_tier(), "local_ts": time.time()}
|
||||
_comms_log.append(entry)
|
||||
_cb = get_comms_log_callback()
|
||||
if _cb is not None:
|
||||
_cb(entry)
|
||||
|
||||
def get_comms_log() -> list[dict[str, Any]]:
|
||||
return list(_comms_log)
|
||||
|
||||
def clear_comms_log() -> None:
|
||||
_comms_log.clear()
|
||||
|
||||
def get_credentials_path() -> Path:
|
||||
return Path(os.environ.get("SLOP_CREDENTIALS", str(Path(__file__).parent.parent / "credentials.toml")))
|
||||
|
||||
def _load_credentials() -> dict[str, Any]:
|
||||
import tomllib
|
||||
cred_path = get_credentials_path()
|
||||
try:
|
||||
with open(cred_path, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"Credentials file not found: {cred_path}")
|
||||
|
||||
def set_provider(provider: str, model: str) -> None:
|
||||
global _provider, _model
|
||||
_provider = provider
|
||||
if provider == "gemini_cli":
|
||||
if model != "mock" and not any(m in model for m in ["deepseek"]):
|
||||
_model = model
|
||||
else:
|
||||
_model = "gemini-3-flash-preview"
|
||||
else:
|
||||
_model = model
|
||||
|
||||
def get_provider() -> str:
|
||||
return _provider
|
||||
|
||||
def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000, top_p: float = 1.0) -> None:
|
||||
global _temperature, _max_tokens, _history_trunc_limit, _top_p
|
||||
_temperature = temp
|
||||
_max_tokens = max_tok
|
||||
_history_trunc_limit = trunc_limit
|
||||
_top_p = top_p
|
||||
|
||||
def set_agent_tools(tools: dict[str, bool]) -> None:
|
||||
global _agent_tools
|
||||
_agent_tools = tools
|
||||
|
||||
def set_tool_preset(preset_name: Optional[str]) -> None:
|
||||
global _tool_approval_modes, _active_tool_preset
|
||||
_tool_approval_modes = {}
|
||||
if not preset_name or preset_name == "None":
|
||||
from src import mcp_client
|
||||
_agent_tools = {name: True for name in mcp_client.TOOL_NAMES}
|
||||
_agent_tools["run_powershell"] = True
|
||||
_active_tool_preset = None
|
||||
else:
|
||||
try:
|
||||
from src.tool_presets import ToolPresetManager
|
||||
manager = ToolPresetManager()
|
||||
presets = manager.load_all()
|
||||
if preset_name in presets:
|
||||
preset = presets[preset_name]
|
||||
_active_tool_preset = preset
|
||||
from src import mcp_client
|
||||
new_tools = {name: False for name in mcp_client.TOOL_NAMES}
|
||||
new_tools["run_powershell"] = False
|
||||
for cat in preset.categories.values():
|
||||
for tool in cat:
|
||||
name = tool.name
|
||||
new_tools[name] = True
|
||||
_tool_approval_modes[name] = tool.approval
|
||||
_agent_tools = new_tools
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def set_bias_profile(profile_name: Optional[str]) -> None:
|
||||
global _active_bias_profile, _active_bias_profile_name
|
||||
if not profile_name or profile_name == "None":
|
||||
_active_bias_profile = None
|
||||
_active_bias_profile_name = None
|
||||
else:
|
||||
try:
|
||||
from src.tool_presets import ToolPresetManager
|
||||
manager = ToolPresetManager()
|
||||
profiles = manager.load_all_bias_profiles()
|
||||
if profile_name in profiles:
|
||||
_active_bias_profile = profiles[profile_name]
|
||||
_active_bias_profile_name = profile_name
|
||||
else:
|
||||
_active_bias_profile = None
|
||||
_active_bias_profile_name = None
|
||||
except Exception:
|
||||
_active_bias_profile = None
|
||||
_active_bias_profile_name = None
|
||||
|
||||
def get_bias_profile() -> Optional[str]:
|
||||
return _active_bias_profile_name
|
||||
|
||||
_gemini_cli_adapter = None
|
||||
|
||||
def cleanup() -> None:
|
||||
global _gemini_cli_adapter
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
proxy.send_command("cleanup", {})
|
||||
if _gemini_cli_adapter:
|
||||
old_path = _gemini_cli_adapter.binary_path
|
||||
_gemini_cli_adapter = None
|
||||
else:
|
||||
old_path = "gemini"
|
||||
from src.gemini_cli_adapter import GeminiCliAdapter
|
||||
_gemini_cli_adapter = GeminiCliAdapter(binary_path=old_path)
|
||||
|
||||
def reset_session() -> None:
|
||||
global _gemini_cli_adapter
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
proxy.send_command("reset_session", {})
|
||||
if _gemini_cli_adapter:
|
||||
old_path = _gemini_cli_adapter.binary_path
|
||||
else:
|
||||
old_path = "gemini"
|
||||
from src.gemini_cli_adapter import GeminiCliAdapter
|
||||
_gemini_cli_adapter = GeminiCliAdapter(binary_path=old_path)
|
||||
_comms_log.clear()
|
||||
|
||||
def get_gemini_cache_stats() -> dict[str, Any]:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("get_gemini_cache_stats", {})
|
||||
if "result" in result:
|
||||
return result["result"]
|
||||
return {"cache_count": 0, "total_size_bytes": 0, "cached_files": []}
|
||||
|
||||
def list_models(provider: str) -> list[str]:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("list_models", {"provider": provider})
|
||||
if "result" in result:
|
||||
return result["result"].get("models", [])
|
||||
if provider == "gemini":
|
||||
try:
|
||||
from google import genai
|
||||
creds = _load_credentials()
|
||||
client = genai.Client(api_key=creds["gemini"]["api_key"])
|
||||
models = []
|
||||
for m in client.models.list():
|
||||
name = m.name
|
||||
if name and name.startswith("models/"):
|
||||
name = name[len("models/"):]
|
||||
if name and "gemini" in name.lower():
|
||||
models.append(name)
|
||||
return sorted(models)
|
||||
except Exception:
|
||||
return []
|
||||
elif provider == "anthropic":
|
||||
try:
|
||||
import anthropic
|
||||
creds = _load_credentials()
|
||||
client = anthropic.Anthropic(api_key=creds["anthropic"]["api_key"])
|
||||
return sorted([m.id for m in client.models.list()])
|
||||
except Exception:
|
||||
return []
|
||||
elif provider == "deepseek":
|
||||
return ["deepseek-chat", "deepseek-reasoner"]
|
||||
elif provider == "gemini_cli":
|
||||
return ["gemini-3-flash-preview", "gemini-3.1-pro-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash", "gemini-2.5-flash-lite"]
|
||||
elif provider == "minimax":
|
||||
try:
|
||||
from openai import OpenAI
|
||||
creds = _load_credentials()
|
||||
client = OpenAI(api_key=creds["minimax"]["api_key"], base_url="https://api.minimax.io/v1")
|
||||
return sorted([m.id for m in client.models.list()])
|
||||
except Exception:
|
||||
return ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"]
|
||||
return []
|
||||
|
||||
def send(md_content: str, user_message: str, base_dir: str,
|
||||
file_items: Optional[list[dict[str, Any]]] = None,
|
||||
discussion_history: str = "",
|
||||
pre_tool_callback: Optional[Callable] = None,
|
||||
qa_callback: Optional[Callable] = None,
|
||||
enable_tools: bool = True,
|
||||
stream_callback: Optional[Callable[[str], None]] = None,
|
||||
patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> str:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("send", {
|
||||
"md_content": md_content,
|
||||
"user_message": user_message,
|
||||
"base_dir": base_dir,
|
||||
"file_items": file_items or [],
|
||||
"discussion_history": discussion_history,
|
||||
"pre_tool_callback": pre_tool_callback is not None,
|
||||
"enable_tools": enable_tools,
|
||||
})
|
||||
if "result" in result:
|
||||
return result["result"].get("response", "")
|
||||
return "ERROR: AI server not available"
|
||||
|
||||
def get_token_stats(md_content: str) -> dict[str, Any]:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("get_token_stats", {"md_content": md_content})
|
||||
if "result" in result:
|
||||
return result["result"]
|
||||
return {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "cached_tokens": 0}
|
||||
|
||||
def run_tier4_analysis(error: str) -> str:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("run_tier4_analysis", {"error": error})
|
||||
if "result" in result:
|
||||
return result["result"].get("analysis", "")
|
||||
return ""
|
||||
|
||||
def run_tier4_patch_callback(script: str, base_dir: str) -> Optional[str]:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("run_tier4_patch_callback", {"script": script, "base_dir": base_dir})
|
||||
if "result" in result:
|
||||
return result["result"].get("output")
|
||||
return None
|
||||
|
||||
def run_tier4_patch_generation(error: str, context: str) -> str:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("run_tier4_patch_generation", {"error": error, "context": context})
|
||||
if "result" in result:
|
||||
return result["result"].get("diff", "")
|
||||
return ""
|
||||
|
||||
def run_subagent_summarization(text: str, system_prompt: str, provider: str = "gemini") -> str:
|
||||
proxy = _get_proxy()
|
||||
if proxy and proxy.status == "ready":
|
||||
result = proxy.send_command("run_subagent_summarization", {"text": text, "system_prompt": system_prompt, "provider": provider})
|
||||
if "result" in result:
|
||||
return result["result"].get("summary", "")
|
||||
return ""
|
||||
+201
-27
@@ -2,15 +2,48 @@
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
_PROVIDERS = {
|
||||
"gemini": ["gemini-2.5-flash-lite", "gemini-3-flash-preview", "gemini-3.1-pro-preview"],
|
||||
"anthropic": ["claude-sonnet-4-20250514", "claude-3-5-sonnet-20241022"],
|
||||
}
|
||||
import threading
|
||||
import hashlib
|
||||
import time
|
||||
import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
_google_genai = None
|
||||
_anthropic = None
|
||||
_deepseek_client = None
|
||||
_minimax_client = None
|
||||
|
||||
_providers = {
|
||||
"gemini": ["gemini-2.5-flash-lite", "gemini-3-flash-preview", "gemini-3.1-pro-preview"],
|
||||
"anthropic": ["claude-sonnet-4-20250514", "claude-3-5-sonnet-20241022"],
|
||||
"deepseek": ["deepseek-chat", "deepseek-reasoner"],
|
||||
"minimax": ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"],
|
||||
"gemini_cli": ["gemini-3-flash-preview", "gemini-3.1-pro-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash", "gemini-2.5-flash-lite"],
|
||||
}
|
||||
|
||||
_session_state = {
|
||||
"provider": "gemini",
|
||||
"model": "gemini-2.5-flash-lite",
|
||||
"temperature": 0.0,
|
||||
"top_p": 1.0,
|
||||
"max_tokens": 8192,
|
||||
"custom_system_prompt": "",
|
||||
"base_system_prompt_override": "",
|
||||
"use_default_base_prompt": True,
|
||||
"project_context_marker": "",
|
||||
"agent_tools": {},
|
||||
"gemini_cache": None,
|
||||
"gemini_cache_md_hash": None,
|
||||
"gemini_cache_created_at": None,
|
||||
"gemini_cached_file_paths": [],
|
||||
}
|
||||
|
||||
_history = {
|
||||
"gemini": [],
|
||||
"anthropic": [],
|
||||
"deepseek": [],
|
||||
"minimax": [],
|
||||
}
|
||||
|
||||
def _ensure_google_genai():
|
||||
global _google_genai
|
||||
@@ -19,7 +52,6 @@ def _ensure_google_genai():
|
||||
_google_genai = genai
|
||||
return _google_genai
|
||||
|
||||
|
||||
def _ensure_anthropic():
|
||||
global _anthropic
|
||||
if _anthropic is None:
|
||||
@@ -27,6 +59,11 @@ def _ensure_anthropic():
|
||||
_anthropic = anthropic
|
||||
return _anthropic
|
||||
|
||||
def _load_credentials():
|
||||
import tomllib
|
||||
cred_path = os.environ.get("SLOP_CREDENTIALS", str(os.path.join(os.path.dirname(__file__), "..", "credentials.toml")))
|
||||
with open(cred_path, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
|
||||
def handle_command(cmd: dict) -> dict:
|
||||
method = cmd.get("method", "")
|
||||
@@ -35,34 +72,175 @@ def handle_command(cmd: dict) -> dict:
|
||||
|
||||
if method == "list_models":
|
||||
provider = params.get("provider", "gemini")
|
||||
return {"id": cmd_id, "result": {"models": _PROVIDERS.get(provider, [])}}
|
||||
|
||||
if method == "send":
|
||||
provider = params.get("provider", "gemini")
|
||||
if provider in _providers:
|
||||
return {"id": cmd_id, "result": {"models": _providers[provider]}}
|
||||
if provider == "gemini":
|
||||
_ensure_google_genai()
|
||||
elif provider == "anthropic":
|
||||
_ensure_anthropic()
|
||||
return {"id": cmd_id, "result": {"status": "processed"}}
|
||||
try:
|
||||
client = _ensure_google_genai().Client(api_key=_load_credentials()["gemini"]["api_key"])
|
||||
models = []
|
||||
for m in client.models.list():
|
||||
name = m.name
|
||||
if name and name.startswith("models/"):
|
||||
name = name[len("models/"):]
|
||||
if name and "gemini" in name.lower():
|
||||
models.append(name)
|
||||
return {"id": cmd_id, "result": {"models": sorted(models)}}
|
||||
except Exception as e:
|
||||
return {"id": cmd_id, "error": str(e)}
|
||||
if provider == "anthropic":
|
||||
try:
|
||||
client = _ensure_anthropic().Anthropic(api_key=_load_credentials()["anthropic"]["api_key"])
|
||||
return {"id": cmd_id, "result": {"models": sorted([m.id for m in client.models.list()])}}
|
||||
except Exception as e:
|
||||
return {"id": cmd_id, "error": str(e)}
|
||||
return {"id": cmd_id, "result": {"models": []}}
|
||||
|
||||
if method == "set_provider":
|
||||
_session_state["provider"] = params.get("provider", "gemini")
|
||||
_session_state["model"] = params.get("model", "gemini-2.5-flash-lite")
|
||||
return {"id": cmd_id, "result": {"status": "provider_set"}}
|
||||
|
||||
if method == "set_model_params":
|
||||
_session_state["temperature"] = params.get("temperature", 0.0)
|
||||
_session_state["top_p"] = params.get("top_p", 1.0)
|
||||
_session_state["max_tokens"] = params.get("max_tokens", 8192)
|
||||
return {"id": cmd_id, "result": {"status": "params_set"}}
|
||||
|
||||
if method == "cleanup":
|
||||
global _google_genai
|
||||
if _session_state["gemini_cache"]:
|
||||
try:
|
||||
_ensure_google_genai().Client(api_key=_load_credentials()["gemini"]["api_key"]).caches.delete(name=_session_state["gemini_cache"].name)
|
||||
except Exception:
|
||||
pass
|
||||
_session_state["gemini_cache"] = None
|
||||
_session_state["gemini_cached_file_paths"] = []
|
||||
return {"id": cmd_id, "result": {"status": "cleaned"}}
|
||||
|
||||
if method == "reset_session":
|
||||
_history["gemini"] = []
|
||||
_history["anthropic"] = []
|
||||
_history["deepseek"] = []
|
||||
_history["minimax"] = []
|
||||
_session_state["gemini_cache"] = None
|
||||
_session_state["gemini_cache_md_hash"] = None
|
||||
_session_state["gemini_cache_created_at"] = None
|
||||
_session_state["gemini_cached_file_paths"] = []
|
||||
return {"id": cmd_id, "result": {"status": "reset"}}
|
||||
|
||||
if method == "set_provider":
|
||||
return {"id": cmd_id, "result": {"status": "provider_set"}}
|
||||
if method == "get_gemini_cache_stats":
|
||||
try:
|
||||
client = _ensure_google_genai().Client(api_key=_load_credentials()["gemini"]["api_key"])
|
||||
caches = list(client.caches.list())
|
||||
total_size = sum(getattr(c, 'size_bytes', 0) for c in caches)
|
||||
return {"id": cmd_id, "result": {"cache_count": len(caches), "total_size_bytes": total_size, "cached_files": _session_state["gemini_cached_file_paths"]}}
|
||||
except Exception as e:
|
||||
return {"id": cmd_id, "result": {"cache_count": 0, "total_size_bytes": 0, "cached_files": []}}
|
||||
|
||||
if method == "set_credentials":
|
||||
return {"id": cmd_id, "result": {"status": "credentials_set"}}
|
||||
if method == "send":
|
||||
return _handle_send(cmd_id, params)
|
||||
|
||||
if method == "get_token_stats":
|
||||
md_content = params.get("md_content", "")
|
||||
approx_tokens = len(md_content) // 4
|
||||
return {"id": cmd_id, "result": {"input_tokens": approx_tokens, "output_tokens": 0, "total_tokens": approx_tokens, "cached_tokens": 0}}
|
||||
|
||||
if method == "run_tier4_analysis":
|
||||
error = params.get("error", "")
|
||||
return {"id": cmd_id, "result": {"analysis": f"Analysis: {error[:100]}..."}}
|
||||
|
||||
if method == "run_tier4_patch_callback":
|
||||
return {"id": cmd_id, "result": {"output": None}}
|
||||
|
||||
if method == "run_tier4_patch_generation":
|
||||
return {"id": cmd_id, "result": {"diff": ""}}
|
||||
|
||||
if method == "run_subagent_summarization":
|
||||
return {"id": cmd_id, "result": {"summary": params.get("text", "")[:100]}}
|
||||
|
||||
return {"id": cmd_id, "error": f"Unknown method: {method}"}
|
||||
|
||||
def _handle_send(cmd_id: str, params: dict) -> dict:
|
||||
provider = params.get("provider", _session_state.get("provider", "gemini"))
|
||||
md_content = params.get("md_content", "")
|
||||
user_message = params.get("user_message", "")
|
||||
base_dir = params.get("base_dir", "")
|
||||
enable_tools = params.get("enable_tools", True)
|
||||
|
||||
try:
|
||||
if provider == "gemini":
|
||||
response = _send_gemini(md_content, user_message, base_dir, enable_tools)
|
||||
elif provider == "anthropic":
|
||||
response = _send_anthropic(md_content, user_message)
|
||||
elif provider == "deepseek":
|
||||
response = _send_deepseek(md_content, user_message)
|
||||
elif provider == "minimax":
|
||||
response = _send_minimax(md_content, user_message)
|
||||
elif provider == "gemini_cli":
|
||||
response = _send_gemini_cli(md_content, user_message, base_dir)
|
||||
else:
|
||||
response = f"ERROR: Unknown provider {provider}"
|
||||
|
||||
return {"id": cmd_id, "result": {"response": response, "provider": provider}}
|
||||
except Exception as e:
|
||||
return {"id": cmd_id, "error": str(e)}
|
||||
|
||||
def _send_gemini(md_content: str, user_message: str, base_dir: str, enable_tools: bool) -> str:
|
||||
client = _ensure_google_genai().Client(api_key=_load_credentials()["gemini"]["api_key"])
|
||||
model = _session_state.get("model", "gemini-2.5-flash-lite")
|
||||
|
||||
system_instruction = f"{_session_state.get('custom_system_prompt', '')}\n\n<context>\n{md_content}\n</context>"
|
||||
|
||||
config = {
|
||||
"temperature": _session_state.get("temperature", 0.0),
|
||||
"top_p": _session_state.get("top_p", 1.0),
|
||||
"max_output_tokens": _session_state.get("max_tokens", 8192),
|
||||
}
|
||||
|
||||
response = client.models.generate_content(model=model, contents=user_message, config=config)
|
||||
return response.text
|
||||
|
||||
def _send_anthropic(md_content: str, user_message: str) -> str:
|
||||
client = _ensure_anthropic().Anthropic(api_key=_load_credentials()["anthropic"]["api_key"])
|
||||
|
||||
response = client.messages.create(
|
||||
model=_session_state.get("model", "claude-sonnet-4-20250514"),
|
||||
max_tokens=_session_state.get("max_tokens", 8192),
|
||||
system=f"{_session_state.get('custom_system_prompt', '')}\n\n<context>\n{md_content}\n</context>",
|
||||
messages=[{"role": "user", "content": user_message}]
|
||||
)
|
||||
return response.content[0].text
|
||||
|
||||
def _send_deepseek(md_content: str, user_message: str) -> str:
|
||||
from openai import OpenAI
|
||||
global _deepseek_client
|
||||
if _deepseek_client is None:
|
||||
_deepseek_client = OpenAI(api_key=_load_credentials()["deepseek"]["api_key"], base_url="https://api.deepseek.com")
|
||||
|
||||
response = _deepseek_client.chat.completions.create(
|
||||
model=_session_state.get("model", "deepseek-chat"),
|
||||
messages=[{"role": "system", "content": f"{_session_state.get('custom_system_prompt', '')}\n\n<context>\n{md_content}\n</context>"}, {"role": "user", "content": user_message}]
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
|
||||
def _send_minimax(md_content: str, user_message: str) -> str:
|
||||
from openai import OpenAI
|
||||
global _minimax_client
|
||||
if _minimax_client is None:
|
||||
creds = _load_credentials()
|
||||
_minimax_client = OpenAI(api_key=creds["minimax"]["api_key"], base_url="https://api.minimax.io/v1")
|
||||
|
||||
response = _minimax_client.chat.completions.create(
|
||||
model=_session_state.get("model", "MiniMax-M2.5"),
|
||||
messages=[{"role": "system", "content": f"{_session_state.get('custom_system_prompt', '')}\n\n<context>\n{md_content}\n</context>"}, {"role": "user", "content": user_message}]
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
|
||||
def _send_gemini_cli(md_content: str, user_message: str, base_dir: str) -> str:
|
||||
return f"[gemini_cli] {user_message[:50]}..."
|
||||
|
||||
def main():
|
||||
print(json.dumps({"type": "ready"}))
|
||||
sys.stdout.flush()
|
||||
print(json.dumps({"type": "ready"}), flush=True)
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
@@ -71,15 +249,11 @@ def main():
|
||||
try:
|
||||
cmd = json.loads(line)
|
||||
response = handle_command(cmd)
|
||||
print(json.dumps(response))
|
||||
sys.stdout.flush()
|
||||
print(json.dumps(response), flush=True)
|
||||
except json.JSONDecodeError as e:
|
||||
print(json.dumps({"error": f"Invalid JSON: {e}"}))
|
||||
sys.stdout.flush()
|
||||
print(json.dumps({"error": f"Invalid JSON: {e}"}), flush=True)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.stdout.flush()
|
||||
|
||||
print(json.dumps({"error": str(e)}), flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -18,7 +18,7 @@ from pathlib import Path
|
||||
from pydantic import BaseModel
|
||||
from typing import Any, List, Dict, Optional, Callable
|
||||
from src import aggregate
|
||||
from src import ai_client
|
||||
from src import ai_client_stub as ai_client
|
||||
from src import conductor_tech_lead
|
||||
from src import events
|
||||
from src import mcp_client
|
||||
@@ -1618,7 +1618,7 @@ class AppController:
|
||||
Stops background threads and cleans up resources.
|
||||
[C: src/gui_2.py:App.run, src/gui_2.py:App.shutdown, tests/conftest.py:app_instance, tests/conftest.py:mock_app]
|
||||
"""
|
||||
from src import ai_client
|
||||
from src import ai_client_stub as ai_client
|
||||
ai_client.cleanup()
|
||||
if hasattr(self, 'hook_server') and self.hook_server:
|
||||
self.hook_server.stop()
|
||||
@@ -3002,7 +3002,7 @@ class AppController:
|
||||
self._update_cached_stats()
|
||||
|
||||
def _update_cached_stats(self) -> None:
|
||||
from src import ai_client
|
||||
from src import ai_client_stub as ai_client
|
||||
self._cached_cache_stats = ai_client.get_gemini_cache_stats()
|
||||
self._cached_tool_stats = dict(self._tool_stats)
|
||||
|
||||
@@ -3010,7 +3010,7 @@ class AppController:
|
||||
"""
|
||||
[C: src/gui_2.py:App._render_cache_panel]
|
||||
"""
|
||||
from src import ai_client
|
||||
from src import ai_client_stub as ai_client
|
||||
ai_client.cleanup()
|
||||
self._update_cached_stats()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user