Skip to content

fix: respect case-sensitive provider model ids#1011

Merged
ding113 merged 2 commits intodevfrom
fix/model-case-sensitive-allowlist
Apr 11, 2026
Merged

fix: respect case-sensitive provider model ids#1011
ding113 merged 2 commits intodevfrom
fix/model-case-sensitive-allowlist

Conversation

@ding113
Copy link
Copy Markdown
Owner

@ding113 ding113 commented Apr 11, 2026

Summary

Fix case-sensitive duplicate detection in provider allowlist and model redirect editors/schemas, so that model IDs differing only in case (e.g. GLM-5 vs glm-5) can coexist as separate rules.

Problem

The runtime matching logic in model-pattern-matcher.ts and allowed-model-rules.ts is case-sensitive -- GLM-5 and glm-5 are treated as distinct model IDs. However, the duplicate-detection code in both the Zod validation schemas and the editor UI components applied .toLowerCase() before comparing rule identities. This meant:

  • Adding both GLM-5 (exact) and glm-5 (exact) in the allowlist editor would incorrectly flag them as duplicates
  • The same issue applied to model redirect rules with different-cased sources
  • Editor-side dedup semantics were inconsistent with runtime matching behavior

Related Issues/PRs:

Solution

Remove .toLowerCase() from the identity/dedup key computation in four locations so that editor validation matches runtime matching semantics:

  1. Schema validation -- provider-allowed-model-schema.ts (2 schemas) and provider-model-redirect-schema.ts (1 schema)
  2. Editor UI -- allowed-model-rule-editor.tsx and model-redirect-editor.tsx (getRuleIdentity functions)

Changes

Core Changes

  • src/lib/provider-allowed-model-schema.ts -- Remove .toLowerCase() from duplicate-check keys in both INPUT_LIST_SCHEMA and LIST_SCHEMA
  • src/lib/provider-model-redirect-schema.ts -- Remove .toLowerCase() from duplicate-check key in LIST_SCHEMA
  • src/app/[locale]/settings/providers/_components/allowed-model-rule-editor.tsx -- Remove .toLowerCase() from getRuleIdentity
  • src/app/[locale]/settings/providers/_components/model-redirect-editor.tsx -- Remove .toLowerCase() from getRuleIdentity

Test Coverage

  • tests/unit/lib/provider-allowed-model-schema.test.ts -- Verify GLM-5 and glm-5 can coexist in allowlist schema
  • tests/unit/lib/provider-model-redirect-schema.test.ts -- Verify GLM-5 and glm-5 can coexist as redirect sources
  • tests/unit/settings/providers/allowed-model-rule-editor.test.tsx -- Verify editor accepts both cased variants without error
  • tests/unit/settings/providers/model-redirect-editor.test.tsx -- Verify redirect editor accepts both cased variants

Testing

bunx vitest run tests/unit/lib/provider-allowed-model-schema.test.ts tests/unit/lib/provider-model-redirect-schema.test.ts tests/unit/settings/providers/allowed-model-rule-editor.test.tsx tests/unit/settings/providers/model-redirect-editor.test.tsx
bunx vitest run tests/unit/proxy/provider-selector-allowed-model-rules.test.ts
bun run typecheck
bun run lint
bun run build
bun run test

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally
  • No breaking changes -- only relaxes a validation constraint

Description enhanced by Claude AI

Greptile Summary

This PR removes .toLowerCase() from duplicate-detection keys in schema validation and editor UI components so that model IDs differing only in case (e.g. GLM-5 vs glm-5) can coexist as separate rules, aligning editor semantics with the runtime's case-sensitive matching behavior. The normalizeAllowedModelRules transform (which runs before schema dedup in PROVIDER_ALLOWED_MODEL_RULE_INPUT_LIST_SCHEMA) was confirmed to only trim whitespace — it does not lowercase — so the fix is effective end-to-end.

Confidence Score: 5/5

Safe to merge — targeted one-line removals of .toLowerCase() across five files, all consistent with runtime behavior and covered by new tests.

