Skip to content

feat: 增加 API 令牌开发者模式#265

Merged
SurviveM merged 3 commits intoawsl-project:mainfrom
ymkiux:feat/api-token-dev-mode
Feb 26, 2026
Merged

feat: 增加 API 令牌开发者模式#265
SurviveM merged 3 commits intoawsl-project:mainfrom
ymkiux:feat/api-token-dev-mode

Conversation

@ymkiux
Copy link
Contributor

@ymkiux ymkiux commented Feb 26, 2026

变更

  • API Tokens 列表新增开发者模式开关(按令牌生效)
  • dev mode 请求详情永久保留(不影响其他令牌)
  • 备份/恢复与清理逻辑支持 dev mode

验证

  • 未运行(UI + 后端逻辑变更)

Summary by CodeRabbit

  • 新功能

    • 在令牌管理界面新增“开发者模式”开关,可为 API 令牌启用或禁用该模式
    • 启用开发者模式的令牌将在请求/响应详情中保留更多信息,便于调试
    • 备份与恢复流程现会包含开发者模式设置,导出/导入时保留该状态
  • 本地化

    • 添加英文与中文的开发者模式相关显示文本(启用/禁用标签)
  • 管理

    • 管理端点支持更新令牌的开发者模式设置

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb75588 and 01f311d.

📒 Files selected for processing (1)
  • internal/executor/middleware_dispatch.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/executor/middleware_dispatch.go

📝 Walkthrough

Walkthrough

为 API 令牌引入 DevMode 布尔标志并将其贯穿域模型、执行器状态、数据库模型、HTTP 处理器、备份/恢复与前端,同时基于该标志调整请求详情清理与数据库清理条件。

Changes

Cohort / File(s) Summary
域模型 & 备份
internal/domain/backup.go, internal/domain/model.go, internal/service/backup.go
BackupAPITokenAPITokenProxyRequest 中新增 DevMode bool 字段,备份导出/导入与序列化/反序列化包含该字段。
流上下文键
internal/flow/keys.go
新增常量 KeyAPITokenDevMode 用于在 flow 上下文中携带令牌的 DevMode。
执行器与状态
internal/executor/flow_state.go, internal/executor/executor.go, internal/executor/middleware_dispatch.go, internal/executor/middleware_ingress.go
execState 增加 apiTokenDevMode;引入 shouldClearRequestDetailFor 并通过 clearDetail 参数控制 processAdapterEventsRealtime 与中间件路径中请求详情的清理与保留。
HTTP 处理器
internal/handler/proxy.go, internal/handler/admin.go
认证时将 apiToken.DevMode 写入 flow 上下文(KeyAPITokenDevMode);管理员更新接口支持可选 DevMode 字段。
数据库模型与仓库
internal/repository/sqlite/models.go, internal/repository/sqlite/api_token.go, internal/repository/sqlite/proxy_request.go, internal/repository/sqlite/proxy_upstream_attempt.go
在 DB 模型加入 dev_mode(int),映射到域模型的 DevMode;ClearDetailOlderThan/清理查询限制为仅清理 dev_mode = 0 的请求。
前端类型、本地化与 UI
web/src/lib/transport/types.ts, web/src/locales/en.json, web/src/locales/zh.json, web/src/pages/api-tokens/index.tsx
TypeScript 接口加入 devMode 字段;新增本地化键 apiTokens.devMode/devModeEnabled/devModeDisabled;令牌列表新增 DevMode 列、开关与状态徽章并支持切换。

Sequence Diagram(s)

sequenceDiagram
    participant UI as "UI (Admin/Client)"
    participant Handler as "Proxy Handler"
    participant Flow as "Flow Context"
    participant Executor as "Executor / Middleware"
    participant DB as "SQLite DB"

    UI->>Handler: 请求或切换令牌 DevMode
    Handler->>Flow: c.Set(KeyAPITokenDevMode, apiToken.DevMode)
    Flow->>Executor: 启动中间件/执行(包含 state.apiTokenDevMode)
    Executor->>Executor: shouldClearRequestDetailFor(state) -> clearDetail
    Executor->>DB: ClearDetailOlderThan(...) WHERE dev_mode = 0
    DB-->>Executor: 更新结果
    Executor-->>UI: 返回(包含或省略 request/response 详情 取决于 clearDetail)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Bowl42
  • awsl233777
  • SurviveM

Poem

