Skip to main content

agent_workspace_contract/
environment.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt::Debug;
6use std::sync::Arc;
7
8mod base64_bytes {
9    use base64::{Engine as _, engine::general_purpose::STANDARD};
10    use serde::{Deserialize, Deserializer, Serializer};
11
12    pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
13    where
14        S: Serializer,
15    {
16        serializer.serialize_str(&STANDARD.encode(value))
17    }
18
19    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
20    where
21        D: Deserializer<'de>,
22    {
23        let value = String::deserialize(deserializer)?;
24        STANDARD.decode(value).map_err(serde::de::Error::custom)
25    }
26}
27
28mod catalog;
29pub use catalog::*;
30
31mod ports;
32pub use ports::*;
33
34mod owners;
35pub use owners::*;
36
37mod commands;
38pub use commands::*;
39
40mod resources;
41pub use resources::*;
42
43mod skills;
44pub use skills::*;
45
46pub const COMPUTERS_PATH: &str = "/internal/v1/workspace/computers";
47pub const COMPUTER_PATH: &str = "/internal/v1/workspace/computers/{computerId}";
48pub const SPACES_PATH: &str = "/internal/v1/workspace/spaces";
49pub const SPACE_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}";
50pub const SPACE_GRANTS_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/grants";
51pub const SPACE_GRANT_REVOKE_PATH: &str =
52    "/internal/v1/workspace/spaces/{spaceId}/grants/{grantId}/revoke";
53pub const SPACE_SESSIONS_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/sessions";
54pub const SPACE_SESSION_HEARTBEAT_PATH: &str =
55    "/internal/v1/workspace/space-sessions/{sessionId}/heartbeat";
56pub const SPACE_SESSION_CLOSE_PATH: &str =
57    "/internal/v1/workspace/space-sessions/{sessionId}/close";
58pub const SPACE_TICKETS_PATH: &str = "/internal/v1/workspace/space-access-tickets";
59pub const SPACE_LEASES_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/leases";
60pub const SPACE_LEASE_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/leases/{leaseId}";
61pub const SPACE_FILES_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/files/{*filePath}";
62pub const SPACE_FILES_READ_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/files/read";
63pub const SPACE_FILES_LIST_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/files/list";
64pub const SPACE_CHANGE_SETS_PATH: &str = "/internal/v1/workspace/spaces/{spaceId}/change-sets";
65pub const SPACE_CHANGE_SET_APPLY_PATH: &str =
66    "/internal/v1/workspace/spaces/{spaceId}/change-sets/{changeSetId}/apply";
67pub const SANDBOXES_PATH: &str = "/internal/v1/workspace/sandboxes";
68pub const SANDBOX_PATH: &str = "/internal/v1/workspace/sandboxes/{sandboxId}";
69pub const SANDBOX_STOP_PATH: &str = "/internal/v1/workspace/sandboxes/{sandboxId}/stop";
70pub const SNAPSHOTS_PATH: &str = "/internal/v1/workspace/snapshots";
71pub const SNAPSHOT_PATH: &str = "/internal/v1/workspace/snapshots/{snapshotId}";
72pub const BUILD_BUILTIN_SNAPSHOTS_PATH: &str = "/internal/v1/workspace/snapshots:build-builtin";
73pub const CATALOG_SNAPSHOTS_PATH: &str = "/internal/v1/workspace/snapshots:catalog";
74pub const TEMPLATES_PATH: &str = "/internal/v1/workspace/templates";
75pub const TEMPLATE_VERSIONS_PATH: &str = "/internal/v1/workspace/templates/{templateId}/versions";
76pub const TEMPLATE_VERSION_PATH: &str =
77    "/internal/v1/workspace/templates/{templateId}/versions/{version}";
78pub const TEMPLATE_VERSION_PUBLISH_PATH: &str =
79    "/internal/v1/workspace/templates/{templateId}/versions/{version}/publish";
80pub const TEMPLATE_VERSION_DEPRECATE_PATH: &str =
81    "/internal/v1/workspace/templates/{templateId}/versions/{version}/deprecate";
82pub const DELEGATIONS_PATH: &str = "/internal/v1/workspace/delegations";
83pub const DELEGATION_REVOKE_PATH: &str = "/internal/v1/workspace/delegations/{delegationId}/revoke";
84pub const ENVIRONMENT_MAX_FILE_BYTES: usize = 8 * 1024 * 1024;
85pub const ENVIRONMENT_MAX_PATH_BYTES: usize = 4096;
86
87/// Logical directory for a Space on its parent Computer filesystem.
88pub fn space_directory_path(space_id: &str) -> String {
89    format!("spaces/{space_id}")
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
94pub enum EnvironmentErrorCode {
95    NotFound,
96    Forbidden,
97    Conflict,
98    InvalidState,
99    CapabilityUnsupported,
100    TicketExpired,
101    LeaseRequired,
102    StaleFencingToken,
103    RevisionConflict,
104}
105
106#[derive(Debug, Clone)]
107pub struct EnvironmentDomainError {
108    pub code: EnvironmentErrorCode,
109    pub message: String,
110}
111
112impl EnvironmentDomainError {
113    pub fn new(code: EnvironmentErrorCode, message: impl Into<String>) -> Self {
114        Self {
115            code,
116            message: message.into(),
117        }
118    }
119}
120
121impl std::fmt::Display for EnvironmentDomainError {
122    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        formatter.write_str(&self.message)
124    }
125}
126
127impl std::error::Error for EnvironmentDomainError {}
128
129#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
130#[serde(rename_all = "snake_case")]
131pub enum PrincipalKind {
132    User,
133    Agent,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
137#[serde(deny_unknown_fields, rename_all = "camelCase")]
138pub struct PrincipalRef {
139    pub kind: PrincipalKind,
140    pub id: String,
141}
142
143impl PrincipalRef {
144    pub fn user(id: impl Into<String>) -> Self {
145        Self {
146            kind: PrincipalKind::User,
147            id: id.into(),
148        }
149    }
150
151    pub fn agent(id: impl Into<String>) -> Self {
152        Self {
153            kind: PrincipalKind::Agent,
154            id: id.into(),
155        }
156    }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(deny_unknown_fields, rename_all = "camelCase")]
161pub struct ResourceScope {
162    pub tenant_id: String,
163    pub project_id: String,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(deny_unknown_fields, rename_all = "camelCase")]
168pub struct ProviderResourceRef {
169    pub provider: String,
170    pub external_id: String,
171}
172
173#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(deny_unknown_fields, rename_all = "camelCase")]
175pub struct ProviderCapabilities {
176    #[serde(default)]
177    pub capabilities: BTreeSet<String>,
178}
179
180impl ProviderCapabilities {
181    pub fn supports(&self, capability: &str) -> bool {
182        self.capabilities.contains(capability)
183    }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(rename_all = "snake_case")]
188pub enum ComputerLifecycle {
189    Ready,
190    Deleted,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194#[serde(deny_unknown_fields, rename_all = "camelCase")]
195pub struct ComputerRecord {
196    pub id: String,
197    pub scope: ResourceScope,
198    pub provider_ref: ProviderResourceRef,
199    pub lifecycle: ComputerLifecycle,
200    pub capabilities: ProviderCapabilities,
201    pub owners: Vec<OwnerRef>,
202    pub version: u64,
203    pub created_at_ms: u64,
204    pub updated_at_ms: u64,
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
208#[serde(deny_unknown_fields, rename_all = "camelCase")]
209pub struct ComputerBinding {
210    pub computer_id: String,
211    pub principal: PrincipalRef,
212    pub role: String,
213    pub active: bool,
214    pub bound_at_ms: u64,
215    #[serde(default, skip_serializing_if = "Option::is_none")]
216    pub released_at_ms: Option<u64>,
217    pub version: u64,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
221#[serde(rename_all = "snake_case")]
222pub enum SpacePersistence {
223    Persistent,
224    CopyOnWrite,
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
228#[serde(rename_all = "snake_case")]
229pub enum SpaceLifecycle {
230    Ready,
231    Deleted,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(deny_unknown_fields, rename_all = "camelCase")]
236pub struct SpaceRecord {
237    pub id: String,
238    pub computer_id: String,
239    pub scope: ResourceScope,
240    pub persistence: SpacePersistence,
241    pub lifecycle: SpaceLifecycle,
242    pub capabilities: ProviderCapabilities,
243    pub owners: Vec<OwnerRef>,
244    #[serde(default, skip_serializing_if = "Option::is_none")]
245    pub source_snapshot_id: Option<String>,
246    pub revision: u64,
247    pub version: u64,
248    pub created_at_ms: u64,
249    pub updated_at_ms: u64,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253#[serde(rename_all = "snake_case")]
254pub enum SandboxPlacement {
255    Computer(String),
256    ProviderPool(String),
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
260#[serde(rename_all = "snake_case")]
261pub enum SandboxLifecycle {
262    Ready,
263    Stopped,
264    Expired,
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(deny_unknown_fields, rename_all = "camelCase")]
269pub struct SandboxMount {
270    pub alias: String,
271    pub space_id: String,
272    pub mode: SpaceAccessMode,
273    #[serde(default, skip_serializing_if = "Option::is_none")]
274    pub path_prefix: Option<String>,
275    pub access_ticket_token: String,
276}
277
278/// Persisted mount metadata. The bearer token from [`SandboxMount`] is
279/// deliberately replaced by its non-secret ticket id before persistence.
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
281#[serde(deny_unknown_fields, rename_all = "camelCase")]
282pub struct SandboxMountRecord {
283    pub alias: String,
284    pub space_id: String,
285    pub mode: SpaceAccessMode,
286    #[serde(default, skip_serializing_if = "Option::is_none")]
287    pub path_prefix: Option<String>,
288    pub access_ticket_id: String,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
292#[serde(deny_unknown_fields, rename_all = "camelCase")]
293pub struct SandboxRecord {
294    pub id: String,
295    pub scope: ResourceScope,
296    pub provider_ref: ProviderResourceRef,
297    pub placement: SandboxPlacement,
298    pub lifecycle: SandboxLifecycle,
299    pub owners: Vec<OwnerRef>,
300    #[serde(default, skip_serializing_if = "Option::is_none")]
301    pub access_delegation_id: Option<String>,
302    pub capabilities: ProviderCapabilities,
303    #[serde(default)]
304    pub mounts: Vec<SandboxMountRecord>,
305    pub expires_at_ms: u64,
306    pub version: u64,
307    pub created_at_ms: u64,
308    pub updated_at_ms: u64,
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
312#[serde(rename_all = "snake_case")]
313pub enum SnapshotLifecycle {
314    Building,
315    Ready,
316    Failed,
317    Deleted,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
321#[serde(tag = "kind", rename_all = "snake_case")]
322pub enum SnapshotSourceRecord {
323    Build { spec: SnapshotBuildSpec },
324}
325
326#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
327#[serde(deny_unknown_fields, rename_all = "camelCase")]
328pub struct SnapshotRecord {
329    pub id: String,
330    pub scope: ResourceScope,
331    pub provider: String,
332    pub provider_ref: ProviderResourceRef,
333    pub source: SnapshotSourceRecord,
334    pub visibility: SnapshotVisibility,
335    pub owners: Vec<OwnerRef>,
336    #[serde(default)]
337    pub builtin: bool,
338    pub created_by: PrincipalRef,
339    pub lifecycle: SnapshotLifecycle,
340    #[serde(default, skip_serializing_if = "Option::is_none")]
341    pub digest: Option<String>,
342    #[serde(default, skip_serializing_if = "Option::is_none")]
343    pub size_bytes: Option<u64>,
344    #[serde(default, skip_serializing_if = "Option::is_none")]
345    pub error: Option<String>,
346    #[serde(default, skip_serializing_if = "String::is_empty")]
347    pub build_key: String,
348    pub created_at_ms: u64,
349    pub updated_at_ms: u64,
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
353#[serde(rename_all = "snake_case")]
354pub enum TemplateTarget {
355    Computer,
356    Sandbox,
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
360#[serde(rename_all = "snake_case")]
361pub enum TemplateVersionState {
362    Draft,
363    Published,
364    Deprecated,
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
368#[serde(deny_unknown_fields, rename_all = "camelCase")]
369pub struct TemplateVersionRecord {
370    pub template_id: String,
371    pub version: String,
372    pub scope: ResourceScope,
373    pub provider: String,
374    pub created_by: PrincipalRef,
375    #[serde(default, skip_serializing_if = "Option::is_none")]
376    pub provider_template_id: Option<String>,
377    pub target: TemplateTarget,
378    #[serde(default, skip_serializing_if = "Option::is_none")]
379    pub base_snapshot_id: Option<String>,
380    #[serde(default)]
381    pub resource_profile: BTreeMap<String, String>,
382    #[serde(default)]
383    pub parameter_schema: BTreeMap<String, String>,
384    pub state: TemplateVersionState,
385    pub created_at_ms: u64,
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
389#[serde(rename_all = "snake_case")]
390pub enum SpaceCapability {
391    Read,
392    Write,
393    Share,
394    Manage,
395    Mount,
396    Snapshot,
397    Restore,
398    Delete,
399}
400
401#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
402#[serde(deny_unknown_fields, rename_all = "camelCase")]
403pub struct SpaceGrant {
404    pub id: String,
405    pub scope: ResourceScope,
406    pub space_id: String,
407    pub principal: PrincipalRef,
408    pub capabilities: BTreeSet<SpaceCapability>,
409    #[serde(default)]
410    pub path_prefixes: Vec<String>,
411    #[serde(default, skip_serializing_if = "Option::is_none")]
412    pub expires_at_ms: Option<u64>,
413    pub active: bool,
414    pub version: u64,
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
418#[serde(rename_all = "snake_case")]
419pub enum SpaceAccessMode {
420    ReadOnly,
421    DirectWrite,
422    CopyOnWrite,
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
426#[serde(rename_all = "snake_case")]
427pub enum SpaceSessionLifecycle {
428    Opening,
429    Active,
430    Closing,
431    Closed,
432    Expired,
433    Revoked,
434}
435
436#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
437#[serde(deny_unknown_fields, rename_all = "camelCase")]
438pub struct SpaceCollaborationSession {
439    pub id: String,
440    pub scope: ResourceScope,
441    pub space_id: String,
442    pub principal: PrincipalRef,
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub delegation_id: Option<String>,
445    pub requested_mode: SpaceAccessMode,
446    pub effective_mode: SpaceAccessMode,
447    pub lifecycle: SpaceSessionLifecycle,
448    pub last_heartbeat_at_ms: u64,
449    pub expires_at_ms: u64,
450    pub version: u64,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
454#[serde(deny_unknown_fields, rename_all = "camelCase")]
455pub struct SpaceAccessTicketRecord {
456    pub id: String,
457    pub token_hash: String,
458    pub scope: ResourceScope,
459    pub space_id: String,
460    pub session_id: String,
461    pub mode: SpaceAccessMode,
462    /// Capability-specific path rules captured at issue time. Runtime
463    /// intersects these with current Grant/Delegation authority on every use.
464    #[serde(default)]
465    pub capability_paths: BTreeMap<SpaceCapability, Vec<String>>,
466    #[serde(default, skip_serializing_if = "Option::is_none")]
467    pub fencing_token: Option<u64>,
468    pub expires_at_ms: u64,
469    pub revoked: bool,
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
473#[serde(deny_unknown_fields, rename_all = "camelCase")]
474pub struct IssuedSpaceAccessTicket {
475    pub ticket: SpaceAccessTicketRecord,
476    /// Returned exactly once. Repositories and logs must never persist this
477    /// bearer value.
478    pub token: String,
479}
480
481#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
482#[serde(deny_unknown_fields, rename_all = "camelCase")]
483pub struct SpaceWriteLease {
484    pub id: String,
485    pub scope: ResourceScope,
486    pub space_id: String,
487    pub session_id: String,
488    pub fencing_token: u64,
489    pub version: u64,
490    pub acquired_at_ms: u64,
491    pub expires_at_ms: u64,
492    pub active: bool,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
496#[serde(deny_unknown_fields, rename_all = "camelCase")]
497pub struct DelegationRecord {
498    pub id: String,
499    pub scope: ResourceScope,
500    pub grantor_user_id: String,
501    pub delegate_agent_id: String,
502    #[serde(default)]
503    pub computer_ids: BTreeSet<String>,
504    #[serde(default)]
505    pub space_ids: BTreeSet<String>,
506    pub capabilities: BTreeSet<SpaceCapability>,
507    #[serde(default)]
508    pub path_prefixes: Vec<String>,
509    pub purpose: String,
510    pub expires_at_ms: u64,
511    #[serde(default, skip_serializing_if = "Option::is_none")]
512    pub revoked_at_ms: Option<u64>,
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
516#[serde(rename_all = "snake_case")]
517pub enum ChangeSetLifecycle {
518    Ready,
519    Applied,
520}
521
522#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
523#[serde(deny_unknown_fields, rename_all = "camelCase")]
524pub struct SpaceChangeSetRecord {
525    pub id: String,
526    pub scope: ResourceScope,
527    pub source_space_id: String,
528    pub target_space_id: String,
529    pub base_revision: u64,
530    pub created_by_session_id: String,
531    #[serde(default, skip_serializing_if = "Option::is_none")]
532    pub provider_change_ref: Option<String>,
533    pub lifecycle: ChangeSetLifecycle,
534    pub created_at_ms: u64,
535}
536
537#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
538#[serde(deny_unknown_fields, rename_all = "camelCase")]
539pub struct CreateComputerRequest {
540    pub provider: String,
541    #[serde(default, skip_serializing_if = "Option::is_none")]
542    pub snapshot_id: Option<String>,
543    #[serde(default, skip_serializing_if = "Option::is_none")]
544    pub template_id: Option<String>,
545    #[serde(default, skip_serializing_if = "Option::is_none")]
546    pub template_version: Option<String>,
547    #[serde(default)]
548    pub profile: BTreeMap<String, String>,
549    /// Additional owners beyond the authenticated creator.
550    #[serde(default)]
551    pub owners: Vec<OwnerRef>,
552}
553
554#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
555#[serde(deny_unknown_fields, rename_all = "camelCase")]
556pub struct CreateSpaceRequest {
557    pub computer_id: String,
558    pub persistence: SpacePersistence,
559    #[serde(default, skip_serializing_if = "Option::is_none")]
560    pub source_snapshot_id: Option<String>,
561    /// Must be a subset of the parent Computer owners when non-empty.
562    #[serde(default)]
563    pub owners: Vec<OwnerRef>,
564}
565
566#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
567#[serde(deny_unknown_fields, rename_all = "camelCase")]
568pub struct CreateSandboxRequest {
569    pub provider: String,
570    pub placement: SandboxPlacement,
571    #[serde(default, skip_serializing_if = "Option::is_none")]
572    pub delegation_id: Option<String>,
573    pub ttl_ms: u64,
574    #[serde(default)]
575    pub mounts: Vec<SandboxMount>,
576    #[serde(default, skip_serializing_if = "Option::is_none")]
577    pub snapshot_id: Option<String>,
578    #[serde(default, skip_serializing_if = "Option::is_none")]
579    pub template_id: Option<String>,
580    #[serde(default, skip_serializing_if = "Option::is_none")]
581    pub template_version: Option<String>,
582    #[serde(default)]
583    pub profile: BTreeMap<String, String>,
584}
585
586/// Provider-facing request after product identifiers and access tickets have
587/// been resolved. These DTOs cannot be supplied by HTTP callers.
588#[derive(Debug, Clone, PartialEq, Eq)]
589pub struct ProviderComputerRequest {
590    pub template_external_id: Option<String>,
591    pub profile: BTreeMap<String, String>,
592}
593
594#[derive(Debug, Clone, PartialEq, Eq)]
595pub struct ProviderSpaceRequest {
596    pub persistence: SpacePersistence,
597    pub source_snapshot_external_id: Option<String>,
598}
599
600#[derive(Debug, Clone, PartialEq, Eq)]
601pub struct ProviderSandboxMount {
602    pub alias: String,
603    pub space_directory: String,
604    pub mode: SpaceAccessMode,
605    pub path_prefix: Option<String>,
606}
607
608#[derive(Debug, Clone, PartialEq, Eq)]
609pub enum ProviderSandboxPlacement {
610    Computer(String),
611    ProviderPool(String),
612}
613
614#[derive(Debug, Clone, PartialEq, Eq)]
615pub struct ProviderSandboxRequest {
616    pub placement: ProviderSandboxPlacement,
617    pub ttl_ms: u64,
618    pub mounts: Vec<ProviderSandboxMount>,
619    pub template_external_id: Option<String>,
620    pub source_snapshot_external_id: Option<String>,
621    pub profile: BTreeMap<String, String>,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq)]
625pub enum ProviderSnapshotSource {
626    Computer(String),
627    Sandbox(String),
628    Space(String),
629}
630
631#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
632#[serde(deny_unknown_fields, rename_all = "camelCase")]
633pub struct ListEnvironmentResourcesQuery {
634    #[serde(default, skip_serializing_if = "Option::is_none")]
635    pub owner_kind: Option<OwnerKind>,
636    #[serde(default, skip_serializing_if = "Option::is_none")]
637    pub owner_id: Option<String>,
638    /// When set, resource owners must contain every listed owner (JSON array).
639    #[serde(default, skip_serializing_if = "Vec::is_empty")]
640    pub contains_all: Vec<OwnerRef>,
641}
642
643#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
644#[serde(deny_unknown_fields, rename_all = "camelCase")]
645pub struct RevokeSpaceGrantRequest {
646    pub expected_version: u64,
647}
648
649#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
650#[serde(deny_unknown_fields, rename_all = "camelCase")]
651pub struct CreateSpaceGrantRequest {
652    pub principal: PrincipalRef,
653    pub capabilities: BTreeSet<SpaceCapability>,
654    #[serde(default)]
655    pub path_prefixes: Vec<String>,
656    #[serde(default, skip_serializing_if = "Option::is_none")]
657    pub expires_at_ms: Option<u64>,
658}
659
660#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
661#[serde(deny_unknown_fields, rename_all = "camelCase")]
662pub struct OpenSpaceSessionRequest {
663    pub mode: SpaceAccessMode,
664    #[serde(default, skip_serializing_if = "Option::is_none")]
665    pub delegation_id: Option<String>,
666    pub ttl_ms: u64,
667}
668
669#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
670#[serde(deny_unknown_fields, rename_all = "camelCase")]
671pub struct IssueSpaceAccessTicketRequest {
672    pub session_id: String,
673    pub ttl_ms: u64,
674}
675
676#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
677#[serde(deny_unknown_fields, rename_all = "camelCase")]
678pub struct AcquireSpaceWriteLeaseRequest {
679    pub session_id: String,
680    pub ttl_ms: u64,
681}
682
683#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
684#[serde(deny_unknown_fields, rename_all = "camelCase")]
685pub struct ReleaseSpaceWriteLeaseRequest {
686    pub session_id: String,
687    pub fencing_token: u64,
688}
689
690#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
691#[serde(deny_unknown_fields, rename_all = "camelCase")]
692pub struct HeartbeatSpaceSessionRequest {
693    pub ttl_ms: u64,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
697#[serde(deny_unknown_fields, rename_all = "camelCase")]
698pub struct CreateDelegationRequest {
699    pub delegate_agent_id: String,
700    #[serde(default)]
701    pub computer_ids: BTreeSet<String>,
702    #[serde(default)]
703    pub space_ids: BTreeSet<String>,
704    pub capabilities: BTreeSet<SpaceCapability>,
705    #[serde(default)]
706    pub path_prefixes: Vec<String>,
707    pub purpose: String,
708    pub expires_at_ms: u64,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
712#[serde(deny_unknown_fields, rename_all = "camelCase")]
713pub struct CreateTemplateVersionRequest {
714    pub template_id: String,
715    pub version: String,
716    pub provider: String,
717    #[serde(default, skip_serializing_if = "Option::is_none")]
718    pub provider_template_id: Option<String>,
719    pub target: TemplateTarget,
720    #[serde(default, skip_serializing_if = "Option::is_none")]
721    pub base_snapshot_id: Option<String>,
722    #[serde(default)]
723    pub resource_profile: BTreeMap<String, String>,
724    #[serde(default)]
725    pub parameter_schema: BTreeMap<String, String>,
726}
727
728#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
729#[serde(deny_unknown_fields, rename_all = "camelCase")]
730pub struct CreateSpaceChangeSetRequest {
731    pub source_space_id: String,
732    pub target_space_id: String,
733    pub created_by_session_id: String,
734}
735
736#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
737#[serde(deny_unknown_fields, rename_all = "camelCase")]
738pub struct ApplySpaceChangeSetRequest {
739    pub access_ticket_token: String,
740    pub fencing_token: u64,
741    pub expected_revision: u64,
742}
743
744#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
745#[serde(deny_unknown_fields, rename_all = "camelCase")]
746pub struct WriteSpaceFileRequest {
747    pub access_ticket_token: String,
748    pub fencing_token: u64,
749    pub expected_revision: u64,
750    #[serde(with = "base64_bytes")]
751    pub content: Vec<u8>,
752}
753
754#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
755#[serde(deny_unknown_fields, rename_all = "camelCase")]
756pub struct ReadSpaceFileRequest {
757    pub access_ticket_token: String,
758    pub path: String,
759}
760
761#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
762#[serde(deny_unknown_fields, rename_all = "camelCase")]
763pub struct ListSpaceFilesRequest {
764    pub access_ticket_token: String,
765    #[serde(default)]
766    pub path: String,
767}
768
769#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
770#[serde(deny_unknown_fields, rename_all = "camelCase")]
771pub struct DeleteSpaceFileRequest {
772    pub access_ticket_token: String,
773    pub fencing_token: u64,
774    pub expected_revision: u64,
775}
776
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778#[serde(deny_unknown_fields, rename_all = "camelCase")]
779pub struct ProviderFileContent {
780    pub bytes: Vec<u8>,
781    #[serde(default, skip_serializing_if = "Option::is_none")]
782    pub provider_revision: Option<String>,
783}
784
785#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
786#[serde(deny_unknown_fields, rename_all = "camelCase")]
787pub struct ProviderFileEntry {
788    pub path: String,
789    pub is_dir: bool,
790    #[serde(default, skip_serializing_if = "Option::is_none")]
791    pub size_bytes: Option<u64>,
792}
793
794#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
795#[serde(deny_unknown_fields, rename_all = "camelCase")]
796pub struct ProviderWriteResult {
797    #[serde(default, skip_serializing_if = "Option::is_none")]
798    pub provider_revision: Option<String>,
799}