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