Skip to content

feat: add strict support-model routing toggle - #789

Merged
awsl233777 merged 1 commit into
mainfrom
feature/strict-support-model-routing
Aug 6, 2026
Merged

feat: add strict support-model routing toggle#789
awsl233777 merged 1 commit into
mainfrom
feature/strict-support-model-routing

Conversation

@awsl233777

@awsl233777 awsl233777 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a default-off system setting for strict support-model routing
  • when enabled, skip providers whose configured SupportModels do not match the requested model and continue to the next provider
  • keep model-list availability checks strict so unavailable provider models are not advertised
  • add Settings UI toggle and English/Chinese copy

Validation

  • go test ./internal/router ./internal/handler ./tests/e2e -run 'TestStrictSupportModelsRouting|TestGetModels_OnlyCurrentAvailableModelsAcrossScopes|TestProxyMatchedRouteWithUnsupportedProviderModelRecordsRejectedRequest' -count=1
  • go test ./internal/router ./internal/handler ./internal/service ./tests/e2e -run 'TestStrictSupportModelsRouting|TestGetModels_OnlyCurrentAvailableModelsAcrossScopes|TestProxyMatchedRouteWithUnsupportedProviderModelRecordsRejectedRequest|TestSystemSettings|TestSettings' -count=1
  • go test ./internal/systemsettingcache ./internal/router ./internal/handler ./tests/e2e -run 'Test|TestStrictSupportModelsRouting|TestGetModels_OnlyCurrentAvailableModelsAcrossScopes|TestProxyMatchedRouteWithUnsupportedProviderModelRecordsRejectedRequest' -count=1
  • cd web && pnpm typecheck && pnpm lint

Notes

  • Default behavior remains unchanged unless strict_support_models_routing_enabled is set to true.
  • CodeRabbit was not checked or requested.

Summary by CodeRabbit

  • 新功能

    • 新增“严格支持模型路由”设置,可按提供商支持的模型筛选路由,避免请求发送至不支持目标模型的提供商。
    • 管理员可在设置页面启用或关闭该功能。
    • 当所有候选提供商均不支持目标模型时,返回明确的模型不支持错误。
  • 改进

    • 新增中英文设置说明与提示文案。
    • 未配置该选项时默认保持原有路由行为。

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

本次变更新增严格支持模型路由设置。路由器读取全局设置或请求级开关,并根据提供商的 SupportModels 过滤候选。管理设置页提供开关,测试覆盖启用、禁用和全部不支持场景。

Changes

严格支持模型路由

Layer / File(s) Summary
设置缓存与路由匹配
internal/domain/model.go, internal/systemsettingcache/boolean.go, internal/router/router.go, internal/service/system_setting_validation.go
新增设置键和布尔值校验。路由器支持系统设置仓库和请求级严格模式。严格模式启用时,路由器跳过不支持请求模型的提供商。
运行时接入与路由验证
cmd/maxx/main.go, internal/handler/models.go, internal/handler/models_availability_test.go, internal/router/support_models_routing_test.go, tests/e2e/*
应用入口和模型可用性处理传入 settingRepo。测试覆盖严格模式、传统候选列表、全部提供商不支持,以及端到端错误响应。
管理员设置界面
web/src/pages/settings/index.tsx, web/src/locales/en.json, web/src/locales/zh.json
管理员设置页新增严格支持模型路由开关。开关状态通过设置接口读取和保存。新增中英文说明。其他改动为格式调整。

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: bowl42

Sequence Diagram(s)

sequenceDiagram
  participant 管理员
  participant 设置页面
  participant 设置仓库
  participant Router
  participant 提供商
  管理员->>设置页面: 启用严格支持模型路由
  设置页面->>设置仓库: 保存布尔设置
  Router->>设置仓库: 读取严格路由设置
  Router->>提供商: 检查 SupportModels
  提供商-->>Router: 返回支持状态
  Router-->>设置页面: 返回匹配候选或 model_not_supported
Loading

Poem

兔子打开开关,
模型沿着新路奔跑。
不支持的提供商先跳过,
支持的候选继续报到。
全部不匹配时,
错误清楚地来到。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了新增严格支持模型路由开关这一主要变更,内容简洁且与变更集直接相关。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/strict-support-model-routing

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between df88cf9 and 3ca381e.

📒 Files selected for processing (13)
  • cmd/maxx/main.go
  • internal/domain/model.go
  • internal/handler/models.go
  • internal/handler/models_availability_test.go
  • internal/router/router.go
  • internal/router/support_models_routing_test.go
  • internal/service/system_setting_validation.go
  • internal/systemsettingcache/boolean.go
  • tests/e2e/proxy_integration_test.go
  • tests/e2e/proxy_setup_test.go
  • web/src/locales/en.json
  • web/src/locales/zh.json
  • web/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!

Comment on lines +146 to +149
settingRepo := &modelAvailabilitySettingRepo{values: map[string]string{domain.SettingKeyStrictSupportModelsRoutingEnabled: "true"}}
if len(settings) > 0 {
settingRepo = settings[0]
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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,确保每个隔离测试环境不复用严格路由设置缓存。

Comment on lines +1052 to +1057
const handleToggle = async (checked: boolean) => {
await updateSetting.mutateAsync({
key: STRICT_SUPPORT_MODELS_ROUTING_SETTING_KEY,
value: checked ? 'true' : 'false',
});
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 -S

Repository: 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 -S

Repository: 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 -S

Repository: 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)))
PY

Repository: 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:


捕获设置保存错误,避免未处理的 Promise rejection。

SupportModelRoutingSectionEnhancedFailureDetailsSection 的同名 handleToggle 都直接等待 updateSetting.mutateAsync,并绑定到 Switch.onCheckedChangeupdateSettingonError 会回滚设置缓存,但没有消费 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 且开关值不变。

@awsl233777
awsl233777 merged commit fadbe64 into main Aug 6, 2026
7 checks passed
@awsl233777
awsl233777 deleted the feature/strict-support-model-routing branch August 6, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant