Skip to content

Commit 4f1f440

Browse files
anticomputerCopilot
andcommitted
fix(anthropic_sdk): preserve empty tool output + harden token test
Two more reviewer-flagged issues: 1. _call_tool_result_to_text() dropped empty TextContent The truthy check 'if text:' treated TextContent(text='') the same as text=None and skipped it. With an only-empty content list, parts would be [] and the helper fell through to the str(result) fallback (which is a noisy repr of the result object) instead of returning the actual empty result the tool reported. Fix: 'if text is not None:' preserves explicit empty strings; the str(result) fallback now only fires when there are no text-bearing blocks at all. 2. test_build_raises_bad_request_when_no_token_available was flaky The test cleared a long list of API key env vars (defensive cargo cult) but missed COPILOT_TOKEN, which is the second variable that capi.get_AI_token() consults. On runners with COPILOT_TOKEN set (e.g. CI envs authed to Copilot), the test would unexpectedly find a token and the assertion would fail. Simplified to clear only the two vars the chain actually consults: AI_API_TOKEN and COPILOT_TOKEN. +2 regression tests for empty-string preservation; 291 passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5e0a38c commit 4f1f440

2 files changed

Lines changed: 36 additions & 6 deletions

File tree

src/seclab_taskflow_agent/sdk/anthropic_sdk/backend.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,18 @@ def _mcp_tools_to_anthropic(tools: list[Any]) -> list[dict[str, Any]]:
5454

5555

5656
def _call_tool_result_to_text(result: Any) -> str:
57-
"""Extract text from an MCP CallToolResult."""
57+
"""Extract text from an MCP CallToolResult.
58+
59+
Preserves empty strings: a tool that returns ``TextContent(text="")``
60+
is returning an explicit empty result, not "no content". Only fall
61+
back to ``str(result)`` (a noisy repr) when there are genuinely no
62+
text-bearing content blocks at all.
63+
"""
5864
content = getattr(result, "content", [])
5965
parts = []
6066
for c in content:
6167
text = getattr(c, "text", None)
62-
if text:
68+
if text is not None:
6369
parts.append(text)
6470
return "\n".join(parts) if parts else str(result)
6571

tests/test_sdk_anthropic_adapter.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,25 @@ def test_call_tool_result_to_text_empty():
193193
assert isinstance(text, str)
194194

195195

196+
def test_call_tool_result_to_text_preserves_empty_string():
197+
"""A tool returning TextContent(text='') is reporting an explicit
198+
empty result. The helper must return '' verbatim, not fall back to
199+
str(result) (which is a noisy repr of the result object).
200+
201+
Regression for the truthy-check bug: ``if text:`` was treating ''
202+
the same as None and dropping it, causing the empty content list
203+
branch to fire and emit ``str(result)`` to the model.
204+
"""
205+
result = type("R", (), {"content": [_FakeContent("")]})()
206+
assert _call_tool_result_to_text(result) == ""
207+
208+
209+
def test_call_tool_result_to_text_preserves_empty_among_nonempty():
210+
"""Empty TextContent should join with neighbors as ''."""
211+
result = type("R", (), {"content": [_FakeContent("a"), _FakeContent(""), _FakeContent("b")]})()
212+
assert _call_tool_result_to_text(result) == "a\n\nb"
213+
214+
196215
# -- bearer_auth via provider registry --
197216

198217

@@ -480,13 +499,18 @@ def test_build_raises_bad_request_when_no_token_available(monkeypatch):
480499
Otherwise the Anthropic client gets created with an empty 'Bearer '
481500
header and the failure surfaces later as an opaque 401 mid-stream
482501
instead of a clear BackendBadRequestError at build time.
502+
503+
Clears every variable consulted by ``capi.get_AI_token``
504+
(``AI_API_TOKEN`` then ``COPILOT_TOKEN``) to keep the test
505+
deterministic regardless of the runner's ambient environment.
483506
"""
484507
import asyncio
485508

486-
# Clear every token-source env var the standard chain consults
487-
for var in ("AI_API_TOKEN", "OPENAI_API_KEY", "AZURE_OPENAI_API_KEY",
488-
"ANTHROPIC_API_KEY", "GITHUB_TOKEN", "GH_TOKEN"):
489-
monkeypatch.delenv(var, raising=False)
509+
# Must clear *every* env var the token chain consults; missing
510+
# COPILOT_TOKEN here would make the test flaky on runners that
511+
# happen to have it set (e.g. CI machines authed to copilot).
512+
monkeypatch.delenv("AI_API_TOKEN", raising=False)
513+
monkeypatch.delenv("COPILOT_TOKEN", raising=False)
490514

491515
spec = AgentSpec(
492516
name="t",

0 commit comments

Comments
 (0)