🐰 开发兔子轻轻跳,
新旗 DevMode 映草梢,
细节留与清理分,
库中标记分明好,
版本里头笑得妙。

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地概括了核心变更:为 API 令牌添加开发者模式功能。

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
internal/executor/middleware_dispatch.go (2)

310-329: ⚠️ Potential issue | 🟠 Major

错误路径遗漏了 clearDetail 清理逻辑。

clearDetail 为 true 时,此上下文取消路径在调用 proxyRequestRepo.Update 前未清理 RequestInfo/ResponseInfoRequestInfo 可能在 ingress 中已设置,会被意外持久化。

类似问题也存在于 Line 357-378 的重试等待取消路径。

🐛 建议修复:在更新前添加清理逻辑
 if ok && ctx.Err() != nil {
     proxyReq.Status = "CANCELLED"
     proxyReq.EndTime = time.Now()
     proxyReq.Duration = proxyReq.EndTime.Sub(proxyReq.StartTime)
     if ctx.Err() == context.Canceled {
         proxyReq.Error = "client disconnected"
     } else if ctx.Err() == context.DeadlineExceeded {
         proxyReq.Error = "request timeout"
     } else {
         proxyReq.Error = ctx.Err().Error()
     }
+    if clearDetail {
+        proxyReq.RequestInfo = nil
+        proxyReq.ResponseInfo = nil
+    }
     _ = e.proxyRequestRepo.Update(proxyReq)

同样需要在 Line 369 的 proxyRequestRepo.Update 前添加相同的清理逻辑。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/executor/middleware_dispatch.go` around lines 310 - 329, The
cancellation/error handling paths that call e.proxyRequestRepo.Update (inside
the block handling proxyErr and the retry-wait cancellation block) must respect
the proxyReq.ClearDetail flag and remove/zero-out RequestInfo and ResponseInfo
before persisting; update the branches in middleware_dispatch.go where proxyErr
is handled (the block that sets proxyReq.Status = "CANCELLED", sets Error and
Duration) and the retry-wait cancel path (the later block that also calls
proxyRequestRepo.Update) to check proxyReq.ClearDetail and clear the
RequestInfo/ResponseInfo fields prior to calling e.proxyRequestRepo.Update and
before e.broadcaster.BroadcastProxyRequest so no sensitive request/response
details get persisted or broadcast. Ensure you modify both locations that call
proxyRequestRepo.Update so behavior is consistent.

284-305: ⚠️ Potential issue | 🟠 Major

错误路径的 RequestInfo 未被清理。

在 Line 286-292,ResponseInfo 正确地根据 !clearDetail 条件设置。但在 Line 305 调用 proxyRequestRepo.Update 前,proxyReq.RequestInfo 未被清理。

与成功路径(Lines 224-227)的处理不一致。

🐛 建议修复
 proxyReq.Cost = attemptRecord.Cost
 proxyReq.TTFT = attemptRecord.TTFT

+if clearDetail {
+    proxyReq.RequestInfo = nil
+}
 _ = e.proxyRequestRepo.Update(proxyReq)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/executor/middleware_dispatch.go` around lines 284 - 305, The error
path fails to clear sensitive request details before persisting: ensure that
when clearDetail is true you set proxyReq.RequestInfo = nil (or an empty
RequestInfo) the same way ResponseInfo is conditionally omitted; update the
block around responseCapture handling (where proxyReq.ResponseInfo is set based
on clearDetail and before calling e.proxyRequestRepo.Update(proxyReq)) to
explicitly clear proxyReq.RequestInfo whenever clearDetail is true so the
error-path persistence matches the success-path behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/executor/middleware_dispatch.go`:
- Around line 310-329: The cancellation/error handling paths that call
e.proxyRequestRepo.Update (inside the block handling proxyErr and the retry-wait
cancellation block) must respect the proxyReq.ClearDetail flag and
remove/zero-out RequestInfo and ResponseInfo before persisting; update the
branches in middleware_dispatch.go where proxyErr is handled (the block that
sets proxyReq.Status = "CANCELLED", sets Error and Duration) and the retry-wait
cancel path (the later block that also calls proxyRequestRepo.Update) to check
proxyReq.ClearDetail and clear the RequestInfo/ResponseInfo fields prior to
calling e.proxyRequestRepo.Update and before e.broadcaster.BroadcastProxyRequest
so no sensitive request/response details get persisted or broadcast. Ensure you
modify both locations that call proxyRequestRepo.Update so behavior is
consistent.
- Around line 284-305: The error path fails to clear sensitive request details
before persisting: ensure that when clearDetail is true you set
proxyReq.RequestInfo = nil (or an empty RequestInfo) the same way ResponseInfo
is conditionally omitted; update the block around responseCapture handling
(where proxyReq.ResponseInfo is set based on clearDetail and before calling
e.proxyRequestRepo.Update(proxyReq)) to explicitly clear proxyReq.RequestInfo
whenever clearDetail is true so the error-path persistence matches the
success-path behavior.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6454614 and c12ecc0.

📒 Files selected for processing (18)
  • internal/domain/backup.go
  • internal/domain/model.go
  • internal/executor/executor.go
  • internal/executor/flow_state.go
  • internal/executor/middleware_dispatch.go
  • internal/executor/middleware_ingress.go
  • internal/flow/keys.go
  • internal/handler/admin.go
  • internal/handler/proxy.go
  • internal/repository/sqlite/api_token.go
  • internal/repository/sqlite/models.go
  • internal/repository/sqlite/proxy_request.go
  • internal/repository/sqlite/proxy_upstream_attempt.go
  • internal/service/backup.go
  • web/src/lib/transport/types.ts
  • web/src/locales/en.json
  • web/src/locales/zh.json
  • web/src/pages/api-tokens/index.tsx
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (5)
internal/repository/sqlite/proxy_upstream_attempt.go (1)
internal/repository/sqlite/models.go (4)
  • ProxyRequest (198-232)
  • ProxyRequest (234-234)
  • ProxyUpstreamAttempt (237-262)
  • ProxyUpstreamAttempt (264-264)
internal/executor/middleware_ingress.go (1)
internal/flow/keys.go (1)
  • KeyAPITokenDevMode (21-21)
internal/handler/proxy.go (1)
internal/flow/keys.go (1)
  • KeyAPITokenDevMode (21-21)
internal/executor/executor.go (6)
internal/domain/adapter_event.go (2)
  • AdapterEventChan (40-40)
  • EventResponseInfo (10-10)
internal/domain/model.go (3)
  • ProxyUpstreamAttempt (361-415)
  • RequestInfo (274-279)
  • ResponseInfo (280-284)
internal/repository/sqlite/models.go (2)
  • ProxyUpstreamAttempt (237-262)
  • ProxyUpstreamAttempt (264-264)
web/src/lib/transport/types.ts (3)
  • ProxyUpstreamAttempt (245-274)
  • RequestInfo (175-180)
  • ResponseInfo (182-186)
web/src/lib/transport/index.ts (3)
  • ProxyUpstreamAttempt (28-28)
  • RequestInfo (30-30)
  • ResponseInfo (31-31)
internal/adapter/client/adapter.go (1)
  • RequestInfo (17-20)
web/src/pages/api-tokens/index.tsx (6)
internal/domain/model.go (1)
  • APIToken (707-742)
internal/repository/sqlite/models.go (2)
  • APIToken (134-146)
  • APIToken (148-148)
web/src/lib/transport/types.ts (1)
  • APIToken (609-623)
web/src/lib/transport/index.ts (1)
  • APIToken (70-70)
web/src/components/ui/index.ts (4)
  • TableHead (13-13)
  • TableCell (15-15)
  • Switch (51-51)
  • Badge (20-20)
web/src/components/ui/badge.tsx (1)
  • Badge (54-54)
🔇 Additional comments (26)
internal/domain/backup.go (1)

88-88: 字段扩展方向正确,备份模型已覆盖 DevMode。

该改动与备份/恢复链路的功能目标一致,数据模型扩展清晰。

internal/flow/keys.go (1)

21-21: 新增 Flow Key 合理,便于在链路中传递令牌开发者模式状态。

web/src/lib/transport/types.ts (1)

619-619: 类型层同步到位:运行态必填、备份态可选的设计是合理的。

Also applies to: 808-808

web/src/locales/en.json (1)

810-812: 国际化键补充完整,可支撑 DevMode 开关与状态展示。

web/src/pages/api-tokens/index.tsx (1)

142-147: DevMode 前端功能闭环完整(操作、展示、状态文案)且实现清晰。

Also applies to: 271-271, 335-355

internal/domain/model.go (1)

357-358: 领域模型扩展正确,DevMode 在请求与令牌两个核心实体上都已覆盖。

Also applies to: 728-729

internal/service/backup.go (1)

197-197: 备份导出/导入链路对 DevMode 的映射是成对且一致的。

Also applies to: 743-743

internal/executor/flow_state.go (1)

25-25: 执行态新增 apiTokenDevMode 字段合理,便于后续链路消费该标志位。

web/src/locales/zh.json (1)

810-812: LGTM!

本地化键值添加正确,与现有结构保持一致。devModedevModeEnabled 使用相同的文本是合理的,分别用于列标题和状态显示。

internal/handler/admin.go (1)

1067-1097: LGTM!

DevMode 字段的处理遵循了现有的部分更新模式,使用 *bool 指针正确区分了"未提供"和"显式设置为 false"的情况。实现与 IsEnabled 等其他字段保持一致。

internal/handler/proxy.go (1)

144-148: LGTM!

DevMode 在 token 认证成功后正确存储到 flow context 中,与 KeyAPITokenID 的处理模式一致。

需注意:当 apiToken 为 nil 时(未启用 token 认证或未提供 token),KeyAPITokenDevMode 不会被设置。从 internal/executor/middleware_ingress.go 的代码来看,下游会正确处理这种情况(获取失败时 state.apiTokenDevMode 保持零值 false),符合预期行为。

internal/repository/sqlite/proxy_upstream_attempt.go (1)

244-258: LGTM!

使用子查询过滤 dev_mode = 0 的请求是正确的实现方式,因为 ProxyUpstreamAttempt 本身没有 dev_mode 字段,需要关联 ProxyRequest 表。逻辑与 proxy_request.go 中的处理保持一致。

internal/repository/sqlite/proxy_request.go (3)

428-429: LGTM!

在清理条件中添加 dev_mode = 0 确保了开发者模式的请求详情被永久保留,符合 PR 需求。


477-477: LGTM!

DevMode 的 boolean-to-int 映射与 IsStream 等其他字段的处理模式一致。


517-517: LGTM!

从数据库读取时正确将整数转换回布尔值。

internal/executor/middleware_ingress.go (2)

54-58: LGTM!

从 flow context 读取 KeyAPITokenDevMode 的模式与其他字段(如 KeyClientTypeKeyProjectID)保持一致,类型断言能正确处理键不存在或类型不匹配的情况。


94-117: LGTM!

  • DevMode 正确传递到 ProxyRequest 对象用于持久化。
  • 使用 shouldClearRequestDetailFor(state) 替换原有的直接调用,使详情清理逻辑能够感知 DevMode 状态。
  • !clearDetail 的条件判断语义正确:当不应清理详情时(包括开发者模式场景),才填充请求信息。
internal/repository/sqlite/models.go (2)

142-142: LGTM!

DevMode 字段使用 int 类型存储布尔值,与 IsEnabled 等字段保持一致。default:0 确保现有记录和未显式设置的新记录默认为普通模式。


231-231: LGTM!

ProxyRequestDevMode 字段定义与 APIToken 保持一致,便于数据清理逻辑根据此字段判断是否保留请求详情。

internal/repository/sqlite/api_token.go (2)

36-44: LGTM!

Update 方法正确包含 dev_mode 字段,确保通过 API 更新 token 时 DevMode 状态能够持久化。


103-143: LGTM!

toModeltoDomain 方法的 DevMode 转换实现正确,与 IsEnabled 等字段的处理模式完全一致。

internal/executor/executor.go (2)

415-421: 实现清晰正确。

新增的 shouldClearRequestDetailFor 辅助函数逻辑清晰:

  • 优先检查令牌级别的开发者模式(apiTokenDevMode
  • 若开发者模式开启则保留详情(返回 false
  • 否则回退到全局配置

这与 PR 目标"开启开发者模式的令牌,其请求详情将被永久保留"一致。


317-322: 事件过滤逻辑正确。

函数签名扩展和事件处理逻辑良好:

  • clearDetail 参数控制是否在实时广播中保留 RequestInfo/ResponseInfo
  • 即使 clearDetail 为 true,仍正确处理 Metrics、ResponseModel、FirstToken 等事件
  • 这确保了即使不保留详情,性能指标仍能正常采集

Also applies to: 360-368

internal/executor/middleware_dispatch.go (3)

28-28: clearDetail 初始化时机和位置正确。

在 dispatch 开始时计算一次 clearDetail,避免在循环中重复调用,性能良好。


183-186: 成功路径的清理逻辑完整正确。

成功路径的实现覆盖全面:

  • Line 183-186:清理 attemptRecord 的详情
  • Line 204-210:仅在 !clearDetail 时设置 ResponseInfo
  • Line 224-227:最终清理确保 RequestInfo(来自 ingress)也被清除

Also applies to: 204-210, 224-227


388-392: 全部路由失败路径的清理逻辑正确。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/executor/middleware_dispatch.go (1)

327-330: 可选重构:考虑提取重复的清理逻辑。

if clearDetail { proxyReq.RequestInfo = nil; proxyReq.ResponseInfo = nil } 模式在文件中重复了 6 次。可以考虑提取为辅助方法以减少重复:

♻️ 可选:提取清理辅助方法

Executor 或相关结构体上添加辅助方法:

func clearProxyRequestDetail(req *domain.ProxyRequest, clearDetail bool) {
    if clearDetail {
        req.RequestInfo = nil
        req.ResponseInfo = nil
    }
}

然后在各处调用:

- if clearDetail {
-     proxyReq.RequestInfo = nil
-     proxyReq.ResponseInfo = nil
- }
+ clearProxyRequestDetail(proxyReq, clearDetail)

当前内联写法也可接受,因为它使每个退出点的行为更加明确。

Also applies to: 378-381, 401-404

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/executor/middleware_dispatch.go` around lines 327 - 330, The
repeated pattern clearing proxy request details (if clearDetail {
proxyReq.RequestInfo = nil; proxyReq.ResponseInfo = nil }) appears multiple
times (e.g., near the exit paths that reference proxyReq and clearDetail);
extract this into a small helper such as clearProxyRequestDetail(req
*domain.ProxyRequest, clearDetail bool) and replace the inline blocks with calls
to that helper at each exit point (locations currently using proxyReq and
clearDetail around the middleware dispatch flows). Ensure the helper sets
RequestInfo and ResponseInfo to nil when clearDetail is true so behavior remains
identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/executor/middleware_dispatch.go`:
- Around line 327-330: The repeated pattern clearing proxy request details (if
clearDetail { proxyReq.RequestInfo = nil; proxyReq.ResponseInfo = nil }) appears
multiple times (e.g., near the exit paths that reference proxyReq and
clearDetail); extract this into a small helper such as
clearProxyRequestDetail(req *domain.ProxyRequest, clearDetail bool) and replace
the inline blocks with calls to that helper at each exit point (locations
currently using proxyReq and clearDetail around the middleware dispatch flows).
Ensure the helper sets RequestInfo and ResponseInfo to nil when clearDetail is
true so behavior remains identical.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c12ecc0 and cb75588.

📒 Files selected for processing (1)
  • internal/executor/middleware_dispatch.go
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (1)
internal/executor/middleware_dispatch.go (2)
internal/domain/model.go (2)
  • RequestInfo (274-279)
  • ResponseInfo (280-284)
web/src/lib/transport/types.ts (2)
  • RequestInfo (175-180)
  • ResponseInfo (182-186)
🔇 Additional comments (4)
internal/executor/middleware_dispatch.go (4)

131-131: LGTM!

clearDetail 传递给异步事件处理器,确保流式事件处理与同步路径保持一致的详情清理行为。


183-227: 成功路径的详情清理逻辑正确。

采用了防御性编程模式:先条件性设置 ResponseInfo(第 204-210 行),再在最终更新前统一清理(第 224-227 行)。这确保了即使 RequestInfo 在其他地方(如 ingress 中间件)被设置,也能在 clearDetail=true 时被正确清除。


269-308: 错误路径的详情处理与成功路径保持一致。

失败场景下的 attemptRecordproxyReq 详情清理逻辑正确,确保开发者模式标志在所有结果状态下都被尊重。


28-28: 集中化详情清理决策是优秀的设计模式。

在调度开始时一次计算 clearDetail 标志,随后在整个请求处理流程的多个关键节点(如事件处理、响应映射、异常处理等)中一致性地使用,确保了行为的统一性和可维护性。

已验证:shouldClearRequestDetailForinternal/executor/executor.go 中正确实现,当 apiTokenDevModetrue 时返回 false,准确反映了"开发者模式开启 → 保留详情"的设计意图。此外,该函数在调用时也正确地作为备选方案回退到全局配置 shouldClearRequestDetail()

@SurviveM SurviveM merged commit 171c86d into awsl-project:main Feb 26, 2026
2 checks passed
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.

2 participants