Skip to main content

everruns_core/
command.rs

1// Custom Commands System
2//
3// Commands are user-invocable actions triggered via /slash syntax.
4// Two sources:
5// 1. System commands — from capabilities, execute through a dedicated handler
6//    without persisting a chat message
7// 2. Skill commands — from skills with user-invocable: true, expand to prompt
8//
9// Skills marked user-invocable appear in the command palette alongside
10// system commands. The UI fetches available commands and renders autocomplete.
11
12use crate::message::Controls;
13use crate::typed_id::SessionId;
14use crate::user_facing_error::UserFacingErrorFields;
15use serde::{Deserialize, Serialize};
16
17#[cfg(feature = "openapi")]
18use utoipa::ToSchema;
19
20/// Descriptor for a command available in a session
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[cfg_attr(feature = "openapi", derive(ToSchema))]
23pub struct CommandDescriptor {
24    /// Command name (used as /name)
25    pub name: String,
26    /// Human-readable description shown in autocomplete
27    pub description: String,
28    /// Where this command comes from
29    pub source: CommandSource,
30    /// Arguments this command accepts
31    #[serde(default, skip_serializing_if = "Vec::is_empty")]
32    pub args: Vec<CommandArg>,
33}
34
35/// Where a command originates from
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37#[cfg_attr(feature = "openapi", derive(ToSchema))]
38#[serde(rename_all = "snake_case")]
39pub enum CommandSource {
40    /// Built-in system command from a capability, executed out-of-band from the
41    /// main chat history. Handlers may call the model or do direct work.
42    System,
43    /// From a skill with user-invocable: true (expands to prompt, triggers LLM)
44    Skill,
45}
46
47/// Argument descriptor for a command
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[cfg_attr(feature = "openapi", derive(ToSchema))]
50pub struct CommandArg {
51    /// Argument name
52    pub name: String,
53    /// Description of the argument
54    pub description: String,
55    /// Whether the argument is required
56    #[serde(default)]
57    pub required: bool,
58    /// Static list of suggested values for this argument. Captured when
59    /// `Capability::commands()` is collected so renderers can surface
60    /// autocomplete entries without round-tripping back to the capability
61    /// on every keystroke. Empty means free-form input. Renderers should
62    /// treat the list as suggestions, not constraints — the capability's
63    /// `execute_command` is still the authority on what's accepted.
64    #[serde(default, skip_serializing_if = "Vec::is_empty")]
65    pub suggestions: Vec<String>,
66}
67
68/// Request payload for executing a system command
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[cfg_attr(feature = "openapi", derive(ToSchema))]
71pub struct ExecuteCommandRequest {
72    /// Command name without the leading slash
73    pub name: String,
74    /// Raw argument text after the command token
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub arguments: Option<String>,
77    /// Optional per-invocation runtime controls
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub controls: Option<Controls>,
80}
81
82/// Context handed to [`crate::capabilities::Capability::execute_command`] when a
83/// system command is dispatched. Carries only data that is safe to expose
84/// across the trait surface; capabilities that need handles to runtime state
85/// (provider store, file system, etc.) own those references directly via the
86/// capability's constructor.
87#[derive(Debug, Clone)]
88pub struct CommandExecutionContext {
89    /// Session the command is being executed against.
90    pub session_id: SessionId,
91}
92
93/// Result of executing a system command
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[cfg_attr(feature = "openapi", derive(ToSchema))]
96pub struct CommandResult {
97    /// Whether the command succeeded
98    pub success: bool,
99    /// Human-readable message describing the result
100    pub message: String,
101    /// Stable error code when `success` is false. Mirrors the codes emitted on
102    /// chat error messages so the UI can localize the copy.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub error_code: Option<String>,
105    /// Optional structured fields associated with `error_code` (provider,
106    /// model_id, retry_after, …).
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
109    pub error_fields: Option<UserFacingErrorFields>,
110}