Skip to content

Commit 0eab17c

Browse files
authored
Merge pull request #14 from dreamrec/claude/v2.0-pr25-drop-error-key-fallback
remove: legacy "error"-key fallback in is_tool_error_result (F-12, BREAKING)
2 parents 7e32054 + 025a0aa commit 0eab17c

4 files changed

Lines changed: 40 additions & 118 deletions

File tree

td_component/tdpilot_api_agent.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,20 @@
2929
from collections.abc import Callable
3030
from typing import Any
3131

32-
# Phase 3 (F-12) — soft-import the tool-error sentinel helper. The
33-
# dispatcher module owns the canonical predicate; the agent loop just
34-
# calls it. Soft-import so a stripped-down test embed without the
35-
# dispatcher module still loads. The fallback mirrors the dispatcher's
36-
# v1.10.0 DeprecationWarning behavior so embeds that exercise this
37-
# shim see the same warning surface as production.
32+
# F-12 — soft-import the tool-error sentinel helper. The dispatcher
33+
# module owns the canonical predicate; the agent loop just calls it.
34+
# Soft-import so a stripped-down test embed without the dispatcher
35+
# module still loads. The fallback mirrors the dispatcher's v2.0
36+
# semantics: only the explicit ``_tool_error`` sentinel marks failure.
3837
try:
3938
from tdpilot_api_dispatcher import is_tool_error_result # type: ignore[import-not-found]
4039
except ImportError:
41-
import warnings as _warnings_shim
4240

4341
def is_tool_error_result(result): # type: ignore[misc]
4442
if not isinstance(result, dict):
4543
return False
4644
if "_tool_error" in result:
4745
return bool(result["_tool_error"])
48-
if "error" in result:
49-
_warnings_shim.warn(
50-
"Tool result was classified as an error via the legacy "
51-
"'error' key. Update your handler to emit "
52-
"{'_tool_error': True, 'error': '...'} explicitly. "
53-
"The legacy fallback is removed in TDPilot DPSK4 v2.0.",
54-
DeprecationWarning,
55-
stacklevel=2,
56-
)
57-
return True
5846
return False
5947

6048
# ---------------------------------------------------------------------------

td_component/tdpilot_api_batch.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,19 @@
2828
import time
2929
from typing import Any
3030

31-
# Phase 3 (F-12) — soft-import the tool-error sentinel helper. Same
32-
# story as agent.py: the dispatcher module owns the canonical
33-
# predicate. The fallback mirrors the dispatcher's v1.10.0
34-
# DeprecationWarning behavior so embeds that exercise this shim see
35-
# the same warning surface as production.
31+
# F-12 — soft-import the tool-error sentinel helper. Same story as
32+
# agent.py: the dispatcher module owns the canonical predicate. The
33+
# fallback mirrors the dispatcher's v2.0 semantics: only the explicit
34+
# ``_tool_error`` sentinel marks failure.
3635
try:
3736
from tdpilot_api_dispatcher import is_tool_error_result # type: ignore[import-not-found]
3837
except ImportError:
39-
import warnings as _warnings_shim
4038

4139
def is_tool_error_result(result): # type: ignore[misc]
4240
if not isinstance(result, dict):
4341
return False
4442
if "_tool_error" in result:
4543
return bool(result["_tool_error"])
46-
if "error" in result:
47-
_warnings_shim.warn(
48-
"Tool result was classified as an error via the legacy "
49-
"'error' key. Update your handler to emit "
50-
"{'_tool_error': True, 'error': '...'} explicitly. "
51-
"The legacy fallback is removed in TDPilot DPSK4 v2.0.",
52-
DeprecationWarning,
53-
stacklevel=2,
54-
)
55-
return True
5644
return False
5745

5846

td_component/tdpilot_api_dispatcher.py

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
from __future__ import annotations
2626

2727
import traceback
28-
import warnings
2928
from collections.abc import Callable
3029
from typing import Any
3130

@@ -52,47 +51,30 @@ def _scrub(s: str) -> str:
5251
return redact_paths(redact(s))
5352

5453

