// Boot-time resolution for direct interactive mode. // // These functions run concurrently at startup to gather everything the runtime // needs before the first frame: keybinds from TUI config, diff display style, // model variant list with context limits, and session history for the prompt // history ring. All are async because they read config or hit the SDK, but // none block each other. import { Context, Effect, Layer } from "effect" import { stringifyKeyStroke } from "@opentui/keymap" import { TuiConfig } from "@/cli/cmd/tui/config/tui" import { TuiKeybind } from "@/cli/cmd/tui/config/keybind" import { makeRuntime } from "@/effect/run-service" import { reusePendingTask } from "./runtime.shared" import { resolveSession, sessionHistory } from "./session.shared" import type { FooterKeybinds, RunDiffStyle, RunInput, RunPrompt, RunProvider } from "./types" import { pickVariant } from "./variant.shared" const DEFAULT_KEYBINDS: FooterKeybinds = { leader: TuiKeybind.LeaderDefault, leaderTimeout: 2000, commandList: [{ key: "ctrl+p" }], variantCycle: [{ key: "ctrl+t" }], interrupt: [{ key: "escape" }], historyPrevious: [{ key: "up" }], historyNext: [{ key: "down" }], inputClear: [{ key: "ctrl+c" }], inputSubmit: [{ key: "return" }], inputNewline: [{ key: "shift+return,ctrl+return,alt+return,ctrl+j" }], } export type ModelInfo = { providers: RunProvider[] variants: string[] limits: Record } export type SessionInfo = { first: boolean history: RunPrompt[] variant: string | undefined } type Config = Awaited> type BootService = { readonly resolveModelInfo: ( sdk: RunInput["sdk"], directory: string, model: RunInput["model"], ) => Effect.Effect readonly resolveSessionInfo: ( sdk: RunInput["sdk"], sessionID: string, model: RunInput["model"], ) => Effect.Effect readonly resolveFooterKeybinds: () => Effect.Effect readonly resolveDiffStyle: () => Effect.Effect } const configTask: { current?: Promise } = {} class Service extends Context.Service()("@opencode/RunBoot") {} function loadConfig() { return reusePendingTask(configTask, () => TuiConfig.get()) } function emptyModelInfo(): ModelInfo { return { providers: [], variants: [], limits: {}, } } function emptySessionInfo(): SessionInfo { return { first: true, history: [], variant: undefined, } } function leaderKey(config: Config) { const key = config.keybinds.get("leader")?.[0]?.key if (!key) return TuiKeybind.LeaderDefault return typeof key === "string" ? key : stringifyKeyStroke(key) } function footerKeybinds(config: Config | undefined): FooterKeybinds { if (!config) { return DEFAULT_KEYBINDS } return { leader: leaderKey(config), leaderTimeout: config.leader_timeout, commandList: config.keybinds.get("command.palette.show"), variantCycle: config.keybinds.get("variant.cycle"), interrupt: config.keybinds.get("session.interrupt"), historyPrevious: config.keybinds.get("prompt.history.previous"), historyNext: config.keybinds.get("prompt.history.next"), inputClear: config.keybinds.get("prompt.clear"), inputSubmit: config.keybinds.get("input.submit"), inputNewline: config.keybinds.get("input.newline"), } } const layer = Layer.effect( Service, Effect.gen(function* () { const config = Effect.fn("RunBoot.config")(() => Effect.promise(() => loadConfig().catch(() => undefined))) const resolveModelInfo = Effect.fn("RunBoot.resolveModelInfo")(function* ( sdk: RunInput["sdk"], directory: string, model: RunInput["model"], ) { const connected = yield* Effect.promise(() => sdk.config .providers({ directory }) .then((item) => item.data?.providers) .catch(() => undefined), ) const providers = yield* Effect.promise(() => connected ? Promise.resolve(connected) : sdk.provider .list() .then((item) => item.data?.all ?? []) .catch(() => []), ) const limits = Object.fromEntries( providers.flatMap((provider) => Object.entries(provider.models ?? {}).flatMap(([modelID, info]) => { const limit = info?.limit?.context if (typeof limit !== "number" || limit <= 0) { return [] } return [[`${provider.id}/${modelID}`, limit] as const] }), ), ) if (!model) { return { providers, variants: [], limits, } } const info = providers.find((item) => item.id === model.providerID)?.models?.[model.modelID] return { providers, variants: Object.keys(info?.variants ?? {}), limits, } }) const resolveSessionInfo = Effect.fn("RunBoot.resolveSessionInfo")(function* ( sdk: RunInput["sdk"], sessionID: string, model: RunInput["model"], ) { const session = yield* Effect.promise(() => resolveSession(sdk, sessionID).catch(() => undefined)) if (!session) { return emptySessionInfo() } return { first: session.first, history: sessionHistory(session), variant: pickVariant(model, session), } }) const resolveFooterKeybinds = Effect.fn("RunBoot.resolveFooterKeybinds")(function* () { return footerKeybinds(yield* config()) }) const resolveDiffStyle = Effect.fn("RunBoot.resolveDiffStyle")(function* () { return (yield* config())?.diff_style ?? "auto" }) return Service.of({ resolveModelInfo, resolveSessionInfo, resolveFooterKeybinds, resolveDiffStyle, }) }), ) const runtime = makeRuntime(Service, layer) // Fetches available variants and context limits for every provider/model pair. export async function resolveModelInfo( sdk: RunInput["sdk"], directory: string, model: RunInput["model"], ): Promise { return runtime.runPromise((svc) => svc.resolveModelInfo(sdk, directory, model)).catch(() => emptyModelInfo()) } // Fetches session messages to determine if this is the first turn and build prompt history. export async function resolveSessionInfo( sdk: RunInput["sdk"], sessionID: string, model: RunInput["model"], ): Promise { return runtime.runPromise((svc) => svc.resolveSessionInfo(sdk, sessionID, model)).catch(() => emptySessionInfo()) } // Reads keybind overrides from TUI config and merges them with defaults. export async function resolveFooterKeybinds(): Promise { return runtime.runPromise((svc) => svc.resolveFooterKeybinds()).catch(() => DEFAULT_KEYBINDS) } export async function resolveDiffStyle(): Promise { return runtime.runPromise((svc) => svc.resolveDiffStyle()).catch(() => "auto") }