Skip to content

release v0.8.6#1273

Open
ding113 wants to merge 27 commits into
mainfrom
dev
Open

release v0.8.6#1273
ding113 wants to merge 27 commits into
mainfrom
dev

Conversation

@ding113

@ding113 ding113 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

Release v0.8.6 — bundles 7 merged PRs since v0.8.5 plus a post-review hardening round (8 direct commits on dev). Highlights: a new thinking-effort conflict rectifier for DeepSeek/MiMo compatibility, full self-service key management for non-admin users (create / edit / delete / toggle / renew), a terminal-marker billing gate for client-aborted streams, key error transparency fixes, provider dialog UX improvements, hedge-loser Codex priority billing fix, request-filter advanced mode regression fix, and three behavior-preserving maintainability refactors.

Included Changes

Features

Bug Fixes

Maintainability (behavior-preserving)

  • Reactive Rectifier Registry (ea1a3b2) — forwarder.ts's three copy-pasted rectifier branches folded into a typed descriptor table with one generic executor; detection precedence, audit payload shapes, hit-then-terminate and retry-once semantics are pinned by the existing suites.
  • Data-Driven Settings Degradation Ladder (632e590) — system-config.ts's hand-written 11-rung read / 10-rung update fallback chains are now generated from a single column table (net −306 lines); adding a future system_settings column touches exactly one entry. A 470-line lock-test pins the exact attempt sequences of the old implementation.
  • Stream Fixer & Billing Pipeline (ea1a3b2) — response-fixer's duplicated transform/flush blocks deduplicated into applyStreamFixers with the inert chat.completion.chunk filter as an integral step plus a byte-level marker pre-scan (non-matching chunks are returned by reference, no decode); billing parameters consolidated into a single BillingComputeInputs bag across updateRequestCostFromUsage/trackCostToRedis (hedge-loser snapshot semantics unchanged); the client-abort drain path no longer parses the same stream text twice.

Other

  • Usage Doc Entry (为登录后用户补充使用文档入口 [未完成] #1233) — Adds usage documentation entry point for logged-in users.
  • i18n Updates — New translation keys across all 5 languages (zh-CN, zh-TW, en, ja, ru) for thinking-effort rectifier settings, error codes, and key management UI.
  • New Test Coverage — 25+ new test files/cases: rectifier, self-service key creation and full write-path authorization matrices (20 cases across action and handler layers), key deletion/creation error codes, provider dialog behavior, hedge-loser billing, client-abort drain with terminal-marker regression fixtures, response-fixer SSE filtering with CJK byte-integrity and no-decode pins, and degradation-ladder lock tests. Two AddKeyForm test files whose mocks pointed at a stale module path (and never intercepted anything) were repaired.

Review Hardening

Two independent multi-agent deep reviews (25 finders + adversarial verification) were run against this PR; every confirmed finding was fixed in place with TDD and the full local matrix re-verified (build / typecheck / lint / full unit suite / test:v1 / OpenAPI check+lint / migrations / i18n audit — all green, coverage failures at baseline parity):

  • Round 1 — 04834e9 (terminal-marker billing gate), 7b6ae61 (key creation error codes), 7a03462 (request-filter effective target), c4e1ff4 (usage-doc read-only link)
  • Round 2 — 1addcc7, 632e590, ea1a3b2, 36b61fe (bot-review triage: 4 threads fixed, 6 refuted with code evidence; plus U02/U03 and 9 maintainability items from the deep-review backlog)

Database Migration

  • 0105_chief_rocket_racer.sql: Adds enable_thinking_effort_conflict_rectifier column (boolean, default true) to system_settings table.

Included PRs

Plus 8 direct commits on dev from the review hardening rounds: 04834e9, 7b6ae61, 7a03462, c4e1ff4, 1addcc7, 632e590, ea1a3b2, 36b61fe.


Description enhanced by Claude AI

Greptile Summary

This release bundles 7 merged PRs since v0.8.5, covering proxy reliability, key management access control, and UI fixes. The changes are well-structured and backed by 19+ new test files.

  • Proxy: adds a reactive rectifier for thinking-effort conflicts (DeepSeek/MiMo), a client-abort drain window that lets completed upstream streams be billed correctly after disconnect, a ResponseFixer filter for inert chat-completion chunks, and fixes hedge-loser Codex priority billing by snapshotting requestedServiceTier before winner sync.
  • Keys: adds POST /api/v1/users:self/keys self-service endpoint, relaxes several key routes from admin to read-tier auth with consistent requireKeyWriteSession / denyKeyWriteForReadOnlySession guards, and surfaces structured error codes for all key deletion failure paths.
  • Other: fixes request-filter advanced-mode regression on binding-only updates, prevents accidental provider dialog close on outside click, and adds usage-doc entry for logged-in users.

Confidence Score: 4/5

Safe to merge with one targeted fix: the client-abort completion detector needs anchored matching to avoid false positives on model text output.

The bulk of the changes — the rectifier registry, key access-control guards, billing snapshot, request-filter fix, and dialog UX — are clean and well-tested. The concern is in hasStreamCompletionMarker: the three plain includes() calls search allContent (the full raw SSE text) for "message_stop", "[DONE]", and "response.completed". Because allContent contains SSE data payloads verbatim, model text output that contains one of those literal strings would satisfy the check on a genuinely truncated stream, reclassifying it as a successful completion and keeping session binding.

src/app/v1/_lib/proxy/response-handler.ts — specifically the hasStreamCompletionMarker function and its three plain-string includes() checks.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/response-handler.ts Adds client-abort drain logic, hedge-loser priority billing fix, and BillingComputeInputs refactor; the hasStreamCompletionMarker plain-string checks can false-positive on model text output
src/app/v1/_lib/proxy/forwarder.ts Refactors reactive rectifiers into a typed descriptor registry; adds thinking-effort conflict rectifier and captures requestedServiceTier in hedge-loser billing snapshot before winner sync
src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts New rectifier: detects and strips conflicting output_config.effort / reasoning_effort fields when thinking is disabled; minimal-invasive and well-guarded
src/app/api/v1/resources/keys/handlers.ts Adds createSelfKey endpoint and requireKeyWriteSession guard; canLoginWebUi check is consistent across all write routes
src/app/api/v1/resources/keys/router.ts Relaxes several key routes from admin to read-tier auth; write endpoints separately enforce Web-UI session via requireKeyWriteSession; ownership enforced by actions
src/actions/keys.ts Adds denyKeyWriteForReadOnlySession guard to editKey/removeKey/toggleKeyEnabled/renewKeyExpiresAt; improves error codes throughout; all write paths guarded consistently
src/app/v1/_lib/proxy/response-fixer/index.ts Extracts applyStreamFixers helper to deduplicate transform/flush code; adds filterInertResponsesChatCompletionChunks with byte-prescan optimization
src/actions/request-filters.ts Fixes regression by forwarding effective ruleMode/operations/name/target to validator on binding-only updates of advanced-mode filters

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant PH as ProxyResponseHandler
    participant US as Upstream
    participant FIN as finalizeDeferredStreaming

    C->>PH: Request
    PH->>US: Forward
    US-->>PH: Stream chunks (tee'd)
    C->>PH: Client disconnects
    Note over PH: Start drain timer (clientAbortDrainTimeoutMs)
    Note over PH: Do NOT cancel internal read loop
    US-->>PH: Continue streaming chunks
    alt Stream completes before drain timeout
        US-->>PH: "done=true → streamEndedNormally=true"
        PH->>FIN: "finalize(streamEndedNormally=true)"
        FIN-->>PH: Normal 2xx success path
    else Drain timeout fires
        PH->>FIN: "finalize(streamEndedNormally=false, clientAborted=true)"
        FIN->>FIN: hasStreamCompletionMarker(allContent)?
        alt Marker found + positive tokens
            FIN-->>PH: "clientAbortCompleteSuccess=true → bill as success"
        else No marker
            FIN-->>PH: 499 CLIENT_ABORTED (unbilled)
        end
    end
Loading

Reviews (3): Last reviewed commit: "feat(keys): enable self-service key mana..." | Re-trigger Greptile

tesgth032 and others added 19 commits June 11, 2026 12:15
为登录后用户补充使用文档入口 [未完成]
The removeKey action previously returned hardcoded Chinese strings or
generic messages, causing the UI to display unhelpful toasts via the
REST bridge. It now returns specific error codes that the UI translates
through the errors namespace.

Additionally, the key creation dialogs incorrectly claimed the full key
is only shown once. The copy now accurately reflects that owners and
admins can reveal and copy the full key from the key list at any time.

Includes regression tests for the action error codes, API error mapping,
UI toast translation, and i18n copy guards.
Non-admin dashboard users previously could not create API keys because the
per-user key creation route was admin-only. This adds a new read-tier
POST /api/v1/users:self/keys endpoint that derives the target user from
the authenticated session, rejecting read-only sessions to prevent
privilege escalation.

The API client now falls back to this self-service endpoint when the
admin route returns 403 Forbidden. Additionally, raw REST error codes
are now mapped to translation keys via getApiErrorMessageKey so frontend
forms display localized messages instead of literal error code strings.
When Anthropic-compatible providers like DeepSeek or MiMo receive a request
with thinking disabled but reasoning_effort or output_config.effort set,
they return a 400 error. This rectifier detects the specific error message,
strips the conflicting effort fields while preserving the disabled thinking
state, and retries the request once against the same provider.

Adds a new system setting enableThinkingEffortConflictRectifier (enabled by
default) with full UI, API, and database migration support to control this
behavior.
Add missing enableThinkingEffortConflictRectifier field to
UpdateSystemSettingsInput to resolve a typecheck failure.

Strip only the effort key from output_config in the thinking effort
conflict rectifier instead of deleting the entire object, preserving
any sibling configuration fields.

Extend the system_settings database degradation fallback chain to
handle missing enableThinkingEffortConflictRectifier columns in
un-migrated databases for both read and update operations.
Deny-by-default canLoginWebUi check (only explicit true proceeds) to
prevent read-tier sessions from minting Web-UI-capable keys. Spread
session userId last in addKey payload to prevent the request body from
overriding the target user.

Add tests covering toVoidActionResult delete-code passthrough and
double-403 self-fallback permission denied mapping.
The previous assertion only checked the transformer default value for the new column, which did not guarantee the fallback query actually stripped the correct column. The test now inspects the second select call to ensure the newly added column is removed while older fallback columns remain, catching regressions in the degradation chain order.
A preceding test left a persistent implementation on postMock, which
interfered with the mockRejectedValueOnce calls in the double-403
self-fallback case. Adding mockReset ensures only the intended
rejections are in play.
…reated-copy

fix(keys): removeKey errorCode passthrough + key-creation dialog copy aligned with real reveal behavior
fix(keys): non-admin self-service key creation (#1259) + errorCode display mapping
…ectifier

feat(proxy): thinking effort conflict rectifier for DeepSeek/MiMo subagent thinking-disabled errors (#1257)
Provider create and edit dialogs contain long forms that were previously
dismissed by accidental outside clicks, window focus loss, or the Escape
key, leading to potential data loss.

Introduce explicitCloseOnlyDialogProps to neutralize implicit close
triggers and apply it across all provider and vendor key dialogs. The
dialogs now only close via the explicit close button, cancel, or
successful submission.

Includes unit tests for the new dialog utility.
The previous explicitCloseOnlyDialogProps helper blocked the Escape key
along with outside clicks and window blur, which was too restrictive
for users trying to dismiss long forms.

Rename the helper to preventCloseOnOutsideInteraction and drop the
onEscapeKeyDown interceptor. This keeps the protection against
accidental click-aways and focus loss while letting Escape close the
dialog as expected.
The file dialog-explicit-close.test.ts is renamed to dialog-prevent-close-on-outside-interaction.test.ts to align with the preventCloseOnOutsideInteraction helper it actually covers.
Consolidate the preventCloseOnOutsideInteraction guard and standard
layout classes into a shared ProviderFormDialogContent component. This
ensures all provider create, edit, and clone dialogs consistently
prevent accidental dismissal via outside clicks or window blur without
requiring manual prop spreading.

Remove the standalone unit test for the interaction guard since the
behavior is now encapsulated within the component.
…-blur

fix(providers): provider create/edit dialogs close only on explicit action, not on outside click or window blur
…1251)

* fix(proxy): finalize complete responses after client abort

* fix(proxy): sanitize inert chat chunks in responses streams
* fix(proxy): preserve hedge loser Codex priority billing

* fix(proxy): track hedge loser Redis cost with billing snapshot
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

新增 thinking-effort 冲突整流器并将其作为系统设置(默认开启)贯通至 proxy forwarder;收紧 key 写入权限、增加自助建 key API 与错误码映射;添加前端使用文档入口与对话框防外部关闭工具;并补充大量单元/集成测试与数据库迁移。

Changes

主要变更(汇总)

Layer / File(s) Summary
数据库迁移与 schema/类型/缓存
drizzle/0105_chief_rocket_racer.sql, drizzle/meta/_journal.json, src/drizzle/schema.ts, src/types/system-config.ts, src/lib/api/v1/schemas/system-config.ts, src/lib/config/system-settings-cache.ts, src/lib/validation/schemas.ts, src/repository/_shared/transformers.ts
为 systemSettings 新增 enableThinkingEffortConflictRectifier 字段(默认 true),并在 schema、类型、API schema、缓存与 transformer 中同步。
Thinking-effort 冲突整流器实现与测试
src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts, src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts, tests/unit/proxy/proxy-forwarder-thinking-effort-conflict-rectifier.test.ts, tests/configs/thinking-effort-conflict-rectifier.config.ts
新增 detect/rectify 函数,能识别上游错误并在满足条件时移除 effort/reasoning_effort 并触发重试;含单元与集成测试。
Forwarder 集成与 special setting
src/app/v1/_lib/proxy/forwarder.ts, src/types/special-settings.ts, src/lib/utils/special-settings.ts
在 reactive rectifier 注册表中加入该整流器,记录 special setting(审计)并维护重试状态。
响应修复与惰性 SSE 过滤
src/app/v1/_lib/proxy/response-fixer/index.ts, src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
新增字节级预扫描与过滤逻辑,剔除 response-format 下惰性 chat.completion.chunk,更新流处理链并补充测试。
response-handler:客户端 abort/drain 与计费重构
src/app/v1/_lib/proxy/response-handler.ts, 多个 tests
引入 clientAbort drain 窗口、clientAbortGateUsage 重用、BillingComputeInputs 统一计费五元组,并修正 hedge/loser 的 requestedServiceTier 传递,配套大量测试。
Keys 权限、错误码与 API
src/actions/keys.ts, src/lib/utils/error-messages.ts, src/lib/api-client/v1/actions/keys.ts, src/app/api/v1/resources/keys/handlers.ts, src/app/api/v1/resources/keys/router.ts, src/lib/api-client/v1/openapi-types.gen.ts
添加会话级写入守卫(只允许 canLoginWebUi 的会话写操作)、新增错误码 CANNOT_DELETE_LAST_KEY/CANNOT_DELETE_LAST_GROUP_KEY、支持 addOwnKey 自助端点与 createSelfKey handler,并调整若干路由鉴权为 read 级别。
前端:错误展示、文档入口与只读访问
src/app/[locale]/dashboard/_components/*, src/app/[locale]/my-usage/*, messages/*/dashboard.json, messages/*/errors.json, messages/*/myUsage.json
前端删除失败用 errorCode 映射为可翻译文案(getErrorMessage)、新增 /usage-doc 文档入口(UserMenu、MyUsageHeader、DashboardHeader 的显示门控依据 canUseDashboard),并更新多语言文案。
Provider 对话框 UX 与工具
src/lib/utils/dialog.ts, src/app/[locale]/settings/providers/_components/provider-form-dialog-content.tsx, 多处替换
新增 preventCloseOnOutsideInteraction,封装 ProviderFormDialogContent 并在多个 provider 弹窗替换 DialogContent,配套测试。
请求过滤与其它修正
src/actions/request-filters.ts, src/lib/api-client/v1/actions/_compat.ts, src/lib/api-client/v1/errors.ts
修正 updateRequestFilterAction 的 validatePayload 构造以使用更新后的有效值;toActionResult/toVoidActionResult 使用 getApiErrorMessageKey 映射 errorCode;新增 isAdminForbidden 辅助。
广泛测试补充
tests/...(见变更清单)
新增/扩展覆盖整流器、forwarder、response-fixer、response-handler、keys 行为、API handlers、前端组件与 i18n 的单元与集成测试。
  • Estimated code review effort: 🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

✨ 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 dev

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new 'Thinking Effort Conflict Rectifier' to handle payload conflicts on Anthropic-compatible providers (such as DeepSeek), adds a self-service API key creation endpoint for non-admin users, and improves error handling and translation mapping for key deletions. It also updates the ResponseFixer to filter out inert chat completion chunks and introduces a dialog guard to prevent accidental dismissals of long forms. Feedback on these changes highlights a potential runtime crash in the dashboard header due to unsafe access to session.key, as well as a risk of UTF-8 character corruption in the streaming response fixer when decoding chunks without a stateful, stream-aware TextDecoder.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

export async function DashboardHeader({ session, locale }: DashboardHeaderProps) {
const t = await getTranslations({ locale, namespace: "dashboard.nav" });
const isAdmin = session?.user.role === "admin";
const canUseDashboard = !!session && (isAdmin || session.key.canLoginWebUi);

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.

high

Potential runtime crash: session.key can be undefined or null (for example, in sessions authenticated via password or OAuth rather than an API key). Accessing session.key.canLoginWebUi directly will throw a TypeError.

Use optional chaining (session.key?.canLoginWebUi) to safely handle cases where the session does not have an associated key.

Suggested change
const canUseDashboard = !!session && (isAdmin || session.key.canLoginWebUi);
const canUseDashboard = !!session && (isAdmin || session.key?.canLoginWebUi);
References
  1. When suggesting optional chaining (?.), verify that a guard clause checking for the object's nullability doesn't already exist earlier in the function.

Comment on lines +589 to +591
if (session.originalFormat !== "response") {
return { data, applied: 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.

medium

Decoding and re-encoding arbitrary stream chunks using a global TextDecoder without { stream: true } can lead to UTF-8 multi-byte character corruption if a character is split across chunk boundaries.

Since filterInertResponsesChatCompletionChunks is called on raw stream chunks, any split multi-byte character at the boundary of a matching chunk will be decoded as a replacement character (``) and permanently corrupted upon re-encoding. Consider using a per-session stateful TextDecoder with `{ stream: true }` to safely handle split UTF-8 characters.

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

Copy link
Copy Markdown

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: 553a310568

ℹ️ 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".

Comment on lines +589 to +590
const { usageMetrics } = parseUsageFromResponseText(allContent, provider?.providerType);
return hasPositiveBillableTokens(usageMetrics);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don’t treat partial client-aborted streams as success

When a client disconnects from an Anthropic/Claude SSE stream after message_start but before the terminal message_delta/completion, parseUsageFromResponseText() can still return positive input tokens from the early message_start event. This new check therefore classifies that truncated client-aborted stream as a completed 2xx success, which updates the request as completed and records provider/session success instead of a 499/client abort. Please require evidence of a terminal/completed stream event rather than any positive usage before taking the success path.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@github-actions github-actions Bot added the size/XL Extra Large PR (> 1000 lines) label Jun 11, 2026

@github-actions github-actions 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.

Code Review Summary

This is a well-structured release PR (v0.8.6) bundling 8 individually reviewed PRs. The proxy-layer changes (thinking-effort rectifier, client-abort drain, hedge-loser billing, response-fixer SSE filtering) follow established patterns from prior rectifiers and are thoroughly tested. The self-service key-creation endpoint has proper authorization fencing (session-derived userId, canLoginWebUi gate, Zod strict schema rejecting extra fields). Error transparency fixes and provider dialog UX are clean, minimal, and i18n-complete across all 5 locales. No significant issues identified.

PR Size: XL

  • Lines changed: 8,524 (8,410 additions + 114 deletions)
  • Files changed: 84

Suggestion for future releases: Consider splitting proxy-layer (rectifier + billing + stream) and UI-layer (dialogs + i18n + key management) into separate release PRs to reduce review surface. The 19+ test files and auto-generated migration snapshot inflate the file count significantly.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean (self-service endpoint properly fenced)
  • Error handling - Clean (all error paths logged, no silent catches)
  • Type safety - Clean (follows existing as never[] action bridge pattern)
  • Documentation accuracy - Clean (rectifier JSDoc matches behavior)
  • Test coverage - Comprehensive (19+ test files, edge cases covered)
  • Code clarity - Clean (new ProviderFormDialogContent and preventCloseOnOutsideInteraction reduce duplication)

Automated review by Claude AI

Comment on lines +18 to +24
export type ThinkingEffortConflictRectifierResult = {
applied: boolean;
removedOutputConfig: boolean;
removedReasoningEffort: boolean;
thinkingType: string | null;
effort: string | null;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The audit field removedOutputConfig is set to true even when only the effort key is stripped from output_config (and the object itself survives). An admin reading the audit record who interprets this as "the entire output_config block was removed" will draw the wrong conclusion in the partial-strip case. A name like removedEffortFromOutputConfig (or a dedicated removedOutputConfigEffort boolean) would remove the ambiguity.

Suggested change
export type ThinkingEffortConflictRectifierResult = {
applied: boolean;
removedOutputConfig: boolean;
removedReasoningEffort: boolean;
thinkingType: string | null;
effort: string | null;
};
export type ThinkingEffortConflictRectifierResult = {
applied: boolean;
/** True when the `effort` field inside `output_config` was stripped (the key itself may still exist if it had other fields). */
removedOutputConfigEffort: boolean;
removedReasoningEffort: boolean;
thinkingType: string | null;
effort: string | null;
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts
Line: 18-24

Comment:
The audit field `removedOutputConfig` is set to `true` even when only the `effort` key is stripped from `output_config` (and the object itself survives). An admin reading the audit record who interprets this as "the entire `output_config` block was removed" will draw the wrong conclusion in the partial-strip case. A name like `removedEffortFromOutputConfig` (or a dedicated `removedOutputConfigEffort` boolean) would remove the ambiguity.

```suggestion
export type ThinkingEffortConflictRectifierResult = {
  applied: boolean;
  /** True when the `effort` field inside `output_config` was stripped (the key itself may still exist if it had other fields). */
  removedOutputConfigEffort: boolean;
  removedReasoningEffort: boolean;
  thinkingType: string | null;
  effort: string | null;
};
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 2278 to +2310
// 使用 AsyncTaskManager 管理后台处理任务
const taskId = `stream-${messageContext?.id || `unknown-${Date.now()}`}`;
const abortController = new AbortController();
const idleTimeoutMs =
provider.streamingIdleTimeoutMs > 0 ? provider.streamingIdleTimeoutMs : Infinity;
const clientAbortDrainTimeoutMs = idleTimeoutMs === Infinity ? 60_000 : idleTimeoutMs;

// ⭐ 提升 idleTimeoutId 到外部作用域,以便客户端断开时能清除
let idleTimeoutId: NodeJS.Timeout | null = null;
let clientAbortDrainTimeoutId: NodeJS.Timeout | null = null;
const clearClientAbortDrainTimer = () => {
if (clientAbortDrainTimeoutId) {
clearTimeout(clientAbortDrainTimeoutId);
clientAbortDrainTimeoutId = null;
}
};
const cleanupClientAbortListener = bindClientAbortListener(session.clientAbortSignal, () => {
logger.debug("ResponseHandler: Client disconnected, cleaning up", {
taskId,
providerId: provider.id,
messageId: messageContext.id,
});

// 客户端断开时清除 idle timeout,避免任务已取消后仍误触发。
if (idleTimeoutId) {
clearTimeout(idleTimeoutId);
idleTimeoutId = null;
logger.debug("ResponseHandler: Idle timeout cleared due to client disconnect", {
// Do not cancel internal accounting on pure client disconnect. If the
// upstream stream has already completed, the tee'd internal branch can
// still drain buffered final usage and record the request as successful.
// Idle/response timeout paths still abort via abortController.
clearClientAbortDrainTimer();
clientAbortDrainTimeoutId = setTimeout(() => {
logger.info("ResponseHandler: Client abort drain window exceeded", {
taskId,
providerId: provider.id,
messageId: messageContext.id,
clientAbortDrainTimeoutMs,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Drain timeout falls back to 60 s when idle timeout is disabled

When provider.streamingIdleTimeoutMs is 0 (disabled), idleTimeoutMs is set to Infinity, and clientAbortDrainTimeoutMs consequently falls back to 60_000 ms. This means a provider configured with no idle timeout (intentionally unlimited streaming duration) will now silently cap the post-disconnect drain window at 60 seconds. A very long or slow upstream response that hasn't completed by the time the client disconnects will be abandoned after 60 s, and the request billed as failed rather than success.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/response-handler.ts
Line: 2278-2310

Comment:
**Drain timeout falls back to 60 s when idle timeout is disabled**

When `provider.streamingIdleTimeoutMs` is 0 (disabled), `idleTimeoutMs` is set to `Infinity`, and `clientAbortDrainTimeoutMs` consequently falls back to `60_000` ms. This means a provider configured with no idle timeout (intentionally unlimited streaming duration) will now silently cap the post-disconnect drain window at 60 seconds. A very long or slow upstream response that hasn't completed by the time the client disconnects will be abandoned after 60 s, and the request billed as failed rather than success.

How can I resolve this? If you propose a fix, please make it concise.

return { data, applied: false };
}

const lines = text.split("\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Splitting on " " leaves a trailing on each line when the upstream emits line endings (valid per the SSE spec). isInertChatCompletionDataLine would then fail the payloadText.startsWith("{") check on "{ ...", so inert chunks would not be filtered. isBlankSseSeparatorLine already handles " " explicitly, which hints at this risk.

Suggested change
const lines = text.split("\n");
const lines = text.split(/\r?\n/);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/response-fixer/index.ts
Line: 598

Comment:
Splitting on `"
"` leaves a trailing `
` on each line when the upstream emits `
` line endings (valid per the SSE spec). `isInertChatCompletionDataLine` would then fail the `payloadText.startsWith("{")` check on `"{
..."`, so inert chunks would not be filtered. `isBlankSseSeparatorLine` already handles `"
"` explicitly, which hints at this risk.

```suggestion
    const lines = text.split(/\r?\n/);
```

How can I resolve this? If you propose a fix, please make it concise.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/actions/request-filters.ts (1)

357-381: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

校验触发条件仍有漏网路径,能写入“simple + 空 target”的无效配置

Line 357 当前只在绑定字段变更时才走 validatePayload。如果仅把 ruleModeadvanced 改为 simple(且历史记录 target=""),更新会绕过 simple 模式的 target 必填约束并成功落库。建议把 validatePayload 的触发条件扩展到所有影响有效配置的字段,并统一使用“更新后 effective 值”组装 payload。

建议修复(示例)
-  if (
-    updates.bindingType !== undefined ||
-    updates.providerIds !== undefined ||
-    updates.groupTags !== undefined
-  ) {
+  if (
+    updates.ruleMode !== undefined ||
+    updates.operations !== undefined ||
+    updates.target !== undefined ||
+    updates.action !== undefined ||
+    updates.matchType !== undefined ||
+    updates.bindingType !== undefined ||
+    updates.providerIds !== undefined ||
+    updates.groupTags !== undefined
+  ) {
     const effectiveBindingType = updates.bindingType ?? existing!.bindingType;
     const effectiveProviderIds =
       updates.providerIds !== undefined ? updates.providerIds : existing!.providerIds;
     const effectiveGroupTags =
       updates.groupTags !== undefined ? updates.groupTags : existing!.groupTags;
     const effectiveRuleMode = updates.ruleMode ?? existing!.ruleMode;
     const effectiveOperations =
       updates.operations !== undefined ? updates.operations : existing!.operations;
+    const effectiveTarget = updates.target ?? existing!.target;
+    const effectiveAction = updates.action ?? existing!.action;
+    const effectiveMatchType = updates.matchType ?? existing!.matchType;

     const validationError = validatePayload({
       name: existing!.name,
       scope: existing!.scope,
-      action: existing!.action,
-      target: existing!.target,
+      action: effectiveAction,
+      target: effectiveTarget,
+      matchType: effectiveMatchType ?? undefined,
       bindingType: effectiveBindingType,
       providerIds: effectiveProviderIds,
       groupTags: effectiveGroupTags,
       ruleMode: effectiveRuleMode,
       operations: effectiveOperations,
     });
🤖 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 `@src/actions/request-filters.ts` around lines 357 - 381, The current
conditional only runs validatePayload when binding-related fields change,
letting a change like ruleMode from "advanced" to "simple" bypass
target-required checks; update the if condition to include all fields that
affect validity (at least ruleMode, target, name, scope, action, operations,
providerIds, groupTags, bindingType) so any of those in updates triggers
validation, and keep using the assembled effective* values
(effectiveBindingType, effectiveProviderIds, effectiveGroupTags,
effectiveRuleMode, effectiveOperations, plus
effectiveTarget/effectiveName/effectiveScope/effectiveAction derived from
existing/updates) when calling validatePayload with
existing!.name/scope/action/target replaced by those effective values.
🤖 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 `@messages/zh-TW/myUsage.json`:
- Line 5: The translation for the JSON key "documentation" is semantically off;
update the value for "documentation" in messages/zh-TW/myUsage.json from "使用文件"
to a clearer phrase such as "使用說明" (or "使用文件說明") so it reads as a
documentation/usage entry rather than a file; locate the "documentation" key in
that file and replace the value accordingly.

In `@src/app/`[locale]/dashboard/_components/dashboard-header.test.tsx:
- Line 1: The test file dashboard-header.test.tsx is placed under src/... which
violates the repo test-location conventions; move or rename it so Vitest picks
it up correctly: either relocate the file into tests/unit/ (or
tests/integration/ as appropriate) or convert it to a source-adjacent test by
renaming/moving it to match src/**/*.test.ts (keeping the same test content and
imports for the DashboardHeader component), and update any import paths if they
change.

In `@src/app/`[locale]/settings/providers/_components/add-provider-dialog.tsx:
- Line 10: Replace the relative import for the local component with the project
path-alias: change the import of ProviderFormDialogContent (currently
"./provider-form-dialog-content") to use
"`@/app/`[locale]/settings/providers/_components/provider-form-dialog-content" so
it follows the codebase rule of using `@/` to map to ./src/, ensuring consistency
across imports and preventing future path-drift during refactors.

In `@src/lib/api-client/v1/actions/keys.ts`:
- Around line 17-29: The addKey function currently falls back to creating a key
for the session user on any admin-forbidden error, which can silently ignore the
caller's userId; change addKey to accept an explicit opt-in flag (e.g.,
allowSelfFallback: boolean, default false) and only attempt the
apiPost("/api/v1/users:self/keys", ...) retry when allowSelfFallback is true and
isAdminForbidden(error) is true; update the call sites that intend self-service
to pass allowSelfFallback=true and keep all other usages unchanged; reference
addKey, isAdminForbidden, and the two apiPost routes when making the change.

---

Outside diff comments:
In `@src/actions/request-filters.ts`:
- Around line 357-381: The current conditional only runs validatePayload when
binding-related fields change, letting a change like ruleMode from "advanced" to
"simple" bypass target-required checks; update the if condition to include all
fields that affect validity (at least ruleMode, target, name, scope, action,
operations, providerIds, groupTags, bindingType) so any of those in updates
triggers validation, and keep using the assembled effective* values
(effectiveBindingType, effectiveProviderIds, effectiveGroupTags,
effectiveRuleMode, effectiveOperations, plus
effectiveTarget/effectiveName/effectiveScope/effectiveAction derived from
existing/updates) when calling validatePayload with
existing!.name/scope/action/target replaced by those effective values.
🪄 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: CHILL

Plan: Pro

Run ID: 1876670c-428a-4e4a-968f-e99cb4b956ae

📥 Commits

Reviewing files that changed from the base of the PR and between 52a3f3b and 553a310.

📒 Files selected for processing (84)
  • drizzle/0105_chief_rocket_racer.sql
  • drizzle/meta/0105_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • messages/en/errors.json
  • messages/en/myUsage.json
  • messages/en/settings/config.json
  • messages/ja/dashboard.json
  • messages/ja/errors.json
  • messages/ja/myUsage.json
  • messages/ja/settings/config.json
  • messages/ru/dashboard.json
  • messages/ru/errors.json
  • messages/ru/myUsage.json
  • messages/ru/settings/config.json
  • messages/zh-CN/dashboard.json
  • messages/zh-CN/errors.json
  • messages/zh-CN/myUsage.json
  • messages/zh-CN/settings/config.json
  • messages/zh-TW/dashboard.json
  • messages/zh-TW/errors.json
  • messages/zh-TW/myUsage.json
  • messages/zh-TW/settings/config.json
  • package.json
  • src/actions/keys.ts
  • src/actions/request-filters.ts
  • src/actions/system-config.ts
  • src/app/[locale]/dashboard/_components/dashboard-header.test.tsx
  • src/app/[locale]/dashboard/_components/dashboard-header.tsx
  • src/app/[locale]/dashboard/_components/user-menu.test.tsx
  • src/app/[locale]/dashboard/_components/user-menu.tsx
  • src/app/[locale]/dashboard/_components/user/forms/delete-key-confirm.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/my-usage/_components/my-usage-header.test.tsx
  • src/app/[locale]/my-usage/_components/my-usage-header.tsx
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/[locale]/settings/providers/_components/add-provider-dialog.tsx
  • src/app/[locale]/settings/providers/_components/provider-form-dialog-content.tsx
  • src/app/[locale]/settings/providers/_components/provider-manager.tsx
  • src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx
  • src/app/[locale]/settings/providers/_components/vendor-keys-compact-list.tsx
  • src/app/[locale]/usage-doc/layout.tsx
  • src/app/api/v1/resources/keys/handlers.ts
  • src/app/api/v1/resources/keys/router.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts
  • src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts
  • src/drizzle/schema.ts
  • src/lib/api-client/v1/actions/_compat.ts
  • src/lib/api-client/v1/actions/keys.ts
  • src/lib/api-client/v1/actions/users.ts
  • src/lib/api-client/v1/errors.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/api/v1/schemas/system-config.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/utils/dialog.ts
  • src/lib/utils/error-messages.ts
  • src/lib/utils/special-settings.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.ts
  • src/repository/system-config.ts
  • src/types/special-settings.ts
  • src/types/system-config.ts
  • tests/api/v1/keys/keys.self.test.ts
  • tests/configs/thinking-effort-conflict-rectifier.config.ts
  • tests/unit/actions/keys-remove-error-codes.test.ts
  • tests/unit/actions/request-filters-cache-reload.test.ts
  • tests/unit/actions/system-config-thinking-effort-conflict-setting.test.ts
  • tests/unit/api/v1/api-client-actions.test.ts
  • tests/unit/api/v1/keys-delete-error-mapping.test.ts
  • tests/unit/delete-key-error-toast-ui.test.tsx
  • tests/unit/dialog-prevent-close-on-outside-interaction.test.ts
  • tests/unit/frontend/api-error-i18n.test.ts
  • tests/unit/i18n/key-created-copy.test.ts
  • tests/unit/i18n/my-usage-keys.test.ts
  • tests/unit/proxy/proxy-forwarder-thinking-effort-conflict-rectifier.test.ts
  • tests/unit/proxy/response-handler-client-abort-drain.test.ts
  • tests/unit/proxy/response-handler-hedge-loser-priority.test.ts
  • tests/unit/repository/system-config-update-missing-columns.test.ts
  • tests/unit/usage-doc/usage-doc-auth-state.test.tsx
👮 Files not reviewed due to content moderation or server errors (5)
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • tests/unit/proxy/response-handler-client-abort-drain.test.ts
  • tests/unit/proxy/response-handler-hedge-loser-priority.test.ts

Comment thread messages/zh-TW/myUsage.json Outdated
@@ -0,0 +1,111 @@
import type { ReactNode } from "react";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

测试文件路径/命名不符合仓库约定,建议迁移到 tests/unit/ 或调整为 src/**/*.test.ts

Line 1 新增文件位于 src/.../dashboard-header.test.tsx,与当前约定不一致,可能导致测试组织和规则检查出现偏差。

As per coding guidelines: {tests/**/*.test.{ts,tsx},src/**/*.test.ts} 要求 Vitest 测试放在 tests/unit/tests/integration/,或 source-adjacent 的 src/**/*.test.ts

🤖 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 `@src/app/`[locale]/dashboard/_components/dashboard-header.test.tsx at line 1,
The test file dashboard-header.test.tsx is placed under src/... which violates
the repo test-location conventions; move or rename it so Vitest picks it up
correctly: either relocate the file into tests/unit/ (or tests/integration/ as
appropriate) or convert it to a source-adjacent test by renaming/moving it to
match src/**/*.test.ts (keeping the same test content and imports for the
DashboardHeader component), and update any import paths if they change.

Source: Coding guidelines

import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Dialog, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { ProviderForm } from "./forms/provider-form";
import { ProviderFormDialogContent } from "./provider-form-dialog-content";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

跨文件导入规范不一致:新增本地组件导入都使用了相对路径。

四处新增导入都使用了 ./provider-form-dialog-content,建议统一替换为 @/app/[locale]/settings/providers/_components/provider-form-dialog-content,避免后续路径迁移和重构时出现风格漂移。
As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use path alias @/ to map to ./src/ for imports.

🤖 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 `@src/app/`[locale]/settings/providers/_components/add-provider-dialog.tsx at
line 10, Replace the relative import for the local component with the project
path-alias: change the import of ProviderFormDialogContent (currently
"./provider-form-dialog-content") to use
"`@/app/`[locale]/settings/providers/_components/provider-form-dialog-content" so
it follows the codebase rule of using `@/` to map to ./src/, ensuring consistency
across imports and preventing future path-drift during refactors.

Source: Coding guidelines

Comment thread src/lib/api-client/v1/actions/keys.ts Outdated
ding113 added 4 commits June 12, 2026 07:16
… success

Prior behaviour reclassified truncated streams as 200 success if they carried
positive usage tokens, but providers like Anthropic emit usage in the first
event. Now checks for a format-appropriate completion marker before billing.
…ilures

Surfaces specific error codes and parameters when self-service key creation
fails due to duplicate names or limit violations, replacing generic failures.
…ates

Resolves a regression where converting an advanced filter to simple mode with
a new target was rejected because validation checked the stale empty target.
Read-only sessions can view docs but not the dashboard, so the back link
dead-ended at login. Gates the dashboard access predicate on canLoginWebUi.
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/actions/request-filters.ts (1)

374-374: 💤 Low value

建议统一有效值计算的代码风格。

第374行使用 ?? 运算符计算 effectiveName,而第375行对 effectiveTarget 使用 !== undefined 检查。两者在功能上等价(因为 nametarget 都是非空字段,Partial<{name: string}> 只会引入 undefined 而不会引入 null),但风格不一致。

为了与第375行以及同一代码块中第368-369行(effectiveOperations)保持一致,建议将第374行也改为显式的 !== undefined 检查。

♻️ 建议的风格统一
-    const effectiveName = updates.name ?? existing!.name;
+    const effectiveName = updates.name !== undefined ? updates.name : existing!.name;
     const effectiveTarget = updates.target !== undefined ? updates.target : existing!.target;
🤖 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 `@src/actions/request-filters.ts` at line 374, Change the effectiveName
assignment to use an explicit !== undefined check like the other computed
values: when computing effectiveName (currently using updates.name ??
existing!.name) replace the nullish-coalescing with a check for updates.name !==
undefined and fall back to existing!.name so it matches the style used for
effectiveTarget and effectiveOperations in this block.
src/actions/keys.ts (1)

213-323: ⚡ Quick win

统一 errorCode 的使用方式。

Line 194 使用了 ERROR_CODES.DUPLICATE_NAME 常量,但 Lines 213, 234, 255, 276, 297, 318 直接使用字符串字面量(如 "KEY_LIMIT_5H_EXCEEDS_USER_LIMIT")。为保持代码一致性与可维护性,建议将所有 errorCode 统一为 ERROR_CODES 常量引用,或在 @/lib/utils/error-messages 中补充缺失的常量定义。

♻️ 建议统一使用 ERROR_CODES 常量

@/lib/utils/error-messages 中补充定义(如果尚未存在):

 export const ERROR_CODES = {
   UNAUTHORIZED: "UNAUTHORIZED",
   PERMISSION_DENIED: "PERMISSION_DENIED",
   DUPLICATE_NAME: "DUPLICATE_NAME",
+  KEY_LIMIT_5H_EXCEEDS_USER_LIMIT: "KEY_LIMIT_5H_EXCEEDS_USER_LIMIT",
+  KEY_LIMIT_DAILY_EXCEEDS_USER_LIMIT: "KEY_LIMIT_DAILY_EXCEEDS_USER_LIMIT",
+  KEY_LIMIT_WEEKLY_EXCEEDS_USER_LIMIT: "KEY_LIMIT_WEEKLY_EXCEEDS_USER_LIMIT",
+  KEY_LIMIT_MONTHLY_EXCEEDS_USER_LIMIT: "KEY_LIMIT_MONTHLY_EXCEEDS_USER_LIMIT",
+  KEY_LIMIT_TOTAL_EXCEEDS_USER_LIMIT: "KEY_LIMIT_TOTAL_EXCEEDS_USER_LIMIT",
+  KEY_LIMIT_CONCURRENT_EXCEEDS_USER_LIMIT: "KEY_LIMIT_CONCURRENT_EXCEEDS_USER_LIMIT",
   // ...
 } as const;

然后在本文件中统一使用常量:

-        errorCode: "KEY_LIMIT_5H_EXCEEDS_USER_LIMIT",
+        errorCode: ERROR_CODES.KEY_LIMIT_5H_EXCEEDS_USER_LIMIT,

对其余 Lines 234, 255, 276, 297, 318 进行相同替换。

🤖 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 `@src/actions/keys.ts` around lines 213 - 323, The errorCode strings in the
validation branch returns (e.g., "KEY_LIMIT_5H_EXCEEDS_USER_LIMIT",
"KEY_LIMIT_DAILY_EXCEEDS_USER_LIMIT", etc.) should be replaced with the
corresponding ERROR_CODES constants to match the earlier usage of
ERROR_CODES.DUPLICATE_NAME; update each return block that sets errorCode and
errorParams to reference the constant from ERROR_CODES (ensure the constants
exist in the error-messages module and add them there if missing), keeping the
tError calls unchanged (symbols to locate: ERROR_CODES, tError,
validatedData.limit5hUsd/limitDailyUsd/limitWeeklyUsd/limitMonthlyUsd/limitTotalUsd/limitConcurrentSessions
and
user.limit5hUsd/user.dailyQuota/user.limitWeeklyUsd/user.limitMonthlyUsd/user.limitTotalUsd/user.limitConcurrentSessions).
🤖 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.

Nitpick comments:
In `@src/actions/keys.ts`:
- Around line 213-323: The errorCode strings in the validation branch returns
(e.g., "KEY_LIMIT_5H_EXCEEDS_USER_LIMIT", "KEY_LIMIT_DAILY_EXCEEDS_USER_LIMIT",
etc.) should be replaced with the corresponding ERROR_CODES constants to match
the earlier usage of ERROR_CODES.DUPLICATE_NAME; update each return block that
sets errorCode and errorParams to reference the constant from ERROR_CODES
(ensure the constants exist in the error-messages module and add them there if
missing), keeping the tError calls unchanged (symbols to locate: ERROR_CODES,
tError,
validatedData.limit5hUsd/limitDailyUsd/limitWeeklyUsd/limitMonthlyUsd/limitTotalUsd/limitConcurrentSessions
and
user.limit5hUsd/user.dailyQuota/user.limitWeeklyUsd/user.limitMonthlyUsd/user.limitTotalUsd/user.limitConcurrentSessions).

In `@src/actions/request-filters.ts`:
- Line 374: Change the effectiveName assignment to use an explicit !== undefined
check like the other computed values: when computing effectiveName (currently
using updates.name ?? existing!.name) replace the nullish-coalescing with a
check for updates.name !== undefined and fall back to existing!.name so it
matches the style used for effectiveTarget and effectiveOperations in this
block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cb11be88-959a-4b20-90b8-ff523a80a0a3

📥 Commits

Reviewing files that changed from the base of the PR and between 553a310 and c4e1ff4.

📒 Files selected for processing (8)
  • src/actions/keys.ts
  • src/actions/request-filters.ts
  • src/app/[locale]/usage-doc/layout.tsx
  • src/app/v1/_lib/proxy/response-handler.ts
  • tests/unit/actions/add-key-error-codes.test.ts
  • tests/unit/actions/request-filters-cache-reload.test.ts
  • tests/unit/proxy/response-handler-client-abort-drain.test.ts
  • tests/unit/usage-doc/usage-doc-auth-state.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/usage-doc/usage-doc-auth-state.test.tsx
  • src/app/v1/_lib/proxy/response-handler.ts

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx (1)

114-118: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

这里把回归条件放宽过头了,测试抓不住原问题。

这条用例的目标是确保“清除到期时间时显式传空字符串”,但断言同时接受 undefined。如果后面又退回成 undefined 被序列化丢弃,这个测试仍然会绿,等于把这次修复的核心回归点放掉了。

建议修改
       expect(Object.hasOwn(payload, "expiresAt")).toBe(true);
-      // 空字符串或 undefined 都是有效的清除值,但根据修复,应该是空字符串
-      expect(payload.expiresAt === "" || payload.expiresAt === undefined).toBe(true);
+      expect(payload.expiresAt).toBe("");
🤖 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 `@tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx` around lines 114
- 118, The test currently allows expiresAt to be either "" or undefined which
weakens the regression check; update the assertion on the payload to require an
explicit empty string for payload.expiresAt (not undefined) so the test enforces
the "clear by sending empty string" behavior — keep the existing
Object.hasOwn(payload, "expiresAt") check and replace the loose truthy check
with an assertion that payload.expiresAt equals the empty string ("") to catch
regressions where the field would be omitted/serialized away.
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)

73-76: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

自助建 Key 路径仍被 userId 硬性拦住了。

非管理员现在走的是会话定向的 addOwnKey(body),但这里仍然在提交前 throw userIdMissing,并且把提交按钮绑在 !!userId 上。这样一来,只要调用方没有传 userId,自助建 Key 就会被前端直接拦死,和这次新增的 self endpoint 契约相冲突。

建议修改
     onSubmit: async (data) => {
-      if (!userId) {
+      if (isAdmin && !userId) {
         throw new Error(t("errors.userIdMissing"));
       }
@@
-        const result = isAdmin ? await addKey({ userId: userId!, ...body }) : await addOwnKey(body);
+        const result = isAdmin ? await addKey({ userId: userId!, ...body }) : await addOwnKey(body);
@@
-      canSubmit={form.canSubmit && !!userId}
+      canSubmit={form.canSubmit && (!isAdmin || !!userId)}

Also applies to: 98-98, 153-153

🤖 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 `@src/app/`[locale]/dashboard/_components/user/forms/add-key-form.tsx around
lines 73 - 76, The onSubmit handler in add-key-form.tsx currently throws when
userId is missing and the submit button is gated by !!userId; change this to
support the new self endpoint by removing the throw and switching the API call
based on presence of userId: inside onSubmit decide between calling
addOwnKey(body) when userId is falsy and the existing addKey/addUserKey call
when userId is present, and update the submit button so it is not disabled
solely by !!userId (keep other validations intact).
🧹 Nitpick comments (1)
tests/unit/api/v1/keys-write-handlers-authz.test.ts (1)

68-131: 💤 Low value

建议补充 createSelfKey handler 的鉴权测试

当前测试覆盖了 deleteKeyupdateKeyenableKeyrenewKey 的会话守卫,但未覆盖同样有内联鉴权逻辑的 createSelfKey。建议补充以下场景:

  • 只读会话(canLoginWebUi=false)返回 403
  • 无 session 返回 401
  • Web-UI 会话正常创建
🤖 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 `@tests/unit/api/v1/keys-write-handlers-authz.test.ts` around lines 68 - 131,
Add unit tests for the createSelfKey handler mirroring existing patterns: import
createSelfKey from "`@/app/api/v1/resources/keys/handlers`" and call it with
makeContext(readOnlyAuth, <body>) asserting a 403 and that the create action
mock (e.g., createSelfKeyMock or the existing key-creation mock used elsewhere)
was not called; call createSelfKey with makeContext(null, <body>) asserting a
401 and no action call; call createSelfKey with makeContext(webAuth, <body>)
asserting a successful creation response and that the create action mock was
invoked with the expected args. Ensure you reuse makeContext, readOnlyAuth,
webAuth and the same mock names/patterns used in this file so the new tests
follow the existing style.
🤖 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 `@tests/unit/actions/keys-self-service-authz.test.ts`:
- Around line 154-157: The test currently asserts the error string
(expect(result.error) === "CANNOT_DISABLE_LAST_KEY") which doesn't verify the
structured error contract; change the assertion to check the structured error
code instead (expect(result.errorCode).toBe("CANNOT_DISABLE_LAST_KEY")) while
keeping the existing expect(result.ok).toBe(false) check so the test ensures
both failure and the correct errorCode from the operation (refer to the result
object used in this test).

---

Outside diff comments:
In `@src/app/`[locale]/dashboard/_components/user/forms/add-key-form.tsx:
- Around line 73-76: The onSubmit handler in add-key-form.tsx currently throws
when userId is missing and the submit button is gated by !!userId; change this
to support the new self endpoint by removing the throw and switching the API
call based on presence of userId: inside onSubmit decide between calling
addOwnKey(body) when userId is falsy and the existing addKey/addUserKey call
when userId is present, and update the submit button so it is not disabled
solely by !!userId (keep other validations intact).

In `@tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx`:
- Around line 114-118: The test currently allows expiresAt to be either "" or
undefined which weakens the regression check; update the assertion on the
payload to require an explicit empty string for payload.expiresAt (not
undefined) so the test enforces the "clear by sending empty string" behavior —
keep the existing Object.hasOwn(payload, "expiresAt") check and replace the
loose truthy check with an assertion that payload.expiresAt equals the empty
string ("") to catch regressions where the field would be omitted/serialized
away.

---

Nitpick comments:
In `@tests/unit/api/v1/keys-write-handlers-authz.test.ts`:
- Around line 68-131: Add unit tests for the createSelfKey handler mirroring
existing patterns: import createSelfKey from
"`@/app/api/v1/resources/keys/handlers`" and call it with
makeContext(readOnlyAuth, <body>) asserting a 403 and that the create action
mock (e.g., createSelfKeyMock or the existing key-creation mock used elsewhere)
was not called; call createSelfKey with makeContext(null, <body>) asserting a
401 and no action call; call createSelfKey with makeContext(webAuth, <body>)
asserting a successful creation response and that the create action mock was
invoked with the expected args. Ensure you reuse makeContext, readOnlyAuth,
webAuth and the same mock names/patterns used in this file so the new tests
follow the existing style.
🪄 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: CHILL

Plan: Pro

Run ID: 07f4822d-c64a-4f67-9f43-0b355612be3d

📥 Commits

Reviewing files that changed from the base of the PR and between c4e1ff4 and 36b61fe.

📒 Files selected for processing (27)
  • messages/zh-TW/myUsage.json
  • src/actions/keys.ts
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/api/v1/resources/keys/handlers.ts
  • src/app/api/v1/resources/keys/router.ts
  • src/app/api/v1/resources/me/handlers.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts
  • src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts
  • src/lib/api-client/v1/actions/keys.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/utils/error-messages.ts
  • src/lib/utils/special-settings.ts
  • src/repository/system-config.ts
  • src/types/special-settings.ts
  • tests/unit/actions/keys-remove-error-codes.test.ts
  • tests/unit/actions/keys-self-service-authz.test.ts
  • tests/unit/api/v1/api-client-actions.test.ts
  • tests/unit/api/v1/keys-delete-error-mapping.test.ts
  • tests/unit/api/v1/keys-write-handlers-authz.test.ts
  • tests/unit/dashboard/add-key-form-balance-page-toggle.test.tsx
  • tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
  • tests/unit/dashboard/add-key-form-self-service.test.tsx
  • tests/unit/repository/system-config-degradation-ladder.test.ts
✅ Files skipped from review due to trivial changes (2)
  • messages/zh-TW/myUsage.json
  • src/lib/api-client/v1/openapi-types.gen.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/lib/utils/special-settings.ts
  • src/types/special-settings.ts
  • src/lib/utils/error-messages.ts
  • tests/unit/api/v1/keys-delete-error-mapping.test.ts
  • src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts
  • tests/unit/actions/keys-remove-error-codes.test.ts
  • src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts

Comment on lines +154 to +157
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toBe("CANNOT_DISABLE_LAST_KEY");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请改为断言 errorCode,不要只看 error 字符串。

这条用例覆盖的是这次 PR 的“结构化错误码”契约,但现在断言的是 result.error === "CANNOT_DISABLE_LAST_KEY"。如果后续实现退化成只返回同名文本而丢失 errorCode,这个测试仍然会通过,前端按错误码翻译的链路却已经坏了。

建议修改
     expect(result.ok).toBe(false);
     if (!result.ok) {
-      expect(result.error).toBe("CANNOT_DISABLE_LAST_KEY");
+      expect(result.errorCode).toBe("CANNOT_DISABLE_LAST_KEY");
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toBe("CANNOT_DISABLE_LAST_KEY");
}
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errorCode).toBe("CANNOT_DISABLE_LAST_KEY");
}
🤖 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 `@tests/unit/actions/keys-self-service-authz.test.ts` around lines 154 - 157,
The test currently asserts the error string (expect(result.error) ===
"CANNOT_DISABLE_LAST_KEY") which doesn't verify the structured error contract;
change the assertion to check the structured error code instead
(expect(result.errorCode).toBe("CANNOT_DISABLE_LAST_KEY")) while keeping the
existing expect(result.ok).toBe(false) check so the test ensures both failure
and the correct errorCode from the operation (refer to the result object used in
this test).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:i18n area:UI enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Backlog

3 participants