From 4b32710c3e0dd256faa0079d718f3af1299c06a1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 21 Oct 2026 12:50:16 -0700 Subject: [PATCH 1/4] prototype(connect): one OpenCode row in /connect for Go, Zen and Console --- packages/app/e2e/regression/settings.spec.ts | 20 +++-- packages/app/src/providers/connect/dialog.tsx | 78 +++++++++++------ packages/app/src/runtime/i18n/en.ts | 13 ++- .../src/settings/providers/accounts.test.ts | 23 ++++- .../app/src/settings/providers/accounts.ts | 6 ++ .../tui/src/component/dialog-integration.tsx | 42 +++++++-- .../cli/cmd/tui/integration-options.test.ts | 32 ++++++- .../test/cli/tui/dialog-integration.test.tsx | 86 +++++++++++++++++++ 8 files changed, 256 insertions(+), 44 deletions(-) diff --git a/packages/app/e2e/regression/settings.spec.ts b/packages/app/e2e/regression/settings.spec.ts index 6ac6987f7a0d..8ae3defa4dbf 100644 --- a/packages/app/e2e/regression/settings.spec.ts +++ b/packages/app/e2e/regression/settings.spec.ts @@ -799,8 +799,18 @@ const consoleIntegrations = [ ] for (const row of [ - { provider: "OpenCode Console", connect: "Connect OpenCode Console", signIn: "Connecting to OpenCode Console" }, - { provider: "OpenCode Go", connect: "Connect OpenCode Go", signIn: "Connecting to OpenCode Go" }, + { + provider: "OpenCode Console", + connect: "Connect OpenCode", + signIn: "Connecting to OpenCode", + keys: ["Zen API key", "Go API key"], + }, + { + provider: "OpenCode Go", + connect: "Connect OpenCode Go", + signIn: "Connecting to OpenCode Go", + keys: ["Use API key"], + }, ]) { test(`providers: ${row.provider} signs in from the Console intro`, async ({ page, context }) => { const url = "https://auth.example.test/device?user_code=ABCD-EFGH" @@ -820,9 +830,9 @@ for (const row of [ const intro = page.getByRole("dialog", { name: row.connect, exact: true }) const container = page.locator('[data-slot="dialog-container"]') await expect( - intro.getByText("Sign in with your OpenCode Console account to use the available models."), + intro.getByText("Sign in with your OpenCode account to use your Go subscription or Console workspace."), ).toBeVisible() - await expect(intro.getByRole("button", { name: "Use API key", exact: true })).toBeVisible() + for (const key of row.keys) await expect(intro.getByRole("button", { name: key, exact: true })).toBeVisible() expect((await container.boundingBox())?.height).toBe(512) const popup = page.waitForEvent("popup") @@ -836,7 +846,7 @@ for (const row of [ const signIn = page.getByRole("dialog", { name: row.signIn, exact: true }) await expect(signIn.getByRole("group", { name: "Device code: ABCD-EFGH", exact: true })).toBeVisible() await expect(signIn.getByRole("button", { name: "Copy sign-in link", exact: true })).toBeVisible() - await expect(signIn.getByRole("button", { name: "Use API key", exact: true })).toHaveCount(0) + for (const key of row.keys) await expect(signIn.getByRole("button", { name: key, exact: true })).toHaveCount(0) await expect.poll(async () => (await container.boundingBox())?.height).toBeLessThan(512) }) } diff --git a/packages/app/src/providers/connect/dialog.tsx b/packages/app/src/providers/connect/dialog.tsx index 15fe5379bf3b..7758d1d3a21e 100644 --- a/packages/app/src/providers/connect/dialog.tsx +++ b/packages/app/src/providers/connect/dialog.tsx @@ -52,7 +52,7 @@ import { import { ConsoleAuthorization } from "./console" import { DialogChatGPTPlanWelcome } from "./chatgpt-welcome" import { authServerName, RemoteAuthNotice } from "./remote" -import { providerAccounts, type ProviderAccount } from "@/settings/providers/accounts" +import { connectPickerIntegrations, providerAccounts, type ProviderAccount } from "@/settings/providers/accounts" import { AccountNameInput } from "@/settings/providers/account-name" import { useProviderAccountActions } from "@/settings/providers/account-actions" import { IconButton } from "@opencode/ui/icon-button" @@ -237,18 +237,27 @@ function ProviderPicker(props: { directory?: string; onSelect: (provider: string filter: "", }) - const featured = ["opencode-go", "opencode", "anthropic", "openai", "google", "openrouter", "vercel"] + const featured = ["opencode", "opencode-go", "anthropic", "openai", "google", "openrouter", "vercel"] const custom = () => ({ id: CUSTOM_ID, name: language.t("dialog.provider.custom.label") }) const all = createMemo(() => { language.locale() const query = store.filter.trim().toLowerCase() - const values = [custom(), ...integrations.list()] + const values = [ + custom(), + ...connectPickerIntegrations(integrations.list()).map((provider) => + provider.id === "opencode" ? { ...provider, name: language.t("dialog.provider.opencode.name") } : provider, + ), + ] if (!query) return values - return values.filter((provider) => `${provider.id} ${provider.name}`.toLowerCase().includes(query)) + return values.filter((provider) => + `${provider.id} ${provider.name} ${provider.id === "opencode" ? "go zen console" : ""}` + .toLowerCase() + .includes(query), + ) }) const popular = createMemo(() => @@ -363,11 +372,7 @@ function ProviderPicker(props: { directory?: string; onSelect: (provider: string - {language.t( - provider.id === "opencode" - ? "dialog.provider.opencode.tagline" - : "dialog.provider.opencodeGo.tagline", - )} + {language.t("dialog.provider.opencodeAll.tagline")} {language.t("dialog.provider.tag.recommended")} @@ -450,6 +455,8 @@ function ProviderConnection(props: { switching?: string activating?: string renaming?: string + /** The OpenCode row stores a Go API key under `opencode-go`. */ + keyProvider?: string }>({ adding: false, direct: false, @@ -477,7 +484,7 @@ function ProviderConnection(props: { provider: () => integrationID, // A Go service-account key still belongs to the `opencode-go` integration (zen/go/v1), // exactly as before; only the sign-in is shared with the Console. - keyProvider: () => props.provider, + keyProvider: () => state.keyProvider ?? props.provider, directory, manual: () => isConsole || (!!props.accounts && accounts().length > 0), prepare: isConsole ? prepareConsoleCatalog : undefined, @@ -534,7 +541,10 @@ function ProviderConnection(props: { const connectionProviders = createMemo(() => (data.location.provider.list(location()) ?? []).filter( - (provider) => provider.id === props.provider || provider.integrationID === integrationID, + (provider) => + provider.id === props.provider || + provider.integrationID === integrationID || + provider.integrationID === state.keyProvider, ), ) @@ -619,10 +629,12 @@ function ProviderConnection(props: { const provider = createMemo(() => ({ id: props.provider, name: - integrations.list().find((item) => item.id === props.provider)?.name ?? - providers.all().get(props.provider)?.name ?? - controller.integration()?.name ?? - props.provider, + props.provider === "opencode" + ? language.t("dialog.provider.opencode.name") + : (integrations.list().find((item) => item.id === props.provider)?.name ?? + providers.all().get(props.provider)?.name ?? + controller.integration()?.name ?? + props.provider), })) const methodLabel = (value?: { type?: string; label?: string }) => { @@ -1040,7 +1052,9 @@ function ProviderConnection(props: {