feat: add Vertex AI storage integration - #6778
Conversation
WalkthroughThis PR adds Vertex AI Cloud Storage support. It includes bucket configuration, channel-type-aware routing, authenticated GCS proxy operations, storage channel probing, retry handling, frontend test integration, OpenAPI documentation, and localized UI text. ChangesVertex AI Cloud Storage
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 💡 1🛠️ Fix failing CI checks 💡
🧪 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: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/features/channels/components/drawers/channel-mutate-drawer.tsx (1)
3550-3559: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse
currentRegularModelsforsourceModelOptions.
currentModelsArrayincludesstorage:gs:<bucket>capability markers. Storage requests handle these markers through a separate storage route, so they are not valid model-mapping sources. PasscurrentRegularModelsto prevent ineffective mappings.🤖 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 - 3559, Update the ModelMappingEditor usage to pass currentRegularModels as sourceModelOptions instead of currentModelsArray, while leaving targetModelOptions and the existing form behavior unchanged.
🧹 Nitpick comments (12)
web/src/features/channels/lib/vertex-storage-models.ts (1)
26-36: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winBucket name validation does not match Google Cloud Storage naming rules.
INVALID_BUCKET_CHARACTERSonly rejects\,/,?, and#. It does not enforce the actual GCS bucket-name character set. Google Cloud Storage requires: Bucket names can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.). Names must also start and end with a number or letter and stay within the required length range.A value such as
"MyBucket"or"my bucket"passesnormalizeVertexStorageBuckethere but fails when the backend submits it to GCS. Add a stricter character-set and length check so the UI rejects invalid names immediately instead of surfacing a confusing failure later, during the channel test or actual storage call.♻️ Proposed stricter bucket-name check
-const INVALID_BUCKET_CHARACTERS = /[\\/?#]/ +// GCS bucket names: lowercase letters, digits, dashes, underscores, dots; +// must start/end with a letter or number; 3-63 characters (222 with dots). +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)) { + if (bucket.includes('://') || !VALID_BUCKET_NAME.test(bucket)) { return null } return bucket }🤖 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, Strengthen normalizeVertexStorageBucket validation to enforce GCS bucket naming rules: allow only lowercase letters, digits, dashes, underscores, and dots; require the name to start and end with a letter or digit; and enforce the required length range. Preserve the existing trimming, reserved-prefix, empty, dot-only, and URL/invalid-name checks while rejecting values such as uppercase or whitespace-containing names before returning the bucket.docs/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md (1)
265-277: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winTest Go's special
http.Request.Hostfield.Go stores
Hostseparately fromhttp.Header. Assert all of the following in the proxy tests:
req.Hostis empty or fixed.req.Header.Get("Host")is empty.req.URL.Hostisstorage.googleapis.com.Otherwise, header tests can pass without covering the field that controls the outbound host.
🤖 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/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md` around lines 265 - 277, Update the proxy request tests in Step 2 to explicitly validate Go’s separate host fields: assert req.Host is empty or fixed, req.Header.Get("Host") is empty, and req.URL.Host remains "storage.googleapis.com". Keep these assertions alongside the existing header-removal and preservation checks.web/src/features/channels/components/drawers/sections/__tests__/vertex-storage-buckets-field.test.tsx (1)
19-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd keyboard and accessibility-state coverage to the combobox test.
The repository consistently uses
node:testwithhappy-domfor component tests and has no Vitest or React Testing Library dependency. Keep this runner. Add keyboard interaction assertions and check relevant combobox states such asaria-expandedandaria-selected.🤖 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/__tests__/vertex-storage-buckets-field.test.tsx` around lines 19 - 71, Extend the existing VertexStorageBucketsField combobox tests using the current node:test, happy-dom, and React act setup. Add assertions for keyboard interaction and verify the combobox’s aria-expanded and option aria-selected states before and after interaction, without introducing another test runner or testing-library dependency.Source: Coding guidelines
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx (2)
119-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest visible dialog behavior with React Testing Library.
ChannelsStateProbeverifies privateChannelsProviderstate. The storage test verifies a test-only data attribute. These assertions can pass while the user-visible dialog flow is broken. Render the dialog flow with React Testing Library. Query accessible elements and visible storage probe content.
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L119-L124: removeChannelsStateProbefrom the assertion path and assert the dialog content after each action.web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx#L153-L162: replace global DOM anddata-vertex-storage-test-descriptionassertions with React Testing Library queries for visible content.As per coding guidelines: “组件测试使用 React Testing Library,从用户视角查询元素并测试交互和行为,禁止断言内部 state、私有函数调用次数或无用户意义的 DOM 层级。”
#!/bin/bash set -euo pipefail # Confirm that the frontend test setup provides React Testing Library. fd -a '^package\.json$' . -x rg -n \ '"`@testing-library/react`"|"`@testing-library/user-event`"|"vitest"' {}🤖 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/__tests__/channel-test-routing.test.tsx` around lines 119 - 124, Update ChannelsStateProbe usage in web/src/features/channels/components/__tests__/channel-test-routing.test.tsx at lines 119-124 to test the rendered dialog flow with React Testing Library, asserting accessible dialog content after each user action rather than private provider state. Update web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx at lines 153-162 to replace global DOM and data-vertex-storage-test-description assertions with React Testing Library queries that verify visible storage probe content.
119-128: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd explicit return types to local test helpers.
The new helpers rely on inferred return types. Add explicit return types for component and asynchronous helper functions.
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L119-L128: annotateChannelsStateProbeandrenderActions.web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L163-L176: annotateclickandgetChannelsState.web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx#L114-L120: annotateSeedCurrentChannel.As per coding guidelines: “参数和返回值应显式标注类型。”
🤖 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/__tests__/channel-test-routing.test.tsx` around lines 119 - 128, In web/src/features/channels/components/__tests__/channel-test-routing.test.tsx lines 119-128, add explicit return types to ChannelsStateProbe and the asynchronous renderActions helper; in lines 163-176, add explicit return types to click and getChannelsState. In web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx lines 114-120, add an explicit return type to SeedCurrentChannel, using the appropriate React component and Promise/utility types without changing behavior.Source: Coding guidelines
model/ability.go (1)
63-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider extracting the enabled-channel-type subquery into one helper.
The same builder appears in
getPriority(Lines 69-72) and ingetChannelQuery(Lines 105-108). One helper keeps the status and type predicate in one place.♻️ Suggested helper
func enabledChannelIDsOfType(requiredChannelType int) *gorm.DB { return DB.Model(&Channel{}). Select("id"). Where("status = ? and type = ?", common.ChannelStatusEnabled, requiredChannelType) }🤖 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/ability.go` around lines 63 - 73, Extract the repeated enabled-channel subquery into an enabledChannelIDsOfType helper, preserving the existing status and requiredChannelType predicates. Update both getPriority and getChannelQuery to reuse this helper instead of building the Channel query inline.model/channel_cache.go (1)
220-232: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNote the lock precondition and the different handling of unknown channel IDs.
filterChannelsByTypereads the package-levelchannelsIDMmap. The only caller holdschannelSyncLock.RLock(Line 127), so the read is safe now. Add a short comment to record that precondition.
filterChannelsByRequestPathAndModelkeeps IDs that are missing fromchannelsIDMso the downstream consistency error still appears (Lines 246-249).filterChannelsByTypedrops them. On the typed path a dangling ability then produces a silent "no channel" result instead of the explicit consistency error.♻️ Suggested change
+// filterChannelsByType requires the caller to hold channelSyncLock. func filterChannelsByType(channelIDs []int, requiredChannelType int) []int { if requiredChannelType == 0 { return channelIDs } filtered := make([]int, 0, len(channelIDs)) for _, channelID := range channelIDs { channel, ok := channelsIDM[channelID] - if ok && channel.Type == requiredChannelType { + if !ok { + // keep it so the downstream consistency error is raised as before + filtered = append(filtered, channelID) + continue + } + if channel.Type == requiredChannelType { filtered = append(filtered, channelID) } } return filtered }🤖 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/channel_cache.go` around lines 220 - 232, Add a brief comment above filterChannelsByType documenting that callers must hold channelSyncLock.RLock while reading channelsIDM. Change its filtering logic to retain channel IDs absent from channelsIDM, while continuing to retain only present channels whose Type matches requiredChannelType.controller/vertex_storage_channel_probe.go (1)
167-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInclude the upstream Google Cloud Storage error message in the probe error.
The probe reports only the status code. Google Cloud Storage returns a JSON error body that names the exact cause, for example a missing
storage.objects.createpermission or a nonexistent bucket. Without that text the administrator cannot tell a permission failure from a wrong bucket name.Read a bounded prefix of the body on failure and add it to the error.
♻️ Suggested change
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { - return nil, fmt.Errorf("Google Cloud Storage returned status %d", response.StatusCode) + detail, _ := io.ReadAll(io.LimitReader(response.Body, 2048)) + if len(detail) > 0 { + return nil, fmt.Errorf("Google Cloud Storage returned status %d: %s", response.StatusCode, strings.TrimSpace(string(detail))) + } + return nil, fmt.Errorf("Google Cloud Storage returned status %d", response.StatusCode) }🤖 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 `@controller/vertex_storage_channel_probe.go` around lines 167 - 169, Update the non-success response handling in the probe function around the visible StatusCode check to read a bounded prefix of the Google Cloud Storage response body and include it in the returned error alongside the status code. Preserve the existing success behavior and ensure body-read failures do not replace the upstream status context.controller/vertex_storage_proxy_test.go (1)
223-265: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a case for the invalid upstream status guard.
relayVertexStorageProxyrejects upstream responses whoseStatusCodeis below 100 or above 599 and returnsinvalid_upstream_status. No test covers that branch. A stub response withStatusCode: 0protects the guard, becausec.Statuswith an out-of-range code produces an invalid response.💚 Proposed test
func TestRelayVertexStorageProxyRejectsInvalidUpstreamStatus(t *testing.T) { responseBody := &trackingVertexStorageBody{Reader: strings.NewReader("upstream-body")} deps := vertexStorageProxyDependencies{ acquireAccessToken: func(vertex.CachedAccessTokenRequest) (string, error) { return "google-token", nil }, doProxy: func(context.Context, vertex.StorageProxyRequest) (*http.Response, error) { return &http.Response{StatusCode: 0, Header: http.Header{}, Body: responseBody}, nil }, } recorder, c := newVertexStorageProxyTestContext(t, http.MethodGet, "/vertexai/storage/v1/b/bucket-a/o", "bucket-a") relayVertexStorageProxy(c, vertex.StorageOperationList, deps) assert.Equal(t, http.StatusBadGateway, recorder.Code) assert.Contains(t, recorder.Body.String(), `"code":"invalid_upstream_status"`) assert.NotContains(t, recorder.Body.String(), "upstream-body") assert.True(t, responseBody.closed) }🤖 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 `@controller/vertex_storage_proxy_test.go` around lines 223 - 265, Add a test alongside the existing relay proxy tests for the invalid upstream status branch in relayVertexStorageProxy, using a successful proxy response with StatusCode 0 and a tracking response body. Assert that the handler returns 502 with the invalid_upstream_status error, does not relay the upstream body, and closes the response body.controller/vertex_storage_proxy.go (1)
76-83: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMerge the two object checks into one branch.
Both conditions repeat
operation == vertex.StorageOperationGet || operation == vertex.StorageOperationDelete. A single guard keeps the two distinct error codes and removes the duplicated condition.♻️ Proposed refactor
- object := strings.TrimPrefix(c.Param("object"), "/") - if (operation == vertex.StorageOperationGet || operation == vertex.StorageOperationDelete) && object == "" { - respondVertexStorageProxyError(c, http.StatusBadRequest, "object_required", "Cloud Storage object is required") - return - } - if (operation == vertex.StorageOperationGet || operation == vertex.StorageOperationDelete) && relayconstant.ValidateVertexStorageObjectName(object) != nil { - respondVertexStorageProxyError(c, http.StatusBadRequest, "invalid_object", "Cloud Storage object contains an invalid path segment") - return - } + object := strings.TrimPrefix(c.Param("object"), "/") + if operation == vertex.StorageOperationGet || operation == vertex.StorageOperationDelete { + if object == "" { + respondVertexStorageProxyError(c, http.StatusBadRequest, "object_required", "Cloud Storage object is required") + return + } + if relayconstant.ValidateVertexStorageObjectName(object) != nil { + respondVertexStorageProxyError(c, http.StatusBadRequest, "invalid_object", "Cloud Storage object contains an invalid path segment") + return + } + }🤖 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 `@controller/vertex_storage_proxy.go` around lines 76 - 83, In the handler containing the object validation, merge the empty-object and invalid-object checks into one branch guarded by the shared GET/DELETE operation condition. Preserve the distinct object_required and invalid_object responses and their early returns, while removing the duplicated operation check.relay/channel/vertex/storage_proxy_test.go (1)
142-173: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtend the rejection table to the userinfo and fragment guards.
RewriteStorageResumableLocationrejects a location that carries userinfo or a fragment, and a gateway URL that carries userinfo. No case exercises those three branches, so a regression that drops them would pass. Add them to the table.♻️ Proposed additional cases
{ name: "bucket mismatch", location: "https://storage.googleapis.com/upload/storage/v1/b/bucket-b/o?upload_id=abc", gatewayURL: "https://gateway.example.com", bucket: "bucket-a", }, + { + name: "location with userinfo", + location: "https://user:pass@storage.googleapis.com/upload/storage/v1/b/bucket-a/o?upload_id=abc", + gatewayURL: "https://gateway.example.com", + bucket: "bucket-a", + }, + { + name: "location with fragment", + location: "https://storage.googleapis.com/upload/storage/v1/b/bucket-a/o?upload_id=abc#frag", + gatewayURL: "https://gateway.example.com", + bucket: "bucket-a", + }, + { + name: "gateway with userinfo", + location: "https://storage.googleapis.com/upload/storage/v1/b/bucket-a/o?upload_id=abc", + gatewayURL: "https://user:pass@gateway.example.com", + bucket: "bucket-a", + },🤖 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_test.go` around lines 142 - 173, Extend the tests in TestRewriteStorageResumableLocationRejectsUnsafeInput with cases for a storage location containing userinfo, a storage location containing a fragment, and a gatewayURL containing userinfo. Use otherwise valid values so each case specifically exercises its corresponding rejection guard.router/relay_router_test.go (1)
180-184: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the exact method set per path, not only the expected methods.
The loop iterates
expected[path]. An extra operation added to a documented path is never inspected, so the contract test passes while the OpenAPI document drifts. Compare the HTTP-method keys present inpathItemagainstmethods.♻️ Proposed change to pin the exact method set
vertexPathCount++ methods, ok := expected[path] require.True(t, ok, "unexpected Vertex Storage path %s", path) + documented := make([]string, 0, len(pathItem)) + for key := range pathItem { + switch key { + case "get", "put", "post", "delete", "patch", "head", "options", "trace": + documented = append(documented, key) + } + } + assert.ElementsMatch(t, methods, documented, "method set for %s", path) for _, method := range methods {🤖 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 `@router/relay_router_test.go` around lines 180 - 184, Update the path validation around the expected[path] lookup and method loop to compare the complete HTTP-method key set in pathItem against methods, rejecting both missing and extra operations. Preserve the existing per-method operation validation while ensuring documented paths cannot contain uninspected methods.
🤖 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/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md`:
- Around line 99-105: Update normalizeVertexStorageBucket to validate complete
GCS bucket-name rules, including length, lowercase requirements, allowed
characters, boundary characters, and invalid whitespace or character
combinations, while preserving existing exclusions. Apply the same validation
behavior in the frontend, and add boundary and invalid-input tests covering both
implementations.
- Around line 459-464: Update the test around renderDialog to avoid asserting
the complete English description directly. Resolve the expected text through the
test i18n instance or query the rendered content using a role, label, or
translation-key semantic, while preserving the assertion that the storage model
description is rendered.
In
`@docs/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.md`:
- Line 47: Define a single non-Vertex policy for preserved Storage entries: in
docs/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.md:47
state that retained entries are hidden or disabled when the channel is not
Vertex AI; in
web/src/features/channels/components/dialogs/channel-test-dialog.tsx:873-906
gate Storage classification and testing on channel type 41; and in
docs/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md:457-469
add the corresponding non-Vertex regression test and implementation rule.
- Around line 104-108: 明确上游请求构造规范中的请求头和查询参数白名单:按对象操作定义允许透传的具体
header(包括内容、Range、条件请求、Content-Range、X-Goog-Hash 和 X-Goog-Meta-* 等),拒绝其他未知
header,并始终剥离 Authorization、Host 及 hop-by-hop headers;同样明确允许的查询参数集合,拒绝未知参数且禁止其影响
bucket 或上游主机。补充测试覆盖不支持的客户端 header 和 query parameter 输入。
In `@docs/vertex-ai-storage.md`:
- Around line 164-172: Extend the troubleshooting table in the documentation to
include rows for invalid_channel_credentials, channel_type_mismatch,
upstream_request_failed, and invalid_upstream_status. Describe each code’s
common cause and corresponding troubleshooting action, explicitly covering
invalid service-account JSON for invalid_channel_credentials.
In `@relay/channel/vertex/storage_proxy.go`:
- Around line 180-181: Update the gateway URL rewriting logic around
gateway.Path and gateway.RawPath to preserve a non-empty parsed gateway base
path by joining it with the VertexStorageRoutePrefix and expectedPath, including
the corresponding escaped path. If sub-path deployment is unsupported, instead
validate gatewayBaseURL before rewriting and reject URLs with a non-empty path
explicitly.
In
`@web/src/features/channels/components/__tests__/channel-test-routing.test.tsx`:
- Around line 183-193: Ensure fixture cleanup runs when tests fail: in
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx
ranges 183-193, 196-206, and 209-224, register each rendered fixture for
guaranteed unmounting rather than relying on the final assertion; in
web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx
range 125-166, guarantee root unmount, query-cache cleanup, and document cleanup
after failures using try/finally or the test framework cleanup hook.
In `@web/src/features/channels/components/data-table-row-actions.tsx`:
- Around line 179-187: Add aria-hidden="true" to the Gauge icon rendered in the
test connection button, while preserving the button’s existing aria-label for
the accessible name.
In `@web/src/features/channels/components/dialogs/channel-test-dialog.tsx`:
- Around line 889-895: Update the channel test dialog component around the GCS
StatusBadge to obtain t via useTranslation and pass t('GCS') as the label
instead of the hardcoded text; add the corresponding GCS key to every supported
locale file.
In `@web/src/i18n/locales/zh-TW.json`:
- Line 216: Update the bucket translations in the locale entries corresponding
to “Add storage bucket "{{value}}"” and the other affected entries to use the
project-approved term “儲存桶” consistently, replacing “儲存值區” without changing the
surrounding wording or placeholders.
---
Outside diff comments:
In `@web/src/features/channels/components/drawers/channel-mutate-drawer.tsx`:
- Around line 3550-3559: Update the ModelMappingEditor usage to pass
currentRegularModels as sourceModelOptions instead of currentModelsArray, while
leaving targetModelOptions and the existing form behavior unchanged.
---
Nitpick comments:
In `@controller/vertex_storage_channel_probe.go`:
- Around line 167-169: Update the non-success response handling in the probe
function around the visible StatusCode check to read a bounded prefix of the
Google Cloud Storage response body and include it in the returned error
alongside the status code. Preserve the existing success behavior and ensure
body-read failures do not replace the upstream status context.
In `@controller/vertex_storage_proxy_test.go`:
- Around line 223-265: Add a test alongside the existing relay proxy tests for
the invalid upstream status branch in relayVertexStorageProxy, using a
successful proxy response with StatusCode 0 and a tracking response body. Assert
that the handler returns 502 with the invalid_upstream_status error, does not
relay the upstream body, and closes the response body.
In `@controller/vertex_storage_proxy.go`:
- Around line 76-83: In the handler containing the object validation, merge the
empty-object and invalid-object checks into one branch guarded by the shared
GET/DELETE operation condition. Preserve the distinct object_required and
invalid_object responses and their early returns, while removing the duplicated
operation check.
In `@docs/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md`:
- Around line 265-277: Update the proxy request tests in Step 2 to explicitly
validate Go’s separate host fields: assert req.Host is empty or fixed,
req.Header.Get("Host") is empty, and req.URL.Host remains
"storage.googleapis.com". Keep these assertions alongside the existing
header-removal and preservation checks.
In `@model/ability.go`:
- Around line 63-73: Extract the repeated enabled-channel subquery into an
enabledChannelIDsOfType helper, preserving the existing status and
requiredChannelType predicates. Update both getPriority and getChannelQuery to
reuse this helper instead of building the Channel query inline.
In `@model/channel_cache.go`:
- Around line 220-232: Add a brief comment above filterChannelsByType
documenting that callers must hold channelSyncLock.RLock while reading
channelsIDM. Change its filtering logic to retain channel IDs absent from
channelsIDM, while continuing to retain only present channels whose Type matches
requiredChannelType.
In `@relay/channel/vertex/storage_proxy_test.go`:
- Around line 142-173: Extend the tests in
TestRewriteStorageResumableLocationRejectsUnsafeInput with cases for a storage
location containing userinfo, a storage location containing a fragment, and a
gatewayURL containing userinfo. Use otherwise valid values so each case
specifically exercises its corresponding rejection guard.
In `@router/relay_router_test.go`:
- Around line 180-184: Update the path validation around the expected[path]
lookup and method loop to compare the complete HTTP-method key set in pathItem
against methods, rejecting both missing and extra operations. Preserve the
existing per-method operation validation while ensuring documented paths cannot
contain uninspected methods.
In
`@web/src/features/channels/components/__tests__/channel-test-routing.test.tsx`:
- Around line 119-124: Update ChannelsStateProbe usage in
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx at
lines 119-124 to test the rendered dialog flow with React Testing Library,
asserting accessible dialog content after each user action rather than private
provider state. Update
web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx
at lines 153-162 to replace global DOM and data-vertex-storage-test-description
assertions with React Testing Library queries that verify visible storage probe
content.
- Around line 119-128: In
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx
lines 119-128, add explicit return types to ChannelsStateProbe and the
asynchronous renderActions helper; in lines 163-176, add explicit return types
to click and getChannelsState. In
web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx
lines 114-120, add an explicit return type to SeedCurrentChannel, using the
appropriate React component and Promise/utility types without changing behavior.
In
`@web/src/features/channels/components/drawers/sections/__tests__/vertex-storage-buckets-field.test.tsx`:
- Around line 19-71: Extend the existing VertexStorageBucketsField combobox
tests using the current node:test, happy-dom, and React act setup. Add
assertions for keyboard interaction and verify the combobox’s aria-expanded and
option aria-selected states before and after interaction, without introducing
another test runner or testing-library dependency.
In `@web/src/features/channels/lib/vertex-storage-models.ts`:
- Around line 26-36: Strengthen normalizeVertexStorageBucket validation to
enforce GCS bucket naming rules: allow only lowercase letters, digits, dashes,
underscores, and dots; require the name to start and end with a letter or digit;
and enforce the required length range. Preserve the existing trimming,
reserved-prefix, empty, dot-only, and URL/invalid-name checks while rejecting
values such as uppercase or whitespace-containing names before returning the
bucket.
🪄 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: cf78b5bc-1b84-4a88-a34c-e2034d41c23e
📒 Files selected for processing (47)
.gitignoreconstant/context_key.gocontroller/channel-test.gocontroller/relay.gocontroller/relay_channel_type_test.gocontroller/vertex_storage_channel_probe.gocontroller/vertex_storage_channel_probe_test.gocontroller/vertex_storage_proxy.gocontroller/vertex_storage_proxy_test.godocs/openapi/relay.jsondocs/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.mddocs/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.mddocs/vertex-ai-storage.mdmiddleware/distributor.gomiddleware/distributor_test.gomodel/ability.gomodel/channel_cache.gomodel/channel_type_selection_test.gorelay/channel/vertex/service_account.gorelay/channel/vertex/service_account_test.gorelay/channel/vertex/storage_proxy.gorelay/channel/vertex/storage_proxy_test.gorelay/constant/relay_mode.gorelay/constant/vertex_storage.gorelay/constant/vertex_storage_test.gorouter/relay-router.gorouter/relay_router_test.goservice/channel_select.goservice/channel_select_channel_type_test.goweb/src/features/channels/components/__tests__/channel-test-routing.test.tsxweb/src/features/channels/components/data-table-row-actions.tsxweb/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.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/__tests__/vertex-storage-buckets-field.test.tsxweb/src/features/channels/components/drawers/sections/index.tsweb/src/features/channels/components/drawers/sections/vertex-storage-buckets-field.tsxweb/src/features/channels/lib/__tests__/vertex-storage-models.test.tsweb/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
| 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('://') || /[\\/?#]/.test(bucket)) return null | ||
| return bucket | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Validate actual GCS bucket names, not only path syntax.
The planned validator returns every value that passes the small set of checks. It can accept invalid case, length, whitespace, or character combinations. The frontend can then persist a configuration that fails later during GCS access.
Use a complete GCS bucket-name validator on the server and mirror its behavior in the frontend. Add boundary and invalid-input tests.
🤖 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/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md`
around lines 99 - 105, Update normalizeVertexStorageBucket to validate complete
GCS bucket-name rules, including length, lowercase requirements, allowed
characters, boundary characters, and invalid whitespace or character
combinations, while preserving existing exclusions. Apply the same validation
behavior in the frontend, and add boundary and invalid-input tests covering both
implementations.
| ```tsx | ||
| renderDialog({ models: 'gemini-2.5-pro,storage:gs:bucket-a' }) | ||
| expect(screen.getByText('storage:gs:bucket-a')).toBeInTheDocument() | ||
| expect( | ||
| screen.getByText('GCS bucket: writes, reads, and deletes a temporary object') | ||
| ).toBeInTheDocument() |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Make the planned i18n assertion locale-safe.
The test hard-codes the complete English description. Resolve the expected value through the i18n test instance or use a translation-key semantic query.
As per coding guidelines, i18n tests should prefer roles, labels, or translation-key semantics and avoid a complete display string from one locale.
🤖 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/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md`
around lines 459 - 464, Update the test around renderDialog to avoid asserting
the complete English description directly. Resolve the expected text through the
test i18n instance or query the rendered content using a role, label, or
translation-key semantic, while preserving the assertion that the storage model
description is rendered.
Source: Coding guidelines
| storage:gs:example-bucket | ||
| ``` | ||
|
|
||
| 前端加载渠道时,将所有 `storage:gs:` 项从普通模型列表中拆出并去掉前缀回显;保存时重新添加前缀,与普通模型合并、去重后写回 `models`。切换到非 Vertex AI 类型时不主动删除已有存储桶项,避免临时切换造成数据丢失。 |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Define one policy for preserved Storage entries on non-Vertex channels.
The specification preserves these entries, but the plan and UI identify them by prefix while Storage testing requires Vertex AI type 41.
docs/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.md#L47-L47: state that retained Storage entries are hidden or disabled when the channel is not Vertex AI.web/src/features/channels/components/dialogs/channel-test-dialog.tsx#L873-L906: gate Storage classification and testing by channel type.docs/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md#L457-L469: add the non-Vertex regression test and matching implementation rule.
📍 Affects 3 files
docs/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.md#L47-L47(this comment)web/src/features/channels/components/dialogs/channel-test-dialog.tsx#L873-L906docs/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md#L457-L469
🤖 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/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.md`
at line 47, Define a single non-Vertex policy for preserved Storage entries: in
docs/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.md:47
state that retained entries are hidden or disabled when the channel is not
Vertex AI; in
web/src/features/channels/components/dialogs/channel-test-dialog.tsx:873-906
gate Storage classification and testing on channel type 41; and in
docs/superpowers/plans/2026-08-09-vertex-ai-storage-and-channel-test.md:457-469
add the corresponding non-Vertex regression test and implementation rule.
| 发送上游请求前必须丢弃客户端 `Authorization`、`Host` 和 hop-by-hop headers,并设置服务端获取的 GCS Bearer Token。允许透传内容、Range、条件请求、`Content-Range`、`X-Goog-Hash` 和 `X-Goog-Meta-*` 等对象操作相关头。 | ||
|
|
||
| 对象名来自 `*object`,允许包含目录形式的 `/`,但构造 GCS JSON API URL 时必须将完整对象名编码为单个 path segment,不能将对象名解释成额外的上游路由层级。 | ||
|
|
||
| 查询参数在固定主机和固定路径语义下透传,包括 `uploadType`、`name`、`alt`、`prefix`、`delimiter`、`pageToken`、generation 条件和 resumable session 参数。查询参数不得改变 bucket 或上游主机。 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Use explicit allowlists for forwarded headers and query parameters.
The phrases "等对象操作相关头" and "包括 ... 等" leave forwarding open-ended. A generic pass-through can forward unsupported controls or sensitive headers.
Define an operation-specific allowlist, reject unknown headers and query parameters, and test unsupported client inputs.
🤖 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/superpowers/specs/2026-08-09-vertex-ai-storage-and-channel-test-design.md`
around lines 104 - 108, 明确上游请求构造规范中的请求头和查询参数白名单:按对象操作定义允许透传的具体
header(包括内容、Range、条件请求、Content-Range、X-Goog-Hash 和 X-Goog-Meta-* 等),拒绝其他未知
header,并始终剥离 Authorization、Host 及 hop-by-hop headers;同样明确允许的查询参数集合,拒绝未知参数且禁止其影响
bucket 或上游主机。补充测试覆盖不支持的客户端 header 和 query parameter 输入。
| | 现象 | 常见原因 | 排查方式 | | ||
| | --- | --- | --- | | ||
| | `400 invalid_bucket` | bucket 包含路径、scheme、查询字符串或完整配置前缀 | 只传纯 bucket 名称 | | ||
| | `400 unsupported_key_type` | 渠道使用 Vertex AI API Key | 改用服务账号 JSON | | ||
| | `400 object_required` | 读取或删除路由缺少对象名 | 对完整对象名做 URL 编码后放入路径 | | ||
| | `403 bucket_not_allowed` | 所选渠道未精确配置目标 bucket | 检查渠道“存储桶”配置及 Token/用户组权限 | | ||
| | GCS `403` 响应 | 服务账号缺少 bucket IAM 权限 | 检查 bucket IAM 和服务账号身份 | | ||
| | `502 access_token_failed` | 服务账号 JSON、私钥、代理或 Google OAuth 异常 | 检查渠道凭证和 Proxy 配置,不要在日志中输出私钥 | | ||
| | `502 invalid_resumable_location` | 系统服务地址缺失,或 Google 返回的 session URL 未通过安全校验 | 配置正确的公开服务地址后重新初始化会话 | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the remaining proxy error codes to the troubleshooting table.
The handler also returns 500 invalid_channel_credentials, 500 channel_type_mismatch, 502 upstream_request_failed, and 502 invalid_upstream_status. invalid_channel_credentials is user-triggerable, because it occurs when the channel Key is not valid service account JSON. Readers who hit it find no entry in the table.
📝 Proposed rows
| `502 access_token_failed` | 服务账号 JSON、私钥、代理或 Google OAuth 异常 | 检查渠道凭证和 Proxy 配置,不要在日志中输出私钥 |
+| `500 invalid_channel_credentials` | 渠道 Key 不是有效的服务账号 JSON | 重新粘贴完整的服务账号 JSON 密钥 |
+| `502 upstream_request_failed` | 无法请求 Google Cloud Storage,或响应被安全校验拒绝 | 检查网络、Proxy 配置和 GCS 可用性 |
| `502 invalid_resumable_location` | 系统服务地址缺失,或 Google 返回的 session URL 未通过安全校验 | 配置正确的公开服务地址后重新初始化会话 |🤖 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` around lines 164 - 172, Extend the troubleshooting
table in the documentation to include rows for invalid_channel_credentials,
channel_type_mismatch, upstream_request_failed, and invalid_upstream_status.
Describe each code’s common cause and corresponding troubleshooting action,
explicitly covering invalid service-account JSON for
invalid_channel_credentials.
| gateway.Path = gatewayPath | ||
| gateway.RawPath = escapedGatewayPath |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The gateway base path is discarded.
gateway.Path is overwritten with VertexStorageRoutePrefix + expectedPath. If an operator deploys the gateway under a sub-path, for example https://example.com/api, the rewritten resumable location becomes https://example.com/vertexai/... and drops /api. The client then sends the resumable PUT to a path that does not exist.
If sub-path deployment is supported, join the parsed gateway path with the route prefix instead of replacing it. If it is not supported, reject a gatewayBaseURL that carries a non-empty path so the failure is explicit rather than silent.
🐛 Proposed fix that preserves a gateway sub-path
- escapedGatewayPath := relayconstant.VertexStorageRoutePrefix + expectedPath
+ basePath := strings.TrimSuffix(gateway.EscapedPath(), "/")
+ escapedGatewayPath := basePath + relayconstant.VertexStorageRoutePrefix + expectedPath
gatewayPath, err := url.PathUnescape(escapedGatewayPath)🤖 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 180 - 181, Update the
gateway URL rewriting logic around gateway.Path and gateway.RawPath to preserve
a non-empty parsed gateway base path by joining it with the
VertexStorageRoutePrefix and expectedPath, including the corresponding escaped
path. If sub-path deployment is unsupported, instead validate gatewayBaseURL
before rewriting and reject URLs with a non-empty path explicitly.
| test('table gauge opens the channel test dialog for the current channel', async () => { | ||
| const rendered = await renderActions('table') | ||
| const gauge = rendered.container.querySelector( | ||
| 'button[aria-label="Test Connection"]' | ||
| ) | ||
| assert.ok(gauge) | ||
|
|
||
| await click(gauge) | ||
|
|
||
| assert.equal(getChannelsState(rendered.container), 'test-channel:7') | ||
| await rendered.unmount() |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Register fixture cleanup before test assertions.
Each test clears the root, query cache, and document only after assertions pass. If rendering, interaction, or an assertion fails, stale global DOM state can affect later tests. Register cleanup at fixture creation with try/finally or the test framework cleanup hook.
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L183-L193: guaranteerendered.unmount()after the table-action test.web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L196-L206: guaranteerendered.unmount()after the card-action test.web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L209-L224: guaranteerendered.unmount()after the dropdown-action test.web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx#L125-L166: guarantee root unmount, query cache cleanup, and document cleanup after all failures.
As per coding guidelines: “每个测试独立初始化和清理全局状态、缓存、localStorage、mock 与定时器,不依赖执行顺序。”
📍 Affects 2 files
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L183-L193(this comment)web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L196-L206web/src/features/channels/components/__tests__/channel-test-routing.test.tsx#L209-L224web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx#L125-L166
🤖 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/__tests__/channel-test-routing.test.tsx`
around lines 183 - 193, Ensure fixture cleanup runs when tests fail: in
web/src/features/channels/components/__tests__/channel-test-routing.test.tsx
ranges 183-193, 196-206, and 209-224, register each rendered fixture for
guaranteed unmounting rather than relying on the final assertion; in
web/src/features/channels/components/dialogs/__tests__/channel-test-storage.test.tsx
range 125-166, guarantee root unmount, query-cache cleanup, and document cleanup
after failures using try/finally or the test framework cleanup hook.
Source: Coding guidelines
| onClick={(e) => { | ||
| e.stopPropagation() | ||
| handleTest() | ||
| }} | ||
| aria-label={t('Test Connection')} | ||
| /> | ||
| } | ||
| > | ||
| {isTesting ? ( | ||
| <Loader2 className='size-4 animate-spin' /> | ||
| ) : ( | ||
| <Gauge className='size-4' /> | ||
| )} | ||
| <Gauge className='size-4' /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Mark the test icon as decorative.
The button already has aria-label={t('Test Connection')}. Add aria-hidden='true' to Gauge to prevent duplicate announcements.
As per coding guidelines, decorative icons in web/**/*.{tsx,css,scss} must use aria-hidden="true".
Proposed fix
- <Gauge className='size-4' />
+ <Gauge aria-hidden='true' className='size-4' />📝 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.
| onClick={(e) => { | |
| e.stopPropagation() | |
| handleTest() | |
| }} | |
| aria-label={t('Test Connection')} | |
| /> | |
| } | |
| > | |
| {isTesting ? ( | |
| <Loader2 className='size-4 animate-spin' /> | |
| ) : ( | |
| <Gauge className='size-4' /> | |
| )} | |
| <Gauge className='size-4' /> | |
| onClick={(e) => { | |
| e.stopPropagation() | |
| handleTest() | |
| }} | |
| aria-label={t('Test Connection')} | |
| /> | |
| } | |
| > | |
| <Gauge aria-hidden='true' className='size-4' /> |
🤖 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/data-table-row-actions.tsx` around lines
179 - 187, Add aria-hidden="true" to the Gauge icon rendered in the test
connection button, while preserving the button’s existing aria-label for the
accessible name.
Source: Coding guidelines
| {isVertexStorage && ( | ||
| <StatusBadge | ||
| label='GCS' | ||
| variant='info' | ||
| size='sm' | ||
| copyable={false} | ||
| /> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Translate the GCS badge label.
label='GCS' is user-facing text. Use t('GCS') and add the key to all supported locale files.
As per coding guidelines, frontend user-facing text must use i18next/react-i18next; React components should call useTranslation() and t('English key').
Proposed fix
- label='GCS'
+ label={t('GCS')}📝 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 - 895, Update the channel test dialog component around the GCS
StatusBadge to obtain t via useTranslation and pass t('GCS') as the label
instead of the hardcoded text; add the corresponding GCS key to every supported
locale file.
Source: Coding guidelines
| "Add rules for a user group": "為用戶分組新增規則", | ||
| "Add selectable group": "新增可選分組", | ||
| "Add split": "新增分流", | ||
| "Add storage bucket \"{{value}}\"": "新增儲存值區「{{value}}」", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use one Traditional Chinese term for bucket.
Lines 216, 978, 1693, and 4341 use 儲存值區, while Line 2097 uses 儲存桶. The same GCS feature should use one consistent term. Replace 儲存值區 with the project-approved term. 儲存桶 matches the existing translation.
Proposed terminology fix
- "Add storage bucket \"{{value}}\"": "新增儲存值區「{{value}}」",
+ "Add storage bucket \"{{value}}": "新增儲存桶「{{value}}」",
- "Configure Google Cloud Storage buckets for this Vertex AI channel.": "為此 Vertex AI 渠道設定 Google Cloud Storage 儲存值區。",
+ "Configure Google Cloud Storage buckets for this Vertex AI channel.": "為此 Vertex AI 渠道設定 Google Cloud Storage 儲存桶。",
- "Enter storage bucket names": "輸入儲存值區名稱",
+ "Enter storage bucket names": "輸入儲存桶名稱",
- "Storage buckets": "儲存值區",
+ "Storage buckets": "儲存桶",Also applies to: 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 216, Update the bucket translations
in the locale entries corresponding to “Add storage bucket "{{value}}"” and the
other affected entries to use the project-approved term “儲存桶” consistently,
replacing “儲存值區” without changing the surrounding wording or placeholders.
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 ./relay/constant ./middleware ./relay/channel/vertex ./controller ./router -count=1go 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...HEAD说明:
bun run lint的全仓扫描仍会被当前upstream/main中大量非本 PR 文件的既有 oxlint 错误阻断;上述本 PR 涉及文件的定向 lint 已通过。Summary by CodeRabbit