Skip to content

v1.0.4-preview.1#6

Merged
CSCITech merged 8 commits into
mainfrom
cscitech
Jul 3, 2026
Merged

v1.0.4-preview.1#6
CSCITech merged 8 commits into
mainfrom
cscitech

Conversation

@CSCITech

@CSCITech CSCITech commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

📝 变更描述 / Description

本次变更围绕 v1.0.4-preview.1 的兼容与日志能力展开:

  • 补充 OpenAI Responses 与 Chat Completions 的双向兼容转换,覆盖普通响应、流式响应、工具调用、reasoning、usage 与 incomplete/status 映射,并接入 Advanced Custom 的 Responses → Chat Completions 转发路径。
  • 增强 Gemini、Claude 与 OpenAI 兼容转换:保留“文本 + 工具调用”同时存在的输出内容,规范工具参数解析失败时的处理,并补充对应回归测试。
  • 完善 Doubao Seedance 2.0 视频任务支持,按分辨率与是否包含视频输入计算计费倍率,并保留 safety_identifierpriority 等请求字段。
  • 拆分用量日志中的重试筛选:新增错误重试与空补全重试的独立标记、查询、统计和前端筛选/i18n 展示,同时保持总 retry 筛选兼容。
  • 修正异步任务差额结算/退款日志的数据导出归属,使 quota data 使用任务提交节点,避免轮询节点覆盖真实归属。

这些改动生效的关键在于:请求进入上游前先转换为目标协议结构,上游响应返回后再按客户端协议还原;日志写入时同步 retry 子类型字段,查询与统计直接使用后端过滤条件;Seedance 计费通过价格表计算相对基础价倍率;异步任务计费日志显式传递任务私有数据中的提交节点。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 本 PR 未标记为 Bug fix;如需按 bug fix 提交,请先关联对应 Issue。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

go test ./service/... ./relay/channel/openai ./relay/channel/gemini ./relay/channel/advancedcustom ./relay/channel/claude ./relay/channel/task/doubao ./model
ok

bun test src/features/usage-logs/lib/utils.test.ts
8 pass, 0 fail

bun run typecheck
passed

git diff --check
passed

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR separates retry markers, adds Responses-to-chat conversion paths and relay handlers, updates Doubao video billing, and expands task protocol request-body configuration across backend and web layers.

Changes

Retry Log Classification and Quota Data Dimensions

Layer / File(s) Summary
Log retry fields and filters
controller/relay.go, model/log.go, model/main.go, model/task.go, service/task_billing.go
Adds separate retry markers, backfill reconciliation, and task-node attribution into logged billing data.
Retry filter tests
model/log_test.go
Updates retry fixtures, subtype assertions, and backfill expectations.
QuotaData dimensions and aggregation
model/usedata.go, service/task_billing_test.go
Extends quota records, logging inputs, aggregation, and the task billing cache test.
Usage-log retry UI and routing
web/default/src/features/usage-logs/*, web/default/src/routes/_authenticated/usage-logs/$section.tsx
Exposes separate retry subtype values through filters, utils, types, route validation, and locale keys.
Retry label translations
web/default/src/i18n/locales/*.json, web/default/src/i18n/static-keys.ts
Adds static keys and locale strings for the new retry labels.

Estimated code review effort: 4 (Complex) | ~75 minutes

OpenAI Responses to Chat Completions Conversion

Layer / File(s) Summary
Responses contracts and core wrappers
dto/channel_settings.go, dto/openai_response.go, service/openai_chat_responses_compat.go, service/openaicompat/chat_to_responses_response.go, service/openaicompat/responses_to_chat.go
Registers the new converter, changes Responses field shapes, and adds request/response helpers for chat-to-responses conversion.
Responses request conversion
service/openaicompat/responses_request_to_chat.go, service/openaicompat/responses_compat_test.go
Maps Responses requests into chat-completions requests, including tools, tool choice, multimodal parts, and call-id handling, with direct tests.
Chat-to-responses response mapping
service/openaicompat/chat_to_responses_response.go, service/openaicompat/responses_to_chat.go, service/openaicompat/responses_compat_test.go
Converts chat completions back into Responses payloads and streams, including status, usage, reasoning, and tool-call state.
Responses relay wiring
relay/channel/advancedcustom/adaptor.go, relay/channel/openai/responses_via_chat.go, relay/channel/gemini/adaptor.go, relay/channel/gemini/adaptor_responses.go, relay/channel/gemini/relay_responses.go, relay/channel/gemini/relay_gemini_usage_test.go
Routes Responses requests through the new conversion paths and emits Responses-format output from OpenAI and Gemini relays.
Claude and Gemini tool handling
relay/channel/claude/relay-claude.go, relay/channel/claude/relay_claude_test.go, service/convert.go, service/convert_test.go, tools/jsonwrapcheck/allowlist.txt
Changes tool-argument handling and preserves text alongside tool-call output in conversion paths.
Advanced custom Responses routing
relay/channel/advancedcustom/adaptor_test.go
Covers the new advanced-custom Responses request conversion and upstream path selection.
Gemini Responses preprocessing and tests
relay/channel/gemini/adaptor_responses_test.go
Covers Gemini-specific request preprocessing for Responses inputs.
OpenAI chat-to-responses handlers
relay/channel/openai/responses_via_chat.go, relay/channel/openai/chat_via_responses.go, relay/channel/openai/empty_completion_test.go
Adds non-streaming and streaming handlers that emit Responses-format output from chat completions and reuses Responses usage conversion for streaming completion events.

Estimated code review effort: 4 (Complex) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Adaptor
  participant OpenAICompat
  participant ResponsesBridge
  participant Client

  Adaptor->>OpenAICompat: ResponsesRequestToChatCompletionsRequest()
  OpenAICompat-->>Adaptor: GeneralOpenAIRequest
  Adaptor->>ResponsesBridge: OaiChatToResponsesHandler / GeminiResponsesHandler
  ResponsesBridge->>OpenAICompat: ChatCompletionsResponseToResponsesResponse()
  ResponsesBridge-->>Client: Responses format output
Loading

Possibly related PRs

  • MAX-API-Next/MAX-API#1: Both modify the retry-log filtering path in model/log.go, and this PR extends it with separate error_retry and empty_retry behavior.
  • MAX-API-Next/MAX-API#2: Both update model/log_test.go retry fixtures and expected totals for the same retry-filter dataset.
  • MAX-API-Next/MAX-API#5: Both touch retry-marker backfill and retry-related log filtering logic in model/log.go.

Poem

A rabbit hopped through logs so bright,
Split retries into day and night. 🐇
Responses learned to chat, then back,
While billing counted every track.
Doubao tuned its price with care,
And carrots fluttered everywhere. 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is only a version tag and does not describe the actual changes. Use a short descriptive title summarizing the main change, e.g. Responses/usage-log compatibility updates.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the compatibility, logging, and billing changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cscitech

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

@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: 10

Caution

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

⚠️ Outside diff range comments (2)
model/log.go (1)

504-548: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use the persisted log timestamp for quota-data export.

Line 535 captures a second timestamp after LOG_DB.Create, so quota data can land in a different second/window than the log row. Capture createdAt once before constructing Log and reuse it for both records.

🧩 Proposed fix
+	createdAt := common.GetTimestamp()
 	log := &Log{
 		UserId:           userId,
 		Username:         username,
-		CreatedAt:        common.GetTimestamp(),
+		CreatedAt:        createdAt,
 		Type:             LogTypeConsume,
@@
 	}
 	err := LOG_DB.Create(log).Error
@@
 	if common.DataExportEnabled {
-		createdAt := common.GetTimestamp()
 		gopool.Go(func() {
🤖 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 `@model/log.go` around lines 504 - 548, The quota-data export in the log
persistence flow is using a second timestamp instead of the timestamp stored on
the Log row. In the log-recording function around the Log struct creation and
LogQuotaData call, capture createdAt once before building the Log, set
Log.CreatedAt from that shared value, and reuse the same value when populating
QuotaDataLogParams.CreatedAt so both records stay aligned.
relay/channel/task/doubao/adaptor.go (1)

207-230: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not treat malformed video_url values as usable video input.

hasUsableVideoInput returns true for bool(true), arbitrary non-empty maps, and all default types. Because Line 207 and Line 226 use it for video_url, inputs like "video_url": true or {"video_url": {"foo": ""}} can incorrectly switch billing to the video-input price bucket. Use a stricter URL-specific validator for video_url fields.

Proposed fix
-	if videoURL, has := metadata["video_url"]; has && hasUsableVideoInput(videoURL) {
+	if videoURL, has := metadata["video_url"]; has && hasUsableVideoURL(videoURL) {
 		return true
 	}
@@
-		if itemMap["type"] == "video_url" && hasUsableVideoInput(itemMap["video_url"]) {
+		if itemMap["type"] == "video_url" && hasUsableVideoURL(itemMap["video_url"]) {
 			return true
 		}
-		if videoURL, has := itemMap["video_url"]; has && hasUsableVideoInput(videoURL) {
+		if videoURL, has := itemMap["video_url"]; has && hasUsableVideoURL(videoURL) {
 			return true
 		}
 	}
 	return false
 }
 
+func hasUsableVideoURL(value interface{}) bool {
+	switch v := value.(type) {
+	case string:
+		return strings.TrimSpace(v) != ""
+	case map[string]interface{}:
+		url, has := v["url"]
+		return has && hasUsableVideoURL(url)
+	case map[string]string:
+		url, has := v["url"]
+		return has && strings.TrimSpace(url) != ""
+	default:
+		return false
+	}
+}
+
 func hasUsableVideoInput(value interface{}) bool {
 	switch v := value.(type) {
@@
 	case map[string]interface{}:
 		if url, has := v["url"]; has {
-			return hasUsableVideoInput(url)
+			return hasUsableVideoURL(url)
 		}
 		return len(v) > 0
@@
 	default:
-		return true
+		return false
 	}
 }

Also applies to: 236-257

🤖 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 `@relay/channel/task/doubao/adaptor.go` around lines 207 - 230, The video-input
detection logic in the adaptor is too permissive for video_url fields and can
misclassify malformed values as usable video input. Update the checks in the
video detection helper in adaptor.go so that video_url only passes a
URL-specific validation path, while keeping hasUsableVideoInput for the broader
video field if appropriate. Make sure the content array handling and the
top-level metadata["video_url"] path both use the stricter validator, including
the repeated checks in the later block that applies the same logic.
🤖 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 `@model/log_test.go`:
- Around line 85-95: The retry-marker expectations in log_test.go need to
account for the fixture that contains both markers, so it should be included in
both subtype filters. Update the assertions around GetAllLogs for
LogFilterErrorRetry and LogFilterEmptyRetry to reflect the combined-marker
behavior, and make the corresponding stats/expected IDs match the corrected
logOtherRetryMarkers handling in the test setup.

In `@model/log.go`:
- Around line 241-255: The retry marker parsing in logOtherRetryMarkers
currently returns as soon as it sees empty_retry, which prevents retry_log from
being recorded when both markers exist. Update logOtherRetryMarkers to inspect
both flags in the parsed otherMap and set errorRetry and emptyRetry
independently before returning, so an Other payload can be counted as both
IsRetry and IsEmptyRetry when appropriate.

In `@relay/channel/gemini/adaptor_responses.go`:
- Around line 32-53: The Gemini tool filtering in filterGeminiResponsesTools is
silently removing any non-"function" tool types, which drops managed tools
without notice. Update the function to detect unsupported tool entries while
iterating the unmarshaled tools and return an explicit error instead of
appending only matching items; keep the existing handling for valid function
tools, but fail fast when search, code execution, file search, computer use, or
any other non-function type is present so callers can see the unsupported
capability.

In `@relay/channel/task/doubao/adaptor.go`:
- Around line 58-59: The optional request fields in the Doubao adapter struct
are using plain strings, so omitted and empty values cannot be distinguished
when the payload is re-marshaled upstream. Update the request struct in
adaptor.go so Resolution and Ratio use *string with omitempty, and adjust any
reads/assignments in the Doubao request path to safely handle nil pointers while
preserving omitted fields for upstream providers.

In `@service/convert_test.go`:
- Around line 39-51: Add a Gemini coverage test for malformed tool arguments in
ResponseOpenAI2Gemini, mirroring the existing Claude fallback test behavior.
Create a test alongside TestResponseOpenAI2GeminiPreservesTextWithToolCalls that
uses openAIResponseWithToolCallArguments with invalid JSON, then assert
ResponseOpenAI2Gemini returns one candidate whose single FunctionCall wraps the
raw value under "arguments". Use the existing ResponseOpenAI2Gemini and
openAIResponseWithToolCallArguments helpers to locate the logic and keep the
assertion shape consistent with the Claude fallback test.

In `@service/convert.go`:
- Around line 618-634: The tool-argument parsing logic is duplicated between
ResponseOpenAI2Claude and ResponseOpenAI2Gemini and should be centralized.
Extract a shared helper for parsing tool call arguments that uses
common.Unmarshal and falls back to a map containing the raw arguments string
when parsing fails. Update both ResponseOpenAI2Claude and ResponseOpenAI2Gemini
to call that helper so the Claude and Gemini conversion paths stay consistent.

In `@service/openaicompat/chat_to_responses_response.go`:
- Around line 535-557: The helper chatToolCallToResponsesOutput currently
advertises an error return but never produces one, which makes the API
misleading and leaves dead error-handling code in the caller. Remove the unused
error from chatToolCallToResponsesOutput and update its callers in the
chat-to-responses conversion path to treat it as a pure transform, using the
function and related dto.ResponsesOutput construction branches to locate the
change.

In `@service/openaicompat/responses_request_to_chat.go`:
- Around line 194-212: The responsesInputContentToChatContent function has a
dead type switch branch for []map[string]any because JSON-unmarshaled nested
arrays arrive as []any and already hit the existing case above. Remove the
unreachable []map[string]any handling, or adjust the upstream decoding/typing so
this function only handles the actual shapes it can receive; keep the logic
centered on responsesInputContentToChatContent and
responsesContentPartsToChatContent.
- Around line 149-178: `responsesInputItemToChatMessages` and
`responsesFunctionCallItemToChatToolCall` allow tool-call messages to be built
with empty identifiers, which can break upstream correlation. Add explicit
validation that function_call items have a non-empty `call_id`/`id` before
creating the assistant tool call, and reject the input with an error if missing.
Do the same for the `responsesInputTypeFunctionCallOutput` branch by requiring a
non-empty `call_id` before appending the tool message. Apply the same validation
in the other conversion path noted by the review so both code paths consistently
prevent empty tool_call_id values.

In `@service/openaicompat/responses_to_chat.go`:
- Around line 120-147: UsageFromResponsesUsage is dropping several nested
usage-detail fields when converting Responses usage into dto.Usage. Update this
function to mirror UsageFromChatUsage by copying all available input detail
fields (CachedTokens, ImageTokens, AudioTokens, CachedCreationTokens,
TextTokens) and output detail fields (ReasoningTokens, TextTokens, AudioTokens,
ImageTokens) from src into the corresponding PromptTokensDetails and
CompletionTokenDetails. Keep the existing token totals behavior unchanged.

---

Outside diff comments:
In `@model/log.go`:
- Around line 504-548: The quota-data export in the log persistence flow is
using a second timestamp instead of the timestamp stored on the Log row. In the
log-recording function around the Log struct creation and LogQuotaData call,
capture createdAt once before building the Log, set Log.CreatedAt from that
shared value, and reuse the same value when populating
QuotaDataLogParams.CreatedAt so both records stay aligned.

In `@relay/channel/task/doubao/adaptor.go`:
- Around line 207-230: The video-input detection logic in the adaptor is too
permissive for video_url fields and can misclassify malformed values as usable
video input. Update the checks in the video detection helper in adaptor.go so
that video_url only passes a URL-specific validation path, while keeping
hasUsableVideoInput for the broader video field if appropriate. Make sure the
content array handling and the top-level metadata["video_url"] path both use the
stricter validator, including the repeated checks in the later block that
applies the same logic.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 3172d872-4c7c-410e-b6e1-4055da6e9678

📥 Commits

Reviewing files that changed from the base of the PR and between 804fb19 and 3fc339e.

📒 Files selected for processing (45)
  • controller/relay.go
  • dto/channel_settings.go
  • dto/openai_response.go
  • model/log.go
  • model/log_test.go
  • model/main.go
  • model/task.go
  • model/usedata.go
  • relay/channel/advancedcustom/adaptor.go
  • relay/channel/advancedcustom/adaptor_test.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/claude/relay_claude_test.go
  • relay/channel/gemini/adaptor.go
  • relay/channel/gemini/adaptor_responses.go
  • relay/channel/gemini/adaptor_responses_test.go
  • relay/channel/gemini/relay_gemini_usage_test.go
  • relay/channel/gemini/relay_responses.go
  • relay/channel/openai/responses_via_chat.go
  • relay/channel/task/doubao/adaptor.go
  • relay/channel/task/doubao/adaptor_media_protocol_test.go
  • relay/channel/task/doubao/constants.go
  • service/convert.go
  • service/convert_test.go
  • service/openai_chat_responses_compat.go
  • service/openaicompat/chat_to_responses_response.go
  • service/openaicompat/responses_compat_test.go
  • service/openaicompat/responses_request_to_chat.go
  • service/openaicompat/responses_to_chat.go
  • service/task_billing.go
  • service/task_billing_test.go
  • web/default/src/features/channels/lib/advanced-custom.ts
  • web/default/src/features/channels/types.ts
  • web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
  • web/default/src/features/usage-logs/constants.ts
  • web/default/src/features/usage-logs/lib/utils.test.ts
  • web/default/src/features/usage-logs/lib/utils.ts
  • web/default/src/features/usage-logs/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/static-keys.ts
  • web/default/src/routes/_authenticated/usage-logs/$section.tsx
📜 Review details
⚠️ CI failures not shown inline (4)

GitHub Actions: CI / 1_Frontend checks.txt: v1.0.4-preview.1

Conclusion: failure

View job details

##[group]Run bun run typecheck
 �[36;1mbun run typecheck�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 $ tsc -b
 src/features/usage-logs/lib/utils.ts(254,18): error TS2345: Argument of type 'string' is not assignable to parameter of type '"retry" | "error_retry" | "empty_retry"'.
 ##[error]Process completed with exit code 2.

GitHub Actions: CI / Backend checks: v1.0.4-preview.1

Conclusion: failure

View job details

##[group]Run go run ./tools/jsonwrapcheck
 �[36;1mgo run ./tools/jsonwrapcheck�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 stale json wrapper allowlist entries:
   relay/channel/claude/relay-claude.go|RequestOpenAI2ClaudeMessage|Unmarshal|***REDACTED***
 Regenerate the baseline with: go run ./tools/jsonwrapcheck -list > tools/jsonwrapcheck/allowlist.txt
 exit status 1
 ##[error]Process completed with exit code 1.

GitHub Actions: CI / Frontend checks: v1.0.4-preview.1

Conclusion: failure

View job details

##[group]Run bun run typecheck
 �[36;1mbun run typecheck�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 $ tsc -b
 src/features/usage-logs/lib/utils.ts(254,18): error TS2345: Argument of type 'string' is not assignable to parameter of type '"retry" | "error_retry" | "empty_retry"'.
 ##[error]Process completed with exit code 2.

GitHub Actions: CI / 0_Backend checks.txt: v1.0.4-preview.1

Conclusion: failure

View job details

##[group]Run go run ./tools/jsonwrapcheck
 �[36;1mgo run ./tools/jsonwrapcheck�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 stale json wrapper allowlist entries:
   relay/channel/claude/relay-claude.go|RequestOpenAI2ClaudeMessage|Unmarshal|***REDACTED***
 Regenerate the baseline with: go run ./tools/jsonwrapcheck -list > tools/jsonwrapcheck/allowlist.txt
 exit status 1
 ##[error]Process completed with exit code 1.
🧰 Additional context used
📓 Path-based instructions (9)
web/default/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/default/AGENTS.md)

web/default/src/**/*.{ts,tsx}: 前端页面文本与组件内文案必须支持 i18n:React 组件中应使用 useTranslation() 取得 t,并通过 t() 渲染用户可见文本;子组件也应自行使用 useTranslation() 保持独立性。
禁止使用两层及以上嵌套三元表达式;复杂逻辑应改用 if-else、提前返回或抽取函数。
控制函数圈复杂度,复杂逻辑应拆成更小的函数;变量与函数命名应有意义并遵循驼峰等常规命名约定。
TypeScript 代码应避免使用 any,优先使用具体类型或 unknown;参数与返回值应显式标注类型;仅类型用途的导入应使用 import type
修改 TypeScript 或 TSX 代码后必须执行类型检查,并修复所有类型错误,不得遗留。
对象非必要不要解构,尤其是组件 props;优先直接使用 props.xxx 以保持代码清晰。
组件应使用函数式组件与 Hooks,遵循单一职责;组件 props 必须有明确类型(接口或类型别名)。
单文件超过约 200 行时应考虑拆分子组件或抽取自定义 Hooks;类型定义可与组件同文件或放在同模块的 types 中。
在 React 中应合理使用 useMemouseCallbackReact.memo,避免在渲染路径中创建新对象或数组;必要时进行代码分割与动态 import
React Query 的数据获取应使用 useQuery、变更应使用 useMutation;每个查询需配置唯一 queryKey,并在成功后对相关查询执行 invalidateQueries;服务端错误应统一交给 handleServerError
Axios 请求应使用项目统一的 api 实例;GET 请求默认去重,特殊请求可显式关闭;认证与通用错误应在拦截器中统一处理。
服务端错误应统一使用 handleServerError,展示层应使用 toast.error 等统一方式;文案需走 i18n;路由级错误应由 errorComponent 承接;表单错误应通过 form.setError 等方式映射到字段。
样式应以 Tailwind 工具类为主,动态类名使用 cn() 合并;非动态场景避免内联样式;响应式采用移动优先与 Tailwind 断点,主题与暗色模式通过 CSS 变量与 dark: 处理。
应使用语义化 HTML、正确关联 label 与输入、保证键盘可操作与合理焦点顺序;必要时添加 ARIA 属性,装饰性图标应使用 aria-hidden="true"
认证与权限应在路由与接口层校验;前后端都应做数据校验(如 Zod);敏感信息不得落前端存储;避免使用 dangerouslySetInnerHTML;跨域与 Cookie 需配合 withCredentials 并按后端要求处理 CSRF。
组件测试应使用 React Testing Library,关注交互与行为,避免测试实现细节;关键流程可补充集成与 E2E 测试。
环境变量应通过 .env 读取,并使用 VITE_ 前缀;代码中不得硬编码密钥。

