Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion src/band/runtime/tools/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
),
}


Expand Down
28 changes: 27 additions & 1 deletion tests/adapters/copilot_sdk/test_tool_bridging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>",
}
Loading