1use std::sync::Arc;
12
13use serde::{Deserialize, Serialize};
14
15use crate::fs::VirtualFileSystem;
16pub use agentos_vm_config::{VmGroupConfig, VmUserAccountConfig, VmUserConfig};
17
18#[derive(Default)]
23pub struct AgentOsConfig {
24 pub database: Option<agentos_vm_config::VmSqliteDescriptor>,
28 pub user: Option<VmUserConfig>,
30 pub software: Vec<SoftwareInput>,
32 pub packages: Vec<PackageRef>,
36 pub packages_mount_at: Option<String>,
39 pub loopback_exempt_ports: Vec<u16>,
41 pub allowed_node_builtins: Option<Vec<String>>,
43 pub root_filesystem: RootFilesystemConfig,
45 pub mounts: Vec<MountConfig>,
47 pub additional_instructions: Option<String>,
49 pub schedule_driver: Option<Arc<dyn ScheduleDriver>>,
51 pub bindings: Vec<Bindings>,
53 pub sidecar_js_bridge_callback: Option<SidecarJsBridgeCallback>,
55 pub permissions: Option<Permissions>,
57 pub limits: Option<AgentOsLimits>,
59 pub sidecar: Option<AgentOsSidecarConfig>,
61 pub sidecar_binary_path: Option<String>,
66}
67
68#[derive(Default)]
70pub struct AgentOsConfigBuilder {
71 config: AgentOsConfig,
72}
73
74impl AgentOsConfigBuilder {
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn database(mut self, database: agentos_vm_config::VmSqliteDescriptor) -> Self {
80 self.config.database = Some(database);
81 self
82 }
83
84 pub fn packages(mut self, packages: Vec<PackageRef>) -> Self {
85 self.config.packages = packages;
86 self
87 }
88
89 pub fn packages_mount_at(mut self, mount_at: impl Into<String>) -> Self {
90 self.config.packages_mount_at = Some(mount_at.into());
91 self
92 }
93
94 pub fn loopback_exempt_ports(mut self, ports: Vec<u16>) -> Self {
95 self.config.loopback_exempt_ports = ports;
96 self
97 }
98
99 pub fn allowed_node_builtins(mut self, builtins: Vec<String>) -> Self {
100 self.config.allowed_node_builtins = Some(builtins);
101 self
102 }
103
104 pub fn user(mut self, user: VmUserConfig) -> Self {
105 self.config.user = Some(user);
106 self
107 }
108
109 pub fn root_filesystem(mut self, root: RootFilesystemConfig) -> Self {
110 self.config.root_filesystem = root;
111 self
112 }
113
114 pub fn mounts(mut self, mounts: Vec<MountConfig>) -> Self {
115 self.config.mounts = mounts;
116 self
117 }
118
119 pub fn additional_instructions(mut self, instructions: impl Into<String>) -> Self {
120 self.config.additional_instructions = Some(instructions.into());
121 self
122 }
123
124 pub fn schedule_driver(mut self, driver: Arc<dyn ScheduleDriver>) -> Self {
125 self.config.schedule_driver = Some(driver);
126 self
127 }
128
129 pub fn bindings(mut self, bindings: Vec<Bindings>) -> Self {
130 self.config.bindings = bindings;
131 self
132 }
133
134 pub fn sidecar_js_bridge_callback(mut self, callback: SidecarJsBridgeCallback) -> Self {
135 self.config.sidecar_js_bridge_callback = Some(callback);
136 self
137 }
138
139 pub fn permissions(mut self, permissions: Permissions) -> Self {
140 self.config.permissions = Some(permissions);
141 self
142 }
143
144 pub fn limits(mut self, limits: AgentOsLimits) -> Self {
145 self.config.limits = Some(limits);
146 self
147 }
148
149 pub fn sidecar(mut self, sidecar: AgentOsSidecarConfig) -> Self {
150 self.config.sidecar = Some(sidecar);
151 self
152 }
153
154 pub fn sidecar_binary_path(mut self, path: impl Into<String>) -> Self {
155 self.config.sidecar_binary_path = Some(path.into());
156 self
157 }
158
159 pub fn build(self) -> AgentOsConfig {
160 self.config
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
167#[serde(rename_all = "kebab-case")]
168pub enum SoftwareKind {
169 #[default]
172 WasmCommands,
173 Agent,
175 Binding,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct SoftwareInput {
182 pub package: String,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub version: Option<String>,
185 #[serde(default)]
187 pub kind: SoftwareKind,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct PackageRef {
195 #[serde(rename = "packagePath")]
198 pub path: String,
199}
200
201pub type BindingCallback = Arc<
205 dyn Fn(
206 serde_json::Value,
207 ) -> futures::future::BoxFuture<'static, Result<serde_json::Value, String>>
208 + Send
209 + Sync,
210>;
211
212#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct SidecarJsBridgeCall {
215 pub call_id: String,
216 pub mount_id: String,
217 pub operation: String,
218 pub args: serde_json::Value,
219}
220
221pub type SidecarJsBridgeCallback = Arc<
227 dyn Fn(
228 SidecarJsBridgeCall,
229 )
230 -> futures::future::BoxFuture<'static, Result<Option<serde_json::Value>, String>>
231 + Send
232 + Sync,
233>;
234
235#[derive(Clone)]
237pub struct Binding {
238 pub name: String,
239 pub description: String,
240 pub input_schema: serde_json::Value,
242 pub timeout_ms: Option<u64>,
243 pub execute: BindingCallback,
245}
246
247#[derive(Clone)]
251pub struct Bindings {
252 pub name: String,
253 pub description: String,
254 pub bindings: Vec<Binding>,
255}
256
257#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
264pub struct AgentOsLimits {
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub resources: Option<ResourceLimits>,
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub http: Option<HttpLimits>,
269 #[serde(default, skip_serializing_if = "Option::is_none")]
270 pub bindings: Option<BindingLimits>,
271 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub plugins: Option<PluginLimits>,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub acp: Option<AcpLimits>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub sqlite: Option<SqliteLimits>,
277 #[serde(default, rename = "jsRuntime", skip_serializing_if = "Option::is_none")]
278 pub js_runtime: Option<JsRuntimeLimits>,
279 #[serde(default, skip_serializing_if = "Option::is_none")]
280 pub python: Option<PythonLimits>,
281 #[serde(default, skip_serializing_if = "Option::is_none")]
282 pub wasm: Option<WasmLimits>,
283 #[serde(default, skip_serializing_if = "Option::is_none")]
284 pub process: Option<ProcessLimits>,
285}
286
287#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
288pub struct ResourceLimits {
289 #[serde(default, rename = "cpuCount", skip_serializing_if = "Option::is_none")]
290 pub cpu_count: Option<u64>,
291 #[serde(
292 default,
293 rename = "maxProcesses",
294 skip_serializing_if = "Option::is_none"
295 )]
296 pub max_processes: Option<u64>,
297 #[serde(
298 default,
299 rename = "maxOpenFds",
300 skip_serializing_if = "Option::is_none"
301 )]
302 pub max_open_fds: Option<u64>,
303 #[serde(default, rename = "maxPipes", skip_serializing_if = "Option::is_none")]
304 pub max_pipes: Option<u64>,
305 #[serde(default, rename = "maxPtys", skip_serializing_if = "Option::is_none")]
306 pub max_ptys: Option<u64>,
307 #[serde(
308 default,
309 rename = "maxSockets",
310 skip_serializing_if = "Option::is_none"
311 )]
312 pub max_sockets: Option<u64>,
313 #[serde(
314 default,
315 rename = "maxConnections",
316 skip_serializing_if = "Option::is_none"
317 )]
318 pub max_connections: Option<u64>,
319 #[serde(
320 default,
321 rename = "maxSocketBufferedBytes",
322 skip_serializing_if = "Option::is_none"
323 )]
324 pub max_socket_buffered_bytes: Option<u64>,
325 #[serde(
326 default,
327 rename = "maxSocketDatagramQueueLen",
328 skip_serializing_if = "Option::is_none"
329 )]
330 pub max_socket_datagram_queue_len: Option<u64>,
331 #[serde(
332 default,
333 rename = "maxFilesystemBytes",
334 skip_serializing_if = "Option::is_none"
335 )]
336 pub max_filesystem_bytes: Option<u64>,
337 #[serde(
338 default,
339 rename = "maxInodeCount",
340 skip_serializing_if = "Option::is_none"
341 )]
342 pub max_inode_count: Option<u64>,
343 #[serde(
344 default,
345 rename = "maxBlockingReadMs",
346 skip_serializing_if = "Option::is_none"
347 )]
348 pub max_blocking_read_ms: Option<u64>,
349 #[serde(
350 default,
351 rename = "maxPreadBytes",
352 skip_serializing_if = "Option::is_none"
353 )]
354 pub max_pread_bytes: Option<u64>,
355 #[serde(
356 default,
357 rename = "maxFdWriteBytes",
358 skip_serializing_if = "Option::is_none"
359 )]
360 pub max_fd_write_bytes: Option<u64>,
361 #[serde(
362 default,
363 rename = "maxProcessArgvBytes",
364 skip_serializing_if = "Option::is_none"
365 )]
366 pub max_process_argv_bytes: Option<u64>,
367 #[serde(
368 default,
369 rename = "maxProcessEnvBytes",
370 skip_serializing_if = "Option::is_none"
371 )]
372 pub max_process_env_bytes: Option<u64>,
373 #[serde(
374 default,
375 rename = "maxReaddirEntries",
376 skip_serializing_if = "Option::is_none"
377 )]
378 pub max_readdir_entries: Option<u64>,
379 #[serde(
380 default,
381 rename = "maxWasmFuel",
382 skip_serializing_if = "Option::is_none"
383 )]
384 pub max_wasm_fuel: Option<u64>,
385 #[serde(
386 default,
387 rename = "maxWasmMemoryBytes",
388 skip_serializing_if = "Option::is_none"
389 )]
390 pub max_wasm_memory_bytes: Option<u64>,
391 #[serde(
392 default,
393 rename = "maxWasmStackBytes",
394 skip_serializing_if = "Option::is_none"
395 )]
396 pub max_wasm_stack_bytes: Option<u64>,
397}
398
399#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
400pub struct HttpLimits {
401 #[serde(
402 default,
403 rename = "maxFetchResponseBytes",
404 skip_serializing_if = "Option::is_none"
405 )]
406 pub max_fetch_response_bytes: Option<u64>,
407}
408
409#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
410pub struct BindingLimits {
411 #[serde(
412 default,
413 rename = "defaultBindingTimeoutMs",
414 skip_serializing_if = "Option::is_none"
415 )]
416 pub default_binding_timeout_ms: Option<u64>,
417 #[serde(
418 default,
419 rename = "maxBindingTimeoutMs",
420 skip_serializing_if = "Option::is_none"
421 )]
422 pub max_binding_timeout_ms: Option<u64>,
423 #[serde(
424 default,
425 rename = "maxRegisteredCollections",
426 skip_serializing_if = "Option::is_none"
427 )]
428 pub max_registered_collections: Option<u64>,
429 #[serde(
430 default,
431 rename = "maxRegisteredBindingsPerVm",
432 skip_serializing_if = "Option::is_none"
433 )]
434 pub max_registered_bindings_per_vm: Option<u64>,
435 #[serde(
436 default,
437 rename = "maxBindingsPerCollection",
438 skip_serializing_if = "Option::is_none"
439 )]
440 pub max_bindings_per_collection: Option<u64>,
441 #[serde(
442 default,
443 rename = "maxBindingSchemaBytes",
444 skip_serializing_if = "Option::is_none"
445 )]
446 pub max_binding_schema_bytes: Option<u64>,
447 #[serde(
448 default,
449 rename = "maxExamplesPerBinding",
450 skip_serializing_if = "Option::is_none"
451 )]
452 pub max_examples_per_binding: Option<u64>,
453 #[serde(
454 default,
455 rename = "maxBindingExampleInputBytes",
456 skip_serializing_if = "Option::is_none"
457 )]
458 pub max_binding_example_input_bytes: Option<u64>,
459}
460
461#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
462pub struct PluginLimits {
463 #[serde(
464 default,
465 rename = "maxPersistedManifestBytes",
466 skip_serializing_if = "Option::is_none"
467 )]
468 pub max_persisted_manifest_bytes: Option<u64>,
469 #[serde(
470 default,
471 rename = "maxPersistedManifestFileBytes",
472 skip_serializing_if = "Option::is_none"
473 )]
474 pub max_persisted_manifest_file_bytes: Option<u64>,
475}
476
477#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
478pub struct AcpLimits {
479 #[serde(
480 default,
481 rename = "maxReadLineBytes",
482 skip_serializing_if = "Option::is_none"
483 )]
484 pub max_read_line_bytes: Option<u64>,
485 #[serde(
486 default,
487 rename = "stdoutBufferByteLimit",
488 skip_serializing_if = "Option::is_none"
489 )]
490 pub stdout_buffer_byte_limit: Option<u64>,
491 #[serde(
492 default,
493 rename = "maxCompletedMessageBytes",
494 skip_serializing_if = "Option::is_none"
495 )]
496 pub max_completed_message_bytes: Option<u64>,
497 #[serde(
498 default,
499 rename = "maxTurnOutputBytes",
500 skip_serializing_if = "Option::is_none"
501 )]
502 pub max_turn_output_bytes: Option<u64>,
503 #[serde(
504 default,
505 rename = "maxPromptBytes",
506 skip_serializing_if = "Option::is_none"
507 )]
508 pub max_prompt_bytes: Option<u64>,
509 #[serde(
510 default,
511 rename = "maxPromptBlocks",
512 skip_serializing_if = "Option::is_none"
513 )]
514 pub max_prompt_blocks: Option<u64>,
515 #[serde(
516 default,
517 rename = "maxFallbackContinuationBytes",
518 skip_serializing_if = "Option::is_none"
519 )]
520 pub max_fallback_continuation_bytes: Option<u64>,
521 #[serde(
522 default,
523 rename = "maxSessionHistoryBytes",
524 skip_serializing_if = "Option::is_none"
525 )]
526 pub max_session_history_bytes: Option<u64>,
527 #[serde(
528 default,
529 rename = "maxSessionHistoryEvents",
530 skip_serializing_if = "Option::is_none"
531 )]
532 pub max_session_history_events: Option<u64>,
533 #[serde(
534 default,
535 rename = "maxHistoryPageEntries",
536 skip_serializing_if = "Option::is_none"
537 )]
538 pub max_history_page_entries: Option<u64>,
539 #[serde(
540 default,
541 rename = "maxSessionListEntries",
542 skip_serializing_if = "Option::is_none"
543 )]
544 pub max_session_list_entries: Option<u64>,
545 #[serde(
546 default,
547 rename = "maxSessionsPerVm",
548 skip_serializing_if = "Option::is_none"
549 )]
550 pub max_sessions_per_vm: Option<u64>,
551 #[serde(
552 default,
553 rename = "maxPromptsPerSession",
554 skip_serializing_if = "Option::is_none"
555 )]
556 pub max_prompts_per_session: Option<u64>,
557 #[serde(
558 default,
559 rename = "maxPromptsPerVm",
560 skip_serializing_if = "Option::is_none"
561 )]
562 pub max_prompts_per_vm: Option<u64>,
563 #[serde(
564 default,
565 rename = "maxPendingPermissionsPerSession",
566 skip_serializing_if = "Option::is_none"
567 )]
568 pub max_pending_permissions_per_session: Option<u64>,
569 #[serde(
570 default,
571 rename = "maxPendingPermissionsPerVm",
572 skip_serializing_if = "Option::is_none"
573 )]
574 pub max_pending_permissions_per_vm: Option<u64>,
575 #[serde(
576 default,
577 rename = "maxPermissionOutcomesPerSession",
578 skip_serializing_if = "Option::is_none"
579 )]
580 pub max_permission_outcomes_per_session: Option<u64>,
581 #[serde(
582 default,
583 rename = "maxPermissionOutcomesPerVm",
584 skip_serializing_if = "Option::is_none"
585 )]
586 pub max_permission_outcomes_per_vm: Option<u64>,
587}
588
589#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
590pub struct SqliteLimits {
591 #[serde(
592 default,
593 rename = "maxResultBytes",
594 skip_serializing_if = "Option::is_none"
595 )]
596 pub max_result_bytes: Option<u64>,
597 #[serde(
598 default,
599 rename = "maxInFlightRequests",
600 skip_serializing_if = "Option::is_none"
601 )]
602 pub max_in_flight_requests: Option<u64>,
603 #[serde(
604 default,
605 rename = "maxQueuedRequestBytes",
606 skip_serializing_if = "Option::is_none"
607 )]
608 pub max_queued_request_bytes: Option<u64>,
609}
610
611#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
612pub struct JsRuntimeLimits {
613 #[serde(
614 default,
615 rename = "v8HeapLimitMb",
616 skip_serializing_if = "Option::is_none"
617 )]
618 pub v8_heap_limit_mb: Option<u64>,
619 #[serde(
620 default,
621 rename = "syncRpcWaitTimeoutMs",
622 skip_serializing_if = "Option::is_none"
623 )]
624 pub sync_rpc_wait_timeout_ms: Option<u64>,
625 #[serde(
626 default,
627 rename = "cpuTimeLimitMs",
628 skip_serializing_if = "Option::is_none"
629 )]
630 pub cpu_time_limit_ms: Option<u64>,
631 #[serde(
632 default,
633 rename = "wallClockLimitMs",
634 skip_serializing_if = "Option::is_none"
635 )]
636 pub wall_clock_limit_ms: Option<u64>,
637 #[serde(
638 default,
639 rename = "importCacheMaterializeTimeoutMs",
640 skip_serializing_if = "Option::is_none"
641 )]
642 pub import_cache_materialize_timeout_ms: Option<u64>,
643 #[serde(
644 default,
645 rename = "capturedOutputLimitBytes",
646 skip_serializing_if = "Option::is_none"
647 )]
648 pub captured_output_limit_bytes: Option<u64>,
649 #[serde(
650 default,
651 rename = "stdinBufferLimitBytes",
652 skip_serializing_if = "Option::is_none"
653 )]
654 pub stdin_buffer_limit_bytes: Option<u64>,
655 #[serde(
656 default,
657 rename = "eventPayloadLimitBytes",
658 skip_serializing_if = "Option::is_none"
659 )]
660 pub event_payload_limit_bytes: Option<u64>,
661 #[serde(
662 default,
663 rename = "v8IpcMaxFrameBytes",
664 skip_serializing_if = "Option::is_none"
665 )]
666 pub v8_ipc_max_frame_bytes: Option<u64>,
667}
668
669#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
670pub struct PythonLimits {
671 #[serde(
672 default,
673 rename = "outputBufferMaxBytes",
674 skip_serializing_if = "Option::is_none"
675 )]
676 pub output_buffer_max_bytes: Option<u64>,
677 #[serde(
678 default,
679 rename = "executionTimeoutMs",
680 skip_serializing_if = "Option::is_none"
681 )]
682 pub execution_timeout_ms: Option<u64>,
683 #[serde(
684 default,
685 rename = "maxOldSpaceMb",
686 skip_serializing_if = "Option::is_none"
687 )]
688 pub max_old_space_mb: Option<u64>,
689 #[serde(
690 default,
691 rename = "vfsRpcTimeoutMs",
692 skip_serializing_if = "Option::is_none"
693 )]
694 pub vfs_rpc_timeout_ms: Option<u64>,
695}
696
697#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
698pub struct WasmLimits {
699 #[serde(
700 default,
701 rename = "maxModuleFileBytes",
702 skip_serializing_if = "Option::is_none"
703 )]
704 pub max_module_file_bytes: Option<u64>,
705 #[serde(
706 default,
707 rename = "capturedOutputLimitBytes",
708 skip_serializing_if = "Option::is_none"
709 )]
710 pub captured_output_limit_bytes: Option<u64>,
711 #[serde(
712 default,
713 rename = "syncReadLimitBytes",
714 skip_serializing_if = "Option::is_none"
715 )]
716 pub sync_read_limit_bytes: Option<u64>,
717 #[serde(
718 default,
719 rename = "prewarmTimeoutMs",
720 skip_serializing_if = "Option::is_none"
721 )]
722 pub prewarm_timeout_ms: Option<u64>,
723 #[serde(
724 default,
725 rename = "runnerHeapLimitMb",
726 skip_serializing_if = "Option::is_none"
727 )]
728 pub runner_heap_limit_mb: Option<u64>,
729 #[serde(
730 default,
731 rename = "runnerCpuTimeLimitMs",
732 skip_serializing_if = "Option::is_none"
733 )]
734 pub runner_cpu_time_limit_ms: Option<u64>,
735}
736
737#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
738pub struct ProcessLimits {
739 #[serde(
740 default,
741 rename = "maxSpawnFileActions",
742 skip_serializing_if = "Option::is_none"
743 )]
744 pub max_spawn_file_actions: Option<u64>,
745 #[serde(
746 default,
747 rename = "maxSpawnFileActionBytes",
748 skip_serializing_if = "Option::is_none"
749 )]
750 pub max_spawn_file_action_bytes: Option<u64>,
751 #[serde(
752 default,
753 rename = "pendingStdinBytes",
754 skip_serializing_if = "Option::is_none"
755 )]
756 pub pending_stdin_bytes: Option<u64>,
757 #[serde(
758 default,
759 rename = "pendingEventCount",
760 skip_serializing_if = "Option::is_none"
761 )]
762 pub pending_event_count: Option<u64>,
763 #[serde(
764 default,
765 rename = "pendingEventBytes",
766 skip_serializing_if = "Option::is_none"
767 )]
768 pub pending_event_bytes: Option<u64>,
769}
770
771#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
777pub struct Permissions {
778 #[serde(default, skip_serializing_if = "Option::is_none")]
779 pub fs: Option<FsPermissions>,
780 #[serde(default, skip_serializing_if = "Option::is_none")]
781 pub network: Option<PatternPermissions>,
782 #[serde(
783 default,
784 rename = "childProcess",
785 skip_serializing_if = "Option::is_none"
786 )]
787 pub child_process: Option<PatternPermissions>,
788 #[serde(default, skip_serializing_if = "Option::is_none")]
789 pub process: Option<PatternPermissions>,
790 #[serde(default, skip_serializing_if = "Option::is_none")]
791 pub env: Option<PatternPermissions>,
792 #[serde(default, skip_serializing_if = "Option::is_none")]
793 pub binding: Option<PatternPermissions>,
794}
795
796#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
798#[serde(rename_all = "lowercase")]
799pub enum PermissionMode {
800 Allow,
801 Deny,
802}
803
804#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
806#[serde(untagged)]
807pub enum FsPermissions {
808 Mode(PermissionMode),
809 Rules(RulePermissions<FsPermissionRule>),
810}
811
812#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
814#[serde(untagged)]
815pub enum PatternPermissions {
816 Mode(PermissionMode),
817 Rules(RulePermissions<PatternPermissionRule>),
818}
819
820#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
822pub struct RulePermissions<T> {
823 #[serde(default, skip_serializing_if = "Option::is_none")]
824 pub default: Option<PermissionMode>,
825 pub rules: Vec<T>,
826}
827
828#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
830pub struct FsPermissionRule {
831 pub mode: PermissionMode,
832 #[serde(default, skip_serializing_if = "Option::is_none")]
833 pub operations: Option<Vec<String>>,
834 #[serde(default, skip_serializing_if = "Option::is_none")]
835 pub paths: Option<Vec<String>>,
836}
837
838#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
840pub struct PatternPermissionRule {
841 pub mode: PermissionMode,
842 #[serde(default, skip_serializing_if = "Option::is_none")]
843 pub operations: Option<Vec<String>>,
844 #[serde(default, skip_serializing_if = "Option::is_none")]
845 pub patterns: Option<Vec<String>>,
846}
847
848#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
854pub struct RootFilesystemConfig {
855 #[serde(default, rename = "type")]
856 pub kind: RootFilesystemKind,
857 #[serde(default, skip_serializing_if = "Option::is_none")]
858 pub mode: Option<RootFilesystemMode>,
859 #[serde(
860 default,
861 rename = "nativePlugin",
862 skip_serializing_if = "Option::is_none"
863 )]
864 pub native_plugin: Option<MountPlugin>,
865 #[serde(default, rename = "disableDefaultBaseLayer")]
866 pub disable_default_base_layer: bool,
867 #[serde(default)]
868 pub lowers: Vec<RootLowerInput>,
869}
870
871impl Default for RootFilesystemConfig {
872 fn default() -> Self {
873 Self {
874 kind: RootFilesystemKind::Overlay,
875 mode: None,
876 native_plugin: None,
877 disable_default_base_layer: false,
878 lowers: Vec::new(),
879 }
880 }
881}
882
883#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
885#[serde(rename_all = "lowercase")]
886pub enum RootFilesystemKind {
887 #[default]
888 Overlay,
889 Native,
890}
891
892#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
894#[serde(rename_all = "kebab-case")]
895pub enum RootFilesystemMode {
896 Ephemeral,
897 ReadOnly,
898}
899
900#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
902#[serde(tag = "kind", rename_all = "kebab-case")]
903pub enum RootLowerInput {
904 BundledBaseFilesystem,
906 #[serde(untagged)]
908 SnapshotExport(crate::fs::RootSnapshotExport),
909}
910
911pub enum MountConfig {
917 Plain {
919 path: String,
920 driver: Arc<dyn VirtualFileSystem>,
921 guest_source: Option<String>,
922 guest_fstype: Option<String>,
923 read_only: bool,
924 },
925 Native {
927 path: String,
928 plugin: MountPlugin,
929 guest_source: Option<String>,
930 guest_fstype: Option<String>,
931 read_only: bool,
932 },
933 Overlay {
935 path: String,
936 filesystem: OverlayMountConfig,
937 },
938}
939
940#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
942pub struct MountPlugin {
943 pub id: String,
944 #[serde(default, skip_serializing_if = "Option::is_none")]
945 pub config: Option<serde_json::Value>,
946}
947
948pub fn node_modules_mount(host_node_modules_dir: impl Into<String>) -> MountConfig {
956 MountConfig::Native {
957 path: "/root/node_modules".to_string(),
958 plugin: MountPlugin {
959 id: "host_dir".to_string(),
960 config: Some(serde_json::json!({
961 "hostPath": host_node_modules_dir.into(),
962 "readOnly": true,
963 })),
964 },
965 guest_source: Some(String::from("host_dir")),
966 guest_fstype: Some(String::from("host_dir")),
967 read_only: true,
968 }
969}
970
971#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
973pub struct OverlayMountConfig {
974 #[serde(rename = "type")]
975 pub kind: String,
976 pub store: serde_json::Value,
977 #[serde(default, skip_serializing_if = "Option::is_none")]
978 pub mode: Option<RootFilesystemMode>,
979 pub lowers: Vec<RootLowerInput>,
980}
981
982pub enum AgentOsSidecarConfig {
988 Shared { pool: Option<String> },
990 Explicit {
992 handle: Arc<crate::sidecar::AgentOsSidecar>,
993 },
994}
995
996pub type ScheduleCallback = Arc<dyn Fn() -> futures::future::BoxFuture<'static, ()> + Send + Sync>;
1006
1007#[derive(Clone)]
1010pub struct ScheduleEntry {
1011 pub id: String,
1013 pub schedule: String,
1015 pub callback: ScheduleCallback,
1017}
1018
1019pub trait ScheduleDriver: Send + Sync {
1025 fn schedule(&self, entry: ScheduleEntry) -> ScheduleHandle;
1028
1029 fn cancel(&self, handle: &ScheduleHandle);
1031
1032 fn dispose(&self);
1034}
1035
1036#[derive(Clone)]
1039pub struct ScheduleHandle {
1040 pub id: String,
1041}
1042
1043#[derive(Default)]
1050pub struct TimerScheduleDriver {
1051 timers: Arc<scc::HashMap<String, tokio_util::sync::CancellationToken>>,
1052}
1053
1054impl TimerScheduleDriver {
1055 pub fn new() -> Self {
1056 Self {
1057 timers: Arc::new(scc::HashMap::new()),
1058 }
1059 }
1060
1061 fn schedule_next(
1065 timers: Arc<scc::HashMap<String, tokio_util::sync::CancellationToken>>,
1066 entry: ScheduleEntry,
1067 cancel: tokio_util::sync::CancellationToken,
1068 ) {
1069 let now = chrono::Utc::now();
1070 let parsed = match crate::cron::parse_schedule(&entry.schedule) {
1071 Ok(parsed) => parsed,
1072 Err(_) => {
1073 let _ = timers.remove(&entry.id);
1074 return;
1075 }
1076 };
1077 let is_cron = parsed.is_cron();
1078 let next = match crate::cron::resolve_next_run(&parsed, now) {
1079 Some(next) => next,
1080 None => {
1081 let _ = timers.remove(&entry.id);
1083 return;
1084 }
1085 };
1086
1087 let delay = (next - now).to_std().unwrap_or(std::time::Duration::ZERO);
1088
1089 tokio::spawn(async move {
1090 tokio::select! {
1091 _ = cancel.cancelled() => {
1092 return;
1093 }
1094 _ = tokio::time::sleep(delay) => {}
1095 }
1096 if cancel.is_cancelled() {
1097 return;
1098 }
1099 (entry.callback)().await;
1101
1102 if is_cron && timers.contains(&entry.id) {
1103 Self::schedule_next(Arc::clone(&timers), entry, cancel);
1104 } else {
1105 let _ = timers.remove(&entry.id);
1106 }
1107 });
1108 }
1109}
1110
1111impl ScheduleDriver for TimerScheduleDriver {
1112 fn schedule(&self, entry: ScheduleEntry) -> ScheduleHandle {
1113 let id = entry.id.clone();
1114 let cancel = tokio_util::sync::CancellationToken::new();
1115 if let Some((_, old)) = self.timers.remove(&id) {
1117 old.cancel();
1118 }
1119 let _ = self.timers.insert(id.clone(), cancel.clone());
1120
1121 Self::schedule_next(Arc::clone(&self.timers), entry, cancel);
1122
1123 ScheduleHandle { id }
1124 }
1125
1126 fn cancel(&self, handle: &ScheduleHandle) {
1127 if let Some((_, cancel)) = self.timers.remove(&handle.id) {
1128 cancel.cancel();
1129 }
1130 }
1131
1132 fn dispose(&self) {
1133 self.timers.scan(|_, cancel| cancel.cancel());
1134 self.timers.clear();
1135 }
1136}