coderouter-cli 2.0.0__py3-none-any.whl → 2.1.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.
@@ -26,7 +26,10 @@ from __future__ import annotations
26
26
 
27
27
  import time
28
28
  from collections.abc import AsyncIterator
29
- from typing import Final
29
+ from typing import TYPE_CHECKING, Any, Final
30
+
31
+ if TYPE_CHECKING:
32
+ from coderouter.guards.drift_detection import DriftVerdict
30
33
 
31
34
  from coderouter.adapters.anthropic_native import AnthropicAdapter
32
35
  from coderouter.adapters.base import (
@@ -469,11 +472,16 @@ class _StreamUsageAccumulator:
469
472
  """
470
473
 
471
474
  __slots__ = (
475
+ "_current_block_text",
476
+ "_current_block_type",
472
477
  "_observed",
478
+ "_text_blocks",
473
479
  "cache_creation_input_tokens",
474
480
  "cache_read_input_tokens",
481
+ "has_tool_use",
475
482
  "input_tokens",
476
483
  "output_tokens",
484
+ "stop_reason",
477
485
  )
478
486
 
479
487
  def __init__(self) -> None:
@@ -482,6 +490,32 @@ class _StreamUsageAccumulator:
482
490
  self.cache_read_input_tokens = 0
483
491
  self.cache_creation_input_tokens = 0
484
492
  self._observed = False
493
+ # v2.0-G: tracked for drift detection observation at stream end.
494
+ self.has_tool_use: bool = False
495
+ self.stop_reason: str | None = None
496
+ # v2.0-H: partial content accumulation for mid-stream recovery.
497
+ # Completed text blocks are moved to _text_blocks on content_block_stop.
498
+ # In-progress text is in _current_block_text (list of str fragments).
499
+ self._text_blocks: list[str] = []
500
+ self._current_block_type: str | None = None
501
+ self._current_block_text: list[str] = []
502
+
503
+ @property
504
+ def partial_content(self) -> list[dict[str, Any]]:
505
+ """Return accumulated text content as Anthropic content blocks.
506
+
507
+ Includes both completed blocks and any in-progress text block
508
+ (useful when the stream is interrupted mid-block). Tool_use blocks
509
+ are excluded because partial JSON is unusable.
510
+ """
511
+ blocks: list[dict[str, Any]] = []
512
+ for text in self._text_blocks:
513
+ if text:
514
+ blocks.append({"type": "text", "text": text})
515
+ # Include in-progress text block if any
516
+ if self._current_block_type == "text" and self._current_block_text:
517
+ blocks.append({"type": "text", "text": "".join(self._current_block_text)})
518
+ return blocks
485
519
 
486
520
  def observe(self, event: AnthropicStreamEvent) -> None:
487
521
  """Update counters from one stream event (no-op for non-usage events)."""
@@ -494,6 +528,33 @@ class _StreamUsageAccumulator:
494
528
  usage = event.data.get("usage") if isinstance(event.data, dict) else None
495
529
  if isinstance(usage, dict):
496
530
  self._merge(usage)
531
+ # v2.0-G: capture stop_reason from the terminal message_delta.
532
+ delta = event.data.get("delta") if isinstance(event.data, dict) else None
533
+ if isinstance(delta, dict) and "stop_reason" in delta:
534
+ self.stop_reason = delta["stop_reason"]
535
+ elif event.type == "content_block_start":
536
+ # v2.0-G: detect tool_use content blocks for drift observation.
537
+ cb = event.data.get("content_block") if isinstance(event.data, dict) else None
538
+ if isinstance(cb, dict):
539
+ block_type = cb.get("type", "")
540
+ if block_type == "tool_use":
541
+ self.has_tool_use = True
542
+ # v2.0-H: start tracking a new content block.
543
+ self._current_block_type = block_type
544
+ self._current_block_text = []
545
+ elif event.type == "content_block_delta":
546
+ # v2.0-H: accumulate text_delta fragments.
547
+ delta = event.data.get("delta") if isinstance(event.data, dict) else None
548
+ if isinstance(delta, dict) and delta.get("type") == "text_delta":
549
+ text = delta.get("text", "")
550
+ if text:
551
+ self._current_block_text.append(text)
552
+ elif event.type == "content_block_stop":
553
+ # v2.0-H: finalize current block.
554
+ if self._current_block_type == "text" and self._current_block_text:
555
+ self._text_blocks.append("".join(self._current_block_text))
556
+ self._current_block_type = None
557
+ self._current_block_text = []
497
558
 
498
559
  def _merge(self, usage: dict[str, object]) -> None:
499
560
  any_nonzero = False
@@ -613,9 +674,18 @@ class MidStreamError(CodeRouterError):
613
674
  one chunk to the client. Fallback is not attempted (the client has
614
675
  received partial content, so switching providers would corrupt the
615
676
  stream). Callers should surface this as a terminal error event.
677
+
678
+ v2.0-H: carries ``partial_content`` — the accumulated text blocks
679
+ generated before the failure. The ingress uses this to synthesize
680
+ a graceful stream termination when ``partial_stitch_action: surface``.
616
681
  """
617
682
 
618
- def __init__(self, provider: str, original: AdapterError) -> None:
683
+ def __init__(
684
+ self,
685
+ provider: str,
686
+ original: AdapterError,
687
+ partial_content: list[dict[str, Any]] | None = None,
688
+ ) -> None:
619
689
  """Wrap the underlying :class:`AdapterError` with the provider name.
620
690
 
621
691
  The ingress layer catches this and converts it into an in-stream
@@ -624,6 +694,7 @@ class MidStreamError(CodeRouterError):
624
694
  """
625
695
  self.provider = provider
626
696
  self.original = original
697
+ self.partial_content: list[dict[str, Any]] = partial_content or []
627
698
  super().__init__(f"provider {provider!r} failed mid-stream: {original}")
628
699
 
629
700
 
@@ -747,6 +818,32 @@ class FallbackEngine:
747
818
  # Distinct from v1.9-C ``adaptive`` which handles the
748
819
  # gradient case via a rolling window.
749
820
  self._backend_health_monitor: BackendHealthMonitor = BackendHealthMonitor()
821
+ # v2.0-G (L4): per-process drift detection window manager.
822
+ # Stores per-provider rolling observations; the detector is
823
+ # invoked after each provider-ok / provider-failed event and
824
+ # returns a verdict. Action dispatch (promote/reload) reuses
825
+ # the adaptive rank machinery.
826
+ from coderouter.guards.drift_detection import DriftWindow
827
+
828
+ self._drift_window: DriftWindow = DriftWindow()
829
+ # Track which providers are currently in drift-demoted state
830
+ # and when their cooldown expires (monotonic timestamp).
831
+ self._drift_demoted: dict[str, float] = {}
832
+ # Last drift verdict (set by _observe_drift_signal for ingress header).
833
+ self._last_drift_verdict: DriftVerdict | None = None
834
+
835
+ @property
836
+ def last_drift_severity(self) -> str | None:
837
+ """Return the severity string of the most recent drift verdict, or None.
838
+
839
+ The ingress reads this after generate_anthropic / stream_anthropic to
840
+ set the ``X-CodeRouter-Drift`` response header. Returns ``"mild"`` or
841
+ ``"severe"`` when drift was detected, ``None`` otherwise.
842
+ """
843
+ v = self._last_drift_verdict
844
+ if v is None or not v.drifted:
845
+ return None
846
+ return v.severity
750
847
 
751
848
  @property
752
849
  def _adaptive(self) -> AdaptiveAdjuster:
@@ -794,12 +891,17 @@ class FallbackEngine:
794
891
  return existing
795
892
 
796
893
  @property
797
- def _backend_health(self) -> BackendHealthMonitor:
894
+ def backend_health(self) -> BackendHealthMonitor:
798
895
  """Return the L5 backend-health monitor, lazily building one if absent.
799
896
 
800
897
  Same legacy-test compatibility pattern as the other guard
801
898
  properties — ``__new__``-constructed engines get a fresh
802
899
  empty monitor so ``state_for`` is always answerable.
900
+
901
+ v2.0-I: promoted from ``_backend_health`` to public ``backend_health``
902
+ so the continuous probe background task can feed results into the
903
+ same state machine. Internal callers continue to work (property
904
+ access is transparent).
803
905
  """
804
906
  existing = getattr(self, "_backend_health_monitor", None)
805
907
  if existing is None:
@@ -807,6 +909,11 @@ class FallbackEngine:
807
909
  existing = self._backend_health_monitor
808
910
  return existing
809
911
 
912
+ # Alias for backward compat with internal callers.
913
+ @property
914
+ def _backend_health(self) -> BackendHealthMonitor:
915
+ return self.backend_health
916
+
810
917
  def _observe_provider_failure(
811
918
  self,
812
919
  provider: str,
@@ -925,6 +1032,132 @@ class FallbackEngine:
925
1032
  consecutive_failures=transition.consecutive_failures,
926
1033
  )
927
1034
 
1035
+ def _observe_drift_signal(
1036
+ self,
1037
+ provider: str,
1038
+ *,
1039
+ profile: str | None,
1040
+ output_tokens: int = 0,
1041
+ has_tool_use: bool = False,
1042
+ request_had_tools: bool = False,
1043
+ stop_reason: str | None = None,
1044
+ is_error: bool = False,
1045
+ stream: bool = False,
1046
+ ) -> DriftVerdict | None:
1047
+ """v2.0-G (L4): record an observation and check for drift.
1048
+
1049
+ Called after every provider-ok / provider-failed event on the
1050
+ Anthropic-shaped paths. Returns a :class:`DriftVerdict` when
1051
+ drift is detected (drifted=True), None otherwise.
1052
+
1053
+ Side effects on detection:
1054
+ - Emits ``drift-detected`` log.
1055
+ - If action is ``promote`` or ``reload``, demotes the provider
1056
+ via the adaptive rank machinery.
1057
+ """
1058
+ from coderouter.guards.drift_detection import (
1059
+ SENSITIVITY_PRESETS,
1060
+ ResponseObservation,
1061
+ detect_drift,
1062
+ )
1063
+ from coderouter.logging import log_drift_detected, log_drift_promoted
1064
+
1065
+ chosen = profile or self.config.default_profile
1066
+ try:
1067
+ chain_cfg = self.config.profile_by_name(chosen)
1068
+ except (KeyError, ValueError):
1069
+ return None
1070
+ if chain_cfg.drift_detection_action == "off":
1071
+ return None
1072
+
1073
+ # Update window size if config differs from default
1074
+ self._drift_window.max_size = chain_cfg.drift_detection_window_size
1075
+
1076
+ # Record observation
1077
+ obs = ResponseObservation(
1078
+ provider=provider,
1079
+ output_tokens=output_tokens,
1080
+ has_tool_use=has_tool_use,
1081
+ request_had_tools=request_had_tools,
1082
+ stop_reason=stop_reason,
1083
+ is_error=is_error,
1084
+ stream=stream,
1085
+ )
1086
+ self._drift_window.record(obs)
1087
+
1088
+ # Check for cooldown recovery
1089
+ import time as _time
1090
+
1091
+ demote_expires = self._drift_demoted.get(provider)
1092
+ if demote_expires is not None and _time.monotonic() >= demote_expires:
1093
+ # Cooldown expired — restore rank and clear drift state
1094
+ from coderouter.logging import log_drift_recovered
1095
+
1096
+ elapsed = chain_cfg.drift_detection_cooldown_s
1097
+ log_drift_recovered(logger, provider=provider, profile=chosen, after_s=elapsed)
1098
+ self._drift_demoted.pop(provider, None)
1099
+ self._drift_window.clear(provider)
1100
+ return None
1101
+
1102
+ # Don't re-detect while in cooldown
1103
+ if provider in self._drift_demoted:
1104
+ return None
1105
+
1106
+ # Run detection
1107
+ window = self._drift_window.get_window(provider)
1108
+ thresholds = SENSITIVITY_PRESETS.get(
1109
+ chain_cfg.drift_detection_sensitivity, SENSITIVITY_PRESETS["normal"]
1110
+ )
1111
+ verdict = detect_drift(window, thresholds)
1112
+
1113
+ if not verdict.drifted:
1114
+ self._last_drift_verdict = None
1115
+ return None
1116
+
1117
+ # Store for ingress response header.
1118
+ self._last_drift_verdict = verdict
1119
+
1120
+ # Emit log
1121
+ log_drift_detected(
1122
+ logger,
1123
+ provider=provider,
1124
+ profile=chosen,
1125
+ severity=verdict.severity,
1126
+ reason=verdict.reason,
1127
+ action=chain_cfg.drift_detection_action,
1128
+ signals=verdict.signals,
1129
+ )
1130
+
1131
+ # Action: promote / reload
1132
+ if chain_cfg.drift_detection_action in ("promote", "reload"):
1133
+ import time as _time_mod
1134
+
1135
+ # Demote via adaptive rank
1136
+ self._adaptive.demote(provider, steps=2)
1137
+ log_drift_promoted(
1138
+ logger,
1139
+ provider=provider,
1140
+ profile=chosen,
1141
+ demoted_to_rank=2,
1142
+ cooldown_s=chain_cfg.drift_detection_cooldown_s,
1143
+ )
1144
+ # Record cooldown expiry
1145
+ self._drift_demoted[provider] = (
1146
+ _time_mod.monotonic() + chain_cfg.drift_detection_cooldown_s
1147
+ )
1148
+
1149
+ # v2.0-G: reload action — attempt Ollama KV cache flush
1150
+ # (best-effort, fire-and-forget background task).
1151
+ if chain_cfg.drift_detection_action == "reload":
1152
+ import asyncio
1153
+
1154
+ from coderouter.guards.drift_actions import attempt_reload
1155
+
1156
+ provider_config = self._adapters[provider].config
1157
+ self._reload_task = asyncio.create_task(attempt_reload(provider_config))
1158
+
1159
+ return verdict
1160
+
928
1161
  def _resolve_profile_overrides(self, profile_name: str | None) -> ProviderCallOverrides:
929
1162
  """v0.6-B: build the ProviderCallOverrides for the active profile.
930
1163
 
@@ -1455,6 +1688,14 @@ class FallbackEngine:
1455
1688
  self._observe_provider_failure(
1456
1689
  adapter.name, exc, profile=request.profile
1457
1690
  )
1691
+ # v2.0-G (L4): drift detection observation (failure path).
1692
+ self._observe_drift_signal(
1693
+ adapter.name,
1694
+ profile=request.profile,
1695
+ is_error=True,
1696
+ request_had_tools=bool(request.tools),
1697
+ stream=False,
1698
+ )
1458
1699
  errors.append(exc)
1459
1700
  if not exc.retryable:
1460
1701
  break
@@ -1482,6 +1723,18 @@ class FallbackEngine:
1482
1723
  self._observe_provider_success(
1483
1724
  adapter.name, profile=request.profile
1484
1725
  )
1726
+ # v2.0-G (L4): drift detection observation (success path).
1727
+ self._observe_drift_signal(
1728
+ adapter.name,
1729
+ profile=request.profile,
1730
+ output_tokens=resp.usage.output_tokens if resp.usage else 0,
1731
+ has_tool_use=any(
1732
+ getattr(b, "type", None) == "tool_use" for b in (resp.content or [])
1733
+ ),
1734
+ request_had_tools=bool(request.tools),
1735
+ stop_reason=resp.stop_reason,
1736
+ stream=False,
1737
+ )
1485
1738
  # v1.9-A: pair every successful Anthropic response with a
1486
1739
  # cache-observed log line. Native Anthropic / LM Studio
1487
1740
  # /v1/messages report cache_read_input_tokens /
@@ -1620,6 +1873,14 @@ class FallbackEngine:
1620
1873
  self._observe_provider_failure(
1621
1874
  adapter.name, exc, profile=request.profile
1622
1875
  )
1876
+ # v2.0-G (L4): drift detection observation (stream failure).
1877
+ self._observe_drift_signal(
1878
+ adapter.name,
1879
+ profile=request.profile,
1880
+ is_error=True,
1881
+ request_had_tools=bool(request.tools),
1882
+ stream=True,
1883
+ )
1623
1884
  errors.append(exc)
1624
1885
  if not exc.retryable:
1625
1886
  break
@@ -1662,7 +1923,27 @@ class FallbackEngine:
1662
1923
  self._observe_provider_failure(
1663
1924
  adapter.name, exc, profile=request.profile
1664
1925
  )
1665
- raise MidStreamError(adapter.name, exc) from exc
1926
+ # v2.0-G (L4): drift detection observation (mid-stream failure).
1927
+ self._observe_drift_signal(
1928
+ adapter.name,
1929
+ profile=request.profile,
1930
+ is_error=True,
1931
+ request_had_tools=bool(request.tools),
1932
+ stream=True,
1933
+ )
1934
+ raise MidStreamError(
1935
+ adapter.name, exc, partial_content=acc.partial_content
1936
+ ) from exc
1937
+ # v2.0-G (L4): drift detection observation (stream success).
1938
+ self._observe_drift_signal(
1939
+ adapter.name,
1940
+ profile=request.profile,
1941
+ output_tokens=acc.output_tokens,
1942
+ has_tool_use=acc.has_tool_use,
1943
+ request_had_tools=bool(request.tools),
1944
+ stop_reason=acc.stop_reason,
1945
+ stream=True,
1946
+ )
1666
1947
  # v1.9-B2: pair the successful stream with a cache-observed
1667
1948
  # log line carrying the aggregated usage counters that the
1668
1949
  # ``_StreamUsageAccumulator`` collected from the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderouter-cli
3
- Version: 2.0.0
3
+ Version: 2.1.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
@@ -60,7 +60,7 @@ Description-Content-Type: text/markdown
60
60
  <p align="center">
61
61
  <a href="https://github.com/zephel01/CodeRouter/actions/workflows/ci.yml"><img src="https://github.com/zephel01/CodeRouter/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
62
62
  <a href=""><img src="https://img.shields.io/badge/status-stable-brightgreen" alt="status"></a>
63
- <a href=""><img src="https://img.shields.io/badge/version-2.0.0-blue" alt="version"></a>
63
+ <a href=""><img src="https://img.shields.io/badge/version-2.1.0-blue" alt="version"></a>
64
64
  <a href=""><img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="python"></a>
65
65
  <a href=""><img src="https://img.shields.io/badge/runtime%20deps-5-brightgreen" alt="deps"></a>
66
66
  <a href=""><img src="https://img.shields.io/badge/license-MIT-yellow" alt="license"></a>
@@ -91,7 +91,8 @@ Description-Content-Type: text/markdown
91
91
  - **v1.10.0 で Long-run reliability pillar が完成**: `cost.monthly_budget_usd` で provider 月次 USD 予算を強制、**L2 memory pressure detector**(Ollama / LM Studio が VRAM 切れで OOM になった時に自動クールダウン)、**L5 backend health 状態機械**(連続失敗で UNHEALTHY → chain 末尾に降格、1 回成功で即復帰)
92
92
  - **v1.10.0 で auto-router が 6 matcher に揃う**: `has_image` / `code_fence_ratio_min` / `content_contains` / `content_regex` / `model_pattern`(Opus/Sonnet/Haiku 分岐)/ `content_token_count_min`(長文 → 1M ctx Gemini Flash 等へ自動切替)
93
93
  - **v2.0.0 で Context Budget Management (L1 overflow 防止) を搭載**: 長時間 agent session で messages が context window に漸近 → backend 400 エラーで session 死亡する問題を根本解決。warn (80%) → auto trim (90%) の 2 段階 guard で **context overflow ゼロ**を実現。tool_use / tool_result ペアは atomic 保全、`X-CodeRouter-Context-Budget` ヘッダで状態通知、Prometheus メトリクス完備
94
- - ランタイム依存 5 個(`fastapi` / `uvicorn` / `httpx` / `pydantic` / `pyyaml`)— Python、MIT、テスト 930 本緑
94
+ - **v2.1.0 で Long-run Reliability 3 機能を追加搭載**: **Drift Detection** (L4 品質劣化検知 — 5 シグナル rolling window + warn/promote/reload 3 段階アクション)、**Partial Stitching** (L6 mid-stream 失敗時の蓄積テキスト返却)、**Continuous Probing** (P3 idle 時 1-token 定期 probe + model drift 検知 + backend health 自動更新)
95
+ - ランタイム依存 5 個(`fastapi` / `uvicorn` / `httpx` / `pydantic` / `pyyaml`)— 純 Python、MIT、テスト 950 本緑
95
96
 
96
97
  → **Claude Code / gemini-cli / codex + Ollama / llama.cpp / NVIDIA NIM で、破綻しない local-first agent が組める**
97
98
 
@@ -198,7 +199,7 @@ CodeRouter / Voice Bridge ともに独立した repo で進化していて、HTT
198
199
 
199
200
  ## クイックスタート(3 コマンド)
200
201
 
201
- **v2.0.0 で Context Budget Management (L1 overflow 防止) を搭載** 長時間 agent session context window を使い切って死ぬ問題を根本解決。`uvx` 一発で動きます (Python 3.12 以上必須):
202
+ **v2.1.0 で Long-run Reliability pillar 完成** — Context Budget (L1) + Drift Detection (L4) + Partial Stitching (L6) + Continuous Probing (P3)。`uvx` 一発で動きます (Python 3.12 以上必須):
202
203
 
203
204
  ```bash
204
205
  # 1. サンプル設定を置く
@@ -266,9 +267,9 @@ CodeRouter 自体は純 Python 3.12+ で、実質的な OS 対応範囲は `min(
266
267
 
267
268
  注意点や「ローカル GPU なし」向けレシピを含むフル版マトリクス: [利用ガイド §1](./docs/usage-guide.md#1-os-互換性)
268
269
 
269
- ## ステータス — v2.0.0 (2026-05)
270
+ ## ステータス — v2.1.0 (2026-05)
270
271
 
271
- **テスト 930 本通過。ランタイム依存 5 個 (36 sub-release 連続据え置き)。macOS / Linux / Windows WSL2 で動作。** v2.0.0 で **Context Budget Management (L1 overflow 防止)** を搭載 長時間 agent session context window を使い切って死ぬ問題を根本解決。v1.10.0 までに **Long-run Reliability** (L2/L3/L5)、**Cost pillar**、**Auto-router 6 matcher** が完成済み。v1.0 の総まとめは [`docs/retrospectives/v1.0.md`](./docs/retrospectives/v1.0.md)。
272
+ **テスト 950 本通過。ランタイム依存 5 個 (39 sub-release 連続据え置き)。macOS / Linux / Windows WSL2 で動作。** v2.1.0 で **Long-run Reliability pillar が完成** — Context Budget (L1) / Drift Detection (L4) / Partial Stitching (L6) / Continuous Probing (P3) の 4 sub-release を統合出荷。v1.10.0 までに **Long-run Reliability** (L2/L3/L5)、**Cost pillar**、**Auto-router 6 matcher** が完成済み。v1.0 の総まとめは [`docs/retrospectives/v1.0.md`](./docs/retrospectives/v1.0.md)。
272
273
 
273
274
  今日の CodeRouter が届ける価値:
274
275
 
@@ -7,7 +7,7 @@ coderouter/doctor.py,sha256=2luNk6BHSRvpQStJnHcqzNvNi-SKdOuKV0WZdorZhVk,82854
7
7
  coderouter/doctor_apply.py,sha256=r_J6xbu5-HivofPNriw4_vjNYs_VRs7GsGTS0oMEX10,24209
8
8
  coderouter/env_security.py,sha256=FEBZnXfJ0xE39kmMMn39zk0W_DRRnmcB_REmP9f4xWo,14796
9
9
  coderouter/errors.py,sha256=Xmq67lheyw8iv3Ox39jh2c4tvNI5RcUR4QkoxVDN6l4,1130
10
- coderouter/logging.py,sha256=tHG3V7N6GJFI4TUc5LKrBjlLFgRfRkmZw1UKMlPOmhg,42272
10
+ coderouter/logging.py,sha256=DV1BwLsQK3wa8493omij5hiG7lU0WTnJFk9xA-ei5Zc,49831
11
11
  coderouter/output_filters.py,sha256=rI4YgKVv5vviDBl3Xkf7rp6LaSSkdWyEV004q6HrkB0,15706
12
12
  coderouter/token_estimation.py,sha256=1Ai1uT68hahpyr4LBhNyVRGq7y4yXItd6J4k5ApGX7M,5995
13
13
  coderouter/adapters/__init__.py,sha256=7dIDSZ-FE_0iSqLSDc_lK1idRdLTKcM2hP9tCJipgPI,463
@@ -19,35 +19,38 @@ coderouter/config/__init__.py,sha256=FODEn74fN-qZnt4INPSHswqhOlEgpL6-_onxsitSx8g
19
19
  coderouter/config/capability_registry.py,sha256=F6DetVL5oM03R4QeK1g6h_Q_zrXH0opnYDp3duZmkN4,15808
20
20
  coderouter/config/env_file.py,sha256=CoMK27fuAXm-NtoLzXb8yN2E-wDFjHQuFwiIlmgTBQw,10356
21
21
  coderouter/config/loader.py,sha256=FUEe8m4Tnmj_aul0vSctD8vKvNW-oLRoMRbTpSKqSmc,4077
22
- coderouter/config/schemas.py,sha256=pXWYDpW9p5QWRSA9pPNZ3ukUySf3ulfX8hfhSejhB_Q,42044
22
+ coderouter/config/schemas.py,sha256=F0xHOnn9knwuaYNBL2sKOwum08mvKFP57JWKkS_-Sho,46407
23
23
  coderouter/data/__init__.py,sha256=uNyfD9jaCvTWsBAWtaw1Fr25OSxzv3psGMfBjT1z0Cc,328
24
24
  coderouter/data/model-capabilities.yaml,sha256=2ztY4PUbGN_cWG-UUB-iPy-baeVFnGV8OcZHJUfZE7c,19290
25
25
  coderouter/guards/__init__.py,sha256=eYjuKo0OvE-GjJo7drYtU2XavUPF9OdiTB5IS76c92Y,855
26
26
  coderouter/guards/backend_health.py,sha256=bW148sadK60ZHaz9n6HKeRMcWxfAEvieBVGQhGh_MwY,7686
27
27
  coderouter/guards/context_budget.py,sha256=moWulVr5NtVci13vXxS0ucV4EvX2b7tbA1W1d9eQnkw,13281
28
+ coderouter/guards/continuous_probe.py,sha256=AKNMbJ7hUJG-FDoU160BCbSEQQUyw0hBxFYMTaBZg84,11681
29
+ coderouter/guards/drift_actions.py,sha256=A6pY5CR480Ct5rCVyjlBvjPFVc93eu_r5qcUpK9mWKc,3602
30
+ coderouter/guards/drift_detection.py,sha256=W6f9yhVZO-J4PCeyeM9MIc40BY0tYoHGewTPU0eze0g,11011
28
31
  coderouter/guards/memory_pressure.py,sha256=mul1KXO9oE1i424cs92Sk6uzoRrV6Seck2Lk3bu-w68,7903
29
32
  coderouter/guards/tool_loop.py,sha256=QRUh0fM9LJx-KFd4751dRp2Tv_8ewjcXaW-TIeWsfJQ,12724
30
33
  coderouter/ingress/__init__.py,sha256=WQsCH2CGJCAhy0mS6GSEdeYZRkkQu2OHDsP4CJWTLug,155
31
- coderouter/ingress/anthropic_routes.py,sha256=eGS-z7hi_UMni8PtEWylIPuJs6YdjoKAPhMNaq-xptQ,12924
32
- coderouter/ingress/app.py,sha256=b9s6NpBGckaTQJCaw-6MZaY7Eq3rxgtX_cCkgWq-0Ig,6620
34
+ coderouter/ingress/anthropic_routes.py,sha256=pfk0rWWH5IJvdPF36h6egxYvX40RBrP5yTVfzOJArcw,15726
35
+ coderouter/ingress/app.py,sha256=E9Ze1hCWeZVqhh1DrDjrc1uJpjmjoHMMfv89tbReO6g,8031
33
36
  coderouter/ingress/dashboard_routes.py,sha256=jVFc_4Q67YrP0VIppuQfBLnLvnvaCWJJ4z7aZsHmu1s,21822
34
37
  coderouter/ingress/metrics_routes.py,sha256=M22dwOGn24P05Ge4W3c7d7mYytSGWjIR-pPSPOAiHJY,3965
35
38
  coderouter/ingress/openai_routes.py,sha256=Zw1efPw9DI6GgV8ZcLrzS6Cda0KLrFkKn2GBZWSe6Vo,6322
36
39
  coderouter/metrics/__init__.py,sha256=7Es351DPS7yLM0yVF_F0eesmiD83n7Zzhie44chht38,1465
37
- coderouter/metrics/collector.py,sha256=BOQcmD3UUPH17J6kONmeyldvuLQyxQKLShsbb2sV4sI,38501
38
- coderouter/metrics/prometheus.py,sha256=2uyr0AxzL2i6RQAr9ODeY-TkDXkaAdJbfrtT1KRC9B4,19192
40
+ coderouter/metrics/collector.py,sha256=JbFqXex9VzInhPIhHcPtuXeGR1tXsMD2r7o0uF2qLuw,43717
41
+ coderouter/metrics/prometheus.py,sha256=YRqyT931s40zVkIj07D-M2UNfDhIEElVFRz3izdJcnQ,24419
39
42
  coderouter/routing/__init__.py,sha256=g2vhutbozRx5QBThReqwPN3imk5qXdpDiaogILd3IRc,257
40
- coderouter/routing/adaptive.py,sha256=pRUphp2_NJ7i6I2HRNk3AkB7Wiu0amZ3vtgBHgmVLBU,20233
43
+ coderouter/routing/adaptive.py,sha256=G2o377twGSjbUh65wiIFx6klnpFGjsD_nI3oDvcBwhY,21257
41
44
  coderouter/routing/auto_router.py,sha256=4_sQR0ztSED9FgQSvQqgqSiydyQVY_qOSRvwyZ5BfRc,12909
42
45
  coderouter/routing/budget.py,sha256=XZQL24YIkusB2MwfgbIJ1uhW1ODtMMdpbZ5e_A7oSU4,7164
43
46
  coderouter/routing/capability.py,sha256=ziIDuE5keH_jxYDlXSKufRVxxSYOAvUxJ6Rw5QkYDDU,18436
44
- coderouter/routing/fallback.py,sha256=Rj5RRb5_EaxkbnMft8hHjlswn4PtPMLOawSrQk_B9C4,74294
47
+ coderouter/routing/fallback.py,sha256=cJW_LYghVKVHOsHmJFaGgEEAkJ4ZsfhFtstOODOI4y8,85983
45
48
  coderouter/translation/__init__.py,sha256=PYXN7XVEwpG1uC8RLy6fvnGbzEZhhrEuUapH8IYOtG8,1788
46
49
  coderouter/translation/anthropic.py,sha256=JpvIWNXHUPVqOGvps7o_6ZADhXuJuvpU7RdMqQFtwwM,6421
47
50
  coderouter/translation/convert.py,sha256=-qyzFzmmr9hhQV6_Sg75kJnvCZvHe3n7vRdaZtk_JqQ,47269
48
51
  coderouter/translation/tool_repair.py,sha256=fyxDb4kWHytO5JWq5y0i4tinJUtWqhMCkyfoCf5BjeM,8314
49
- coderouter_cli-2.0.0.dist-info/METADATA,sha256=rCenT_ZMrkR1GWvJHvs5cNG7sJEbonjdh40OKPURYS0,48192
50
- coderouter_cli-2.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
51
- coderouter_cli-2.0.0.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
52
- coderouter_cli-2.0.0.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
53
- coderouter_cli-2.0.0.dist-info/RECORD,,
52
+ coderouter_cli-2.1.0.dist-info/METADATA,sha256=5Jyg_kLzgs6PAuzyDdqjIVbabeUOwqu2k8c5V0ZTLrc,48570
53
+ coderouter_cli-2.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
54
+ coderouter_cli-2.1.0.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
55
+ coderouter_cli-2.1.0.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
56
+ coderouter_cli-2.1.0.dist-info/RECORD,,