Skip to content

fix: hide implicit alibaba region#1914

Closed
steebchen wants to merge 5 commits intomainfrom
fix-alibaba-region-label
Closed

fix: hide implicit alibaba region#1914
steebchen wants to merge 5 commits intomainfrom
fix-alibaba-region-label

Conversation

@steebchen
Copy link
Copy Markdown
Member

@steebchen steebchen commented Mar 28, 2026

Summary

  • normalize routing metadata before logging so provider-level price-only-no-metrics scores do not show a single implicit region
  • keep region labels when the score table is actually comparing multiple regional rows for the same provider
  • add a focused unit regression test for the metadata normalization

Verification

  • pnpm vitest run apps/gateway/src/fallback.spec.ts apps/gateway/src/chat/tools/normalize-routing-metadata.spec.ts --no-file-parallelism
  • pnpm build

Notes

  • pnpm format still fails in this branch because of unrelated pre-existing ESLint errors in other packages (packages/actions and apps/worker).

Summary by CodeRabbit

  • New Features

    • Option to omit provider region details from selection and scoring results.
    • Model display appends a region suffix only when a provider was directly specified.
  • Behavior Changes

    • Routing now prefers provider-root (non‑regional) mappings over regional ones.
    • Fallback selection excludes entire providers rather than individual provider-region pairs.
  • Tests

    • Tests tightened to validate region-omission and provider-score expectations.

Copilot AI review requested due to automatic review settings March 28, 2026 18:56
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Prefer provider-root (no-region) mappings when selecting candidates, exclude fallback candidates by providerId only, add an optional includeProviderScoreRegions flag to omit regions from routing metadata, and only append :region to usedModel for direct-provider-specified selections.

Changes

Provider selection & Gateway routing

Layer / File(s) Summary
Options / Data Shape
packages/actions/src/get-cheapest-from-available-providers.ts
Added optional includeProviderScoreRegions?: boolean to ProviderSelectionOptions to control whether metadata.providerScores[].region is populated.
Core Selection Logic
packages/actions/src/get-cheapest-from-available-providers.ts
getCheapestFromAvailableProviders reads includeProviderScoreRegions and conditionally clears region in returned providerScores for both metrics-based (weighted) and price-only paths.
Gateway Routing / Preference Rule
apps/gateway/src/chat/chat.ts
Replaced preferConcreteRegionalMappings(...) with preferProviderRootMappings(...) across routing flows; collapse eligible routing providers to provider-root entries except when selectionReason === "direct-provider-specified".
Fallback Exclusion Logic
apps/gateway/src/chat/chat.ts
Fallback filtering (rate-limit, low-uptime, no-provider) now excludes by providerId only (no region pairing) and calls selection with includeProviderScoreRegions: false except for direct-specified selections.
Routing Metadata & usedModel Formatting
apps/gateway/src/chat/chat.ts
Constructs routingMetadata.providerScores with region present only when selectionReason === "direct-provider-specified"; usedModelFormatted appends :region only for direct-provider-specified selections with a usedRegion.
Tests
apps/gateway/src/fallback.spec.ts, packages/actions/src/models.spec.ts
Updated fallback tests to expect Alibaba providerScores entries with region === undefined and unqualified usedModel; added unit test verifying includeProviderScoreRegions: false causes returned providerScores to omit regions.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Gateway as Gateway (chat.ts)
    participant Selector as ProviderSelector (get-cheapest...)
    participant Provider as ProviderPool

    Client->>Gateway: Send generation request
    Gateway->>Selector: Build candidates (preferProviderRootMappings) and call getCheapest(..., includeProviderScoreRegions: false)
    Selector->>Provider: Request provider entries and scores
    Provider-->>Selector: Return provider entries (root + regional variants)
    Selector-->>Gateway: Return chosen provider + routingMetadata (providerScores with region omitted or present)
    Gateway-->>Client: Route request to chosen provider
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is partially related to the changeset. It mentions hiding the Alibaba region, but the actual changes involve a broader refactoring to prefer provider root mappings (without regions) across all providers during routing and fallback flows, not just Alibaba-specific fixes. Consider a more accurate title like 'fix: prefer provider root mappings in routing metadata' that better reflects the comprehensive nature of the changes affecting region handling across all providers.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-alibaba-region-label

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.

