@miller-tech/uap 1.15.11 → 1.15.12

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.11",
3
+ "version": "1.15.12",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1343,19 +1343,56 @@ def _last_user_has_tool_result(anthropic_body: dict) -> bool:
1343
1343
  return False
1344
1344
 
1345
1345
 
1346
+ def _sanitize_tool_schema_for_llama(schema):
1347
+ """Remove JSON Schema keywords that generate unsupported regex grammar.
1348
+
1349
+ llama.cpp's tool grammar generator can fail on regex-heavy schema fields
1350
+ such as "pattern" and "patternProperties" (for example "\\w").
1351
+ """
1352
+
1353
+ removed = 0
1354
+
1355
+ def _walk(node):
1356
+ nonlocal removed
1357
+ if isinstance(node, dict):
1358
+ cleaned = {}
1359
+ for key, value in node.items():
1360
+ if key in {"pattern", "patternProperties"}:
1361
+ removed += 1
1362
+ continue
1363
+ cleaned[key] = _walk(value)
1364
+ return cleaned
1365
+ if isinstance(node, list):
1366
+ return [_walk(item) for item in node]
1367
+ return node
1368
+
1369
+ return _walk(schema), removed
1370
+
1371
+
1346
1372
  def _convert_anthropic_tools_to_openai(anthropic_tools: list[dict]) -> list[dict]:
1347
1373
  converted = []
1374
+ removed_pattern_fields = 0
1348
1375
  for tool in anthropic_tools:
1376
+ input_schema, removed = _sanitize_tool_schema_for_llama(
1377
+ tool.get("input_schema", {})
1378
+ )
1379
+ removed_pattern_fields += removed
1349
1380
  converted.append(
1350
1381
  {
1351
1382
  "type": "function",
1352
1383
  "function": {
1353
1384
  "name": tool.get("name", ""),
1354
1385
  "description": tool.get("description", ""),
1355
- "parameters": tool.get("input_schema", {}),
1386
+ "parameters": input_schema,
1356
1387
  },
1357
1388
  }
1358
1389
  )
1390
+ if removed_pattern_fields > 0:
1391
+ logger.warning(
1392
+ "TOOL SCHEMA SANITIZE: removed %d regex pattern fields from %d tools",
1393
+ removed_pattern_fields,
1394
+ len(anthropic_tools),
1395
+ )
1359
1396
  return converted
1360
1397
 
1361
1398
 
@@ -100,6 +100,61 @@ class TestProxyConfigTuning(unittest.TestCase):
100
100
  setattr(proxy, "PROXY_CONTEXT_PRUNE_TARGET_FRACTION", old_target)
101
101
 
102
102
 
103
+ class TestToolSchemaSanitization(unittest.TestCase):
104
+ def test_convert_tools_strips_pattern_fields(self):
105
+ anthropic_tools = [
106
+ {
107
+ "name": "Sample",
108
+ "description": "test",
109
+ "input_schema": {
110
+ "type": "object",
111
+ "properties": {
112
+ "id": {
113
+ "type": "string",
114
+ "pattern": "^[\\w-]+$",
115
+ }
116
+ },
117
+ "required": ["id"],
118
+ },
119
+ }
120
+ ]
121
+
122
+ converted = proxy._convert_anthropic_tools_to_openai(anthropic_tools)
123
+ params = converted[0]["function"]["parameters"]
124
+ self.assertEqual(params["properties"]["id"]["type"], "string")
125
+ self.assertNotIn("pattern", params["properties"]["id"])
126
+
127
+ def test_convert_tools_strips_pattern_properties_fields(self):
128
+ anthropic_tools = [
129
+ {
130
+ "name": "Sample",
131
+ "description": "test",
132
+ "input_schema": {
133
+ "type": "object",
134
+ "patternProperties": {
135
+ "^x-": {"type": "string"},
136
+ },
137
+ "properties": {
138
+ "meta": {
139
+ "type": "object",
140
+ "properties": {
141
+ "tag": {
142
+ "type": "string",
143
+ "pattern": "^[a-z]+$",
144
+ }
145
+ },
146
+ }
147
+ },
148
+ },
149
+ }
150
+ ]
151
+
152
+ converted = proxy._convert_anthropic_tools_to_openai(anthropic_tools)
153
+ params = converted[0]["function"]["parameters"]
154
+ self.assertNotIn("patternProperties", params)
155
+ self.assertNotIn("pattern", params["properties"]["meta"]["properties"]["tag"])
156
+
157
+
103
158
  class TestStreamGuardedPathSelection(unittest.TestCase):
104
159
  def test_required_tool_turn_uses_guarded_non_stream(self):
105
160
  old_force = getattr(proxy, "PROXY_FORCE_NON_STREAM")