| 1 | //! Host registration/action adapter for the portable debug group. Replaces |
| 2 | //! groups/debug/mod.rs's concrete-App dispatcher; the existing central |
| 3 | //! dispatcher still constructs the declared envelope and consumes the result. |
| 4 | |
| 5 | use super::CommandResult; |
| 6 | use super::groups::debug::*; |
| 7 | use super::traits::{Command, CommandGroup, ContextualCommand}; |
| 8 | use crate::tui::app::AppAction; |
| 9 | use codewhale_command_contract::handler::CommandHandler; |
| 10 | use codewhale_command_contract::metadata::{CommandInfo, RegisterCommand}; |
| 11 | use codewhale_command_contract::outcome::{DebugAction, DebugCommandResult}; |
| 12 | use std::marker::PhantomData; |
| 13 | |
| 14 | pub(super) struct DebugCommands; |
| 15 | |
| 16 | struct HostRegistration<C>(PhantomData<C>); |
| 17 | impl<C: RegisterCommand<DebugCommandResult>> RegisterCommand<CommandResult> |
| 18 | for HostRegistration<C> |
| 19 | { |
| 20 | fn info() -> &'static CommandInfo { |
| 21 | C::info() |
| 22 | } |
| 23 | fn handler() -> CommandHandler<CommandResult> { |
| 24 | match C::handler() { |
| 25 | CommandHandler::Pure(_) => CommandHandler::Pure(|args| match C::handler() { |
| 26 | CommandHandler::Pure(run) => host_result(run(args)), |
| 27 | _ => CommandResult::error("command handler shape changed"), |
| 28 | }), |
| 29 | CommandHandler::Contextual { capabilities, .. } => CommandHandler::Contextual { |
| 30 | capabilities, |
| 31 | handler: |contexts, args| match C::handler() { |
| 32 | CommandHandler::Contextual { handler, .. } => { |
| 33 | host_result(handler(contexts, args)) |
| 34 | } |
| 35 | _ => CommandResult::error("command handler shape changed"), |
| 36 | }, |
| 37 | }, |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | impl CommandGroup for DebugCommands { |
| 43 | fn commands(&self) -> &'static [Box<dyn Command>] { |
| 44 | static COMMANDS: std::sync::OnceLock<Vec<Box<dyn Command>>> = std::sync::OnceLock::new(); |
| 45 | COMMANDS.get_or_init(|| { |
| 46 | let commands: Vec<Box<dyn Command>> = vec![ |
| 47 | Box::new(ContextualCommand::from_contract::<HostRegistration<tokens::TokensCmd>>().expect("debug registration")), |
| 48 | Box::new(ContextualCommand::from_contract::<HostRegistration<tokens::CostCmd>>().expect("debug registration")), |
| 49 | Box::new(ContextualCommand::from_contract::<HostRegistration<receipts::ReceiptsCmd>>().expect("debug registration")), |
| 50 | Box::new(ContextualCommand::from_contract::<HostRegistration<balance::BalanceCmd>>().expect("debug registration")), |
| 51 | Box::new(ContextualCommand::from_contract::<HostRegistration<cache::CacheCmd>>().expect("debug registration")), |
| 52 | Box::new(ContextualCommand::from_contract::<HostRegistration<preview_request::PreviewRequestCmd>>().expect("debug registration")), |
| 53 | Box::new(ContextualCommand::from_contract::<HostRegistration<tool_inspection::ToolsCmd>>().expect("debug registration")), |
| 54 | Box::new(ContextualCommand::from_contract::<HostRegistration<change::ChangeCmd>>().expect("debug registration")), |
| 55 | Box::new(ContextualCommand::from_contract::<HostRegistration<tokens::SystemCmd>>().expect("debug registration")), |
| 56 | Box::new(ContextualCommand::from_contract::<HostRegistration<tokens::ContextCmd>>().expect("debug registration")), |
| 57 | Box::new(ContextualCommand::from_contract::<HostRegistration<undo::EditCmd>>().expect("debug registration")), |
| 58 | Box::new(ContextualCommand::from_contract::<HostRegistration<undo::DiffCmd>>().expect("debug registration")), |
| 59 | Box::new(ContextualCommand::from_contract::<HostRegistration<undo::UndoCmd>>().expect("debug registration")), |
| 60 | Box::new(ContextualCommand::from_contract::<HostRegistration<undo::RetryCmd>>().expect("debug registration")), |
| 61 | ]; |
| 62 | assert_eq!(commands.iter().map(|command| command.info().name).collect::<Vec<_>>(), |
| 63 | portable_handlers().iter().map(|(info, _)| info.name).collect::<Vec<_>>(), |
| 64 | "host registry must cover the complete portable debug inventory"); |
| 65 | commands |
| 66 | }).as_slice() |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | fn sync_session(sync: codewhale_command_contract::facets::SessionSyncPayload) -> AppAction { |
| 71 | AppAction::SyncSession { |
| 72 | session_id: sync.session_id, |
| 73 | messages: sync.messages, |
| 74 | system_prompt: sync.system_prompt, |
| 75 | model: sync.model, |
| 76 | workspace: sync.workspace, |
| 77 | mode: super::contract::from_command_mode(sync.mode), |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | pub(in crate::commands) fn host_result(result: DebugCommandResult) -> CommandResult { |
| 82 | let action = result.action.map(|action| match action { |
| 83 | DebugAction::FetchBalance => AppAction::FetchBalance, |
| 84 | DebugAction::CacheWarmup => AppAction::CacheWarmup, |
| 85 | DebugAction::PreviewOutboundRequest { |
| 86 | json, |
| 87 | base_prompt_only, |
| 88 | hypothetical_prompt, |
| 89 | } => AppAction::PreviewOutboundRequest { |
| 90 | json, |
| 91 | base_prompt_only, |
| 92 | hypothetical_prompt, |
| 93 | }, |
| 94 | DebugAction::OpenTextPager { title, content } => { |
| 95 | AppAction::OpenTextPager { title, content } |
| 96 | } |
| 97 | DebugAction::OpenContextInspector => AppAction::OpenContextInspector, |
| 98 | DebugAction::SendMessage(input) => AppAction::SendMessage(input), |
| 99 | DebugAction::SyncSession(sync) => sync_session(sync), |
| 100 | DebugAction::ConversationUndo { sync, retry_input } => AppAction::ConversationUndo { |
| 101 | sync, |
| 102 | retry_input, |
| 103 | edit_replacement: false, |
| 104 | }, |
| 105 | }); |
| 106 | CommandResult { |
| 107 | message: result.message, |
| 108 | action, |
| 109 | is_error: result.is_error, |
| 110 | } |
| 111 | } |
| 112 |