❤️ Share

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5ff8d0ea5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/gateway/src/chat/chat.ts Outdated
}

if (routingMetadata) {
routingMetadata = normalizeRoutingMetadataForLogging(routingMetadata);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Normalize metadata only after fallback selection

This mutates routingMetadata.providerScores before the retry loop, but selectNextProvider later matches scores to model mappings using both provider and region (p.providerId === score.providerId && p.region === score.region in apps/gateway/src/chat/tools/retry-with-fallback.ts). For price-only-no-metrics, normalizeRoutingMetadataForLogging drops region for single-row providers, so any provider whose mapping has a concrete region (e.g. alibaba/singapore) can no longer be selected on retry, causing fallback to stop early and potentially fail requests that previously would have retried successfully.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a small normalization step to routing metadata specifically for logging, so “price-only-no-metrics” score tables don’t misleadingly show a single implicit region (while still preserving region labels when multiple regional rows are compared).

Changes:

  • Introduce normalizeRoutingMetadataForLogging() to hide region on providerScores when a provider only has a single score row under price-only-no-metrics.
  • Apply the normalization to routingMetadata in chat.ts before it’s used for log entry creation.
  • Add a focused Vitest unit regression suite covering the normalization behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
apps/gateway/src/chat/tools/normalize-routing-metadata.ts Adds routing-metadata normalization logic for log output under price-only-no-metrics.
apps/gateway/src/chat/tools/normalize-routing-metadata.spec.ts Adds unit tests verifying collapsed-region hiding and multi-region preservation.
apps/gateway/src/chat/chat.ts Normalizes routing metadata before it’s passed into log entry creation paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

(score) => score.providerId === "alibaba",
);

expect(alibabaScore?.region).toBeUndefined();
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

This assertion can pass even if the provider score isn't found (e.g., if providerScores doesn’t contain an alibaba entry), because alibabaScore?.region will be undefined in that case. Add an explicit expectation that alibabaScore is defined (or use find(...)!) so the test actually validates the intended behavior.

Suggested change
expect(alibabaScore?.region).toBeUndefined();
expect(alibabaScore).toBeDefined();
expect(alibabaScore!.region).toBeUndefined();

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 1849-1851: The code overwrites runtime routing state by
reassigning routingMetadata with normalizeRoutingMetadataForLogging, which can
strip providerScores[*].region and break retry/region-failure dedupe; instead,
preserve the original routingMetadata and assign the normalized result to a
separate variable (e.g., routingMetadataForLogging) or pass a shallow/deep clone
into normalizeRoutingMetadataForLogging so retry selection logic still sees the
full providerScores with region; update places that log to use the new
logging-only variable and leave routingMetadata untouched for
retry/provider-region logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3d085e2e-264c-4c8d-b3b1-6538d206c335

📥 Commits

Reviewing files that changed from the base of the PR and between 1444f54 and a5ff8d0.

📒 Files selected for processing (3)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/normalize-routing-metadata.spec.ts
  • apps/gateway/src/chat/tools/normalize-routing-metadata.ts