55-
# Phase 3 (F-12) — explicit tool-error sentinel. Pre-1.8.1 the agent
56-
# loop and tool_batch checked ``"error" in result`` to decide whether
57-
# a tool call failed, which misclassifies any handler whose successful
58-
# result legitimately contains an "error" field (e.g. ``td_get_errors``
59-
# returning a list of TD compile errors). The new convention sets
60-
# ``_tool_error: True`` on results that represent a dispatch / handler
61-
# failure; the agent loop checks the sentinel first and falls back to
62-
# the legacy ``"error"`` key.
63-
#
64-
# v1.10.0: the legacy fallback now emits ``DeprecationWarning`` to
65-
# nudge external dispatcher integrations / user-authored handlers off
66-
# the brittle heuristic. The fallback drops entirely in v2.0.
54+
# F-12 — explicit tool-error sentinel. Handlers signal a dispatch /
55+
# handler failure by stamping ``_tool_error: True`` on the result;
56+
# this is the authoritative signal the agent loop checks. A handler
57+
# whose successful result legitimately contains an ``error`` field
58+
# (e.g. ``td_get_errors`` returning a list of TD compile errors) is
59+
# classified as success — the sentinel is the only thing that marks
60+
# failure. v1.10.0 emitted a ``DeprecationWarning`` for the legacy
61+
# ``"error" in result`` heuristic; v2.0 removed the fallback entirely.
6762
TOOL_ERROR_KEY = "_tool_error"
6863

6964

7065
def is_tool_error_result(result: Any) -> bool:
7166
"""True when ``result`` represents a tool-call failure that the
7267
agent loop should signal back to the model with ``is_error=True``.
7368
74-
Resolution order:
75-
1. Explicit ``_tool_error`` sentinel — authoritative when present
76-
(allowing handlers to flag an error WITHOUT carrying an
77-
``error`` key, or to flag success even WITH an ``error`` key).
78-
2. Legacy ``error`` key — backward-compat shim for handlers that
79-
haven't been updated. **Deprecated in v1.10.0**, removed in v2.0.
80-
Reaching this branch emits a ``DeprecationWarning``.
69+
The ``_tool_error`` sentinel is authoritative — a handler must
70+
stamp it explicitly to mark failure. A bare ``"error"`` key alone
71+
is no longer a failure signal (the legacy v1.x fallback was
72+
removed in v2.0).
8173
"""
8274
if not isinstance(result, dict):
8375
return False
8476
if TOOL_ERROR_KEY in result:
8577
return bool(result[TOOL_ERROR_KEY])
86-
if "error" in result:
87-
warnings.warn(
88-
"Tool result was classified as an error via the legacy "
89-
"'error' key. Update your handler to emit "
90-
"{'_tool_error': True, 'error': '...'} explicitly. "
91-
"The legacy fallback is removed in TDPilot DPSK4 v2.0.",
92-
DeprecationWarning,
93-
stacklevel=2,
94-
)
95-
return True
9678
return False
9779

9880

tests/test_tool_error_sentinel.py

