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
Copy Markdown
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

@chatgpt-codex-connector
Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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.

Comment thread sdk/api/handlers/handlers.go Outdated
if len(providers) == 0 {
if stripped := stripModelVariantSuffix(baseModel); stripped != baseModel {
providers = util.GetProviderName(stripped)
resolvedModelName = stripModelVariantSuffix(resolvedModelName)
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.

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 thread internal/registry/model_registry.go Outdated
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
Copy Markdown
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 thread sdk/api/handlers/handlers.go Outdated
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
Copy Markdown
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
Copy Markdown

@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
Copy Markdown

@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
Copy Markdown

@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
Copy Markdown
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.

@thebtf thebtf force-pushed the fix/customtools-variant-suffix branch from 2ce6ef6 to 1745453 Compare April 10, 2026 09:45
thebtf added a commit to thebtf/CLIProxyAPIPlus that referenced this pull request Apr 10, 2026
Google's Gemini CLI appends `-customtools` to model names when it
detects an API key (CPA's key), but CPA routes gemini-cli traffic
through cloudcode-pa.googleapis.com/v1internal (OAuth) which rejects
the variant suffix with 404. `-customtools` is only a behavioural
fine-tune that prioritises custom tools over bash — same weights,
same capabilities — so stripping it for provider and auth lookup is
safe.

Introduce registry.StripModelVariantSuffix as the single source of
truth for variant normalization. It handles every known form:
terminal ("foo-customtools"), thinking-budget-adjacent
("foo-customtools(8192)", "foo-customtools(high)"), and chained
segments ("foo-customtools-thinking"). The match is anchored at a
segment boundary ('-', '(', or end-of-string) so it never strips
partial identifiers.

Call sites:
  - sdk/api/handlers/handlers.go: getRequestDetails() falls back to
    the stripped baseModel for provider lookup, then normalizes
    resolvedModelName so the downstream request drops the variant.
  - internal/registry/model_registry.go: ClientSupportsModel() uses
    the helper so auth selection matches clients registered with the
    base model.

Includes unit tests for all documented variants and idempotency.

Upstream PR: router-for-me#1958
thebtf added a commit to thebtf/CLIProxyAPIPlus that referenced this pull request Apr 10, 2026
Google's Gemini CLI appends `-customtools` to model names when it
detects an API key (CPA's key), but CPA routes gemini-cli traffic
through cloudcode-pa.googleapis.com/v1internal (OAuth) which rejects
the variant suffix with 404. `-customtools` is only a behavioural
fine-tune that prioritises custom tools over bash — same weights,
same capabilities — so stripping it for provider and auth lookup is
safe.

Introduce registry.StripModelVariantSuffix as the single source of
truth for variant normalization. It handles every known form:
terminal ("foo-customtools"), thinking-budget-adjacent
("foo-customtools(8192)", "foo-customtools(high)"), and chained
segments ("foo-customtools-thinking"). The match is anchored at a
segment boundary ('-', '(', or end-of-string) so it never strips
partial identifiers.

Call sites:
  - sdk/api/handlers/handlers.go: getRequestDetails() falls back to
    the stripped baseModel for provider lookup, then normalizes
    resolvedModelName so the downstream request drops the variant.
  - internal/registry/model_registry.go: ClientSupportsModel() uses
    the helper so auth selection matches clients registered with the
    base model.

Includes unit tests for all documented variants and idempotency.

Upstream PR: router-for-me#1958
@thebtf
Copy link
Copy Markdown
Contributor Author

thebtf commented Apr 10, 2026

@luispater @xkonjin thanks for the review — the blocking correctness gap is now fixed, force-pushed to fix/customtools-variant-suffix.

Changes

Normalized strip handles every suffix position, not only terminal:

gemini-3.1-pro-preview-customtools              -> gemini-3.1-pro-preview
gemini-3.1-pro-preview-customtools(8192)        -> gemini-3.1-pro-preview(8192)
gemini-3.1-pro-preview-customtools(high)        -> gemini-3.1-pro-preview(high)
gemini-3.1-pro-preview-customtools(auto)        -> gemini-3.1-pro-preview(auto)
gemini-2.0-flash-customtools-thinking           -> gemini-2.0-flash-thinking
gemini-2.0-flash-customtools-thinking(4096)     -> gemini-2.0-flash-thinking(4096)
gemini-2.0-flash-thinking-exp-customtools(8192) -> gemini-2.0-flash-thinking-exp(8192)

The match is anchored at a segment boundary (-, (, or end-of-string) so partial identifiers like gemini-customtoolsextra never strip.

Deduplicated into a shared helper as you requested. Single source of truth:

internal/registry/model_variants.go
  -> StripModelVariantSuffix(model string) string

Both sdk/api/handlers/handlers.go (getRequestDetails) and internal/registry/model_registry.go (ClientSupportsModel) now call this exported function instead of keeping local copies.

Also addressed xkonjin's concern about resolvedModelName mutation: the strip now only runs on resolvedModelName after the provider lookup against the stripped baseModel succeeds, so we never mutate a resolved name unless a real variant match was confirmed.

Regression tests in internal/registry/model_variants_test.go cover:

  • terminal, thinking-budget (int/level/auto), chained segments, thinking-exp+customtools order
  • idempotency (Strip(Strip(x)) == Strip(x))
  • no-partial-match guard (gemini-customtoolsextra stays unchanged)
  • empty input

Non-blocking follow-up

The /v1/responses websocket capability probe still uses the old lookup path. Happy to take that in a separate PR once the normalization helper is merged so the same StripModelVariantSuffix can be reused there.

thebtf added a commit to thebtf/CLIProxyAPIPlus that referenced this pull request Apr 12, 2026
Google's Gemini CLI appends `-customtools` to model names when it
detects an API key (CPA's key), but CPA routes gemini-cli traffic
through cloudcode-pa.googleapis.com/v1internal (OAuth) which rejects
the variant suffix with 404. `-customtools` is only a behavioural
fine-tune that prioritises custom tools over bash — same weights,
same capabilities — so stripping it for provider and auth lookup is
safe.

Introduce registry.StripModelVariantSuffix as the single source of
truth for variant normalization. It handles every known form:
terminal ("foo-customtools"), thinking-budget-adjacent
("foo-customtools(8192)", "foo-customtools(high)"), and chained
segments ("foo-customtools-thinking"). The match is anchored at a
segment boundary ('-', '(', or end-of-string) so it never strips
partial identifiers.

Call sites:
  - sdk/api/handlers/handlers.go: getRequestDetails() falls back to
    the stripped baseModel for provider lookup, then normalizes
    resolvedModelName so the downstream request drops the variant.
  - internal/registry/model_registry.go: ClientSupportsModel() uses
    the helper so auth selection matches clients registered with the
    base model.

Includes unit tests for all documented variants and idempotency.

Upstream PR: router-for-me#1958
@thebtf thebtf force-pushed the fix/customtools-variant-suffix branch from 1745453 to 6aac1b0 Compare April 12, 2026 18:36
thebtf added a commit to thebtf/CLIProxyAPIPlus that referenced this pull request Apr 12, 2026
Google's Gemini CLI appends `-customtools` to model names when it
detects an API key (CPA's key), but CPA routes gemini-cli traffic
through cloudcode-pa.googleapis.com/v1internal (OAuth) which rejects
the variant suffix with 404. `-customtools` is only a behavioural
fine-tune that prioritises custom tools over bash — same weights,
same capabilities — so stripping it for provider and auth lookup is
safe.

Introduce registry.StripModelVariantSuffix as the single source of
truth for variant normalization. It handles every known form:
terminal ("foo-customtools"), thinking-budget-adjacent
("foo-customtools(8192)", "foo-customtools(high)"), and chained
segments ("foo-customtools-thinking"). The match is anchored at a
segment boundary ('-', '(', or end-of-string) so it never strips
partial identifiers.

Call sites:
  - sdk/api/handlers/handlers.go: getRequestDetails() falls back to
    the stripped baseModel for provider lookup, then normalizes
    resolvedModelName so the downstream request drops the variant.
  - internal/registry/model_registry.go: ClientSupportsModel() uses
    the helper so auth selection matches clients registered with the
    base model.

Includes unit tests for all documented variants and idempotency.

Upstream PR: router-for-me#1958
Google's Gemini CLI appends `-customtools` to model names when it
detects an API key (CPA's key), but CPA routes gemini-cli traffic
through cloudcode-pa.googleapis.com/v1internal (OAuth) which rejects
the variant suffix with 404. `-customtools` is only a behavioural
fine-tune that prioritises custom tools over bash — same weights,
same capabilities — so stripping it for provider and auth lookup is
safe.

Introduce registry.StripModelVariantSuffix as the single source of
truth for variant normalization. It handles every known form:
terminal ("foo-customtools"), thinking-budget-adjacent
("foo-customtools(8192)", "foo-customtools(high)"), and chained
segments ("foo-customtools-thinking"). The match is anchored at a
segment boundary ('-', '(', or end-of-string) so it never strips
partial identifiers.

Call sites:
  - sdk/api/handlers/handlers.go: getRequestDetails() falls back to
    the stripped baseModel for provider lookup, then normalizes
    resolvedModelName so the downstream request drops the variant.
  - internal/registry/model_registry.go: ClientSupportsModel() uses
    the helper so auth selection matches clients registered with the
    base model.

Includes unit tests for all documented variants and idempotency.

Upstream PR: router-for-me#1958
@thebtf thebtf force-pushed the fix/customtools-variant-suffix branch from 6aac1b0 to 4190413 Compare April 17, 2026 02:46
thebtf added a commit to thebtf/CLIProxyAPIPlus that referenced this pull request Apr 17, 2026
Google's Gemini CLI appends `-customtools` to model names when it
detects an API key (CPA's key), but CPA routes gemini-cli traffic
through cloudcode-pa.googleapis.com/v1internal (OAuth) which rejects
the variant suffix with 404. `-customtools` is only a behavioural
fine-tune that prioritises custom tools over bash — same weights,
same capabilities — so stripping it for provider and auth lookup is
safe.

Introduce registry.StripModelVariantSuffix as the single source of
truth for variant normalization. It handles every known form:
terminal ("foo-customtools"), thinking-budget-adjacent
("foo-customtools(8192)", "foo-customtools(high)"), and chained
segments ("foo-customtools-thinking"). The match is anchored at a
segment boundary ('-', '(', or end-of-string) so it never strips
partial identifiers.

Call sites:
  - sdk/api/handlers/handlers.go: getRequestDetails() falls back to
    the stripped baseModel for provider lookup, then normalizes
    resolvedModelName so the downstream request drops the variant.
  - internal/registry/model_registry.go: ClientSupportsModel() uses
    the helper so auth selection matches clients registered with the
    base model.

Includes unit tests for all documented variants and idempotency.

Upstream PR: router-for-me#1958
xj999 pushed a commit to xj999/CLIProxyAPI that referenced this pull request Apr 22, 2026
Google's Gemini CLI appends `-customtools` to model names when it
detects an API key (CPA's key), but CPA routes gemini-cli traffic
through cloudcode-pa.googleapis.com/v1internal (OAuth) which rejects
the variant suffix with 404. `-customtools` is only a behavioural
fine-tune that prioritises custom tools over bash — same weights,
same capabilities — so stripping it for provider and auth lookup is
safe.

Introduce registry.StripModelVariantSuffix as the single source of
truth for variant normalization. It handles every known form:
terminal ("foo-customtools"), thinking-budget-adjacent
("foo-customtools(8192)", "foo-customtools(high)"), and chained
segments ("foo-customtools-thinking"). The match is anchored at a
segment boundary ('-', '(', or end-of-string) so it never strips
partial identifiers.

Call sites:
  - sdk/api/handlers/handlers.go: getRequestDetails() falls back to
    the stripped baseModel for provider lookup, then normalizes
    resolvedModelName so the downstream request drops the variant.
  - internal/registry/model_registry.go: ClientSupportsModel() uses
    the helper so auth selection matches clients registered with the
    base model.

Includes unit tests for all documented variants and idempotency.

Upstream PR: router-for-me#1958
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