Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { createMemo, createSignal } from "solid-js"
import { useLocal } from "@tui/context/local"
import { defer } from "@/util/defer"
import { useSDK } from "@tui/context/sdk"
import { useSync } from "@tui/context/sync"
import { useToast } from "@tui/ui/toast"
import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
Expand All @@ -19,8 +22,11 @@ export function DialogModel(props: { providerID?: string }) {
const local = useLocal()
const sync = useSync()
const dialog = useDialog()
const sdk = useSDK()
const toast = useToast()
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
const [query, setQuery] = createSignal("")
const [refreshing, setRefreshing] = createSignal(false)

const connected = useConnected()
const providers = createDialogProviderOptions()
Expand Down Expand Up @@ -218,6 +224,28 @@ export function DialogModel(props: { providerID?: string }) {
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
},
},
{
keybind: Keybind.parse("ctrl+r")[0],
title: refreshing() ? "Refreshing..." : "Refresh",
onTrigger: async () => {
if (refreshing()) return

setRefreshing(true)
using _ = defer(() => {
setRefreshing(false)
})
const result = await sdk.client.config.refreshProviders()

if (result.error) {
toast.show({ message: "Failed to refresh providers", variant: "error" })
return
}

await sdk.client.instance.dispose()
await sync.bootstrap()
toast.show({ message: "Refreshed providers", variant: "success" })
},
},
]}
ref={setRef}
onFilter={setQuery}
Expand Down
21 changes: 19 additions & 2 deletions packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
import { useTheme, selectedForeground } from "@tui/context/theme"
import { entries, filter, flatMap, groupBy, pipe, take } from "remeda"
import { entries, filter, flatMap, groupBy, pipe } from "remeda"
import { batch, createEffect, createMemo, For, Show, type JSX, on } from "solid-js"
import { createStore } from "solid-js/store"
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
Expand All @@ -11,6 +11,8 @@ import { useKeybind } from "@tui/context/keybind"
import { Keybind } from "@/util/keybind"
import { Locale } from "@/util/locale"

const KEYBINDS_ROW_GAP = 2

