|
10 | 10 | import sys |
11 | 11 | from collections import defaultdict |
12 | 12 | from pathlib import Path |
13 | | -from typing import List, Optional, Tuple, Literal |
| 13 | +from typing import Annotated, List, Literal, Optional, Tuple |
14 | 14 | import io |
15 | 15 |
|
16 | 16 | import logging |
|
19 | 19 |
|
20 | 20 | logger = logging.getLogger(__name__) |
21 | 21 |
|
| 22 | +from pydantic import BeforeValidator |
22 | 23 | from pydantic_ai import RunContext, Tool |
23 | 24 |
|
24 | 25 | from .deps import CodeWikiDeps |
25 | 26 | from ..utils import validate_mermaid_diagrams |
26 | 27 |
|
27 | 28 |
|
| 29 | +def _coerce_json_string(value): |
| 30 | + """Coerce a JSON encoded string to its parsed Python value before pydantic |
| 31 | + strict validation runs. No op on already typed values. |
| 32 | +
|
| 33 | + Some local models routed through OpenAI compatible endpoints (LiteLLM, |
| 34 | + vLLM, Ollama, etc.) emit list and int tool args as JSON encoded strings |
| 35 | + (e.g. `"[1, 50]"` instead of `[1, 50]`) which strict pydantic validation |
| 36 | + rejects. This validator parses them so the tool accepts both shapes. |
| 37 | + Anthropic native API users are unaffected because they already emit |
| 38 | + structured values. |
| 39 | + """ |
| 40 | + if isinstance(value, str): |
| 41 | + try: |
| 42 | + return json.loads(value) |
| 43 | + except (json.JSONDecodeError, ValueError): |
| 44 | + pass |
| 45 | + return value |
| 46 | + |
| 47 | + |
| 48 | +ViewRange = Annotated[Optional[List[int]], BeforeValidator(_coerce_json_string)] |
| 49 | +InsertLine = Annotated[Optional[int], BeforeValidator(_coerce_json_string)] |
| 50 | + |
| 51 | + |
28 | 52 | # There are some super strange "ascii can't decode x" errors, |
29 | 53 | # that can be solved with setting the default encoding for stdout |
30 | 54 | # (note that python3.6 doesn't have the reconfigure method) |
@@ -713,10 +737,10 @@ async def str_replace_editor( |
713 | 737 | path: Optional[str] = None, |
714 | 738 | file: Optional[str] = None, |
715 | 739 | file_text: Optional[str] = None, |
716 | | - view_range: Optional[List[int]] = None, |
| 740 | + view_range: ViewRange = None, |
717 | 741 | old_str: Optional[str] = None, |
718 | 742 | new_str: Optional[str] = None, |
719 | | - insert_line: Optional[int] = None, |
| 743 | + insert_line: InsertLine = None, |
720 | 744 | ) -> str: |
721 | 745 | """ |
722 | 746 | Custom editing tool for viewing, creating and editing files |
|
0 commit comments