Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
528b7c9
fix(plugins): record error-bearing tool results as TOOL_ERROR
cursoragent Sep 14, 2026
cda4961
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 21, 2026
e800481
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 21, 2026
390c73b
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 22, 2026
fc4b6f5
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 22, 2026
b8fbae5
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 22, 2026
0ffe578
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 22, 2026
37f4317
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 22, 2026
dcd6cc2
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 22, 2026
3351c69
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 23, 2026
f5d542b
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 23, 2026
655d711
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 23, 2026
b80dd9d
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 23, 2026
68faa1d
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 23, 2026
47ffadb
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 23, 2026
652d9d0
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 24, 2026
d2f4ed0
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 24, 2026
1fbe288
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 24, 2026
cb14987
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 24, 2026
ac033f8
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 25, 2026
c3b9a3c
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 25, 2026
a7b5798
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 25, 2026
e1bc5ac
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 25, 2026
6802333
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 25, 2026
6e1dfb7
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 26, 2026
6167747
Merge branch 'main' into cursor/bqaa-tool-error-result-241d
Zsanz3 Sep 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/google/adk/plugins/bigquery_agent_analytics_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8234,13 +8234,29 @@ async def after_tool_callback(
span_id, duration = TraceManager.pop_span()
parent_span_id, _ = TraceManager.get_current_span_and_parent()

# MCP CallToolResult (isError) and plugin-rewritten failures
# (error_details) reach here as ordinary results. Classify them as
# TOOL_ERROR so status/error_message match on_tool_error_callback;
# otherwise audits treat them as successes.
error_message = None
if isinstance(result, dict):
if result.get("error_details"):
error_message = str(result["error_details"])
elif result.get("isError") or result.get("is_error"):
content = result.get("content")
first = content[0] if isinstance(content, list) and content else None
text = first.get("text") if isinstance(first, dict) else None
error_message = str(text) if text else "Tool returned an error"

event_data = EventData(
latency_ms=duration,
span_id_override=span_id,
parent_span_id_override=parent_span_id,
status="ERROR" if error_message is not None else "OK",
error_message=error_message,
)
await self._log_event(
"TOOL_COMPLETED",
"TOOL_ERROR" if error_message is not None else "TOOL_COMPLETED",
tool_context,
raw_content=content_dict,
is_truncated=is_truncated,
Expand All @@ -8254,7 +8270,10 @@ async def after_tool_callback(
# args (the final-answer payload the model supplied) as AGENT_RESPONSE so
# the visible response text is captured. Opt-in via
# ``config.final_response_tool_names`` (empty by default).
if tool.name in self.config.final_response_tool_names:
if (
error_message is None
and tool.name in self.config.final_response_tool_names
):
args_truncated, args_is_truncated = _recursive_smart_truncate(
tool_args, self.config.max_content_length
)
Expand Down
46 changes: 46 additions & 0 deletions tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2120,6 +2120,52 @@ async def test_after_tool_callback_logs_correctly(
assert content_dict["tool"] == "MyTool"
assert content_dict["result"] == {"res": "success"}

@pytest.mark.asyncio
@pytest.mark.parametrize(
("result", "expected_error_message"),
[
(
{
"content": [
{"type": "text", "text": "Git reset to remote failed."}
],
"isError": True,
},
"Git reset to remote failed.",
),
(
{"error_details": "MCP request failed with code 403"},
"MCP request failed with code 403",
),
],
)
async def test_after_tool_callback_logs_error_bearing_result_as_tool_error(
self, bq_plugin_inst, tool_context, result, expected_error_message
):
"""Error-bearing tool results are recorded as TOOL_ERROR, not TOOL_COMPLETED."""
mock_tool = mock.create_autospec(
base_tool_lib.BaseTool, instance=True, spec_set=True
)
type(mock_tool).name = mock.PropertyMock(return_value="MyTool")
type(mock_tool).description = mock.PropertyMock(return_value="Description")
log_event = mock.AsyncMock()
bq_plugin_inst._log_event = log_event
bigquery_agent_analytics_plugin.TraceManager.push_span(tool_context)

await bq_plugin_inst.after_tool_callback(
tool=mock_tool,
tool_args={"arg1": "val1"},
tool_context=tool_context,
result=result,
)

log_event.assert_awaited_once()
assert log_event.await_args.args[0] == "TOOL_ERROR"
event_data = log_event.await_args.kwargs["event_data"]
assert event_data.status == "ERROR"
assert event_data.error_message == expected_error_message
assert log_event.await_args.kwargs["raw_content"]["tool"] == "MyTool"

@pytest.mark.asyncio
async def test_after_tool_callback_no_state_delta_logging(
self, bq_plugin_inst, mock_write_client, tool_context, dummy_arrow_schema
Expand Down
Loading