feat(ai_client): Emit API lifecycle and tool execution events

This commit is contained in:
2026-02-23 16:31:48 -05:00
parent c44026c06c
commit 20ebab55a0
2 changed files with 106 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ from pathlib import Path
import file_cache
import mcp_client
import google.genai
from google.genai import types
from events import EventEmitter
_provider: str = "gemini"
@@ -620,6 +621,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items:
r["output"] = val
for r_idx in range(MAX_TOOL_ROUNDS + 2):
events.emit("request_start", payload={"provider": "gemini", "model": _model, "round": r_idx})
resp = _gemini_chat.send_message(payload)
txt = "\n".join(p.text for c in resp.candidates if getattr(c, "content", None) for p in c.content.parts if hasattr(p, "text") and p.text)
if txt: all_text.append(txt)
@@ -629,6 +631,8 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items:
cached_tokens = getattr(resp.usage_metadata, "cached_content_token_count", None)
if cached_tokens:
usage["cache_read_input_tokens"] = cached_tokens
events.emit("response_received", payload={"provider": "gemini", "model": _model, "usage": usage, "round": r_idx})
reason = resp.candidates[0].finish_reason.name if resp.candidates and hasattr(resp.candidates[0], "finish_reason") else "STOP"
_append_comms("IN", "response", {"round": r_idx, "stop_reason": reason, "text": txt, "tool_calls": [{"name": c.name, "args": dict(c.args)} for c in calls], "usage": usage})
@@ -662,6 +666,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items:
f_resps, log = [], []
for i, fc in enumerate(calls):
name, args = fc.name, dict(fc.args)
events.emit("tool_execution", payload={"status": "started", "tool": name, "args": args, "round": r_idx})
if name in mcp_client.TOOL_NAMES:
_append_comms("OUT", "tool_call", {"name": name, "args": args})
out = mcp_client.dispatch(name, args)
@@ -681,6 +686,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items:
f_resps.append(types.Part.from_function_response(name=name, response={"output": out}))
log.append({"tool_use_id": name, "content": out})
events.emit("tool_execution", payload={"status": "completed", "tool": name, "result": out, "round": r_idx})
_append_comms("OUT", "tool_result_send", {"results": log})
payload = f_resps
@@ -998,6 +1004,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
def _strip_private_keys(history):
return [{k: v for k, v in m.items() if not k.startswith("_")} for m in history]
events.emit("request_start", payload={"provider": "anthropic", "model": _model, "round": round_idx})
response = _anthropic_client.messages.create(
model=_model,
max_tokens=_max_tokens,
@@ -1036,6 +1043,8 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
if cache_read is not None:
usage_dict["cache_read_input_tokens"] = cache_read
events.emit("response_received", payload={"provider": "anthropic", "model": _model, "usage": usage_dict, "round": round_idx})
_append_comms("IN", "response", {
"round": round_idx,
"stop_reason": response.stop_reason,
@@ -1059,6 +1068,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
b_name = getattr(block, "name", None)
b_id = getattr(block, "id", "")
b_input = getattr(block, "input", {})
events.emit("tool_execution", payload={"status": "started", "tool": b_name, "args": b_input, "round": round_idx})
if b_name in mcp_client.TOOL_NAMES:
_append_comms("OUT", "tool_call", {"name": b_name, "id": b_id, "args": b_input})
output = mcp_client.dispatch(b_name, b_input)
@@ -1068,6 +1078,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
"tool_use_id": b_id,
"content": output,
})
events.emit("tool_execution", payload={"status": "completed", "tool": b_name, "result": output, "round": round_idx})
elif b_name == TOOL_NAME:
script = b_input.get("script", "")
_append_comms("OUT", "tool_call", {
@@ -1086,6 +1097,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
"tool_use_id": b_id,
"content": output,
})
events.emit("tool_execution", payload={"status": "completed", "tool": b_name, "result": output, "round": round_idx})
# Refresh file context after tool calls — only inject CHANGED files
if file_items: