@miller-tech/uap 1.195.0 → 1.195.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.
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env python3
2
+ """A monitor that cannot see the answers cannot tell working from looping.
3
+
4
+ `RESP:` was logged only from `stream_anthropic_response`. `uap deliver`'s
5
+ agentic executor is entirely non-streaming, so during a three-hour run on
6
+ 2026-08-11 the journal held 431 requests and ONE response — and every one of
7
+ those 431 turns was invisible. Diagnosing that run meant reconstructing the
8
+ model's behaviour from the delivery log's tool counts instead of reading what
9
+ it actually returned.
10
+
11
+ These pin that a non-streaming turn now logs its outcome, in the same shape as
12
+ the streaming one so a single parser reads both.
13
+ """
14
+
15
+ import importlib.util
16
+ import json
17
+ import logging
18
+ import unittest
19
+ from pathlib import Path
20
+
21
+
22
+ def _load_proxy_module():
23
+ proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
24
+ spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
25
+ assert spec is not None and spec.loader is not None
26
+ module = importlib.util.module_from_spec(spec)
27
+ spec.loader.exec_module(module)
28
+ return module
29
+
30
+
31
+ proxy = _load_proxy_module()
32
+
33
+
34
+ def _resp(message, finish="stop", usage=None):
35
+ return {
36
+ "choices": [{"message": message, "finish_reason": finish}],
37
+ "usage": usage or {"prompt_tokens": 10, "completion_tokens": 7},
38
+ }
39
+
40
+
41
+ class NonStreamRespLog(unittest.TestCase):
42
+ def _convert(self, openai_resp):
43
+ with self.assertLogs("uap.anthropic_proxy", level="INFO") as captured:
44
+ out = proxy.openai_to_anthropic_response(openai_resp, "qwen")
45
+ return out, [r for r in captured.output if "RESP: finish=" in r]
46
+
47
+ def test_text_turn_logs_its_outcome(self):
48
+ _, lines = self._convert(_resp({"role": "assistant", "content": "hello world"}))
49
+ self.assertEqual(len(lines), 1, "a non-streaming turn must log exactly one RESP")
50
+ line = lines[0]
51
+ self.assertIn("finish=stop", line)
52
+ self.assertIn("output_tokens=7", line)
53
+ self.assertIn("text_len=11", line)
54
+ self.assertIn("hello world", line)
55
+
56
+ def test_tool_turn_names_the_tool_and_its_args(self):
57
+ # The whole point: "what did it DO this round" has to be readable.
58
+ _, lines = self._convert(
59
+ _resp(
60
+ {
61
+ "role": "assistant",
62
+ "content": None,
63
+ "tool_calls": [
64
+ {
65
+ "id": "call_1",
66
+ "type": "function",
67
+ "function": {
68
+ "name": "read_file",
69
+ "arguments": json.dumps({"path": "setup.sql", "offset": 607}),
70
+ },
71
+ }
72
+ ],
73
+ },
74
+ finish="tool_calls",
75
+ )
76
+ )
77
+ self.assertEqual(len(lines), 1)
78
+ self.assertIn("read_file", lines[0])
79
+ self.assertIn("setup.sql", lines[0])
80
+ self.assertIn("607", lines[0], "the args are what distinguish paging from a re-read")
81
+
82
+ def test_empty_completion_is_visible_as_empty(self):
83
+ # An empty completion is a failure mode with its own history here
84
+ # (decode-compliance/budget truncation). It must not read as silence.
85
+ _, lines = self._convert(
86
+ _resp({"role": "assistant", "content": ""}, finish="length",
87
+ usage={"prompt_tokens": 5, "completion_tokens": 0})
88
+ )
89
+ self.assertEqual(len(lines), 1)
90
+ self.assertIn("text_len=0", lines[0])
91
+ self.assertIn("finish=length", lines[0])
92
+
93
+ def test_shape_matches_the_streaming_line_so_one_parser_reads_both(self):
94
+ _, lines = self._convert(_resp({"role": "assistant", "content": "x"}))
95
+ for field in ("RESP: finish=", "output_tokens=", "text_len=", "text=", "tool_calls=", "args="):
96
+ self.assertIn(field, lines[0], field)
97
+ self.assertIn("path=json", lines[0], "…while still being distinguishable")
98
+
99
+ def test_logging_never_breaks_the_conversion(self):
100
+ # Fail-soft: the response is the product, the log line is not. A
101
+ # malformed content block must not cost the client its answer.
102
+ out, _ = self._convert(_resp({"role": "assistant", "content": "fine"}))
103
+ self.assertEqual(out["role"], "assistant")
104
+ self.assertEqual(out["content"][0]["text"], "fine")
105
+ self.assertEqual(out["stop_reason"], "end_turn")
106
+
107
+
108
+ class RespLogSurvivesOddContent(unittest.TestCase):
109
+ def test_non_dict_content_blocks_do_not_raise(self):
110
+ logging.getLogger("uap.anthropic_proxy").setLevel(logging.INFO)
111
+ # Defensive: content is assembled upstream and has been non-uniform
112
+ # before (thinking promotion, text-tool extraction).
113
+ proxy._log_non_stream_resp(["not a dict", {"type": "text", "text": "ok"}], "stop", {})
114
+
115
+
116
+ if __name__ == "__main__":
117
+ unittest.main()