tenseleyflow/loader / d328e1c

Browse files

Normalize edit content aliases

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
d328e1ce266baf94e4ac7d1000e0b0f1226131f9
Parents
07dbbb9
Tree
c9f6279

2 changed files

StatusFile+-
M src/loader/runtime/executor.py 18 0
M tests/test_permissions.py 32 0
src/loader/runtime/executor.pymodified
@@ -78,6 +78,7 @@ class ToolExecutor:
7878
     ) -> ToolExecutionOutcome:
7979
         """Execute a tool call through one consistent runtime path."""
8080
 
81
+        tool_call = _normalize_tool_call_arguments(tool_call)
8182
         self.tracer.record(
8283
             "tool.received",
8384
             tool_name=tool_call.name,
@@ -536,3 +537,20 @@ class ToolExecutor:
536537
         if reason:
537538
             details.append(f"reason={reason}")
538539
         return "\n".join(details)
540
+
541
+
542
+def _normalize_tool_call_arguments(tool_call: ToolCall) -> ToolCall:
543
+    """Accept narrow, unambiguous argument aliases from local models."""
544
+
545
+    if tool_call.name != "edit":
546
+        return tool_call
547
+
548
+    arguments = dict(tool_call.arguments)
549
+    if "new_string" not in arguments and "content" in arguments:
550
+        arguments["new_string"] = arguments["content"]
551
+        return ToolCall(
552
+            id=tool_call.id,
553
+            name=tool_call.name,
554
+            arguments=arguments,
555
+        )
556
+    return tool_call
tests/test_permissions.pymodified
@@ -207,6 +207,38 @@ async def test_allow_mode_executor_skips_prompt_for_destructive_write(
207207
     assert prompts == []
208208
 
209209
 
210
+@pytest.mark.asyncio
211
+async def test_executor_accepts_edit_content_alias_for_new_string(
212
+    temp_dir: Path,
213
+) -> None:
214
+    registry = create_default_registry(temp_dir)
215
+    policy = build_permission_policy(
216
+        active_mode=PermissionMode.ALLOW,
217
+        workspace_root=temp_dir,
218
+        tool_requirements=registry.get_tool_requirements(),
219
+    )
220
+    executor = ToolExecutor(registry, RuntimeTracer(), policy)
221
+    target = temp_dir / "guide.html"
222
+    target.write_text("<h1>Old</h1>\n")
223
+
224
+    outcome = await executor.execute_tool_call(
225
+        ToolCall(
226
+            id="edit-1",
227
+            name="edit",
228
+            arguments={
229
+                "file_path": str(target),
230
+                "old_string": "<h1>Old</h1>",
231
+                "content": "<h1>New</h1>",
232
+            },
233
+        ),
234
+        source="native",
235
+    )
236
+
237
+    assert outcome.state == ToolExecutionState.EXECUTED
238
+    assert target.read_text() == "<h1>New</h1>\n"
239
+    assert outcome.tool_call.arguments["new_string"] == "<h1>New</h1>"
240
+
241
+
210242
 @pytest.mark.asyncio
211243
 async def test_ask_rule_prompts_even_when_allow_mode(temp_dir: Path) -> None:
212244
     prompts: list[str] = []