39
ai_client.py
39
ai_client.py
@@ -465,16 +465,16 @@ def _get_gemini_history_list(chat):
|
||||
return chat.get_history()
|
||||
return []
|
||||
|
||||
def _send_gemini(static_md: str, dynamic_md: str, user_message: str, base_dir: str, file_items: list[dict] | None = None) -> str:
|
||||
def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items: list[dict] | None = None) -> str:
|
||||
global _gemini_chat, _gemini_cache
|
||||
from google.genai import types
|
||||
try:
|
||||
_ensure_gemini_client(); mcp_client.configure(file_items or [], [base_dir])
|
||||
sys_instr = f"{_get_combined_system_prompt()}\n\n<context>\n{static_md}\n</context>"
|
||||
sys_instr = f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"
|
||||
tools_decl = [_gemini_tool_declaration()]
|
||||
|
||||
# DYNAMIC CONTEXT: Check if files/context changed mid-session
|
||||
current_md_hash = hash(static_md)
|
||||
current_md_hash = hash(md_content)
|
||||
old_history = None
|
||||
if _gemini_chat and getattr(_gemini_chat, "_last_md_hash", None) != current_md_hash:
|
||||
old_history = list(_get_gemini_history_list(_gemini_chat)) if _get_gemini_history_list(_gemini_chat) else []
|
||||
@@ -520,20 +520,10 @@ def _send_gemini(static_md: str, dynamic_md: str, user_message: str, base_dir: s
|
||||
_gemini_chat = _gemini_client.chats.create(**kwargs)
|
||||
_gemini_chat._last_md_hash = current_md_hash
|
||||
|
||||
# Build user message: prepend dynamic context (discussion) so it's NOT cached in system_instruction
|
||||
full_user_msg = f"<discussion>\n{dynamic_md}\n</discussion>\n\n{user_message}" if dynamic_md.strip() else user_message
|
||||
_append_comms("OUT", "request", {"message": f"[ctx {len(static_md)} static + {len(dynamic_md)} dynamic + msg {len(user_message)}]"})
|
||||
payload, all_text = full_user_msg, []
|
||||
_append_comms("OUT", "request", {"message": f"[ctx {len(md_content)} + msg {len(user_message)}]"})
|
||||
payload, all_text = user_message, []
|
||||
|
||||
for r_idx in range(MAX_TOOL_ROUNDS + 2):
|
||||
# Strip stale <discussion> blocks from old user messages so they don't accumulate
|
||||
import re as _re
|
||||
if _gemini_chat and _get_gemini_history_list(_gemini_chat):
|
||||
for msg in _get_gemini_history_list(_gemini_chat):
|
||||
if msg.role == "user" and hasattr(msg, "parts"):
|
||||
for p in msg.parts:
|
||||
if hasattr(p, "text") and p.text and "<discussion>" in p.text:
|
||||
p.text = _re.sub(r"<discussion>.*?</discussion>\n\n", "", p.text, flags=_re.DOTALL)
|
||||
# Strip stale file refreshes and truncate old tool outputs in Gemini history
|
||||
if _gemini_chat and _get_gemini_history_list(_gemini_chat):
|
||||
for msg in _get_gemini_history_list(_gemini_chat):
|
||||
@@ -828,16 +818,13 @@ def _repair_anthropic_history(history: list[dict]):
|
||||
})
|
||||
|
||||
|
||||
def _send_anthropic(static_md: str, dynamic_md: str, user_message: str, base_dir: str, file_items: list[dict] | None = None) -> str:
|
||||
def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_items: list[dict] | None = None) -> str:
|
||||
try:
|
||||
_ensure_anthropic_client()
|
||||
mcp_client.configure(file_items or [], [base_dir])
|
||||
|
||||
system_text = _get_combined_system_prompt() + f"\n\n<context>\n{static_md}\n</context>"
|
||||
system_text = _get_combined_system_prompt() + f"\n\n<context>\n{md_content}\n</context>"
|
||||
system_blocks = _build_chunked_context_blocks(system_text)
|
||||
# Dynamic context (discussion history) goes after the cached static prefix, without cache_control
|
||||
if dynamic_md.strip():
|
||||
system_blocks.append({"type": "text", "text": f"<discussion>\n{dynamic_md}\n</discussion>"})
|
||||
|
||||
user_content = [{"type": "text", "text": user_message}]
|
||||
|
||||
@@ -857,7 +844,7 @@ def _send_anthropic(static_md: str, dynamic_md: str, user_message: str, base_dir
|
||||
n_chunks = len(system_blocks)
|
||||
_append_comms("OUT", "request", {
|
||||
"message": (
|
||||
f"[system {n_chunks} chunk(s), {len(static_md)} static + {len(dynamic_md)} dynamic chars] "
|
||||
f"[system {n_chunks} chunk(s), {len(md_content)} chars context] "
|
||||
f"{user_message[:200]}{'...' if len(user_message) > 200 else ''}"
|
||||
),
|
||||
})
|
||||
@@ -1010,8 +997,7 @@ def _send_anthropic(static_md: str, dynamic_md: str, user_message: str, base_dir
|
||||
# ------------------------------------------------------------------ unified send
|
||||
|
||||
def send(
|
||||
static_md: str,
|
||||
dynamic_md: str,
|
||||
md_content: str,
|
||||
user_message: str,
|
||||
base_dir: str = ".",
|
||||
file_items: list[dict] | None = None,
|
||||
@@ -1019,15 +1005,14 @@ def send(
|
||||
"""
|
||||
Send a message to the active provider.
|
||||
|
||||
static_md : cacheable context (files + screenshots) from aggregate.run()
|
||||
dynamic_md : volatile context (discussion history) that changes every turn
|
||||
md_content : aggregated markdown string from aggregate.run()
|
||||
user_message: the user question / instruction
|
||||
base_dir : project base directory (for PowerShell tool calls)
|
||||
file_items : list of file dicts from aggregate.build_file_items() for
|
||||
dynamic context refresh after tool calls
|
||||
"""
|
||||
if _provider == "gemini":
|
||||
return _send_gemini(static_md, dynamic_md, user_message, base_dir, file_items)
|
||||
return _send_gemini(md_content, user_message, base_dir, file_items)
|
||||
elif _provider == "anthropic":
|
||||
return _send_anthropic(static_md, dynamic_md, user_message, base_dir, file_items)
|
||||
return _send_anthropic(md_content, user_message, base_dir, file_items)
|
||||
raise ValueError(f"unknown provider: {_provider}")
|
||||
Reference in New Issue
Block a user