All changed sites apply the same fix symmetrically. The normalizeAllowedModelRules transform (which precedes schema dedup) was confirmed to only trim whitespace, not lowercase, so the fix is effective end-to-end. New tests cover the primary regression case in every affected layer. No P0/P1 findings.

No files require special attention.

Important Files Changed

Filename Overview
src/lib/provider-allowed-model-schema.ts Removed .toLowerCase() from dedup keys in both INPUT_LIST_SCHEMA and LIST_SCHEMA; dedup logic is now case-sensitive and consistent with runtime behavior.
src/lib/provider-model-redirect-schema.ts Removed .toLowerCase() from redirect rule dedup key; case-sensitive matching now applies consistently at both validation and runtime layers.
src/app/[locale]/settings/providers/_components/allowed-model-rule-editor.tsx Removed .toLowerCase() from getRuleIdentity; handles add, edit, remove, move, and model-picker path — all consistent after the fix.
src/app/[locale]/settings/providers/_components/model-redirect-editor.tsx Removed .toLowerCase() from getRuleIdentity; edit-while-delete test confirms index-based save still works correctly under the case-sensitive key scheme.
src/app/[locale]/settings/providers/_components/model-multi-select.tsx Removed .toLowerCase() from getModelKey; search filtering intentionally remains case-insensitive (UX), while selection/dedup keys are now case-sensitive.
tests/unit/lib/provider-allowed-model-schema.test.ts Adds GLM-5/glm-5 coexistence test for PROVIDER_ALLOWED_MODEL_RULE_INPUT_LIST_SCHEMA; PROVIDER_ALLOWED_MODEL_RULE_LIST_SCHEMA (also fixed) has no parallel case-sensitivity test, but both schemas use identical dedup logic so the fix is verifiable by inspection.
tests/unit/lib/provider-model-redirect-schema.test.ts Adds GLM-5/glm-5 coexistence test for PROVIDER_MODEL_REDIRECT_RULE_LIST_SCHEMA; covers the redirect schema fix directly.
tests/unit/settings/providers/allowed-model-rule-editor.test.tsx New test verifies the editor accepts both GLM-5 and glm-5 without a duplicate error; existing tests cover add, edit, and large-list scenarios.
tests/unit/settings/providers/model-redirect-editor.test.tsx New test verifies the redirect editor accepts both GLM-5 and glm-5 as separate sources without error.
tests/unit/settings/providers/model-multi-select.test.tsx New test confirms deselecting GLM-5 leaves glm-5 intact in the picker — covers the key UX regression introduced by the old lowercase key scheme.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User enters model ID\n(e.g. GLM-5 or glm-5)"] --> B["Editor UI\ngetRuleIdentity(rule)\nmatchType + ':' + pattern.trim()"]
    B --> C{Duplicate check\ncase-sensitive}
    C -- "GLM-5 ≠ glm-5 ✓" --> D["Rule accepted\nonChange called"]
    C -- "GLM-5 = GLM-5 ✗" --> E["Error: duplicate rule"]
    D --> F["Zod Schema Validation\nmatchType + ':' + pattern.trim()"]
    F --> G{Dedup refine\ncase-sensitive}
    G -- "GLM-5 ≠ glm-5 ✓" --> H["Schema valid\nRules persisted"]
    G -- "duplicate detected ✗" --> I["Validation error"]
    H --> J["Runtime matching\nmodel-pattern-matcher.ts\ncase-sensitive"]
    J -- "GLM-5 matches GLM-5" --> K["Request allowed"]
    J -- "glm-5 matches glm-5" --> K

    style C fill:#22c55e,color:#fff
    style G fill:#22c55e,color:#fff
    style J fill:#22c55e,color:#fff
Loading

Reviews (2): Last reviewed commit: "fix: keep model picker ids case-sensitiv..." | Re-trigger Greptile

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

将规则/模型标识键的归一化行为从小写化改为仅修剪空白:构建身份字符串时不再对 pattern/source/模型键进行 .toLowerCase(),使重复检测和编辑/删除/移动匹配变为区分字母大小写。

