Files
CodeNomad/packages/ui/src/stores/preferences.tsx
codenomadbot[bot] 9800afb785 feat(ui): toggle tool call input YAML (#182)
* feat(ui): toggle tool call input yaml

* ui: rename tool input toggle and add IO headers

* ui: add input/output accordions in tool calls

* ui: refine tool IO accordion styling

* ui: remove extra padding around IO sections

* ui: remove semibold from IO headers

* feat(ui): add tool input visibility preference

* fix(ui): scope tool input toggle to current tool call

* ui: left-align tool IO header text

* fix(ui): let palette tool input visibility override per-call

* ui: default tool input visibility to collapsed

* fix(ui): expand read tool calls on error

---------

Co-authored-by: Shantur Rathore <i@shantur.com>
2026-02-19 22:08:41 +00:00

674 lines
24 KiB
TypeScript

import { createContext, createMemo, createSignal, onMount, useContext } from "solid-js"
import type { Accessor, ParentComponent } from "solid-js"
import { storage, type OwnerBucket } from "../lib/storage"
import {
ensureInstanceConfigLoaded,
getInstanceConfig,
updateInstanceConfig as updateInstanceData,
} from "./instance-config"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
type DeepReadonly<T> = T extends (...args: any[]) => unknown
? T
: T extends Array<infer U>
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T
export interface ModelPreference {
providerId: string
modelId: string
}
export type DiffViewMode = "split" | "unified"
export type ExpansionPreference = "expanded" | "collapsed"
export type ToolInputsVisibilityPreference = "hidden" | "collapsed" | "expanded"
export type ListeningMode = "local" | "all"
export interface UiSettings {
showThinkingBlocks: boolean
showKeyboardShortcutHints: boolean
thinkingBlocksExpansion: ExpansionPreference
showTimelineTools: boolean
promptSubmitOnEnter: boolean
locale?: string
diffViewMode: DiffViewMode
toolOutputExpansion: ExpansionPreference
diagnosticsExpansion: ExpansionPreference
toolInputsVisibility: ToolInputsVisibilityPreference
showUsageMetrics: boolean
autoCleanupBlankSessions: boolean
// OS notifications
osNotificationsEnabled: boolean
osNotificationsAllowWhenVisible: boolean
notifyOnNeedsInput: boolean
notifyOnIdle: boolean
}
// Backwards-compatible alias for older imports.
export type Preferences = UiSettings
export interface OpenCodeBinary {
path: string
version?: string
lastUsed: number
label?: string
}
export interface RecentFolder {
path: string
lastAccessed: number
}
export type ThemePreference = "light" | "dark" | "system"
interface UiConfigBucket {
theme?: ThemePreference
settings?: Partial<UiSettings>
}
interface ServerConfigBucket {
listeningMode?: ListeningMode
environmentVariables?: Record<string, string>
opencodeBinary?: string
}
interface UiStateBucket {
recentFolders?: RecentFolder[]
opencodeBinaries?: OpenCodeBinary[]
models?: {
recents?: ModelPreference[]
favorites?: ModelPreference[]
thinkingSelections?: Record<string, string>
}
}
interface NormalizedUiState {
recentFolders: RecentFolder[]
opencodeBinaries: OpenCodeBinary[]
models: {
recents: ModelPreference[]
favorites: ModelPreference[]
thinkingSelections: Record<string, string>
}
}
const MAX_RECENT_FOLDERS = 20
const MAX_RECENT_MODELS = 5
const MAX_FAVORITE_MODELS = 50
const defaultUiSettings: UiSettings = {
showThinkingBlocks: false,
showKeyboardShortcutHints: true,
thinkingBlocksExpansion: "expanded",
showTimelineTools: true,
promptSubmitOnEnter: false,
diffViewMode: "split",
toolOutputExpansion: "expanded",
diagnosticsExpansion: "expanded",
toolInputsVisibility: "collapsed",
showUsageMetrics: true,
autoCleanupBlankSessions: true,
osNotificationsEnabled: false,
osNotificationsAllowWhenVisible: false,
notifyOnNeedsInput: true,
notifyOnIdle: true,
}
function normalizeUiSettings(input?: Partial<UiSettings> | null): UiSettings {
const sanitized = input ?? {}
return {
showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultUiSettings.showThinkingBlocks,
showKeyboardShortcutHints:
sanitized.showKeyboardShortcutHints ?? defaultUiSettings.showKeyboardShortcutHints,
thinkingBlocksExpansion: sanitized.thinkingBlocksExpansion ?? defaultUiSettings.thinkingBlocksExpansion,
showTimelineTools: sanitized.showTimelineTools ?? defaultUiSettings.showTimelineTools,
promptSubmitOnEnter: sanitized.promptSubmitOnEnter ?? defaultUiSettings.promptSubmitOnEnter,
locale: sanitized.locale ?? defaultUiSettings.locale,
diffViewMode: sanitized.diffViewMode ?? defaultUiSettings.diffViewMode,
toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultUiSettings.toolOutputExpansion,
diagnosticsExpansion: sanitized.diagnosticsExpansion ?? defaultUiSettings.diagnosticsExpansion,
toolInputsVisibility:
sanitized.toolInputsVisibility === "hidden" || sanitized.toolInputsVisibility === "collapsed" || sanitized.toolInputsVisibility === "expanded"
? sanitized.toolInputsVisibility
: defaultUiSettings.toolInputsVisibility,
showUsageMetrics: sanitized.showUsageMetrics ?? defaultUiSettings.showUsageMetrics,
autoCleanupBlankSessions: sanitized.autoCleanupBlankSessions ?? defaultUiSettings.autoCleanupBlankSessions,
osNotificationsEnabled: sanitized.osNotificationsEnabled ?? defaultUiSettings.osNotificationsEnabled,
osNotificationsAllowWhenVisible:
sanitized.osNotificationsAllowWhenVisible ?? defaultUiSettings.osNotificationsAllowWhenVisible,
notifyOnNeedsInput: sanitized.notifyOnNeedsInput ?? defaultUiSettings.notifyOnNeedsInput,
notifyOnIdle: sanitized.notifyOnIdle ?? defaultUiSettings.notifyOnIdle,
}
}
function normalizeRecord(value: unknown): Record<string, string> {
if (!value || typeof value !== "object" || Array.isArray(value)) return {}
const out: Record<string, string> = {}
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
if (typeof v === "string") out[k] = v
}
return out
}
function cloneArray<T>(value: unknown, mapper: (item: any) => T | null): T[] {
if (!Array.isArray(value)) return []
const out: T[] = []
for (const item of value) {
const mapped = mapper(item)
if (mapped) out.push(mapped)
}
return out
}
function normalizeUiState(input?: UiStateBucket | null): NormalizedUiState {
const source = input ?? {}
return {
recentFolders: cloneArray<RecentFolder>(source.recentFolders, (f) => {
if (!f || typeof f !== "object") return null
const p = (f as any).path
const lastAccessed = (f as any).lastAccessed
if (typeof p !== "string") return null
const ts = typeof lastAccessed === "number" ? lastAccessed : Date.now()
return { path: p, lastAccessed: ts }
}),
opencodeBinaries: cloneArray<OpenCodeBinary>(source.opencodeBinaries, (b) => {
if (!b || typeof b !== "object") return null
const p = (b as any).path
if (typeof p !== "string") return null
const lastUsed = typeof (b as any).lastUsed === "number" ? (b as any).lastUsed : Date.now()
const version = typeof (b as any).version === "string" ? (b as any).version : undefined
const label = typeof (b as any).label === "string" ? (b as any).label : undefined
return { path: p, version, label, lastUsed }
}),
models: {
recents: cloneArray<ModelPreference>((source.models as any)?.recents, (m) => {
if (!m || typeof m !== "object") return null
const providerId = (m as any).providerId
const modelId = (m as any).modelId
if (typeof providerId !== "string" || typeof modelId !== "string") return null
return { providerId, modelId }
}),
favorites: cloneArray<ModelPreference>((source.models as any)?.favorites, (m) => {
if (!m || typeof m !== "object") return null
const providerId = (m as any).providerId
const modelId = (m as any).modelId
if (typeof providerId !== "string" || typeof modelId !== "string") return null
return { providerId, modelId }
}),
thinkingSelections: normalizeRecord((source.models as any)?.thinkingSelections),
},
}
}
function normalizeServerConfig(input?: ServerConfigBucket | null): Required<Pick<ServerConfigBucket, "listeningMode" | "environmentVariables" | "opencodeBinary">> {
const source = input ?? {}
const listeningMode = source.listeningMode === "all" ? "all" : "local"
const opencodeBinary = typeof source.opencodeBinary === "string" && source.opencodeBinary.trim() ? source.opencodeBinary : "opencode"
const environmentVariables = normalizeRecord(source.environmentVariables)
return { listeningMode, opencodeBinary, environmentVariables }
}
function getModelKey(model: { providerId: string; modelId: string }): string {
return `${model.providerId}/${model.modelId}`
}
function buildRecentFolderList(folderPath: string, source: RecentFolder[]): RecentFolder[] {
const folders = source.filter((f) => f.path !== folderPath)
folders.unshift({ path: folderPath, lastAccessed: Date.now() })
return folders.slice(0, MAX_RECENT_FOLDERS)
}
function buildBinaryList(binaryPath: string, version: string | undefined, source: OpenCodeBinary[]): OpenCodeBinary[] {
const timestamp = Date.now()
const existing = source.find((b) => b.path === binaryPath)
if (existing) {
const updatedEntry: OpenCodeBinary = { ...existing, lastUsed: timestamp, version: version ?? existing.version }
const remaining = source.filter((b) => b.path !== binaryPath)
return [updatedEntry, ...remaining]
}
const nextEntry: OpenCodeBinary = version
? { path: binaryPath, version, lastUsed: timestamp }
: { path: binaryPath, lastUsed: timestamp }
return [nextEntry, ...source].slice(0, 10)
}
const [uiConfigBucket, setUiConfigBucket] = createSignal<UiConfigBucket>({})
const [serverConfigBucket, setServerConfigBucket] = createSignal<ServerConfigBucket>({})
const [uiStateBucket, setUiStateBucket] = createSignal<UiStateBucket>({})
const [isLoaded, setIsLoaded] = createSignal(false)
const uiSettings = createMemo<UiSettings>(() => normalizeUiSettings(uiConfigBucket().settings))
const themePreference = createMemo<ThemePreference>(() => uiConfigBucket().theme ?? "system")
const serverSettings = createMemo(() => normalizeServerConfig(serverConfigBucket()))
const uiState = createMemo(() => normalizeUiState(uiStateBucket()))
const preferences = uiSettings
const recentFolders = createMemo<RecentFolder[]>(() => uiState().recentFolders)
const opencodeBinaries = createMemo<OpenCodeBinary[]>(() => uiState().opencodeBinaries)
let loadPromise: Promise<void> | null = null
async function ensureLoaded(): Promise<void> {
if (isLoaded()) return
if (!loadPromise) {
loadPromise = Promise.all([
storage.loadConfigOwner("ui"),
storage.loadConfigOwner("server"),
storage.loadStateOwner("ui"),
])
.then(([uiCfg, srvCfg, uiSt]) => {
setUiConfigBucket(uiCfg as any)
setServerConfigBucket(srvCfg as any)
setUiStateBucket(uiSt as any)
setIsLoaded(true)
})
.catch((error) => {
log.error("Failed to load settings", error)
setUiConfigBucket({})
setServerConfigBucket({})
setUiStateBucket({})
setIsLoaded(true)
})
.finally(() => {
loadPromise = null
})
}
await loadPromise
}
async function patchConfigOwner(owner: string, patch: unknown) {
await ensureLoaded()
const updated = await storage.patchConfigOwner(owner, patch)
if (owner === "ui") setUiConfigBucket(updated as any)
if (owner === "server") setServerConfigBucket(updated as any)
}
async function patchStateOwner(owner: string, patch: unknown) {
await ensureLoaded()
const updated = await storage.patchStateOwner(owner, patch)
if (owner === "ui") setUiStateBucket(updated as any)
}
function updateUiSettings(updates: Partial<UiSettings>) {
const current = uiConfigBucket()
const nextSettings = normalizeUiSettings({ ...(current.settings ?? {}), ...updates })
const patch = { settings: nextSettings }
void patchConfigOwner("ui", patch).catch((error) => log.error("Failed to patch ui settings", error))
}
function updatePreferences(updates: Partial<UiSettings>): void {
updateUiSettings(updates)
}
function setThemePreference(preference: ThemePreference): void {
if (themePreference() === preference) return
void patchConfigOwner("ui", { theme: preference }).catch((error) => log.error("Failed to set theme", error))
}
async function setListeningMode(mode: ListeningMode): Promise<void> {
if (serverSettings().listeningMode === mode) return
await patchConfigOwner("server", { listeningMode: mode })
}
function updateEnvironmentVariables(envVars: Record<string, string>): void {
void patchConfigOwner("server", { environmentVariables: envVars }).catch((error) =>
log.error("Failed to update environment variables", error),
)
}
function addEnvironmentVariable(key: string, value: string): void {
const current = serverSettings().environmentVariables
updateEnvironmentVariables({ ...current, [key]: value })
}
function removeEnvironmentVariable(key: string): void {
const current = serverSettings().environmentVariables
const { [key]: removed, ...rest } = current
updateEnvironmentVariables(rest)
}
function updateLastUsedBinary(path: string): void {
const target = path && path.trim().length > 0 ? path : "opencode"
void patchConfigOwner("server", { opencodeBinary: target }).catch((error) => log.error("Failed to set default binary", error))
// also bump lastUsed in state ui.opencodeBinaries
const nextList = buildBinaryList(target, undefined, opencodeBinaries())
void patchStateOwner("ui", { opencodeBinaries: nextList }).catch((error) => log.error("Failed to update binary list", error))
}
function addOpenCodeBinary(path: string, version?: string): void {
const nextList = buildBinaryList(path, version, opencodeBinaries())
void patchStateOwner("ui", { opencodeBinaries: nextList }).catch((error) => log.error("Failed to add binary", error))
}
function removeOpenCodeBinary(path: string): void {
const nextList = opencodeBinaries().filter((b) => b.path !== path)
void patchStateOwner("ui", { opencodeBinaries: nextList }).catch((error) => log.error("Failed to remove binary", error))
if (serverSettings().opencodeBinary === path) {
void patchConfigOwner("server", { opencodeBinary: "opencode" }).catch((error) =>
log.error("Failed to reset default binary", error),
)
}
}
function addRecentFolder(folderPath: string): void {
const next = buildRecentFolderList(folderPath, recentFolders())
void patchStateOwner("ui", { recentFolders: next }).catch((error) => log.error("Failed to add recent folder", error))
}
function removeRecentFolder(folderPath: string): void {
const next = recentFolders().filter((f) => f.path !== folderPath)
void patchStateOwner("ui", { recentFolders: next }).catch((error) => log.error("Failed to remove recent folder", error))
}
function recordWorkspaceLaunch(folderPath: string, binaryPath?: string): void {
const targetBinary = binaryPath && binaryPath.trim().length > 0 ? binaryPath : serverSettings().opencodeBinary
const nextFolders = buildRecentFolderList(folderPath, recentFolders())
const nextBinaries = buildBinaryList(targetBinary, undefined, opencodeBinaries())
void patchStateOwner("ui", { recentFolders: nextFolders, opencodeBinaries: nextBinaries }).catch((error) =>
log.error("Failed to update ui state on launch", error),
)
void patchConfigOwner("server", { opencodeBinary: targetBinary }).catch((error) =>
log.error("Failed to persist selected binary", error),
)
}
function addRecentModelPreference(model: ModelPreference): void {
if (!model.providerId || !model.modelId) return
const recents = uiState().models.recents
const filtered = recents.filter((item) => item.providerId !== model.providerId || item.modelId !== model.modelId)
const updated = [model, ...filtered].slice(0, MAX_RECENT_MODELS)
void patchStateOwner("ui", { models: { recents: updated } }).catch((error) => log.error("Failed to update model recents", error))
}
function isFavoriteModelPreference(model: ModelPreference): boolean {
if (!model.providerId || !model.modelId) return false
return uiState().models.favorites.some((item) => item.providerId === model.providerId && item.modelId === model.modelId)
}
function toggleFavoriteModelPreference(model: ModelPreference): void {
if (!model.providerId || !model.modelId) return
const favorites = uiState().models.favorites
const exists = favorites.some((item) => item.providerId === model.providerId && item.modelId === model.modelId)
const updated = exists
? favorites.filter((item) => item.providerId !== model.providerId || item.modelId !== model.modelId)
: [model, ...favorites.filter((item) => item.providerId !== model.providerId || item.modelId !== model.modelId)].slice(
0,
MAX_FAVORITE_MODELS,
)
void patchStateOwner("ui", { models: { favorites: updated } }).catch((error) => log.error("Failed to update model favorites", error))
}
function getModelThinkingSelection(model: { providerId: string; modelId: string }): string | undefined {
if (!model.providerId || !model.modelId) return undefined
return uiState().models.thinkingSelections[getModelKey(model)]
}
function setModelThinkingSelection(model: { providerId: string; modelId: string }, value: string | undefined): void {
if (!model.providerId || !model.modelId) return
const key = getModelKey(model)
const current = uiState().models.thinkingSelections[key]
if (current === value) return
const selections = { ...uiState().models.thinkingSelections }
if (!value) {
delete selections[key]
} else {
selections[key] = value
}
void patchStateOwner("ui", { models: { thinkingSelections: selections } }).catch((error) =>
log.error("Failed to update thinking selection", error),
)
}
function setDiffViewMode(mode: DiffViewMode): void {
if (preferences().diffViewMode === mode) return
updateUiSettings({ diffViewMode: mode })
}
function setToolOutputExpansion(mode: ExpansionPreference): void {
if (preferences().toolOutputExpansion === mode) return
updateUiSettings({ toolOutputExpansion: mode })
}
function setDiagnosticsExpansion(mode: ExpansionPreference): void {
if (preferences().diagnosticsExpansion === mode) return
updateUiSettings({ diagnosticsExpansion: mode })
}
function setToolInputsVisibility(mode: ToolInputsVisibilityPreference): void {
if (preferences().toolInputsVisibility === mode) return
updateUiSettings({ toolInputsVisibility: mode })
}
function setThinkingBlocksExpansion(mode: ExpansionPreference): void {
if (preferences().thinkingBlocksExpansion === mode) return
updateUiSettings({ thinkingBlocksExpansion: mode })
}
function toggleShowThinkingBlocks(): void {
updateUiSettings({ showThinkingBlocks: !preferences().showThinkingBlocks })
}
function toggleKeyboardShortcutHints(): void {
updatePreferences({ showKeyboardShortcutHints: !preferences().showKeyboardShortcutHints })
}
function toggleShowTimelineTools(): void {
updateUiSettings({ showTimelineTools: !preferences().showTimelineTools })
}
function toggleUsageMetrics(): void {
updateUiSettings({ showUsageMetrics: !preferences().showUsageMetrics })
}
function togglePromptSubmitOnEnter(): void {
updateUiSettings({ promptSubmitOnEnter: !preferences().promptSubmitOnEnter })
}
function toggleAutoCleanupBlankSessions(): void {
const nextValue = !preferences().autoCleanupBlankSessions
log.info("toggle auto cleanup", { value: nextValue })
updateUiSettings({ autoCleanupBlankSessions: nextValue })
}
async function setAgentModelPreference(instanceId: string, agent: string, model: ModelPreference): Promise<void> {
if (!instanceId || !agent || !model.providerId || !model.modelId) return
await ensureInstanceConfigLoaded(instanceId)
await updateInstanceData(instanceId, (draft) => {
const selections = { ...(draft.agentModelSelections ?? {}) }
const existing = selections[agent]
if (existing && existing.providerId === model.providerId && existing.modelId === model.modelId) {
return
}
selections[agent] = model
draft.agentModelSelections = selections
})
}
async function getAgentModelPreference(instanceId: string, agent: string): Promise<ModelPreference | undefined> {
if (!instanceId || !agent) return undefined
await ensureInstanceConfigLoaded(instanceId)
const selections = getInstanceConfig(instanceId).agentModelSelections ?? {}
return selections[agent]
}
void ensureLoaded().catch((error: unknown) => {
log.error("Failed to initialize settings", error)
})
interface ConfigContextValue {
isLoaded: Accessor<boolean>
preferences: typeof preferences
updatePreferences: typeof updatePreferences
themePreference: typeof themePreference
setThemePreference: typeof setThemePreference
// server-owned stable config
serverSettings: typeof serverSettings
setListeningMode: typeof setListeningMode
updateEnvironmentVariables: typeof updateEnvironmentVariables
addEnvironmentVariable: typeof addEnvironmentVariable
removeEnvironmentVariable: typeof removeEnvironmentVariable
updateLastUsedBinary: typeof updateLastUsedBinary
// ui-owned state
recentFolders: typeof recentFolders
opencodeBinaries: typeof opencodeBinaries
uiState: typeof uiState
addRecentFolder: typeof addRecentFolder
removeRecentFolder: typeof removeRecentFolder
addOpenCodeBinary: typeof addOpenCodeBinary
removeOpenCodeBinary: typeof removeOpenCodeBinary
recordWorkspaceLaunch: typeof recordWorkspaceLaunch
addRecentModelPreference: typeof addRecentModelPreference
isFavoriteModelPreference: typeof isFavoriteModelPreference
toggleFavoriteModelPreference: typeof toggleFavoriteModelPreference
getModelThinkingSelection: typeof getModelThinkingSelection
setModelThinkingSelection: typeof setModelThinkingSelection
// ui settings helpers
toggleShowThinkingBlocks: typeof toggleShowThinkingBlocks
toggleKeyboardShortcutHints: typeof toggleKeyboardShortcutHints
toggleShowTimelineTools: typeof toggleShowTimelineTools
toggleUsageMetrics: typeof toggleUsageMetrics
toggleAutoCleanupBlankSessions: typeof toggleAutoCleanupBlankSessions
togglePromptSubmitOnEnter: typeof togglePromptSubmitOnEnter
setDiffViewMode: typeof setDiffViewMode
setToolOutputExpansion: typeof setToolOutputExpansion
setDiagnosticsExpansion: typeof setDiagnosticsExpansion
setThinkingBlocksExpansion: typeof setThinkingBlocksExpansion
setToolInputsVisibility: typeof setToolInputsVisibility
// instance scoped
setAgentModelPreference: typeof setAgentModelPreference
getAgentModelPreference: typeof getAgentModelPreference
}
const ConfigContext = createContext<ConfigContextValue>()
const configContextValue: ConfigContextValue = {
isLoaded,
preferences,
updatePreferences,
themePreference,
setThemePreference,
serverSettings,
setListeningMode,
updateEnvironmentVariables,
addEnvironmentVariable,
removeEnvironmentVariable,
updateLastUsedBinary,
recentFolders,
opencodeBinaries,
uiState,
addRecentFolder,
removeRecentFolder,
addOpenCodeBinary,
removeOpenCodeBinary,
recordWorkspaceLaunch,
addRecentModelPreference,
isFavoriteModelPreference,
toggleFavoriteModelPreference,
getModelThinkingSelection,
setModelThinkingSelection,
toggleShowThinkingBlocks,
toggleKeyboardShortcutHints,
toggleShowTimelineTools,
toggleUsageMetrics,
toggleAutoCleanupBlankSessions,
togglePromptSubmitOnEnter,
setDiffViewMode,
setToolOutputExpansion,
setDiagnosticsExpansion,
setThinkingBlocksExpansion,
setToolInputsVisibility,
setAgentModelPreference,
getAgentModelPreference,
}
export const ConfigProvider: ParentComponent = (props) => {
onMount(() => {
ensureLoaded().catch((error: unknown) => {
log.error("Failed to initialize settings", error)
})
const unsubUi = storage.onConfigOwnerChanged("ui", (bucket) => {
setUiConfigBucket(bucket as any)
setIsLoaded(true)
})
const unsubServer = storage.onConfigOwnerChanged("server", (bucket) => {
setServerConfigBucket(bucket as any)
setIsLoaded(true)
})
const unsubStateUi = storage.onStateOwnerChanged("ui", (bucket) => {
setUiStateBucket(bucket as any)
setIsLoaded(true)
})
return () => {
unsubUi()
unsubServer()
unsubStateUi()
}
})
return <ConfigContext.Provider value={configContextValue}>{props.children}</ConfigContext.Provider>
}
export function useConfig(): ConfigContextValue {
const context = useContext(ConfigContext)
if (!context) {
throw new Error("useConfig must be used within ConfigProvider")
}
return context
}
export {
preferences,
uiState,
serverSettings,
recentFolders,
opencodeBinaries,
themePreference,
setThemePreference,
updatePreferences,
setListeningMode,
updateEnvironmentVariables,
addEnvironmentVariable,
removeEnvironmentVariable,
updateLastUsedBinary,
addRecentFolder,
removeRecentFolder,
addOpenCodeBinary,
removeOpenCodeBinary,
recordWorkspaceLaunch,
addRecentModelPreference,
isFavoriteModelPreference,
toggleFavoriteModelPreference,
getModelThinkingSelection,
setModelThinkingSelection,
toggleShowThinkingBlocks,
toggleKeyboardShortcutHints,
toggleShowTimelineTools,
toggleUsageMetrics,
toggleAutoCleanupBlankSessions,
togglePromptSubmitOnEnter,
setDiffViewMode,
setToolOutputExpansion,
setDiagnosticsExpansion,
setThinkingBlocksExpansion,
setAgentModelPreference,
getAgentModelPreference,
}