fix(model): require confirmation for expensive model selections

Rebased onto current main and re-ported across the restructured
surfaces: model flows now thread confirm_provider/base_url/api_key
through hermes_cli/model_setup_flows.py, the Discord picker lives in
plugins/platforms/discord/adapter.py, and the web dashboard picker
applies chat-mode switches via config.set so the expensive-model
confirmation can ride the response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Robin Fernandes
2026-06-10 00:24:06 -07:00
committed by Teknium
co-authored by Claude Fable 5
parent 4eadef18a9
commit af978ecb17
27 changed files with 1354 additions and 111 deletions
@@ -108,6 +108,7 @@ describe('createSlashHandler', () => {
expect(createSlashHandler(ctx)('/model x-model')).toBe(true)
expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', {
confirm_expensive_model: false,
key: 'model',
session_id: 'sid-abc',
value: 'x-model'
@@ -128,6 +129,7 @@ describe('createSlashHandler', () => {
createSlashHandler(ctx)(`/model anthropic/claude-sonnet-4.6 --provider openrouter ${TUI_SESSION_MODEL_FLAG}`)
).toBe(true)
expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', {
confirm_expensive_model: false,
key: 'model',
session_id: 'sid-abc',
value: 'anthropic/claude-sonnet-4.6 --provider openrouter'
@@ -140,6 +142,7 @@ describe('createSlashHandler', () => {
createSlashHandler(ctx)('/model x-model --global')
expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', {
confirm_expensive_model: false,
key: 'model',
session_id: 'sid-abc',
value: 'x-model --global'
+19 -2
View File
@@ -72,10 +72,25 @@ export const sessionCommands: SlashCommand[] = [
return patchOverlayState({ modelPicker: true })
}
ctx.gateway
.rpc<ConfigSetResponse>('config.set', { key: 'model', session_id: ctx.sid, value: modelValueForConfigSet(arg) })
const switchModel = (confirmExpensiveModel = false) => ctx.gateway
.rpc<ConfigSetResponse>('config.set', { confirm_expensive_model: confirmExpensiveModel, key: 'model', session_id: ctx.sid, value: modelValueForConfigSet(arg) })
.then(
ctx.guarded<ConfigSetResponse>(r => {
if (r.confirm_required) {
patchOverlayState({
confirm: {
cancelLabel: 'Cancel',
confirmLabel: 'Switch anyway',
danger: true,
detail: r.confirm_message || r.warning || 'This model has unusually high known pricing.',
onConfirm: () => switchModel(true),
title: 'Expensive model selection'
}
})
return
}
if (!r.value) {
return ctx.transcript.sys('error: invalid response: model switch')
}
@@ -89,6 +104,8 @@ export const sessionCommands: SlashCommand[] = [
}))
})
)
switchModel()
}
},
+2
View File
@@ -103,6 +103,8 @@ export interface ConfigGetValueResponse {
}
export interface ConfigSetResponse {
confirm_message?: string
confirm_required?: boolean
credential_warning?: string
history_reset?: boolean
info?: SessionInfo