@miller-tech/uap 1.20.44 → 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.44",
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
  ):
@@ -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):