Skip to content

fix(provider-wizard): allow multiple custom OpenAI-compatible providers#389

Closed
KunjShah95 wants to merge 4 commits into
Gitlawb:mainfrom
KunjShah95:fix/multiple-custom-openai-providers
Closed

fix(provider-wizard): allow multiple custom OpenAI-compatible providers#389
KunjShah95 wants to merge 4 commits into
Gitlawb:mainfrom
KunjShah95:fix/multiple-custom-openai-providers

Conversation

@KunjShah95

@KunjShah95 KunjShah95 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #375

Problem: \wizardProviderStoredKey\ matched saved providers by CatalogID. Since ALL custom OpenAI-compatible providers share CatalogID \custom-openai-compatible, setting up one blocked configuring a second.

Fix:

  1. Skip CatalogID matching for generic/custom providers (they all share the same ID)
  2. When replacing a key for a generic provider, route through endpoint/name steps so the user enters a new base URL and name

Summary by CodeRabbit

  • Bug Fixes
    • Improved saved provider matching to better handle generic/custom OpenAI-compatible provider IDs, reducing incorrect keep/replace/remove prompts.
    • Updated the “Replace” flow to clear stale connection details and route to the correct next step (endpoint when required; otherwise credentials).
  • Tests
    • Increased OAuth callback polling/completion timeouts.
    • Added test coverage for generic OpenAI-compatible provider replace routing behavior.

Copilot AI review requested due to automatic review settings July 2, 2026 12:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ef816fa7-703f-4991-a686-3076e41c7370

📥 Commits

Reviewing files that changed from the base of the PR and between 74d61e0 and dd6ddf1.

📒 Files selected for processing (1)
  • internal/tui/provider_wizard_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tui/provider_wizard_test.go

Walkthrough

Provider wizard saved-key matching now skips CatalogID checks for generic providers, and the Replace action clears more state before routing to the next wizard step. MCP OAuth login tests also allow longer time for callback and completion.

Changes

Provider Wizard Fix

Layer / File(s) Summary
Stored key matching and replace routing
internal/tui/provider_wizard.go
CatalogID matching is skipped for generic provider IDs, and the Replace path clears baseURL and profileName before routing to the endpoint step when needed or the credential step otherwise.
Generic replace path test
internal/tui/provider_wizard_test.go
Adds coverage for replacing a generic OpenAI-compatible provider and advancing the wizard to the endpoint step.

MCP OAuth Login Test Timing

Layer / File(s) Summary
Login timeout adjustments
internal/cli/mcp_oauth_test.go
Extends the callback polling deadline and the overall completion timeout in the login-token test.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • Gitlawb/zero#161: Related provider wizard onboarding work in the same TUI flow and saved-key handling area.

Suggested reviewers: anandh8x, Vasanthdev2004

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The MCP OAuth test timeout increases are unrelated to the provider-wizard issue and appear out of scope. Move the MCP OAuth timeout adjustments to a separate PR, or revert them unless they are required for this change.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: supporting multiple custom OpenAI-compatible providers.
Linked Issues check ✅ Passed The provider wizard now avoids CatalogID collisions for generic providers and adds coverage for the replacement flow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
internal/tui/provider_wizard.go (1)

939-952: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add coverage for the Replace → endpoint path

TestProviderWizardManageKeyReplaceAndKeep still only hits the credential-step branch because the test builds providerWizardState without providers, so currentProvider() is zero-value and providerWizardNeedsEndpoint(...) stays false. Add a case with a custom-openai-compatible or custom-anthropic-compatible provider so Replace exercises the new endpoint routing too.

