Repository navigation
fix(cli): TUI onboarding cannot create custom relay connections (OpenAI Chat / OpenAI Responses / Anthropic) — Desktop can #3405
Description
What happened
In the TUI setup wizard (/setup and first-run), the three custom relay providers never appear in the provider picker, so there is no way to create these connections from the TUI:
- Custom relay (OpenAI Chat-compatible) —
openai-compatible - Custom relay (OpenAI Responses) —
openai-responses-compatible - Custom relay (Anthropic) —
anthropic-compatible
The Desktop Settings → Models page lists all three and can create them (its add form collects the base URL for category === 'custom' providers). The TUI is the only surface where they are unreachable.
Root cause
Two gaps, one visible and one structural:
-
The TUI catalog filter drops every provider without a built-in base URL.
listApiKeyOnboardableProviders()maps the registry and then filters with.filter((provider) => !provider.requiresBaseUrl)(packages/cli/src/onboarding-catalog.ts:20), whererequiresBaseUrl: !definition.baseUrl(:16). The three relay entries declarebaseUrl: ''precisely because the user must supply their own endpoint (packages/core/src/provider-registry.ts:1724,:1743,:1762), so all three are filtered out of both TUI paths: the standalone fallback list (packages/cli/src/pi-tui-runner.ts:1765) and the runtime-host projection (packages/cli/src/runtime-host-onboarding.ts:81). -
The onboarding protocol has no field to carry a base URL anyway. The wizard only collects an API key and an enabled-model set;
OnboardingVerifyInput/OnboardingSaveInput(packages/cli/src/pi-tui-contracts.ts:37-51) and the host-sideConnectionOnboardingVerifyInput/ConnectionOnboardingSaveInput(packages/runtime-host/src/protocol/connection-effects.ts:67-74) carry only{ providerType, apiKey, enabledModelIds }. Host-side discovery buildstransientConnection(input.providerType)with no endpoint override (packages/runtime-host/src/server/connection-effect-coordinator.ts:220-224), so even if the relays were listed, verify would run model discovery against an empty base URL and fail.
So this is not just "add three rows to the picker": the TUI wizard needs a base URL step for requiresBaseUrl providers, plumbed through the CLI contracts and the runtime-host onboarding operations.
Proposed boundary
- Stop filtering
requiresBaseUrlout oflistApiKeyOnboardableProviders(); keep the flag so the wizard knows which providers need an endpoint. - Add a base URL step to
OnboardingWizardbetween provider pick and API key for entries withrequiresBaseUrl(validate non-empty URL; reuse Desktop's normalization rules for relay base URLs). - Extend
OnboardingVerifyInput/OnboardingSaveInputand the runtime-hostconnection.onboarding.verify/saveinputs with an optionalbaseUrl; thread it intotransientConnectionandcommitConnectionOnboardingso the saved connection persists it. - Keep the existing single-provider-slug model: saving a relay updates the same derived slug connection Desktop manages, so both surfaces stay in sync.
How to reproduce
- Run the
makaTUI (first run, or/setupin-session). - Scroll the provider picker: OpenAI, Anthropic, aggregators, local servers are listed — no "Custom relay" entries.
- Open Desktop → Settings → Models → Add provider: all three custom relay types are present and can be created with a base URL + API key.
Environment
- Maka commit:
57e08d834 - Surface: CLI TUI (
pi-tui-runneronboarding wizard), compared against Desktop settings - OS: Linux
Activity
me2seeks commentedon Aug 21, 2026
Archaeology: this gap was originally an explicit phase-1 scope cut, not an oversight at the time the filter was written.
.filter((provider) => !provider.requiresBaseUrl)plus a now-lost comment: "Phase 1 collects only an API key; providers without a default baseUrl cannot be completed by this wizard and would wedge a fresh install, so exclude them until the base-URL prompt lands (phase 2)." At that point no relay providers existed, so the filter was inert.packages/clichanges) and never landed the wizard's "phase 2" base-URL prompt.onboarding-catalog.tsand kept the filter but dropped the explanatory comment, leaving the intent invisible.Net effect stands as described: the deferral was intentional, but phase 2 never arrived and the rationale is no longer visible in the code, so the TUI/Desktop asymmetry now functions as a defect for users.
UncertaintyDeterminesYou4ndMe commentedon Aug 22, 2026
Claiming this — implementing the proposed boundary as specified: unfilter
requiresBaseUrlproviders from the TUI catalog, add a base-URL step to the wizard between provider pick and API key, thread an always-presentbaseUrl: string | nullthroughconnection.onboarding.verify/save(exact-record protocol style, mirroringapiKey), apply it to discovery and persist it viacommitConnectionOnboardingwithpersistedBaseUrl()semantics. Blank input on an existing relay connection will reuse the persisted base URL, mirroring the blank-key-reuses-stored-secret pattern.fix(cli): let the TUI wizard create custom relay connections
fix(cli): let the TUI wizard create custom relay connections
fix(cli): let the TUI wizard create custom relay connections
fix(cli): let the TUI wizard create custom relay connections
fix(cli): let the TUI wizard create custom relay connections (apache#…