feat(agent): align the document tools with the harness-trained shape — ls, read(path), grep(pattern, path?)

This commit is contained in:
2026-09-03 11:17:47 -04:00
parent 16f1cfbcaf
commit 801639efcc
55 changed files with 4031 additions and 1466 deletions
+9 -8
View File
@@ -88,21 +88,22 @@ def test_tool_frame_serializes_exactly() -> None:
``{type: "tool", name: str, argument: str | null}`` — one per
model-requested document tool call, streamed ahead of the ``delta``
frames of the answer."""
frame = sse_event(ChatToolEvent(name="read_document", argument="S/p.md").model_dump())
assert frame == 'data: {"type": "tool", "name": "read_document", "argument": "S/p.md"}\n\n'
assert _payload(frame) == {"type": "tool", "name": "read_document", "argument": "S/p.md"}
frame = sse_event(ChatToolEvent(name="read", argument="S/p.md").model_dump())
assert frame == 'data: {"type": "tool", "name": "read", "argument": "S/p.md"}\n\n'
assert _payload(frame) == {"type": "tool", "name": "read", "argument": "S/p.md"}
def test_tool_frame_argument_is_null_for_parameterless_tools() -> None:
"""``list_documents`` takes no parameters, so its frame's ``argument``
serializes as JSON null (the client renders the name alone)."""
dumped = ChatToolEvent(name="list_documents").model_dump()
assert dumped == {"type": "tool", "name": "list_documents", "argument": None}
"""``ls`` (unscoped) carries no string argument, so its frame's
``argument`` serializes as JSON null (the client renders the name
alone)."""
dumped = ChatToolEvent(name="ls").model_dump()
assert dumped == {"type": "tool", "name": "ls", "argument": None}
assert _payload(sse_event(dumped))["argument"] is None
def test_tool_event_shape_is_type_name_argument_only() -> None:
dumped = ChatToolEvent(name="read_document", argument="S/p.md").model_dump()
dumped = ChatToolEvent(name="read", argument="S/p.md").model_dump()
assert set(dumped.keys()) == {"type", "name", "argument"}
assert dumped["type"] == "tool" # default — call sites never spell it out