Comment thread apps/gateway/src/chat/chat.ts Outdated
Comment on lines +1849 to +1851
if (routingMetadata) {
routingMetadata = normalizeRoutingMetadataForLogging(routingMetadata);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not overwrite runtime routing state with logging-only normalization.

Reassigning routingMetadata here can remove providerScores[*].region before retry selection runs, which may break provider-region failure deduping and cause fallback to re-pick failed targets.

💡 Safer direction
-if (routingMetadata) {
-	routingMetadata = normalizeRoutingMetadataForLogging(routingMetadata);
-}
+// Keep `routingMetadata` intact for retry/fallback logic.
+// Normalize only at log emission boundaries (e.g. inside `createLogEntry`
+// or immediately before passing metadata to it).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/chat.ts` around lines 1849 - 1851, The code overwrites
runtime routing state by reassigning routingMetadata with
normalizeRoutingMetadataForLogging, which can strip providerScores[*].region and
break retry/region-failure dedupe; instead, preserve the original
routingMetadata and assign the normalized result to a separate variable (e.g.,
routingMetadataForLogging) or pass a shallow/deep clone into
normalizeRoutingMetadataForLogging so retry selection logic still sees the full
providerScores with region; update places that log to use the new logging-only
variable and leave routingMetadata untouched for retry/provider-region logic.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 1827-1829: The current predicate sets includeProviderScoreRegions
whenever selectionReason === "direct-provider-specified", which leaks a single
implicit region; change the condition to only enable provider region emission
when there was an explicit region selection or there is an actual multi-region
comparison (e.g. selectionReason === "direct-provider-specified" &&
providerScoreRegions?.length > 1 OR an explicit-region flag is true). Update the
boolean expressions where includeProviderScoreRegions is computed (the instance
using selectionReason === "direct-provider-specified" and the other two
occurrences referenced in the comment) to check for explicit selection or
providerScoreRegions.length > 1 so single implicit rows no longer emit region
metadata.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 646f37a2-fc8a-4b6e-9bbc-0d778b5131ac

📥 Commits

Reviewing files that changed from the base of the PR and between a5ff8d0 and 0711e86.

📒 Files selected for processing (4)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/fallback.spec.ts
  • packages/actions/src/get-cheapest-from-available-providers.ts
  • packages/actions/src/models.spec.ts

Comment thread apps/gateway/src/chat/chat.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0711e8629e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{
metricsMap,
isStreaming: stream,
includeProviderScoreRegions: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve region in provider scores used for retries

Passing includeProviderScoreRegions: false here strips region data from routingMetadata.providerScores before the retry loop runs. In selectNextProvider (apps/gateway/src/chat/tools/retry-with-fallback.ts), the next mapping is resolved with p.providerId === score.providerId && p.region === score.region; when the chosen mappings are regional (for example alibaba with cn-beijing), score.region becomes undefined, no mapping matches, and fallback retries stop after the first retryable failure even when alternative providers exist.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bc1bf05aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Filter model providers to only those eligible for this request
const availableModelProviders = filterEligibleModelProviders(
preferConcreteRegionalMappings(expandedIamFilteredModelProviders),
preferProviderRootMappings(expandedIamFilteredModelProviders),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve regional rows when applying provider locks

Passing preferProviderRootMappings(expandedIamFilteredModelProviders) into filterEligibleModelProviders removes concrete region mappings before routing in the API-key/hybrid path, which makes providerLockedRegions effectively inert because that lock check only applies when provider.region is present. As a result, scoring and eligibility checks can run against the synthetic root row instead of the user’s locked region (e.g., cn-beijing), so routing decisions and pricing/capability filtering are based on the wrong region until execution-time token resolution.

Useful? React with 👍 / 👎.

Comment on lines 1448 to 1450
const providerIds = modelInfo.providers
.filter(
(p) => !(p.providerId === usedProvider && p.region === usedRegion),
) // Exclude the exact low-uptime provider+region pair
.filter((p) => p.providerId !== usedProvider)
.map((p) => p.providerId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude only the failed provider-region in uptime fallback

This change drops all mappings for usedProvider during low-uptime rerouting, rather than excluding only the failing provider+region pair. In a model that has multiple regions for one provider, a low-uptime region can no longer fail over to a healthier region of the same provider, and if no other providers exist this path now silently skips fallback even though a viable regional alternative is available.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
apps/gateway/src/chat/chat.ts (1)

1627-1638: ⚠️ Potential issue | 🟠 Major

Don't collapse away provider-key region locks.

This block just built providerLockedRegions, but preferProviderRootMappings(...) runs first and drops the locked regional row whenever that provider also has a root mapping. The router can then score the provider on its root/default mapping and later execute against the DB-locked region, so price/uptime/capability selection can be wrong. Apply the root-mapping collapse only to providers that are not present in providerLockedRegions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/chat.ts` around lines 1627 - 1638, The call to
preferProviderRootMappings(expandedIamFilteredModelProviders) collapses provider
root mappings and unintentionally drops providerLockedRegions entries when a
provider has a root mapping; update the logic so you only apply
preferProviderRootMappings to providers that are not present in
providerLockedRegions (i.e., compute a filteredProviders =
expandedIamFilteredModelProviders.filter(p =>
!providerLockedRegions.has(p.providerKey) or equivalent), pass that filtered
list into preferProviderRootMappings, and then merge its results with the
original providerLockedRegions so locked regional rows are preserved during
scoring and selection (ensure you reference preferProviderRootMappings,
expandedIamFilteredModelProviders, and providerLockedRegions in the change).
♻️ Duplicate comments (1)
apps/gateway/src/chat/chat.ts (1)

1822-1823: ⚠️ Potential issue | 🟠 Major

Don't treat every direct-provider request as an explicit region choice.

Using selectionReason === "direct-provider-specified" as the gate still emits providerScores[*].region and appends :region to usedModelFormatted when the flow resolved to a single implicit/default row. That recreates the leak this PR is trying to remove. Base the emitted-region flag on directProviderRegionWasExplicit || routingMetadataProviders.length > 1 (or equivalent “actual multi-region comparison” logic) and reuse that same flag for the score rows and formatted used model. If retries still need raw region data internally, keep it in the pre-normalized state and only apply this gate to the emitted/logged copy.

Also applies to: 1847-1857, 1929-1935, 2389-2394

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/chat.ts` around lines 1822 - 1823, The emitted-region
gating currently uses selectionReason === "direct-provider-specified", which
still leaks region when the choice was implicit; change the logic that sets
includeProviderScoreRegions (and any places that append :region to
usedModelFormatted or emit providerScores[*].region) to use a new boolean
computed from the actual multi-region condition (e.g.,
directProviderRegionWasExplicit || routingMetadataProviders.length > 1) and
reuse that same flag for both score rows and the formatted usedModel string;
keep raw region info available internally (pre-normalized) for retries but only
attach region to emitted/logged copies when the new flag is true, and apply this
change consistently to the other occurrences around the
includeProviderScoreRegions usage and usedModelFormatted construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 1627-1638: The call to
preferProviderRootMappings(expandedIamFilteredModelProviders) collapses provider
root mappings and unintentionally drops providerLockedRegions entries when a
provider has a root mapping; update the logic so you only apply
preferProviderRootMappings to providers that are not present in
providerLockedRegions (i.e., compute a filteredProviders =
expandedIamFilteredModelProviders.filter(p =>
!providerLockedRegions.has(p.providerKey) or equivalent), pass that filtered
list into preferProviderRootMappings, and then merge its results with the
original providerLockedRegions so locked regional rows are preserved during
scoring and selection (ensure you reference preferProviderRootMappings,
expandedIamFilteredModelProviders, and providerLockedRegions in the change).

---

Duplicate comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 1822-1823: The emitted-region gating currently uses
selectionReason === "direct-provider-specified", which still leaks region when
the choice was implicit; change the logic that sets includeProviderScoreRegions
(and any places that append :region to usedModelFormatted or emit
providerScores[*].region) to use a new boolean computed from the actual
multi-region condition (e.g., directProviderRegionWasExplicit ||
routingMetadataProviders.length > 1) and reuse that same flag for both score
rows and the formatted usedModel string; keep raw region info available
internally (pre-normalized) for retries but only attach region to emitted/logged
copies when the new flag is true, and apply this change consistently to the
other occurrences around the includeProviderScoreRegions usage and
usedModelFormatted construction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 65472ee1-bbc2-41c7-a961-c46aeeefee34

📥 Commits

Reviewing files that changed from the base of the PR and between 0711e86 and 2bc1bf0.

📒 Files selected for processing (2)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/fallback.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/gateway/src/fallback.spec.ts

@steebchen steebchen force-pushed the fix-alibaba-region-label branch from 2bc1bf0 to 6141a4e Compare March 29, 2026 19:28
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6141a4ed4d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 185 to 188
return providers.filter(
(mapping) =>
!providersWithRegions.has(mapping.providerId) || Boolean(mapping.region),
!providersWithRootMappings.has(mapping.providerId) || !mapping.region,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve concrete mapping before resolving key region

This filter now drops concrete regional rows whenever a provider has a root row, so routing selects a root modelName (no :region) even for regional providers. In API-key/hybrid mode, usedRegion is later populated from the provider key, but downstream validation still matches mappings by exact (providerId, modelName, region) (e.g. finalModelInfo.providers.find(...) used for max token and supported-parameter checks), which no longer matches and silently skips those guards. For regional providers like Alibaba, this can let invalid max_tokens/unsupported params through to upstream instead of being rejected by the gateway.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 1477-1489: Root-mapping is currently applied before eligibility
filtering which lets providers with locked regions be returned with region
undefined; fix by ensuring region-locks are enforced before/while root-mapping:
either call filterEligibleModelProviders on expandedIamFilteredModelProviders
(and related calls at the other sites) before preferProviderRootMappings, or
modify preferProviderRootMappings to preserve/propagate any explicit region
property from provider keys so that filterEligibleModelProviders still rejects
mismatched regions; update the call sites using filterEligibleModelProviders and
preferProviderRootMappings (referencing expandedIamFilteredModelProviders,
modelInfo.providers, availableProviders, response_format, hasImages, max_tokens,
reasoning_effort) accordingly so region locks cannot be bypassed.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a5a05fbc-93f1-4b46-85c9-c9e54a4cc105

📥 Commits

Reviewing files that changed from the base of the PR and between 2bc1bf0 and 6141a4e.

📒 Files selected for processing (4)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/fallback.spec.ts
  • packages/actions/src/get-cheapest-from-available-providers.ts
  • packages/actions/src/models.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/actions/src/models.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/gateway/src/fallback.spec.ts

Comment on lines +1477 to 1489
const availableModelProviders = filterEligibleModelProviders(
preferProviderRootMappings(expandedIamFilteredModelProviders),
{
allProviderVariants: modelInfo.providers,
availableProviders,
webSearchTool,
responseFormatType: response_format?.type,
hasImages,
maxTokens: max_tokens,
reasoningEffort: reasoning_effort,
},
).filter((provider) => provider.providerId !== usedProvider);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provider-key region locks can be bypassed after root-mapping preference.

At Line 1781 (and similarly at Line 1478 and Line 1624), root mappings are preferred before eligibility filtering. Since filterEligibleModelProviders only rejects mismatched non-empty regions, a locked provider can still pass with region: undefined, which weakens explicit region pinning from provider keys.

💡 Suggested fix
diff --git a/apps/gateway/src/chat/chat.ts b/apps/gateway/src/chat/chat.ts
@@
 		const lockedRegion = options.providerLockedRegions?.get(
 			provider.providerId,
 		);
-		if (lockedRegion && provider.region && provider.region !== lockedRegion) {
-			return false;
-		}
+		if (lockedRegion) {
+			// Enforce explicit provider-key region pin.
+			if (!provider.region || provider.region !== lockedRegion) {
+				return false;
+			}
+		}

Also applies to: 1624-1634, 1781-1791

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/chat.ts` around lines 1477 - 1489, Root-mapping is
currently applied before eligibility filtering which lets providers with locked
regions be returned with region undefined; fix by ensuring region-locks are
enforced before/while root-mapping: either call filterEligibleModelProviders on
expandedIamFilteredModelProviders (and related calls at the other sites) before
preferProviderRootMappings, or modify preferProviderRootMappings to
preserve/propagate any explicit region property from provider keys so that
filterEligibleModelProviders still rejects mismatched regions; update the call
sites using filterEligibleModelProviders and preferProviderRootMappings
(referencing expandedIamFilteredModelProviders, modelInfo.providers,
availableProviders, response_format, hasImages, max_tokens, reasoning_effort)
accordingly so region locks cannot be bypassed.

Resolved conflicts in chat.ts and
get-cheapest-from-available-providers.ts by combining
includeProviderScoreRegions (PR) with promptTokens and
addContentFilterRoutingMetadata from main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

Resolved merge conflicts with origin/main.

Conflicts in 2 files:

packages/actions/src/get-cheapest-from-available-providers.ts

  • ProviderSelectionOptions interface: combined the PR's includeProviderScoreRegions?: boolean with main's promptTokens?: number field. Also kept the new findProviderMapping and providerSupportsCaching helper functions added by main.
  • Function body: kept both includeProviderScoreRegions and promptTokens extractions from options.

apps/gateway/src/chat/chat.ts

  • 5 getCheapestFromAvailableProviders call sites: merged PR's includeProviderScoreRegions: false (or conditional) with main's promptTokens: routingPromptTokens — both options are now passed.
  • Routing metadata assignment: used main's addContentFilterRoutingMetadata wrapper and getNoFallbackRoutingMetadata utility, while preserving PR's region-filtering logic (region: includeRoutingScoreRegions ? score.region : undefined).

TypeScript compiles cleanly and all 95 unit tests pass.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/gateway/src/chat/chat.ts (2)

2460-2471: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply region locks before collapsing to provider-root mappings.

With a provider-key region pin, collapsing to preferProviderRootMappings(...) here can leave only the root row, and the current lock check still accepts region: undefined. That means routing can ignore the pinned region’s price/metrics and even succeed when the locked region has no matching mapping.

💡 Possible fix
-			const availableModelProviders = filterEligibleModelProviders(
-				preferProviderRootMappings(expandedIamFilteredModelProviders),
+			const eligibleExpandedProviders = filterEligibleModelProviders(
+				expandedIamFilteredModelProviders,
 				{
 					allProviderVariants: modelInfo.providers,
 					availableProviders,
 					providerLockedRegions,
 					webSearchTool,
 					responseFormatType: response_format?.type,
 					hasImages,
 					maxTokens: max_tokens,
 					reasoningEffort: reasoning_effort,
 				},
 			);
+			const availableModelProviders = preferProviderRootMappings(
+				eligibleExpandedProviders,
+			);
🤖 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 `@apps/gateway/src/chat/chat.ts` around lines 2460 - 2471, The collapse to
provider-root mappings via preferProviderRootMappings is happening before region
lock checks, allowing region: undefined rows to pass; move the region-lock
filtering to run against the full expanded provider list
(expandedIamFilteredModelProviders) before calling preferProviderRootMappings,
or alternatively apply providerLockedRegions filtering inside
preferProviderRootMappings so that region-pinned provider keys are honored;
update the call site around filterEligibleModelProviders/
preferProviderRootMappings so providerLockedRegions (and any region pin logic)
filter out entries with mismatched regions prior to collapsing to root mappings.

2718-2773: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Single-row direct-provider scores still leak implicit regions.

includeProviderScoreRegions and includeRoutingScoreRegions are still keyed only off selectionReason === "direct-provider-specified". After credits/hybrid filtering or capability filtering leaves one effective regional row, providerScores[*].region exposes the implicit/default region again.

💡 Possible fix
+		const regionalScoreRowCount = routingMetadataProviders.filter(
+			(provider) => Boolean(provider.region),
+		).length;
+		const includeRoutingScoreRegions =
+			directProviderRegionWasExplicit || regionalScoreRowCount > 1;
+
 		const weightedScores =
 			selectionReason === "direct-provider-specified" &&
 			directProviderRegionWasExplicit
 				? null
 				: getCheapestFromAvailableProviders(
 						routingMetadataProviders,
 						modelInfo as ModelDefinition & {
 							id: string;
 							output?: string[];
 						},
 						{
 							metricsMap,
 							isStreaming: stream,
-							includeProviderScoreRegions:
-								selectionReason === "direct-provider-specified",
+							includeProviderScoreRegions: includeRoutingScoreRegions,
 							promptTokens: routingPromptTokens,
 						},
 					);
@@
-		const includeRoutingScoreRegions =
-			selectionReason === "direct-provider-specified";
-
 		routingMetadata = addContentFilterRoutingMetadata(
 			{
 				availableProviders: routingMetadataProviders.map((p) => p.providerId),
🤖 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 `@apps/gateway/src/chat/chat.ts` around lines 2718 - 2773, The provider score
regions leak because both includeProviderScoreRegions (passed into
getCheapestFromAvailableProviders) and includeRoutingScoreRegions are only
checking selectionReason === "direct-provider-specified" and not whether the
region was actually explicit; change both checks to require
directProviderRegionWasExplicit as well (i.e., selectionReason ===
"direct-provider-specified" && directProviderRegionWasExplicit), update the call
site that passes includeProviderScoreRegions into
getCheapestFromAvailableProviders and the local includeRoutingScoreRegions used
when mapping providerScores so implicit/default regions are suppressed unless
the region was explicitly specified.
🤖 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 `@apps/gateway/src/chat/chat.ts`:
- Around line 2850-2856: The formatted model string is including a region even
when that region was inferred; change the includeUsedModelRegion condition so it
is true only when the selection was explicitly provider-specified and the region
itself is explicitly set (not inferred). Concretely, update
includeUsedModelRegion to check both routingMetadata?.selectionReason ===
"direct-provider-specified" and a flag/property indicating the region was
explicit (e.g., routingMetadata?.regionIsExplicit or an equivalent boolean you
add when populating usedRegion), then use that boolean when calling
formatUsedModelForDisplay (same change also at the second occurrence around
usedModelFormatted at the other location).

---

Duplicate comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 2460-2471: The collapse to provider-root mappings via
preferProviderRootMappings is happening before region lock checks, allowing
region: undefined rows to pass; move the region-lock filtering to run against
the full expanded provider list (expandedIamFilteredModelProviders) before
calling preferProviderRootMappings, or alternatively apply providerLockedRegions
filtering inside preferProviderRootMappings so that region-pinned provider keys
are honored; update the call site around filterEligibleModelProviders/
preferProviderRootMappings so providerLockedRegions (and any region pin logic)
filter out entries with mismatched regions prior to collapsing to root mappings.
- Around line 2718-2773: The provider score regions leak because both
includeProviderScoreRegions (passed into getCheapestFromAvailableProviders) and
includeRoutingScoreRegions are only checking selectionReason ===
"direct-provider-specified" and not whether the region was actually explicit;
change both checks to require directProviderRegionWasExplicit as well (i.e.,
selectionReason === "direct-provider-specified" &&
directProviderRegionWasExplicit), update the call site that passes
includeProviderScoreRegions into getCheapestFromAvailableProviders and the local
includeRoutingScoreRegions used when mapping providerScores so implicit/default
regions are suppressed unless the region was explicitly specified.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e09dc792-7667-4343-adfb-6989a88d00b8

📥 Commits

Reviewing files that changed from the base of the PR and between 6141a4e and 07d991f.

📒 Files selected for processing (2)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/fallback.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/gateway/src/fallback.spec.ts

Comment on lines +2850 to +2856
const includeUsedModelRegion =
routingMetadata?.selectionReason === "direct-provider-specified";
let usedModelFormatted = formatUsedModelForDisplay(
usedProvider,
usedRegion ? `${baseModelName}:${usedRegion}` : baseModelName,
includeUsedModelRegion && usedRegion
? `${baseModelName}:${usedRegion}`
: baseModelName,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep usedModelFormatted regionless when the region was inferred.

This boolean now follows direct-provider-specified alone, so once usedRegion is filled from a default/implicit region, logs still show model:region. That reintroduces the hidden-region leak even if providerScores get normalized.

💡 Possible fix
-	const includeUsedModelRegion =
-		routingMetadata?.selectionReason === "direct-provider-specified";
+	const includeUsedModelRegion =
+		routingMetadata?.selectionReason === "direct-provider-specified" &&
+		routingMetadata.providerScores.some(
+			(score) => score.providerId === usedProvider && Boolean(score.region),
+		);

Also applies to: 3397-3402

🤖 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 `@apps/gateway/src/chat/chat.ts` around lines 2850 - 2856, The formatted model
string is including a region even when that region was inferred; change the
includeUsedModelRegion condition so it is true only when the selection was
explicitly provider-specified and the region itself is explicitly set (not
inferred). Concretely, update includeUsedModelRegion to check both
routingMetadata?.selectionReason === "direct-provider-specified" and a
flag/property indicating the region was explicit (e.g.,
routingMetadata?.regionIsExplicit or an equivalent boolean you add when
populating usedRegion), then use that boolean when calling
formatUsedModelForDisplay (same change also at the second occurrence around
usedModelFormatted at the other location).

@steebchen steebchen closed this May 7, 2026
@steebchen steebchen deleted the fix-alibaba-region-label branch May 7, 2026 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants