/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type { CliUiRuntime } from '../cliUiRuntime.js'; import { useCallback, useState } from 'react'; import type { RecordingIntegration, Todo, AgentRequestInput, } from '@vybestack/llxprt-code-core'; import type { Agent } from '@vybestack/llxprt-code-agents'; import type { UseHistoryManagerReturn } from './useHistoryManager.js'; import type { RecordingSwapCallbacks } from '../../services/performResume.js'; import { useSessionStats } from '../contexts/SessionContext.js'; import type { HistoryItemWithoutId, SlashCommandProcessorResult, } from '../types.js'; import type { LoadedSettings } from '../../config/settings.js'; import type { SlashCommand } from '../commands/types.js'; import type { ExtensionUpdateState } from '../state/extensions.js'; import { processSlashCommand } from './slashCommandHandlers.js'; import { confirmationLogger, slashCommandLogger, type SlashCommandProcessorActions, } from './slashCommandProcessor.js'; import { loadBuiltinSlashCommandsForTesting, useCommandContext, useCommandReload, useManagers, usePendingHistory, } from './slashCommandProcessorSupport.js'; interface TodoContextValue { todos: Todo[]; updateTodos: (todos: Todo[]) => void; refreshTodos: () => void; } export type SlashCommandProcessorCoreResult = { handleSlashCommand: ( rawQuery: AgentRequestInput, oneTimeShellAllowlist?: Set, overwriteConfirmed?: boolean, addToHistory?: boolean, ) => Promise; slashCommands: readonly SlashCommand[] | undefined; pendingHistoryItems: HistoryItemWithoutId[]; commandContext: ReturnType; confirmationRequest: { prompt: React.ReactNode; onConfirm: (confirmed: boolean) => void; } | null; }; export interface UseSlashCommandProcessorCoreArgs { config: CliUiRuntime | null; agent: Agent | null; settings: LoadedSettings; addItem: UseHistoryManagerReturn['addItem']; clearItems: UseHistoryManagerReturn['clearItems']; loadHistory: UseHistoryManagerReturn['loadHistory']; refreshStatic: () => void; toggleVimEnabled: () => Promise; setIsProcessing: (isProcessing: boolean) => void; setLlxprtMdFileCount: (count: number) => void; actions: SlashCommandProcessorActions; extensionsUpdateState: Map; isConfigInitialized: boolean; todoContext?: TodoContextValue; recordingIntegration?: RecordingIntegration; recordingSwapCallbacks?: RecordingSwapCallbacks; } interface SlashCommandProcessorState { commands: readonly SlashCommand[] | undefined; setCommands: (commands: readonly SlashCommand[]) => void; reloadTrigger: number; reloadCommands: () => void; localIsProcessing: boolean; setLocalIsProcessing: (isProcessing: boolean) => void; sessionShellAllowlist: Set; setSessionShellAllowlist: React.Dispatch>>; confirmationRequest: { prompt: React.ReactNode; onConfirm: (confirmed: boolean) => void; } | null; setConfirmationRequest: ( request: { prompt: React.ReactNode; onConfirm: (confirmed: boolean) => void; } | null, ) => void; } function useSlashCommandProcessorState( config: CliUiRuntime | null, ): SlashCommandProcessorState { const [commands, setCommands] = useState( () => process.env.LLXPRT_CODE_BUILTIN_COMMANDS_ONLY === 'true' ? loadBuiltinSlashCommandsForTesting(config) : undefined, ); const [reloadTrigger, setReloadTrigger] = useState(0); const [localIsProcessing, setLocalIsProcessing] = useState(false); const [sessionShellAllowlist, setSessionShellAllowlist] = useState( new Set(), ); const [confirmationRequest, setConfirmationRequest] = useState void; }>(null); const reloadCommands = useCallback(() => { setReloadTrigger((v) => v + 1); }, []); return { commands, setCommands, reloadTrigger, reloadCommands, localIsProcessing, setLocalIsProcessing, sessionShellAllowlist, setSessionShellAllowlist, confirmationRequest, setConfirmationRequest, }; } export function useSlashCommandProcessorCore( args: UseSlashCommandProcessorCoreArgs, ): SlashCommandProcessorCoreResult { const session = useSessionStats(); const state = useSlashCommandProcessorState(args.config); const managers = useManagers(args.config); const pending = usePendingHistory(args.addItem); const commandContext = useCommandContext({ config: args.config, agent: args.agent, settings: args.settings, ...managers, addItem: args.addItem, clearItems: args.clearItems, loadHistory: args.loadHistory, refreshStatic: args.refreshStatic, toggleVimEnabled: args.toggleVimEnabled, setLlxprtMdFileCount: args.setLlxprtMdFileCount, actions: args.actions, alternateBuffer: args.settings.merged.ui.useAlternateBuffer === true && !(args.config?.getScreenReader() ?? false), ...pending, sessionShellAllowlist: state.sessionShellAllowlist, localIsProcessing: state.localIsProcessing, reloadCommands: state.reloadCommands, extensionsUpdateState: args.extensionsUpdateState, todoContext: args.todoContext, recordingIntegration: args.recordingIntegration, recordingSwapCallbacks: args.recordingSwapCallbacks, stats: session, }); useCommandReload( args.config, state.reloadTrigger, args.isConfigInitialized, state.reloadCommands, state.setCommands, ); const handleSlashCommand = useCallback( ( rawQuery: AgentRequestInput, oneTimeShellAllowlist?: Set, overwriteConfirmed: boolean | undefined = undefined, addToHistory: boolean = true, ): Promise => processSlashCommand( { commands: state.commands, config: args.config, commandContext, actions: args.actions, addItem: args.addItem, addMessage: pending.addMessage, setIsProcessing: args.setIsProcessing, setLocalIsProcessing: state.setLocalIsProcessing, setPendingItem: pending.setPendingItem, setSessionShellAllowlist: state.setSessionShellAllowlist, setConfirmationRequest: state.setConfirmationRequest, recordingIntegration: args.recordingIntegration, recordingSwapCallbacks: args.recordingSwapCallbacks, confirmationLogger, slashCommandLogger, }, rawQuery, oneTimeShellAllowlist, overwriteConfirmed, addToHistory, ), [args, commandContext, pending, state], ); return { handleSlashCommand, slashCommands: state.commands, pendingHistoryItems: pending.pendingHistoryItems, commandContext, confirmationRequest: state.confirmationRequest, }; }