Skip to content

fix: strip -customtools variant suffix for provider and auth lookup#1958

Open
thebtf wants to merge 1 commit intorouter-for-me:mainfrom
thebtf:fix/customtools-variant-suffix
Open

fix: strip -customtools variant suffix for provider and auth lookup#1958
thebtf wants to merge 1 commit intorouter-for-me:mainfrom
thebtf:fix/customtools-variant-suffix

Conversation

@thebtf
Copy link
Contributor

@thebtf thebtf commented Mar 8, 2026

Summary

  • Google's Gemini CLI appends -customtools to model names for a behavioral fine-tune variant. CPA doesn't recognize these suffixed names, causing 502 errors for all Gemini CLI users when this suffix is active.
  • Adds stripModelVariantSuffix() fallback at two points:
    1. getRequestDetails() in handlers.go — provider lookup
    2. ClientSupportsModel() in model_registry.go — auth selection
  • The suffix is stripped only when the original name yields no matches, preserving existing behavior.

Files changed

  • sdk/api/handlers/handlers.go — variant suffix stripping in provider lookup
  • internal/registry/model_registry.go — variant suffix stripping in client model support check

Test plan

  • Send request with model gemini-2.5-pro-preview-05-06-customtools through CPA
  • Verify provider is resolved correctly (no 502)
  • Verify standard model names without suffix still work as before

Google's Gemini CLI appends "-customtools" to model names for a
behavioral fine-tune that prioritizes custom tools over bash. CPA does
not recognize these suffixed names, causing 502 errors.

Add stripModelVariantSuffix() at two points:
1. getRequestDetails (handlers.go) — provider lookup fallback
2. ClientSupportsModel (model_registry.go) — auth selection fallback

The suffix is stripped only when the original name yields no matches,
preserving existing behavior for standard model names.
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where Google's Gemini CLI appends a -customtools suffix to model names, leading to 502 errors when these models are used. The changes introduce a robust fallback mechanism to recognize and correctly process these suffixed model names during both provider lookup and client model support checks, ensuring seamless operation for Gemini CLI users without altering behavior for standard model requests.

Highlights

  • Model Variant Suffix Stripping: Introduced a mechanism to strip the -customtools variant suffix from Google Gemini model names to ensure compatibility.
  • Provider Lookup Fix: Implemented this suffix stripping as a fallback during provider lookup in getRequestDetails() to prevent 502 errors for Gemini CLI users.
  • Authentication Selection Enhancement: Applied the same suffix stripping logic during client model support checks in ClientSupportsModel() for correct authentication selection.
  • Preservation of Existing Behavior: Ensured that the suffix stripping only occurs if the original model name does not yield any matches, preserving existing behavior for standard model names.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • internal/registry/model_registry.go
    • Added logic to strip model variant suffixes for client model support checks.
  • sdk/api/handlers/handlers.go
    • Added logic to strip model variant suffixes for provider lookup.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly identifies and addresses an issue with model names having a -customtools suffix. The approach of stripping the suffix as a fallback is sound. However, the implementation introduces code duplication for the stripping logic across two different packages. Additionally, there is a critical bug in sdk/api/handlers/handlers.go where the suffix stripping fails for model names that also include a thinking suffix. I've provided comments with suggestions to address these issues.

