refactor(ai_client): Add strict type hints to global variables
This commit is contained in:
54
ai_client.py
54
ai_client.py
@@ -40,7 +40,7 @@ _max_tokens: int = 8192
|
|||||||
_history_trunc_limit: int = 8000
|
_history_trunc_limit: int = 8000
|
||||||
|
|
||||||
# Global event emitter for API lifecycle events
|
# Global event emitter for API lifecycle events
|
||||||
events = EventEmitter()
|
events: EventEmitter = EventEmitter()
|
||||||
|
|
||||||
def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000) -> None:
|
def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000) -> None:
|
||||||
global _temperature, _max_tokens, _history_trunc_limit
|
global _temperature, _max_tokens, _history_trunc_limit
|
||||||
@@ -55,52 +55,52 @@ def set_history_trunc_limit(val: int) -> None:
|
|||||||
global _history_trunc_limit
|
global _history_trunc_limit
|
||||||
_history_trunc_limit = val
|
_history_trunc_limit = val
|
||||||
|
|
||||||
_gemini_client = None
|
_gemini_client: genai.Client | None = None
|
||||||
_gemini_chat = None
|
_gemini_chat: Any = None
|
||||||
_gemini_cache = None
|
_gemini_cache: Any = None
|
||||||
_gemini_cache_md_hash: int | None = None
|
_gemini_cache_md_hash: int | None = None
|
||||||
_gemini_cache_created_at: float | None = None
|
_gemini_cache_created_at: float | None = None
|
||||||
|
|
||||||
# Gemini cache TTL in seconds. Caches are created with this TTL and
|
# Gemini cache TTL in seconds. Caches are created with this TTL and
|
||||||
# proactively rebuilt at 90% of this value to avoid stale-reference errors.
|
# proactively rebuilt at 90% of this value to avoid stale-reference errors.
|
||||||
_GEMINI_CACHE_TTL = 3600
|
_GEMINI_CACHE_TTL: int = 3600
|
||||||
|
|
||||||
_anthropic_client = None
|
_anthropic_client: anthropic.Anthropic | None = None
|
||||||
_anthropic_history: list[dict] = []
|
_anthropic_history: list[dict] = []
|
||||||
_anthropic_history_lock = threading.Lock()
|
_anthropic_history_lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
_deepseek_client = None
|
_deepseek_client: Any = None
|
||||||
_deepseek_history: list[dict] = []
|
_deepseek_history: list[dict] = []
|
||||||
_deepseek_history_lock = threading.Lock()
|
_deepseek_history_lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
_send_lock = threading.Lock()
|
_send_lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
_gemini_cli_adapter = None
|
_gemini_cli_adapter: GeminiCliAdapter | None = None
|
||||||
|
|
||||||
# Injected by gui.py - called when AI wants to run a command.
|
# Injected by gui.py - called when AI wants to run a command.
|
||||||
# Signature: (script: str, base_dir: str) -> str | None
|
# Signature: (script: str, base_dir: str) -> str | None
|
||||||
confirm_and_run_callback = None
|
confirm_and_run_callback: Callable[[str, str], str | None] | None = None
|
||||||
|
|
||||||
# Injected by gui.py - called whenever a comms entry is appended.
|
# Injected by gui.py - called whenever a comms entry is appended.
|
||||||
# Signature: (entry: dict) -> None
|
# Signature: (entry: dict) -> None
|
||||||
comms_log_callback = None
|
comms_log_callback: Callable[[dict[str, Any]], None] | None = None
|
||||||
|
|
||||||
# Injected by gui.py - called whenever a tool call completes.
|
# Injected by gui.py - called whenever a tool call completes.
|
||||||
# Signature: (script: str, result: str) -> None
|
# Signature: (script: str, result: str) -> None
|
||||||
tool_log_callback = None
|
tool_log_callback: Callable[[str, str], None] | None = None
|
||||||
|
|
||||||
# Increased to allow thorough code exploration before forcing a summary
|
# Increased to allow thorough code exploration before forcing a summary
|
||||||
MAX_TOOL_ROUNDS = 10
|
MAX_TOOL_ROUNDS: int = 10
|
||||||
|
|
||||||
# Maximum cumulative bytes of tool output allowed per send() call.
|
# Maximum cumulative bytes of tool output allowed per send() call.
|
||||||
# Prevents unbounded memory growth during long tool-calling loops.
|
# Prevents unbounded memory growth during long tool-calling loops.
|
||||||
_MAX_TOOL_OUTPUT_BYTES = 500_000
|
_MAX_TOOL_OUTPUT_BYTES: int = 500_000
|
||||||
|
|
||||||
# Maximum characters per text chunk sent to Anthropic.
|
# Maximum characters per text chunk sent to Anthropic.
|
||||||
# Kept well under the ~200k token API limit.
|
# Kept well under the ~200k token API limit.
|
||||||
_ANTHROPIC_CHUNK_SIZE = 120_000
|
_ANTHROPIC_CHUNK_SIZE: int = 120_000
|
||||||
|
|
||||||
_SYSTEM_PROMPT = (
|
_SYSTEM_PROMPT: str = (
|
||||||
"You are a helpful coding assistant with access to a PowerShell tool and MCP tools (file access: read_file, list_directory, search_files, get_file_summary, web access: web_search, fetch_url). "
|
"You are a helpful coding assistant with access to a PowerShell tool and MCP tools (file access: read_file, list_directory, search_files, get_file_summary, web access: web_search, fetch_url). "
|
||||||
"When calling file/directory tools, always use the 'path' parameter for the target path. "
|
"When calling file/directory tools, always use the 'path' parameter for the target path. "
|
||||||
"When asked to create or edit files, prefer targeted edits over full rewrites. "
|
"When asked to create or edit files, prefer targeted edits over full rewrites. "
|
||||||
@@ -130,7 +130,7 @@ def _get_combined_system_prompt() -> str:
|
|||||||
|
|
||||||
_comms_log: list[dict] = []
|
_comms_log: list[dict] = []
|
||||||
|
|
||||||
COMMS_CLAMP_CHARS = 300
|
COMMS_CLAMP_CHARS: int = 300
|
||||||
|
|
||||||
def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
|
def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
|
||||||
entry = {
|
entry = {
|
||||||
@@ -379,7 +379,7 @@ def _list_deepseek_models(api_key: str) -> list[str]:
|
|||||||
return ["deepseek-chat", "deepseek-reasoner", "deepseek-v3", "deepseek-r1"]
|
return ["deepseek-chat", "deepseek-reasoner", "deepseek-v3", "deepseek-r1"]
|
||||||
# ------------------------------------------------------------------ tool definition
|
# ------------------------------------------------------------------ tool definition
|
||||||
|
|
||||||
TOOL_NAME = "run_powershell"
|
TOOL_NAME: str = "run_powershell"
|
||||||
|
|
||||||
_agent_tools: dict = {}
|
_agent_tools: dict = {}
|
||||||
|
|
||||||
@@ -427,9 +427,9 @@ def _build_anthropic_tools() -> list[dict]:
|
|||||||
tools_list[-1]["cache_control"] = {"type": "ephemeral"}
|
tools_list[-1]["cache_control"] = {"type": "ephemeral"}
|
||||||
return tools_list
|
return tools_list
|
||||||
|
|
||||||
_ANTHROPIC_TOOLS = _build_anthropic_tools()
|
_ANTHROPIC_TOOLS: list[dict[str, Any]] = _build_anthropic_tools()
|
||||||
|
|
||||||
_CACHED_ANTHROPIC_TOOLS = None
|
_CACHED_ANTHROPIC_TOOLS: list[dict[str, Any]] | None = None
|
||||||
|
|
||||||
def _get_anthropic_tools() -> list[dict]:
|
def _get_anthropic_tools() -> list[dict]:
|
||||||
"""Return the Anthropic tools list, rebuilding only once per session."""
|
"""Return the Anthropic tools list, rebuilding only once per session."""
|
||||||
@@ -550,7 +550,7 @@ def _build_file_context_text(file_items: list[dict]) -> str:
|
|||||||
parts.append(f"### `{path}`\n\n```{suffix}\n{content}\n```")
|
parts.append(f"### `{path}`\n\n```{suffix}\n{content}\n```")
|
||||||
return "\n\n---\n\n".join(parts)
|
return "\n\n---\n\n".join(parts)
|
||||||
|
|
||||||
_DIFF_LINE_THRESHOLD = 200
|
_DIFF_LINE_THRESHOLD: int = 200
|
||||||
|
|
||||||
def _build_file_diff_text(changed_items: list[dict]) -> str:
|
def _build_file_diff_text(changed_items: list[dict]) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -933,18 +933,18 @@ def _send_gemini_cli(md_content: str, user_message: str, base_dir: str,
|
|||||||
# ------------------------------------------------------------------ anthropic history management
|
# ------------------------------------------------------------------ anthropic history management
|
||||||
# Rough chars-per-token ratio. Anthropic tokeniser averages ~3.5-4 chars/token.
|
# Rough chars-per-token ratio. Anthropic tokeniser averages ~3.5-4 chars/token.
|
||||||
# We use 3.5 to be conservative (overestimate token count = safer).
|
# We use 3.5 to be conservative (overestimate token count = safer).
|
||||||
_CHARS_PER_TOKEN = 3.5
|
_CHARS_PER_TOKEN: float = 3.5
|
||||||
|
|
||||||
# Maximum token budget for the entire prompt (system + tools + messages).
|
# Maximum token budget for the entire prompt (system + tools + messages).
|
||||||
# Anthropic's limit is 200k. We leave headroom for the response + tool schemas.
|
# Anthropic's limit is 200k. We leave headroom for the response + tool schemas.
|
||||||
_ANTHROPIC_MAX_PROMPT_TOKENS = 180_000
|
_ANTHROPIC_MAX_PROMPT_TOKENS: int = 180_000
|
||||||
|
|
||||||
# Gemini models have a 1M context window but we cap well below to leave headroom.
|
# Gemini models have a 1M context window but we cap well below to leave headroom.
|
||||||
# If the model reports input tokens exceeding this, we trim old history.
|
# If the model reports input tokens exceeding this, we trim old history.
|
||||||
_GEMINI_MAX_INPUT_TOKENS = 900_000
|
_GEMINI_MAX_INPUT_TOKENS: int = 900_000
|
||||||
|
|
||||||
# Marker prefix used to identify stale file-refresh injections in history
|
# Marker prefix used to identify stale file-refresh injections in history
|
||||||
_FILE_REFRESH_MARKER = "[FILES UPDATED"
|
_FILE_REFRESH_MARKER: str = "[FILES UPDATED"
|
||||||
|
|
||||||
def _estimate_message_tokens(msg: dict) -> int:
|
def _estimate_message_tokens(msg: dict) -> int:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user