feat: add strict support-model routing toggle - #789
Conversation
📝 WalkthroughWalkthrough本次变更新增严格支持模型路由设置。路由器读取全局设置或请求级开关,并根据提供商的 Changes严格支持模型路由
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant 管理员
participant 设置页面
participant 设置仓库
participant Router
participant 提供商
管理员->>设置页面: 启用严格支持模型路由
设置页面->>设置仓库: 保存布尔设置
Router->>设置仓库: 读取严格路由设置
Router->>提供商: 检查 SupportModels
提供商-->>Router: 返回支持状态
Router-->>设置页面: 返回匹配候选或 model_not_supported
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: 2
🤖 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/handler/models_availability_test.go`:
- Around line 146-149: 在 internal/handler/models_availability_test.go 的行 146-149
中,确定 settingRepo 后、创建 Router 前调用
systemsettingcache.Invalidate(domain.SettingKeyStrictSupportModelsRoutingEnabled)。在
tests/e2e/proxy_setup_test.go 的行 134 同样在创建 Router 前调用该
Invalidate,确保每个隔离测试环境不复用严格路由设置缓存。
In `@web/src/pages/settings/index.tsx`:
- Around line 1052-1057: 在 SupportModelRoutingSection 和
EnhancedFailureDetailsSection 的 handleToggle 中捕获 updateSetting.mutateAsync 的
rejected Promise,避免触发 unhandledrejection,并通过可访问的错误状态提示保存失败。确保失败时沿用 onError
的缓存回滚,使 Switch 保持原有值;补充 mock 测试验证拒绝请求不会产生 unhandledrejection 且开关值不变。
🪄 Autofix
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: CHILL
Plan: Pro Plus
Run ID: cd9eb953-4868-42fb-b9df-49736abbc3fa
📒 Files selected for processing (13)
cmd/maxx/main.gointernal/domain/model.gointernal/handler/models.gointernal/handler/models_availability_test.gointernal/router/router.gointernal/router/support_models_routing_test.gointernal/service/system_setting_validation.gointernal/systemsettingcache/boolean.gotests/e2e/proxy_integration_test.gotests/e2e/proxy_setup_test.goweb/src/locales/en.jsonweb/src/locales/zh.jsonweb/src/pages/settings/index.tsx
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
- GitHub Check: multiinstance
- GitHub Check: e2e
- GitHub Check: playwright
🔇 Additional comments (12)
internal/domain/model.go (1)
978-978: LGTM!internal/service/system_setting_validation.go (1)
16-16: LGTM!internal/systemsettingcache/boolean.go (1)
4-10: LGTM!Also applies to: 29-38, 48-51
internal/router/router.go (1)
20-23: LGTM!Also applies to: 65-65, 75-75, 93-105, 337-337, 549-551
cmd/maxx/main.go (1)
280-280: LGTM!internal/handler/models.go (1)
171-176: LGTM!internal/router/support_models_routing_test.go (1)
12-73: LGTM!Also applies to: 75-131
tests/e2e/proxy_integration_test.go (1)
1189-1191: LGTM!web/src/pages/settings/index.tsx (2)
70-73: LGTM!Also applies to: 819-825, 877-880, 908-910, 975-977, 1766-1766, 1777-1779
143-143: LGTM!Also applies to: 1045-1051, 1059-1073, 1079-1094
web/src/locales/en.json (1)
1379-1382: LGTM!web/src/locales/zh.json (1)
1376-1379: LGTM!
| settingRepo := &modelAvailabilitySettingRepo{values: map[string]string{domain.SettingKeyStrictSupportModelsRoutingEnabled: "true"}} | ||
| if len(settings) > 0 { | ||
| settingRepo = settings[0] | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
在每个隔离测试环境中清除严格路由设置缓存。
systemsettingcache 是进程级缓存,并且仅按设置键缓存值。前一个测试环境可在 BooleanTTL 内缓存 "true" 或 "false"。后续环境即使使用不同的设置仓库,也会读取该旧值。这样会使注入的设置值或缺失设置的默认值失效,并导致测试结果依赖执行顺序。
internal/handler/models_availability_test.go#L146-L149: 在确定settingRepo后、创建Router前调用systemsettingcache.Invalidate(domain.SettingKeyStrictSupportModelsRoutingEnabled)。tests/e2e/proxy_setup_test.go#L134-L134: 在创建Router前调用相同的Invalidate,以隔离每个NewProxyTestEnv实例。
📍 Affects 2 files
internal/handler/models_availability_test.go#L146-L149(this comment)tests/e2e/proxy_setup_test.go#L134-L134
🤖 Prompt for 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.
In `@internal/handler/models_availability_test.go` around lines 146 - 149, 在
internal/handler/models_availability_test.go 的行 146-149 中,确定 settingRepo 后、创建
Router 前调用
systemsettingcache.Invalidate(domain.SettingKeyStrictSupportModelsRoutingEnabled)。在
tests/e2e/proxy_setup_test.go 的行 134 同样在创建 Router 前调用该
Invalidate,确保每个隔离测试环境不复用严格路由设置缓存。
| const handleToggle = async (checked: boolean) => { | ||
| await updateSetting.mutateAsync({ | ||
| key: STRICT_SUPPORT_MODELS_ROUTING_SETTING_KEY, | ||
| value: checked ? 'true' : 'false', | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file =="
fd -a 'index\.tsx$' . | rg 'web/src/pages/settings/index\.tsx$' || true
echo "== relevant lines =="
nl -ba web/src/pages/settings/index.tsx | sed -n '1020,1095p'
echo "== related updateSetting definitions/usages =="
rg -n "updateSetting|STRICT_SUPPORT_MODELS_ROUTING_SETTING_KEY|Settings|onCheckedChange" web/src -SRepository: awsl-project/maxx
Length of output: 286
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file =="
fd 'index\.tsx$' web/src/pages/settings | head -20
echo "== relevant lines with awk =="
awk 'NR>=1020 && NR<=1095 { printf "%6d\t%s\n", NR, $0 }' web/src/pages/settings/index.tsx
echo "== related updateSetting definitions/usages =="
rg -n "updateSetting|STRICT_SUPPORT_MODELS_ROUTING_SETTING_KEY|Settings|onCheckedChange" web/src -SRepository: awsl-project/maxx
Length of output: 35339
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== use-update-setting hook =="
awk 'NR>=80 && NR>=130 { printf "%6d\t%s\n", NR, $0 }' web/src/hooks/queries/use-settings.ts
echo "== proxy kill switch example =="
nl -ba web/src/components/settings/proxy-kill-switch-card.tsx 2>/dev/null || awk 'NR>=1 && NR<=80 { printf "%6d\t%s\n", NR, $0 }' web/src/components/settings/proxy-kill-switch-card.tsx
echo "== app error handling =="
rg -n "ReactQueryError|unhandledrejection|setErrorHandler|ErrorBoundary|uncaught" web/src -SRepository: awsl-project/maxx
Length of output: 4972
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== hook exports and relevant lines with grep-aware awk =="
awk 'NR>=40 && NR<=65' web/src/hooks/queries/use-settings.ts
echo "== package versions snippets =="
for f in package.json web/package.json web/package-lock.json package-lock.json; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n '"(`@tanstack/query-core`|`@tanstack/react-query`|react-query|vite|vite-react)' "$f" | head -80 || true
fi
done
echo "== browser event-handler rejected promise behavior standalone =="
node - <<'JS'
async function asyncHandlerReject() {
await Promise.reject(new Error('fake mutation failure'));
}
const handler = asyncHandlerReject();
handler.catch(() => {});
JS
echo "== source code shape validation for unhandled mutation failure =="
python3 - <<'PY'
from pathlib import Path as P
import re
p = P('web/src/pages/settings/index.tsx')
text = p.read_text()
pattern = re.compile(r'const\s+handleToggle\s*=\s*async\s*\([^)]*\)\s*=>\s*\{(?P<body>.*?)\n\s*\};', re.S)
m = pattern.search(text[text.index('export function SupportModelRoutingSection'):])
if not m:
print('handleToggle not found in SupportModelRoutingSection')
raise SystemExit(1)
body = m.group('body')
print('handleToggle body:')
for line in body.strip().splitlines():
print(' ' + line)
print('contains catch:', bool(re.search(r'\bcatch\b', body)))
print('contains unhandledrejection reference:', bool(re.search(r'unhandledrejection', text)))
PYRepository: awsl-project/maxx
Length of output: 1764
🌐 Web query:
MDN async function returned value rejecting promise unhandled error browser event handler
💡 Result:
In JavaScript, when an async function is used as a browser event handler, it returns a promise [1][2]. Because the browser's event system expects to execute functions synchronously, it does not "await" the promise returned by the async handler [2]. If an error is thrown or a promise is rejected within that async handler, there is no caller to await it, resulting in an unhandled promise rejection rather than a standard synchronous exception [1][3][2]. Consequently, the standard error event does not catch these failures because it is designed for synchronous errors [3]. Instead, these errors trigger the unhandledrejection event on the global Window object [3][4][5]. To properly manage errors in async event handlers: 1. Use try-catch blocks: Wrap the internal logic of your async event handler in a try-catch block to handle errors explicitly within the function [6][7]. 2. Global handling: Listen for the unhandledrejection event on the window object to catch any async errors that were not handled locally [4][7]. You can call event.preventDefault within this listener to prevent the default behavior, such as logging the error to the console [4]. 3. Utility wrapper: You can create a higher-order function to wrap your async handlers, which automatically attaches a.catch handler to the returned promise [8]. Example of a robust async handler: button.addEventListener('click', async (event) => { try { await performAsyncAction; } catch (error) { console.error('Handled error:', error); } }); Example of a global unhandled rejection handler: window.addEventListener('unhandledrejection', (event) => { console.warn('Unhandled promise rejection:', event.reason); event.preventDefault; // Prevents the error from bubbling to the console });
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- 2: https://dev.to/somedood/promises-and-events-some-pitfalls-and-workarounds-elp
- 3: https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event
- 4: https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event
- 5: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
- 6: https://advancedweb.hu/how-to-avoid-uncaught-async-errors-in-javascript/
- 7: https://code-js.in/js/javascript-error-handling-patterns/
- 8: https://stackoverflow.com/questions/61080783/handling-errors-in-async-event-handlers-in-javascript-in-the-web-browser
捕获设置保存错误,避免未处理的 Promise rejection。
SupportModelRoutingSection 和 EnhancedFailureDetailsSection 的同名 handleToggle 都直接等待 updateSetting.mutateAsync,并绑定到 Switch.onCheckedChange。updateSetting 的 onError 会回滚设置缓存,但没有消费 mutateAsync 返回的 rejected Promise;当请求失败时,浏览器可能产生 unhandledrejection,而开关也不会提示保存失败。
用 try/catch 处理 updateSetting 并显示可访问错误状态;添加 mock 验证 rejected updateSetting 不会触发 unhandledrejection,同时开关保持原有值。
🤖 Prompt for 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.
In `@web/src/pages/settings/index.tsx` around lines 1052 - 1057, 在
SupportModelRoutingSection 和 EnhancedFailureDetailsSection 的 handleToggle 中捕获
updateSetting.mutateAsync 的 rejected Promise,避免触发
unhandledrejection,并通过可访问的错误状态提示保存失败。确保失败时沿用 onError 的缓存回滚,使 Switch 保持原有值;补充
mock 测试验证拒绝请求不会产生 unhandledrejection 且开关值不变。
Summary
SupportModelsdo not match the requested model and continue to the next providerValidation
go test ./internal/router ./internal/handler ./tests/e2e -run 'TestStrictSupportModelsRouting|TestGetModels_OnlyCurrentAvailableModelsAcrossScopes|TestProxyMatchedRouteWithUnsupportedProviderModelRecordsRejectedRequest' -count=1go test ./internal/router ./internal/handler ./internal/service ./tests/e2e -run 'TestStrictSupportModelsRouting|TestGetModels_OnlyCurrentAvailableModelsAcrossScopes|TestProxyMatchedRouteWithUnsupportedProviderModelRecordsRejectedRequest|TestSystemSettings|TestSettings' -count=1go test ./internal/systemsettingcache ./internal/router ./internal/handler ./tests/e2e -run 'Test|TestStrictSupportModelsRouting|TestGetModels_OnlyCurrentAvailableModelsAcrossScopes|TestProxyMatchedRouteWithUnsupportedProviderModelRecordsRejectedRequest' -count=1cd web && pnpm typecheck && pnpm lintNotes
strict_support_models_routing_enabledis set totrue.Summary by CodeRabbit
新功能
改进