diff --git a/src/band/runtime/tools/registry.py b/src/band/runtime/tools/registry.py index 28dc3df16..dcae0f26a 100644 --- a/src/band/runtime/tools/registry.py +++ b/src/band/runtime/tools/registry.py @@ -906,7 +906,9 @@ def redact_tool_call_args(tool_name: str, args: dict[str, Any]) -> dict[str, Any return args return { **args, - "content": file_content_placeholder(len(content.encode("utf-8"))), + "content": file_content_placeholder( + len(content.encode("utf-8", "surrogatepass")) + ), } diff --git a/tests/adapters/copilot_sdk/test_tool_bridging.py b/tests/adapters/copilot_sdk/test_tool_bridging.py index 4646d24ec..9f84d48b5 100644 --- a/tests/adapters/copilot_sdk/test_tool_bridging.py +++ b/tests/adapters/copilot_sdk/test_tool_bridging.py @@ -10,9 +10,10 @@ from pydantic import BaseModel, model_validator from band.adapters.copilot_sdk import _COPILOT_SDK_AVAILABLE -from band.core.types import Emit +from band.core.types import Emit, MessageType from band.runtime.custom_tools import declares_turn_effect from band.runtime.tools import ToolCallOutcome, TurnEffect +from band.testing.fake_tools import events_of_type from tests.adapters.copilot_sdk.fakes import ( FakeCopilotClient, FakeCopilotSession, @@ -419,3 +420,28 @@ async def test_tool_call_event_redacts_file_content(self): == f"<{len(raw_content.encode('utf-8'))} byte file content>" ) assert raw_content not in call_event["content"] + + @pytest.mark.asyncio + async def test_lone_surrogate_file_content_is_redacted_without_losing_event( + self, + ) -> None: + client = FakeCopilotClient() + adapter = await make_started_adapter(client, emit=Emit.TOOL_CALLS) + tools = _SendRoomFileFakeTools() + await run_message(adapter, tools) + handler = client.sessions[0].find_tool("band_send_room_file").handler + await handler( + ToolInvocation( + tool_call_id="surrogate-file", + tool_name="band_send_room_file", + arguments={"content": "\ud800", "filename": "notes.txt"}, + ) + ) + (call_event,) = events_of_type(tools, MessageType.TOOL_CALL) + call = json.loads(call_event["content"]) + assert call["tool_call_id"] == "surrogate-file" + assert call["name"] == "band_send_room_file" + assert call["args"] == { + "filename": "notes.txt", + "content": "<3 byte file content>", + }