agent_workspace_contract/environment/
command_context.rs1use super::*;
3use agent_secret_contract::SecretRef;
4
5pub const COMMAND_CONTEXTS_PATH: &str = "/internal/v1/workspace/command-contexts";
6pub const COMMAND_CONTEXT_PATH: &str = "/internal/v1/workspace/command-contexts/{contextId}";
7pub const COMMAND_CONTEXT_CLOSE_PATH: &str =
8 "/internal/v1/workspace/command-contexts/{contextId}/close";
9pub const COMMAND_CONTEXT_BIND_RUN_PATH: &str =
10 "/internal/v1/workspace/command-contexts/{contextId}/bind-run";
11pub const COMMAND_CONTEXT_MAX_TTL_MS: u64 = 24 * 60 * 60 * 1000;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(deny_unknown_fields, rename_all = "camelCase")]
15pub struct CreateCommandContextRequest {
16 pub agent_id: String,
17 pub agent_revision: u64,
18 pub computer_id: String,
19 pub space_id: String,
20 pub skills: Vec<String>,
22 #[serde(default)]
23 pub temporary_skills: Vec<TemporaryCommandContextSkill>,
24 #[serde(default)]
25 pub project_skills: Vec<ProjectCommandContextSkill>,
26 pub ttl_ms: u64,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum CommandContextState {
32 Ready,
33 Closed,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct CommandContextSkill {
39 #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub clis: Vec<InstalledSpaceCli>,
41 #[serde(default)]
42 pub source: CommandContextSkillSource,
43 pub instructions: String,
44 pub instructions_digest: String,
45 pub name: String,
46 pub version: String,
47 pub install_path: String,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct CommandContextResource {
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub execution_run_id: Option<String>,
55 pub id: String,
56 pub agent_id: String,
57 pub agent_revision: u64,
58 pub computer_id: String,
59 pub space_id: String,
60 pub skills: Vec<CommandContextSkill>,
61 pub state: CommandContextState,
62 pub created_at_ms: u64,
63 pub expires_at_ms: u64,
64 pub version: u64,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct StoredCommandContext {
71 #[serde(default)]
72 pub execution_scope: Option<ResourceScope>,
73 pub resource: CommandContextResource,
74 pub owner: PrincipalRef,
75 pub executors: Vec<PrincipalRef>,
76 pub env_refs: BTreeMap<String, SecretRef>,
77 pub request_hash: String,
78 #[serde(default)]
79 pub caller_credential_ref: Option<SecretRef>,
80}
81
82#[derive(Debug, Clone)]
84pub struct CommandContextAgent {
85 pub owner_user_id: String,
86 pub id: String,
87 pub revision: u64,
88 pub runtime_ids: Vec<String>,
89 pub skills: Vec<CommandContextBinding>,
90}
91#[derive(Debug, Clone)]
92pub struct CommandContextBinding {
93 pub name: String,
94 pub version: String,
95 pub env_refs: BTreeMap<String, SecretRef>,
96}
97#[async_trait]
98pub trait CommandContextAgentSource: Send + Sync {
99 async fn caller(&self, _authorization: &str) -> Result<CommandContextCaller> {
101 anyhow::bail!("caller credential validation is not configured")
102 }
103 fn requires_caller_credential(&self) -> bool {
104 false
105 }
106
107 async fn agent(&self, agent_id: &str, authorization: &str) -> Result<CommandContextAgent>;
108}
109
110#[async_trait]
112pub trait CommandContextRepository: Send + Sync {
113 async fn bind_command_context_run(
114 &self,
115 scope: &ResourceScope,
116 id: &str,
117 run_id: &str,
118 ) -> Result<StoredCommandContext>;
119
120 async fn expired_command_contexts(
122 &self,
123 now: u64,
124 after: Option<&CommandContextSweepCursor>,
125 limit: usize,
126 ) -> Result<Vec<(ResourceScope, StoredCommandContext)>>;
127 async fn acknowledge_context_command(
129 &self,
130 scope: &ResourceScope,
131 id: &str,
132 command_id: &str,
133 ) -> Result<()>;
134 async fn compact_command_context(&self, scope: &ResourceScope, id: &str) -> Result<()>;
136
137 async fn context_commands(
138 &self,
139 scope: &ResourceScope,
140 id: &str,
141 after: &str,
142 ) -> Result<Vec<(String, String)>>;
143 async fn store_command_context(
144 &self,
145 scope: &ResourceScope,
146 value: StoredCommandContext,
147 ) -> Result<StoredCommandContext>;
148 async fn command_context(
149 &self,
150 scope: &ResourceScope,
151 id: &str,
152 ) -> Result<Option<StoredCommandContext>>;
153 async fn close_command_context(
154 &self,
155 scope: &ResourceScope,
156 id: &str,
157 expected_version: u64,
158 ) -> Result<StoredCommandContext>;
159}
160
161#[derive(Debug, Clone)]
163pub struct CommandContextSweepCursor {
164 pub expires_at_ms: u64,
165 pub scope: ResourceScope,
166 pub id: String,
167}
168
169#[derive(Debug)]
171pub struct CommandContextCaller {
172 pub user_id: String,
173 pub process_user_id: String,
174 pub sso_token: Option<agent_secret_contract::SecretValue>,
175 pub expires_at_ms: u64,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180#[serde(deny_unknown_fields, rename_all = "camelCase")]
181pub struct CommandContextRunFence {
182 pub run_id: String,
183 pub worker_id: String,
184 pub fencing_token: u64,
185}
186#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(deny_unknown_fields, rename_all = "camelCase")]
188pub struct BindCommandContextRunRequest {
189 pub run_id: String,
190}
191#[async_trait]
192pub trait CommandContextRunSource: Send + Sync {
193 async fn validate_run(
194 &self,
195 scope: &ResourceScope,
196 context: &CommandContextResource,
197 fence: &CommandContextRunFence,
198 authorization: &str,
199 ) -> Result<()>;
200}
201
202#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204pub enum CommandContextSkillSource {
205 #[default]
206 Agent,
207 Temporary,
208 Project,
209}
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211#[serde(deny_unknown_fields, rename_all = "camelCase")]
212pub struct TemporaryCommandContextSkill {
213 pub name: String,
214 pub version: String,
215}
216#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(deny_unknown_fields, rename_all = "camelCase")]
218pub struct ProjectCommandContextSkill {
219 pub name: String,
221 pub path: String,
223}