@miller-tech/uap 1.15.13 → 1.16.0

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.
@@ -88,6 +88,55 @@ class TestProxyConfigTuning(unittest.TestCase):
88
88
  finally:
89
89
  setattr(proxy, "PROXY_MAX_TOKENS_FLOOR", old_floor)
90
90
 
91
+ def test_build_request_bypasses_floor_for_tool_turns_when_thinking_disabled(self):
92
+ old_floor = getattr(proxy, "PROXY_MAX_TOKENS_FLOOR")
93
+ old_disable = getattr(proxy, "PROXY_DISABLE_THINKING_ON_TOOL_TURNS")
94
+ try:
95
+ setattr(proxy, "PROXY_MAX_TOKENS_FLOOR", 4096)
96
+ setattr(proxy, "PROXY_DISABLE_THINKING_ON_TOOL_TURNS", True)
97
+
98
+ body = {
99
+ "model": "test",
100
+ "max_tokens": 512,
101
+ "messages": [{"role": "user", "content": "run pwd"}],
102
+ "tools": [
103
+ {
104
+ "name": "Bash",
105
+ "description": "run command",
106
+ "input_schema": {"type": "object"},
107
+ }
108
+ ],
109
+ }
110
+
111
+ openai = proxy.build_openai_request(
112
+ body, proxy.SessionMonitor(context_window=0)
113
+ )
114
+ self.assertEqual(openai.get("max_tokens"), 512)
115
+ finally:
116
+ setattr(proxy, "PROXY_MAX_TOKENS_FLOOR", old_floor)
117
+ setattr(proxy, "PROXY_DISABLE_THINKING_ON_TOOL_TURNS", old_disable)
118
+
119
+ def test_build_request_keeps_floor_for_non_tool_turns(self):
120
+ old_floor = getattr(proxy, "PROXY_MAX_TOKENS_FLOOR")
121
+ old_disable = getattr(proxy, "PROXY_DISABLE_THINKING_ON_TOOL_TURNS")
122
+ try:
123
+ setattr(proxy, "PROXY_MAX_TOKENS_FLOOR", 4096)
124
+ setattr(proxy, "PROXY_DISABLE_THINKING_ON_TOOL_TURNS", True)
125
+
126
+ body = {
127
+ "model": "test",
128
+ "max_tokens": 512,
129
+ "messages": [{"role": "user", "content": "say ok"}],
130
+ }
131
+
132
+ openai = proxy.build_openai_request(
133
+ body, proxy.SessionMonitor(context_window=0)
134
+ )
135
+ self.assertEqual(openai.get("max_tokens"), 4096)
136
+ finally:
137
+ setattr(proxy, "PROXY_MAX_TOKENS_FLOOR", old_floor)
138
+ setattr(proxy, "PROXY_DISABLE_THINKING_ON_TOOL_TURNS", old_disable)
139
+
91
140
  def test_prune_target_fraction_uses_config_or_default(self):
92
141
  old_target = getattr(proxy, "PROXY_CONTEXT_PRUNE_TARGET_FRACTION")
93
142
  try:
@@ -1397,6 +1446,197 @@ class TestMalformedToolGuardrail(unittest.TestCase):
1397
1446
  finally:
1398
1447
  setattr(proxy, "PROXY_MALFORMED_TOOL_RETRY_MAX", old_retry)
1399
1448
 
