@miller-tech/uap 1.185.0 → 1.185.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.185.0",
3
+ "version": "1.185.1",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "start": "node dist/bin/cli.js",
22
22
  "test": "vitest",
23
23
  "test:ci": "vitest run",
24
- "test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core",
24
+ "test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core tools.agents.tests.test_models_context_window",
25
25
  "test:coverage": "vitest --coverage",
26
26
  "bench": "vitest --config vitest.bench.config.ts",
27
27
  "lint": "eslint src --ext .ts",
@@ -12650,6 +12650,57 @@ def _parse_anthropic_sse_to_message(raw: bytes) -> dict | None:
12650
12650
  }
12651
12651
 
12652
12652
 
12653
+ ADVERTISED_MODEL_IDS = (
12654
+ "claude-haiku-4-5-20251001",
12655
+ "claude-sonnet-4-6",
12656
+ "claude-sonnet-5-20250514",
12657
+ "claude-fable-5",
12658
+ "qwen36-35b-a3b-iq4xs",
12659
+ )
12660
+
12661
+ # Keys OpenAI-compatible clients probe for a model's context window. There is no
12662
+ # standard, so emit the common spellings rather than betting on one: hermes reads
12663
+ # context_length / context_window / max_context_length / max_model_len / n_ctx
12664
+ # (agent/model_metadata.py:_CONTEXT_LENGTH_KEYS), LiteLLM and vLLM prefer
12665
+ # max_model_len, LM Studio uses max_context_length. They are all the same number.
12666
+ _CONTEXT_WINDOW_KEYS = (
12667
+ "context_length",
12668
+ "context_window",
12669
+ "max_context_length",
12670
+ "max_model_len",
12671
+ "n_ctx",
12672
+ )
12673
+
12674
+
12675
+ def _model_entry(model_id: str) -> dict:
12676
+ """One /v1/models row, carrying the context window when we know it.
12677
+
12678
+ Advertising this is not cosmetic. A client that cannot discover the window
12679
+ cannot size its own history to it, so it grows unbounded and the FIRST thing
12680
+ that notices is this proxy — which can then only prune blind, after the
12681
+ prompt is already built.
12682
+
12683
+ Live, 2026-08-04: hermes has a context compressor and probes for exactly
12684
+ these keys. We advertised bare {"id", "object"} rows, its model cache held no
12685
+ entry for our model, so the compressor never engaged. It sent 470 messages /
12686
+ 219,957 tokens against a 130,048 window (169%), and the proxy CRITICAL PRUNEd
12687
+ 290 of them to fit — 61 such events in 18 hours. Raising the window from
12688
+ 86,784 to 130,048 had not helped, because the growth was never sized to the
12689
+ window in the first place.
12690
+
12691
+ Only advertised for models this proxy serves LOCALLY. A model that
12692
+ round-trips to api.anthropic.com has its own (much larger) window, and
12693
+ stamping the local llama.cpp figure on it would make clients truncate
12694
+ needlessly — a worse bug than the one being fixed, so when in doubt emit
12695
+ nothing and leave the client on its own defaults.
12696
+ """
12697
+ entry = {"id": model_id, "object": "model"}
12698
+ if PROXY_CONTEXT_WINDOW > 0 and not _should_passthrough_model(model_id):
12699
+ for key in _CONTEXT_WINDOW_KEYS:
12700
+ entry[key] = PROXY_CONTEXT_WINDOW
12701
+ return entry
12702
+
12703
+
12653
12704
  @app.get("/v1/models")
12654
12705
  async def models():
12655
12706
  """Return available model list.
@@ -12666,15 +12717,7 @@ async def models():
12666
12717
  ANTHROPIC_PASSTHROUGH_MODELS=__local_only__ is set, all IDs (including
12667
12718
  the Claude ones below) are served by the local llama.cpp backend.
12668
12719
  """
12669
- return {
12670
- "data": [
12671
- {"id": "claude-haiku-4-5-20251001", "object": "model"},
12672
- {"id": "claude-sonnet-4-6", "object": "model"},
12673
- {"id": "claude-sonnet-5-20250514", "object": "model"},
12674
- {"id": "claude-fable-5", "object": "model"},
12675
- {"id": "qwen36-35b-a3b-iq4xs", "object": "model"},
12676
- ]
12677
- }
12720
+ return {"data": [_model_entry(mid) for mid in ADVERTISED_MODEL_IDS]}
12678
12721
 
12679
12722
 
12680
12723
  @app.get("/health")