🤖 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 `@internal/tui/provider_wizard.go` around lines 939 - 952, Add test coverage
for the Replace flow’s endpoint-routing path in the provider wizard. Update
TestProviderWizardManageKeyReplaceAndKeep so it doesn’t only exercise the
credential-step branch: build providerWizardState with a real provider entry so
currentProvider() is populated and providerWizardNeedsEndpoint(...) can become
true. Add a Replace case using a custom-openai-compatible or
custom-anthropic-compatible provider and assert wizardProviderStoredKey/current
provider handling covers the endpoint path too.
🤖 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 `@internal/tui/provider_wizard.go`:
- Around line 964-974: The Replace branch in providerWizardUpdate now routes to
providerWizardStepEndpoint when providerWizardNeedsEndpoint(currentProvider())
is true, but the existing TestProviderWizardManageKeyReplaceAndKeep never sets
wizard.providers so currentProvider() stays zero-value and only the
credential-step path is covered. Update
TestProviderWizardManageKeyReplaceAndKeep in provider_wizard_test.go, or add a
dedicated case, to populate wizard.providers with a custom-openai-compatible
descriptor and assert that the Replace flow reaches providerWizardStepEndpoint
instead of only providerWizardStepCredential.

---

Outside diff comments:
In `@internal/tui/provider_wizard.go`:
- Around line 939-952: Add test coverage for the Replace flow’s endpoint-routing
path in the provider wizard. Update TestProviderWizardManageKeyReplaceAndKeep so
it doesn’t only exercise the credential-step branch: build providerWizardState
with a real provider entry so currentProvider() is populated and
providerWizardNeedsEndpoint(...) can become true. Add a Replace case using a
custom-openai-compatible or custom-anthropic-compatible provider and assert
wizardProviderStoredKey/current provider handling covers the endpoint path too.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2547d457-edd5-4768-a06b-40c8caf72953

📥 Commits

Reviewing files that changed from the base of the PR and between 39e7100 and 7f681d9.

📒 Files selected for processing (1)
  • internal/tui/provider_wizard.go

Comment on lines 964 to 974
case 1: // Replace
wizard.apiKey = ""
wizard.err = ""
wizard.step = providerWizardStepCredential
wizard.baseURL = ""
wizard.profileName = ""
if providerWizardNeedsEndpoint(wizard.currentProvider()) {
wizard.step = providerWizardStepEndpoint
} else {
wizard.step = providerWizardStepCredential
}
return m, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add test coverage for the new endpoint-step routing.

The Replace branch's conditional routing to providerWizardStepEndpoint (Lines 969-970) is the core fix enabling users to enter a new base URL for a second custom provider, but TestProviderWizardManageKeyReplaceAndKeep in provider_wizard_test.go (context snippet 4) never populates wizard.providers, so currentProvider() returns a zero-value descriptor and providerWizardNeedsEndpoint always evaluates to false there — only the else credential-step path is exercised. The behavior this PR is meant to add is currently unverified by tests.

Consider extending the test (or adding a new case) with wizard.providers set to a custom-openai-compatible-ID descriptor so the Replace path to providerWizardStepEndpoint is asserted.

🤖 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 `@internal/tui/provider_wizard.go` around lines 964 - 974, The Replace branch
in providerWizardUpdate now routes to providerWizardStepEndpoint when
providerWizardNeedsEndpoint(currentProvider()) is true, but the existing
TestProviderWizardManageKeyReplaceAndKeep never sets wizard.providers so
currentProvider() stays zero-value and only the credential-step path is covered.
Update TestProviderWizardManageKeyReplaceAndKeep in provider_wizard_test.go, or
add a dedicated case, to populate wizard.providers with a
custom-openai-compatible descriptor and assert that the Replace flow reaches
providerWizardStepEndpoint instead of only providerWizardStepCredential.

@Vasanthdev2004

Copy link
Copy Markdown
Collaborator

fix coderabbit issues @KunjShah95

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/tui/provider_wizard_test.go (1)

1170-1189: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen test to also verify state-clearing on Replace.

Test only checks the step transition to providerWizardStepEndpoint, but the underlying fix (per PR summary) is that Replace now clears apiKey, baseURL, profileName, and err before routing. Pre-seeding those fields with stale values and asserting they're cleared would directly cover the behavior this fix targets — without it, a regression that stops clearing state (letting a second provider inherit the first provider's baseURL) would slip through undetected.

