use super::*;
use agent_secret_contract::SecretRef;
pub const COMMAND_CONTEXTS_PATH: &str = "/internal/v1/workspace/command-contexts";
pub const COMMAND_CONTEXT_PATH: &str = "/internal/v1/workspace/command-contexts/{contextId}";
pub const COMMAND_CONTEXT_CLOSE_PATH: &str =
"/internal/v1/workspace/command-contexts/{contextId}/close";
pub const COMMAND_CONTEXT_BIND_RUN_PATH: &str =
"/internal/v1/workspace/command-contexts/{contextId}/bind-run";
pub const COMMAND_CONTEXT_MAX_TTL_MS: u64 = 24 * 60 * 60 * 1000;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct CreateCommandContextRequest {
pub agent_id: String,
pub agent_revision: u64,
pub computer_id: String,
pub space_id: String,
pub skills: Vec<String>,
#[serde(default)]
pub temporary_skills: Vec<TemporaryCommandContextSkill>,
#[serde(default)]
pub project_skills: Vec<ProjectCommandContextSkill>,
pub ttl_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommandContextState {
Ready,
Closed,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandContextSkill {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub clis: Vec<InstalledSpaceCli>,
#[serde(default)]
pub source: CommandContextSkillSource,
pub instructions: String,
pub instructions_digest: String,
pub name: String,
pub version: String,
pub install_path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandContextResource {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub execution_run_id: Option<String>,
pub id: String,
pub agent_id: String,
pub agent_revision: u64,
pub computer_id: String,
pub space_id: String,
pub skills: Vec<CommandContextSkill>,
pub state: CommandContextState,
pub created_at_ms: u64,
pub expires_at_ms: u64,
pub version: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StoredCommandContext {
#[serde(default)]
pub execution_scope: Option<ResourceScope>,
pub resource: CommandContextResource,
pub owner: PrincipalRef,
pub executors: Vec<PrincipalRef>,
pub env_refs: BTreeMap<String, SecretRef>,
pub request_hash: String,
#[serde(default)]
pub caller_credential_ref: Option<SecretRef>,
}
#[derive(Debug, Clone)]
pub struct CommandContextAgent {
pub owner_user_id: String,
pub id: String,
pub revision: u64,
pub runtime_ids: Vec<String>,
pub skills: Vec<CommandContextBinding>,
}
#[derive(Debug, Clone)]
pub struct CommandContextBinding {
pub name: String,
pub version: String,
pub env_refs: BTreeMap<String, SecretRef>,
}
#[async_trait]
pub trait CommandContextAgentSource: Send + Sync {
async fn caller(&self, _authorization: &str) -> Result<CommandContextCaller> {
anyhow::bail!("caller credential validation is not configured")
}
fn requires_caller_credential(&self) -> bool {
false
}
async fn agent(&self, agent_id: &str, authorization: &str) -> Result<CommandContextAgent>;
}
#[async_trait]
pub trait CommandContextRepository: Send + Sync {
async fn bind_command_context_run(
&self,
scope: &ResourceScope,
id: &str,
run_id: &str,
) -> Result<StoredCommandContext>;
async fn expired_command_contexts(
&self,
now: u64,
after: Option<&CommandContextSweepCursor>,
limit: usize,
) -> Result<Vec<(ResourceScope, StoredCommandContext)>>;
async fn acknowledge_context_command(
&self,
scope: &ResourceScope,
id: &str,
command_id: &str,
) -> Result<()>;
async fn compact_command_context(&self, scope: &ResourceScope, id: &str) -> Result<()>;
async fn context_commands(
&self,
scope: &ResourceScope,
id: &str,
after: &str,
) -> Result<Vec<(String, String)>>;
async fn store_command_context(
&self,
scope: &ResourceScope,
value: StoredCommandContext,
) -> Result<StoredCommandContext>;
async fn command_context(
&self,
scope: &ResourceScope,
id: &str,
) -> Result<Option<StoredCommandContext>>;
async fn close_command_context(
&self,
scope: &ResourceScope,
id: &str,
expected_version: u64,
) -> Result<StoredCommandContext>;
}
#[derive(Debug, Clone)]
pub struct CommandContextSweepCursor {
pub expires_at_ms: u64,
pub scope: ResourceScope,
pub id: String,
}
#[derive(Debug)]
pub struct CommandContextCaller {
pub user_id: String,
pub process_user_id: String,
pub sso_token: Option<agent_secret_contract::SecretValue>,
pub expires_at_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct CommandContextRunFence {
pub run_id: String,
pub worker_id: String,
pub fencing_token: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct BindCommandContextRunRequest {
pub run_id: String,
}
#[async_trait]
pub trait CommandContextRunSource: Send + Sync {
async fn validate_run(
&self,
scope: &ResourceScope,
context: &CommandContextResource,
fence: &CommandContextRunFence,
authorization: &str,
) -> Result<()>;
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommandContextSkillSource {
#[default]
Agent,
Temporary,
Project,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct TemporaryCommandContextSkill {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProjectCommandContextSkill {
pub name: String,
pub path: String,
}