diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
index bcd1d98d5..c330fdf9a 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
@@ -2,7 +2,7 @@ import { createMemo, createSignal } from "solid-js"
import { useLocal } from "@tui/context/local"
import { useSync } from "@tui/context/sync"
import { map, pipe, flatMap, entries, filter, isDeepEqual, sortBy } from "remeda"
-import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
+import { DialogSelect, type DialogSelectOption, type DialogSelectRef } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
import { useTheme } from "../context/theme"
@@ -10,6 +10,15 @@ function Free() {
const { theme } = useTheme()
return Free
}
+const PROVIDER_PRIORITY: Record = {
+ opencode: 0,
+ anthropic: 1,
+ "github-copilot": 2,
+ openai: 3,
+ google: 4,
+ openrouter: 5,
+ vercel: 6,
+}
export function DialogModel() {
const local = useLocal()
@@ -17,9 +26,15 @@ export function DialogModel() {
const dialog = useDialog()
const [ref, setRef] = createSignal>()
+ const connected = createMemo(() =>
+ sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
+ )
+
+ const showRecent = createMemo(() => !ref()?.filter && local.model.recent().length > 0 && connected())
+
const options = createMemo(() => {
return [
- ...(!ref()?.filter
+ ...(showRecent()
? local.model.recent().flatMap((item) => {
const provider = sync.data.provider.find((x) => x.id === item.providerID)!
if (!provider) return []
@@ -36,6 +51,16 @@ export function DialogModel() {
description: provider.name,
category: "Recent",
footer: model.cost?.input === 0 && provider.id === "opencode" ? : undefined,
+ onSelect: () => {
+ dialog.clear()
+ local.model.set(
+ {
+ providerID: provider.id,
+ modelID: model.id,
+ },
+ { recent: true },
+ )
+ },
},
]
})
@@ -57,27 +82,37 @@ export function DialogModel() {
},
title: info.name ?? model,
description: provider.name,
- category: provider.name,
+ category: connected() ? provider.name : undefined,
footer: info.cost?.input === 0 && provider.id === "opencode" ? : undefined,
+ onSelect() {
+ dialog.clear()
+ local.model.set(
+ {
+ providerID: provider.id,
+ modelID: model,
+ },
+ { recent: true },
+ )
+ },
})),
- filter((x) => Boolean(ref()?.filter) || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
+ filter((x) => !showRecent() || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
sortBy((x) => x.title),
),
),
),
+ ...(!connected()
+ ? pipe(
+ sync.data.provider_next.all,
+ map((provider) => ({
+ title: provider.name,
+ category: "Connect a provider",
+ value: provider.id,
+ })),
+ sortBy((x) => PROVIDER_PRIORITY[x.value] ?? 99),
+ )
+ : []),
]
})
- return (
- {
- dialog.clear()
- local.model.set(option.value, { recent: true })
- }}
- />
- )
+ return
}
diff --git a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx
index 8b7564eb5..41f69f0d9 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx
@@ -18,7 +18,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
sdk.event.subscribe().then(async (events) => {
for await (const event of events.stream) {
- console.log("event", event.type)
emitter.emit(event.type, event)
}
})
diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
index 74fea2fd0..76dea5af9 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
@@ -12,6 +12,7 @@ import type {
McpStatus,
FormatterStatus,
SessionStatus,
+ ProviderListResponse,
} from "@opencode-ai/sdk"
import { createStore, produce, reconcile } from "solid-js/store"
import { useSDK } from "@tui/context/sdk"
@@ -28,6 +29,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
status: "loading" | "partial" | "complete"
provider: Provider[]
provider_default: Record
+ provider_next: ProviderListResponse
agent: Agent[]
command: Command[]
permission: {
@@ -56,6 +58,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
formatter: FormatterStatus[]
}>({
+ provider_next: {
+ all: [],
+ default: {},
+ connected: [],
+ },
config: {},
status: "loading",
agent: [],
@@ -241,6 +248,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
setStore("provider_default", x.data!.default)
})
}),
+ sdk.client.provider.list({ throwOnError: true }).then((x) => {
+ batch(() => {
+ setStore("provider_next", x.data!)
+ })
+ }),
sdk.client.app.agents({ throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
sdk.client.config.get({ throwOnError: true }).then((x) => setStore("config", x.data!)),
])
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
index eaf427aff..58798accd 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
@@ -24,7 +24,7 @@ export function DialogPrompt(props: DialogPromptProps) {
})
onMount(() => {
- dialog.setSize("large")
+ dialog.setSize("medium")
setTimeout(() => {
textarea.focus()
}, 1)