@miller-tech/uap 1.15.1 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.15.1",
3
+ "version": "1.15.2",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -898,6 +898,28 @@ def _extract_text(content) -> str:
898
898
  return str(content)
899
899
 
900
900
 
901
+ _TOOL_CALL_APOLOGY_MARKERS = (
902
+ "i could not produce a valid tool-call format in this turn",
903
+ "i will issue exactly one valid tool call next",
904
+ )
905
+
906
+ _TOOL_CALL_RETRY_MESSAGE = (
907
+ "Tool-call formatting failed after automatic retries. "
908
+ "Please retry the same request."
909
+ )
910
+
911
+
912
+ def _contains_tool_call_apology(text: str) -> bool:
913
+ if not text:
914
+ return False
915
+ lowered = text.lower()
916
+ return any(marker in lowered for marker in _TOOL_CALL_APOLOGY_MARKERS)
917
+
918
+
919
+ def _sanitize_tool_call_apology_text(text: str) -> str:
920
+ return _TOOL_CALL_RETRY_MESSAGE if _contains_tool_call_apology(text) else text
921
+
922
+
901
923
  def _has_tool_definitions(anthropic_body: dict) -> bool:
902
924
  tools = anthropic_body.get("tools")
903
925
  return isinstance(tools, list) and len(tools) > 0
@@ -1647,11 +1669,7 @@ def _looks_malformed_tool_payload(text: str) -> bool:
1647
1669
  return False
1648
1670
 
1649
1671
  lowered = text.lower()
1650
- apology_markers = (
1651
- "i could not produce a valid tool-call format in this turn",
1652
- "i will issue exactly one valid tool call next",
1653
- )
1654
- if any(marker in lowered for marker in apology_markers):
1672
+ if _contains_tool_call_apology(text):
1655
1673
  return True
1656
1674
 
1657
1675
  primary_markers = ("</parameter", "<parameter", "<tool_call", "<function=")
@@ -1756,10 +1774,7 @@ def _build_clean_guardrail_openai_response(openai_resp: dict) -> dict:
1756
1774
  "finish_reason": "stop",
1757
1775
  "message": {
1758
1776
  "role": "assistant",
1759
- "content": (
1760
- "Tool-call formatting failed after automatic retries. "
1761
- "Please retry the same request."
1762
- ),
1777
+ "content": _TOOL_CALL_RETRY_MESSAGE,
1763
1778
  },
1764
1779
  }
1765
1780
  ],
@@ -1959,7 +1974,17 @@ def openai_to_anthropic_response(openai_resp: dict, model: str) -> dict:
1959
1974
 
1960
1975
  content = []
1961
1976
  if message.get("content"):
1962
- content.append({"type": "text", "text": message["content"]})
1977
+ raw_text = (
1978
+ message["content"]
1979
+ if isinstance(message["content"], str)
1980
+ else str(message["content"])
1981
+ )
1982
+ sanitized_text = _sanitize_tool_call_apology_text(raw_text)
1983
+ if sanitized_text != raw_text:
1984
+ logger.warning(
1985
+ "SANITIZE: replaced known malformed tool-call apology text in assistant response"
1986
+ )
1987
+ content.append({"type": "text", "text": sanitized_text})
1963
1988
 
1964
1989
  # Convert tool calls
1965
1990
  for tc in message.get("tool_calls", []):
@@ -185,6 +185,14 @@ class TestMalformedToolGuardrail(unittest.TestCase):
185
185
  }
186
186
  self.assertTrue(proxy._is_malformed_tool_response(openai_resp, anthropic_body))
187
187
 
188
+ def test_tool_call_apology_helper_detects_phrase(self):
189
+ apology_text = (
190
+ "I could not produce a valid tool-call format in this turn. "
191
+ "Please continue; I will issue exactly one valid tool call next."
192
+ )
193
+ self.assertTrue(proxy._contains_tool_call_apology(apology_text))
194
+ self.assertFalse(proxy._contains_tool_call_apology("normal assistant response"))
195
+
188
196
  def test_clean_tool_call_response_is_not_malformed(self):
189
197
  openai_resp = {
190
198
  "choices": [
@@ -442,6 +450,27 @@ class TestMalformedToolGuardrail(unittest.TestCase):
442
450
  self.assertIn("Please retry the same request", text)
443
451
  self.assertNotIn("I will issue exactly one valid tool call next", text)
444
452
 
453
+ def test_openai_to_anthropic_response_sanitizes_tool_call_apology(self):
454
+ openai_resp = {
455
+ "choices": [
456
+ {
457
+ "finish_reason": "stop",
458
+ "message": {
459
+ "content": (
460
+ "I could not produce a valid tool-call format in this turn. "
461
+ "Please continue; I will issue exactly one valid tool call next."
462
+ ),
463
+ "tool_calls": [],
464
+ },
465
+ }
466
+ ]
467
+ }
468
+
469
+ converted = proxy.openai_to_anthropic_response(openai_resp, "test-model")
470
+ text = converted["content"][0]["text"]
471
+ self.assertIn("Please retry the same request", text)
472
+ self.assertNotIn("I will issue exactly one valid tool call next", text)
473
+
445
474
 
446
475
  class TestToolTurnControls(unittest.TestCase):
447
476
  def test_tool_narrowing_reduces_tool_count(self):