@miller-tech/uap 1.20.43 → 1.20.45

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.20.43",
3
+ "version": "1.20.45",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -2472,8 +2472,21 @@ def _completion_blockers(
2472
2472
  def _sanitize_tool_schema_for_llama(schema):
2473
2473
  """Remove JSON Schema keywords that generate unsupported regex grammar.
2474
2474
 
2475
- llama.cpp's tool grammar generator can fail on regex-heavy schema fields
2476
- such as "pattern" and "patternProperties" (for example "\\w").
2475
+ llama.cpp's tool grammar generator can fail on regex-heavy schema fields:
2476
+
2477
+ - "pattern" / "patternProperties" — regex strings (e.g. "\\w").
2478
+ - "format" — string formats. llama.cpp's json-schema-to-grammar turns
2479
+ "format": "date" / "date-time" / "time" / "uuid" into grammar rules
2480
+ built from `\\d`, which its own GBNF parser then rejects with
2481
+ `error parsing grammar: unknown escape at \\d...` → `failed to parse
2482
+ grammar`. Observed on MCP tools with date fields (Atlassian
2483
+ getJiraIssue, tempo bulkCreateWorklogs). "format" is an advisory
2484
+ annotation — dropping it just leaves the field as an unconstrained
2485
+ string in the tool-call grammar, which is correct behaviour.
2486
+
2487
+ All three are stripped only when they appear as schema *keywords*, not
2488
+ when they are property *names* (a tool may legitimately have a parameter
2489
+ literally called "pattern" or "format").
2477
2490
  """
2478
2491
 
2479
2492
  removed = 0
@@ -2486,7 +2499,7 @@ def _sanitize_tool_schema_for_llama(schema):
2486
2499
  for key, value in node.items():
2487
2500
  key_is_property_name = parent_key in property_map_keys
2488
2501
  if (
2489
- key == "pattern"
2502
+ key in ("pattern", "format")
2490
2503
  and isinstance(value, str)
2491
2504
  and not key_is_property_name
2492
2505
  ):
@@ -8440,7 +8453,7 @@ async def models():
8440
8453
  {"id": "claude-haiku-4-5-20251001", "object": "model"},
8441
8454
  {"id": "claude-sonnet-4-6", "object": "model"},
8442
8455
  {"id": "claude-opus-4-7", "object": "model"},
8443
- {"id": "qwen36-27b-iq4xs", "object": "model"},
8456
+ {"id": "qwen36-35b-a3b-iq4xs", "object": "model"},
8444
8457
  ]
8445
8458
  }
8446
8459
 
@@ -284,6 +284,79 @@ class TestToolSchemaSanitization(unittest.TestCase):
284
284
  self.assertIn("pattern", params["required"])
285
285
  self.assertEqual(params["properties"]["pattern"]["type"], "string")
286
286
 
287
+ def test_convert_tools_strips_format_fields(self):
288
+ """A string field with "format": "date" must have format stripped.
289
+ llama.cpp's json-schema-to-grammar turns format:date/date-time/etc.
290
+ into `\\d`-based grammar rules that its own GBNF parser then rejects
291
+ ('unknown escape at \\d' -> 'failed to parse grammar'). Observed on
292
+ MCP tools like tempo bulkCreateWorklogs (a worklogEntries[].date
293
+ field) and Atlassian getJiraIssue."""
294
+ anthropic_tools = [
295
+ {
296
+ "name": "bulkCreateWorklogs",
297
+ "description": "test",
298
+ "input_schema": {
299
+ "type": "object",
300
+ "properties": {
301
+ "worklogEntries": {
302
+ "type": "array",
303
+ "items": {
304
+ "type": "object",
305
+ "properties": {
306
+ "date": {
307
+ "type": "string",
308
+ "format": "date",
309
+ },
310
+ "started": {
311
+ "type": "string",
312
+ "format": "date-time",
313
+ },
314
+ },
315
+ },
316
+ }
317
+ },
318
+ },
319
+ }
320
+ ]
321
+
322
+ converted = proxy._convert_anthropic_tools_to_openai(anthropic_tools)
323
+ item = converted[0]["function"]["parameters"]["properties"][
324
+ "worklogEntries"
325
+ ]["items"]
326
+ self.assertNotIn("format", item["properties"]["date"])
327
+ self.assertNotIn("format", item["properties"]["started"])
328
+ # The field itself and its type survive — only the format hint goes.
329
+ self.assertEqual(item["properties"]["date"]["type"], "string")
330
+
331
+ def test_convert_tools_keeps_property_named_format(self):
332
+ """A tool parameter literally named "format" (e.g. an output-format
333
+ selector) must NOT be stripped — only the format *keyword* is."""
334
+ anthropic_tools = [
335
+ {
336
+ "name": "ExportTool",
337
+ "description": "test",
338
+ "input_schema": {
339
+ "type": "object",
340
+ "required": ["format"],
341
+ "properties": {
342
+ "format": {
343
+ "type": "string",
344
+ "enum": ["json", "csv", "yaml"],
345
+ "description": "Output format",
346
+ },
347
+ },
348
+ },
349
+ }
350
+ ]
351
+
352
+ converted = proxy._convert_anthropic_tools_to_openai(anthropic_tools)
353
+ params = converted[0]["function"]["parameters"]
354
+ self.assertIn("format", params["required"])
355
+ self.assertEqual(params["properties"]["format"]["type"], "string")
356
+ self.assertEqual(
357
+ params["properties"]["format"]["enum"], ["json", "csv", "yaml"]
358
+ )
359
+
287
360
 
288
361
  class TestStreamGuardedPathSelection(unittest.TestCase):
289
362
  def test_required_tool_turn_uses_guarded_non_stream(self):
@@ -5028,12 +5101,13 @@ class TestModelsEndpoint(unittest.TestCase):
5028
5101
  self.assertIn("claude-sonnet-4-6", ids)
5029
5102
  self.assertIn("claude-opus-4-7", ids)
5030
5103
 
5031
- # Local model — what llama-server actually serves. Updated
5032
- # 2026-05-15 from qwen35-a3b-iq4xs after the switch from 35B-A3B
5033
- # MoE to Qwen3.6-27B dense (see project_active_server memory).
5034
- # Requests for this ID route locally even with __local_only__
5035
- # passthrough sentinel set.
5036
- self.assertIn("qwen36-27b-iq4xs", ids)
5104
+ # Local model — what llama-server actually serves. Tracks the
5105
+ # active model: qwen36-35b-a3b-iq4xs as of the 2026-05-17 switch
5106
+ # back to Qwen3.6-35B-A3B MoE from the 27B dense (see
5107
+ # project_active_server memory). Requests for this ID route
5108
+ # locally even with the __local_only__ passthrough sentinel set.
5109
+ self.assertIn("qwen36-35b-a3b-iq4xs", ids)
5110
+ self.assertNotIn("qwen36-27b-iq4xs", ids)
5037
5111
  self.assertNotIn("qwen35-a3b-iq4xs", ids)
5038
5112
 
5039
5113
  def test_models_endpoint_drops_stale_4_6_dated_variants(self):