Files:

  • web/default/src/routes/_authenticated/usage-logs/$section.tsx
  • web/default/src/i18n/static-keys.ts
  • web/default/src/features/channels/types.ts
  • web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
  • web/default/src/features/usage-logs/types.ts
  • web/default/src/features/usage-logs/lib/utils.test.ts
  • web/default/src/features/usage-logs/constants.ts
  • web/default/src/features/channels/lib/advanced-custom.ts
  • web/default/src/features/usage-logs/lib/utils.ts
web/default/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/default/AGENTS.md)

路由应使用 TanStack Router,并通过 createFileRoute 定义;搜索参数应使用 Zod schema + validateSearch 校验;认证与重定向应放在 beforeLoad 中;导航应优先使用 useNavigateLink,避免直接操作 window.location

Files:

  • web/default/src/routes/_authenticated/usage-logs/$section.tsx
web/default/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Use bun as the preferred package manager and script runner for the frontend in web/default/ (bun install, bun run dev, bun run build, and bun run i18n:*).

Files:

  • web/default/src/routes/_authenticated/usage-logs/$section.tsx
  • web/default/src/i18n/static-keys.ts
  • web/default/src/features/channels/types.ts
  • web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
  • web/default/src/features/usage-logs/types.ts
  • web/default/src/features/usage-logs/lib/utils.test.ts
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/features/usage-logs/constants.ts
  • web/default/src/i18n/locales/ja.json
  • web/default/src/features/channels/lib/advanced-custom.ts
  • web/default/src/i18n/locales/fr.json
  • web/default/src/features/usage-logs/lib/utils.ts
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: All JSON marshal/unmarshal operations in Go business code must use the wrappers in common/json.go (common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, common.GetJsonType) instead of directly calling encoding/json; encoding/json types like json.RawMessage and json.Number may still be used as types.
All database code must be compatible with SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+ simultaneously: prefer GORM methods over raw SQL, avoid direct AUTO_INCREMENT/SERIAL, use cross-DB quoting/boolean helpers when raw SQL is unavoidable, avoid DB-specific functions/operators without fallbacks, and ensure migrations work on all three databases.
For request structs parsed from client JSON and later re-marshaled to upstream providers, especially in relay/convert paths, optional scalar fields must use pointer types with omitempty so explicit zero/false values are preserved and omitted-only fields remain nil.

**/*.go: Use common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, and common.GetJsonType from common/json.go for all JSON marshal/unmarshal operations; do not directly call encoding/json in business code.
Keep all database code fully compatible with SQLite, MySQL >= 5.7.8, and PostgreSQL >= 9.6; prefer GORM abstractions, avoid raw SQL unless cross-DB-safe, and use the provided DB-specific helpers/flags for quoting, booleans, and branching.
For request structs parsed from client JSON and re-marshaled to upstream providers, especially in relay/convert paths, use pointer types with omitempty for optional scalar fields so explicit zero/false values are preserved.

Files:

  • controller/relay.go
  • relay/channel/gemini/adaptor_responses.go
  • dto/openai_response.go
  • model/task.go
  • service/task_billing.go
  • relay/channel/openai/responses_via_chat.go
  • service/convert_test.go
  • relay/channel/advancedcustom/adaptor.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/gemini/adaptor_responses_test.go
  • relay/channel/gemini/relay_responses.go
  • relay/channel/gemini/relay_gemini_usage_test.go
  • model/main.go
  • relay/channel/advancedcustom/adaptor_test.go
  • dto/channel_settings.go
  • relay/channel/gemini/adaptor.go
  • relay/channel/task/doubao/constants.go
  • relay/channel/task/doubao/adaptor_media_protocol_test.go
  • service/convert.go
  • service/task_billing_test.go
  • relay/channel/claude/relay_claude_test.go
  • service/openaicompat/responses_to_chat.go
  • relay/channel/task/doubao/adaptor.go
  • service/openaicompat/responses_request_to_chat.go
  • service/openai_chat_responses_compat.go
  • model/log_test.go
  • model/usedata.go
  • service/openaicompat/chat_to_responses_response.go
  • model/log.go
  • service/openaicompat/responses_compat_test.go
relay/channel/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

When implementing a new relay channel, confirm whether the provider supports StreamOptions; if it does, add that channel to streamSupportedChannels.

When implementing a new channel, confirm whether the provider supports StreamOptions; if it does, add the channel to streamSupportedChannels.

Files:

  • relay/channel/gemini/adaptor_responses.go
  • relay/channel/openai/responses_via_chat.go
  • relay/channel/advancedcustom/adaptor.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/gemini/adaptor_responses_test.go
  • relay/channel/gemini/relay_responses.go
  • relay/channel/gemini/relay_gemini_usage_test.go
  • relay/channel/advancedcustom/adaptor_test.go
  • relay/channel/gemini/adaptor.go
  • relay/channel/task/doubao/constants.go
  • relay/channel/task/doubao/adaptor_media_protocol_test.go
  • relay/channel/claude/relay_claude_test.go
  • relay/channel/task/doubao/adaptor.go
web/default/src/features/**

📄 CodeRabbit inference engine (web/default/AGENTS.md)

功能模块应放在 src/features/<feature>/,并按需包含 components/lib/hooks/api.tstypes.tsconstants.ts 等;通用组件应放在 src/components/,通用工具与类型应放在 src/lib/

Files:

  • web/default/src/features/channels/types.ts
  • web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
  • web/default/src/features/usage-logs/types.ts
  • web/default/src/features/usage-logs/lib/utils.test.ts
  • web/default/src/features/usage-logs/constants.ts
  • web/default/src/features/channels/lib/advanced-custom.ts
  • web/default/src/features/usage-logs/lib/utils.ts
web/default/src/features/**/lib/**/*.ts

📄 CodeRabbit inference engine (web/default/AGENTS.md)

表单应使用 React Hook Form + Zod:在功能模块的 lib/ 下定义 schema,并用 z.infer 导出表单类型;useForm 应配合 @hookform/resolvers/zod 进行校验。

Files:

  • web/default/src/features/usage-logs/lib/utils.test.ts
  • web/default/src/features/channels/lib/advanced-custom.ts
  • web/default/src/features/usage-logs/lib/utils.ts
