coderouter-cli 2.11.2__py3-none-any.whl → 2.13.0__py3-none-any.whl

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.
@@ -6,6 +6,8 @@ Provides a shared char/4 token estimator used by:
6
6
  matcher for longContext auto-switch (v1.10).
7
7
  - :mod:`coderouter.guards.context_budget` — L1 context overflow
8
8
  detection (v2.0-F).
9
+ - ``POST /v1/messages/count_tokens`` (:mod:`coderouter.ingress.anthropic_routes`).
10
+ - :mod:`coderouter.language_tax` — CJK tax measurement.
9
11
 
10
12
  No external dependencies. The heuristic is ``total_chars // 4``:
11
13
 
@@ -18,6 +20,58 @@ No external dependencies. The heuristic is ``total_chars // 4``:
18
20
  this is a known gap; operators can lower the warn/trim thresholds
19
21
  to compensate (e.g. 0.60 / 0.70 instead of 0.80 / 0.90).
20
22
 
23
+ Which content blocks are counted (H-5, v2.12)
24
+ =============================================
25
+
26
+ Up to v2.11.x only ``text`` blocks were counted. Agent clients
27
+ (Claude Code, Cline, OpenClaw, …) put almost all of their context in
28
+ ``tool_result`` and ``tool_use`` blocks, so the estimate was off by
29
+ 5x at 20 turns and ~29x at 200 turns for a tool-heavy session. The
30
+ extractor now dispatches on the block ``type``:
31
+
32
+ ===================== ===========================================
33
+ block type contribution
34
+ ===================== ===========================================
35
+ ``text`` ``text`` verbatim
36
+ ``tool_result`` ``content`` — ``str`` verbatim, or the same
37
+ per-type rules applied recursively to the
38
+ block list (so a nested ``image`` is still 0)
39
+ ``tool_use`` ``name`` + ``json.dumps(input)``
40
+ ``thinking`` the ``thinking`` string (it is replayed back
41
+ to the model on the next turn, so it really
42
+ does occupy the context window)
43
+ ``image`` **0** — deliberately
44
+ ``redacted_thinking`` 0 (opaque ciphertext, not model-visible text)
45
+ anything else 0 (forward-compatible default)
46
+ ===================== ===========================================
47
+
48
+ The ``image`` exclusion is load-bearing, not an oversight: a naive
49
+ ``json.dumps(block)`` of a request carrying a 400 KB base64 PNG
50
+ over-estimates by ~35x (11,789 → 410,781 tokens measured), which
51
+ would make the context-budget guard shred the history of any session
52
+ that ever pasted a screenshot. Images are billed on their own axis
53
+ and must stay at 0.
54
+
55
+ Known cross-path inconsistency (out of scope for H-5)
56
+ -----------------------------------------------------
57
+ ``POST /v1/messages/count_tokens`` additionally appends the JSON of
58
+ the request's ``tools`` array before counting (see
59
+ ``ingress/anthropic_routes.py``), while
60
+ :func:`estimate_tokens_from_anthropic_request` /
61
+ :func:`estimate_tokens_from_body` do **not** count tool *definitions*
62
+ at all. A request with large tool schemas therefore gets a bigger
63
+ number from ``count_tokens`` than the context-budget guard sees. This
64
+ is pre-existing behavior and is intentionally left unchanged here —
65
+ tracked as a follow-up, not part of the H-5 fix.
66
+
67
+ Escape hatch
68
+ ------------
69
+ :func:`set_include_tool_content` (wired to the top-level config key
70
+ ``token_estimation_include_tool_content``) restores the exact v2.11.x
71
+ numbers by skipping ``tool_result`` / ``tool_use`` / ``thinking``
72
+ again. It exists only to unblock operators surprised by the new
73
+ (correct) guard/routing behavior and is scheduled for removal.
74
+
21
75
  Future (v2.0+): detect ``tiktoken`` at import time and offer a
22
76
  precision mode. The public API is stable either way — callers get
23
77
  an ``int`` estimate regardless of the backend.
@@ -25,6 +79,7 @@ an ``int`` estimate regardless of the backend.
25
79
 
26
80
  from __future__ import annotations
27
81
 
82
+ import json
28
83
  from typing import Any
29
84
 
30
85
  # ---------------------------------------------------------------------------