Lines changed: 19 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,15 @@
77
of compile errors).
88
99
PR-17 introduced ``_tool_error: bool`` as the authoritative flag.
10-
``is_tool_error_result(result)`` checks the sentinel first and falls
11-
back to the legacy ``error`` key.
12-
13-
**v1.10.0 (PR-24)**: the legacy fallback now emits
14-
``DeprecationWarning`` to nudge external dispatcher integrations off
15-
the brittle heuristic. The fallback is removed entirely in v2.0.
10+
``is_tool_error_result(result)`` checks the sentinel; v1.10.0 emitted
11+
a ``DeprecationWarning`` for the legacy ``"error" in result`` fallback;
12+
**v2.0 (PR-25) removed the fallback entirely** — only the explicit
13+
sentinel marks failure.
1614
1715
Tests cover:
1816
* ``is_tool_error_result`` truth table (sentinel-True, sentinel-False,
19-
no-error-key, non-dict input). The legacy fallback paths are
20-
tested separately so the ``DeprecationWarning`` is asserted at
21-
its emission site.
22-
* Legacy ``"error"`` key still classifies as an error AND emits
23-
``DeprecationWarning`` (v1.10.0).
24-
* Sentinel-driven results never emit ``DeprecationWarning``.
17+
no-error-key, non-dict input, AND a bare ``"error"`` key — which
18+
no longer marks failure post-v2.0).
2519
* Dispatcher synthetic errors carry the sentinel.
2620
* The agent loop (``tdpilot_api_agent``) imports + uses the helper.
2721
* ``tool_batch`` imports + uses the helper.
@@ -64,6 +58,14 @@
6458
({"_tool_error": 0}, False),
6559
({"_tool_error": ""}, False),
6660
({"_tool_error": None}, False),
61+
# v2.0 (PR-25): a bare ``error`` key no longer marks failure —
62+
# the sentinel is the only signal. Handlers that emit
63+
# ``{"error": "..."}`` without the sentinel are silently
64+
# treated as success. Pre-v2.0 these classified as True via
65+
# the legacy heuristic; v1.10.0 emitted DeprecationWarning;
66+
# v2.0 removed the fallback entirely.
67+
({"error": "Unknown tool"}, False),
68+
({"error": ""}, False),
6769
# No sentinel, no error key.
6870
({"ok": True, "path": "/project1"}, False),
6971
({}, False),
@@ -75,9 +77,12 @@
7577
],
7678
)
7779
def test_is_tool_error_result_truth_table(result, expected):
78-
"""Sentinel-driven cases must never emit a warning."""
80+
"""No path through ``is_tool_error_result`` should emit a warning
81+
in v2.0 — the legacy ``DeprecationWarning`` was removed alongside
82+
the fallback. ``simplefilter('error')`` is kept as a regression
83+
guard against accidentally re-introducing one."""
7984
with warnings.catch_warnings():
80-
warnings.simplefilter("error") # any DeprecationWarning fails the test
85+
warnings.simplefilter("error")
8186
assert disp.is_tool_error_result(result) is expected
8287

8388

@@ -87,47 +92,6 @@ def test_tool_error_key_constant_is_dunder_underscore():
8792
assert disp.TOOL_ERROR_KEY == "_tool_error"
8893

8994

90-
# ---------------------------------------------------------------------------
91-
# v1.10.0 (PR-24) — legacy "error"-key fallback emits DeprecationWarning.
92-
# These cases used to live in the truth-table parametrize above; they were
93-
# extracted here so the warning surface is asserted at its emission site.
94-
# In v2.0 these tests flip to expecting `False` and the warning assertion
95-
# goes away (the fallback is removed entirely).
96-
# ---------------------------------------------------------------------------
97-
98-
99-
@pytest.mark.parametrize(
100-
"result",
101-
[
102-
{"error": "Unknown tool"},
103-
{"error": ""}, # empty string still triggers (key-presence semantics)
104-
],
105-
)
106-
def test_legacy_error_key_classifies_as_error_with_deprecation_warning(result):
107-
with pytest.warns(DeprecationWarning, match="legacy 'error' key"):
108-
assert disp.is_tool_error_result(result) is True
109-
110-
111-
def test_sentinel_path_emits_no_deprecation_warning():
112-
"""Sentinel-driven classification (the new convention) must stay
113-
silent — only the legacy fallback is deprecated."""
114-
with warnings.catch_warnings():
115-
warnings.simplefilter("error") # any DeprecationWarning fails the test
116-
assert disp.is_tool_error_result({"_tool_error": True}) is True
117-
assert disp.is_tool_error_result({"_tool_error": False}) is False
118-
assert disp.is_tool_error_result({"_tool_error": True, "error": "x"}) is True
119-
assert disp.is_tool_error_result({"_tool_error": False, "error": "x"}) is False
120-
121-
122-
def test_no_warning_on_no_error_key():
123-
"""A dict with neither sentinel nor error key returns False
124-
silently — there's nothing to deprecate."""
125-
with warnings.catch_warnings():
126-
warnings.simplefilter("error")
127-
assert disp.is_tool_error_result({"ok": True}) is False
128-
assert disp.is_tool_error_result({}) is False
129-
130-
13195
# ---------------------------------------------------------------------------
13296
# Dispatcher synthetic errors carry the sentinel
13397
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)