feat(grok): import CPA xAI OAuth provider - #676
Conversation
|
Warning Review limit reached
Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough新增 Grok provider 支持:后端适配 CLIProxyAPI 的 xAI OAuth 请求,前端提供 JSON/文件凭据导入、provider 创建、类型选择、路由及主题展示。 ChangesGrok provider integration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Operator
participant ProviderSelector
participant GrokTokenImport
participant ProviderAPI
participant GrokAdapter
participant XAIExecutor
Operator->>ProviderSelector: Select Grok
ProviderSelector->>GrokTokenImport: Navigate to Grok import
Operator->>GrokTokenImport: Paste or upload OAuth JSON
GrokTokenImport->>ProviderAPI: Create grok provider
ProviderAPI->>GrokAdapter: Resolve registered adapter
GrokAdapter->>XAIExecutor: Execute mapped OpenAI request
XAIExecutor-->>GrokAdapter: Return JSON or SSE response
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/adapter/provider/cliproxyapi_grok/adapter.go`:
- Around line 224-227: 在流式响应处理逻辑中更新 w.Write
调用,检查其返回的错误;当写入失败(例如客户端断开连接)时立即退出当前上游读取循环,避免继续处理和写入后续数据。保留成功写入后的 flusher.Flush
及首个分块处理逻辑不变。
- Around line 156-163: Update updateModelInBody to parse the request with a
json.Decoder configured with UseNumber(), preserving integer values and their
original numeric representation. Serialize the updated payload with a
json.Encoder configured to disable HTML escaping, while retaining the existing
error propagation and model replacement behavior.
In `@web/src/pages/providers/components/grok-token-import.tsx`:
- Around line 180-191: 为包含 jsonText 的 Textarea 添加可访问名称,并通过关联的 label
让其具备明确语义;同时更新 error 条件渲染的错误容器,加入 role="alert" 或等效的 aria-live
属性。保持现有输入处理和错误展示内容不变。
- Around line 157-198: Move the Grok import UI strings in
web/src/pages/providers/components/grok-token-import.tsx (lines 157-198) into
the existing translation resources and render them through the component’s
established i18n helper, including the title, description, file label/status,
textarea placeholder, error-related text, buttons, and dynamic singular/plural
wording. In web/src/pages/providers/components/select-type-step.tsx (lines
309-312), replace hardcoded Grok name and description text with the existing t()
calls and corresponding translation keys.
- Around line 119-143: 更新 submit,先完成所有 fileItems/parseImportItemsFromText
项目的本地解析与 normalizeGrokConfig 校验,确认全部有效后再执行写入。优先改用原子批量创建接口,避免循环调用
createProvider.mutateAsync 产生部分持久化;若只能逐项创建,则记录成功与失败结果,并让重试仅提交失败项。
- Around line 105-117: 为文件解析流程增加递增版本号,更新 handleFilesSelected
时使当前版本失效,并仅允许与最新版本匹配的 parseImportItemsFromFiles 结果写入 fileItems 或 setError;在 JSON
文本编辑处理器中同样递增版本号,使文件读取完成后不能覆盖用户最新输入。
- Around line 196-198: Update the Cancel Button in the token import form to be
disabled while submitting, preventing navigation during the active import
request. Preserve its existing navigation behavior when submitting is false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1381d73a-0668-4ac5-a6aa-63d96feabef4
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (15)
cmd/maxx/main.gogo.modinternal/adapter/provider/cliproxyapi_grok/adapter.gointernal/adapter/provider/cliproxyapi_grok/adapter_test.gointernal/adapter/provider/grok/adapter.gointernal/core/database.gointernal/domain/model.goweb/src/index.cssweb/src/lib/theme.tsweb/src/lib/transport/types.tsweb/src/pages/providers/components/grok-token-import.tsxweb/src/pages/providers/components/select-type-step.tsxweb/src/pages/providers/create-layout.tsxweb/src/pages/providers/hooks/use-provider-navigation.tsweb/src/pages/providers/types.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
- GitHub Check: playwright
- GitHub Check: multiinstance
- GitHub Check: e2e
🔇 Additional comments (15)
internal/domain/model.go (1)
309-330: LGTM!Also applies to: 341-341
web/src/lib/transport/types.ts (1)
117-137: LGTM!Also applies to: 171-171
web/src/lib/theme.ts (1)
24-25: LGTM!web/src/pages/providers/types.ts (1)
4-4: LGTM!Also applies to: 17-17, 86-93, 301-301
web/src/index.css (1)
6-11: LGTM!Also applies to: 13-17, 100-100, 387-387
internal/adapter/provider/cliproxyapi_grok/adapter_test.go (1)
9-56: LGTM!web/src/pages/providers/components/grok-token-import.tsx (1)
1-104: LGTM!Also applies to: 145-156, 199-202
web/src/pages/providers/components/select-type-step.tsx (1)
12-12: LGTM!Also applies to: 32-50, 293-308, 313-322
web/src/pages/providers/create-layout.tsx (1)
7-7: LGTM!Also applies to: 24-24
web/src/pages/providers/hooks/use-provider-navigation.ts (1)
15-15: LGTM!internal/adapter/provider/grok/adapter.go (1)
1-15: LGTM!internal/core/database.go (1)
17-17: LGTM!cmd/maxx/main.go (1)
22-22: LGTM!go.mod (2)
53-53: LGTM!
126-126: 🎯 Functional Correctness请确认这个 replace 目标的模块路径与
github.com/router-for-me/CLIProxyAPI/v7保持一致,并且包含 Grok 适配器依赖的sdk/exec入口;否则依赖解析或构建会失败。
| func updateModelInBody(body []byte, model string) ([]byte, error) { | ||
| var req map[string]any | ||
| if err := json.Unmarshal(body, &req); err != nil { | ||
| return nil, err | ||
| } | ||
| req["model"] = model | ||
| return json.Marshal(req) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
避免 json.Unmarshal 导致的数字精度丢失和 HTML 字符转义。
使用 json.Unmarshal 解析到 map[string]any 时,会将所有数字转换为 float64,可能导致请求体中较大的整数参数(如 seed)丢失精度或格式被改变。同时,默认的 json.Marshal 会转义 HTML 字符。建议使用带有 UseNumber() 的 json.Decoder 以及禁用 HTML 转义的 json.Encoder 进行重写,以确保负载数据的完整性。
🐛 修复建议
func updateModelInBody(body []byte, model string) ([]byte, error) {
var req map[string]any
- if err := json.Unmarshal(body, &req); err != nil {
+ dec := json.NewDecoder(bytes.NewReader(body))
+ dec.UseNumber()
+ if err := dec.Decode(&req); err != nil {
return nil, err
}
req["model"] = model
- return json.Marshal(req)
+
+ var buf bytes.Buffer
+ enc := json.NewEncoder(&buf)
+ enc.SetEscapeHTML(false)
+ if err := enc.Encode(req); err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func updateModelInBody(body []byte, model string) ([]byte, error) { | |
| var req map[string]any | |
| if err := json.Unmarshal(body, &req); err != nil { | |
| return nil, err | |
| } | |
| req["model"] = model | |
| return json.Marshal(req) | |
| } | |
| func updateModelInBody(body []byte, model string) ([]byte, error) { | |
| var req map[string]any | |
| dec := json.NewDecoder(bytes.NewReader(body)) | |
| dec.UseNumber() | |
| if err := dec.Decode(&req); err != nil { | |
| return nil, err | |
| } | |
| req["model"] = model | |
| var buf bytes.Buffer | |
| enc := json.NewEncoder(&buf) | |
| enc.SetEscapeHTML(false) | |
| if err := enc.Encode(req); err != nil { | |
| return nil, err | |
| } | |
| return buf.Bytes(), nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/adapter/provider/cliproxyapi_grok/adapter.go` around lines 156 -
163, Update updateModelInBody to parse the request with a json.Decoder
configured with UseNumber(), preserving integer values and their original
numeric representation. Serialize the updated payload with a json.Encoder
configured to disable HTML escaping, while retaining the existing error
propagation and model replacement behavior.
| sseBuffer.Write(chunk.Payload) | ||
| _, _ = w.Write(chunk.Payload) | ||
| flusher.Flush() | ||
| if !firstChunkSent && eventChan != nil { |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
检查 w.Write 的返回值以处理客户端断开连接的情况。
目前忽略了 w.Write 的错误返回值。如果客户端在流式传输过程中断开连接,代理会继续读取上游的流并尝试写入已关闭的连接,造成无谓的资源消耗。建议检查错误并在写入失败时及时退出循环。
🐛 修复建议
if len(chunk.Payload) > 0 {
sseBuffer.Write(chunk.Payload)
- _, _ = w.Write(chunk.Payload)
+ if _, err := w.Write(chunk.Payload); err != nil {
+ break
+ }
flusher.Flush()
if !firstChunkSent && eventChan != nil {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sseBuffer.Write(chunk.Payload) | |
| _, _ = w.Write(chunk.Payload) | |
| flusher.Flush() | |
| if !firstChunkSent && eventChan != nil { | |
| sseBuffer.Write(chunk.Payload) | |
| if _, err := w.Write(chunk.Payload); err != nil { | |
| break | |
| } | |
| flusher.Flush() | |
| if !firstChunkSent && eventChan != nil { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/adapter/provider/cliproxyapi_grok/adapter.go` around lines 224 -
227, 在流式响应处理逻辑中更新 w.Write
调用,检查其返回的错误;当写入失败(例如客户端断开连接)时立即退出当前上游读取循环,避免继续处理和写入后续数据。保留成功写入后的 flusher.Flush
及首个分块处理逻辑不变。
| const submit = async () => { | ||
| setError(null); | ||
| setSubmitting(true); | ||
| try { | ||
| const items = fileItems.length > 0 ? fileItems : parseImportItemsFromText(jsonText); | ||
| if (items.length === 0) { | ||
| throw new Error('Paste JSON or select one or more JSON files'); | ||
| } | ||
| for (const item of items) { | ||
| const grok = normalizeGrokConfig(item.raw); | ||
| const data: CreateProviderData = { | ||
| type: 'grok', | ||
| name: providerName(grok, item.source), | ||
| config: { grok }, | ||
| supportedClientTypes: ['openai'], | ||
| }; | ||
| await createProvider.mutateAsync(data); | ||
| } | ||
| navigate('/providers'); | ||
| } catch (err) { | ||
| setError(err instanceof Error ? err.message : String(err)); | ||
| } finally { | ||
| setSubmitting(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
避免批量导入留下半完成状态。
第 N 项创建失败时,前 N-1 项已持久化,但界面仅显示错误;用户重试会再次提交全部项目。应优先使用原子批量接口,或记录逐项结果并仅重试失败项,同时在首次写入前完成全部本地校验。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/pages/providers/components/grok-token-import.tsx` around lines 119 -
143, 更新 submit,先完成所有 fileItems/parseImportItemsFromText 项目的本地解析与
normalizeGrokConfig 校验,确认全部有效后再执行写入。优先改用原子批量创建接口,避免循环调用
createProvider.mutateAsync 产生部分持久化;若只能逐项创建,则记录成功与失败结果,并让重试仅提交失败项。
| <Textarea | ||
| value={jsonText} | ||
| onChange={(event) => { | ||
| setJsonText(event.target.value); | ||
| if (event.target.value.trim()) { | ||
| setFileItems([]); | ||
| } | ||
| }} | ||
| placeholder='{"type":"xai","auth_kind":"oauth",...}' | ||
| className="min-h-80 font-mono text-xs" | ||
| /> | ||
| {error && <div className="rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">{error}</div>} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
为 JSON 文本框和错误消息补充可访问语义。
占位符不能替代可访问名称。请为 Textarea 添加关联的 label,并为错误容器添加 role="alert" 或 aria-live。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/pages/providers/components/grok-token-import.tsx` around lines 180 -
191, 为包含 jsonText 的 Textarea 添加可访问名称,并通过关联的 label 让其具备明确语义;同时更新 error
条件渲染的错误容器,加入 role="alert" 或等效的 aria-live 属性。保持现有输入处理和错误展示内容不变。
| <Button type="button" variant="outline" onClick={() => navigate('/providers/create')}> | ||
| Cancel | ||
| </Button> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
提交期间禁用“取消”,或真正中止导入。
当前点击取消只会导航离开;后台创建仍会继续,成功后还会再次导航到 /providers。至少应在 submitting 时禁用按钮。
建议修改
-<Button type="button" variant="outline" onClick={() => navigate('/providers/create')}>
+<Button
+ type="button"
+ variant="outline"
+ disabled={submitting}
+ onClick={() => navigate('/providers/create')}
+>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Button type="button" variant="outline" onClick={() => navigate('/providers/create')}> | |
| Cancel | |
| </Button> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| disabled={submitting} | |
| onClick={() => navigate('/providers/create')}> | |
| Cancel | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/pages/providers/components/grok-token-import.tsx` around lines 196 -
198, Update the Cancel Button in the token import form to be disabled while
submitting, preventing navigation during the active import request. Preserve its
existing navigation behavior when submitting is false.
|
Validation update for
Current known blocker: repository review requirement. The PR author is |
# Conflicts: # web/src/pages/providers/components/grok-token-import.tsx
Summary
Dependency
Validation
Notes
Summary by CodeRabbit
新功能
界面优化