@@ -40,40 +95,153 @@ CHARS_PER_TOKEN_HEURISTIC: int = 4
40
95
  #: operators who use it should declare explicitly or via registry).
41
96
  DEFAULT_MAX_CONTEXT_TOKENS: int = 128_000
42
97
 
98
+ #: Depth cap for the recursive block walk. ``tool_result.content`` is
99
+ #: the only legal nesting level in the Anthropic wire format; the cap
100
+ #: just makes a hand-crafted / buggy payload unable to blow the stack.
101
+ _MAX_BLOCK_DEPTH: int = 4
102
+
103
+ #: Block types that never contribute characters, whatever the flags.
104
+ #: ``image`` is base64 (or a URL) billed on a separate axis;
105
+ #: ``redacted_thinking`` is opaque ciphertext the model never reads as
106
+ #: text. Counting either one over-estimates catastrophically.
107
+ _ZERO_CHAR_BLOCK_TYPES: frozenset[str] = frozenset(
108
+ {"image", "image_url", "redacted_thinking"}
109
+ )
110
+
111
+ #: Block types gated behind the ``include_tool_content`` switch.
112
+ _TOOL_BLOCK_TYPES: frozenset[str] = frozenset({"tool_result", "tool_use", "thinking"})
113
+
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # Process-wide opt-out (v2.11.x compatibility escape hatch)
117
+ # ---------------------------------------------------------------------------
118
+
119
+ #: Module-level default for :func:`set_include_tool_content`. Set from
120
+ #: ``CodeRouterConfig.token_estimation_include_tool_content`` at config
121
+ #: load time. A module global (rather than a parameter threaded through
122
+ #: every call site) is required because three of the four consumers —
123
+ #: the auto-router, ``count_tokens`` and language-tax — run *outside*
124
+ #: any profile context and must all agree on one number per process.
125
+ _INCLUDE_TOOL_CONTENT: bool = True
126
+
127
+
128
+ def set_include_tool_content(enabled: bool) -> None:
129
+ """Set the process-wide default for tool-content counting.
130
+
131
+ Called from ``CodeRouterConfig``'s validator so a single config key
132
+ controls every consumer of this module consistently. ``False``
133
+ reproduces v2.11.x estimates exactly.
134
+ """
135
+ global _INCLUDE_TOOL_CONTENT
136
+ _INCLUDE_TOOL_CONTENT = bool(enabled)
137
+
138
+
139
+ def get_include_tool_content() -> bool:
140
+ """Return the current process-wide tool-content counting default."""
141
+ return _INCLUDE_TOOL_CONTENT
142
+
143
+
144
+ def _resolve_include(include_tool_content: bool | None) -> bool:
145
+ return _INCLUDE_TOOL_CONTENT if include_tool_content is None else include_tool_content
146
+
43
147
 
44
148
  # ---------------------------------------------------------------------------
45
149
  # Text extraction helpers
46
150
  # ---------------------------------------------------------------------------
47
151
 
48
152
 
49
- def _extract_text_from_content(content: Any) -> str:
153
+ def _tool_use_text(block: dict[str, Any]) -> str:
154
+ """Serialize a ``tool_use`` block's name + input for char counting.
155
+
156
+ ``input`` is arbitrary JSON, so its wire size is the honest proxy
157
+ for how much context it occupies. ``ensure_ascii=False`` keeps CJK
158
+ arguments at their real character count instead of inflating them
159
+ into ``\\uXXXX`` escapes.
160
+ """
161
+ pieces: list[str] = []
162
+ name = block.get("name")
163
+ if isinstance(name, str):
164
+ pieces.append(name)
165
+ raw_input = block.get("input")
166
+ if raw_input is not None:
167
+ try:
168
+ pieces.append(json.dumps(raw_input, ensure_ascii=False))
169
+ except (TypeError, ValueError):
170
+ pieces.append(str(raw_input))
171
+ return "\n".join(pieces)
172
+
173
+
174
+ def _extract_text_from_content(
175
+ content: Any,
176
+ *,
177
+ include_tool_content: bool | None = None,
178
+ _depth: int = 0,
179
+ ) -> str:
50
180
  """Extract concatenated text from a message's ``content`` field.
51
181
 
52
182
  Handles:
53
183
  - ``str`` (short form) → returned verbatim.
54
- - ``list[dict]`` (multimodal blocks) → text-type blocks
55
- concatenated with newlines. Image / tool_use / tool_result
56
- blocks contribute 0 chars (billed differently or don't
57
- consume text-token budget).
184
+ - ``list[dict]`` (multimodal blocks) → dispatched per block
185
+ ``type``; see the module docstring's table for the exact rules.
186
+ ``image`` blocks always contribute 0 chars; ``tool_result`` /
187
+ ``tool_use`` / ``thinking`` contribute their text unless
188
+ ``include_tool_content`` is False.
58
189
  - ``None`` / other → empty string.
190
+
191
+ ``include_tool_content=None`` (the default) resolves to the
192
+ process-wide setting from :func:`set_include_tool_content`.
59
193
  """
