wip better file support and word wrap
This commit is contained in:
77
ai_client.py
77
ai_client.py
@@ -39,7 +39,12 @@ _ANTHROPIC_SYSTEM = (
|
||||
"When writing or rewriting large files (especially those containing quotes, backticks, or special characters), "
|
||||
"avoid python -c with inline strings. Instead: (1) write a .py helper script to disk using a PS here-string "
|
||||
"(@'...'@ for literal content), (2) run it with `python <script>`, (3) delete the helper. "
|
||||
"For small targeted edits, use PowerShell's (Get-Content) / .Replace() / Set-Content or Add-Content directly."
|
||||
"For small targeted edits, use PowerShell's (Get-Content) / .Replace() / Set-Content or Add-Content directly.\n\n"
|
||||
"When making function calls using tools that accept array or object parameters "
|
||||
"ensure those are structured using JSON. For example:\n"
|
||||
"When you need to verify a change, rely on the exit code and stdout/stderr from the tool \u2014 "
|
||||
"the user's context files are automatically refreshed after every tool call, so you do NOT "
|
||||
"need to re-read files that are already provided in the <context> block."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ comms log
|
||||
@@ -281,6 +286,45 @@ def _run_script(script: str, base_dir: str) -> str:
|
||||
return output
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ dynamic file context refresh
|
||||
|
||||
def _reread_file_items(file_items: list[dict]) -> list[dict]:
|
||||
"""
|
||||
Re-read every file in file_items from disk, returning a fresh list.
|
||||
This is called after tool calls so the AI sees updated file contents.
|
||||
"""
|
||||
refreshed = []
|
||||
for item in file_items:
|
||||
path = item.get("path")
|
||||
if path is None:
|
||||
refreshed.append(item)
|
||||
continue
|
||||
from pathlib import Path as _P
|
||||
p = _P(path) if not isinstance(path, _P) else path
|
||||
try:
|
||||
content = p.read_text(encoding="utf-8")
|
||||
refreshed.append({**item, "content": content, "error": False})
|
||||
except Exception as e:
|
||||
refreshed.append({**item, "content": f"ERROR re-reading {p}: {e}", "error": True})
|
||||
return refreshed
|
||||
|
||||
|
||||
def _build_file_context_text(file_items: list[dict]) -> str:
|
||||
"""
|
||||
Build a compact text summary of all files from file_items, suitable for
|
||||
injecting into a tool_result message so the AI sees current file contents.
|
||||
"""
|
||||
if not file_items:
|
||||
return ""
|
||||
parts = []
|
||||
for item in file_items:
|
||||
path = item.get("path") or item.get("entry", "unknown")
|
||||
suffix = str(path).rsplit(".", 1)[-1] if "." in str(path) else "text"
|
||||
content = item.get("content", "")
|
||||
parts.append(f"### `{path}`\n\n```{suffix}\n{content}\n```")
|
||||
return "\n\n---\n\n".join(parts)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ content block serialisation
|
||||
|
||||
def _content_block_to_dict(block) -> dict:
|
||||
@@ -314,7 +358,7 @@ def _ensure_gemini_client():
|
||||
_gemini_client = genai.Client(api_key=creds["gemini"]["api_key"])
|
||||
|
||||
|
||||
def _send_gemini(md_content: str, user_message: str, base_dir: str) -> str:
|
||||
def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items: list[dict] | None = None) -> str:
|
||||
global _gemini_chat
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
@@ -384,6 +428,10 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str) -> str:
|
||||
if not function_responses:
|
||||
break
|
||||
|
||||
# Refresh file context after tool calls
|
||||
if file_items:
|
||||
file_items = _reread_file_items(file_items)
|
||||
|
||||
response = _gemini_chat.send_message(function_responses)
|
||||
|
||||
text_parts = [
|
||||
@@ -476,7 +524,7 @@ def _repair_anthropic_history(history: list[dict]):
|
||||
})
|
||||
|
||||
|
||||
def _send_anthropic(md_content: str, user_message: str, base_dir: str) -> str:
|
||||
def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_items: list[dict] | None = None) -> str:
|
||||
try:
|
||||
_ensure_anthropic_client()
|
||||
|
||||
@@ -574,6 +622,20 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str) -> str:
|
||||
if not tool_results:
|
||||
break
|
||||
|
||||
# Refresh file context after tool calls and inject into tool result message
|
||||
if file_items:
|
||||
file_items = _reread_file_items(file_items)
|
||||
refreshed_ctx = _build_file_context_text(file_items)
|
||||
if refreshed_ctx:
|
||||
tool_results.append({
|
||||
"type": "text",
|
||||
"text": (
|
||||
"[FILES UPDATED — current contents below. "
|
||||
"Do NOT re-read these files with PowerShell.]\n\n"
|
||||
+ refreshed_ctx
|
||||
),
|
||||
})
|
||||
|
||||
_anthropic_history.append({
|
||||
"role": "user",
|
||||
"content": tool_results,
|
||||
@@ -582,7 +644,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str) -> str:
|
||||
_append_comms("OUT", "tool_result_send", {
|
||||
"results": [
|
||||
{"tool_use_id": r["tool_use_id"], "content": r["content"]}
|
||||
for r in tool_results
|
||||
for r in tool_results if r.get("type") == "tool_result"
|
||||
],
|
||||
})
|
||||
|
||||
@@ -605,6 +667,7 @@ def send(
|
||||
md_content: str,
|
||||
user_message: str,
|
||||
base_dir: str = ".",
|
||||
file_items: list[dict] | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Send a message to the active provider.
|
||||
@@ -612,9 +675,11 @@ def send(
|
||||
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(md_content, user_message, base_dir)
|
||||
return _send_gemini(md_content, user_message, base_dir, file_items)
|
||||
elif _provider == "anthropic":
|
||||
return _send_anthropic(md_content, user_message, base_dir)
|
||||
return _send_anthropic(md_content, user_message, base_dir, file_items)
|
||||
raise ValueError(f"unknown provider: {_provider}")
|
||||
|
||||
Reference in New Issue
Block a user