@@ -1425,6 +1425,7 @@ class Agent: |
| 1425 | 1425 | """ |
| 1426 | 1426 | import re |
| 1427 | 1427 | import json |
| 1428 | + import os |
| 1428 | 1429 | |
| 1429 | 1430 | tool_calls = [] |
| 1430 | 1431 | tool_names = ["write", "read", "edit", "bash", "glob", "grep"] |
@@ -1461,14 +1462,31 @@ class Agent: |
| 1461 | 1462 | debug(f" skipping - tool_name '{tool_name}' not in tool_names") |
| 1462 | 1463 | continue |
| 1463 | 1464 | |
| 1465 | + # Skip if we already have a tool call at this position (avoid duplicates) |
| 1466 | + match_start = match.start() |
| 1467 | + if any(tc.id.endswith(f"_pos{match_start}") for tc in tool_calls): |
| 1468 | + debug(f" skipping - already extracted at position {match_start}") |
| 1469 | + continue |
| 1470 | + |
| 1464 | 1471 | try: |
| 1465 | 1472 | # Parse the arguments based on tool type |
| 1466 | 1473 | if tool_name == "bash": |
| 1467 | | - # bash tool: command is the whole string |
| 1474 | + # bash tool: extract command, handling various formats |
| 1475 | + # Model might output: "mkdir -p /foo" or "command='mkdir -p /foo'" |
| 1476 | + cmd = args_str |
| 1477 | + # If it has command= prefix, extract just the command value |
| 1478 | + cmd_match = re.search(r'command\s*[=:]\s*["\']?([^"\']+)["\']?', args_str) |
| 1479 | + if cmd_match: |
| 1480 | + cmd = cmd_match.group(1).strip() |
| 1481 | + # Also handle case where model outputs "cmd, command='cmd'" - take first part |
| 1482 | + elif ',' in args_str and 'command=' in args_str: |
| 1483 | + cmd = args_str.split(',')[0].strip() |
| 1484 | + # Expand ~ in command |
| 1485 | + cmd = os.path.expanduser(cmd) |
| 1468 | 1486 | tool_calls.append(ToolCall( |
| 1469 | | - id=f"bracket_{tool_name}_{len(tool_calls)}", |
| 1487 | + id=f"bracket_{tool_name}_{len(tool_calls)}_pos{match_start}", |
| 1470 | 1488 | name=tool_name, |
| 1471 | | - arguments={"command": args_str}, |
| 1489 | + arguments={"command": cmd}, |
| 1472 | 1490 | )) |
| 1473 | 1491 | elif tool_name == "write": |
| 1474 | 1492 | # write tool: file_path=..., content="..." |
@@ -1504,41 +1522,47 @@ class Agent: |
| 1504 | 1522 | |
| 1505 | 1523 | if file_path_match: |
| 1506 | 1524 | file_path = file_path_match.group(1).strip('"\'') |
| 1525 | + file_path = os.path.expanduser(file_path) # Expand ~ |
| 1507 | 1526 | tool_calls.append(ToolCall( |
| 1508 | | - id=f"bracket_{tool_name}_{len(tool_calls)}", |
| 1527 | + id=f"bracket_{tool_name}_{len(tool_calls)}_pos{match_start}", |
| 1509 | 1528 | name=tool_name, |
| 1510 | 1529 | arguments={"file_path": file_path, "content": file_content}, |
| 1511 | 1530 | )) |
| 1512 | 1531 | elif tool_name == "read": |
| 1513 | 1532 | # read tool: file_path |
| 1514 | 1533 | file_path = args_str.split(',')[0].split('=')[-1].strip().strip('"\'') |
| 1534 | + file_path = os.path.expanduser(file_path) |
| 1515 | 1535 | tool_calls.append(ToolCall( |
| 1516 | | - id=f"bracket_{tool_name}_{len(tool_calls)}", |
| 1536 | + id=f"bracket_{tool_name}_{len(tool_calls)}_pos{match_start}", |
| 1517 | 1537 | name=tool_name, |
| 1518 | 1538 | arguments={"file_path": file_path}, |
| 1519 | 1539 | )) |
| 1520 | 1540 | elif tool_name == "edit": |
| 1521 | 1541 | # edit tool: file_path=..., old_string="...", new_string="..." |
| 1522 | | - file_path_match = re.search(r'file_path[=:]\s*([^,\s]+)', args_str) |
| 1542 | + file_path_match = re.search(r'file_path[=:]\s*["\']?([^"\'`,]+)["\']?', args_str) |
| 1523 | 1543 | old_match = re.search(r'old_string[=:]\s*["\'](.+?)["\']', args_str) |
| 1524 | 1544 | new_match = re.search(r'new_string[=:]\s*["\'](.+?)["\']', args_str) |
| 1525 | 1545 | |
| 1526 | 1546 | if file_path_match and old_match and new_match: |
| 1547 | + file_path = os.path.expanduser(file_path_match.group(1).strip('"\'')) |
| 1527 | 1548 | tool_calls.append(ToolCall( |
| 1528 | | - id=f"bracket_{tool_name}_{len(tool_calls)}", |
| 1549 | + id=f"bracket_{tool_name}_{len(tool_calls)}_pos{match_start}", |
| 1529 | 1550 | name=tool_name, |
| 1530 | 1551 | arguments={ |
| 1531 | | - "file_path": file_path_match.group(1).strip('"\''), |
| 1552 | + "file_path": file_path, |
| 1532 | 1553 | "old_string": old_match.group(1), |
| 1533 | 1554 | "new_string": new_match.group(1), |
| 1534 | 1555 | }, |
| 1535 | 1556 | )) |
| 1536 | 1557 | elif tool_name in ("glob", "grep"): |
| 1537 | | - # glob/grep: pattern |
| 1558 | + # glob/grep: pattern - expand ~ if it looks like a path |
| 1559 | + pattern = args_str |
| 1560 | + if '~' in pattern: |
| 1561 | + pattern = os.path.expanduser(pattern) |
| 1538 | 1562 | tool_calls.append(ToolCall( |
| 1539 | | - id=f"bracket_{tool_name}_{len(tool_calls)}", |
| 1563 | + id=f"bracket_{tool_name}_{len(tool_calls)}_pos{match_start}", |
| 1540 | 1564 | name=tool_name, |
| 1541 | | - arguments={"pattern": args_str}, |
| 1565 | + arguments={"pattern": pattern}, |
| 1542 | 1566 | )) |
| 1543 | 1567 | except Exception: |
| 1544 | 1568 | continue |