feat: add Vertex AI storage integration - #6779
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughAdds Vertex AI Cloud Storage upload, listing, retrieval, resumable upload, and deletion support. The change adds typed channel routing, cached Vertex authentication, storage probing, API documentation, channel configuration UI, and automatic-group badge rendering. ChangesVertex AI Cloud Storage relay
API key group status badge
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
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: 5
🧹 Nitpick comments (6)
middleware/distributor.go (1)
276-283: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winWrap the bucket parse error with i18n, as the sibling branches do.
The error from
relayconstant.VertexStorageModelNamereaches the client throughabortWithOpenAiMessageat line 48. Every other branch ingetModelRequestreturns an i18n-translated message. This branch returns a raw English string, so the response text is not localized.Proposed change
if relayconstant.IsVertexStoragePath(c.Request.URL.Path) { modelName, err := relayconstant.VertexStorageModelName(c.Param("bucket")) if err != nil { - return nil, false, err + return nil, false, errors.New(i18n.T(c, i18n.MsgDistributorInvalidRequest, map[string]any{"Error": err.Error()})) }🤖 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 `@middleware/distributor.go` around lines 276 - 283, Update the Vertex storage branch in getModelRequest to wrap the error returned by relayconstant.VertexStorageModelName with the same i18n translation mechanism used by sibling branches before returning it, while preserving the existing success behavior and relay mode assignment.web/src/features/channels/components/drawers/channel-mutate-drawer.tsx (2)
3550-3560: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAlign
sourceModelOptionswith the bucket-filtered options used elsewhere.
sourceModelOptions={currentModelsArray}passes the full model list, includingstorage:gs:bucket pseudo-model entries, toModelMappingEditor. Every other guardrail in this file (missingSourceModelsat line 1195,exposedTargetModelsat line 1207, andtargetModelOptionsat line 3556) treats storage buckets as non-models by filtering throughcurrentRegularModels/modelOptions.Use
currentRegularModelsforsourceModelOptionsso storage bucket entries do not appear as selectable "source" models in the model-mapping editor.♻️ Proposed fix
- sourceModelOptions={currentModelsArray} + sourceModelOptions={currentRegularModels}🤖 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/features/channels/components/drawers/channel-mutate-drawer.tsx` around lines 3550 - 3560, Update the ModelMappingEditor invocation to pass currentRegularModels as sourceModelOptions instead of currentModelsArray, keeping storage bucket pseudo-models excluded from selectable source models.
3435-3447: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winClarify that "Clear All" does not remove configured storage buckets.
handleClearModelscallsupdateModels([]), which merges the cleared model list withcurrentStorageBuckets, so Vertex AI storage bucket entries survive this action. The button is labeled "Clear All" and only disables whencurrentRegularModels.length === 0, so an admin with only storage buckets configured could reasonably expect this action to also affect bucket configuration.Add a short description, tooltip, or adjust the label so it is clear this action clears regular models only and preserves configured storage buckets.
🤖 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/features/channels/components/drawers/channel-mutate-drawer.tsx` around lines 3435 - 3447, Clarify the button action in the JSX using the existing handleClearModels control: update the “Clear All” label or add a short description/tooltip stating that it clears regular models only and preserves configured storage buckets. Keep the current disabled condition and handler behavior unchanged.web/src/i18n/locales/zh-TW.json (1)
2097-2097: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse one Traditional Chinese term for "bucket" consistently.
Four of the five new entries translate "bucket" as "儲存值區" (lines 216, 978, 1693, 4341), but line 2097 translates the same English term as "儲存桶". Use the same term for the same concept across all five new strings.
Pick one term (for example "儲存值區", matching the majority of the new entries) and update line 2097 to match.
♻️ Proposed fix
- "GCS bucket: writes, reads, and deletes a temporary object": "GCS 儲存桶:寫入、讀取並刪除一個暫存物件", + "GCS bucket: writes, reads, and deletes a temporary object": "GCS 儲存值區:寫入、讀取並刪除一個暫存物件",Also applies to: 216-216, 978-978, 1693-1693, 4341-4341
🤖 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/i18n/locales/zh-TW.json` at line 2097, Update the Traditional Chinese translation for “GCS bucket: writes, reads, and deletes a temporary object” to use the same bucket terminology as the other new entries, preferably “儲存值區” instead of “儲存桶.”web/src/features/channels/lib/vertex-storage-models.ts (1)
26-36: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winTighten bucket name validation to match GCS naming rules.
normalizeVertexStorageBucketonly rejects\,/,?,#,://,.,.., and the model prefix. Google Cloud Storage bucket names must contain only lowercase letters, numbers, dashes, underscores, and dots, must start and end with a letter or number, and must be 3–63 characters long. Uppercase letters, spaces, and other invalid characters currently pass this check and reach the backend, where they will fail against the real GCS API with a less clear error.Update the validation to enforce the full GCS naming rule set (lowercase-only character class, length bound, start/end character constraint) so invalid names are rejected at the point of entry instead of surfacing later as an upstream failure.
♻️ Proposed tightened validation
-const INVALID_BUCKET_CHARACTERS = /[\\/?#]/ +const VALID_BUCKET_NAME = /^[a-z0-9][a-z0-9._-]{1,61}[a-z0-9]$/ export function normalizeVertexStorageBucket(value: string): string | null { const bucket = value.trim() if (!bucket || bucket === '.' || bucket === '..') return null if (bucket.startsWith(VERTEX_STORAGE_MODEL_PREFIX)) return null - if (bucket.includes('://') || INVALID_BUCKET_CHARACTERS.test(bucket)) { - return null - } - return bucket + if (bucket.includes('://') || !VALID_BUCKET_NAME.test(bucket)) { + return null + } + return bucket }Since this relates to Google Cloud Storage's external naming specification, please confirm the exact rule set is still current before adopting this diff verbatim.
🤖 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/features/channels/lib/vertex-storage-models.ts` around lines 26 - 36, Update normalizeVertexStorageBucket to enforce the current GCS bucket naming rules: accept only lowercase letters, digits, dashes, underscores, and dots; require 3–63 characters; and require the first and last characters to be alphanumeric. Preserve the existing null returns for empty, dot-only, model-prefixed, and URL-like values, and verify the validation matches the current GCS specification.web/src/features/channels/components/drawers/sections/vertex-storage-buckets-field.tsx (1)
54-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the magic number
41with a named channel-type constant.Define
CHANNEL_TYPE_VERTEX_AIalongsideCHANNEL_TYPE_NEW_APIand use it for the Vertex AI check. Keep the numeric ID in one central definition.🤖 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/features/channels/components/drawers/sections/vertex-storage-buckets-field.tsx` at line 54, Define the named CHANNEL_TYPE_VERTEX_AI constant alongside CHANNEL_TYPE_NEW_API with value 41, then update the channelType check in the Vertex storage buckets field to use that constant instead of the literal. Keep the numeric channel ID centralized in the constant definition.
🤖 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 `@docs/vertex-ai-storage.md`:
- Line 35: Update every GCS_OBJECT example in the documentation to
percent-encode the complete object name, not only slash characters; ensure
characters such as ?, #, &, %, spaces, and non-ASCII text are encoded
consistently wherever GCS_OBJECT is used as a query value or path segment.
- Line 13: 更新文档中的 GCS IAM 权限说明:为固定的上传、列举、读取和删除流程记录所需的精确自定义角色权限,并将
roles/storage.objectUser 明确标为宽泛的便利回退选项;同时说明只读/列举场景可使用
roles/storage.objectViewer,以及 roles/storage.objectCreator 不能覆盖或删除对象。
- Line 160: Update the Location guidance in the documentation to treat
RESUMABLE_LOCATION as opaque and instruct clients to use the returned Location
unchanged. Remove the requirement that it start with NEW_API_BASE_URL/vertexai/,
while preserving the explanation of gateway validation, rewriting, and
subsequent PUT authorization.
In `@relay/channel/vertex/storage_proxy.go`:
- Around line 188-207: Update DoStorageProxy after copying the HTTP client to
set storageClient.Timeout to zero, preserving request-context cancellation while
preventing the shared RELAY_TIMEOUT from terminating progressing storage
transfers.
In `@web/src/features/channels/components/dialogs/channel-test-dialog.tsx`:
- Around line 889-896: Update the GCS StatusBadge in the channel test dialog to
pass its label through the existing translation function t(), matching the
adjacent Default badge pattern and preserving the current badge behavior.
---
Nitpick comments:
In `@middleware/distributor.go`:
- Around line 276-283: Update the Vertex storage branch in getModelRequest to
wrap the error returned by relayconstant.VertexStorageModelName with the same
i18n translation mechanism used by sibling branches before returning it, while
preserving the existing success behavior and relay mode assignment.
In `@web/src/features/channels/components/drawers/channel-mutate-drawer.tsx`:
- Around line 3550-3560: Update the ModelMappingEditor invocation to pass
currentRegularModels as sourceModelOptions instead of currentModelsArray,
keeping storage bucket pseudo-models excluded from selectable source models.
- Around line 3435-3447: Clarify the button action in the JSX using the existing
handleClearModels control: update the “Clear All” label or add a short
description/tooltip stating that it clears regular models only and preserves
configured storage buckets. Keep the current disabled condition and handler
behavior unchanged.
In
`@web/src/features/channels/components/drawers/sections/vertex-storage-buckets-field.tsx`:
- Line 54: Define the named CHANNEL_TYPE_VERTEX_AI constant alongside
CHANNEL_TYPE_NEW_API with value 41, then update the channelType check in the
Vertex storage buckets field to use that constant instead of the literal. Keep
the numeric channel ID centralized in the constant definition.
In `@web/src/features/channels/lib/vertex-storage-models.ts`:
- Around line 26-36: Update normalizeVertexStorageBucket to enforce the current
GCS bucket naming rules: accept only lowercase letters, digits, dashes,
underscores, and dots; require 3–63 characters; and require the first and last
characters to be alphanumeric. Preserve the existing null returns for empty,
dot-only, model-prefixed, and URL-like values, and verify the validation matches
the current GCS specification.
In `@web/src/i18n/locales/zh-TW.json`:
- Line 2097: Update the Traditional Chinese translation for “GCS bucket: writes,
reads, and deletes a temporary object” to use the same bucket terminology as the
other new entries, preferably “儲存值區” instead of “儲存桶.”
🪄 Autofix
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 Plus
Run ID: a4bacb76-12a5-4369-b5b6-b7ee3033b6b5
📒 Files selected for processing (31)
constant/context_key.gocontroller/channel-test.gocontroller/relay.gocontroller/vertex_storage_channel_probe.gocontroller/vertex_storage_proxy.godocs/openapi/relay.jsondocs/vertex-ai-storage.mdmiddleware/distributor.gomodel/ability.gomodel/channel_cache.gorelay/channel/vertex/service_account.gorelay/channel/vertex/storage_proxy.gorelay/constant/relay_mode.gorelay/constant/vertex_storage.gorouter/relay-router.gorouter/relay_router_test.goservice/channel_select.goweb/src/features/channels/components/data-table-row-actions.tsxweb/src/features/channels/components/dialogs/channel-test-dialog.tsxweb/src/features/channels/components/drawers/channel-mutate-drawer.tsxweb/src/features/channels/components/drawers/sections/index.tsweb/src/features/channels/components/drawers/sections/vertex-storage-buckets-field.tsxweb/src/features/channels/lib/index.tsweb/src/features/channels/lib/vertex-storage-models.tsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/i18n/locales/zh.json
|
|
||
| 1. 在 Google Cloud 项目中创建供 Vertex AI 渠道使用的服务账号。 | ||
| 2. 为服务账号授予调用 Vertex AI 所需的权限,例如 `roles/aiplatform.user`。 | ||
| 3. 在目标 bucket 上授予与实际操作匹配的 GCS 权限。需要完整执行本指南的上传、下载、列举和删除操作时,可授予 `roles/storage.objectUser`;生产环境应按最小权限原则拆分只读或只写权限。 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Use least-privilege IAM guidance.
roles/storage.objectUser grants folder, multipart-upload, object move, restore, and update permissions in addition to the permissions needed by these fixed routes. Document a custom role for the exact upload/list/get/delete flow, or clearly mark roles/storage.objectUser as a broad convenience fallback. Also state that roles/storage.objectViewer is suitable for read/list-only access and roles/storage.objectCreator cannot overwrite or delete objects. (docs.cloud.google.com)
🤖 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 `@docs/vertex-ai-storage.md` at line 13, 更新文档中的 GCS IAM
权限说明:为固定的上传、列举、读取和删除流程记录所需的精确自定义角色权限,并将 roles/storage.objectUser
明确标为宽泛的便利回退选项;同时说明只读/列举场景可使用 roles/storage.objectViewer,以及
roles/storage.objectCreator 不能覆盖或删除对象。
Source: MCP tools
| export NEW_API_BASE_URL="https://api.example.com" | ||
| export NEW_API_TOKEN="<your-new-api-token>" | ||
| export GCS_BUCKET="example-bucket" | ||
| export GCS_OBJECT="docs%2Freport.pdf" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Encode the complete object name.
GCS_OBJECT is used both as the name query value and as a path segment. Encoding only / is insufficient for names containing ?, #, &, %, spaces, or non-ASCII characters. Require percent-encoding of the complete object name and apply the same rule to every example.
Proposed documentation update
- export GCS_OBJECT="docs%2Freport.pdf"
+ # Percent-encode the complete object name as one URL component.
+ export GCS_OBJECT="docs%2Freport.pdf"
- 对象名中的 `/` 应编码为 `%2F`。
+ `GCS_OBJECT` 必须将完整对象名按 URL 组件编码,包括将 `/` 编码为 `%2F`。Also applies to: 53-53
🤖 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 `@docs/vertex-ai-storage.md` at line 35, Update every GCS_OBJECT example in the
documentation to percent-encode the complete object name, not only slash
characters; ensure characters such as ?, #, &, %, spaces, and non-ASCII text are
encoded consistently wherever GCS_OBJECT is used as a query value or path
segment.
| } | awk 'tolower($1) == "location:" { sub(/\r$/, "", $2); print $2 }')" | ||
| ``` | ||
|
|
||
| 返回的 `Location` 必须以 `${NEW_API_BASE_URL}/vertexai/` 开头。网关会校验 Google 返回的 session URL,并使用系统配置的服务地址安全改写;改写失败时返回 `502`,不会把 `storage.googleapis.com` session URL 暴露给客户端。后续每个 `PUT` 都会重新执行 Token 鉴权、限流、渠道分发和 bucket 授权。 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Treat RESUMABLE_LOCATION as opaque.
If the client uses an alias or base URL that differs from system_setting.ServerAddress, the valid rewritten Location will not necessarily start with NEW_API_BASE_URL/vertexai/. The proxy rewrites it from the configured server address in controller/vertex_storage_proxy.go, Lines 48-143. Tell clients to use the returned Location unchanged.
🤖 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 `@docs/vertex-ai-storage.md` at line 160, Update the Location guidance in the
documentation to treat RESUMABLE_LOCATION as opaque and instruct clients to use
the returned Location unchanged. Remove the requirement that it start with
NEW_API_BASE_URL/vertexai/, while preserving the explanation of gateway
validation, rewriting, and subsequent PUT authorization.
| func DoStorageProxy(ctx context.Context, input StorageProxyRequest) (*http.Response, error) { | ||
| request, err := buildStorageRequest(ctx, input) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| client, err := service.GetHttpClientWithProxy(input.Proxy) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("new proxy http client failed: %w", err) | ||
| } | ||
| storageClient := *client | ||
| storageClient.CheckRedirect = func(_ *http.Request, _ []*http.Request) error { | ||
| return http.ErrUseLastResponse | ||
| } | ||
| response, err := storageClient.Do(request) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| response.Header = SanitizeStorageResponseHeader(response.Header) | ||
| return response, nil | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check whether the shared proxy HTTP client sets a global Timeout.
rg -n -C 10 'func GetHttpClientWithProxy\b' serviceRepository: QuantumNous/new-api
Length of output: 1789
Clear the shared client timeout for storage transfers.
If RELAY_TIMEOUT is non-zero, DoStorageProxy preserves it on the copied client. The timeout can terminate large uploads or downloads during an otherwise progressing transfer.
Set storageClient.Timeout = 0 and rely on the request context for cancellation.
🤖 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/vertex/storage_proxy.go` around lines 188 - 207, Update
DoStorageProxy after copying the HTTP client to set storageClient.Timeout to
zero, preserving request-context cancellation while preventing the shared
RELAY_TIMEOUT from terminating progressing storage transfers.
| {isVertexStorage && ( | ||
| <StatusBadge | ||
| label='GCS' | ||
| variant='info' | ||
| size='sm' | ||
| copyable={false} | ||
| /> | ||
| )} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Wrap the GCS badge label with t().
The GCS badge label is a literal string. The adjacent Default badge at line 883 uses t('Default'). Use the same pattern for consistency and to keep all user-facing text translatable.
🌐 Proposed fix
{isVertexStorage && (
<StatusBadge
- label='GCS'
+ label={t('GCS')}
variant='info'
size='sm'
copyable={false}
/>
)}Based on path instructions, "Frontend user-facing text must use i18next/react-i18next: React components should call useTranslation() and t('English key')."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {isVertexStorage && ( | |
| <StatusBadge | |
| label='GCS' | |
| variant='info' | |
| size='sm' | |
| copyable={false} | |
| /> | |
| )} | |
| {isVertexStorage && ( | |
| <StatusBadge | |
| label={t('GCS')} | |
| variant='info' | |
| size='sm' | |
| copyable={false} | |
| /> | |
| )} |
🤖 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/features/channels/components/dialogs/channel-test-dialog.tsx` around
lines 889 - 896, Update the GCS StatusBadge in the channel test dialog to pass
its label through the existing translation function t(), matching the adjacent
Default badge pattern and preserving the current badge behavior.
Source: Path instructions
|
@seefs001 新增vertexai的存储桶逻辑 。 可以合并下 |
Important
📝 变更描述 / Description
为 Vertex AI 渠道增加受控的 Google Cloud Storage 集成,使客户端可以通过 new-api 使用已配置服务账号读写指定 bucket,并可在渠道管理界面直接验证真实的存储权限。
storage:gs:<bucket>精确声明允许访问的 bucket,并提供前端独立配置字段与七语言说明。/vertexaiStorage Proxy,共五个方法/路由:可恢复或媒体上传、对象列表、对象下载和对象删除。上游主机固定为storage.googleapis.com,仅支持服务账号 JSON,不接受 API Key 渠道。/vertexai地址,不泄露 Google session URL。./..点段,避免上游路径归一化突破对象级 API 边界;响应统一设置Cache-Control: private, no-store并移除Expires/Age,避免共享缓存跨 Token 复用。本变更由 AI 辅助完成;PR 描述已依据实际代码路径、当前提交文件、验证结果和安全审查结论整理。
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
后端:
go test ./...go build ./...前端:
bun run i18n:sync:en、zh、zh-TW、fr、ru、ja、vi 的 missing / extras / untranslated 均为 0。bun run typecheckbun run build安全与差异检查:
git diff --check upstream/main...HEADdocs/superpowers/文件,也未新增测试文件。说明:
bun run lint的全仓扫描仍会被当前upstream/main中大量非本 PR 文件的既有 oxlint 错误阻断;上述本 PR 涉及文件的定向 lint 已通过。Summary by CodeRabbit
New Features
User Experience
Documentation