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.
- coderouter/cli.py +20 -21
- coderouter/config/loader.py +133 -34
- coderouter/config/schemas.py +143 -47
- coderouter/guards/context_budget.py +165 -20
- coderouter/guards/self_healing.py +58 -3
- coderouter/ingress/app.py +72 -29
- coderouter/ingress/launcher_routes.py +248 -62
- coderouter/launcher_devices.py +11 -4
- coderouter/py.typed +0 -0
- coderouter/token_estimation.py +264 -31
- {coderouter_cli-2.11.2.dist-info → coderouter_cli-2.13.0.dist-info}/METADATA +1 -1
- {coderouter_cli-2.11.2.dist-info → coderouter_cli-2.13.0.dist-info}/RECORD +15 -14
- {coderouter_cli-2.11.2.dist-info → coderouter_cli-2.13.0.dist-info}/WHEEL +0 -0
- {coderouter_cli-2.11.2.dist-info → coderouter_cli-2.13.0.dist-info}/entry_points.txt +0 -0
- {coderouter_cli-2.11.2.dist-info → coderouter_cli-2.13.0.dist-info}/licenses/LICENSE +0 -0
coderouter/token_estimation.py
CHANGED
|
@@ -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
|
|
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) →
|
|
55
|
-
|
|
56
|
-
blocks contribute 0 chars
|
|
57
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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(
|
|
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(
|
|
134
|
-
|
|
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(
|
|
168
|
-
|
|
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
|
|
180
|
-
of a char/4 count. Used by :mod:`coderouter.language_tax`
|
|
181
|
-
accurate tokenizer for language-tax measurement
|
|
182
|
-
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
30
|
-
coderouter/config/schemas.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
|
|
75
|
-
coderouter_cli-2.
|
|
76
|
-
coderouter_cli-2.
|
|
77
|
-
coderouter_cli-2.
|
|
78
|
-
coderouter_cli-2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|