if len(providers) == 0 {
if stripped := stripModelVariantSuffix(baseModel); stripped != baseModel {
providers = util.GetProviderName(stripped)
resolvedModelName = stripModelVariantSuffix(resolvedModelName)
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

There's a potential bug here. The stripModelVariantSuffix function uses strings.HasSuffix to find and remove the variant suffix. If resolvedModelName contains a thinking suffix (e.g., (8192)), strings.HasSuffix will fail to find the variant suffix (e.g., -customtools), and resolvedModelName will not be stripped as intended. This would cause issues with providers that don't accept variant suffixes.

A more robust way to strip the variant suffix while preserving the thinking suffix would be to replace the baseModel part within resolvedModelName with the stripped version.

Suggested change
resolvedModelName = stripModelVariantSuffix(resolvedModelName)
resolvedModelName = strings.Replace(resolvedModelName, baseModel, stripped, 1)

Comment on lines +758 to +770
// knownModelVariantSuffixes lists suffixes that Google appends to model names
// for specialised fine-tune variants (e.g. "-customtools" for tool-calling).
// These variants share the same provider/executor as the base model.
var knownModelVariantSuffixes = []string{"-customtools"}

func stripModelVariantSuffix(model string) string {
for _, suffix := range knownModelVariantSuffixes {
if strings.HasSuffix(model, suffix) {
return strings.TrimSuffix(model, suffix)
}
}
return model
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This helper function and the knownModelVariantSuffixes variable are also defined in sdk/api/handlers/handlers.go. To avoid code duplication and improve maintainability, they should be moved to a shared package. The internal/thinking/suffix.go file seems like a suitable location, as it already handles model name suffix parsing. You would then need to add an import for internal/thinking in this file and use the shared function.

Comment on lines +826 to +838
// knownModelVariantSuffixes lists suffixes that Google appends to model names
// for specialised fine-tune variants (e.g. "-customtools" for tool-calling).
// These variants share the same provider/executor as the base model.
var knownModelVariantSuffixes = []string{"-customtools"}

func stripModelVariantSuffix(model string) string {
for _, suffix := range knownModelVariantSuffixes {
if strings.HasSuffix(model, suffix) {
return strings.TrimSuffix(model, suffix)
}
}
return model
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This helper function and the knownModelVariantSuffixes variable are duplicated from internal/registry/model_registry.go. To avoid code duplication and improve maintainability, they should be moved to a shared package like internal/thinking/suffix.go. Since this file already imports the thinking package, you can remove this duplicated code and call the function from there.

Copy link

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

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

Code Review

Overall: ⚠️ Needs fix before merge — logic works in the common case but has a confirmed bug for thinking+customtools combinations.

What's good

  • Fallback approach (strip suffix → retry provider lookup) is the right pattern instead of adding duplicates to model lists.
  • Stripping from both and is correct — OAuth routes reject suffixed names, so the resolved name must be stripped too.
  • fallback in registry is clean and isolated.

Confirmed bug (flagged by gemini-code-assist, needs fix)

**Ordering of suffix stripping for thinking models in ** — if a model name has both a thinking suffix and -customtools (e.g. gemini-2.0-flash-thinking-exp-customtools), stripModelVariantSuffix only strips -customtools, leaving gemini-2.0-flash-thinking-exp. That's correct. But if the suffix ordering is reversed (e.g. gemini-2.0-flash-customtools-thinking), the function returns the model unchanged because HasSuffix for -customtools fails. This is a latent ordering fragility.

The bigger issue: resolvedModelName may already have been transformed (thinking suffix stripped, version pinned, etc.) by the time we call stripModelVariantSuffix on it. Stripping from resolvedModelName without knowing its current form could produce an incorrect base name. Safer: strip from baseModel only for provider lookup, and strip from modelID input in registry — don't mutate resolvedModelName in the fallback path.

Code duplication

The knownModelVariantSuffixes var and stripModelVariantSuffix function are copy-pasted verbatim between internal/registry/model_registry.go and sdk/api/handlers/handlers.go. Move to a shared util package and import in both.

Non-blocking

  • stripModelVariantSuffix iterates the suffix list on every call. With one entry this is negligible, but if the suffix list grows, pre-compile with a regex or strings.Replacer.
  • Consider a unit test with a thinking+customtools composite model name to lock in the expected behavior.

Copy link

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

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

Code Review

Overall: ⚠️ Needs fix before merge — logic works in the common case but has a confirmed bug for thinking+customtools combinations.

What's good

  • Fallback approach (strip suffix, retry provider lookup) is the right pattern instead of adding duplicates to model lists.
  • Stripping from both baseModel and resolvedModelName is correct — OAuth routes reject suffixed names, so the resolved name must be stripped too.
  • ClientSupportsModel fallback in registry is clean and isolated.

Confirmed bug (flagged by gemini-code-assist, needs fix)

Ordering of suffix stripping for thinking models in handlers.go — if a model name has both a thinking suffix and -customtools (e.g. gemini-2.0-flash-thinking-exp-customtools), stripModelVariantSuffix only strips -customtools, leaving gemini-2.0-flash-thinking-exp. That's correct for this ordering. But if the suffix ordering is reversed (e.g. gemini-2.0-flash-customtools-thinking), the function returns the model unchanged because HasSuffix for -customtools fails. Latent ordering fragility.

Bigger issue: resolvedModelName may already have been transformed (thinking suffix stripped, version pinned, etc.) before we call stripModelVariantSuffix on it. Stripping from resolvedModelName without knowing its current form could produce an incorrect base name. Safer: strip from baseModel only for provider lookup; strip from the raw modelID in registry — don't mutate resolvedModelName in the fallback path.

Code duplication

knownModelVariantSuffixes and stripModelVariantSuffix are copy-pasted verbatim between internal/registry/model_registry.go and sdk/api/handlers/handlers.go. Move to a shared util package and import in both.

Non-blocking

  • stripModelVariantSuffix iterates the suffix list on every call. With one entry this is negligible, but if the list grows, consider pre-compiling.
  • Add a unit test with a thinking+customtools composite model name to lock in the expected behavior and catch future regressions.

Copy link

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

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

This fixes the simple *-customtools case, but it still misses one important path: models with a thinking suffix.

In getRequestDetails(), provider lookup is done against baseModel, so gemini-foo-customtools(8192) can now resolve its provider. But the code then normalizes the outbound model with stripModelVariantSuffix(resolvedModelName). That call is against the full string, so when resolvedModelName still has a trailing thinking suffix, HasSuffix("-customtools") is false and the variant suffix is preserved. That means the downstream CPA/cloudcode-pa request can still receive the unsupported -customtools model name and 404.

I think the fix needs to strip the variant on the parsed model portion, then re-attach any thinking suffix, and it needs a regression test for something like ...-customtools(8192). Without that, the most failure-prone path still slips through.

Copy link
Collaborator

@luispater luispater left a comment

Choose a reason for hiding this comment

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

Summary:
The main fallback direction looks right, but there is still one correctness gap before this is safe to merge.

Key findings:

  • Blocking: stripModelVariantSuffix() only works when -customtools is the terminal suffix. We already support combined forms such as gemini-...-customtools(high) / (auto), so those requests can still flow downstream with the variant suffix intact and still miss provider/auth lookup. Please add regression coverage for the combined-suffix case and normalize that path as well.
  • Non-blocking: /v1/responses websocket capability probing still uses the old provider/model lookup path, so suffixed models there still take inconsistent fallback behavior.

Test plan:

  • go test ./...

Follow-up:

  • Reuse the same normalization helper across the request path, auth selection path, and websocket capability probe so the suffix handling stays consistent.

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