243 lines
7.3 KiB
TypeScript
243 lines
7.3 KiB
TypeScript
import { createStore } from "solid-js/store"
|
|
import { batch, createMemo } from "solid-js"
|
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
|
import { useSDK } from "./sdk"
|
|
import { useSync } from "./sync"
|
|
import { base64Encode } from "@opencode-ai/util/encode"
|
|
import { useProviders } from "@/hooks/use-providers"
|
|
import { useModels } from "@/context/models"
|
|
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
|
|
|
|
export type ModelKey = { providerID: string; modelID: string }
|
|
|
|
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|
name: "Local",
|
|
init: () => {
|
|
const sdk = useSDK()
|
|
const sync = useSync()
|
|
const providers = useProviders()
|
|
|
|
function isModelValid(model: ModelKey) {
|
|
const provider = providers.all().find((x) => x.id === model.providerID)
|
|
return (
|
|
!!provider?.models[model.modelID] &&
|
|
providers
|
|
.connected()
|
|
.map((p) => p.id)
|
|
.includes(model.providerID)
|
|
)
|
|
}
|
|
|
|
function getFirstValidModel(...modelFns: (() => ModelKey | undefined)[]) {
|
|
for (const modelFn of modelFns) {
|
|
const model = modelFn()
|
|
if (!model) continue
|
|
if (isModelValid(model)) return model
|
|
}
|
|
}
|
|
|
|
const agent = (() => {
|
|
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
|
|
const [store, setStore] = createStore<{
|
|
current?: string
|
|
}>({
|
|
current: list()[0]?.name,
|
|
})
|
|
return {
|
|
list,
|
|
current() {
|
|
const available = list()
|
|
if (available.length === 0) return undefined
|
|
return available.find((x) => x.name === store.current) ?? available[0]
|
|
},
|
|
set(name: string | undefined) {
|
|
const available = list()
|
|
if (available.length === 0) {
|
|
setStore("current", undefined)
|
|
return
|
|
}
|
|
if (name && available.some((x) => x.name === name)) {
|
|
setStore("current", name)
|
|
return
|
|
}
|
|
setStore("current", available[0].name)
|
|
},
|
|
move(direction: 1 | -1) {
|
|
const available = list()
|
|
if (available.length === 0) {
|
|
setStore("current", undefined)
|
|
return
|
|
}
|
|
let next = available.findIndex((x) => x.name === store.current) + direction
|
|
if (next < 0) next = available.length - 1
|
|
if (next >= available.length) next = 0
|
|
const value = available[next]
|
|
if (!value) return
|
|
setStore("current", value.name)
|
|
if (value.model)
|
|
model.set({
|
|
providerID: value.model.providerID,
|
|
modelID: value.model.modelID,
|
|
})
|
|
},
|
|
}
|
|
})()
|
|
|
|
const model = (() => {
|
|
const models = useModels()
|
|
|
|
const [ephemeral, setEphemeral] = createStore<{
|
|
model: Record<string, ModelKey | undefined>
|
|
}>({
|
|
model: {},
|
|
})
|
|
|
|
const fallbackModel = createMemo<ModelKey | undefined>(() => {
|
|
if (sync.data.config.model) {
|
|
const [providerID, modelID] = sync.data.config.model.split("/")
|
|
if (isModelValid({ providerID, modelID })) {
|
|
return {
|
|
providerID,
|
|
modelID,
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const item of models.recent.list()) {
|
|
if (isModelValid(item)) {
|
|
return item
|
|
}
|
|
}
|
|
|
|
const defaults = providers.default()
|
|
for (const p of providers.connected()) {
|
|
const configured = defaults[p.id]
|
|
if (configured) {
|
|
const key = { providerID: p.id, modelID: configured }
|
|
if (isModelValid(key)) return key
|
|
}
|
|
|
|
const first = Object.values(p.models)[0]
|
|
if (!first) continue
|
|
const key = { providerID: p.id, modelID: first.id }
|
|
if (isModelValid(key)) return key
|
|
}
|
|
|
|
return undefined
|
|
})
|
|
|
|
const current = createMemo(() => {
|
|
const a = agent.current()
|
|
if (!a) return undefined
|
|
const key = getFirstValidModel(
|
|
() => ephemeral.model[a.name],
|
|
() => a.model,
|
|
fallbackModel,
|
|
)
|
|
if (!key) return undefined
|
|
return models.find(key)
|
|
})
|
|
|
|
const recent = createMemo(() => models.recent.list().map(models.find).filter(Boolean))
|
|
|
|
const cycle = (direction: 1 | -1) => {
|
|
const recentList = recent()
|
|
const currentModel = current()
|
|
if (!currentModel) return
|
|
|
|
const index = recentList.findIndex(
|
|
(x) => x?.provider.id === currentModel.provider.id && x?.id === currentModel.id,
|
|
)
|
|
if (index === -1) return
|
|
|
|
let next = index + direction
|
|
if (next < 0) next = recentList.length - 1
|
|
if (next >= recentList.length) next = 0
|
|
|
|
const val = recentList[next]
|
|
if (!val) return
|
|
|
|
model.set({
|
|
providerID: val.provider.id,
|
|
modelID: val.id,
|
|
})
|
|
}
|
|
|
|
return {
|
|
ready: models.ready,
|
|
current,
|
|
recent,
|
|
list: models.list,
|
|
cycle,
|
|
set(model: ModelKey | undefined, options?: { recent?: boolean }) {
|
|
batch(() => {
|
|
const currentAgent = agent.current()
|
|
const next = model ?? fallbackModel()
|
|
if (currentAgent) setEphemeral("model", currentAgent.name, next)
|
|
if (model) models.setVisibility(model, true)
|
|
if (options?.recent && model) models.recent.push(model)
|
|
})
|
|
},
|
|
visible(model: ModelKey) {
|
|
return models.visible(model)
|
|
},
|
|
setVisibility(model: ModelKey, visible: boolean) {
|
|
models.setVisibility(model, visible)
|
|
},
|
|
variant: {
|
|
configured() {
|
|
const a = agent.current()
|
|
const m = current()
|
|
if (!a || !m) return undefined
|
|
return getConfiguredAgentVariant({
|
|
agent: { model: a.model, variant: a.variant },
|
|
model: { providerID: m.provider.id, modelID: m.id, variants: m.variants },
|
|
})
|
|
},
|
|
selected() {
|
|
const m = current()
|
|
if (!m) return undefined
|
|
return models.variant.get({ providerID: m.provider.id, modelID: m.id })
|
|
},
|
|
current() {
|
|
return resolveModelVariant({
|
|
variants: this.list(),
|
|
selected: this.selected(),
|
|
configured: this.configured(),
|
|
})
|
|
},
|
|
list() {
|
|
const m = current()
|
|
if (!m) return []
|
|
if (!m.variants) return []
|
|
return Object.keys(m.variants)
|
|
},
|
|
set(value: string | undefined) {
|
|
const m = current()
|
|
if (!m) return
|
|
models.variant.set({ providerID: m.provider.id, modelID: m.id }, value)
|
|
},
|
|
cycle() {
|
|
const variants = this.list()
|
|
if (variants.length === 0) return
|
|
this.set(
|
|
cycleModelVariant({
|
|
variants,
|
|
selected: this.selected(),
|
|
configured: this.configured(),
|
|
}),
|
|
)
|
|
},
|
|
},
|
|
}
|
|
})()
|
|
|
|
const result = {
|
|
slug: createMemo(() => base64Encode(sdk.directory)),
|
|
model,
|
|
agent,
|
|
}
|
|
return result
|
|
},
|
|
})
|