Skip to content

Commit 67e288a

Browse files
committed
Add JSON preprocessing for common model generated json errors
1 parent d1e9df7 commit 67e288a

4 files changed

Lines changed: 40 additions & 8 deletions

File tree

cecli/coders/agent_coder.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# All conversation functions are now available via ConversationChunks class
2222
from cecli.helpers.conversation.manager import ConversationManager
2323
from cecli.helpers.conversation.tags import MessageTag
24+
from cecli.helpers.responses import preprocess_json
2425
from cecli.helpers.similarity import (
2526
cosine_similarity,
2627
create_bigram_vector,
@@ -58,15 +59,14 @@ def __init__(self, *args, **kwargs):
5859
"viewfileswithsymbol",
5960
"grep",
6061
"listchanges",
61-
"extractlines",
6262
"shownumberedcontext",
6363
}
6464
self.write_tools = {
6565
"command",
6666
"commandinteractive",
67-
"insertblock",
68-
"replaceblock",
69-
"replaceall",
67+
"deletetext",
68+
"indenttext",
69+
"inserttext",
7070
"replacetext",
7171
"undochange",
7272
}
@@ -244,11 +244,20 @@ async def _execute_local_tool_calls(self, tool_calls_list):
244244
json_chunks = utils.split_concatenated_json(args_string)
245245
for chunk in json_chunks:
246246
try:
247-
parsed_args_list.append(json.loads(chunk))
248-
except json.JSONDecodeError:
247+
parsed_args_list.append(json.loads(preprocess_json(chunk)))
248+
except json.JSONDecodeError as e:
249249
self.io.tool_warning(
250250
f"Could not parse JSON chunk for tool {tool_name}: {chunk}"
251251
)
252+
tool_responses.append(
253+
{
254+
"role": "tool",
255+
"tool_call_id": tool_call.id,
256+
"content": (
257+
f"Could not parse JSON chunk for tool {tool_name}: {str(e)}"
258+
),
259+
}
260+
)
252261
continue
253262
if not parsed_args_list and not args_string:
254263
parsed_args_list.append({})

cecli/helpers/responses.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import re
2+
3+
4+
def preprocess_json(response: str) -> str:
5+
response = fix_json_backslashes(response)
6+
return response
7+
8+
9+
def fix_json_backslashes(raw_str: str) -> str:
10+
"""
11+
Finds invalid JSON escape sequences and escapes the backslash
12+
so it is treated as a literal character.
13+
"""
14+
# Look for a backslash NOT followed by valid escape characters
15+
# We use a capturing group for the backslash to replace it
16+
invalid_escape_pattern = r'\\(?!(["\\\/bfnrt]|u[0-9a-fA-F]{4}))'
17+
18+
# Replace the single backslash with a double backslash
19+
return re.sub(invalid_escape_pattern, r"\\\\", raw_str)

cecli/tools/utils/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import re
44
import traceback
55

6+
from cecli.helpers.responses import preprocess_json
7+
68

79
class ToolError(Exception):
810
"""Custom exception for tool-specific errors that should be reported to the LLM."""
@@ -258,7 +260,7 @@ def parse_arg_as_list(arg):
258260
import json
259261

260262
try:
261-
parsed = json.loads(arg)
263+
parsed = json.loads(preprocess_json(arg))
262264
if isinstance(parsed, list):
263265
return parsed
264266
else:

cecli/tools/utils/output.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
import re
33

4+
from cecli.helpers.responses import preprocess_json
5+
46

57
def print_tool_response(coder, mcp_server, tool_response):
68
"""
@@ -65,7 +67,7 @@ def tool_body_unwrapped(coder, tool_response):
6567
color_start, color_end = color_markers(coder)
6668

6769
try:
68-
args_dict = json.loads(tool_response.function.arguments)
70+
args_dict = json.loads(preprocess_json(tool_response.function.arguments))
6971
first_key = True
7072
for key, value in args_dict.items():
7173
# Convert explicit \\n sequences to actual newlines using regex

0 commit comments

Comments
 (0)