feat(ext): expose the detected session ID to request rewriters - #612
Conversation
Extensions can now steer load balancing for virtual models. A new ext.RouteSelector registers through the registry's single selector slot and is consulted by redirects using the new "adaptive" strategy; it picks among the currently viable targets (catalog-supported, with rate-limit capacity) and observes every upstream attempt — primaries, retries, and failovers — through the llmclient hooks, so it can score targets by real traffic. Session affinity, capacity filtering, failover chains, and retries all remain core's responsibility. Without a registered selector — or when the selector declines, answers outside the pool, or panics (contained and logged) — the adaptive strategy behaves exactly like round_robin, so configs stay portable between core and extended builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthrough
ChangesSession ID propagation
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/server/request_rewrite_test.go`:
- Around line 122-154: Convert TestRequestRewriteMiddlewareExposesSessionID into
a table-driven test covering both a populated session ID and an absent session
ID. For each case, configure the session-stamping middleware accordingly,
execute the request, and assert the rewriter observes the expected session
value, including an empty default when no session is present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ef829019-e33f-432e-920d-8082be09d54a
📒 Files selected for processing (3)
ext/ext.gointernal/server/request_rewrite.gointernal/server/request_rewrite_test.go
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Files Needing Attention: No files require changes.
What T-Rex did
Reviews (2): Last reviewed commit: "test(server): assert the rewriter ran in..." | Re-trigger Greptile |
Panic recovery around selector calls no longer re-enters extension code: the selector's name is captured panic-safe at install time, so a Name that panics mid-recovery can no longer escape the handler and abort the request instead of falling back. The recovered value is no longer logged — it is extension-controlled and could carry request data. Candidate pricing is now defensively copied: selectors received the catalog's own *float64 values, so a misbehaving extension could mutate the prices that cost routing and cost calculation later read. The per-attempt observation contract is corrected to match behavior: hooks fire once per upstream call, and transport-level retries inside the provider client are aggregated into that call's single outcome (final status, duration spanning retry backoff). Moving observation inside the client retry loop would change hook semantics for every consumer (metrics, health tracking) and is out of scope here. Also: registry selector-slot tests are table-driven (incl. nil reset), the delegation test asserts the full candidate set, a regression test covers the double-panic path, and the example config no longer implies the adaptive strategy requires an extension. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3d71ff7 to
1a3bd5b
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1a3bd5b to
5a91572
Compare
The strategy dropdown was hardcoded to round_robin and cost, so the new adaptive strategy was unreachable from the UI even on deployments with a route selector registered — and hardcoding would have offered it on plain core, where it silently falls back to round robin. /admin/runtime/config now carries VIRTUAL_MODEL_STRATEGIES, the comma- separated strategies this deployment supports; adaptive appears only when a route-selector extension is registered. The backend owns the values, the dashboard owns the labels (unknown values render as their raw value), and the editor keeps the edited row's current strategy selectable even when the deployment no longer offers it, so opening an adaptive virtual model on a plain-core dashboard no longer renders a blank select that could silently rewrite the strategy on save. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5a91572 to
edd6ec5
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/server/request_rewrite_test.go`:
- Around line 133-165: In the test around the capturing stubRewriter, assert
that capturing.calls equals 1 after the request succeeds and before validating
seenSession. This must cover both session and no-session table cases, ensuring
the rewriter was invoked rather than relying only on the default empty
seenSession value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 848cc916-eae3-4f57-9dd6-c874e641aac6
📒 Files selected for processing (3)
ext/ext.gointernal/server/request_rewrite.gointernal/server/request_rewrite_test.go
The label promised health/latency/cost routing, but core's contract only delegates target choice to the registered extension — describe that, not one extension's policy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Session detection already runs before the rewrite middleware, so the scoped session ID is sitting in the request context when rewriters fire. Surfacing it on ext.Input lets a rewriter keep per-conversation decisions stable — for example pinning a model-tier choice to the session instead of re-deciding on every message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
edd6ec5 to
3662589
Compare
# Conflicts: # internal/admin/dashboard/static/dist/index.html # web/dashboard/src/pages/models/VirtualModelEditor.svelte
The help condition used the dropdown options, which include an edited row's unsupported value for compatibility — so an adaptive virtual model opened on a deployment without a route selector still showed help claiming a registered extension picks targets. Gate on the server-provided strategy list instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # internal/admin/dashboard/static/dist/index.html
…normalization normalizeDashboardRuntimeConfig rebuilt the response field-by-field and dropped VirtualModelStrategies, so /admin/runtime/config never exposed the strategy list and the dashboard could not offer adaptive. Carry the field through and assert it in the handler-level runtime-config test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
Adds
SessionIDtoext.Input. Session detection (SessionCapture) already runs beforeRequestRewriteMiddleware, so the user-path-scoped session ID is in the request context when rewriters fire — this just surfaces it.Motivation: lets a rewriter keep per-conversation decisions stable across messages, e.g. GoModel Pro's content-aware tier router pinning its easy/hard model choice to the session instead of re-deciding (and potentially flip-flopping) on every message.
Stacked on #611 (base:
feat/ext-route-selector); rebase tomainif it lands first.Tests
TestRequestRewriteMiddlewareExposesSessionID— stamps a session viaExtraMiddleware(which runs before rewriters, standing in for the detector) and asserts the rewriter observes it.🤖 Generated with Claude Code
Summary by CodeRabbit