@miller-tech/uap 1.84.0 → 1.84.1

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.84.0",
3
+ "version": "1.84.1",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3351,7 +3351,28 @@ def anthropic_to_openai_response(anthropic_resp: dict) -> dict:
3351
3351
  }
3352
3352
 
3353
3353
 
3354
+ # A3: the anthropic->openai tool conversion + schema sanitize walks every tool's
3355
+ # (often deeply nested) JSON schema. The tool set is IDENTICAL across every turn
3356
+ # of a session, so this recomputed the same result each turn (observed: ~1
3357
+ # SCHEMA SANITIZE log per turn). Cache by a stable hash of the tool definitions.
3358
+ # Downstream only READS the converted dicts and FILTERS the list (narrowing), so
3359
+ # returning the cached object directly is safe.
3360
+ _TOOL_CONVERT_CACHE: "OrderedDict[str, list]" = OrderedDict()
3361
+ _TOOL_CONVERT_CACHE_MAX = 32
3362
+
3363
+
3354
3364
  def _convert_anthropic_tools_to_openai(anthropic_tools: list[dict]) -> list[dict]:
3365
+ cache_key = None
3366
+ try:
3367
+ cache_key = hashlib.sha1(
3368
+ json.dumps(anthropic_tools, sort_keys=True, default=str).encode("utf-8")
3369
+ ).hexdigest()
3370
+ except Exception:
3371
+ cache_key = None
3372
+ if cache_key is not None and cache_key in _TOOL_CONVERT_CACHE:
3373
+ _TOOL_CONVERT_CACHE.move_to_end(cache_key)
3374
+ return _TOOL_CONVERT_CACHE[cache_key]
3375
+
3355
3376
  converted = []
3356
3377
  removed_pattern_fields = 0
3357
3378
  for tool in anthropic_tools:
@@ -3375,6 +3396,10 @@ def _convert_anthropic_tools_to_openai(anthropic_tools: list[dict]) -> list[dict
3375
3396
  removed_pattern_fields,
3376
3397
  len(anthropic_tools),
3377
3398
  )
3399
+ if cache_key is not None:
3400
+ _TOOL_CONVERT_CACHE[cache_key] = converted
3401
+ if len(_TOOL_CONVERT_CACHE) > _TOOL_CONVERT_CACHE_MAX:
3402
+ _TOOL_CONVERT_CACHE.popitem(last=False)
3378
3403
  return converted
3379
3404
 
3380
3405
 
@@ -0,0 +1,49 @@
1
+ """Tests for A3: per-session tool-conversion cache."""
2
+ import importlib.util
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ proxy_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts" / "anthropic_proxy.py"
7
+ spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
8
+ ap = importlib.util.module_from_spec(spec)
9
+ spec.loader.exec_module(ap)
10
+
11
+ TOOLS = [
12
+ {"name": "Read", "description": "read", "input_schema": {"type": "object",
13
+ "properties": {"p": {"type": "string", "pattern": "^/.*"}}, "required": ["p"]}},
14
+ {"name": "Bash", "description": "run", "input_schema": {"type": "object",
15
+ "properties": {"cmd": {"type": "string"}}}},
16
+ ]
17
+
18
+
19
+ class ToolConvertCacheTest(unittest.TestCase):
20
+ def setUp(self):
21
+ ap._TOOL_CONVERT_CACHE.clear()
22
+
23
+ def test_correct_conversion_and_sanitize(self):
24
+ out = ap._convert_anthropic_tools_to_openai(TOOLS)
25
+ self.assertEqual(out[0]["function"]["name"], "Read")
26
+ # regex pattern field stripped by sanitize
27
+ self.assertNotIn("pattern", out[0]["function"]["parameters"]["properties"]["p"])
28
+
29
+ def test_second_call_is_cache_hit_same_object(self):
30
+ a = ap._convert_anthropic_tools_to_openai(TOOLS)
31
+ self.assertEqual(len(ap._TOOL_CONVERT_CACHE), 1)
32
+ b = ap._convert_anthropic_tools_to_openai([dict(t) for t in TOOLS]) # equal-by-value
33
+ self.assertIs(a, b, "identical tool set must return the cached object")
34
+
35
+ def test_different_tools_miss(self):
36
+ ap._convert_anthropic_tools_to_openai(TOOLS)
37
+ ap._convert_anthropic_tools_to_openai(TOOLS[:1])
38
+ self.assertEqual(len(ap._TOOL_CONVERT_CACHE), 2)
39
+
40
+ def test_cache_bounded(self):
41
+ for i in range(ap._TOOL_CONVERT_CACHE_MAX + 5):
42
+ ap._convert_anthropic_tools_to_openai(
43
+ [{"name": f"T{i}", "description": "", "input_schema": {"type": "object"}}]
44
+ )
45
+ self.assertLessEqual(len(ap._TOOL_CONVERT_CACHE), ap._TOOL_CONVERT_CACHE_MAX)
46
+
47
+
48
+ if __name__ == "__main__":
49
+ unittest.main()