Skip to main content

agent_workspace_contract/environment/
commands.rs

1use serde::{Deserialize, Serialize};
2
3use crate::CommandSpec;
4
5pub const COMPUTER_COMMANDS_PATH: &str = "/internal/v1/workspace/computers/{computerId}/commands";
6pub const COMPUTER_COMMAND_PATH: &str =
7    "/internal/v1/workspace/computers/{computerId}/commands/{commandId}";
8pub const COMPUTER_COMMAND_OUTPUT_PATH: &str =
9    "/internal/v1/workspace/computers/{computerId}/commands/{commandId}/output";
10pub const COMPUTER_COMMAND_CANCEL_PATH: &str =
11    "/internal/v1/workspace/computers/{computerId}/commands/{commandId}/cancel";
12pub const COMPUTER_OWNERS_PATH: &str = "/internal/v1/workspace/computers/{computerId}/owners";
13pub const SPACE_OWNERS_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/owners";
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum ComputerCommandState {
18    Queued,
19    Running,
20    Succeeded,
21    Failed,
22    Canceling,
23    Canceled,
24}
25
26pub fn valid_computer_command_transition(
27    from: ComputerCommandState,
28    to: ComputerCommandState,
29) -> bool {
30    matches!(
31        (from, to),
32        (ComputerCommandState::Queued, ComputerCommandState::Running)
33            | (ComputerCommandState::Queued, ComputerCommandState::Canceled)
34            | (
35                ComputerCommandState::Running,
36                ComputerCommandState::Succeeded
37            )
38            | (ComputerCommandState::Running, ComputerCommandState::Failed)
39            | (
40                ComputerCommandState::Running,
41                ComputerCommandState::Canceling
42            )
43            | (
44                ComputerCommandState::Running,
45                ComputerCommandState::Canceled
46            )
47            | (
48                ComputerCommandState::Canceling,
49                ComputerCommandState::Canceled
50            )
51            | (
52                ComputerCommandState::Canceling,
53                ComputerCommandState::Failed
54            )
55    )
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(deny_unknown_fields, rename_all = "camelCase")]
60pub struct CreateComputerCommandRequest {
61    pub space_id: String,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub id: Option<String>,
64    pub command: CommandSpec,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(deny_unknown_fields, rename_all = "camelCase")]
69pub struct ComputerCommandResource {
70    pub id: String,
71    pub computer_id: String,
72    pub space_id: String,
73    pub spec: CommandSpec,
74    pub state: ComputerCommandState,
75    pub output_end_cursor: u64,
76    pub output_truncated: bool,
77    pub version: u64,
78    pub created_at_ms: u64,
79    pub updated_at_ms: u64,
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub exit_code: Option<i32>,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub error_code: Option<String>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct ComputerCommandOutputPage {
89    pub chunks: Vec<ComputerCommandOutputChunk>,
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub next_cursor: Option<u64>,
92    pub truncated: bool,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(deny_unknown_fields, rename_all = "camelCase")]
97pub struct ComputerCommandOutputQuery {
98    #[serde(default)]
99    pub after: u64,
100    #[serde(default = "default_command_output_page_limit")]
101    pub limit: usize,
102}
103
104fn default_command_output_page_limit() -> usize {
105    64
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct ComputerCommandOutputChunk {
111    pub stream: ComputerCommandOutputStream,
112    pub content: String,
113    pub cursor: u64,
114}
115
116/// Durable, scope-qualified command creation input owned by the Workspace
117/// command repository. Only hashes of caller idempotency material cross the
118/// persistence boundary.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct StoreComputerCommandRequest {
121    pub scope: crate::ResourceScope,
122    pub idempotency_key_hash: String,
123    pub request_hash: String,
124    pub command: ComputerCommandResource,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub enum StoreComputerCommandOutcome {
129    Created(ComputerCommandResource),
130    Replayed(ComputerCommandResource),
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct ComputerCommandTransition {
135    pub scope: crate::ResourceScope,
136    pub id: String,
137    pub expected_version: u64,
138    pub state: ComputerCommandState,
139    pub exit_code: Option<i32>,
140    pub error_code: Option<String>,
141    pub updated_at_ms: u64,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct CompleteComputerCommandRequest {
146    pub transition: ComputerCommandTransition,
147    pub chunks: Vec<ComputerCommandOutputChunk>,
148    pub output_truncated: bool,
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum ComputerCommandOutputStream {
154    Stdout,
155    Stderr,
156}
157
158/// Synchronous provider command result. The control plane may persist chunks.
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct ProviderCommandOutput {
161    pub exit_code: Option<i32>,
162    pub stdout: String,
163    pub stderr: String,
164    pub truncated: bool,
165}