wip better file support and word wrap

This commit is contained in:
2026-02-21 21:16:50 -05:00
parent d9a51332ef
commit 0a591d58eb
4 changed files with 105 additions and 12 deletions

View File

@@ -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}")