@@ -0,0 +1,84 @@
1
+ """/v1/models must advertise the context window for locally-served models.
2
+
3
+ Regression (hermes, 2026-08-04): the endpoint returned bare {"id","object"}
4
+ rows. Hermes HAS a context compressor and probes for context_length /
5
+ context_window / max_context_length / max_model_len / n_ctx, found none, and its
6
+ model cache held no entry for our model — so the compressor never engaged. It
7
+ sent 470 messages / 219,957 tokens against a 130,048 window (169%) and the proxy
8
+ CRITICAL PRUNEd 290 of them; 61 such events in 18 hours. Raising the window from
9
+ 86,784 to 130,048 had not helped, because the growth was never sized to the
10
+ window at all.
11
+
12
+ A client that cannot discover the window cannot size its history to it.
13
+ """
14
+ import importlib.util
15
+ import os
16
+ import unittest
17
+ from pathlib import Path
18
+
19
+ proxy_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts" / "anthropic_proxy.py"
20
+
21
+
22
+ def load_proxy(window="130048", passthrough=None):
23
+ """Import a fresh proxy module under the given env (constants bind at import)."""
24
+ os.environ["PROXY_CONTEXT_WINDOW"] = window
25
+ if passthrough is None:
26
+ os.environ.pop("ANTHROPIC_PASSTHROUGH_MODELS", None)
27
+ else:
28
+ os.environ["ANTHROPIC_PASSTHROUGH_MODELS"] = passthrough
29
+ spec = importlib.util.spec_from_file_location("anthropic_proxy_ctx", proxy_path)
30
+ mod = importlib.util.module_from_spec(spec)
31
+ spec.loader.exec_module(mod)
32
+ return mod
33
+
34
+
35
+ class ModelsAdvertiseContextWindowTest(unittest.TestCase):
36
+ def tearDown(self):
37
+ os.environ.pop("PROXY_CONTEXT_WINDOW", None)
38
+ os.environ.pop("ANTHROPIC_PASSTHROUGH_MODELS", None)
39
+
40
+ def test_local_model_carries_the_window(self):
41
+ ap = load_proxy()
42
+ e = ap._model_entry("qwen36-35b-a3b-iq4xs")
43
+ self.assertEqual(e["context_length"], 130048)
44
+
45
+ def test_every_probed_key_is_emitted(self):
46
+ # There is no standard key. Clients disagree, so emit the common
47
+ # spellings — missing the one a client happens to read is the same
48
+ # failure as advertising nothing.
49
+ ap = load_proxy()
50
+ e = ap._model_entry("qwen36-35b-a3b-iq4xs")
51
+ for key in ("context_length", "context_window", "max_context_length",
52
+ "max_model_len", "n_ctx"):
53
+ self.assertEqual(e.get(key), 130048, key)
54
+
55
+ def test_passthrough_models_do_not_get_the_local_window(self):
56
+ # A model that round-trips to api.anthropic.com has a much larger window.
57
+ # Stamping the local llama.cpp figure on it would make clients truncate
58
+ # needlessly — worse than the bug being fixed.
59
+ ap = load_proxy(passthrough=None) # default patterns: Claude passes through
60
+ for mid in ("claude-sonnet-4-6", "claude-haiku-4-5-20251001"):
61
+ self.assertNotIn("context_length", ap._model_entry(mid), mid)
62
+
63
+ def test_local_only_sentinel_means_every_id_is_local(self):
64
+ ap = load_proxy(passthrough="__local_only__")
65
+ for mid in ap.ADVERTISED_MODEL_IDS:
66
+ self.assertEqual(ap._model_entry(mid).get("context_length"), 130048, mid)
67
+
68
+ def test_unset_window_advertises_nothing(self):
69
+ # Better to say nothing than to assert a wrong number.
70
+ ap = load_proxy(window="0")
71
+ self.assertEqual(
72
+ ap._model_entry("qwen36-35b-a3b-iq4xs"), {"id": "qwen36-35b-a3b-iq4xs", "object": "model"}
73
+ )
74
+
75
+ def test_entry_always_keeps_the_openai_shape(self):
76
+ ap = load_proxy()
77
+ for mid in ap.ADVERTISED_MODEL_IDS:
78
+ e = ap._model_entry(mid)
79
+ self.assertEqual(e["id"], mid)
80
+ self.assertEqual(e["object"], "model")
81
+
82
+
83
+ if __name__ == "__main__":
84
+ unittest.main()