Skip to content

Commit 7ffc216

Browse files
committed
fix(agent): make a binary @file: reference actionable instead of a dead end
A binary @file: ref (PDF, docx, spreadsheet, …) expanded to a bare "binary files are not supported" warning with no content. The model saw a failure and gave up — e.g. a dropped PDF came back as a text note claiming the type was unsupported, even though the file was staged on disk right next to it. Inject an actionable content block instead: the path, mime type, size, and a nudge to use its tools to read/convert/view the file (and explicitly not to tell the user the type is unsupported). General across every binary type — not PDF-specific. The file already resolves where the agent's tools run (local cwd or the staged copy in a remote session workspace), so it can act on it directly.
1 parent 29147af commit 7ffc216

2 files changed

Lines changed: 55 additions & 5 deletions

File tree

agent/context_references.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,14 @@ def _expand_file_reference(
246246
if not path.is_file():
247247
return f"{ref.raw}: path is not a file", None
248248
if _is_binary_file(path):
249-
return f"{ref.raw}: binary files are not supported", None
249+
# A binary file can't be inlined as text, but it IS on disk (the agent's
250+
# tools run where this resolves — the local cwd, or the staged copy in a
251+
# remote session workspace). Returning a bare "not supported" warning
252+
# with no content was a dead end: the model saw a failure and gave up
253+
# (told the user the file type wasn't supported). Instead, hand it an
254+
# actionable block — the path, type, size, and a nudge to use its tools —
255+
# so it can read/convert/view the file itself.
256+
return None, _binary_reference_block(ref, path)
250257

251258
text = path.read_text(encoding="utf-8")
252259
if ref.line_start is not None:
@@ -493,6 +500,30 @@ def _rg_files(path: Path, cwd: Path, limit: int) -> list[Path] | None:
493500
return files[:limit]
494501

495502

503+
def _human_bytes(n: int) -> str:
504+
size = float(n)
505+
for unit in ("B", "KB", "MB", "GB"):
506+
if size < 1024 or unit == "GB":
507+
return f"{int(size)} {unit}" if unit == "B" else f"{size:.1f} {unit}"
508+
size /= 1024
509+
return f"{size:.1f} GB"
510+
511+
512+
def _binary_reference_block(ref: ContextReference, path: Path) -> str:
513+
mime, _ = mimetypes.guess_type(path.name)
514+
mime = mime or "application/octet-stream"
515+
try:
516+
size = _human_bytes(path.stat().st_size)
517+
except OSError:
518+
size = "unknown size"
519+
return (
520+
f"📎 {ref.raw} ({mime}, {size}) — binary file, not inlined as text. "
521+
f"It is available on disk at `{path}`. Use your tools to work with it "
522+
f"(read or convert it, extract its text, or view/render it as needed); "
523+
f"do not tell the user the file type is unsupported."
524+
)
525+
526+
496527
def _file_metadata(path: Path) -> str:
497528
if _is_binary_file(path):
498529
return f"{path.stat().st_size} bytes"

tests/agent/test_context_references.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,21 +192,40 @@ def test_expand_git_diff_staged_and_log(sample_repo: Path):
192192
assert "VALUE = 2" in result.message
193193

194194

195-
def test_binary_and_missing_files_become_warnings(sample_repo: Path):
195+
def test_missing_file_becomes_warning(sample_repo: Path):
196196
from agent.context_references import preprocess_context_references
197197

198198
result = preprocess_context_references(
199-
"Check @file:blob.bin and @file:nope.txt",
199+
"Check @file:nope.txt",
200200
cwd=sample_repo,
201201
context_length=100_000,
202202
)
203203

204204
assert result.expanded
205-
assert len(result.warnings) == 2
206-
assert "binary" in result.message.lower()
205+
assert len(result.warnings) == 1
207206
assert "not found" in result.message.lower()
208207

209208

209+
def test_binary_file_yields_actionable_block_not_a_dead_warning(sample_repo: Path):
210+
from agent.context_references import preprocess_context_references
211+
212+
result = preprocess_context_references(
213+
"Check @file:blob.bin",
214+
cwd=sample_repo,
215+
context_length=100_000,
216+
)
217+
218+
assert result.expanded
219+
# The whole point: a binary attachment must NOT degrade into a discouraging
220+
# warning that makes the model give up — it gets an actionable content block.
221+
assert not result.warnings
222+
assert "blob.bin" in result.message
223+
assert "binary" in result.message.lower()
224+
assert "not supported" not in result.message.lower()
225+
# And it must point the agent at the file so it can act on it with tools.
226+
assert str(sample_repo / "blob.bin") in result.message
227+
228+
210229
def test_soft_budget_warns_and_hard_budget_refuses(sample_repo: Path):
211230
from agent.context_references import preprocess_context_references
212231

0 commit comments

Comments
 (0)