194
+ include = _resolve_include(include_tool_content)
60
195
  if isinstance(content, str):
61
196
  return content
62
- if isinstance(content, list):
63
- pieces: list[str] = []
64
- for block in content:
65
- if not isinstance(block, dict):
197
+ if not isinstance(content, list):
198
+ return ""
199
+
200
+ pieces: list[str] = []
201
+ for block in content:
202
+ if not isinstance(block, dict):
203
+ continue
204
+ btype = block.get("type")
205
+
206
+ if btype == "text":
207
+ text = block.get("text")
208
+ if isinstance(text, str):
209
+ pieces.append(text)
210
+ continue
211
+ if btype is None:
212
+ # Fallback for blocks without explicit type but with text
213
+ text = block.get("text")
214
+ if isinstance(text, str):
215
+ pieces.append(text)
216
+ continue
217
+ if btype in _ZERO_CHAR_BLOCK_TYPES:
218
+ # Deliberate 0: see module docstring (35x over-estimate).
219
+ continue
220
+ if btype not in _TOOL_BLOCK_TYPES or not include:
221
+ # Unknown/forward-compat blocks contribute nothing, and the
222
+ # escape hatch drops the tool blocks back to v2.11.x's 0.
223
+ continue
224
+
225
+ if btype == "tool_result":
226
+ if _depth >= _MAX_BLOCK_DEPTH:
66
227
  continue
67
- btype = block.get("type")
68
- if btype == "text":
69
- text = block.get("text")
70
- if isinstance(text, str):
71
- pieces.append(text)
72
- elif btype is None and "text" in block and isinstance(block["text"], str):
73
- # Fallback for blocks without explicit type but with text
74
- pieces.append(block["text"])
75
- return "\n".join(pieces)
76
- return ""
228
+ inner = _extract_text_from_content(
229
+ block.get("content"),
230
+ include_tool_content=True,
231
+ _depth=_depth + 1,
232
+ )
233
+ if inner:
234
+ pieces.append(inner)
235
+ elif btype == "tool_use":
236
+ rendered = _tool_use_text(block)
237
+ if rendered:
238
+ pieces.append(rendered)
239
+ elif btype == "thinking":
240
+ thinking = block.get("thinking")
241
+ if isinstance(thinking, str):
242
+ pieces.append(thinking)
243
+
244
+ return "\n".join(pieces)
77
245
 
78
246
 
79
247
  def _count_system_chars(system: Any) -> int:
@@ -106,12 +274,56 @@ def _extract_system_text(system: Any) -> str:
106
274
  return ""
107
275
 
108
276
 
277
+ def _message_content(msg: Any) -> Any:
278
+ """Pull ``content`` off a Pydantic message model or a plain dict."""
279
+ if hasattr(msg, "content"):
280
+ return msg.content
281
+ if isinstance(msg, dict):
282
+ return msg.get("content")
283
+ return None
284
+
285
+
286
+ # ---------------------------------------------------------------------------
287
+ # Public API: character-level primitives
288
+ # ---------------------------------------------------------------------------
289
+ #
290
+ # The guard's trim loop needs per-message character counts so it can
291
+ # subtract as it drops instead of re-walking the whole conversation
292
+ # after every removal (O(n^2) → O(n)). Counting *chars* rather than
293
+ # per-message tokens keeps the incremental sum bit-identical to a full
294
+ # recompute: the heuristic floor-divides the grand total exactly once.
295
+
296
+
297
+ def count_system_chars(system: Any) -> int:
298
+ """Public alias of the system-prompt character count."""
299
+ return _count_system_chars(system)
300
+
301
+
302
+ def count_message_chars(msg: Any, *, include_tool_content: bool | None = None) -> int:
303
+ """Characters a single message contributes to the estimate."""
304
+ return len(
305
+ _extract_text_from_content(
306
+ _message_content(msg),
307
+ include_tool_content=include_tool_content,
308
+ )
309
+ )
310
+
311
+
312
+ def chars_to_tokens(total_chars: int) -> int:
313
+ """Apply the char/4 heuristic to an already-summed character count."""
314
+ return total_chars // CHARS_PER_TOKEN_HEURISTIC
315
+
316
+
109
317
  # ---------------------------------------------------------------------------