export interface DialogSelectProps<T> {
title: string
placeholder?: string
Expand Down Expand Up @@ -184,6 +186,14 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
props.ref?.(ref)

const keybinds = createMemo(() => props.keybind?.filter((x) => !x.disabled) ?? [])
const keybindsFitInRow = createMemo(() => {
const keybindsWidth =
keybinds()
.map((x) => x.title.length + Keybind.toString(x.keybind).length + 1)
.reduce((a, b) => a + b, 0) +
(keybinds().length - 1) * KEYBINDS_ROW_GAP
return dialog.width >= keybindsWidth + 8
})

return (
<box gap={1} paddingBottom={1}>
Expand Down Expand Up @@ -269,7 +279,14 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
</For>
</scrollbox>
<Show when={keybinds().length} fallback={<box flexShrink={0} />}>
<box paddingRight={2} paddingLeft={4} flexDirection="row" gap={2} flexShrink={0} paddingTop={1}>
<box
paddingRight={2}
paddingLeft={4}
flexDirection={keybindsFitInRow() ? "row" : "column"}
gap={keybindsFitInRow() ? KEYBINDS_ROW_GAP : 0}
flexShrink={0}
paddingTop={1}
>
<For each={keybinds()}>
{(item) => (
<text>
Expand Down
5 changes: 5 additions & 0 deletions packages/opencode/src/cli/cmd/tui/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ function init() {
}
})

const dimensions = useTerminalDimensions()

const renderer = useRenderer()
let focus: Renderable | null
function refocus() {
Expand Down Expand Up @@ -117,6 +119,9 @@ function init() {
get size() {
return store.size
},
get width() {
return Math.min(store.size === "large" ? 80 : 60, dimensions().width - 2)
},
setSize(size: "medium" | "large") {
setStore("size", size)
},
Expand Down
15 changes: 13 additions & 2 deletions packages/opencode/src/provider/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export namespace ModelsDev {
return JSON.parse(json) as Record<string, Provider>
}

export async function refresh() {
export async function refresh(throwOnError = false) {
if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return
const file = Bun.file(filepath)
log.info("refreshing", {
Expand All @@ -98,8 +98,19 @@ export namespace ModelsDev {
log.error("Failed to fetch models.dev", {
error: e,
})

if (throwOnError) throw e
})
if (result && result.ok) await Bun.write(file, await result.text())

if (!result?.ok) {
log.error("Failed to fetch models.dev", { status: result?.status })
if (throwOnError) {
throw new Error(`Failed to refresh models.dev: ${result?.status ?? "no response"}`)
}
return undefined
}

await Bun.write(file, await result.text())
}
}

Expand Down
22 changes: 22 additions & 0 deletions packages/opencode/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,28 @@ export namespace Server {
})
},
)
.post(
"/config/refresh_providers",
describeRoute({
description: "Refresh providers cache",
operationId: "config.refresh_providers",
responses: {
200: {
description: "Refreshed providers successfully",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
},
}),
async (c) => {
using _ = log.time("refresh_providers")
await ModelsDev.refresh(true)
return c.json(true)
},
)
.get(
"/provider",
describeRoute({
Expand Down
14 changes: 14 additions & 0 deletions packages/sdk/js/src/gen/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ import type {
CommandListResponses,
ConfigProvidersData,
ConfigProvidersResponses,
ConfigRefreshProvidersData,
ConfigRefreshProvidersResponses,
ProviderListData,
ProviderListResponses,
ProviderAuthData,
Expand Down Expand Up @@ -368,6 +370,18 @@ class Config extends _HeyApiClient {
...options,
})
}

/**
* Refresh providers cache
*/
public refreshProviders<ThrowOnError extends boolean = false>(
options?: Options<ConfigRefreshProvidersData, ThrowOnError>,
) {
return (options?.client ?? this._client).post<ConfigRefreshProvidersResponses, unknown, ThrowOnError>({
url: "/config/refresh_providers",
...options,
})
}
}

class Tool extends _HeyApiClient {
Expand Down
18 changes: 18 additions & 0 deletions packages/sdk/js/src/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2950,6 +2950,24 @@ export type ConfigProvidersResponses = {

export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]

export type ConfigRefreshProvidersData = {
body?: never
path?: never
query?: {
directory?: string
}
url: "/config/refresh_providers"
}

export type ConfigRefreshProvidersResponses = {
/**
* Refreshed providers successfully
*/
200: boolean
}

export type ConfigRefreshProvidersResponse = ConfigRefreshProvidersResponses[keyof ConfigRefreshProvidersResponses]

export type ProviderListData = {
body?: never
path?: never
Expand Down
18 changes: 18 additions & 0 deletions packages/sdk/js/src/v2/gen/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
Config as Config2,
ConfigGetResponses,
ConfigProvidersResponses,
ConfigRefreshProvidersResponses,
ConfigUpdateErrors,
ConfigUpdateResponses,
EventSubscribeResponses,
Expand Down Expand Up @@ -579,6 +580,23 @@ export class Config extends HeyApiClient {
...params,
})
}

/**
* Refresh providers cache
*/
public refreshProviders<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
return (options?.client ?? this.client).post<ConfigRefreshProvidersResponses, unknown, ThrowOnError>({
url: "/config/refresh_providers",
...options,
...params,
})
}
}

export class Tool extends HeyApiClient {
Expand Down
18 changes: 18 additions & 0 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3362,6 +3362,24 @@ export type ConfigProvidersResponses = {

export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]

export type ConfigRefreshProvidersData = {
body?: never
path?: never
query?: {
directory?: string
}
url: "/config/refresh_providers"
}

export type ConfigRefreshProvidersResponses = {
/**
* Refreshed providers successfully
*/
200: boolean
}

export type ConfigRefreshProvidersResponse = ConfigRefreshProvidersResponses[keyof ConfigRefreshProvidersResponses]

export type ProviderListData = {
body?: never
path?: never
Expand Down