Changes

Cohort / File(s) Summary
编辑器身份识别
src/app/[locale]/settings/providers/_components/allowed-model-rule-editor.tsx, src/app/[locale]/settings/providers/_components/model-redirect-editor.tsx
getRuleIdentity 停止对 pattern/source 做小写化,仅 trim() 后用于生成 ${matchType}:${value},改变 UI 操作(添加/编辑/删除/重排)和重复检查的匹配规则为大小写敏感。
Schema 去重逻辑
src/lib/provider-allowed-model-schema.ts, src/lib/provider-model-redirect-schema.ts
将去重键从 trim().toLowerCase() 改为仅 trim(),使 schema 层对 pattern/source 的重复判定区分大小写。
模型选择键
src/app/[locale]/settings/providers/_components/model-multi-select.tsx
getModelKey 不再对 normalizeModelName(model).toLowerCase(),改变选中/切换/过滤键的大小写敏感性。
单元测试:schema
tests/unit/lib/provider-allowed-model-schema.test.ts, tests/unit/lib/provider-model-redirect-schema.test.ts
新增用例覆盖大小写不同但内容相同(如 GLM-5glm-5)的规则可共存,断言 schema 验证通过。
单元测试:UI 组件
tests/unit/settings/providers/allowed-model-rule-editor.test.tsx, tests/unit/settings/providers/model-redirect-editor.test.tsx, tests/unit/settings/providers/model-multi-select.test.tsx
新增 UI 测试,验证编辑器与多选组件允许大小写仅异的模型/规则同时存在且无错误,且 onChange 行为符合预期。

Estimated code review effort

🎯 3 (中等) | ⏱️ ~20 分钟

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR title clearly identifies the main change: making provider model IDs case-sensitive, which aligns with the changeset's core objective of removing toLowerCase() from duplicate detection.
Description check ✅ Passed The PR description clearly explains the problem (case-insensitive duplicate detection inconsistent with runtime behavior), the solution (remove .toLowerCase()), affected files, and test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/model-case-sensitive-allowlist

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the model rule and redirection logic to be case-sensitive. By removing the .toLowerCase() transformation during rule identity generation and schema validation, the system now allows multiple rules or redirects that differ only by case (e.g., "GLM-5" and "glm-5"). Corresponding unit and UI tests have been added to verify this behavior. I have no feedback to provide.

@github-actions github-actions Bot added bug Something isn't working area:provider labels Apr 11, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67e0d21436

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

