feat(channel): add Cloudflare AI Gateway channel (type 58) - #4814
feat(channel): add Cloudflare AI Gateway channel (type 58)#4814xn030523 wants to merge 7 commits into
Conversation
Adds a new channel adapter cloudflare_aig that targets Cloudflare AI
Gateway's OpenAI-compatible endpoint (/compat/chat/completions,
/compat/embeddings, /compat/responses). The Other field stores
{account_id}/{gateway_id} which is composed into the upstream URL
https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat/...
Backend:
- New channel type 58 (Cloudflare AI Gateway) and API type registered
in constant/channel.go, constant/api_type.go, common/api_type.go,
middleware/distributor.go and relay/relay_adaptor.go.
- relay/channel/cloudflare_aig/adaptor.go reuses openai handlers for
responses and supports Claude relay format via ClaudeToOpenAIRequest.
- Channel is added to streamSupportedChannels so stream_options work.
- autoPrefixModel auto-prepends the required {provider}/ prefix
(anthropic/, openai/, google-ai-studio/, grok/, deepseek/, cohere/,
mistral/, workers-ai/) when the configured model name does not
already contain a '/' separator, so users can keep using bare model
names that match SDK expectations.
Frontend:
- web/default: channel constants, channel-type-config, mutate drawer
(new Other field with account_id/gateway_id placeholder) and i18n
entries for en/fr/ja/ru/vi/zh.
- web/classic: channel options entry, icon mapping, EditChannelModal
Other field for type 58.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Cloudflare AI Gateway (channel type 58): constants, backend relay adaptor and registration, middleware/stream support, frontend UI/config updates, data/Claude handling, and translations. ChangesCloudflare AI Gateway Channel Integration
Sequence Diagram(s)sequenceDiagram
participant Client
participant RelayDispatcher
participant Adaptor
participant DoApiRequest
participant CloudflareAPI
participant ResponseHandler
Client->>RelayDispatcher: inbound relay request (APITypeCloudflareAIGateway)
RelayDispatcher->>Adaptor: select cloudflare_aig.Adaptor
Adaptor->>Adaptor: ConvertRequest (autoPrefixModel, validate)
Adaptor->>Adaptor: SetupRequestHeader (cf-aig headers, auth)
Adaptor->>Adaptor: GetRequestURL (mode & format)
Adaptor->>DoApiRequest: DoApiRequest (HTTP)
DoApiRequest->>CloudflareAPI: HTTP call
CloudflareAPI->>ResponseHandler: response + cf-aig headers
ResponseHandler->>Client: parsed response + forwarded cf-aig headers
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 (5)
web/classic/src/helpers/render.jsx (1)
354-355: 💤 Low valueConsider using a distinct icon for Cloudflare AI Gateway.
Channel type 58 (Cloudflare AI Gateway) reuses the same
Cloudflare.Coloricon as channel type 39 (Cloudflare). While this might be intentional since both are Cloudflare services, using distinct icons could improve UX by making it easier to visually differentiate between the two channel types in the UI.🤖 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/classic/src/helpers/render.jsx` around lines 354 - 355, The Cloudflare AI Gateway case reuses Cloudflare.Color (case 58), making it indistinguishable from Cloudflare (case 39); update the switch in render.jsx to render a distinct icon for channel type 58 by replacing Cloudflare.Color with a new identifier (e.g., Cloudflare.AIGateway or Cloudflare.Gateway) and ensure that new icon is added/exported from the Cloudflare icon module (or create a new SVG component in the icons set) so the component reference resolves correctly; keep case 39 using Cloudflare.Color and update any exports/imports so the new icon is available to the render switch.web/default/src/i18n/locales/ja.json (1)
751-753: 🏗️ Heavy liftUse hierarchical i18n keys for new entries
These newly added keys are raw UI strings. Please switch to semantic hierarchical keys (for example under a
channels.cloudflareAiGateway.*namespace) and update references accordingly.As per coding guidelines, "Use hierarchical and semantically clear translation key names such as
dashboard.overview.titleand maintain naming consistency".🤖 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/i18n/locales/ja.json` around lines 751 - 753, The three new raw UI entries ("Cloudflare AI Gateway", "Account ID / Gateway ID *", and the format string "Format: {account_id}/{gateway_id}. The default gateway is \"default\".") should be converted to hierarchical i18n keys (e.g. channels.cloudflareAiGateway.title, channels.cloudflareAiGateway.accountLabel, channels.cloudflareAiGateway.formatHint) in the ja.json file; add those keys under a channels.cloudflareAiGateway namespace, move the Japanese strings to those keys, and then update all code references that directly used the raw strings to use the new i18n keys (e.g., lookup channels.cloudflareAiGateway.title/accountLabel/formatHint) so naming is consistent with the existing translation structure.web/default/src/i18n/locales/ru.json (1)
751-753: 🏗️ Heavy liftUse hierarchical i18n keys for new entries.
These new translation keys are phrase-based; please switch to semantic hierarchical keys (for example under a
channels.cloudflareAiGateway.*namespace) and update usages accordingly to match project i18n standards.As per coding guidelines, "Use hierarchical and semantically clear translation key names such as
dashboard.overview.titleand maintain naming consistency".🤖 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/i18n/locales/ru.json` around lines 751 - 753, Replace the phrase-based keys ("Cloudflare AI Gateway", "Account ID / Gateway ID *", "Format: {account_id}/{gateway_id}. The default gateway is \"default\".") with hierarchical keys under a channels.cloudflareAiGateway namespace (e.g., channels.cloudflareAiGateway.title, channels.cloudflareAiGateway.accountLabel, channels.cloudflareAiGateway.formatHint), move the Russian strings to those keys in ru.json, remove the old phrase keys, and update all code references that currently use the phrase keys to use channels.cloudflareAiGateway.title / .accountLabel / .formatHint so the project follows the semantic hierarchical i18n convention.web/default/src/i18n/locales/en.json (1)
751-753: ⚡ Quick winUse hierarchical i18n keys for new Cloudflare AI Gateway entries.
These new keys are phrase-based; please switch to semantic hierarchical keys (for example,
channel.cloudflareAigateway.name,channel.cloudflareAigateway.accountGatewayIdLabel,channel.cloudflareAigateway.accountGatewayIdHint) and update the corresponding references.As per coding guidelines, "Use hierarchical and semantically clear translation key names such as
dashboard.overview.titleand maintain naming consistency".🤖 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/i18n/locales/en.json` around lines 751 - 753, Replace the phrase-based translation keys with semantic hierarchical keys and update their usages: create keys like channel.cloudflareAigateway.name, channel.cloudflareAigateway.accountGatewayIdLabel, and channel.cloudflareAigateway.accountGatewayIdHint in the en.json (replacing "Cloudflare AI Gateway", "Account ID / Gateway ID *", and the format hint string respectively) and then update any code references that currently use the old phrase strings to use these new keys (search for the literal strings or existing lookups and replace them with the new hierarchical key names in UI components/forms).web/default/src/i18n/locales/vi.json (1)
751-753: ⚡ Quick winUse hierarchical i18n keys for the new Cloudflare strings.
These new entries use raw English sentence keys. Please switch to semantic keys (for example,
channel.cloudflareAiGateway.title,channel.cloudflareAiGateway.accountGatewayId,channel.cloudflareAiGateway.formatHint) to align with project i18n conventions.As per coding guidelines, use “hierarchical and semantically clear translation key names such as
dashboard.overview.titleand maintain naming consistency”.🤖 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/i18n/locales/vi.json` around lines 751 - 753, The three new raw-English i18n entries ("Cloudflare AI Gateway", "Account ID / Gateway ID *", and "Format: {account_id}/{gateway_id}. The default gateway is \"default\".") should be converted to hierarchical semantic keys; replace them with channel.cloudflareAiGateway.title, channel.cloudflareAiGateway.accountGatewayId, and channel.cloudflareAiGateway.formatHint respectively in vi.json and ensure any code/UI that reads the old literal keys is updated to use these new keys; keep the Vietnamese translations as-is but move them under the new hierarchical keys to match project i18n conventions.
🤖 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
`@web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx`:
- Around line 1538-1559: The "other" field (used when currentType === 58) lacks
format validation; update the channelFormSchema to validate the "other" string
contains a '/' with non-empty parts on both sides (e.g., account_id/gateway_id).
Add a refinement (e.g., z.string().refine(...) or equivalent validator for your
schema) that runs only when currentType === 58, splits the value on '/', ensures
at least two parts and that parts[0].trim() and parts[1].trim() are non-empty,
and returns a clear error message like "Format must be
{account_id}/{gateway_id}" to surface via the FormMessage for the form field
named "other".
In `@web/default/src/i18n/locales/fr.json`:
- Line 752: The French locale entry for the key "Account ID / Gateway ID *" in
fr.json is still in English; replace the value with the correct French
translation (e.g., "ID de compte / ID de passerelle *" or the preferred French
phrasing) so the JSON pair becomes "Account ID / Gateway ID *": "ID de compte /
ID de passerelle *"; ensure you keep the same JSON key and punctuation/asterisk
exactly and validate the JSON remains well-formed after editing.
In `@web/default/src/i18n/locales/ja.json`:
- Line 752: Replace the English value for the JSON key "Account ID / Gateway ID
*" in the ja locale with an appropriate Japanese translation; change the value
to "アカウントID / ゲートウェイID *" so the entry reads "Account ID / Gateway ID *":
"アカウントID / ゲートウェイID *".
In `@web/default/src/i18n/locales/ru.json`:
- Line 752: The value for the localization key "Account ID / Gateway ID *" in
ru.json is still English; replace the English string with a proper Russian
translation (for example: "ID аккаунта / ID шлюза *" or "Идентификатор аккаунта
/ Идентификатор шлюза *") so the Russian UI shows a fully localized label;
update the value for the exact key "Account ID / Gateway ID *" in the ru.json
locale file.
In `@web/default/src/i18n/locales/vi.json`:
- Line 752: The value for the key "Account ID / Gateway ID *" in vi.json is
still in English; replace the English string with the Vietnamese translation
(e.g., "ID tài khoản / ID cổng *") while preserving the exact key, punctuation
and the trailing asterisk so the JSON structure and usage by any consumers (the
"Account ID / Gateway ID *" entry) remain unchanged.
---
Nitpick comments:
In `@web/classic/src/helpers/render.jsx`:
- Around line 354-355: The Cloudflare AI Gateway case reuses Cloudflare.Color
(case 58), making it indistinguishable from Cloudflare (case 39); update the
switch in render.jsx to render a distinct icon for channel type 58 by replacing
Cloudflare.Color with a new identifier (e.g., Cloudflare.AIGateway or
Cloudflare.Gateway) and ensure that new icon is added/exported from the
Cloudflare icon module (or create a new SVG component in the icons set) so the
component reference resolves correctly; keep case 39 using Cloudflare.Color and
update any exports/imports so the new icon is available to the render switch.
In `@web/default/src/i18n/locales/en.json`:
- Around line 751-753: Replace the phrase-based translation keys with semantic
hierarchical keys and update their usages: create keys like
channel.cloudflareAigateway.name,
channel.cloudflareAigateway.accountGatewayIdLabel, and
channel.cloudflareAigateway.accountGatewayIdHint in the en.json (replacing
"Cloudflare AI Gateway", "Account ID / Gateway ID *", and the format hint string
respectively) and then update any code references that currently use the old
phrase strings to use these new keys (search for the literal strings or existing
lookups and replace them with the new hierarchical key names in UI
components/forms).
In `@web/default/src/i18n/locales/ja.json`:
- Around line 751-753: The three new raw UI entries ("Cloudflare AI Gateway",
"Account ID / Gateway ID *", and the format string "Format:
{account_id}/{gateway_id}. The default gateway is \"default\".") should be
converted to hierarchical i18n keys (e.g. channels.cloudflareAiGateway.title,
channels.cloudflareAiGateway.accountLabel,
channels.cloudflareAiGateway.formatHint) in the ja.json file; add those keys
under a channels.cloudflareAiGateway namespace, move the Japanese strings to
those keys, and then update all code references that directly used the raw
strings to use the new i18n keys (e.g., lookup
channels.cloudflareAiGateway.title/accountLabel/formatHint) so naming is
consistent with the existing translation structure.
In `@web/default/src/i18n/locales/ru.json`:
- Around line 751-753: Replace the phrase-based keys ("Cloudflare AI Gateway",
"Account ID / Gateway ID *", "Format: {account_id}/{gateway_id}. The default
gateway is \"default\".") with hierarchical keys under a
channels.cloudflareAiGateway namespace (e.g.,
channels.cloudflareAiGateway.title, channels.cloudflareAiGateway.accountLabel,
channels.cloudflareAiGateway.formatHint), move the Russian strings to those keys
in ru.json, remove the old phrase keys, and update all code references that
currently use the phrase keys to use channels.cloudflareAiGateway.title /
.accountLabel / .formatHint so the project follows the semantic hierarchical
i18n convention.
In `@web/default/src/i18n/locales/vi.json`:
- Around line 751-753: The three new raw-English i18n entries ("Cloudflare AI
Gateway", "Account ID / Gateway ID *", and "Format: {account_id}/{gateway_id}.
The default gateway is \"default\".") should be converted to hierarchical
semantic keys; replace them with channel.cloudflareAiGateway.title,
channel.cloudflareAiGateway.accountGatewayId, and
channel.cloudflareAiGateway.formatHint respectively in vi.json and ensure any
code/UI that reads the old literal keys is updated to use these new keys; keep
the Vietnamese translations as-is but move them under the new hierarchical keys
to match project i18n conventions.
🪄 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: 58012e83-a099-4beb-bee6-25fca30aa70d
📒 Files selected for processing (19)
common/api_type.goconstant/api_type.goconstant/channel.gomiddleware/distributor.gorelay/channel/cloudflare_aig/adaptor.gorelay/common/relay_info.gorelay/relay_adaptor.goweb/classic/src/components/table/channels/modals/EditChannelModal.jsxweb/classic/src/constants/channel.constants.jsweb/classic/src/helpers/render.jsxweb/default/src/features/channels/components/drawers/channel-mutate-drawer.tsxweb/default/src/features/channels/constants.tsweb/default/src/features/channels/lib/channel-type-config.tsweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh.json
| {/* Cloudflare AI Gateway (type 58) */} | ||
| {currentType === 58 && ( | ||
| <FormField | ||
| control={form.control} | ||
| name='other' | ||
| render={({ field }) => ( | ||
| <FormItem> | ||
| <FormLabel>{t('Account ID / Gateway ID *')}</FormLabel> | ||
| <FormControl> | ||
| <Input | ||
| placeholder={t('e.g., d6b5da8hk1awo8nap34ube6gh/default')} | ||
| {...field} | ||
| /> | ||
| </FormControl> | ||
| <FormDescription> | ||
| {t('Format: {account_id}/{gateway_id}. The default gateway is "default".')} | ||
| </FormDescription> | ||
| <FormMessage /> | ||
| </FormItem> | ||
| )} | ||
| /> | ||
| )} |
There was a problem hiding this comment.
Add format validation for the "other" field.
The "other" field expects the format {account_id}/{gateway_id} (e.g., abcd1234.../default), but there's no frontend validation to enforce this. Consider adding a validation rule to the form schema to ensure the field contains at least one / separator and non-empty parts before and after it.
✨ Example validation pattern
In the schema definition (likely in channelFormSchema), add validation for type 58's "other" field:
other: z.string()
.refine(
(val) => {
if (currentType === 58) {
const parts = val.split('/')
return parts.length >= 2 && parts[0].trim() && parts[1].trim()
}
return true
},
{ message: 'Format must be {account_id}/{gateway_id}' }
)This catches malformed input before submission, improving UX and reducing backend errors.
🤖 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/channels/components/drawers/channel-mutate-drawer.tsx`
around lines 1538 - 1559, The "other" field (used when currentType === 58) lacks
format validation; update the channelFormSchema to validate the "other" string
contains a '/' with non-empty parts on both sides (e.g., account_id/gateway_id).
Add a refinement (e.g., z.string().refine(...) or equivalent validator for your
schema) that runs only when currentType === 58, splits the value on '/', ensures
at least two parts and that parts[0].trim() and parts[1].trim() are non-empty,
and returns a clear error message like "Format must be
{account_id}/{gateway_id}" to surface via the FormMessage for the form field
named "other".
| "Codes copied!": "Codes copiés !", | ||
| "Codex": "Codex", | ||
| "Cloudflare AI Gateway": "Cloudflare AI Gateway", | ||
| "Account ID / Gateway ID *": "Account ID / Gateway ID *", |
There was a problem hiding this comment.
French locale string is not translated on Line 752
"Account ID / Gateway ID *" is still English in fr.json, which causes mixed-language UI in French locale.
💡 Proposed fix
- "Account ID / Gateway ID *": "Account ID / Gateway ID *",
+ "Account ID / Gateway ID *": "ID de compte / ID de passerelle *",🤖 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/i18n/locales/fr.json` at line 752, The French locale entry
for the key "Account ID / Gateway ID *" in fr.json is still in English; replace
the value with the correct French translation (e.g., "ID de compte / ID de
passerelle *" or the preferred French phrasing) so the JSON pair becomes
"Account ID / Gateway ID *": "ID de compte / ID de passerelle *"; ensure you
keep the same JSON key and punctuation/asterisk exactly and validate the JSON
remains well-formed after editing.
| "Codes copied!": "コードをコピーしました!", | ||
| "Codex": "Codex", | ||
| "Cloudflare AI Gateway": "Cloudflare AI Gateway", | ||
| "Account ID / Gateway ID *": "Account ID / Gateway ID *", |
There was a problem hiding this comment.
Localize the Japanese value at Line 752
The value is still English in the ja locale. Please translate it to Japanese for UI consistency.
🤖 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/i18n/locales/ja.json` at line 752, Replace the English value
for the JSON key "Account ID / Gateway ID *" in the ja locale with an
appropriate Japanese translation; change the value to "アカウントID / ゲートウェイID *" so
the entry reads "Account ID / Gateway ID *": "アカウントID / ゲートウェイID *".
| "Codes copied!": "Коды скопированы!", | ||
| "Codex": "Codex", | ||
| "Cloudflare AI Gateway": "Cloudflare AI Gateway", | ||
| "Account ID / Gateway ID *": "Account ID / Gateway ID *", |
There was a problem hiding this comment.
Russian locale value is still English.
"Account ID / Gateway ID *" is not localized in ru.json, so Russian UI will show mixed language text.
🤖 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/i18n/locales/ru.json` at line 752, The value for the
localization key "Account ID / Gateway ID *" in ru.json is still English;
replace the English string with a proper Russian translation (for example: "ID
аккаунта / ID шлюза *" or "Идентификатор аккаунта / Идентификатор шлюза *") so
the Russian UI shows a fully localized label; update the value for the exact key
"Account ID / Gateway ID *" in the ru.json locale file.
| "Codes copied!": "Đã sao chép mã!", | ||
| "Codex": "Codex", | ||
| "Cloudflare AI Gateway": "Cloudflare AI Gateway", | ||
| "Account ID / Gateway ID *": "Account ID / Gateway ID *", |
There was a problem hiding this comment.
Translate the Vietnamese locale value on Line 752.
"Account ID / Gateway ID *" is still in English in vi.json, so users on Vietnamese locale will see mixed-language UI text.
🤖 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/i18n/locales/vi.json` at line 752, The value for the key
"Account ID / Gateway ID *" in vi.json is still in English; replace the English
string with the Vietnamese translation (e.g., "ID tài khoản / ID cổng *") while
preserving the exact key, punctuation and the trailing asterisk so the JSON
structure and usage by any consumers (the "Account ID / Gateway ID *" entry)
remain unchanged.
…nd forward cf-aig-* headers - Route RelayFormatClaude to CF's native Anthropic endpoint instead of /compat/chat/completions, preserving cache_control and full anthropic usage detail (cache_creation_input_tokens / cache_read_input_tokens / ephemeral_5m/1h). - Forward client-supplied cf-aig-* request headers (cache-ttl/skip-cache/cache-key) so users can control CF AI Gateway response caching per-request. - Passthrough cf-aig-cache-status/cache-ttl/event-id/log-id/request-id and cf-ray response headers back to clients for observability. - Switch auth header per route: x-api-key + anthropic-version (+optional anthropic-beta) for Claude native, Authorization: Bearer for OpenAI compat. - ConvertClaudeRequest now passes the request through untouched; Claude responses are handled by claude.ClaudeHandler / ClaudeStreamHandler so prompt-cache fields survive end-to-end. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… before any data received When a Claude streaming request ends with received=0 + client_gone (HTTP/2 client cancelled before the first SSE chunk arrived), HandleStreamFinalResponse used to fill the empty Usage with locally-estimated prompt tokens via ResponseText2Usage, which then caused PostTextConsumeQuota to bill the user for the full estimated quota even though no upstream bytes were consumed. Detect this case (info.IsStream && info.ReceivedResponseCount == 0 && info.StreamStatus.EndReason == StreamEndReasonClientGone) and leave Usage zeroed so summary.TotalTokens == 0 causes the quota path to settle at 0, refunding any pre-consumed quota. Other abort reasons (EOF / scanner_error / timeout) still go through the existing fallback so genuine upstream failures continue to be billed against the local estimate as before. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/cloudflare_aig/adaptor.go`:
- Around line 124-128: The log currently prints raw cf-aig-* header values by
serializing the variable forwarded via fmt.Sprintf in the logger.LogInfo calls;
replace this with non-sensitive output (e.g., log only the header names, their
count, or a redacted placeholder) wherever forwarded is used (including the
other occurrence around lines 227-230). Update the logger.LogInfo(c, ...) calls
to compute a safe summary from forwarded (like len(forwarded) or a slice of
header names or values replaced with "<redacted>") and log that summary instead
of the raw forwarded value.
- Around line 68-75: The ApiVersion (info.ApiVersion) is currently only checked
for non-empty and can contain slashes that produce bad URLs; update the logic in
adaptor.go (around apiVersion variable and the isClaudeNativeRoute branch) to
validate that apiVersion matches the expected "account_id/gateway_id" pattern
(exactly one slash, non-empty segments) and return a descriptive error if it
does not; keep using isClaudeNativeRoute(info) to decide the anthropic path but
only after the apiVersion format check passes so malformed values like
"account", "/gateway", or "a/b/c" are rejected rather than interpolated into the
URL.
🪄 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: 82b42ba6-3a93-4432-b1af-858d9d18f2c6
📒 Files selected for processing (2)
relay/channel/claude/relay-claude.gorelay/channel/cloudflare_aig/adaptor.go
| apiVersion := info.ApiVersion | ||
| if apiVersion == "" { | ||
| return "", errors.New("account_id/gateway_id is required (set in Other field, format: {account_id}/{gateway_id})") | ||
| } | ||
|
|
||
| if isClaudeNativeRoute(info) { | ||
| return fmt.Sprintf("%s/v1/%s/anthropic/v1/messages", info.ChannelBaseUrl, apiVersion), nil | ||
| } |
There was a problem hiding this comment.
Validate the account_id/gateway_id format before composing the URL.
Any non-empty ApiVersion currently gets interpolated into the path, so values like account, /gateway, or account/gateway/extra silently produce malformed upstream routes.
Suggested fix
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
apiVersion := info.ApiVersion
- if apiVersion == "" {
+ if apiVersion == "" ||
+ strings.Count(apiVersion, "/") != 1 ||
+ strings.HasPrefix(apiVersion, "/") ||
+ strings.HasSuffix(apiVersion, "/") {
return "", errors.New("account_id/gateway_id is required (set in Other field, format: {account_id}/{gateway_id})")
}📝 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.
| apiVersion := info.ApiVersion | |
| if apiVersion == "" { | |
| return "", errors.New("account_id/gateway_id is required (set in Other field, format: {account_id}/{gateway_id})") | |
| } | |
| if isClaudeNativeRoute(info) { | |
| return fmt.Sprintf("%s/v1/%s/anthropic/v1/messages", info.ChannelBaseUrl, apiVersion), nil | |
| } | |
| apiVersion := info.ApiVersion | |
| if apiVersion == "" || | |
| strings.Count(apiVersion, "/") != 1 || | |
| strings.HasPrefix(apiVersion, "/") || | |
| strings.HasSuffix(apiVersion, "/") { | |
| return "", errors.New("account_id/gateway_id is required (set in Other field, format: {account_id}/{gateway_id})") | |
| } | |
| if isClaudeNativeRoute(info) { | |
| return fmt.Sprintf("%s/v1/%s/anthropic/v1/messages", info.ChannelBaseUrl, apiVersion), 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 `@relay/channel/cloudflare_aig/adaptor.go` around lines 68 - 75, The ApiVersion
(info.ApiVersion) is currently only checked for non-empty and can contain
slashes that produce bad URLs; update the logic in adaptor.go (around apiVersion
variable and the isClaudeNativeRoute branch) to validate that apiVersion matches
the expected "account_id/gateway_id" pattern (exactly one slash, non-empty
segments) and return a descriptive error if it does not; keep using
isClaudeNativeRoute(info) to decide the anthropic path but only after the
apiVersion format check passes so malformed values like "account", "/gateway",
or "a/b/c" are rejected rather than interpolated into the URL.
| if len(forwarded) > 0 { | ||
| logger.LogInfo(c, fmt.Sprintf("[cloudflare_aig] forwarding cf-aig-* request headers: %v", forwarded)) | ||
| } else { | ||
| logger.LogInfo(c, "[cloudflare_aig] no cf-aig-* request headers from client (cache will fall back to gateway default TTL)") | ||
| } |
There was a problem hiding this comment.
Stop logging raw cf-aig-* header values.
These logs serialize user/upstream-controlled header contents verbatim. That can leak cache metadata or other sensitive values into logs and also creates very high-cardinality log lines. Log only header names or counts, or redact the values first.
Suggested fix
- if len(forwarded) > 0 {
- logger.LogInfo(c, fmt.Sprintf("[cloudflare_aig] forwarding cf-aig-* request headers: %v", forwarded))
+ if len(forwarded) > 0 {
+ keys := make([]string, 0, len(forwarded))
+ for k := range forwarded {
+ keys = append(keys, k)
+ }
+ logger.LogInfo(c, fmt.Sprintf("[cloudflare_aig] forwarding cf-aig-* request headers: %v", keys))
} else {
logger.LogInfo(c, "[cloudflare_aig] no cf-aig-* request headers from client (cache will fall back to gateway default TTL)")
}
@@
- if len(seen) > 0 {
- logger.LogInfo(c, fmt.Sprintf("[cloudflare_aig] upstream cf-aig-* response headers: %v", seen))
+ if len(seen) > 0 {
+ keys := make([]string, 0, len(seen))
+ for k := range seen {
+ keys = append(keys, k)
+ }
+ logger.LogInfo(c, fmt.Sprintf("[cloudflare_aig] upstream cf-aig-* response headers: %v", keys))
} else {
logger.LogInfo(c, "[cloudflare_aig] upstream returned no cf-aig-* response headers")
}Also applies to: 227-230
🤖 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/cloudflare_aig/adaptor.go` around lines 124 - 128, The log
currently prints raw cf-aig-* header values by serializing the variable
forwarded via fmt.Sprintf in the logger.LogInfo calls; replace this with
non-sensitive output (e.g., log only the header names, their count, or a
redacted placeholder) wherever forwarded is used (including the other occurrence
around lines 227-230). Update the logger.LogInfo(c, ...) calls to compute a safe
summary from forwarded (like len(forwarded) or a slice of header names or values
replaced with "<redacted>") and log that summary instead of the raw forwarded
value.
…er logging behind DebugEnabled Previously the adaptor logged `forwarding cf-aig-* request headers` and `upstream cf-aig-* response headers` (or the `no headers` variants) on EVERY request, polluting production logs. Move these behind common.DebugEnabled so they only appear when explicitly running in debug mode. The map for collecting forwarded headers is also skipped entirely when debug is off to avoid the extra allocation. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… it from usage log status panel * relay/helper/stream_scanner.go: when a stream ends with reason=client_gone and no upstream errors recorded, write a single [INFO] `stream aborted by client` line instead of [ERR] `stream ended` — client-initiated disconnects (e.g. Claude CLI cancel/retry over HTTP/2) are not server-side faults and should not flood production error logs. * service/log_info_generate.go: skip emitting the `other.stream_status` block entirely on the same condition (client_gone + no errors). The frontend usage-logs detail panel only renders the red Badge when `other.stream_status && other.stream_status.status !== 'ok'`, so omitting the field makes those entries look like normal completions in the UI. * No billing / quota logic touched. Real `HasErrors` cases and other abnormal end reasons (timeout, scanner_error, panic, ping_fail) still log at [ERR] and still set `stream_status.status = 'error'` as before. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…s when thinking enabled
Two Anthropic 400 errors surfaced after routing more traffic through native /v1/messages:
1. dto/claude.go: ClaudeMediaMessage had no Data field, so the opaque payload of redacted_thinking content blocks was lost on unmarshal and never sent back upstream, producing "Invalid data in redacted_thinking block". Added Data string with json tag data,omitempty, plus IsError pointer for tool_result blocks so error markers round-trip.
2. relay/claude_handler.go: Anthropic now rejects temperature, top_p, top_k whenever extended thinking is enabled ("temperature is deprecated for this model"). The previous logic only zeroed them on the claude-opus-4-7 effort branch. After all model-specific thinking setup, unconditionally set Temperature, TopP, TopK to nil when request.Thinking is non-nil. Applies to every claude-format channel (claude, cf_aig, aws, vertex, etc.) because the cleanup runs before adaptor.ConvertClaudeRequest.
Note: channels with pass_through_body_enabled=true bypass both fixes since the raw client body is forwarded verbatim.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
relay/claude_handler.go (2)
66-68: 💤 Low valueOptional: consolidate redundant parameter stripping.
Lines 66-68 and 81-83 strip the same sampling parameters that are now unconditionally stripped at lines 111-113 when
Thinking != nil. Since the centralized stripping at lines 106-114 runs after all model-specific branches and covers all thinking-enabled requests, these earlier stripping operations are redundant.♻️ Optional cleanup to remove redundant stripping
Remove the parameter stripping from the model-specific branches since the centralized logic now handles all cases:
Lines 66-68 (Opus 4.7 with effort suffix):
if strings.HasPrefix(request.Model, "claude-opus-4-7") { // Opus 4.7 rejects non-default temperature/top_p/top_k with 400 // and defaults display to "omitted"; restore the 4.6 visible summary. request.Thinking.Display = "summarized" - request.Temperature = nil - request.TopP = nil - request.TopK = nilLines 81-83 (Opus 4.7 with -thinking suffix):
if strings.HasPrefix(baseModel, "claude-opus-4-7") { // Opus 4.7 rejects thinking.type="enabled"; use adaptive at high effort. request.Thinking = &dto.Thinking{Type: "adaptive", Display: "summarized"} request.OutputConfig = json.RawMessage(`{"effort":"high"}`) - request.Temperature = nil - request.TopP = nil - request.TopK = nilAlso applies to: 81-83
🤖 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/claude_handler.go` around lines 66 - 68, Remove the redundant early stripping of sampling parameters in the Opus model-specific branches: do not set request.Temperature, request.TopP or request.TopK to nil inside the Opus 4.7 branches (the ones handling the effort and -thinking suffix cases); rely on the centralized stripping that runs when request.Thinking != nil (the logic that nils these fields later) so behavior is unchanged—delete the duplicate nil assignments in the model-specific branches and keep the centralized removal under the Thinking check.
70-70: 💤 Low valueOptional: remove Temperature assignments that are immediately stripped.
Line 70 (Opus 4.6 with effort suffix) and line 97 (non-Opus-4.7 with -thinking suffix) set
Temperature = 1.0, but this value is immediately stripped by the centralized logic at line 111 sinceThinking != nilin both branches. These assignments have no effect.♻️ Optional cleanup to remove dead Temperature assignments
Remove the Temperature assignments that are overridden by the centralized stripping:
Line 70 (Opus 4.6 branch):
} else { - request.Temperature = common.GetPointer[float64](1.0) }Line 97 (non-Opus-4.7 thinking branch):
request.Thinking = &dto.Thinking{ Type: "enabled", BudgetTokens: common.GetPointer[int](int(float64(*request.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)), } - // TODO: 临时处理 - // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking - request.Temperature = common.GetPointer[float64](1.0)Also applies to: 97-97
🤖 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/claude_handler.go` at line 70, Remove the dead assignments of request.Temperature = common.GetPointer[float64](1.0) in the Opus 4.6 branch and the non-Opus-4.7 thinking branch since the centralized logic later checks Thinking != nil and always strips/overrides Temperature; specifically, delete the two temperature set lines where request.Temperature is immediately made irrelevant, leaving the Thinking-related assignments and centralized stripping logic intact (look for the request.Temperature assignments adjacent to the Opus 4.6 effort-suffix branch and the non-Opus-4.7 "-thinking" branch).
🤖 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.
Nitpick comments:
In `@relay/claude_handler.go`:
- Around line 66-68: Remove the redundant early stripping of sampling parameters
in the Opus model-specific branches: do not set request.Temperature,
request.TopP or request.TopK to nil inside the Opus 4.7 branches (the ones
handling the effort and -thinking suffix cases); rely on the centralized
stripping that runs when request.Thinking != nil (the logic that nils these
fields later) so behavior is unchanged—delete the duplicate nil assignments in
the model-specific branches and keep the centralized removal under the Thinking
check.
- Line 70: Remove the dead assignments of request.Temperature =
common.GetPointer[float64](1.0) in the Opus 4.6 branch and the non-Opus-4.7
thinking branch since the centralized logic later checks Thinking != nil and
always strips/overrides Temperature; specifically, delete the two temperature
set lines where request.Temperature is immediately made irrelevant, leaving the
Thinking-related assignments and centralized stripping logic intact (look for
the request.Temperature assignments adjacent to the Opus 4.6 effort-suffix
branch and the non-Opus-4.7 "-thinking" branch).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cd9b7895-c342-49f9-bf78-2665d59afec6
📒 Files selected for processing (2)
dto/claude.gorelay/claude_handler.go
…prefix-order 500s Anthropic now enforces strict prefix ordering for prompt-cache scopes: a cache_control block with scope:"global" is only valid when every preceding rendered block (tools, then earlier system entries, then earlier messages) also uses scope:"global". When any preceding block has a narrower scope or none, the request is rejected with HTTP 500: cache_control.scope: "global" is only valid when every preceding block is also globally scoped. A block with scope: "global" was found after content with a narrower cache scope... Many clients (Claude CLI variants, custom param_override rules, etc.) generate layouts that violate this in the common case where tools render before a system[0] block tagged scope:"global". Production traffic shows this firing on hundreds of requests per hour, with no observable workaround on the request path. This commit: - Adds AllowGlobalCacheScope to dto.ChannelOtherSettings (default false). When false, all cache_control.scope keys are recursively stripped from the request JSON in relay/common.RemoveDisabledFields, degrading the cache scope to the user-scoped default. Cache hit rates within a single account are unaffected; only cross-account global cache is given up. - Adds a stripCacheControlScope helper that walks any nested map/slice tree (covers system[], messages[].content[], tools[], and top-level cache_control) and deletes the scope key from every cache_control object encountered. Channels that genuinely need cross-account global caching can opt in by setting allow_global_cache_scope=true; pass_through_body_enabled=true continues to bypass all sanitization. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
51fdfc5 to
2b6f1df
Compare
Summary
Adds a new channel type Cloudflare AI Gateway (type
58) that targets Cloudflare's OpenAI-compatible aggregated endpoint:https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat/chat/completionsThe
Otherfield on the channel stores{account_id}/{gateway_id}and is composed into the upstream URL./compat/embeddingsand/compat/responsesare also supported.Docs: https://developers.cloudflare.com/ai-gateway/usage/chat-completion/
Backend
constant/channel.go/constant/api_type.go/common/api_type.go/middleware/distributor.go/relay/relay_adaptor.go: register channel type 58 and API type, default base URLhttps://gateway.ai.cloudflare.com.relay/channel/cloudflare_aig/adaptor.go: new adapter reusing OpenAI handlers andservice.ClaudeToOpenAIRequestfor Claude relay format.relay/common/relay_info.go: added tostreamSupportedChannelssostream_options.include_usageworks.autoPrefixModelauto-prepends the required{provider}/segment (anthropic, openai, google-ai-studio, grok, deepseek, cohere, mistral, workers-ai) when the channel-configured model name does not already contain a/. This allows users to keep using bare model names (claude-sonnet-4-5,gpt-4o, etc.) that match upstream SDK expectations, while still allowing explicit overrides likevertex/claude-sonnet-4-5.Frontend
web/default: channel constants, channel-type-config, mutate drawer with a newOtherfield (account_id/gateway_id), and i18n strings added for en/fr/ja/ru/vi/zh.web/classic:CHANNEL_OPTIONSentry, icon mapping,EditChannelModalOther field for type 58.Usage
Cloudflare AI Gateway.{account_id}/{gateway_id}(e.g.abcd1234.../default).claude-sonnet-4-5,gpt-4o,gemini-2.5-flash) — the adapter will auto-prefix. Or use explicitprovider/modelto override.Testing
go buildsuccessfully.claude-sonnet-4-5/anthropic/claude-sonnet-4-5and Claude Code (/v1/messages) with model mapping path.Notes
?beta=true) on Claude relay format is not appended for the compat endpoint;anthropic-betaheader would be the correct path if needed in future iterations.Summary by CodeRabbit
New Features
Bug Fixes