Extract final result from agent messages. Mirrors finalizeAgentTool() from typescript/src/tools/AgentTool/agentToolUtils.ts. Chapter-10 / Chunk C / WI-2.4: token aggregation now goes through ``ProgressTracker``. Pre-WI-2.4 ``total_tokens`` was hard-coded to ``0`` (gap-analysis §2.2
(
agent_messages: list[Message],
agent_id: str,
metadata: dict[str, Any],
*,
progress: Any | None = None,
)
| 198 | |
| 199 | |
| 200 | def finalize_agent_tool( |
| 201 | agent_messages: list[Message], |
| 202 | agent_id: str, |
| 203 | metadata: dict[str, Any], |
| 204 | *, |
| 205 | progress: Any | None = None, |
| 206 | ) -> AgentToolResult: |
| 207 | """Extract final result from agent messages. |
| 208 | |
| 209 | Mirrors finalizeAgentTool() from typescript/src/tools/AgentTool/agentToolUtils.ts. |
| 210 | |
| 211 | Chapter-10 / Chunk C / WI-2.4: token aggregation now goes through |
| 212 | ``ProgressTracker``. Pre-WI-2.4 ``total_tokens`` was hard-coded to |
| 213 | ``0`` (gap-analysis §2.2 row); post-WI-2.4 callers pass the live |
| 214 | tracker via the ``progress`` keyword and we read the chapter-correct |
| 215 | ``latest_input_tokens + cumulative_output_tokens`` total off it. If |
| 216 | the caller can't supply a tracker (e.g. a sync agent that didn't |
| 217 | feed one during iteration), we fall back to recomputing from |
| 218 | ``message.usage`` so the behavior degrades gracefully rather than |
| 219 | silently returning zero. |
| 220 | """ |
| 221 | # Find the last assistant message |
| 222 | last_assistant: AssistantMessage | None = None |
| 223 | for msg in reversed(agent_messages): |
| 224 | if isinstance(msg, AssistantMessage): |
| 225 | last_assistant = msg |
| 226 | break |
| 227 | |
| 228 | if last_assistant is None: |
| 229 | raise ValueError("No assistant messages found") |
| 230 | |
| 231 | # Extract text content from the agent's response |
| 232 | content: list[dict[str, Any]] = [] |
| 233 | raw_content = last_assistant.content |
| 234 | if isinstance(raw_content, str): |
| 235 | if raw_content: |
| 236 | content = [{"type": "text", "text": raw_content}] |
| 237 | elif isinstance(raw_content, list): |
| 238 | for block in raw_content: |
| 239 | if hasattr(block, "type") and block.type == "text": |
| 240 | content.append({"type": "text", "text": block.text}) |
| 241 | |
| 242 | # If no text content in last message, search backwards |
| 243 | if not content: |
| 244 | for msg in reversed(agent_messages): |
| 245 | if isinstance(msg, AssistantMessage): |
| 246 | raw = msg.content |
| 247 | if isinstance(raw, list): |
| 248 | for block in raw: |
| 249 | if hasattr(block, "type") and block.type == "text": |
| 250 | content.append({"type": "text", "text": block.text}) |
| 251 | if content: |
| 252 | break |
| 253 | |
| 254 | total_tool_use_count = count_tool_uses(agent_messages) |
| 255 | start_time = metadata.get("start_time", time.time()) |
| 256 | duration_ms = int((time.time() - start_time) * 1000) |
| 257 | total_tokens = _resolve_total_tokens(progress, agent_messages) |