/** @jsxImportSource @opentui/solid */ import type { TuiPluginApi } from '@opencode-ai/plugin/tui' import type { OpenDialogPayload } from '../rpc/protocol.js' type ApplyFn = ( command: OpenDialogPayload['command'], args: string, ) => Promise<{ text: string; knobs: Record }> type KillswitchDialogConfig = { enabled?: boolean main?: Record accounts?: Record> } export function buildKillswitchThresholdSeed( config: KillswitchDialogConfig, accountIds: string[], ) { const readT = (t: Record | undefined) => { const fh = t?.five_hour ?? t?.['5h'] ?? 5 const sd = t?.seven_day ?? t?.['1w'] ?? 10 const scoped = t?.scoped ?? 0 return { fh, sd, scoped } } const mainT = readT(config.main) const seedParts = [`main:${mainT.fh},${mainT.sd},${mainT.scoped}`] for (const id of accountIds) { const t = readT(config.accounts?.[id] ?? config.main) seedParts.push(`${id}:${t.fh},${t.sd},${t.scoped}`) } return seedParts.join(' ') } export function buildAccountDialogOption(account: { id: string label: string role: string enabled: boolean quotaPercent: number | null tierLabel?: string }) { const pct = account.quotaPercent != null ? ` ${Math.round(account.quotaPercent)}%` : ' \u2013%' const status = !account.enabled ? ' (disabled)' : '' return { title: `${account.label} [${account.role}]${status}${pct}`, value: account.id, ...(account.tierLabel && { description: account.tierLabel }), } } function showText(api: TuiPluginApi, text: string) { api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( {text} )) } export function openCommandDialog( api: TuiPluginApi, payload: OpenDialogPayload, apply: ApplyFn, ) { if (payload.command === 'claude-routing') { const current = (payload.knobs.mode as string) ?? 'main-first' const DialogSelect = api.ui.DialogSelect api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { void apply('claude-routing', String(option.value)).then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) }} /> )) return } if (payload.command === 'claude-fast' || payload.command === 'claude-dump') { const enabled = payload.knobs.enabled === true const label = payload.command === 'claude-fast' ? 'fast mode' : 'request dump' const DialogConfirm = api.ui.DialogConfirm api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { void apply(payload.command, enabled ? 'off' : 'on').then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) }} onCancel={() => api.ui.dialog.clear()} /> )) return } if (payload.command === 'claude-cache') { const enabled = payload.knobs.enabled === true const mode = (payload.knobs.mode as string) ?? 'hybrid' const currentValue = enabled ? mode : 'off' const DialogSelect = api.ui.DialogSelect api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { if (option.value === 'off') { void apply('claude-cache', 'off').then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) return } void apply('claude-cache', `mode ${option.value}`) .then(() => apply('claude-cache', 'on')) .then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) }} /> )) return } if (payload.command === 'claude-cachekeep') { const window = payload.knobs.window as | { startHour: number; endHour: number } | undefined const seed = window ? `${String(window.startHour).padStart(2, '0')}-${String(window.endHour).padStart(2, '0')}` : '' const DialogPrompt = api.ui.DialogPrompt api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( {payload.text}} placeholder="'always', HH-HH (e.g. 08-20), or 'off'" value={seed} onConfirm={(value: string) => { void apply('claude-cachekeep', value.trim()).then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) }} onCancel={() => api.ui.dialog.clear()} /> )) return } if (payload.command === 'claude-killswitch') { const config = (payload.knobs.config ?? {}) as KillswitchDialogConfig const accountIds = (payload.knobs.accountIds as string[]) ?? [] const enabled = config.enabled === true const seed = buildKillswitchThresholdSeed(config, accountIds) const openEdit = () => { const DialogPrompt = api.ui.DialogPrompt api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( {payload.text}} placeholder='main:5,10,0 work-alt:5,10,0' value={seed} onConfirm={(value: string) => { void apply('claude-killswitch', `set ${value.trim()}`).then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) }} onCancel={() => api.ui.dialog.clear()} /> )) } const DialogSelect = api.ui.DialogSelect api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { if (option.value === 'edit') { openEdit() return } void apply('claude-killswitch', String(option.value)).then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) }} /> )) return } if (payload.command === 'claude-account') { const accounts = (payload.knobs.accounts as Array<{ id: string label: string role: string enabled: boolean quotaPercent: number | null tierLabel?: string }>) ?? [] const updateAccounts = (r: { text: string knobs: Record }) => { const updated = r.knobs.accounts as typeof accounts if (updated && updated.length > 0) { accounts.length = 0 accounts.push(...updated) } } const buildL1 = () => { const DialogSelect = api.ui.DialogSelect const l1Options: Array<{ title: string value: string description?: string }> = [ { title: 'Add account\u2026', value: '__add__', description: 'Add an API key or OAuth fallback account', }, ...accounts.map(buildAccountDialogOption), ] api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { if (option.value === '__add__') { openAddType() return } const account = accounts.find((a) => a.id === option.value) if (!account) return if (account.role === 'main') { const pct = account.quotaPercent != null ? ` ${Math.round(account.quotaPercent)}%` : ' \u2013%' showText( api, `${account.label}\nRole: main (read-only)\nQuota:${pct}`, ) return } openManage(account, false) }} /> )) } // -- Add type selection (OAuth vs API key) ------------------------------ const openAddType = () => { const DialogSelect = api.ui.DialogSelect api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { if (option.value === 'back') { buildL1() return } if (option.value === 'apikey') { openAddApiKey() return } openAddOAuthStart() }} /> )) } // -- Add API key (multi-step: key → baseURL → authHeader → label) ------ const openAddApiKey = () => { const collected: { apiKey?: string baseURL?: string authHeader?: string label?: string } = {} const openApiKeyPrompt = () => { const DialogPrompt = api.ui.DialogPrompt api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( Paste your API key (required).} placeholder='sk-ant-...' value='' onConfirm={(value: string) => { const trimmed = value.trim() if (!trimmed) { openAddType() return } collected.apiKey = trimmed openBaseURLPrompt() }} onCancel={() => openAddType()} /> )) } const openBaseURLPrompt = () => { const DialogPrompt = api.ui.DialogPrompt api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( ( Anthropic-compatible API base URL. Default: https://api.kie.ai/claude )} placeholder='https://api.kie.ai/claude' value='' onConfirm={(value: string) => { const trimmed = value.trim() collected.baseURL = trimmed || 'https://api.kie.ai/claude' openAuthHeaderSelect() }} onCancel={() => openApiKeyPrompt()} /> )) } const openAuthHeaderSelect = () => { const DialogSelect = api.ui.DialogSelect api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { collected.authHeader = option.value as | 'authorization-bearer' | 'x-api-key' openLabelPrompt() }} /> )) } const openLabelPrompt = () => { const DialogPrompt = api.ui.DialogPrompt api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( ( A short name for this account (optional). )} placeholder='e.g. Work API' value='' onConfirm={(value: string) => { const trimmed = value.trim() collected.label = trimmed || undefined const apiKey = collected.apiKey if (!apiKey) return let args = `add-apikey ${apiKey}` if ( collected.baseURL && collected.baseURL !== 'https://api.kie.ai/claude' ) { args += ` --base-url ${collected.baseURL}` } if ( collected.authHeader && collected.authHeader !== 'authorization-bearer' ) { args += ` --auth-header ${collected.authHeader}` } if (collected.label) { args += ` --label ${collected.label}` } void apply('claude-account', args).then((r) => { api.ui.toast({ message: r.text }) updateAccounts(r) buildL1() }) }} onCancel={() => openAuthHeaderSelect()} /> )) } openApiKeyPrompt() } // -- Add OAuth (OSC-52 copy + code entry) ------------------------------ const openAddOAuthStart = () => { void apply('claude-account', 'add-oauth-start').then((r) => { const oauthUrl = r.knobs.oauthUrl as string | undefined updateAccounts(r) if (oauthUrl) { openOAuthUrlScreen(oauthUrl) } else { api.ui.toast({ message: r.text }) buildL1() } }) } const openOAuthUrlScreen = (oauthUrl: string) => { const DialogSelect = api.ui.DialogSelect api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( { if (option.value === 'cancel') { buildL1() return } if (option.value === 'copy') { const ok = api.renderer.copyToClipboardOSC52(oauthUrl) if (ok) { api.ui.toast({ message: 'URL copied to clipboard' }) } else { api.ui.toast({ message: 'Copy unavailable \u2014 select the URL text above to copy', }) } openOAuthUrlScreen(oauthUrl) return } openOAuthCodePrompt(oauthUrl) }} /> )) } const openOAuthCodePrompt = (oauthUrl: string) => { const DialogPrompt = api.ui.DialogPrompt api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( ( After signing in you will be redirected. Paste the full callback URL or authorization code below. )} placeholder='Paste callback URL or code here' value='' onConfirm={(value: string) => { const trimmed = value.trim() if (!trimmed) { buildL1() return } openOAuthLabelPrompt(trimmed, oauthUrl) }} // Step BACK to the sign-in URL screen, not L1: the OAuth session // (PKCE verifier/state) is already minted. Returning to L1 would let a // retry re-run add-oauth-start and re-mint it, invalidating the URL the // user is mid-sign-in with. Same session → same URL is preserved. onCancel={() => openOAuthUrlScreen(oauthUrl)} /> )) } const openOAuthLabelPrompt = (code: string, oauthUrl: string) => { const DialogPrompt = api.ui.DialogPrompt api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( ( A short name for this account (optional). )} placeholder='e.g. work' value='' onConfirm={(value: string) => { const label = value.trim() const args = label ? `add-oauth-finish ${code} --label ${label}` : `add-oauth-finish ${code}` void apply('claude-account', args).then((r) => { api.ui.toast({ message: r.text }) updateAccounts(r) buildL1() }) }} // Step BACK to the code prompt, not L1: the user already obtained an // auth code to reach this step. Returning to L1 would let a retry // re-run add-oauth-start, minting a new PKCE verifier/state that // invalidates the code they already have and forces a full re-auth. onCancel={() => openOAuthCodePrompt(oauthUrl)} /> )) } // -- Manage existing account ------------------------------------------- const openManage = ( account: (typeof accounts)[number], isMain: boolean, ) => { const DialogSelect = api.ui.DialogSelect const DialogConfirm = api.ui.DialogConfirm api.ui.dialog.setSize('xlarge') const options: Array<{ title: string value: string description?: string }> = [] if (!isMain) { const toggleLabel = account.enabled ? 'Disable' : 'Enable' options.push({ title: toggleLabel, value: account.enabled ? 'disable' : 'enable', description: account.enabled ? 'Stop using this fallback account' : 'Allow this fallback account to be used', }) options.push({ title: 'Move up', value: 'move-up', description: 'Higher priority in fallback order', }) options.push({ title: 'Move down', value: 'move-down', description: 'Lower priority in fallback order', }) options.push({ title: 'Remove\u2026', value: 'remove', description: 'Delete this account permanently', }) } options.push({ title: 'Back', value: 'back' }) api.ui.dialog.replace(() => ( { if (option.value === 'back') { buildL1() return } if (option.value === 'remove') { api.ui.dialog.replace(() => ( { void apply('claude-account', `remove ${account.id}`).then( (r) => { api.ui.toast({ message: r.text }) updateAccounts(r) buildL1() }, ) }} onCancel={() => openManage(account, isMain)} /> )) return } void apply('claude-account', `${option.value} ${account.id}`).then( (r) => { api.ui.toast({ message: r.text }) updateAccounts(r) const updatedList = r.knobs.accounts as typeof accounts const refreshed = (updatedList && updatedList.length > 0 ? updatedList.find((a) => a.id === account.id) : undefined) ?? account openManage(refreshed, isMain) }, ) }} /> )) } buildL1() return } if (payload.command === 'claude-logging') { const current = (payload.knobs.level as string) ?? 'info' const levels = ['error', 'warn', 'info', 'debug', 'trace'] const DialogSelect = api.ui.DialogSelect api.ui.dialog.setSize('xlarge') api.ui.dialog.replace(() => ( ({ title: level === current ? `\u2022 ${level}` : level, value: level, }))} onSelect={(option) => { void apply('claude-logging', String(option.value)).then((r) => { api.ui.toast({ message: r.text }) api.ui.dialog.clear() }) }} /> )) return } // fallback for quota (display-only) showText(api, payload.text) }