From 577fe02296a5ce00f50108c98fdf1ff94c534187 Mon Sep 17 00:00:00 2001 From: Robert Douglass Date: Fri, 5 Jun 2026 09:31:56 +0200 Subject: [PATCH 1/3] fix(tui): sort connect providers alphabetically --- .../cli/cmd/tui/component/dialog-provider.tsx | 16 +++++----------- .../test/cli/cmd/tui/provider-options.test.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) 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 7c1386ae76c3..9c0295b101ce 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -16,15 +16,6 @@ import { isConsoleManagedProvider } from "@tui/util/provider-origin" import { useConnected } from "./use-connected" import { useBindings } from "../keymap" -const PROVIDER_PRIORITY: Record = { - opencode: 0, - "opencode-go": 1, - openai: 2, - "github-copilot": 3, - anthropic: 4, - google: 5, -} - const CUSTOM_PROVIDER_OPTION_VALUE = "__opencode_custom_provider__" const CUSTOM_PROVIDER_ID = /^[a-z0-9][a-z0-9-_]*$/ @@ -48,7 +39,10 @@ export function providerOptions(list: { id: string; name: string }[]): ProviderO return [ ...pipe( list, - sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99), + sortBy( + (x) => x.name.toLowerCase(), + (x) => x.id, + ), map((provider) => ({ type: "provider" as const, title: provider.name, @@ -60,7 +54,7 @@ export function providerOptions(list: { id: string; name: string }[]): ProviderO openai: "(ChatGPT Plus/Pro or API key)", "opencode-go": "Low cost subscription for everyone", }[provider.id], - category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Providers", + category: "Providers", })), ), { diff --git a/packages/opencode/test/cli/cmd/tui/provider-options.test.ts b/packages/opencode/test/cli/cmd/tui/provider-options.test.ts index 39d639837933..06ae99dc0b1e 100644 --- a/packages/opencode/test/cli/cmd/tui/provider-options.test.ts +++ b/packages/opencode/test/cli/cmd/tui/provider-options.test.ts @@ -14,6 +14,23 @@ describe("providerOptions", () => { expect(providerOptions([{ id: "mistral", name: "Mistral" }])[0]?.category).toBe("Providers") }) + test("sorts provider entries alphabetically and keeps Other last", () => { + const options = providerOptions([ + { id: "openai", name: "OpenAI" }, + { id: "custom-z", name: "OpenAI" }, + { id: "anthropic", name: "Anthropic" }, + { id: "google", name: "Google" }, + ]) + + expect(options.map((option) => option.value)).toEqual([ + "anthropic", + "google", + "custom-z", + "openai", + "__opencode_custom_provider__", + ]) + }) + test("does not collide with a configured provider named other", () => { const values = providerOptions([{ id: "other", name: "Other Provider" }]).map((option) => option.value) expect(new Set(values).size).toBe(values.length) From 9dd784be9429b1d56048de152b23175ee1416933 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 6 Jun 2026 23:38:47 +0000 Subject: [PATCH 2/3] Popular first, rest sorted A-Z, tests updated Co-authored-by: rekram1-node --- .../cli/cmd/tui/component/dialog-provider.tsx | 41 ++++++++++++------- .../test/cli/cmd/tui/provider-options.test.ts | 26 ++++++++++-- 2 files changed, 50 insertions(+), 17 deletions(-) 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 9c0295b101ce..4fe6f6b2cf51 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -35,27 +35,40 @@ type ProviderOption = type: "custom" }) +const popularProviders = ["opencode", "opencode-go", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"] +const popularProviderSet = new Set(popularProviders) + export function providerOptions(list: { id: string; name: string }[]): ProviderOption[] { + const popular = list.filter((x) => popularProviderSet.has(x.id)) + const rest = list.filter((x) => !popularProviderSet.has(x.id)) + + const toOption = (provider: { id: string; name: string }, category: string) => ({ + type: "provider" as const, + title: provider.name, + value: provider.id, + providerID: provider.id, + description: { + opencode: "(Recommended)", + anthropic: "(API key)", + openai: "(ChatGPT Plus/Pro or API key)", + "opencode-go": "Low cost subscription for everyone", + }[provider.id], + category, + }) + return [ ...pipe( - list, + popular, + sortBy((x) => popularProviders.indexOf(x.id)), + map((provider) => toOption(provider, "Popular")), + ), + ...pipe( + rest, sortBy( (x) => x.name.toLowerCase(), (x) => x.id, ), - map((provider) => ({ - type: "provider" as const, - title: provider.name, - value: provider.id, - providerID: provider.id, - description: { - opencode: "(Recommended)", - anthropic: "(API key)", - openai: "(ChatGPT Plus/Pro or API key)", - "opencode-go": "Low cost subscription for everyone", - }[provider.id], - category: "Providers", - })), + map((provider) => toOption(provider, "Providers")), ), { type: "custom", diff --git a/packages/opencode/test/cli/cmd/tui/provider-options.test.ts b/packages/opencode/test/cli/cmd/tui/provider-options.test.ts index 06ae99dc0b1e..186966479b9d 100644 --- a/packages/opencode/test/cli/cmd/tui/provider-options.test.ts +++ b/packages/opencode/test/cli/cmd/tui/provider-options.test.ts @@ -14,23 +14,43 @@ describe("providerOptions", () => { expect(providerOptions([{ id: "mistral", name: "Mistral" }])[0]?.category).toBe("Providers") }) - test("sorts provider entries alphabetically and keeps Other last", () => { + test("sorts popular providers first in defined order, then rest alphabetically, and keeps Other last", () => { const options = providerOptions([ { id: "openai", name: "OpenAI" }, - { id: "custom-z", name: "OpenAI" }, + { id: "custom-z", name: "Zebra Provider" }, { id: "anthropic", name: "Anthropic" }, { id: "google", name: "Google" }, + { id: "mistral", name: "Mistral" }, + { id: "aws", name: "AWS Bedrock" }, ]) expect(options.map((option) => option.value)).toEqual([ + // Popular providers in defined order "anthropic", + "openai", "google", + // Non-popular providers sorted alphabetically by name + "aws", + "mistral", "custom-z", - "openai", + // Other always last "__opencode_custom_provider__", ]) }) + test("assigns Popular category to popular providers and Providers to others", () => { + const options = providerOptions([ + { id: "openai", name: "OpenAI" }, + { id: "mistral", name: "Mistral" }, + ]) + + const openai = options.find((o) => o.value === "openai") + const mistral = options.find((o) => o.value === "mistral") + + expect(openai?.category).toBe("Popular") + expect(mistral?.category).toBe("Providers") + }) + test("does not collide with a configured provider named other", () => { const values = providerOptions([{ id: "other", name: "Other Provider" }]).map((option) => option.value) expect(new Set(values).size).toBe(values.length) From e65e951f7f89c675c55f239701d5b03014ad4bef Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sun, 7 Jun 2026 00:06:29 -0500 Subject: [PATCH 3/3] fix(tui): minimize provider sorting changes --- .../cli/cmd/tui/component/dialog-provider.tsx | 51 +++++++++---------- .../test/cli/cmd/tui/provider-options.test.ts | 45 ++++------------ 2 files changed, 34 insertions(+), 62 deletions(-) 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 4fe6f6b2cf51..d5519b0e96fe 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -16,6 +16,15 @@ import { isConsoleManagedProvider } from "@tui/util/provider-origin" import { useConnected } from "./use-connected" import { useBindings } from "../keymap" +const PROVIDER_PRIORITY: Record = { + opencode: 0, + "opencode-go": 1, + openai: 2, + "github-copilot": 3, + anthropic: 4, + google: 5, +} + const CUSTOM_PROVIDER_OPTION_VALUE = "__opencode_custom_provider__" const CUSTOM_PROVIDER_ID = /^[a-z0-9][a-z0-9-_]*$/ @@ -35,40 +44,28 @@ type ProviderOption = type: "custom" }) -const popularProviders = ["opencode", "opencode-go", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"] -const popularProviderSet = new Set(popularProviders) - export function providerOptions(list: { id: string; name: string }[]): ProviderOption[] { - const popular = list.filter((x) => popularProviderSet.has(x.id)) - const rest = list.filter((x) => !popularProviderSet.has(x.id)) - - const toOption = (provider: { id: string; name: string }, category: string) => ({ - type: "provider" as const, - title: provider.name, - value: provider.id, - providerID: provider.id, - description: { - opencode: "(Recommended)", - anthropic: "(API key)", - openai: "(ChatGPT Plus/Pro or API key)", - "opencode-go": "Low cost subscription for everyone", - }[provider.id], - category, - }) - return [ ...pipe( - popular, - sortBy((x) => popularProviders.indexOf(x.id)), - map((provider) => toOption(provider, "Popular")), - ), - ...pipe( - rest, + list, sortBy( + (x) => PROVIDER_PRIORITY[x.id] ?? 99, (x) => x.name.toLowerCase(), (x) => x.id, ), - map((provider) => toOption(provider, "Providers")), + map((provider) => ({ + type: "provider" as const, + title: provider.name, + value: provider.id, + providerID: provider.id, + description: { + opencode: "(Recommended)", + anthropic: "(API key)", + openai: "(ChatGPT Plus/Pro or API key)", + "opencode-go": "Low cost subscription for everyone", + }[provider.id], + category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Providers", + })), ), { type: "custom", diff --git a/packages/opencode/test/cli/cmd/tui/provider-options.test.ts b/packages/opencode/test/cli/cmd/tui/provider-options.test.ts index 186966479b9d..1d8d5f7d9ceb 100644 --- a/packages/opencode/test/cli/cmd/tui/provider-options.test.ts +++ b/packages/opencode/test/cli/cmd/tui/provider-options.test.ts @@ -14,41 +14,16 @@ describe("providerOptions", () => { expect(providerOptions([{ id: "mistral", name: "Mistral" }])[0]?.category).toBe("Providers") }) - test("sorts popular providers first in defined order, then rest alphabetically, and keeps Other last", () => { - const options = providerOptions([ - { id: "openai", name: "OpenAI" }, - { id: "custom-z", name: "Zebra Provider" }, - { id: "anthropic", name: "Anthropic" }, - { id: "google", name: "Google" }, - { id: "mistral", name: "Mistral" }, - { id: "aws", name: "AWS Bedrock" }, - ]) - - expect(options.map((option) => option.value)).toEqual([ - // Popular providers in defined order - "anthropic", - "openai", - "google", - // Non-popular providers sorted alphabetically by name - "aws", - "mistral", - "custom-z", - // Other always last - "__opencode_custom_provider__", - ]) - }) - - test("assigns Popular category to popular providers and Providers to others", () => { - const options = providerOptions([ - { id: "openai", name: "OpenAI" }, - { id: "mistral", name: "Mistral" }, - ]) - - const openai = options.find((o) => o.value === "openai") - const mistral = options.find((o) => o.value === "mistral") - - expect(openai?.category).toBe("Popular") - expect(mistral?.category).toBe("Providers") + test("keeps popular providers first and sorts the rest alphabetically", () => { + expect( + providerOptions([ + { id: "openai", name: "OpenAI" }, + { id: "custom-z", name: "Zebra Provider" }, + { id: "anthropic", name: "Anthropic" }, + { id: "mistral", name: "Mistral" }, + { id: "aws", name: "AWS Bedrock" }, + ]).map((option) => option.value), + ).toEqual(["openai", "anthropic", "aws", "mistral", "custom-z", "__opencode_custom_provider__"]) }) test("does not collide with a configured provider named other", () => {