@miller-tech/uap 1.133.2 → 1.134.0
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/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +4 -1
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/config-command.d.ts +49 -0
- package/dist/cli/config-command.d.ts.map +1 -0
- package/dist/cli/config-command.js +465 -0
- package/dist/cli/config-command.js.map +1 -0
- package/dist/cli/config-wizard.d.ts +13 -0
- package/dist/cli/config-wizard.d.ts.map +1 -0
- package/dist/cli/config-wizard.js +134 -0
- package/dist/cli/config-wizard.js.map +1 -0
- package/dist/cli/guided-setup.d.ts.map +1 -1
- package/dist/cli/guided-setup.js +11 -0
- package/dist/cli/guided-setup.js.map +1 -1
- package/dist/cli/policy.d.ts.map +1 -1
- package/dist/cli/policy.js +34 -0
- package/dist/cli/policy.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +22 -0
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/policy-recommendations.d.ts +29 -0
- package/dist/config/policy-recommendations.d.ts.map +1 -0
- package/dist/config/policy-recommendations.js +108 -0
- package/dist/config/policy-recommendations.js.map +1 -0
- package/dist/config/settings-registry.d.ts +65 -0
- package/dist/config/settings-registry.d.ts.map +1 -0
- package/dist/config/settings-registry.js +334 -0
- package/dist/config/settings-registry.js.map +1 -0
- package/docs/INDEX.md +2 -0
- package/docs/guides/POLICY_SELECTION.md +98 -0
- package/docs/reference/CONFIGURATION_REFERENCE.md +688 -0
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +137 -12
|
@@ -184,6 +184,27 @@ PROXY_CONTEXT_WINDOW = int(os.environ.get("PROXY_CONTEXT_WINDOW", "0"))
|
|
|
184
184
|
PROXY_CONTEXT_PRUNE_THRESHOLD = float(
|
|
185
185
|
os.environ.get("PROXY_CONTEXT_PRUNE_THRESHOLD", "0.85")
|
|
186
186
|
)
|
|
187
|
+
# Compaction forcing (Option A, 2026-07-10): Claude Code decides when to
|
|
188
|
+
# auto-compact against ITS believed model window (~200k) using
|
|
189
|
+
# /v1/messages/count_tokens, so an HONEST count on a smaller local rail lets
|
|
190
|
+
# every session grow into the "thrash band" — above the rail (per-request
|
|
191
|
+
# critical prunes, findings evaporate, read-only doom loops; observed live:
|
|
192
|
+
# 87 reads / 0 writes in 30 min at 122% utilization) but below the client's
|
|
193
|
+
# compact trigger (~92.5% of 200k). Scaling the reported count makes the
|
|
194
|
+
# client compact BEFORE the rail. "auto" (default) derives the scale from the
|
|
195
|
+
# LIVE rail at call time — assumed_window / (rail * target_fraction) — so it
|
|
196
|
+
# tracks rail resizes; a number forces that scale; "1"/"off" disables.
|
|
197
|
+
PROXY_COUNT_TOKENS_SCALE = os.environ.get("PROXY_COUNT_TOKENS_SCALE", "auto")
|
|
198
|
+
PROXY_CLIENT_ASSUMED_WINDOW = int(
|
|
199
|
+
os.environ.get("PROXY_CLIENT_ASSUMED_WINDOW", "200000")
|
|
200
|
+
)
|
|
201
|
+
# Where compaction should land sessions, as a fraction of the rail. Unset
|
|
202
|
+
# (0) = auto: just under the pruner threshold (x0.95), so the client's own
|
|
203
|
+
# compaction fires BEFORE the proxy ever needs to prune — the pruner stays on
|
|
204
|
+
# purely as a backstop. An explicit value overrides (clamped to <1).
|
|
205
|
+
PROXY_COMPACT_TARGET_FRACTION = float(
|
|
206
|
+
os.environ.get("PROXY_COMPACT_TARGET_FRACTION", "0")
|
|
207
|
+
)
|
|
187
208
|
PROXY_CONTEXT_PRUNE_TARGET_FRACTION = float(
|
|
188
209
|
os.environ.get("PROXY_CONTEXT_PRUNE_TARGET_FRACTION", "0.50")
|
|
189
210
|
)
|
|
@@ -8936,7 +8957,7 @@ async def _maybe_apply_recipe(anthropic_resp, anthropic_body, openai_body, clien
|
|
|
8936
8957
|
return anthropic_resp
|
|
8937
8958
|
|
|
8938
8959
|
|
|
8939
|
-
async def _heartbeat_then_buffered(produce_coro, model: str):
|
|
8960
|
+
async def _heartbeat_then_buffered(produce_coro, model: str, input_tokens: int = 0):
|
|
8940
8961
|
"""SSE generator: keep-alive heartbeat wrapper for the guarded-non-stream path.
|
|
8941
8962
|
|
|
8942
8963
|
Emits an immediate ``message_start`` so the client registers an active
|
|
@@ -8953,7 +8974,7 @@ async def _heartbeat_then_buffered(produce_coro, model: str):
|
|
|
8953
8974
|
msg_id = f"msg_{uuid.uuid4().hex[:24]}"
|
|
8954
8975
|
yield (
|
|
8955
8976
|
f"event: message_start\n"
|
|
8956
|
-
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens':
|
|
8977
|
+
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens': input_tokens, 'output_tokens': 0}}})}\n\n"
|
|
8957
8978
|
)
|
|
8958
8979
|
interval = PROXY_STREAM_HEARTBEAT_SECS if PROXY_STREAM_HEARTBEAT_SECS > 0 else 15.0
|
|
8959
8980
|
task = asyncio.ensure_future(produce_coro)
|
|
@@ -9011,7 +9032,13 @@ async def stream_anthropic_message(anthropic_resp: dict, emit_message_start: boo
|
|
|
9011
9032
|
"model": anthropic_resp.get("model", "unknown"),
|
|
9012
9033
|
"stop_reason": None,
|
|
9013
9034
|
"stop_sequence": None,
|
|
9014
|
-
"usage": {
|
|
9035
|
+
"usage": {
|
|
9036
|
+
"input_tokens": int(
|
|
9037
|
+
(anthropic_resp.get("usage", {}) or {}).get("input_tokens", 0)
|
|
9038
|
+
* _count_tokens_scale()
|
|
9039
|
+
),
|
|
9040
|
+
"output_tokens": 0,
|
|
9041
|
+
},
|
|
9015
9042
|
}
|
|
9016
9043
|
yield f"event: message_start\ndata: {json.dumps({'type': 'message_start', 'message': message})}\n\n"
|
|
9017
9044
|
|
|
@@ -9053,11 +9080,13 @@ async def stream_anthropic_message(anthropic_resp: dict, emit_message_start: boo
|
|
|
9053
9080
|
)
|
|
9054
9081
|
block_index += 1
|
|
9055
9082
|
|
|
9056
|
-
|
|
9083
|
+
resp_usage = anthropic_resp.get("usage", {}) or {}
|
|
9084
|
+
output_tokens = resp_usage.get("output_tokens", 0)
|
|
9085
|
+
scaled_input = int(resp_usage.get("input_tokens", 0) * _count_tokens_scale())
|
|
9057
9086
|
stop_reason = anthropic_resp.get("stop_reason", "end_turn")
|
|
9058
9087
|
yield (
|
|
9059
9088
|
"event: message_delta\n"
|
|
9060
|
-
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': stop_reason, 'stop_sequence': None}, 'usage': {'output_tokens': output_tokens}})}\n\n"
|
|
9089
|
+
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': stop_reason, 'stop_sequence': None}, 'usage': {'input_tokens': scaled_input, 'output_tokens': output_tokens}})}\n\n"
|
|
9061
9090
|
)
|
|
9062
9091
|
yield f"event: message_stop\ndata: {json.dumps({'type': 'message_stop'})}\n\n"
|
|
9063
9092
|
|
|
@@ -9084,10 +9113,11 @@ async def stream_anthropic_response(
|
|
|
9084
9113
|
msg_id = f"msg_{uuid.uuid4().hex[:24]}"
|
|
9085
9114
|
stream_started = time.monotonic()
|
|
9086
9115
|
|
|
9087
|
-
# message_start
|
|
9116
|
+
# message_start — carries the client's (scaled) input size; a hardcoded 0
|
|
9117
|
+
# here blinded clients to their own context usage (compaction never fired).
|
|
9088
9118
|
yield (
|
|
9089
9119
|
f"event: message_start\n"
|
|
9090
|
-
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens':
|
|
9120
|
+
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens': _client_input_tokens(monitor), 'output_tokens': 0}}})}\n\n"
|
|
9091
9121
|
)
|
|
9092
9122
|
|
|
9093
9123
|
# content_block_start for text (index 0)
|
|
@@ -9392,10 +9422,12 @@ async def stream_anthropic_response(
|
|
|
9392
9422
|
if _is_unexpected_end_turn(synthetic_openai_resp, anthropic_body):
|
|
9393
9423
|
monitor.unexpected_end_turn_count += 1
|
|
9394
9424
|
|
|
9395
|
-
# message_delta with final stop reason
|
|
9425
|
+
# message_delta with final stop reason. Repeats the (scaled) input size so
|
|
9426
|
+
# clients that read usage from the delta rather than message_start still
|
|
9427
|
+
# see their context usage — the compaction-forcing signal.
|
|
9396
9428
|
yield (
|
|
9397
9429
|
f"event: message_delta\n"
|
|
9398
|
-
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': finish_reason, 'stop_sequence': None}, 'usage': {'output_tokens': output_tokens}})}\n\n"
|
|
9430
|
+
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': finish_reason, 'stop_sequence': None}, 'usage': {'input_tokens': _client_input_tokens(monitor), 'output_tokens': output_tokens}})}\n\n"
|
|
9399
9431
|
)
|
|
9400
9432
|
|
|
9401
9433
|
# message_stop
|
|
@@ -9563,7 +9595,98 @@ async def count_tokens(request: Request):
|
|
|
9563
9595
|
status_code=400,
|
|
9564
9596
|
media_type="application/json",
|
|
9565
9597
|
)
|
|
9566
|
-
|
|
9598
|
+
est = estimate_total_tokens(body)
|
|
9599
|
+
scale = _count_tokens_scale()
|
|
9600
|
+
if scale > 1.0:
|
|
9601
|
+
scaled = int(est * scale)
|
|
9602
|
+
# Once per scale value, explain the discrepancy an operator would
|
|
9603
|
+
# otherwise chase between this endpoint and the pruner's numbers.
|
|
9604
|
+
global _count_scale_logged
|
|
9605
|
+
if _count_scale_logged != scale:
|
|
9606
|
+
_count_scale_logged = scale
|
|
9607
|
+
logger.info(
|
|
9608
|
+
"COMPACT FORCING: scaling count_tokens by %.2f "
|
|
9609
|
+
"(client assumed window %d, rail %d) — client auto-compact "
|
|
9610
|
+
"fires at ~%d real tokens, before the pruner",
|
|
9611
|
+
scale,
|
|
9612
|
+
PROXY_CLIENT_ASSUMED_WINDOW,
|
|
9613
|
+
default_context_window if default_context_window > 0 else PROXY_CONTEXT_WINDOW,
|
|
9614
|
+
int(PROXY_CLIENT_ASSUMED_WINDOW * 0.925 / scale),
|
|
9615
|
+
)
|
|
9616
|
+
return {"input_tokens": scaled}
|
|
9617
|
+
return {"input_tokens": est}
|
|
9618
|
+
|
|
9619
|
+
|
|
9620
|
+
_count_scale_logged: float = 0.0
|
|
9621
|
+
|
|
9622
|
+
|
|
9623
|
+
def _count_tokens_scale() -> float:
|
|
9624
|
+
"""Resolve the count_tokens compaction-forcing scale (>= 1.0; 1.0 = off).
|
|
9625
|
+
|
|
9626
|
+
"auto" derives it from the LIVE rail each call so a rail resize (server
|
|
9627
|
+
restart with a different --ctx-size) re-tunes the client's compact point
|
|
9628
|
+
without a proxy restart.
|
|
9629
|
+
"""
|
|
9630
|
+
raw = PROXY_COUNT_TOKENS_SCALE.strip().lower()
|
|
9631
|
+
if raw in ("", "off", "none", "0", "1", "1.0"):
|
|
9632
|
+
return 1.0
|
|
9633
|
+
if raw != "auto":
|
|
9634
|
+
try:
|
|
9635
|
+
return max(1.0, float(raw))
|
|
9636
|
+
except ValueError:
|
|
9637
|
+
return 1.0
|
|
9638
|
+
window = default_context_window if default_context_window > 0 else PROXY_CONTEXT_WINDOW
|
|
9639
|
+
if window <= 0:
|
|
9640
|
+
return 1.0
|
|
9641
|
+
frac = (
|
|
9642
|
+
PROXY_COMPACT_TARGET_FRACTION
|
|
9643
|
+
if 0 < PROXY_COMPACT_TARGET_FRACTION < 1
|
|
9644
|
+
# Auto: land compaction just under the pruner trigger so the client
|
|
9645
|
+
# compacts before the proxy ever prunes (pruner = backstop only).
|
|
9646
|
+
else min(0.9, PROXY_CONTEXT_PRUNE_THRESHOLD * 0.95)
|
|
9647
|
+
)
|
|
9648
|
+
target = window * frac
|
|
9649
|
+
if target <= 0 or PROXY_CLIENT_ASSUMED_WINDOW <= target:
|
|
9650
|
+
# Rail already exceeds the client's own compact point — honest counts
|
|
9651
|
+
# are fine, the client compacts before the rail unaided.
|
|
9652
|
+
return 1.0
|
|
9653
|
+
return PROXY_CLIENT_ASSUMED_WINDOW / target
|
|
9654
|
+
|
|
9655
|
+
|
|
9656
|
+
def _client_input_tokens(monitor) -> int:
|
|
9657
|
+
"""Input size to REPORT to the client, scaled for compaction forcing.
|
|
9658
|
+
|
|
9659
|
+
Streaming responses historically hardcoded ``input_tokens: 0`` in
|
|
9660
|
+
message_start — the client could never see its own context usage, so its
|
|
9661
|
+
auto-compaction NEVER fired and sessions ground against the pruner forever
|
|
9662
|
+
(the actual root cause of the interactive thrash band; count_tokens
|
|
9663
|
+
scaling alone was insufficient because the dominant clients don't call
|
|
9664
|
+
it). Reports the PRE-prune estimate — the client's own conversation size,
|
|
9665
|
+
which is what its compaction decision is about — times the forcing scale.
|
|
9666
|
+
"""
|
|
9667
|
+
est = (
|
|
9668
|
+
getattr(monitor, "pre_prune_input_tokens", 0)
|
|
9669
|
+
or getattr(monitor, "last_input_tokens", 0)
|
|
9670
|
+
or 0
|
|
9671
|
+
)
|
|
9672
|
+
return int(est * _count_tokens_scale())
|
|
9673
|
+
|
|
9674
|
+
|
|
9675
|
+
def _scale_client_usage(anthropic_resp: dict) -> dict:
|
|
9676
|
+
"""Shallow-copy a finalized response with usage.input_tokens scaled for the
|
|
9677
|
+
CLIENT. Internal consumers (pruner accounting, telemetry) read the original
|
|
9678
|
+
dict's honest numbers — only the wire copy lies, and it lies consistently
|
|
9679
|
+
with count_tokens and the streaming frames."""
|
|
9680
|
+
scale = _count_tokens_scale()
|
|
9681
|
+
usage = anthropic_resp.get("usage")
|
|
9682
|
+
if scale <= 1.0 or not isinstance(usage, dict):
|
|
9683
|
+
return anthropic_resp
|
|
9684
|
+
out = dict(anthropic_resp)
|
|
9685
|
+
out["usage"] = {
|
|
9686
|
+
**usage,
|
|
9687
|
+
"input_tokens": int(usage.get("input_tokens", 0) * scale),
|
|
9688
|
+
}
|
|
9689
|
+
return out
|
|
9567
9690
|
|
|
9568
9691
|
|
|
9569
9692
|
@app.post("/v1/messages")
|
|
@@ -10037,7 +10160,7 @@ async def messages(request: Request):
|
|
|
10037
10160
|
|
|
10038
10161
|
if PROXY_STREAM_HEARTBEAT_SECS > 0:
|
|
10039
10162
|
return StreamingResponse(
|
|
10040
|
-
_heartbeat_then_buffered(_produce_guarded(), model),
|
|
10163
|
+
_heartbeat_then_buffered(_produce_guarded(), model, _client_input_tokens(monitor)),
|
|
10041
10164
|
media_type="text/event-stream",
|
|
10042
10165
|
headers={
|
|
10043
10166
|
"Cache-Control": "no-cache",
|
|
@@ -10466,7 +10589,9 @@ async def messages(request: Request):
|
|
|
10466
10589
|
await asyncio.to_thread(
|
|
10467
10590
|
_record_project_telemetry, body, model, anthropic_resp.get("usage", {})
|
|
10468
10591
|
)
|
|
10469
|
-
|
|
10592
|
+
# Wire copy only: telemetry above and monitor accounting keep the
|
|
10593
|
+
# honest numbers; the client sees the compaction-forcing scale.
|
|
10594
|
+
return _scale_client_usage(anthropic_resp)
|
|
10470
10595
|
|
|
10471
10596
|
|
|
10472
10597
|
@app.post("/anthropic/v1/messages")
|