♻️ Suggested strengthening
 	m.providerWizard = &providerWizardState{
 		step:               providerWizardStepManageKey,
 		manageProviderName: "my-custom-openai",
 		manageKeyCursor:    1,
+		apiKey:             "stale-key",
+		baseURL:            "https://stale.example.com",
+		profileName:        "stale-profile",
+		err:                "stale error",
 		providers: []providercatalog.Descriptor{
 			{
 				ID:        "custom-openai-compatible",
 				Transport: providercatalog.TransportOpenAICompatible,
 			},
 		},
 		selectedProvider: 0,
 	}
 	next, _ := m.applyManageKeyChoice()
 	if next.providerWizard == nil || next.providerWizard.step != providerWizardStepEndpoint {
 		t.Fatalf("replace for generic provider should route to the endpoint step, got step: %v", next.providerWizard.step)
 	}
+	if next.providerWizard.apiKey != "" || next.providerWizard.baseURL != "" || next.providerWizard.profileName != "" || next.providerWizard.err != "" {
+		t.Fatal("replace should clear stale apiKey/baseURL/profileName/err before routing")
+	}
 }
🤖 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 `@internal/tui/provider_wizard_test.go` around lines 1170 - 1189, Strengthen
TestProviderWizardManageKeyReplaceGeneric to cover the Replace state-reset
behavior in applyManageKeyChoice, not just the providerWizardStepEndpoint
transition. Pre-seed providerWizardState fields like apiKey, baseURL,
profileName, and err with stale values before calling applyManageKeyChoice, then
assert those fields are cleared in the returned state when manageKeyCursor
selects Replace. Use the existing providerWizardState,
providerWizardStepManageKey, and providerWizardStepEndpoint symbols to locate
the test and verify the generic-provider Replace path.
🤖 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 `@internal/tui/provider_wizard_test.go`:
- Around line 1170-1189: Strengthen TestProviderWizardManageKeyReplaceGeneric to
cover the Replace state-reset behavior in applyManageKeyChoice, not just the
providerWizardStepEndpoint transition. Pre-seed providerWizardState fields like
apiKey, baseURL, profileName, and err with stale values before calling
applyManageKeyChoice, then assert those fields are cleared in the returned state
when manageKeyCursor selects Replace. Use the existing providerWizardState,
providerWizardStepManageKey, and providerWizardStepEndpoint symbols to locate
the test and verify the generic-provider Replace path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7ef1c56c-41c5-4e0b-9db9-989d6326ebc7

📥 Commits

Reviewing files that changed from the base of the PR and between 7f681d9 and 74d61e0.

📒 Files selected for processing (2)
  • internal/cli/mcp_oauth_test.go
  • internal/tui/provider_wizard_test.go

@KunjShah95

Copy link
Copy Markdown
Contributor Author

@Vasanthdev2004

@KunjShah95

Copy link
Copy Markdown
Contributor Author

do check it out now @Vasanthdev2004

@Vasanthdev2004

Copy link
Copy Markdown
Collaborator

yea wait a min @KunjShah95

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: Correct fix — but #403 is the stronger duplicate of this exact change;

I checked this out, built it, ran the wizard suite (green), and reproduced the fix directly: with a saved custom-openai-compatible provider, re-selecting the generic "Custom OpenAI-compatible" catalog entry now returns matched=false from wizardProviderStoredKey, so the wizard proceeds to a fresh endpoint/name/key flow instead of getting trapped in keep/replace/remove. It does fix #375. CI is green.

But this PR (#389) and #403 fix the same bug the same way, and #403 is the cleaner implementation. Since you can only merge one, here's the honest comparison.

Where the two differ

  • Locus of the fix. #389 mutates the low-level lookup wizardProviderStoredKey (adds a !genericProviderCatalogID(...) guard around the CatalogID clause). #403 leaves the lookup's semantics intact and gates at the call site in advanceProviderWizard — "if this provider needs an endpoint, skip ManageKey and fall through to add." Gating at the decision point is the better locus: the lookup keeps meaning "is there a stored key for this descriptor," and only the manage-vs-add decision changes.
  • Test coverage. #403 adds TestAdvanceProviderWizardCustomSkipsManageKey (table over both custom-openai-compatible and custom-anthropic-compatible, asserting the real advanceProviderWizard path routes to the endpoint step, not ManageKey) plus TestAdvanceProviderWizardNamedShowsManageKey (a named provider still reaches ManageKey — guards against an over-broad skip). #389's TestProviderWizardManageKeyReplaceGeneric instead exercises a manually-constructed ManageKey state that the real flow can no longer reach for a generic provider (see below), so it doesn't actually cover the add-a-second-provider scenario that #375 is about.
  • Scope. #389 also bundles unrelated mcp_oauth_test.go timeout bumps (3s→15s, 10s→20s). Harmless flake-loosening, but it doesn't belong in this fix.

One thing both PRs should acknowledge (shared limitation, not a blocker)

The wizard's provider list comes from providercatalog.All(), so custom providers only ever appear as the single generic "Custom OpenAI-compatible" entry — never as your saved instance name. wizardProviderStoredKey (via advanceProviderWizard) is the only path into keep/replace/remove. Once we skip the CatalogID match for generics, there's no longer any way to reach replace/remove for an existing custom provider through the wizard (the generic descriptor's Name is "Custom OpenAI-compatible", which won't match a user's saved name). That's an acceptable trade to unblock multi-provider setup — the old behavior could only ever manage the first CatalogID match anyway — but it means removing a stale custom key now has no wizard surface. Worth a short follow-up (e.g. list saved custom instances for management), and worth a one-line note in the PR so it's a conscious choice.

As a direct consequence, #389's applyManageKeyChoice Replace change (clear baseURL/profileName, route to endpoint for generics) is now effectively unreachable in the real flow — generic providers can't reach ManageKey anymore. It's harmless/defensive, but it's dead relative to the actual bug.

Recommendation

Both are correct. I'd consolidate on #403 (cleaner locus, tests the real flow for both custom types + a named-provider guard, no unrelated changes) and close this one — or, if you prefer this author, ask them to (1) drop the mcp_oauth timeout hunk, and (2) replace the ManageKey-replace test with a table test that drives advanceProviderWizard for both custom transports like #403 does. Either way, please capture the "can't manage an existing custom key via the wizard" follow-up.

Thanks @KunjShah95 — the diagnosis is right and the fix works; this is purely a "which of two identical fixes to keep" call.

@KunjShah95

Copy link
Copy Markdown
Contributor Author

ohkay no problem bhai

@Vasanthdev2004

Copy link
Copy Markdown
Collaborator

Closing in favor of #403, which fixes the same issue (#375) with a cleaner approach — it gates at the call site in advanceProviderWizard instead of mutating the wizardProviderStoredKey lookup, tests the real advance flow for both custom-openai-compatible and custom-anthropic-compatible, adds a named-provider guard test, and doesn't bundle the unrelated mcp_oauth_test timeout changes.

Thanks a lot @KunjShah95 — your diagnosis of #375 was spot on and your fix worked; this is purely a "two identical fixes, keep the cleaner one" call, not a knock on the work. Full comparison is in my review above. Appreciate the contribution, and look forward to the next one.

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this — the core fix (hunk 1: wizardProviderStoredKey no longer matching a generic custom descriptor by CatalogID) correctly resolves #375, and the requested Replace→endpoint test is now in.

I'm going to go with #403 for this issue, though, so I'd suggest closing this in its favor — two reasons:

  • Unrelated scope creep: this also bumps the MCP-OAuth login test timeouts (mcp_oauth_test.go) in a different subsystem — the commit message even calls it "fix flaky MCP OAuth test." That doesn't belong in a provider-wizard change.
  • Redundant second hunk: the applyManageKeyChoice Replace hunk repairs a path that hunk 1 makes unreachable for custom providers, and the added test targets that dead path rather than the actual #375 repro.

#403 does the same fix with one localized change plus tests that hit the real repro and the regression boundary, no scope creep. No hard feelings — your root-cause diagnosis was correct. Marking request-changes to reflect that this shouldn't merge as-is.

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.

Not able to setup multiple openai comp providers

4 participants