web/default/src/**/*.test.ts

📄 CodeRabbit inference engine (web/default/AGENTS.md)

工具函数与纯逻辑应优先编写单元测试;测试文件应命名为 *.test.ts

Files:

  • web/default/src/features/usage-logs/lib/utils.test.ts
web/default/src/features/**/constants.ts

📄 CodeRabbit inference engine (web/default/AGENTS.md)

各 feature 的 constants.ts 中,成功/错误/提示类消息常量应仅保存 i18n 键,展示时必须通过 t() 渲染;状态/选项的 label 应统一使用 labelKeylabel(二选一且同一 feature 内保持一致);新增此类常量时应同步登记翻译键。

Files:

  • web/default/src/features/usage-logs/constants.ts
🪛 GitHub Actions: CI / 1_Frontend checks.txt
web/default/src/features/usage-logs/lib/utils.ts

[error] 254-254: TypeScript (tsc -b) error TS2345: Argument of type 'string' is not assignable to parameter of type '"retry" | "error_retry" | "empty_retry"'.

🪛 GitHub Actions: CI / Frontend checks
web/default/src/features/usage-logs/lib/utils.ts

[error] 254-254: TypeScript (tsc -b) failed with TS2345: Argument of type 'string' is not assignable to parameter of type '"retry" | "error_retry" | "empty_retry"'.

🔇 Additional comments (48)
model/log.go (1)

57-58: LGTM!

Also applies to: 72-80, 94-129, 144-165, 553-614, 784-854

model/main.go (2)

30-49: LGTM!

Also applies to: 460-474


435-450: 🩺 Stability & Availability

Check startup ordering for the new retry columns. backfillLogRetryMarker reads is_error_retry and is_empty_retry; if it can run before those columns exist, upgraded databases will fail on the select.

model/log_test.go (1)

37-39: LGTM!

Also applies to: 52-55, 72-75, 182-232, 292-332

relay/channel/task/doubao/constants.go (1)

3-59: LGTM!

relay/channel/task/doubao/adaptor_media_protocol_test.go (2)

9-208: LGTM!

Also applies to: 244-271


210-223: 🎯 Functional Correctness

Initialize TaskAdaptor with the Seedance Media protocol here

This test should exercise the a.useSeedanceMediaProtocol() path in EstimateBilling; if the zero-value adaptor does not enable that mode, it won’t cover the Seedance-specific media handling.

controller/relay.go (1)

585-605: LGTM!

model/task.go (1)

100-110: LGTM!

service/task_billing.go (1)

180-191: LGTM!

Also applies to: 244-255

service/task_billing_test.go (1)

187-193: LGTM!

Also applies to: 336-381

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

701-703: LGTM!

Also applies to: 1541-1541, 1693-1693, 2219-2220

model/usedata.go (2)

141-161: 🗄️ Data Integrity & Integration

Check SUM() scan targets for cross-DB compatibility model/usedata.go:141-161
sum(count), sum(quota), and sum(token_used) can return NULL or driver-specific numeric types; ensure these aggregate results map safely into int fields across SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+.


100-139: 🗄️ Data Integrity & Integration

Potential duplicate rows in quota persistence. The lookup-then-create/update flow can race unless quota_data has a composite unique constraint over the full 8-field key or this is changed to an atomic upsert. The leftover commented-out block can be removed.

web/default/src/features/usage-logs/constants.ts (1)

69-71: LGTM!

Also applies to: 110-138

web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx (1)

63-65: LGTM!

web/default/src/features/usage-logs/types.ts (1)

42-43: LGTM!

web/default/src/routes/_authenticated/usage-logs/$section.tsx (1)

22-22: LGTM!

Also applies to: 34-34

web/default/src/i18n/locales/ru.json (1)

701-703: LGTM!

Also applies to: 1541-1541, 1693-1693, 2219-2220

web/default/src/i18n/locales/vi.json (1)

701-703: LGTM!

Also applies to: 1541-1541, 1693-1693, 2219-2220

web/default/src/features/usage-logs/lib/utils.ts (1)

36-37: LGTM!

Also applies to: 101-107, 118-134, 249-257

web/default/src/features/usage-logs/lib/utils.test.ts (1)

36-55: LGTM!

Also applies to: 138-202

web/default/src/i18n/static-keys.ts (1)

54-55: LGTM!

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

701-703: LGTM!

Also applies to: 1541-1541, 1693-1693, 2219-2220

web/default/src/i18n/locales/fr.json (1)

701-703: LGTM!

Also applies to: 1541-1541, 1693-1693, 2219-2220

web/default/src/i18n/locales/ja.json (2)

1541-1541: LGTM!

Also applies to: 1693-1693


701-703: LGTM!

Also applies to: 2219-2220

dto/channel_settings.go (1)

80-80: LGTM!

Also applies to: 165-167, 259-262

web/default/src/features/channels/types.ts (1)

144-151: LGTM!

service/openaicompat/responses_request_to_chat.go (2)

1-481: LGTM on the remaining conversion logic (messages, tools, tool_choice, response_format, media parts) — verified against the accompanying test suite.


89-107: 🎯 Functional Correctness

Check unsupported-field coverage for Responses requests. If dto.OpenAIResponsesRequest exposes output-shaping fields like include, reject them here or handle them explicitly instead of silently dropping them.

service/openai_chat_responses_compat.go (1)

12-34: LGTM!

relay/channel/advancedcustom/adaptor.go (1)

106-117: LGTM!

Also applies to: 231-235

relay/channel/advancedcustom/adaptor_test.go (1)

9-9: LGTM!

Also applies to: 372-408, 433-439

relay/channel/gemini/adaptor.go (1)

15-15: LGTM!

Also applies to: 242-251, 257-263

relay/channel/gemini/adaptor_responses_test.go (1)

1-100: LGTM! Test assumptions are consistent with the adaptor.go wiring reviewed in this cohort; the filtering behavior itself lives in adaptor_responses.go, which isn't part of this review batch.

web/default/src/features/channels/lib/advanced-custom.ts (1)

46-49: LGTM!

Also applies to: 585-587

dto/openai_response.go (1)

336-338: Correct fix — matches real OpenAI schema.

OpenAI's Responses API actually names this field reason (e.g. incomplete_details.reason === "max_output_tokens"), so renaming from Reasoning/reasoning to Reason/reason fixes a schema mismatch rather than introducing one.

Source

If the generated tokens reach the context window limit or the max_output_tokens value you've set, you'll receive a response with a status of incomplete and incomplete_details with reason set to max_output_tokens.

service/openaicompat/chat_to_responses_response.go (1)

61-120: LGTM!

Manually traced the streaming state machine against the aggregation test in responses_compat_test.go (created → text delta → tool added → tool arg delta → done events → completed) and the event ordering/count/usage all line up correctly.

Also applies to: 181-226, 228-243

service/openaicompat/responses_compat_test.go (1)

1-324: LGTM!

relay/channel/openai/responses_via_chat.go (1)

1-135: LGTM!

service/openaicompat/responses_to_chat.go (1)

59-82: 🎯 Functional Correctness

Need to confirm the custom_tool_call output shape before treating it as function and reading ArgumentsString() here.

relay/channel/gemini/adaptor_responses.go (1)

1-30: LGTM!

Also applies to: 55-97

relay/channel/gemini/relay_responses.go (1)

1-175: LGTM!

relay/channel/gemini/relay_gemini_usage_test.go (1)

8-8: LGTM!

Also applies to: 273-329, 379-394

relay/channel/claude/relay-claude.go (1)

464-479: LGTM!

relay/channel/claude/relay_claude_test.go (1)

8-8: LGTM!

Also applies to: 308-392

service/convert.go (1)

865-870: LGTM!

Comment thread model/log_test.go
Comment thread model/log.go
Comment thread relay/channel/gemini/adaptor_responses.go
Comment thread relay/channel/task/doubao/adaptor.go Outdated
Comment thread service/convert_test.go
Comment thread service/convert.go Outdated
Comment thread service/openaicompat/chat_to_responses_response.go Outdated
Comment thread service/openaicompat/responses_request_to_chat.go
Comment thread service/openaicompat/responses_request_to_chat.go
Comment thread service/openaicompat/responses_to_chat.go

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

Caution

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

⚠️ Outside diff range comments (1)
web/default/src/features/usage-logs/lib/utils.ts (1)

130-146: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve OR semantics for multiple retry subtype selections.

filterValues/typeValues can be arrays, but the retry branch returns from error_retry before checking empty_retry, and buildApiParams keeps only the first retry value. Selecting both retry subtypes can therefore omit empty-retry rows/stats. Treat subtype filters as additive, or collapse error_retry + empty_retry to the existing retry filter.

Proposed direction
-    if (filterValues.includes(LOG_TYPE_ERROR_RETRY_VALUE)) {
+    const retryFilterValues = filterValues.filter(isRetryLogFilterValue)
+    const other = parseLogOther(log.other)
+    const isEmptyRetry =
+      log.is_empty_retry === true || other?.empty_retry === true
+    const isErrorRetry =
+      log.is_error_retry === true ||
+      (!isEmptyRetry && (log.is_retry === true || other?.retry_log === true))
+
+    if (retryFilterValues.includes(LOG_TYPE_ERROR_RETRY_VALUE) && isErrorRetry) {
+      return true
+    }
+    if (retryFilterValues.includes(LOG_TYPE_EMPTY_RETRY_VALUE) && isEmptyRetry) {
+      return true
+    }
+    if (retryFilterValues.includes(LOG_TYPE_RETRY_VALUE)) {
+      return log.is_retry === true || isErrorRetry || isEmptyRetry
+    }
+    return false
-      if (log.is_error_retry === true) return true
-      if (log.is_empty_retry === true) return false
-      const other = parseLogOther(log.other)
-      if (other?.empty_retry === true) return false
-      return other?.retry_log === true
-    }
-    const retryFilter = typeValues.find(isRetryLogFilterValue)
+    const retryFilters = typeValues.filter(isRetryLogFilterValue)
+    const retryFilter =
+      retryFilters.includes(LOG_TYPE_RETRY_VALUE) || retryFilters.length > 1
+        ? LOG_TYPE_RETRY_VALUE
+        : retryFilters[0]

