Skip to main content

ai_agent/types/
command.rs

1// Source: ~/claudecode/openclaudecode/src/types/command.ts
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use uuid::Uuid;
6
7use crate::types::ids::AgentId;
8use crate::types::logs::LogOption;
9use crate::types::message::Message;
10
11/// Local command result type.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(tag = "type")]
14pub enum LocalCommandResult {
15    #[serde(rename = "text")]
16    Text { value: String },
17    #[serde(rename = "compact")]
18    Compact {
19        #[serde(rename = "compactionResult")]
20        compaction_result: serde_json::Value,
21        #[serde(skip_serializing_if = "Option::is_none")]
22        #[serde(rename = "displayText")]
23        display_text: Option<String>,
24    },
25    #[serde(rename = "skip")]
26    Skip,
27}
28
29/// Setting source enum.
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32pub enum SettingSource {
33    UserSettings,
34    ProjectSettings,
35    LocalSettings,
36    PolicySettings,
37}
38
39/// Prompt command configuration.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct PromptCommand {
42    #[serde(rename = "type")]
43    pub command_type: String, // "prompt"
44    #[serde(rename = "progressMessage")]
45    pub progress_message: String,
46    #[serde(rename = "contentLength")]
47    pub content_length: usize,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    #[serde(rename = "argNames")]
50    pub arg_names: Option<Vec<String>>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[serde(rename = "allowedTools")]
53    pub allowed_tools: Option<Vec<String>>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub model: Option<String>,
56    pub source: CommandSource,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    #[serde(rename = "pluginInfo")]
59    pub plugin_info: Option<PluginInfo>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    #[serde(rename = "disableNonInteractive")]
62    pub disable_non_interactive: Option<bool>,
63    /// Hooks to register when this skill is invoked
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub hooks: Option<serde_json::Value>,
66    /// Base directory for skill resources
67    #[serde(skip_serializing_if = "Option::is_none")]
68    #[serde(rename = "skillRoot")]
69    pub skill_root: Option<String>,
70    /// Execution context: 'inline' (default) or 'fork' (run as sub-agent)
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub context: Option<ExecutionContext>,
73    /// Agent type to use when forked
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub agent: Option<String>,
76    /// Effort level
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub effort: Option<String>,
79    /// Glob patterns for file paths this skill applies to
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub paths: Option<Vec<String>>,
82}
83
84/// Where a command was loaded from.
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(rename_all = "snake_case")]
87pub enum CommandSource {
88    UserSettings,
89    ProjectSettings,
90    LocalSettings,
91    PolicySettings,
92    Builtin,
93    Mcp,
94    Plugin,
95}
96
97/// Plugin info for a command.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct PluginInfo {
100    #[serde(rename = "pluginManifest")]
101    pub plugin_manifest: serde_json::Value,
102    pub repository: String,
103}
104
105/// Execution context for commands.
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "lowercase")]
108pub enum ExecutionContext {
109    Inline,
110    Fork,
111}
112
113/// Local command module shape.
114pub type LocalCommandModule = serde_json::Value;
115
116/// Local command definition.
117pub struct LocalCommand {
118    pub command_type: String, // "local"
119    pub supports_non_interactive: bool,
120    pub load: Box<
121        dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = LocalCommandModule> + Send>>
122            + Send
123            + Sync,
124    >,
125}
126
127/// Resume entry point.
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129#[serde(rename_all = "snake_case")]
130pub enum ResumeEntrypoint {
131    CliFlag,
132    SlashCommandPicker,
133    SlashCommandSessionId,
134    SlashCommandTitle,
135    Fork,
136}
137
138/// Command result display enum.
139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
140#[serde(rename_all = "lowercase")]
141pub enum CommandResultDisplay {
142    Skip,
143    System,
144    User,
145}
146
147/// Options for command completion.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct CommandCompleteOptions {
150    /// How to display the result
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub display: Option<CommandResultDisplay>,
153    /// If true, send messages to the model after command completes
154    #[serde(skip_serializing_if = "Option::is_none")]
155    #[serde(rename = "shouldQuery")]
156    pub should_query: Option<bool>,
157    /// Additional messages to insert as isMeta
158    #[serde(skip_serializing_if = "Option::is_none")]
159    #[serde(rename = "metaMessages")]
160    pub meta_messages: Option<Vec<String>>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    #[serde(rename = "nextInput")]
163    pub next_input: Option<String>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    #[serde(rename = "submitNextInput")]
166    pub submit_next_input: Option<bool>,
167}
168
169/// Callback when a command completes.
170pub type LocalJsxCommandOnDone =
171    Box<dyn Fn(Option<String>, Option<CommandCompleteOptions>) + Send + Sync>;
172
173/// Local JSX command module shape.
174pub type LocalJsxCommandModule = serde_json::Value;
175
176/// Local JSX command definition.
177pub struct LocalJsxCommand {
178    pub command_type: String, // "local-jsx"
179    pub load: Box<
180        dyn Fn()
181                -> std::pin::Pin<Box<dyn std::future::Future<Output = LocalJsxCommandModule> + Send>>
182            + Send
183            + Sync,
184    >,
185}
186
187/// Callback when a command declares who can use it (auth/provider requirement, static).
188/// This is separate from `isEnabled()`:
189///   - `availability` = who can use this (auth/provider requirement, static)
190///   - `isEnabled()`  = is this turned on right now (GrowthBook, platform, env vars)
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
192#[serde(rename_all = "kebab-case")]
193pub enum CommandAvailability {
194    /// claude.ai OAuth subscriber (Pro/Max/Team/Enterprise via claude.ai)
195    ClaudeAi,
196    /// Console API key user (direct api.anthropic.com, not via claude.ai OAuth)
197    Console,
198}
199
200/// Base command shared by all command types.
201pub struct CommandBase {
202    pub availability: Option<Vec<CommandAvailability>>,
203    pub description: String,
204    pub has_user_specified_description: Option<bool>,
205    /// Defaults to true. Only set when the command has conditional enablement.
206    pub is_enabled: Option<Box<dyn Fn() -> bool + Send + Sync>>,
207    /// Defaults to false. Only set when the command should be hidden from typeahead/help.
208    pub is_hidden: Option<bool>,
209    pub name: String,
210    pub aliases: Option<Vec<String>>,
211    pub is_mcp: Option<bool>,
212    pub argument_hint: Option<String>,
213    /// Detailed usage scenarios for when to use this command
214    pub when_to_use: Option<String>,
215    /// Version of the command/skill
216    pub version: Option<String>,
217    /// Whether to disable this command from being invoked by models
218    pub disable_model_invocation: Option<bool>,
219    /// Whether users can invoke this skill by typing /skill-name
220    pub user_invocable: Option<bool>,
221    /// Where the command was loaded from
222    pub loaded_from: Option<CommandLoadSource>,
223    /// Distinguishes workflow-backed commands (badged in autocomplete)
224    pub kind: Option<CommandKind>,
225    /// If true, command executes immediately without waiting for a stop point
226    pub immediate: Option<bool>,
227    /// If true, args are redacted from the conversation history
228    pub is_sensitive: Option<bool>,
229}
230
231/// Where the command was loaded from.
232#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
233#[serde(rename_all = "snake_case")]
234pub enum CommandLoadSource {
235    CommandsDeprecated,
236    Skills,
237    Plugin,
238    Managed,
239    Bundled,
240    Mcp,
241}
242
243/// Command kind enum.
244#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
245#[serde(rename_all = "lowercase")]
246pub enum CommandKind {
247    Workflow,
248}
249
250/// Resolves the user-visible name, falling back to `cmd.name` when not overridden.
251pub fn get_command_name(cmd: &CommandBase) -> &str {
252    &cmd.name
253}
254
255/// Resolves whether the command is enabled, defaulting to true.
256pub fn is_command_enabled(is_enabled: Option<&Box<dyn Fn() -> bool + Send + Sync>>) -> bool {
257    match is_enabled {
258        Some(f) => f(),
259        None => true,
260    }
261}
262
263/// Unified command enum combining base with specific implementation types.
264pub enum Command {
265    Prompt {
266        base: CommandBase,
267        prompt: PromptCommand,
268    },
269    Local {
270        base: CommandBase,
271        local: LocalCommand,
272    },
273    LocalJsx {
274        base: CommandBase,
275        local_jsx: LocalJsxCommand,
276    },
277}