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: {