Also applies to: 261-263

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/usage-logs/lib/utils.ts` around lines 130 - 146,
Preserve OR semantics in the retry filter handling: the usage-log filtering
logic in the retry branch of utils.ts currently short-circuits on error_retry
and buildApiParams only forwards the first retry subtype, so selecting both
subtypes can hide empty-retry results. Update the filter handling around the
retry subtype checks in the utility function and the parameter builder so
multiple retry selections are treated additively, or normalize error_retry plus
empty_retry into the existing retry filter before applying the API params.
🤖 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.

Outside diff comments:
In `@web/default/src/features/usage-logs/lib/utils.ts`:
- Around line 130-146: Preserve OR semantics in the retry filter handling: the
usage-log filtering logic in the retry branch of utils.ts currently
short-circuits on error_retry and buildApiParams only forwards the first retry
subtype, so selecting both subtypes can hide empty-retry results. Update the
filter handling around the retry subtype checks in the utility function and the
parameter builder so multiple retry selections are treated additively, or
normalize error_retry plus empty_retry into the existing retry filter before
applying the API params.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ed120bbf-7003-4fc2-aff1-aadebe378007

📥 Commits

Reviewing files that changed from the base of the PR and between 3fc339e and 3abcc4f.

📒 Files selected for processing (2)
  • tools/jsonwrapcheck/allowlist.txt
  • web/default/src/features/usage-logs/lib/utils.ts
💤 Files with no reviewable changes (1)
  • tools/jsonwrapcheck/allowlist.txt
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Backend checks
🧰 Additional context used
📓 Path-based instructions (4)
web/default/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/default/AGENTS.md)

web/default/src/**/*.{ts,tsx}: 前端页面文本与组件内文案必须支持 i18n:React 组件中应使用 useTranslation() 取得 t,并通过 t() 渲染用户可见文本;子组件也应自行使用 useTranslation() 保持独立性。
禁止使用两层及以上嵌套三元表达式;复杂逻辑应改用 if-else、提前返回或抽取函数。
控制函数圈复杂度,复杂逻辑应拆成更小的函数;变量与函数命名应有意义并遵循驼峰等常规命名约定。
TypeScript 代码应避免使用 any,优先使用具体类型或 unknown;参数与返回值应显式标注类型;仅类型用途的导入应使用 import type
修改 TypeScript 或 TSX 代码后必须执行类型检查,并修复所有类型错误,不得遗留。
对象非必要不要解构,尤其是组件 props;优先直接使用 props.xxx 以保持代码清晰。
组件应使用函数式组件与 Hooks,遵循单一职责;组件 props 必须有明确类型(接口或类型别名)。
单文件超过约 200 行时应考虑拆分子组件或抽取自定义 Hooks;类型定义可与组件同文件或放在同模块的 types 中。
在 React 中应合理使用 useMemouseCallbackReact.memo,避免在渲染路径中创建新对象或数组;必要时进行代码分割与动态 import
React Query 的数据获取应使用 useQuery、变更应使用 useMutation;每个查询需配置唯一 queryKey,并在成功后对相关查询执行 invalidateQueries;服务端错误应统一交给 handleServerError
Axios 请求应使用项目统一的 api 实例;GET 请求默认去重,特殊请求可显式关闭;认证与通用错误应在拦截器中统一处理。
服务端错误应统一使用 handleServerError,展示层应使用 toast.error 等统一方式;文案需走 i18n;路由级错误应由 errorComponent 承接;表单错误应通过 form.setError 等方式映射到字段。
样式应以 Tailwind 工具类为主,动态类名使用 cn() 合并;非动态场景避免内联样式;响应式采用移动优先与 Tailwind 断点,主题与暗色模式通过 CSS 变量与 dark: 处理。
应使用语义化 HTML、正确关联 label 与输入、保证键盘可操作与合理焦点顺序;必要时添加 ARIA 属性,装饰性图标应使用 aria-hidden="true"
认证与权限应在路由与接口层校验;前后端都应做数据校验(如 Zod);敏感信息不得落前端存储;避免使用 dangerouslySetInnerHTML;跨域与 Cookie 需配合 withCredentials 并按后端要求处理 CSRF。
组件测试应使用 React Testing Library,关注交互与行为,避免测试实现细节;关键流程可补充集成与 E2E 测试。
环境变量应通过 .env 读取,并使用 VITE_ 前缀;代码中不得硬编码密钥。

Files:

  • web/default/src/features/usage-logs/lib/utils.ts
web/default/src/features/**/lib/**/*.ts

📄 CodeRabbit inference engine (web/default/AGENTS.md)

表单应使用 React Hook Form + Zod:在功能模块的 lib/ 下定义 schema,并用 z.infer 导出表单类型;useForm 应配合 @hookform/resolvers/zod 进行校验。

Files:

  • web/default/src/features/usage-logs/lib/utils.ts
web/default/src/features/**

📄 CodeRabbit inference engine (web/default/AGENTS.md)

功能模块应放在 src/features/<feature>/,并按需包含 components/lib/hooks/api.tstypes.tsconstants.ts 等;通用组件应放在 src/components/,通用工具与类型应放在 src/lib/

Files:

  • web/default/src/features/usage-logs/lib/utils.ts
web/default/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Use bun as the preferred package manager and script runner for the frontend in web/default/ (bun install, bun run dev, bun run build, and bun run i18n:*).

Files:

  • web/default/src/features/usage-logs/lib/utils.ts
🔇 Additional comments (1)
web/default/src/features/usage-logs/lib/utils.ts (1)

48-58: LGTM!

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

Caution

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

⚠️ Outside diff range comments (1)
relay/channel/task/doubao/adaptor.go (1)

167-190: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Silent error swallowing may skew Seedance billing.

Both conversion errors here (convertToRequestPayload at Line 174-175 and convertToGenericMediaRequest at Line 183) are ignored — on failure, the function silently falls back to metadata-only resolution/hasVideo with no logging. Since this feeds the video_input billing multiplier in EstimateBilling, a masked conversion failure could silently mis-price Seedance video tasks (e.g., missing the video-input surcharge) without any operator visibility.

Consider at least logging the error so billing anomalies caused by conversion failures are diagnosable.

🤖 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 `@relay/channel/task/doubao/adaptor.go` around lines 167 - 190, The billing
input resolver in TaskAdaptor.resolveSeedanceBillingInputs is swallowing
failures from convertToRequestPayload and convertToGenericMediaRequest, which
can hide Seedance billing mismatches. Update the two conversion call sites to
log the returned error with enough context, while preserving the existing
fallback behavior for resolution and hasVideo. Keep the fix localized to
resolveSeedanceBillingInputs and use the existing helper names so conversion
failures are visible when EstimateBilling derives the video_input multiplier.
🤖 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.

Outside diff comments:
In `@relay/channel/task/doubao/adaptor.go`:
- Around line 167-190: The billing input resolver in
TaskAdaptor.resolveSeedanceBillingInputs is swallowing failures from
convertToRequestPayload and convertToGenericMediaRequest, which can hide
Seedance billing mismatches. Update the two conversion call sites to log the
returned error with enough context, while preserving the existing fallback
behavior for resolution and hasVideo. Keep the fix localized to
resolveSeedanceBillingInputs and use the existing helper names so conversion
failures are visible when EstimateBilling derives the video_input multiplier.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 324c9cc9-b637-46da-ad89-1289881d88a5

📥 Commits

Reviewing files that changed from the base of the PR and between 3abcc4f and 4e24e67.

📒 Files selected for processing (12)
  • model/log.go
  • model/log_test.go
  • relay/channel/gemini/adaptor_responses.go
  • relay/channel/gemini/adaptor_responses_test.go
  • relay/channel/task/doubao/adaptor.go
  • relay/channel/task/doubao/adaptor_media_protocol_test.go
  • service/convert.go
  • service/convert_test.go
  • service/openaicompat/chat_to_responses_response.go
  • service/openaicompat/responses_compat_test.go
  • service/openaicompat/responses_request_to_chat.go
  • service/openaicompat/responses_to_chat.go
📜 Review details
⚠️ CI failures not shown inline (2)

GitHub Actions: CI / Backend checks: v1.0.4-preview.1

Conclusion: failure

View job details

##[group]Run go run ./tools/jsonwrapcheck
 �[36;1mgo run ./tools/jsonwrapcheck�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 stale json wrapper allowlist entries:
   service/convert.go|ResponseOpenAI2Gemini|Unmarshal|***REDACTED***
   service/convert.go|StreamResponseOpenAI2Gemini|Unmarshal|***REDACTED***
 Regenerate the baseline with: go run ./tools/jsonwrapcheck -list > tools/jsonwrapcheck/allowlist.txt
 exit status 1
 ##[error]Process completed with exit code 1.

GitHub Actions: CI / 1_Backend checks.txt: v1.0.4-preview.1

Conclusion: failure

View job details

##[group]Run go run ./tools/jsonwrapcheck
 �[36;1mgo run ./tools/jsonwrapcheck�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 stale json wrapper allowlist entries:
   service/convert.go|ResponseOpenAI2Gemini|Unmarshal|***REDACTED***
   service/convert.go|StreamResponseOpenAI2Gemini|Unmarshal|***REDACTED***
 Regenerate the baseline with: go run ./tools/jsonwrapcheck -list > tools/jsonwrapcheck/allowlist.txt
 exit status 1
 ##[error]Process completed with exit code 1.
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: All JSON marshal/unmarshal operations in Go business code must use the wrappers in common/json.go (common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, common.GetJsonType) instead of directly calling encoding/json; encoding/json types like json.RawMessage and json.Number may still be used as types.
All database code must be compatible with SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+ simultaneously: prefer GORM methods over raw SQL, avoid direct AUTO_INCREMENT/SERIAL, use cross-DB quoting/boolean helpers when raw SQL is unavoidable, avoid DB-specific functions/operators without fallbacks, and ensure migrations work on all three databases.
For request structs parsed from client JSON and later re-marshaled to upstream providers, especially in relay/convert paths, optional scalar fields must use pointer types with omitempty so explicit zero/false values are preserved and omitted-only fields remain nil.

**/*.go: Use common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, and common.GetJsonType from common/json.go for all JSON marshal/unmarshal operations; do not directly call encoding/json in business code.
Keep all database code fully compatible with SQLite, MySQL >= 5.7.8, and PostgreSQL >= 9.6; prefer GORM abstractions, avoid raw SQL unless cross-DB-safe, and use the provided DB-specific helpers/flags for quoting, booleans, and branching.
For request structs parsed from client JSON and re-marshaled to upstream providers, especially in relay/convert paths, use pointer types with omitempty for optional scalar fields so explicit zero/false values are preserved.

Files:

  • relay/channel/gemini/adaptor_responses_test.go
  • service/convert_test.go
  • relay/channel/gemini/adaptor_responses.go
  • relay/channel/task/doubao/adaptor.go
  • service/convert.go
  • service/openaicompat/responses_compat_test.go
  • model/log_test.go
  • relay/channel/task/doubao/adaptor_media_protocol_test.go
  • model/log.go
  • service/openaicompat/responses_to_chat.go
  • service/openaicompat/responses_request_to_chat.go
  • service/openaicompat/chat_to_responses_response.go
relay/channel/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

When implementing a new relay channel, confirm whether the provider supports StreamOptions; if it does, add that channel to streamSupportedChannels.

When implementing a new channel, confirm whether the provider supports StreamOptions; if it does, add the channel to streamSupportedChannels.

Files:

  • relay/channel/gemini/adaptor_responses_test.go
  • relay/channel/gemini/adaptor_responses.go
  • relay/channel/task/doubao/adaptor.go
  • relay/channel/task/doubao/adaptor_media_protocol_test.go
🪛 GitHub Actions: CI / 1_Backend checks.txt
service/convert.go

[error] 1-1: json wrapper allowlist contains stale entry: service/convert.go|ResponseOpenAI2Gemini|Unmarshal|REDACTED


[error] 1-1: json wrapper allowlist contains stale entry: service/convert.go|StreamResponseOpenAI2Gemini|Unmarshal|REDACTED

🔇 Additional comments (12)
relay/channel/gemini/adaptor_responses_test.go (1)

1-130: LGTM!

service/convert_test.go (1)

53-64: LGTM! Closes the previously flagged Gemini malformed-arguments coverage gap.

relay/channel/gemini/adaptor_responses.go (1)

33-55: LGTM! Properly surfaces an error for unsupported tool types instead of silently dropping them, addressing the earlier major finding.

relay/channel/task/doubao/adaptor.go (1)

56-64: LGTM! Resolution/Ratio now use *string with omitempty, correctly preserving explicit empty values while omitting truly-absent fields, addressing the prior major finding.

service/convert.go (1)

607-650: LGTM! The shared parseToolCallArguments helper cleanly removes the duplicated Claude/Gemini argument-parsing logic and standardizes on common.Unmarshal, resolving the earlier duplication nitpick.

Also applies to: 871-897, 900-991

relay/channel/task/doubao/adaptor_media_protocol_test.go (1)

244-298: LGTM! Good regression coverage confirming explicit empty optional strings are preserved as distinct from omitted fields.

service/openaicompat/responses_compat_test.go (1)

191-230: LGTM!

Also applies to: 279-329

model/log_test.go (1)

85-95: LGTM!

Also applies to: 106-116

model/log.go (1)

241-256: LGTM!

service/openaicompat/responses_to_chat.go (1)

120-146: LGTM!

service/openaicompat/responses_request_to_chat.go (1)

160-166: LGTM!

Also applies to: 252-268

service/openaicompat/chat_to_responses_response.go (1)

111-113: LGTM!

Also applies to: 531-552

@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: 3

🤖 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 `@relay/channel/task/taskcommon/request_body.go`:
- Around line 58-82: In BuildConfiguredTaskRequestBody, the media-generation
path is re-parsing the request body in both configuredTaskRequest and
buildMediaGenerationTaskBody, which creates redundant work and depends on
reusable-body behavior. Update the TaskRequestBodyModeMediaGeneration flow to
parse the body once into a raw map/object, then pass that parsed value through
to both the configuredTaskRequest and buildMediaGenerationTaskBody steps. Adjust
the related helpers in configuredTaskRequest and buildMediaGenerationTaskBody so
they share the same parsed input instead of calling common.UnmarshalBodyReusable
twice.
- Around line 289-313: Silent overwrite of existing non-map values in nested
mappings: setMappedOutputValue currently replaces any intermediate non-map at a
dotted path with a new map, which can discard RequestBodyDefaults data without
warning. Update setMappedOutputValue to detect when current[part] exists but is
not a map[string]any, and stop processing or return an error/log instead of
overwriting it; keep the behavior only when the intermediate key is missing or
already a map. Refer to setMappedOutputValue and the RequestBodyDefaults mapping
flow when implementing the conflict check.
- Around line 370-404: applyMediaRawFields is only copying a fixed set of media
request keys and currently drops any unknown top-level fields from the raw body.
Update applyMediaRawFields to preserve unrecognized entries by adding them to
payload.Extra, similar to the default handling in applyMediaMetadata, while
keeping the existing typed parsing for known fields like model, prompt,
duration_seconds, with_audio, and reference_images. Use payload.Extra and
setPayloadExtra as the main insertion points so provider-specific top-level
parameters are not silently lost.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 4e0451a8-d523-4b42-8c57-de0c0483bb95

📥 Commits

Reviewing files that changed from the base of the PR and between 1e7e2f1 and f463b51.

📒 Files selected for processing (26)
  • dto/channel_settings.go
  • model/log.go
  • model/log_test.go
  • relay/channel/api_request.go
  • relay/channel/api_request_test.go
  • relay/channel/openai/chat_via_responses.go
  • relay/channel/openai/empty_completion_test.go
  • relay/channel/openai/responses_via_chat.go
  • relay/channel/task/doubao/adaptor.go
  • relay/channel/task/doubao/adaptor_billing_test.go
  • relay/channel/task/doubao/media_protocol.go
  • relay/channel/task/taskcommon/path_config.go
  • relay/channel/task/taskcommon/path_config_test.go
  • relay/channel/task/taskcommon/protocol_config.go
  • relay/channel/task/taskcommon/request_body.go
  • relay/relay_task.go
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/lib/channel-form.ts
  • web/default/src/features/channels/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/tests/channel-form.test.ts
💤 Files with no reviewable changes (2)
  • relay/channel/task/doubao/media_protocol.go
  • model/log.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: All JSON marshal/unmarshal operations in Go business code must use the wrappers in common/json.go (common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, common.GetJsonType) instead of directly calling encoding/json; encoding/json types like json.RawMessage and json.Number may still be used as types.
All database code must be compatible with SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+ simultaneously: prefer GORM methods over raw SQL, avoid direct AUTO_INCREMENT/SERIAL, use cross-DB quoting/boolean helpers when raw SQL is unavoidable, avoid DB-specific functions/operators without fallbacks, and ensure migrations work on all three databases.
For request structs parsed from client JSON and later re-marshaled to upstream providers, especially in relay/convert paths, optional scalar fields must use pointer types with omitempty so explicit zero/false values are preserved and omitted-only fields remain nil.

**/*.go: Use common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, and common.GetJsonType from common/json.go for all JSON marshal/unmarshal operations; do not directly call encoding/json in business code.
Keep all database code fully compatible with SQLite, MySQL >= 5.7.8, and PostgreSQL >= 9.6; prefer GORM abstractions, avoid raw SQL unless cross-DB-safe, and use the provided DB-specific helpers/flags for quoting, booleans, and branching.
For request structs parsed from client JSON and re-marshaled to upstream providers, especially in relay/convert paths, use pointer types with omitempty for optional scalar fields so explicit zero/false values are preserved.

Files:

  • relay/channel/openai/empty_completion_test.go
  • relay/relay_task.go
  • relay/channel/openai/chat_via_responses.go
  • relay/channel/api_request.go
  • relay/channel/openai/responses_via_chat.go
  • relay/channel/api_request_test.go
  • relay/channel/task/taskcommon/path_config.go
  • relay/channel/task/doubao/adaptor_billing_test.go
  • dto/channel_settings.go
  • relay/channel/task/taskcommon/protocol_config.go
  • model/log_test.go
  • relay/channel/task/taskcommon/request_body.go
  • relay/channel/task/taskcommon/path_config_test.go
  • relay/channel/task/doubao/adaptor.go
relay/channel/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

When implementing a new relay channel, confirm whether the provider supports StreamOptions; if it does, add that channel to streamSupportedChannels.

When implementing a new channel, confirm whether the provider supports StreamOptions; if it does, add the channel to streamSupportedChannels.

Files:

  • relay/channel/openai/empty_completion_test.go
  • relay/channel/openai/chat_via_responses.go
  • relay/channel/api_request.go
  • relay/channel/openai/responses_via_chat.go
  • relay/channel/api_request_test.go
  • relay/channel/task/taskcommon/path_config.go
  • relay/channel/task/doubao/adaptor_billing_test.go
  • relay/channel/task/taskcommon/protocol_config.go
  • relay/channel/task/taskcommon/request_body.go
  • relay/channel/task/taskcommon/path_config_test.go
  • relay/channel/task/doubao/adaptor.go
web/default/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Use bun as the preferred package manager and script runner for the frontend in web/default/ (bun install, bun run dev, bun run build, and bun run i18n:*).

Files:

  • web/default/tests/channel-form.test.ts
  • web/default/src/features/channels/types.ts
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/features/channels/lib/channel-form.ts
web/default/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/default/AGENTS.md)

web/default/src/**/*.{ts,tsx}: 前端页面文本与组件内文案必须支持 i18n:React 组件中应使用 useTranslation() 取得 t,并通过 t() 渲染用户可见文本;子组件也应自行使用 useTranslation() 保持独立性。
禁止使用两层及以上嵌套三元表达式;复杂逻辑应改用 if-else、提前返回或抽取函数。
控制函数圈复杂度,复杂逻辑应拆成更小的函数;变量与函数命名应有意义并遵循驼峰等常规命名约定。
TypeScript 代码应避免使用 any,优先使用具体类型或 unknown;参数与返回值应显式标注类型;仅类型用途的导入应使用 import type
修改 TypeScript 或 TSX 代码后必须执行类型检查,并修复所有类型错误,不得遗留。
对象非必要不要解构,尤其是组件 props;优先直接使用 props.xxx 以保持代码清晰。
组件应使用函数式组件与 Hooks,遵循单一职责;组件 props 必须有明确类型(接口或类型别名)。
单文件超过约 200 行时应考虑拆分子组件或抽取自定义 Hooks;类型定义可与组件同文件或放在同模块的 types 中。
在 React 中应合理使用 useMemouseCallbackReact.memo,避免在渲染路径中创建新对象或数组;必要时进行代码分割与动态 import
React Query 的数据获取应使用 useQuery、变更应使用 useMutation;每个查询需配置唯一 queryKey,并在成功后对相关查询执行 invalidateQueries;服务端错误应统一交给 handleServerError
Axios 请求应使用项目统一的 api 实例;GET 请求默认去重,特殊请求可显式关闭;认证与通用错误应在拦截器中统一处理。
服务端错误应统一使用 handleServerError,展示层应使用 toast.error 等统一方式;文案需走 i18n;路由级错误应由 errorComponent 承接;表单错误应通过 form.setError 等方式映射到字段。
样式应以 Tailwind 工具类为主,动态类名使用 cn() 合并;非动态场景避免内联样式;响应式采用移动优先与 Tailwind 断点,主题与暗色模式通过 CSS 变量与 dark: 处理。
应使用语义化 HTML、正确关联 label 与输入、保证键盘可操作与合理焦点顺序;必要时添加 ARIA 属性,装饰性图标应使用 aria-hidden="true"
认证与权限应在路由与接口层校验;前后端都应做数据校验(如 Zod);敏感信息不得落前端存储;避免使用 dangerouslySetInnerHTML;跨域与 Cookie 需配合 withCredentials 并按后端要求处理 CSRF。
组件测试应使用 React Testing Library,关注交互与行为,避免测试实现细节;关键流程可补充集成与 E2E 测试。
环境变量应通过 .env 读取,并使用 VITE_ 前缀;代码中不得硬编码密钥。

Files:

  • web/default/src/features/channels/types.ts
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/lib/channel-form.ts
web/default/src/features/**

📄 CodeRabbit inference engine (web/default/AGENTS.md)

功能模块应放在 src/features/<feature>/,并按需包含 components/lib/hooks/api.tstypes.tsconstants.ts 等;通用组件应放在 src/components/,通用工具与类型应放在 src/lib/

Files:

  • web/default/src/features/channels/types.ts
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/lib/channel-form.ts
web/default/src/features/**/lib/**/*.ts

📄 CodeRabbit inference engine (web/default/AGENTS.md)

表单应使用 React Hook Form + Zod:在功能模块的 lib/ 下定义 schema,并用 z.infer 导出表单类型;useForm 应配合 @hookform/resolvers/zod 进行校验。

Files:

  • web/default/src/features/channels/lib/channel-form.ts
🔇 Additional comments (39)
relay/channel/task/doubao/adaptor.go (2)

57-65: Pointer-type fix from prior review confirmed.

payload.Resolution/SafetyIdentifier/Priority are now used as pointer types throughout (resolveBillingInputs, TestConvertToRequestPayloadPreservesDoubaoVideoFields), addressing the previously flagged guideline violation.


107-199: LGTM!

Also applies to: 201-266, 268-289, 297-329, 332-355, 365-399, 401-447, 449-505, 506-513

relay/channel/openai/empty_completion_test.go (1)

357-400: LGTM!

relay/channel/openai/chat_via_responses.go (1)

16-16: LGTM!

Also applies to: 485-485

relay/channel/api_request.go (1)

535-545: LGTM!

Also applies to: 547-576, 578-586

relay/channel/openai/responses_via_chat.go (1)

61-138: LGTM!

relay/channel/api_request_test.go (1)

4-14: LGTM!

Also applies to: 15-35, 36-53, 55-73

web/default/src/i18n/locales/vi.json (1)

154-154: LGTM!

Also applies to: 702-704, 830-830, 1543-1543, 1695-1695, 1911-1911, 2222-2223, 2364-2365, 2590-2590, 3198-3198, 3722-3728

relay/relay_task.go (1)

281-295: 🩺 Stability & Availability

No panic risk here. buildFieldMappingTaskBody and configuredTaskRequest both handle a nil req by rereading the reusable body, and buildMediaGenerationTaskBody returns a regular error if no request can be obtained. The helper no longer depends on task_request being stored ahead of time.

			> Likely an incorrect or invalid review comment.
web/default/tests/channel-form.test.ts (1)

1-231: LGTM!

web/default/src/features/channels/types.ts (1)

114-131: LGTM!

Also applies to: 156-156

web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx (1)

212-238: LGTM!

Also applies to: 398-398, 3086-3188

web/default/src/i18n/locales/ja.json (1)

154-154: LGTM! All new/changed keys correspond to t() calls added in channel-mutate-drawer.tsx and are translated consistently.

Also applies to: 830-830, 1543-1543, 1695-1695, 1911-1911, 2222-2223, 2364-2365, 2590-2590, 3198-3198, 3722-3728

web/default/src/i18n/locales/ru.json (1)

154-154: LGTM! Consistent with the corresponding ja.json updates and code usage.

Also applies to: 830-830, 1543-1543, 1695-1695, 1911-1911, 2222-2223, 2364-2365, 2590-2590, 3198-3198, 3722-3728

relay/channel/task/taskcommon/path_config.go (3)

27-29: 🎯 Functional Correctness | ⚡ Quick win

Error message omits a valid task_protocol value.

UseConfiguredTaskProtocol accepts both TaskProtocolGenericVideo and TaskProtocolLegacySeedanceMedia, but this error only mentions the former, which can mislead admins fixing an invalid task_protocol.

🐛 Proposed fix
 	if strings.TrimSpace(settings.TaskProtocol) != "" && !UseConfiguredTaskProtocol(settings) {
-		return fmt.Errorf("task_protocol must be %s", TaskProtocolGenericVideo)
+		return fmt.Errorf("task_protocol must be %s or %s", TaskProtocolGenericVideo, TaskProtocolLegacySeedanceMedia)
 	}

121-158: LGTM!


65-75: LGTM!

Also applies to: 91-107

relay/channel/task/taskcommon/path_config_test.go (1)

4-9: LGTM! Comprehensive new test coverage for the configured task protocol request-body modes and URL deduplication behavior.

Also applies to: 57-72, 81-425, 493-526, 528-544

web/default/src/features/channels/lib/channel-form.ts (12)

543-567: 📐 Maintainability & Code Quality

Callers must guard against JSON.parse throwing.

parseOptionalJsonObjectInput and parseOptionalStringRecordInput call parseOptionalJson, which runs a bare JSON.parse and throws a SyntaxError on invalid input. They are correctly wrapped in a try/catch at the superRefine call site (Line 415-424), but are called unguarded later in buildSettingsJSON (Line 1013-1018). See the follow-up comment there.


1051-1064: 🎯 Functional Correctness

See the related comment on Lines 742-766 regarding legacy protocol identity loss.

This is where task_protocol is unconditionally set to VIDEO_TASK_PROTOCOL, which is the site of the concern raised above about overwriting the legacy protocol value on save.


42-67: LGTM!


264-277: LGTM!


408-433: 📐 Maintainability & Code Quality | 💤 Low value

Consider surfacing the issue on both fields.

The required-issue is only attached to video_task_request_body_mapping, even though video_task_request_body_defaults can also satisfy the requirement. If a user only touches the defaults field, the error will still point to the mapping field, which can be confusing.


494-496: LGTM!


618-636: 📐 Maintainability & Code Quality | ⚡ Quick win

Missing explicit return type annotation.

normalizeVideoTaskRequestBodyMode has no declared return type (unlike the other new helpers in this file, e.g. stringifyOptionalJsonObject, parseOptionalJsonObjectInput). Per coding guidelines, function return values should be explicitly annotated.

As per coding guidelines, "参数与返回值应显式标注类型".

🔧 Proposed fix
 function normalizeVideoTaskRequestBodyMode(
   value: unknown,
   taskProtocol = '',
   hasImplicitFieldMappingConfig = false
-) {
+): VideoTaskRequestBodyMode {

Source: Coding guidelines


691-694: LGTM!


865-867: LGTM!


1013-1023: 🩺 Stability & Availability | ⚡ Quick win

Unguarded JSON parsing can throw during form submission.

parseOptionalStringRecordInput and parseOptionalJsonObjectInput internally call JSON.parse via parseOptionalJson and will throw a SyntaxError on malformed input. Elsewhere in this file (e.g. Lines 658-671, 710-816) JSON.parse calls are wrapped in try/catch, and the superRefine validator (Lines 414-425) also wraps these same helper calls in a try/catch. Here in buildSettingsJSON they're called directly with no guard. Since channelFormSchema's refinements should reject malformed JSON before submission, actual risk is low in the normal React Hook Form flow, but any direct/programmatic call to buildSettingsJSON/transformFormDataToCreatePayload bypassing validation would throw an uncaught exception instead of failing gracefully.

🛡️ Proposed fix
   if (enableVideoTaskProtocol) {
-    const requestBodyMapping = parseOptionalStringRecordInput(
-      formData.video_task_request_body_mapping
-    )
-    const requestBodyDefaults = parseOptionalJsonObjectInput(
-      formData.video_task_request_body_defaults
-    )
+    let requestBodyMapping: Record<string, string> | undefined
+    let requestBodyDefaults: Record<string, unknown> | undefined
+    try {
+      requestBodyMapping = parseOptionalStringRecordInput(
+        formData.video_task_request_body_mapping
+      )
+      requestBodyDefaults = parseOptionalJsonObjectInput(
+        formData.video_task_request_body_defaults
+      )
+    } catch (error) {
+      // eslint-disable-next-line no-console
+      console.error('Failed to parse video task request body config:', error)
+    }

1084-1102: 📐 Maintainability & Code Quality | 💤 Low value

Redundant delete of task_protocol.

Line 1084 deletes settingsObj.task_protocol inside the enableVideoTaskPathOverride branch, but Line 1100-1102 already performs the same delete unconditionally whenever !enableVideoTaskProtocol, which covers this branch too. The Line 1084 delete is now dead code.

🧹 Proposed cleanup
   } else if (enableVideoTaskPathOverride) {
-    if ('task_protocol' in settingsObj) {
-      delete settingsObj.task_protocol
-    }
     settingsObj.task_protocol_config = {

742-766: 🎯 Functional Correctness

Legacy protocol is normalized on save. The backend treats seedance_official_media and generic_video_task as the same configured protocol, and media_generation carries the legacy behavior here.

			> Likely an incorrect or invalid review comment.
web/default/src/i18n/locales/en.json (1)

154-154: LGTM!

Also applies to: 702-704, 830-830, 1543-1543, 1695-1695, 1911-1911, 2222-2223, 2364-2365, 2590-2590, 3198-3198, 3722-3728

relay/channel/task/doubao/adaptor_billing_test.go (1)

143-157: LGTM!

Also applies to: 159-180, 199-227, 255-284, 286-304, 305-321

web/default/src/i18n/locales/fr.json (2)

2364-2365: 📐 Maintainability & Code Quality | ⚡ Quick win

Fix missing accents/typos in newly added French strings.

Lines 2364, 2365, 3722, and 3725 drop diacritics ("requete"/"defaut" instead of "requête"/"défaut"), inconsistent with sibling strings in the same batch (e.g. line 3723-3724, 3726-3728) that are correctly accented. Line 2365 also uses "fusionne"/"mappes" instead of the correct participle forms "fusionné"/"mappés".

🇫🇷 Proposed fix
-    "JSON object mapping upstream request fields to source request paths": "Objet JSON associant les champs de requete amont aux chemins de la requete source",
-    "JSON object merged into the upstream request body before mapped fields": "Objet JSON fusionne dans le corps de requete amont avant les champs mappes",
+    "JSON object mapping upstream request fields to source request paths": "Objet JSON associant les champs de requête amont aux chemins de la requête source",
+    "JSON object merged into the upstream request body before mapped fields": "Objet JSON fusionné dans le corps de requête amont avant les champs mappés",
-    "Request Body Defaults": "Valeurs par defaut du corps de requete",
+    "Request Body Defaults": "Valeurs par défaut du corps de requête",
     "Request Body Disk Cache": "Cache disque du corps de requête",
     "Request Body Field": "Champ du corps de requête",
-    "Request Body Field Mapping": "Mappage des champs du corps de requete",
+    "Request Body Field Mapping": "Mappage des champs du corps de requête",

Also applies to: 3722-3728


154-154: LGTM!

Also applies to: 702-704, 830-830, 1543-1543, 1695-1695, 1911-1911, 2222-2223, 2590-2590, 3198-3198

dto/channel_settings.go (1)

55-69: LGTM!

relay/channel/task/taskcommon/protocol_config.go (2)

22-26: LGTM!

Also applies to: 52-69, 76-151, 153-159, 183-187, 224-228, 284-297


161-181: 🩺 Stability & Availability

No aliasing issue here

GetOtherSettings() returns a fresh settings struct, buildFieldMappingTaskBody() deep-clones RequestBodyDefaults before any writes, and RequestBodyMapping only contains strings. The shallow top-level copy here is harmless.

			> Likely an incorrect or invalid review comment.
model/log_test.go (1)

134-167: LGTM!

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

154-154: LGTM!

Also applies to: 830-830, 1543-1543, 1695-1695, 1911-1911, 2364-2365, 2590-2590, 3198-3198, 3722-3728

Comment on lines +58 to +82
func BuildConfiguredTaskRequestBody(c *gin.Context, info *relaycommon.RelayInfo, req *relaycommon.TaskSubmitReq) (io.Reader, bool, error) {
if info == nil || info.ChannelMeta == nil || !UseConfiguredTaskProtocol(info.ChannelOtherSettings) {
return nil, false, nil
}
cfg := EffectiveTaskProtocolConfig(info.ChannelOtherSettings)
switch cfg.RequestBodyMode {
case TaskRequestBodyModeAdapter:
return nil, false, nil
case TaskRequestBodyModePassThrough:
body, err := buildPassThroughTaskBody(c, info)
return body, true, err
case TaskRequestBodyModeFieldMapping:
body, err := buildFieldMappingTaskBody(c, info, req, cfg.RequestBodyMapping, cfg.RequestBodyDefaults)
return body, true, err
case TaskRequestBodyModeMediaGeneration:
taskReq, err := configuredTaskRequest(c, req)
if err != nil {
return nil, true, err
}
body, err := buildMediaGenerationTaskBody(c, info, taskReq)
return body, true, err
default:
return nil, false, fmt.Errorf("unsupported task request body mode: %s", cfg.RequestBodyMode)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Body re-parsed multiple times per request for media-generation mode.

For TaskRequestBodyModeMediaGeneration, the body is unmarshaled once in configuredTaskRequest (only if req was nil) and again in buildMediaGenerationTaskBody via common.UnmarshalBodyReusable(c, &raw). This relies on the reusable-body buffer working correctly across repeated reads; consider consolidating into a single raw-map parse passed to both steps to reduce redundant work and coupling to buffer-reuse correctness.

Also applies to: 134-150

🤖 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 `@relay/channel/task/taskcommon/request_body.go` around lines 58 - 82, In
BuildConfiguredTaskRequestBody, the media-generation path is re-parsing the
request body in both configuredTaskRequest and buildMediaGenerationTaskBody,
which creates redundant work and depends on reusable-body behavior. Update the
TaskRequestBodyModeMediaGeneration flow to parse the body once into a raw
map/object, then pass that parsed value through to both the
configuredTaskRequest and buildMediaGenerationTaskBody steps. Adjust the related
helpers in configuredTaskRequest and buildMediaGenerationTaskBody so they share
the same parsed input instead of calling common.UnmarshalBodyReusable twice.

Comment on lines +289 to +313
func setMappedOutputValue(out map[string]any, path string, value any) {
path = strings.TrimSpace(path)
if out == nil || path == "" {
return
}
parts := strings.Split(path, ".")
current := out
for _, part := range parts[:len(parts)-1] {
part = strings.TrimSpace(part)
if part == "" {
return
}
next, ok := current[part].(map[string]any)
if !ok {
next = map[string]any{}
current[part] = next
}
current = next
}
leaf := strings.TrimSpace(parts[len(parts)-1])
if leaf == "" {
return
}
current[leaf] = value
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Silent overwrite of non-map defaults during nested field mapping.

If out (built from RequestBodyDefaults) already has a non-map value at an intermediate path segment (e.g. defaults set "a": "x" and a mapping targets "a.b"), setMappedOutputValue silently replaces it with a new empty map, discarding the default value without any error or log. Since defaults and mappings are both admin-configured JSON, a conflicting path is plausible and would fail silently.

🤖 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 `@relay/channel/task/taskcommon/request_body.go` around lines 289 - 313, Silent
overwrite of existing non-map values in nested mappings: setMappedOutputValue
currently replaces any intermediate non-map at a dotted path with a new map,
which can discard RequestBodyDefaults data without warning. Update
setMappedOutputValue to detect when current[part] exists but is not a
map[string]any, and stop processing or return an error/log instead of
overwriting it; keep the behavior only when the intermediate key is missing or
already a map. Refer to setMappedOutputValue and the RequestBodyDefaults mapping
flow when implementing the conflict check.

Comment thread relay/channel/task/taskcommon/request_body.go Outdated
@CSCITech
CSCITech merged commit b10421a into main Jul 3, 2026
3 checks passed
This was referenced Jul 4, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jul 12, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant