From 0ebd2b0b34b927ba0bfacb849dd7c3516f9abfe9 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Wed, 21 Jan 2026 18:11:55 +0800 Subject: [PATCH] fix(opentui): add 'Other' option to /connect menu for custom providers The /connect menu in the TUI was missing the "Other" option that allows users to configure custom OpenAI-compatible providers. This option was available in the CLI `auth login` command but not in the TUI. Changes: - Add "Other" option at the end of the provider list in DialogProvider - Add OtherProviderMethod component to prompt for custom provider ID - Add CustomApiMethod component to handle API key entry for custom providers - Include validation for provider ID format (lowercase, numbers, hyphens) - Show helpful message about configuring opencode.json after setup Fixes #9802 Fixes #8820 Fixes #9071 Co-Authored-By: Claude --- .../cli/cmd/tui/component/dialog-provider.tsx | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index 4e1171a4201..e8d679ad39b 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -28,7 +28,7 @@ export function createDialogProviderOptions() { const sdk = useSDK() const connected = createMemo(() => new Set(sync.data.provider_next.connected)) const options = createMemo(() => { - return pipe( + const providerOptions = pipe( sync.data.provider_next.all, sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99), map((provider) => { @@ -103,6 +103,20 @@ export function createDialogProviderOptions() { } }), ) + + // Add "Other" option for custom provider configuration + const otherOption = { + title: "Other", + value: "other", + description: "(Custom provider)", + category: "Other" as const, + footer: undefined, + async onSelect() { + dialog.replace(() => ) + }, + } + + return [...providerOptions, otherOption] }) return options } @@ -212,6 +226,77 @@ function CodeMethod(props: CodeMethodProps) { ) } +function OtherProviderMethod() { + const dialog = useDialog() + const { theme } = useTheme() + const [error, setError] = createSignal(null) + + return ( + ( + + Enter a unique ID for your custom provider. + Use lowercase letters, numbers, and hyphens only. + + {error()} + + + )} + onConfirm={(value) => { + if (!value) return + const trimmed = value.trim().replace(/^@ai-sdk\//, "") + if (!trimmed.match(/^[0-9a-z-]+$/)) { + setError("Use lowercase letters, numbers, and hyphens only") + return + } + dialog.replace(() => ) + }} + /> + ) +} + +interface CustomApiMethodProps { + providerID: string +} +function CustomApiMethod(props: CustomApiMethodProps) { + const dialog = useDialog() + const sdk = useSDK() + const sync = useSync() + const { theme } = useTheme() + + return ( + ( + + + This only stores a credential for {props.providerID}. + + + You will need to configure it in opencode.json. + + + )} + onConfirm={async (value) => { + if (!value) return + await sdk.client.auth.set({ + providerID: props.providerID, + auth: { + type: "api", + key: value, + }, + }) + await sdk.client.instance.dispose() + await sync.bootstrap() + dialog.clear() + }} + /> + ) +} + interface ApiMethodProps { providerID: string title: string