1449
+ def test_guardrails_skip_finalize_turn(self):
1450
+ monitor = proxy.SessionMonitor(context_window=262144)
1451
+ monitor.finalize_turn_active = True
1452
+
1453
+ openai_resp = {
1454
+ "choices": [
1455
+ {
1456
+ "finish_reason": "stop",
1457
+ "message": {"content": "final answer", "tool_calls": []},
1458
+ }
1459
+ ]
1460
+ }
1461
+ openai_body = {
1462
+ "model": "test",
1463
+ "messages": [{"role": "user", "content": "continue"}],
1464
+ }
1465
+ anthropic_body = {
1466
+ "tools": [{"name": "Bash", "input_schema": {"type": "object"}}],
1467
+ "messages": [
1468
+ {"role": "user", "content": "start"},
1469
+ {
1470
+ "role": "assistant",
1471
+ "content": [
1472
+ {
1473
+ "type": "tool_use",
1474
+ "id": "toolu_1",
1475
+ "name": "Bash",
1476
+ "input": {"command": "pwd"},
1477
+ }
1478
+ ],
1479
+ },
1480
+ {
1481
+ "role": "user",
1482
+ "content": [
1483
+ {
1484
+ "type": "tool_result",
1485
+ "tool_use_id": "toolu_1",
1486
+ "content": "ok",
1487
+ }
1488
+ ],
1489
+ },
1490
+ ],
1491
+ }
1492
+
1493
+ fake_client = _FakeClient([_FakeResponse({"choices": []})])
1494
+
1495
+ unexpected = asyncio.run(
1496
+ proxy._apply_unexpected_end_turn_guardrail(
1497
+ fake_client,
1498
+ openai_resp,
1499
+ openai_body,
1500
+ anthropic_body,
1501
+ monitor,
1502
+ "session-finalize",
1503
+ )
1504
+ )
1505
+ malformed = asyncio.run(
1506
+ proxy._apply_malformed_tool_guardrail(
1507
+ fake_client,
1508
+ openai_resp,
1509
+ openai_body,
1510
+ anthropic_body,
1511
+ monitor,
1512
+ "session-finalize",
1513
+ )
1514
+ )
1515
+
1516
+ self.assertEqual(unexpected, openai_resp)
1517
+ self.assertEqual(malformed, openai_resp)
1518
+ self.assertEqual(len(fake_client.requests), 0)
1519
+
1520
+ def test_unexpected_end_turn_guardrail_skips_review_auto_turn(self):
1521
+ monitor = proxy.SessionMonitor(context_window=262144)
1522
+ monitor.tool_turn_phase = "review"
1523
+
1524
+ openai_resp = {
1525
+ "choices": [
1526
+ {
1527
+ "finish_reason": "stop",
1528
+ "message": {"content": "looks complete", "tool_calls": []},
1529
+ }
1530
+ ]
1531
+ }
1532
+ openai_body = {
1533
+ "model": "test",
1534
+ "tool_choice": "auto",
1535
+ "messages": [{"role": "user", "content": "continue"}],
1536
+ }
1537
+ anthropic_body = {
1538
+ "tools": [{"name": "Bash", "input_schema": {"type": "object"}}],
1539
+ "messages": [
1540
+ {"role": "user", "content": "start"},
1541
+ {
1542
+ "role": "assistant",
1543
+ "content": [
1544
+ {
1545
+ "type": "tool_use",
1546
+ "id": "toolu_2",
1547
+ "name": "Bash",
1548
+ "input": {"command": "pwd"},
1549
+ }
1550
+ ],
1551
+ },
1552
+ {
1553
+ "role": "user",
1554
+ "content": [
1555
+ {
1556
+ "type": "tool_result",
1557
+ "tool_use_id": "toolu_2",
1558
+ "content": "ok",
1559
+ }
1560
+ ],
1561
+ },
1562
+ ],
1563
+ }
1564
+
1565
+ fake_client = _FakeClient([_FakeResponse({"choices": []})])
1566
+ result = asyncio.run(
1567
+ proxy._apply_unexpected_end_turn_guardrail(
1568
+ fake_client,
1569
+ openai_resp,
1570
+ openai_body,
1571
+ anthropic_body,
1572
+ monitor,
1573
+ "session-review-auto",
1574
+ )
1575
+ )
1576
+
1577
+ self.assertEqual(result, openai_resp)
1578
+ self.assertEqual(len(fake_client.requests), 0)
1579
+
1580
+ def test_unexpected_end_turn_guardrail_skips_act_auto_release_turn(self):
1581
+ monitor = proxy.SessionMonitor(context_window=262144)
1582
+ monitor.tool_turn_phase = "act"
1583
+
1584
+ openai_resp = {
1585
+ "choices": [
1586
+ {
1587
+ "finish_reason": "stop",
1588
+ "message": {"content": "done", "tool_calls": []},
1589
+ }
1590
+ ]
1591
+ }
1592
+ openai_body = {
1593
+ "model": "test",
1594
+ "tool_choice": "auto",
1595
+ "messages": [{"role": "user", "content": "continue"}],
1596
+ }
1597
+ anthropic_body = {
1598
+ "tools": [{"name": "Bash", "input_schema": {"type": "object"}}],
1599
+ "messages": [
1600
+ {"role": "user", "content": "start"},
1601
+ {
1602
+ "role": "assistant",
1603
+ "content": [
1604
+ {
1605
+ "type": "tool_use",
1606
+ "id": "toolu_3",
1607
+ "name": "Bash",
1608
+ "input": {"command": "pwd"},
1609
+ }
1610
+ ],
1611
+ },
1612
+ {
1613
+ "role": "user",
1614
+ "content": [
1615
+ {
1616
+ "type": "tool_result",
1617
+ "tool_use_id": "toolu_3",
1618
+ "content": "ok",
1619
+ }
1620
+ ],
1621
+ },
1622
+ ],
1623
+ }
1624
+
1625
+ fake_client = _FakeClient([_FakeResponse({"choices": []})])
1626
+ result = asyncio.run(
1627
+ proxy._apply_unexpected_end_turn_guardrail(
1628
+ fake_client,
1629
+ openai_resp,
1630
+ openai_body,
1631
+ anthropic_body,
1632
+ monitor,
1633
+ "session-act-auto",
1634
+ )
1635
+ )
1636
+
1637
+ self.assertEqual(result, openai_resp)
1638
+ self.assertEqual(len(fake_client.requests), 0)
1639
+
1400
1640
 