110
318
  # Public API
111
319
  # ---------------------------------------------------------------------------
112
320
 
113
321
 
114
- def estimate_tokens_from_body(body: dict[str, Any]) -> int:
322
+ def estimate_tokens_from_body(
323
+ body: dict[str, Any],
324
+ *,
325
+ include_tool_content: bool | None = None,
326
+ ) -> int:
115
327
  """Estimate the prompt's token count from a raw request body dict.
116
328
 
117
329
  Works with both Anthropic (``system`` + ``messages``) and OpenAI
@@ -120,7 +332,10 @@ def estimate_tokens_from_body(body: dict[str, Any]) -> int:
120
332
  sums character counts, divides by :data:`CHARS_PER_TOKEN_HEURISTIC`.
121
333
 
122
334
  Image blocks contribute 0 — they're billed differently and don't
123
- fill the text-token side of the context window.
335
+ fill the text-token side of the context window. ``tool_result`` /
336
+ ``tool_use`` / ``thinking`` blocks DO count (H-5); see the module
337
+ docstring. Tool *definitions* (``body["tools"]``) are still not
338
+ counted — known inconsistency with ``count_tokens``.
124
339
 
125
340
  This is the function formerly known as
126
341
  ``coderouter.routing.auto_router._estimate_total_tokens``.
@@ -130,14 +345,20 @@ def estimate_tokens_from_body(body: dict[str, Any]) -> int:
130
345
  if isinstance(messages, list):
131
346
  for msg in messages:
132
347
  if isinstance(msg, dict):
133
- total_chars += len(_extract_text_from_content(msg.get("content")))
134
- return total_chars // CHARS_PER_TOKEN_HEURISTIC
348
+ total_chars += len(
349
+ _extract_text_from_content(
350
+ msg.get("content"),
351
+ include_tool_content=include_tool_content,
352
+ )
353
+ )
354
+ return chars_to_tokens(total_chars)
135
355
 
136
356
 
137
357
  def estimate_tokens_from_anthropic_request(
138
358
  *,
139
359
  system: Any,
140
360
  messages: list[Any],
361
+ include_tool_content: bool | None = None,
141
362
  ) -> int:
142
363
  """Estimate token count for an Anthropic-shaped request.
143
364
 
