Pass through JSON objects with thoughts or headline
After repair, return a recovered JSON object that already contains thoughts or headline as compact JSON instead of wrapping the raw response in another thoughts envelope.
linkliti committed
Aug 25, 2026 at 09:26 UTC
e970e1e89435495ec5c26c56e00cd6a146ecf40c
2 files changed
+37
-4
plugins/_context_doctor/helpers/context_doctor.py
+19
-4
@@ -49,23 +49,29 @@ def transform_response(response: str, *, suppress_xml: bool) -> str:
49
50
apply_patch()
51
try:
52
- repaired = repair_json(
52
+ salvage_repaired = repair_json(
53
response,
54
return_objects=True,
55
schema=_A0_SALVAGE_SCHEMA,
56
schema_repair_mode="salvage",
57
)
58
except Exception:
59
- repaired = repair_json(response, return_objects=True)
59
+ salvage_repaired = repair_json(response, return_objects=True)
60
61
- candidates = repaired if isinstance(repaired, list) else [repaired]
61
+ candidates = (
62
+ salvage_repaired
63
+ if isinstance(salvage_repaired, list)
64
+ else [salvage_repaired]
65
+ )
66
try:
67
no_schema = repair_json(response, return_objects=True)
68
except Exception:
69
no_schema = None
70
if isinstance(no_schema, list):
71
valid = [item for item in no_schema if _is_tool_call(item)]
68
- if len(valid) > 1 or (len(valid) == 1 and not _is_tool_call(repaired)):
72
+ if len(valid) > 1 or (
73
+ len(valid) == 1 and not _is_tool_call(salvage_repaired)
74
+ ):
75
candidates = no_schema
76
repaired = max(
77
(item for item in candidates if _is_tool_call(item)),
@@ -74,10 +80,19 @@ def transform_response(response: str, *, suppress_xml: bool) -> str:
80
)
81
except Exception:
82
repaired = None
83
+ salvage_repaired = None
84
85
if _is_tool_call(repaired):
86
return json.dumps(repaired, ensure_ascii=False, separators=(",", ":"))
87
88
+ for candidate in (repaired, salvage_repaired):
89
+ if isinstance(candidate, dict) and (
90
+ "thoughts" in candidate or "headline" in candidate
91
+ ):
92
+ return json.dumps(
93
+ candidate, ensure_ascii=False, separators=(",", ":")
94
+ )
95
+
96
if suppress_xml and "<" in response and ">" in response:
97
return "{}"
98
return json.dumps({"thoughts": [response]}, ensure_ascii=False, separators=(",", ":"))
plugins/_context_doctor/tests/test_context_doctor.py
+18
@@ -31,6 +31,24 @@ def test_wraps_raw_text_in_thoughts():
31
)
32
33
34
+def test_does_not_wrap_json_with_thoughts_or_headline():
35
+ response = '{"thoughts":["Reasoning"],"headline":"A title"}'
36
+
37
+ assert transform_response(response, suppress_xml=True) == response
38
+
39
+
40
+def test_does_not_wrap_json_with_only_headline():
41
+ response = '{"headline":"Just a headline"}'
42
+
43
+ assert transform_response(response, suppress_xml=True) == response
44
+
45
+
46
+def test_does_not_wrap_json_with_only_thoughts():
47
+ response = '{"thoughts":["only thoughts"]}'
48
+
49
+ assert transform_response(response, suppress_xml=True) == response
50
+
51
+
52
def test_suppresses_xml_when_enabled():
53
assert transform_response("<tool>response</tool>", suppress_xml=True) == "{}"
54
assert transform_response("<tool>response</tool>", suppress_xml=False) == (