1401
1641
  class TestToolTurnControls(unittest.TestCase):
1402
1642
  def test_tool_narrowing_reduces_tool_count(self):
@@ -1473,6 +1713,546 @@ class TestToolTurnControls(unittest.TestCase):
1473
1713
  finally:
1474
1714
  setattr(proxy, "PROXY_DISABLE_THINKING_ON_TOOL_TURNS", old_disable)
1475
1715
 
1716
+ def test_state_machine_releases_after_forced_budget(self):
1717
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
1718
+ old_min_msgs = getattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES")
1719
+ old_forced = getattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET")
1720
+ old_auto = getattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET")
1721
+ old_stagnation = getattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD")
1722
+ try:
1723
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
1724
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", 3)
1725
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", 2)
1726
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", 1)
1727
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", 99)
1728
+
1729
+ monitor = proxy.SessionMonitor(context_window=262144)
1730
+ body = {
1731
+ "model": "test",
1732
+ "messages": [
1733
+ {"role": "user", "content": "start"},
1734
+ {
1735
+ "role": "assistant",
1736
+ "content": [
1737
+ {
1738
+ "type": "tool_use",
1739
+ "id": "toolu_1",
1740
+ "name": "Read",
1741
+ "input": {"file_path": "README.md"},
1742
+ }
1743
+ ],
1744
+ },
1745
+ {
1746
+ "role": "user",
1747
+ "content": [
1748
+ {
1749
+ "type": "tool_result",
1750
+ "tool_use_id": "toolu_1",
1751
+ "content": "ok",
1752
+ }
1753
+ ],
1754
+ },
1755
+ ],
1756
+ "tools": [
1757
+ {
1758
+ "name": "Read",
1759
+ "description": "Read file",
1760
+ "input_schema": {"type": "object"},
1761
+ }
1762
+ ],
1763
+ }
1764
+
1765
+ openai_1 = proxy.build_openai_request(body, monitor)
1766
+ openai_2 = proxy.build_openai_request(body, monitor)
1767
+ openai_3 = proxy.build_openai_request(body, monitor)
1768
+
1769
+ self.assertEqual(openai_1.get("tool_choice"), "required")
1770
+ self.assertEqual(openai_2.get("tool_choice"), "required")
1771
+ self.assertEqual(openai_3.get("tool_choice"), "auto")
1772
+ finally:
1773
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
1774
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", old_min_msgs)
1775
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", old_forced)
1776
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", old_auto)
1777
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", old_stagnation)
1778
+
1779
+ def test_state_machine_releases_on_two_tool_cycle(self):
1780
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
1781
+ old_min_msgs = getattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES")
1782
+ old_forced = getattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET")
1783
+ old_auto = getattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET")
1784
+ old_stagnation = getattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD")
1785
+ old_cycle_window = getattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW")
1786
+ try:
1787
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
1788
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", 3)
1789
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", 20)
1790
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", 2)
1791
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", 99)
1792
+ setattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW", 6)
1793
+
1794
+ monitor = proxy.SessionMonitor(context_window=262144)
1795
+ monitor.tool_turn_phase = "act"
1796
+ monitor.tool_state_forced_budget_remaining = 20
1797
+ monitor.tool_call_history = [
1798
+ "Bash",
1799
+ "TaskOutput",
1800
+ "Bash",
1801
+ "TaskOutput",
1802
+ "Bash",
1803
+ "TaskOutput",
1804
+ ]
1805
+ monitor.last_tool_fingerprint = "TaskOutput"
1806
+
1807
+ body = {
1808
+ "model": "test",
1809
+ "messages": [
1810
+ {"role": "user", "content": "start"},
1811
+ {
1812
+ "role": "assistant",
1813
+ "content": [
1814
+ {
1815
+ "type": "tool_use",
1816
+ "id": "toolu_2",
1817
+ "name": "Bash",
1818
+ "input": {"command": "pwd"},
1819
+ }
1820
+ ],
1821
+ },
1822
+ {
1823
+ "role": "user",
1824
+ "content": [
1825
+ {
1826
+ "type": "tool_result",
1827
+ "tool_use_id": "toolu_2",
1828
+ "content": "ok",
1829
+ }
1830
+ ],
1831
+ },
1832
+ ],
1833
+ "tools": [
1834
+ {
1835
+ "name": "Bash",
1836
+ "description": "Run command",
1837
+ "input_schema": {"type": "object"},
1838
+ },
1839
+ {
1840
+ "name": "TaskOutput",
1841
+ "description": "Return result",
1842
+ "input_schema": {"type": "object"},
1843
+ },
1844
+ ],
1845
+ }
1846
+
1847
+ openai = proxy.build_openai_request(body, monitor)
1848
+ self.assertEqual(openai.get("tool_choice"), "auto")
1849
+ self.assertEqual(monitor.tool_turn_phase, "review")
1850
+ finally:
1851
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
1852
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", old_min_msgs)
1853
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", old_forced)
1854
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", old_auto)
1855
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", old_stagnation)
1856
+ setattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW", old_cycle_window)
1857
+
1858
+ def test_state_machine_review_budget_handoff_returns_required(self):
1859
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
1860
+ old_min_msgs = getattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES")
1861
+ old_forced = getattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET")
1862
+ old_auto = getattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET")
1863
+ old_stagnation = getattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD")
1864
+ try:
1865
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
1866
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", 3)
1867
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", 20)
1868
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", 1)
1869
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", 99)
1870
+
1871
+ monitor = proxy.SessionMonitor(context_window=262144)
1872
+ monitor.tool_turn_phase = "review"
1873
+ monitor.tool_state_auto_budget_remaining = 1
1874
+ monitor.tool_state_forced_budget_remaining = 0
1875
+ monitor.last_tool_fingerprint = "Bash"
1876
+
1877
+ body = {
1878
+ "model": "test",
1879
+ "messages": [
1880
+ {"role": "user", "content": "start"},
1881
+ {
1882
+ "role": "assistant",
1883
+ "content": [
1884
+ {
1885
+ "type": "tool_use",
1886
+ "id": "toolu_review_1",
1887
+ "name": "Bash",
1888
+ "input": {"command": "pwd"},
1889
+ }
1890
+ ],
1891
+ },
1892
+ {
1893
+ "role": "user",
1894
+ "content": [
1895
+ {
1896
+ "type": "tool_result",
1897
+ "tool_use_id": "toolu_review_1",
1898
+ "content": "ok",
1899
+ }
1900
+ ],
1901
+ },
1902
+ ],
1903
+ "tools": [
1904
+ {
1905
+ "name": "Bash",
1906
+ "description": "Run command",
1907
+ "input_schema": {"type": "object"},
1908
+ }
1909
+ ],
1910
+ }
1911
+
1912
+ openai = proxy.build_openai_request(body, monitor)
1913
+ self.assertEqual(openai.get("tool_choice"), "required")
1914
+ self.assertEqual(monitor.tool_turn_phase, "act")
1915
+ self.assertEqual(monitor.tool_state_auto_budget_remaining, 0)
1916
+ self.assertEqual(monitor.tool_state_forced_budget_remaining, 10)
1917
+ finally:
1918
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
1919
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", old_min_msgs)
1920
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", old_forced)
1921
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", old_auto)
1922
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", old_stagnation)
1923
+
1924
+ def test_state_machine_review_cycles_increment_on_forced_budget_exhausted(self):
1925
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
1926
+ old_min_msgs = getattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES")
1927
+ old_forced = getattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET")
1928
+ old_auto = getattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET")
1929
+ old_stagnation = getattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD")
1930
+ try:
1931
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
1932
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", 3)
1933
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", 20)
1934
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", 2)
1935
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", 99)
1936
+
1937
+ monitor = proxy.SessionMonitor(context_window=262144)
1938
+ monitor.tool_turn_phase = "act"
1939
+ monitor.tool_state_forced_budget_remaining = 0
1940
+
1941
+ body = {
1942
+ "model": "test",
1943
+ "messages": [
1944
+ {"role": "user", "content": "start"},
1945
+ {
1946
+ "role": "assistant",
1947
+ "content": [
1948
+ {
1949
+ "type": "tool_use",
1950
+ "id": "toolu_cycle_1",
1951
+ "name": "Bash",
1952
+ "input": {"command": "pwd"},
1953
+ }
1954
+ ],
1955
+ },
1956
+ {
1957
+ "role": "user",
1958
+ "content": [
1959
+ {
1960
+ "type": "tool_result",
1961
+ "tool_use_id": "toolu_cycle_1",
1962
+ "content": "ok",
1963
+ }
1964
+ ],
1965
+ },
1966
+ ],
1967
+ "tools": [
1968
+ {
1969
+ "name": "Bash",
1970
+ "description": "Run command",
1971
+ "input_schema": {"type": "object"},
1972
+ }
1973
+ ],
1974
+ }
1975
+
1976
+ openai = proxy.build_openai_request(body, monitor)
1977
+ self.assertEqual(openai.get("tool_choice"), "auto")
1978
+ self.assertEqual(monitor.tool_turn_phase, "review")
1979
+ self.assertEqual(monitor.tool_state_review_cycles, 1)
1980
+ finally:
1981
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
1982
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", old_min_msgs)
1983
+ setattr(proxy, "PROXY_TOOL_STATE_FORCED_BUDGET", old_forced)
1984
+ setattr(proxy, "PROXY_TOOL_STATE_AUTO_BUDGET", old_auto)
1985
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", old_stagnation)
1986
+
1987
+ def test_state_machine_finalize_after_review_cycle_limit(self):
1988
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
1989
+ old_min_msgs = getattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES")
1990
+ old_stagnation = getattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD")
1991
+ old_cycle_window = getattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW")
1992
+ old_review_cycles = getattr(proxy, "PROXY_TOOL_STATE_REVIEW_CYCLE_LIMIT")
1993
+ try:
1994
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
1995
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", 3)
1996
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", 99)
1997
+ setattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW", 8)
1998
+ setattr(proxy, "PROXY_TOOL_STATE_REVIEW_CYCLE_LIMIT", 2)
1999
+
2000
+ monitor = proxy.SessionMonitor(context_window=262144)
2001
+ monitor.tool_turn_phase = "act"
2002
+ monitor.tool_state_review_cycles = 2
2003
+
2004
+ body = {
2005
+ "model": "test",
2006
+ "messages": [
2007
+ {"role": "user", "content": "start"},
2008
+ {
2009
+ "role": "assistant",
2010
+ "content": [
2011
+ {
2012
+ "type": "tool_use",
2013
+ "id": "toolu_cycle_2",
2014
+ "name": "Bash",
2015
+ "input": {"command": "pwd"},
2016
+ }
2017
+ ],
2018
+ },
2019
+ {
2020
+ "role": "user",
2021
+ "content": [
2022
+ {
2023
+ "type": "tool_result",
2024
+ "tool_use_id": "toolu_cycle_2",
2025
+ "content": "ok",
2026
+ }
2027
+ ],
2028
+ },
2029
+ ],
2030
+ "tools": [
2031
+ {
2032
+ "name": "Bash",
2033
+ "description": "Run command",
2034
+ "input_schema": {"type": "object"},
2035
+ }
2036
+ ],
2037
+ }
2038
+
2039
+ openai = proxy.build_openai_request(body, monitor)
2040
+ self.assertNotIn("tools", openai)
2041
+ self.assertNotIn("tool_choice", openai)
2042
+ self.assertEqual(monitor.tool_turn_phase, "finalize")
2043
+ self.assertTrue(monitor.finalize_turn_active)
2044
+ finally:
2045
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
2046
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", old_min_msgs)
2047
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", old_stagnation)
2048
+ setattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW", old_cycle_window)
2049
+ setattr(proxy, "PROXY_TOOL_STATE_REVIEW_CYCLE_LIMIT", old_review_cycles)
2050
+
2051
+ def test_state_machine_fresh_user_text_clears_stale_tool_history(self):
2052
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
2053
+ try:
2054
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
2055
+
2056
+ monitor = proxy.SessionMonitor(context_window=262144)
2057
+ monitor.tool_call_history = ["Bash", "Bash", "Bash"]
2058
+ monitor.tool_turn_phase = "review"
2059
+ monitor.forced_auto_cooldown_turns = 3
2060
+ monitor.consecutive_forced_count = 5
2061
+ monitor.no_progress_streak = 2
2062
+ monitor.malformed_tool_streak = 1
2063
+ monitor.invalid_tool_call_streak = 1
2064
+ monitor.required_tool_miss_streak = 1
2065
+
2066
+ body = {
2067
+ "model": "test",
2068
+ "messages": [{"role": "user", "content": "new task"}],
2069
+ "tools": [
2070
+ {
2071
+ "name": "Bash",
2072
+ "description": "Run command",
2073
+ "input_schema": {"type": "object"},
2074
+ }
2075
+ ],
2076
+ }
2077
+
2078
+ openai = proxy.build_openai_request(body, monitor)
2079
+ self.assertEqual(monitor.tool_call_history, [])
2080
+ self.assertEqual(monitor.tool_turn_phase, "bootstrap")
2081
+ self.assertEqual(monitor.forced_auto_cooldown_turns, 0)
2082
+ self.assertEqual(monitor.malformed_tool_streak, 0)
2083
+ self.assertEqual(monitor.invalid_tool_call_streak, 0)
2084
+ self.assertEqual(monitor.required_tool_miss_streak, 0)
2085
+ self.assertNotEqual(openai.get("tool_choice"), "auto")
2086
+ finally:
2087
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
2088
+
2089
+ def test_state_machine_inactive_loop_clears_stale_tool_history(self):
2090
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
2091
+ try:
2092
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
2093
+
2094
+ monitor = proxy.SessionMonitor(context_window=262144)
2095
+ monitor.tool_call_history = ["Bash", "TaskOutput"]
2096
+ monitor.tool_turn_phase = "act"
2097
+ monitor.forced_auto_cooldown_turns = 2
2098
+ monitor.consecutive_forced_count = 4
2099
+ monitor.no_progress_streak = 3
2100
+ monitor.malformed_tool_streak = 1
2101
+ monitor.invalid_tool_call_streak = 1
2102
+ monitor.required_tool_miss_streak = 1
2103
+
2104
+ body = {
2105
+ "model": "test",
2106
+ "messages": [{"role": "assistant", "content": "done"}],
2107
+ "tools": [
2108
+ {
2109
+ "name": "Bash",
2110
+ "description": "Run command",
2111
+ "input_schema": {"type": "object"},
2112
+ }
2113
+ ],
2114
+ }
2115
+
2116
+ openai = proxy.build_openai_request(body, monitor)
2117
+ self.assertEqual(monitor.tool_call_history, [])
2118
+ self.assertEqual(monitor.tool_turn_phase, "bootstrap")
2119
+ self.assertEqual(monitor.forced_auto_cooldown_turns, 0)
2120
+ self.assertEqual(monitor.consecutive_forced_count, 0)
2121
+ self.assertEqual(monitor.no_progress_streak, 0)
2122
+ self.assertEqual(monitor.malformed_tool_streak, 0)
2123
+ self.assertEqual(monitor.invalid_tool_call_streak, 0)
2124
+ self.assertEqual(monitor.required_tool_miss_streak, 0)
2125
+ self.assertNotEqual(openai.get("tool_choice"), "auto")
2126
+ finally:
2127
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
2128
+
2129
+ def test_state_machine_finalize_temporarily_disables_tools(self):
2130
+ old_state = getattr(proxy, "PROXY_TOOL_STATE_MACHINE")
2131
+ old_min_msgs = getattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES")
2132
+ old_stagnation = getattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD")
2133
+ old_cycle_window = getattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW")
2134
+ old_finalize = getattr(proxy, "PROXY_TOOL_STATE_FINALIZE_THRESHOLD")
2135
+ try:
2136
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", True)
2137
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", 3)
2138
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", 2)
2139
+ setattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW", 4)
2140
+ setattr(proxy, "PROXY_TOOL_STATE_FINALIZE_THRESHOLD", 4)
2141
+
2142
+ monitor = proxy.SessionMonitor(context_window=262144)
2143
+ monitor.tool_turn_phase = "act"
2144
+ monitor.tool_state_stagnation_streak = 4
2145
+ monitor.tool_call_history = ["Bash", "TaskOutput", "Bash", "TaskOutput"]
2146
+ monitor.last_tool_fingerprint = "TaskOutput"
2147
+
2148
+ body = {
2149
+ "model": "test",
2150
+ "messages": [
2151
+ {"role": "user", "content": "start"},
2152
+ {
2153
+ "role": "assistant",
2154
+ "content": [
2155
+ {
2156
+ "type": "tool_use",
2157
+ "id": "toolu_4",
2158
+ "name": "Bash",
2159
+ "input": {"command": "pwd"},
2160
+ }
2161
+ ],
2162
+ },
2163
+ {
2164
+ "role": "user",
2165
+ "content": [
2166
+ {
2167
+ "type": "tool_result",
2168
+ "tool_use_id": "toolu_4",
2169
+ "content": "ok",
2170
+ }
2171
+ ],
2172
+ },
2173
+ ],
2174
+ "tools": [
2175
+ {
2176
+ "name": "Bash",
2177
+ "description": "Run command",
2178
+ "input_schema": {"type": "object"},
2179
+ },
2180
+ {
2181
+ "name": "TaskOutput",
2182
+ "description": "Return result",
2183
+ "input_schema": {"type": "object"},
2184
+ },
2185
+ ],
2186
+ }
2187
+
2188
+ openai = proxy.build_openai_request(body, monitor)
2189
+ self.assertNotIn("tools", openai)
2190
+ self.assertNotIn("tool_choice", openai)
2191
+ finally:
2192
+ setattr(proxy, "PROXY_TOOL_STATE_MACHINE", old_state)
2193
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", old_min_msgs)
2194
+ setattr(proxy, "PROXY_TOOL_STATE_STAGNATION_THRESHOLD", old_stagnation)
2195
+ setattr(proxy, "PROXY_TOOL_STATE_CYCLE_WINDOW", old_cycle_window)
2196
+ setattr(proxy, "PROXY_TOOL_STATE_FINALIZE_THRESHOLD", old_finalize)
2197
+
2198
+ def test_narrowing_keeps_full_toolset_for_no_token_active_loop(self):
2199
+ old_narrow = getattr(proxy, "PROXY_TOOL_NARROWING")
2200
+ old_keep = getattr(proxy, "PROXY_TOOL_NARROWING_KEEP")
2201
+ old_min = getattr(proxy, "PROXY_TOOL_NARROWING_MIN_TOOLS")
2202
+ old_expand = getattr(proxy, "PROXY_TOOL_NARROWING_EXPAND_ON_LOOP")
2203
+ old_min_msgs = getattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES")
2204
+ try:
2205
+ setattr(proxy, "PROXY_TOOL_NARROWING", True)
2206
+ setattr(proxy, "PROXY_TOOL_NARROWING_KEEP", 2)
2207
+ setattr(proxy, "PROXY_TOOL_NARROWING_MIN_TOOLS", 3)
2208
+ setattr(proxy, "PROXY_TOOL_NARROWING_EXPAND_ON_LOOP", True)
2209
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", 3)
2210
+
2211
+ body = {
2212
+ "model": "test",
2213
+ "messages": [
2214
+ {"role": "user", "content": "start"},
2215
+ {
2216
+ "role": "assistant",
2217
+ "content": [
2218
+ {
2219
+ "type": "tool_use",
2220
+ "id": "toolu_3",
2221
+ "name": "Read",
2222
+ "input": {"file_path": "README.md"},
2223
+ }
2224
+ ],
2225
+ },
2226
+ {
2227
+ "role": "user",
2228
+ "content": [
2229
+ {
2230
+ "type": "tool_result",
2231
+ "tool_use_id": "toolu_3",
2232
+ "content": "done",
2233
+ }
2234
+ ],
2235
+ },
2236
+ ],
2237
+ "tools": [
2238
+ {"name": "Read", "input_schema": {"type": "object"}},
2239
+ {"name": "Edit", "input_schema": {"type": "object"}},
2240
+ {"name": "Write", "input_schema": {"type": "object"}},
2241
+ {"name": "Bash", "input_schema": {"type": "object"}},
2242
+ ],
2243
+ }
2244
+
2245
+ openai = proxy.build_openai_request(
2246
+ body, proxy.SessionMonitor(context_window=262144)
2247
+ )
2248
+ self.assertEqual(len(openai.get("tools", [])), 4)
2249
+ finally:
2250
+ setattr(proxy, "PROXY_TOOL_NARROWING", old_narrow)
2251
+ setattr(proxy, "PROXY_TOOL_NARROWING_KEEP", old_keep)
2252
+ setattr(proxy, "PROXY_TOOL_NARROWING_MIN_TOOLS", old_min)
2253
+ setattr(proxy, "PROXY_TOOL_NARROWING_EXPAND_ON_LOOP", old_expand)
2254
+ setattr(proxy, "PROXY_TOOL_STATE_MIN_MESSAGES", old_min_msgs)
2255
+
1476
2256
  def test_forced_tool_dampener_temporarily_releases_required(self):
1477
2257
  old_enabled = getattr(proxy, "PROXY_FORCED_TOOL_DAMPENER")
1478
2258
  old_min_forced = getattr(proxy, "PROXY_FORCED_TOOL_DAMPENER_MIN_FORCED")