@@ -153,6 +374,10 @@ def estimate_tokens_from_anthropic_request(
153
374
  messages : list[AnthropicMessage]
154
375
  The request's ``messages`` list. Each item should have a
155
376
  ``content`` attribute (str or list of blocks).
377
+ include_tool_content : bool | None
378
+ ``None`` → process-wide default (see
379
+ :func:`set_include_tool_content`). ``False`` reproduces the
380
+ v2.11.x estimate exactly.
156
381
  """
157
382
  total_chars = _count_system_chars(system)
158
383
  for msg in messages:
@@ -164,23 +389,26 @@ def estimate_tokens_from_anthropic_request(
164
389
  content = msg.get("content")
165
390
  else:
166
391
  continue
167
- total_chars += len(_extract_text_from_content(content))
168
- return total_chars // CHARS_PER_TOKEN_HEURISTIC
392
+ total_chars += len(
393
+ _extract_text_from_content(content, include_tool_content=include_tool_content)
394
+ )
395
+ return chars_to_tokens(total_chars)
169
396
 
170
397
 
171
398
  def extract_text_from_anthropic_request(
172
399
  *,
173
400
  system: Any,
174
401
  messages: list[Any],
402
+ include_tool_content: bool | None = None,
175
403
  ) -> str:
176
404
  """Concatenate all text in an Anthropic-shaped request.
177
405
 
178
406
  Mirrors :func:`estimate_tokens_from_anthropic_request` but returns
179
- the raw text (system prompt + every message's text blocks) instead
180
- of a char/4 count. Used by :mod:`coderouter.language_tax` to feed an
181
- accurate tokenizer for language-tax measurement. Non-text blocks
182
- (images / tool_use / tool_result) contribute nothing — same rule the
183
- char/4 estimator uses.
407
+ the raw text (system prompt + every message's countable blocks)
408
+ instead of a char/4 count. Used by :mod:`coderouter.language_tax`
409
+ to feed an accurate tokenizer for language-tax measurement, and by
410
+ ``POST /v1/messages/count_tokens``. Image blocks contribute nothing
411
+ — same rule the char/4 estimator uses.
184
412
  """
185
413
  pieces: list[str] = []
186
414
  sys_text = _extract_system_text(system)
@@ -193,7 +421,7 @@ def extract_text_from_anthropic_request(
193
421
  content = msg.get("content")
194
422
  else:
195
423
  continue
196
- text = _extract_text_from_content(content)
424
+ text = _extract_text_from_content(content, include_tool_content=include_tool_content)
197
425
  if text:
198
426
  pieces.append(text)
199
427
  return "\n".join(pieces)
@@ -202,7 +430,12 @@ def extract_text_from_anthropic_request(
202
430
  __all__ = [
203
431
  "CHARS_PER_TOKEN_HEURISTIC",
204
432
  "DEFAULT_MAX_CONTEXT_TOKENS",
433
+ "chars_to_tokens",
434
+ "count_message_chars",
435
+ "count_system_chars",
205
436
  "estimate_tokens_from_anthropic_request",
206
437
  "estimate_tokens_from_body",
207
438
  "extract_text_from_anthropic_request",
439
+ "get_include_tool_content",
440
+ "set_include_tool_content",
208
441
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderouter-cli
3
- Version: 2.11.2
3
+ Version: 2.13.0
4
4
  Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
5
5
  Project-URL: Homepage, https://github.com/zephel01/CodeRouter
6
6
  Project-URL: Repository, https://github.com/zephel01/CodeRouter
@@ -1,6 +1,6 @@
1
1
  coderouter/__init__.py,sha256=ghdjPrLtnRzY8fyQ4CJZI1UJKADyNTLtA3G7se8H7Ns,696
2
2
  coderouter/__main__.py,sha256=-LCgxJnvgUV240HjQKv7ly-mn2NuKHpC4nCpvTHjeSU,130
3
- coderouter/cli.py,sha256=2g-0HILLM16mNnkFUOR6lWXy5f6Y0LipCjo_YH4--j0,36896
3
+ coderouter/cli.py,sha256=TyI29d8-Zc8SClcKn92Oj8DGyQRxMpBSWTxLiFTH3Qo,37043
4
4
  coderouter/cli_stats.py,sha256=CCjzc1G4hTRHZ2gG1XhxhDpUkJnnl3NXbcbp1T18jpg,29894
5
5
  coderouter/cost.py,sha256=32h6uzb4nxh2eA5d2Hn3kD9yJbtis6CFDAbeIy5KRkM,7431
6
6
  coderouter/doctor.py,sha256=wmPunlvqm2s7x3VGLjexqHPLD2oejwE3Mu04V8y-xn4,89869
@@ -10,12 +10,12 @@ coderouter/errors.py,sha256=Xmq67lheyw8iv3Ox39jh2c4tvNI5RcUR4QkoxVDN6l4,1130
10
10
  coderouter/gguf_introspect.py,sha256=Scu0JjUfLHj7T7TL0HQIm1AZcGYrXsjkaMMSN9HkTro,16121
11
11
  coderouter/hardware.py,sha256=gn3_9qbVcGRR81yKMn1lJE_8-YDRau0LxIH_M-f7pxE,8356
12
12
  coderouter/language_tax.py,sha256=LTbE3tIfoJuV2O3T0NixRKhzq_dEOTUuPEerJv2q9uk,9360
13
- coderouter/launcher_devices.py,sha256=Jd2hVJMsQZ0x6YFc_2ROvEc2L10b6dh77Y-gvP_Po8M,29039
13
+ coderouter/launcher_devices.py,sha256=DAV34Q1crGptmj1zOAk21YQzlxQNAt5Ud7qcldTD95w,29406
14
14
  coderouter/launcher_speculative.py,sha256=h0RTGsD1Y56sJVywDJwybrT75_Ta3GF1Jz_B1d42aPg,12931
15
15
  coderouter/launcher_swap.py,sha256=tMW5ZElDdf9T1VQGsUkvfLiSCdj_oXN_UtkV7DE8ySY,24175
16
16
  coderouter/logging.py,sha256=5rfi4_R4aoeeUYDc642dgs61wA0aBX7wvubyB-k3Uyo,58074
17
17
  coderouter/output_filters.py,sha256=0ry_rPiS_kC-FnHgaNVP6v7e6Al2djxzu9vBzZ8kEkE,25314
18
- coderouter/token_estimation.py,sha256=iz22vZEEW2P7uKLB2pYvPNpIbZGbgXRO5MtfkS_-9Sk,7531
18
+ coderouter/token_estimation.py,sha256=7klQYA4FIg1vtjQGWAhwiBhiLwyz8KTetrLVXFJLGIc,16957
19
19
  coderouter/token_estimation_accurate.py,sha256=GTfzrBVnvAGjeVzmzAeUdOYZvWZKLAxcxPpFiJGlzjk,4609
20
20
  coderouter/vscode_init.py,sha256=fW0chCDTngvWQuJgQ7aqYmE6hnZO3hCk8mAiEkZ9FCo,26741
21
21
  coderouter/adapters/__init__.py,sha256=7dIDSZ-FE_0iSqLSDc_lK1idRdLTKcM2hP9tCJipgPI,463
@@ -26,26 +26,26 @@ coderouter/adapters/registry.py,sha256=XNc1wmFv_oZlqKS0rA693TRDdbq1sZ-OIcofXOVOt
26
26
  coderouter/config/__init__.py,sha256=FODEn74fN-qZnt4INPSHswqhOlEgpL6-_onxsitSx8g,274
27
27
  coderouter/config/capability_registry.py,sha256=RZgzHF54dXy0CCDocRCz2ee21qXGbbgTSKTkRI9iLL4,16936
28
28
  coderouter/config/env_file.py,sha256=CoMK27fuAXm-NtoLzXb8yN2E-wDFjHQuFwiIlmgTBQw,10356
29
- coderouter/config/loader.py,sha256=jUoHAbEyjK6Y0HDqhjXHO4tfncMPuzsjfWJokrPb2x8,6876
30
- coderouter/config/schemas.py,sha256=8MZMW3Iask-ZvkWV2b77UYszqMgJ4ETc6gEU80Ekx9g,131685
29
+ coderouter/config/loader.py,sha256=vk1LFm-OCiyq2GB5G53g7kTnrJgmQjkZPqgr9X13wpc,11294
30
+ coderouter/config/schemas.py,sha256=J05km7-YXgNxy6guKUDcNsuMyJ21SnDIurjx8Rz_hSg,136672
31
31
  coderouter/data/__init__.py,sha256=uNyfD9jaCvTWsBAWtaw1Fr25OSxzv3psGMfBjT1z0Cc,328
32
32
  coderouter/data/model-capabilities.yaml,sha256=S9jt6SC6-3s2-icZ_n-a14iEMnc2yB1C2R6q-N_tZWQ,19309
33
33
  coderouter/guards/__init__.py,sha256=5qliYBqygvVPneej7nx0uSjxDKsz7t8VzvrDgVBJlvU,1170
34
34
  coderouter/guards/_fingerprint.py,sha256=qsgNzIq9jv3FHrKL39nGJARp0cMenpN_QmWoJu87vU4,4835
35
35
  coderouter/guards/backend_health.py,sha256=GLUVQ5BhU6i8Cxz_FrgIMs5AZ-YSKAW8veFWgvn9GFI,12565
36
- coderouter/guards/context_budget.py,sha256=6u08JqBdRQkDz9NIQ-aISXo3w3L804oYFg027s04IwY,17202
36
+ coderouter/guards/context_budget.py,sha256=gw3MkiHDjpLmQTlTdJT6gRAxYEnoU0xmwSvPZk_miOQ,23059
37
37
  coderouter/guards/continuous_probe.py,sha256=WIfS-apVMWXGv7bPBxxkJssePa95I4fT0mi4PNqK5iE,12181
38
38
  coderouter/guards/drift_actions.py,sha256=A6pY5CR480Ct5rCVyjlBvjPFVc93eu_r5qcUpK9mWKc,3602
39
39
  coderouter/guards/drift_detection.py,sha256=lW45EZDGRleDUTstw_CHlfUiwLUrlDHX8g7ncuOYNEw,13962
40
40
  coderouter/guards/memory_budget.py,sha256=_bRtusk4AwrU781wVXW32OFU7zD2FXxOwTb7yGqPJqA,8278
41
41
  coderouter/guards/memory_pressure.py,sha256=9Xm-b7rv9Td5kPNmAFoL3sNmaOR5zT8NLdn6erWcuyY,8719
42
- coderouter/guards/self_healing.py,sha256=Qene73S1kqMJn8HYzHO-WT8hhWxKbo2v_JtXtEgpCp8,14434
42
+ coderouter/guards/self_healing.py,sha256=XeUlgFWGqMQ5E9YvvzR2CsHRJOAJEyDChqRN0xmZ0vo,16841
43
43
  coderouter/guards/tool_loop.py,sha256=EzeMcmU7BLeTW2jsRVevU81l5rhWcn1oUr7EpzgXjVM,15209
44
44
  coderouter/ingress/__init__.py,sha256=WQsCH2CGJCAhy0mS6GSEdeYZRkkQu2OHDsP4CJWTLug,155
45
45
  coderouter/ingress/anthropic_routes.py,sha256=1ThXzx8VgxRV7UCi3NF5ey7kbr0ADPKTXyQuYS5gK1w,27063
46
- coderouter/ingress/app.py,sha256=6e_qNlYZIyjLKhY20t0ZNL-x5Fv8R8oaIH_HCdxa9Ic,20921
46
+ coderouter/ingress/app.py,sha256=ZOx7dSvMxaWS4CzXOOEwgzFuycAVgAeWKCN6lFNF-F0,22433
47
47
  coderouter/ingress/dashboard_routes.py,sha256=1SqNNrVTfQiZcx5n2MvjAzWirittX0e61tCt-MKp4Ag,25617
48
- coderouter/ingress/launcher_routes.py,sha256=g-0Mee1vpY10b82jI51-2fTeR8hxutMCaLYBcicwM_k,133287
48
+ coderouter/ingress/launcher_routes.py,sha256=uP_Otp9JE0fydUt1YP-wYcnS8Kj5RPLNIFf88YpfkPM,141535
49
49
  coderouter/ingress/metrics_routes.py,sha256=M22dwOGn24P05Ge4W3c7d7mYytSGWjIR-pPSPOAiHJY,3965
50
50
  coderouter/ingress/openai_routes.py,sha256=TrugsQNJfNPgKKLzfT1aXzrs4emWYGE4XebEBuaZoWk,12505
51
51
  coderouter/metrics/__init__.py,sha256=7Es351DPS7yLM0yVF_F0eesmiD83n7Zzhie44chht38,1465
@@ -71,8 +71,9 @@ coderouter/translation/__init__.py,sha256=PYXN7XVEwpG1uC8RLy6fvnGbzEZhhrEuUapH8I
71
71
  coderouter/translation/anthropic.py,sha256=ZQQGS9itN_3o7IKD68lKr5gR_Kem32Jsd7OcGjJzv8Q,13468
72
72
  coderouter/translation/convert.py,sha256=VrstDAMsUpcQ1VgMTn_-ETp1OjGX8v0ZGhCkcd4icyU,53037
73
73
  coderouter/translation/tool_repair.py,sha256=1p8rmdyyscGX2g0DDOYitMnLv-nKfR1SLuLaRB4z7GM,64244
74
- coderouter_cli-2.11.2.dist-info/METADATA,sha256=zw-XK_yuV-v6dBPqAohL8iYdQJW_rmbI0UhmVvyfT0s,16897
75
- coderouter_cli-2.11.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
76
- coderouter_cli-2.11.2.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
77
- coderouter_cli-2.11.2.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
78
- coderouter_cli-2.11.2.dist-info/RECORD,,
74
+ coderouter/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
+ coderouter_cli-2.13.0.dist-info/METADATA,sha256=J6h8klUNsTIhWoMjrax70AR9nCFXYK_rFP2tbpChn8I,16897
76
+ coderouter_cli-2.13.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
77
+ coderouter_cli-2.13.0.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
78
+ coderouter_cli-2.13.0.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
79
+ coderouter_cli-2.13.0.dist-info/RECORD,,