function getRuleIdentity(rule: Pick<AllowedModelRule, "matchType" | "pattern">): string {
return `${rule.matchType}:${rule.pattern.trim().toLowerCase()}`;
// exact/prefix/suffix/contains/regex 的运行时匹配都区分大小写,这里去重也必须保持一致。
return `${rule.matchType}:${rule.pattern.trim()}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep picker keys case-sensitive for exact model rules

Switching rule identity to matchType + pattern makes GLM-5 and glm-5 valid distinct rules, but the quick picker still collapses selections by lowercased keys (getModelKey/handleToggleModel in model-multi-select.tsx). In this mixed-case scenario, toggling one selected item removes both entries because removal filters by the shared lowercase key, so users can silently lose rules after interacting with the picker even though the editor now allows them.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in ff94e4f. ModelMultiSelect now keeps its internal keys case-sensitive as well, so selected exact rules like GLM-5 and glm-5 no longer collapse in the picker. Added a regression test in tests/unit/settings/providers/model-multi-select.test.tsx that verifies toggling one mixed-case entry preserves the other.

@github-actions github-actions Bot added the size/S Small PR (< 200 lines) label Apr 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

No significant issues identified in this PR.

PR Size: S

  • Lines changed: 125
  • Files changed: 8

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Codex AI

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

  • Reviewed PR #1011 (“fix: respect case-sensitive provider model ids”): 120 additions, 5 deletions, 8 files → Size S
  • Applied PR label size/S
  • Submitted the required PR review summary comment via gh pr review (no diff-scoped issues met the reporting threshold, so no inline comments were posted)

@github-actions
Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@ding113 ding113 merged commit d2e71b8 into dev Apr 11, 2026
12 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Apr 11, 2026
@github-actions github-actions Bot mentioned this pull request Apr 13, 2026
13 tasks
ding113 added a commit that referenced this pull request Apr 13, 2026
* fix: relax provider routing rule limits

* feat: improve provider detection template selection (#1003)

* feat: improve provider detection templates

* fix: address provider detection review feedback

* chore: format code (feat-provider-detect-templates-02e4d1f)

* test: cover provider detection fallback retries

* chore: format code (feat-provider-detect-templates-4dc1701)

* fix: tighten provider detection validation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix: honor disabled error rules during queued reloads (#1006)

* fix: honor disabled error rules during queued reloads

* fix: propagate matched rule metadata in hedge errors

* fix: avoid hot retries on error rule reload failure

* fix: avoid redundant error-rule reload passes

* fix: await queued error rule reload completion

* fix: prevent STREAM_PROCESSING_ERROR from premature agent pool eviction (#1009)

* fix: prevent STREAM_PROCESSING_ERROR from premature agent pool eviction

The agent pool cleanup timer was destroying HTTP agents after 5min TTL
with no awareness of in-flight streaming requests, causing
ClientDestroyedError and ERR_HTTP2_STREAM_ERROR for long-running
responses. These errors were also misclassified because isTransportError
did not recognize them.

Three fixes:
1. Add UND_ERR_DESTROYED, UND_ERR_CLOSED, and HTTP/2 errors to
   isTransportError() so they classify as STREAM_UPSTREAM_ABORTED
2. Add reference counting (activeRequests) to AgentPool to prevent
   eviction of agents with in-flight requests (30min hard upper bound)
3. Wire up releaseAgent lifecycle in forwarder/response-handler to
   decrement refcount when streams complete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: harden agent pool refcount against cross-generation misattribution

Addresses bot review feedback on #1009 covering four root causes that
still let STREAM_PROCESSING_ERROR leak through under concurrency:

- Track dispatcher generation ids so releaseAgent can ignore stale
  releases from old dispatchers after markUnhealthy / 30-min hard
  expire / LRU eviction rebuilds the same cacheKey
- Increment activeRequests for pending-creation waiters, not just the
  creator, so concurrent bursts don't prematurely evict the shared
  agent once the first request completes
- Move agent acquisition inside forwarder's fetch try block so any
  exception between acquire and fetch still releases the refcount
- Clear proxyConfig after a successful proxy-to-direct fallback to
  prevent session.releaseAgent double-decrementing the shared agent
- Add ERR_HTTP2_STREAM_ERROR to transport error codes so wrapped
  HTTP/2 errors on the cause chain are detected by isTransportError

Adds regression coverage for concurrent pending-creation waiter
counting (Promise.all with blocked createAgent), stale dispatcher
generation release safety, and HTTP/2 cause-chain detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: respect case-sensitive provider model ids (#1011)

* fix: respect case-sensitive provider model ids

* fix: keep model picker ids case-sensitive

* chore: update package dependencies to use major version ranges

* fix: address code review findings from PR #1014

- Use nullish coalescing (??) instead of logical OR (||) for parsed.content
  in test-service.ts to preserve empty string as valid content
- Wrap parseResponse() in try-catch to prevent parse exceptions from
  masking HTTP status codes as network_error
- Add releaseSessionAgent() to early-return branches in response-handler.ts
  to prevent agent pool reference count leaks
- Fix error-rule-detector reload() to always queue a fresh pass when
  called during an active reload, preventing stale snapshot reads
- Remove pre-clearing of isInitialized in event handler to avoid
  retry storms when database is unavailable
- Use ICU {max, number} format in i18n messages for locale-aware
  number formatting of rule limits across all 5 languages
- Remove emoji from test comments and fix pool cleanup in finally block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: format code (dev-56698a2)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request Apr 22, 2026
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider bug Something isn't working size/S Small PR (< 200 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant