coderouter-cli 2.7.0__py3-none-any.whl → 2.7.1__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/config/capability_registry.py +22 -0
- coderouter/config/schemas.py +78 -0
- coderouter/ingress/anthropic_routes.py +108 -0
- coderouter/logging.py +5 -0
- coderouter/routing/capability.py +217 -0
- coderouter/routing/fallback.py +152 -0
- coderouter/translation/tool_repair.py +644 -39
- {coderouter_cli-2.7.0.dist-info → coderouter_cli-2.7.1.dist-info}/METADATA +28 -9
- {coderouter_cli-2.7.0.dist-info → coderouter_cli-2.7.1.dist-info}/RECORD +12 -12
- {coderouter_cli-2.7.0.dist-info → coderouter_cli-2.7.1.dist-info}/WHEEL +0 -0
- {coderouter_cli-2.7.0.dist-info → coderouter_cli-2.7.1.dist-info}/entry_points.txt +0 -0
- {coderouter_cli-2.7.0.dist-info → coderouter_cli-2.7.1.dist-info}/licenses/LICENSE +0 -0
coderouter/routing/fallback.py
CHANGED
|
@@ -86,10 +86,14 @@ from coderouter.routing.adaptive import AdaptiveAdjuster
|
|
|
86
86
|
from coderouter.routing.budget import BudgetTracker
|
|
87
87
|
from coderouter.routing.capability import (
|
|
88
88
|
anthropic_request_has_cache_control,
|
|
89
|
+
anthropic_request_has_forced_tool_choice,
|
|
89
90
|
anthropic_request_requires_thinking,
|
|
91
|
+
emulate_tool_choice,
|
|
90
92
|
log_capability_degraded,
|
|
91
93
|
provider_supports_cache_control,
|
|
92
94
|
provider_supports_thinking,
|
|
95
|
+
provider_supports_tool_choice,
|
|
96
|
+
strip_cache_control,
|
|
93
97
|
strip_thinking,
|
|
94
98
|
)
|
|
95
99
|
from coderouter.translation import (
|
|
@@ -435,6 +439,104 @@ def _apply_context_budget_guard(
|
|
|
435
439
|
return request, "warning"
|
|
436
440
|
|
|
437
441
|
|
|
442
|
+
# ---------------------------------------------------------------------------
|
|
443
|
+
# S2 / S3 (shim): per-provider tool_choice + cache_control shims
|
|
444
|
+
#
|
|
445
|
+
# Applied at the adapter-call site (same position as strip_thinking) so the
|
|
446
|
+
# mutation is per-provider: a request that falls through to a *capable*
|
|
447
|
+
# provider later in the chain still receives its original tool_choice /
|
|
448
|
+
# cache_control markers. All three actions are opt-in (default ``off``) and
|
|
449
|
+
# read from the resolved profile.
|
|
450
|
+
#
|
|
451
|
+
# The shim is a pure function returning the (possibly-copied) request to
|
|
452
|
+
# send to *this* provider. It never mutates the original request object.
|
|
453
|
+
# ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _apply_tool_choice_shim(
|
|
457
|
+
request: AnthropicRequest,
|
|
458
|
+
*,
|
|
459
|
+
adapter: BaseAdapter,
|
|
460
|
+
action: str,
|
|
461
|
+
) -> AnthropicRequest:
|
|
462
|
+
"""S2: warn / emulate a forced tool_choice on a non-supporting provider.
|
|
463
|
+
|
|
464
|
+
Returns the request to hand to this provider. Only acts when the
|
|
465
|
+
request carries a forced ``tool_choice`` (``any`` / ``tool``) AND the
|
|
466
|
+
provider does not support it:
|
|
467
|
+
|
|
468
|
+
* ``warn`` → emit a ``capability-degraded`` log; request unchanged.
|
|
469
|
+
* ``emulate`` → return :func:`emulate_tool_choice` (tool_choice
|
|
470
|
+
stripped, system directive injected) + emit a
|
|
471
|
+
``tool-choice-emulated`` log.
|
|
472
|
+
|
|
473
|
+
Any other case (action ``off``, non-forced choice, or a capable
|
|
474
|
+
provider) returns ``request`` unchanged.
|
|
475
|
+
"""
|
|
476
|
+
if action == "off":
|
|
477
|
+
return request
|
|
478
|
+
if not anthropic_request_has_forced_tool_choice(request):
|
|
479
|
+
return request
|
|
480
|
+
if provider_supports_tool_choice(adapter.config):
|
|
481
|
+
return request
|
|
482
|
+
|
|
483
|
+
if action == "warn":
|
|
484
|
+
log_capability_degraded(
|
|
485
|
+
logger,
|
|
486
|
+
provider=adapter.name,
|
|
487
|
+
dropped=["tool_choice"],
|
|
488
|
+
reason="unsupported-backend",
|
|
489
|
+
)
|
|
490
|
+
return request
|
|
491
|
+
|
|
492
|
+
if action == "emulate":
|
|
493
|
+
choice = request.tool_choice if isinstance(request.tool_choice, dict) else {}
|
|
494
|
+
mode = choice.get("type")
|
|
495
|
+
tool_name = choice.get("name") if mode == "tool" else None
|
|
496
|
+
emulated = emulate_tool_choice(request)
|
|
497
|
+
logger.info(
|
|
498
|
+
"tool-choice-emulated",
|
|
499
|
+
extra={"provider": adapter.name, "tool_name": tool_name, "mode": mode},
|
|
500
|
+
)
|
|
501
|
+
return emulated
|
|
502
|
+
|
|
503
|
+
return request
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _apply_cache_control_shim(
|
|
507
|
+
request: AnthropicRequest,
|
|
508
|
+
*,
|
|
509
|
+
adapter: BaseAdapter,
|
|
510
|
+
action: str,
|
|
511
|
+
request_had_cache_control: bool,
|
|
512
|
+
) -> AnthropicRequest:
|
|
513
|
+
"""S3: strip cache_control markers before a non-supporting provider.
|
|
514
|
+
|
|
515
|
+
Returns the request to hand to this provider. Only acts when the
|
|
516
|
+
profile's ``cache_control_action`` is ``strip``, the request carries
|
|
517
|
+
cache_control markers, and the provider does not support them. Emits a
|
|
518
|
+
``cache-control-stripped`` log with the marker count. Never emits a
|
|
519
|
+
tokens-saved event (this is a wire-compatibility strip, not a savings).
|
|
520
|
+
|
|
521
|
+
When the strip does not apply, returns ``request`` unchanged — the
|
|
522
|
+
existing v0.5-B ``capability-degraded`` observability log (emitted at
|
|
523
|
+
the call site) is preserved as the ``off`` default behavior.
|
|
524
|
+
"""
|
|
525
|
+
if action != "strip":
|
|
526
|
+
return request
|
|
527
|
+
if not request_had_cache_control:
|
|
528
|
+
return request
|
|
529
|
+
if provider_supports_cache_control(adapter.config):
|
|
530
|
+
return request
|
|
531
|
+
|
|
532
|
+
stripped, markers_removed = strip_cache_control(request)
|
|
533
|
+
logger.info(
|
|
534
|
+
"cache-control-stripped",
|
|
535
|
+
extra={"provider": adapter.name, "markers_removed": markers_removed},
|
|
536
|
+
)
|
|
537
|
+
return stripped
|
|
538
|
+
|
|
539
|
+
|
|
438
540
|
def _emit_cache_observed(
|
|
439
541
|
response: AnthropicResponse,
|
|
440
542
|
*,
|
|
@@ -1596,6 +1698,25 @@ class FallbackEngine:
|
|
|
1596
1698
|
append_system_prompt=profile.append_system_prompt,
|
|
1597
1699
|
)
|
|
1598
1700
|
|
|
1701
|
+
def _resolve_shim_actions(self, profile_name: str | None) -> tuple[str, str]:
|
|
1702
|
+
"""S2 / S3 (shim): resolve ``(tool_choice_action, cache_control_action)``.
|
|
1703
|
+
|
|
1704
|
+
Both default to ``off``, so a missing / stub profile (e.g. a
|
|
1705
|
+
``__new__``-constructed test engine) yields the backward-compatible
|
|
1706
|
+
no-op pair. Resolved once at the top of each Anthropic entry point
|
|
1707
|
+
(profiles are immutable per request) and passed to every adapter
|
|
1708
|
+
call so the per-provider shim helpers stay pure.
|
|
1709
|
+
"""
|
|
1710
|
+
chosen = profile_name or self.config.default_profile
|
|
1711
|
+
try:
|
|
1712
|
+
profile = self.config.profile_by_name(chosen)
|
|
1713
|
+
except (KeyError, ValueError):
|
|
1714
|
+
return "off", "off"
|
|
1715
|
+
return (
|
|
1716
|
+
getattr(profile, "tool_choice_action", "off"),
|
|
1717
|
+
getattr(profile, "cache_control_action", "off"),
|
|
1718
|
+
)
|
|
1719
|
+
|
|
1599
1720
|
def _resolve_chain(self, profile_name: str | None) -> list[BaseAdapter]:
|
|
1600
1721
|
"""Return the list of adapters to try, in order, for this profile.
|
|
1601
1722
|
|
|
@@ -2306,6 +2427,10 @@ class FallbackEngine:
|
|
|
2306
2427
|
# ever asked for caching. Compute once; the v0.5-B gate uses the
|
|
2307
2428
|
# same value below for the capability-degraded log.
|
|
2308
2429
|
request_had_cache_control = anthropic_request_has_cache_control(request)
|
|
2430
|
+
# S2 / S3 (shim): resolve the opt-in per-provider actions once.
|
|
2431
|
+
tool_choice_action, cache_control_action = self._resolve_shim_actions(
|
|
2432
|
+
request.profile
|
|
2433
|
+
)
|
|
2309
2434
|
|
|
2310
2435
|
for adapter, will_degrade in chain:
|
|
2311
2436
|
is_native = isinstance(adapter, AnthropicAdapter)
|
|
@@ -2337,6 +2462,19 @@ class FallbackEngine:
|
|
|
2337
2462
|
dropped=["cache_control"],
|
|
2338
2463
|
reason="translation-lossy",
|
|
2339
2464
|
)
|
|
2465
|
+
# S2 (shim): forced tool_choice warn / emulate on non-supporting
|
|
2466
|
+
# providers. S3 (shim): cache_control strip. Both mutate a
|
|
2467
|
+
# per-provider copy; a later capable provider still gets the
|
|
2468
|
+
# original request.
|
|
2469
|
+
effective_request = _apply_tool_choice_shim(
|
|
2470
|
+
effective_request, adapter=adapter, action=tool_choice_action
|
|
2471
|
+
)
|
|
2472
|
+
effective_request = _apply_cache_control_shim(
|
|
2473
|
+
effective_request,
|
|
2474
|
+
adapter=adapter,
|
|
2475
|
+
action=cache_control_action,
|
|
2476
|
+
request_had_cache_control=request_had_cache_control,
|
|
2477
|
+
)
|
|
2340
2478
|
logger.info(
|
|
2341
2479
|
"try-provider",
|
|
2342
2480
|
extra={
|
|
@@ -2534,6 +2672,10 @@ class FallbackEngine:
|
|
|
2534
2672
|
# v1.9-A: compute once for the v0.5-B capability-degraded gate
|
|
2535
2673
|
# AND for the cache-observed emission below.
|
|
2536
2674
|
request_had_cache_control = anthropic_request_has_cache_control(request)
|
|
2675
|
+
# S2 / S3 (shim): resolve the opt-in per-provider actions once.
|
|
2676
|
+
tool_choice_action, cache_control_action = self._resolve_shim_actions(
|
|
2677
|
+
request.profile
|
|
2678
|
+
)
|
|
2537
2679
|
|
|
2538
2680
|
for adapter, will_degrade in chain:
|
|
2539
2681
|
is_native = isinstance(adapter, AnthropicAdapter)
|
|
@@ -2557,6 +2699,16 @@ class FallbackEngine:
|
|
|
2557
2699
|
dropped=["cache_control"],
|
|
2558
2700
|
reason="translation-lossy",
|
|
2559
2701
|
)
|
|
2702
|
+
# S2 / S3 (shim): mirror of the non-streaming path.
|
|
2703
|
+
effective_request = _apply_tool_choice_shim(
|
|
2704
|
+
effective_request, adapter=adapter, action=tool_choice_action
|
|
2705
|
+
)
|
|
2706
|
+
effective_request = _apply_cache_control_shim(
|
|
2707
|
+
effective_request,
|
|
2708
|
+
adapter=adapter,
|
|
2709
|
+
action=cache_control_action,
|
|
2710
|
+
request_had_cache_control=request_had_cache_control,
|
|
2711
|
+
)
|
|
2560
2712
|
logger.info(
|
|
2561
2713
|
"try-provider",
|
|
2562
2714
|
extra={
|