1use std::collections::BTreeMap;
12use std::sync::Arc;
13
14use serde::{Deserialize, Serialize};
15
16use crate::fs::VirtualFileSystem;
17pub use agentos_vm_config::{VmGroupConfig, VmUserAccountConfig, VmUserConfig};
18
19#[derive(Default)]
24pub struct AgentOsConfig {
25 pub database: Option<agentos_vm_config::VmSqliteDescriptor>,
29 pub user: Option<VmUserConfig>,
31 pub software: Vec<SoftwareInput>,
33 pub packages: Vec<PackageRef>,
37 pub packages_mount_at: Option<String>,
40 pub loopback_exempt_ports: Vec<u16>,
42 pub allowed_node_builtins: Option<Vec<String>>,
44 pub root_filesystem: RootFilesystemConfig,
46 pub mounts: Vec<MountConfig>,
48 pub additional_instructions: Option<String>,
50 pub schedule_driver: Option<Arc<dyn ScheduleDriver>>,
52 pub host_functions: HostFunctionCollections,
54 pub sidecar_js_bridge_callback: Option<SidecarJsBridgeCallback>,
56 pub permissions: Option<Permissions>,
58 pub limits: Option<AgentOsLimits>,
60 pub sidecar: Option<AgentOsSidecarConfig>,
62 pub sidecar_binary_path: Option<String>,
67}
68
69#[derive(Default)]
71pub struct AgentOsConfigBuilder {
72 config: AgentOsConfig,
73}
74
75impl AgentOsConfigBuilder {
76 pub fn new() -> Self {
77 Self::default()
78 }
79
80 pub fn database(mut self, database: agentos_vm_config::VmSqliteDescriptor) -> Self {
81 self.config.database = Some(database);
82 self
83 }
84
85 pub fn packages(mut self, packages: Vec<PackageRef>) -> Self {
86 self.config.packages = packages;
87 self
88 }
89
90 pub fn packages_mount_at(mut self, mount_at: impl Into<String>) -> Self {
91 self.config.packages_mount_at = Some(mount_at.into());
92 self
93 }
94
95 pub fn loopback_exempt_ports(mut self, ports: Vec<u16>) -> Self {
96 self.config.loopback_exempt_ports = ports;
97 self
98 }
99
100 pub fn allowed_node_builtins(mut self, builtins: Vec<String>) -> Self {
101 self.config.allowed_node_builtins = Some(builtins);
102 self
103 }
104
105 pub fn user(mut self, user: VmUserConfig) -> Self {
106 self.config.user = Some(user);
107 self
108 }
109
110 pub fn root_filesystem(mut self, root: RootFilesystemConfig) -> Self {
111 self.config.root_filesystem = root;
112 self
113 }
114
115 pub fn mounts(mut self, mounts: Vec<MountConfig>) -> Self {
116 self.config.mounts = mounts;
117 self
118 }
119
120 pub fn additional_instructions(mut self, instructions: impl Into<String>) -> Self {
121 self.config.additional_instructions = Some(instructions.into());
122 self
123 }
124
125 pub fn schedule_driver(mut self, driver: Arc<dyn ScheduleDriver>) -> Self {
126 self.config.schedule_driver = Some(driver);
127 self
128 }
129
130 pub fn host_functions(mut self, host_functions: HostFunctionCollections) -> Self {
131 self.config.host_functions = host_functions;
132 self
133 }
134
135 pub fn sidecar_js_bridge_callback(mut self, callback: SidecarJsBridgeCallback) -> Self {
136 self.config.sidecar_js_bridge_callback = Some(callback);
137 self
138 }
139
140 pub fn permissions(mut self, permissions: Permissions) -> Self {
141 self.config.permissions = Some(permissions);
142 self
143 }
144
145 pub fn limits(mut self, limits: AgentOsLimits) -> Self {
146 self.config.limits = Some(limits);
147 self
148 }
149
150 pub fn sidecar(mut self, sidecar: AgentOsSidecarConfig) -> Self {
151 self.config.sidecar = Some(sidecar);
152 self
153 }
154
155 pub fn sidecar_binary_path(mut self, path: impl Into<String>) -> Self {
156 self.config.sidecar_binary_path = Some(path.into());
157 self
158 }
159
160 pub fn build(self) -> AgentOsConfig {
161 self.config
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
168#[serde(rename_all = "kebab-case")]
169pub enum SoftwareKind {
170 #[default]
173 WasmCommands,
174 Agent,
176 HostFunction,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182pub struct SoftwareInput {
183 pub package: String,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub version: Option<String>,
186 #[serde(default)]
188 pub kind: SoftwareKind,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195pub struct PackageRef {
196 #[serde(rename = "packagePath")]
199 pub path: String,
200}
201
202pub type HostFunctionCallback = Arc<
206 dyn Fn(
207 serde_json::Value,
208 ) -> futures::future::BoxFuture<'static, Result<serde_json::Value, String>>
209 + Send
210 + Sync,
211>;
212
213#[derive(Debug, Clone, PartialEq, Eq)]
215pub struct SidecarJsBridgeCall {
216 pub call_id: String,
217 pub mount_id: String,
218 pub operation: String,
219 pub args: serde_json::Value,
220}
221
222pub type SidecarJsBridgeCallback = Arc<
228 dyn Fn(
229 SidecarJsBridgeCall,
230 )
231 -> futures::future::BoxFuture<'static, Result<Option<serde_json::Value>, String>>
232 + Send
233 + Sync,
234>;
235
236#[derive(Clone)]
239pub struct HostFunction {
240 pub input_schema: serde_json::Value,
242 pub timeout_ms: Option<u64>,
243 pub execute: HostFunctionCallback,
245}
246
247pub type HostFunctionCollection = BTreeMap<String, HostFunction>;
250
251pub type HostFunctionCollections = BTreeMap<String, HostFunctionCollection>;
257
258#[derive(Clone)]
260pub struct ResolvedHostFunction {
261 pub name: String,
263 pub description: String,
265 pub input_schema: serde_json::Value,
266 pub timeout_ms: Option<u64>,
267 pub execute: HostFunctionCallback,
268}
269
270#[derive(Clone)]
274pub struct ResolvedHostFunctions {
275 pub name: String,
277 pub functions: Vec<ResolvedHostFunction>,
278}
279
280pub fn host_function_command_name(key: &str) -> String {
285 let mut out = String::with_capacity(key.len() + 4);
286 let chars: Vec<char> = key.chars().collect();
287 for (index, character) in chars.iter().enumerate() {
288 if character.is_ascii_uppercase() && index > 0 {
289 let previous = chars[index - 1];
290 let next_is_lower = chars
291 .get(index + 1)
292 .is_some_and(|next| next.is_ascii_lowercase());
293 if previous.is_ascii_lowercase()
294 || previous.is_ascii_digit()
295 || (previous.is_ascii_uppercase() && next_is_lower)
296 {
297 out.push('-');
298 }
299 }
300 out.extend(character.to_lowercase());
301 }
302 out
303}
304
305fn is_command_name(name: &str) -> bool {
306 !name.is_empty()
307 && !name.starts_with('-')
308 && !name.ends_with('-')
309 && !name.contains("--")
310 && name.chars().all(|character| {
311 character.is_ascii_lowercase() || character.is_ascii_digit() || character == '-'
312 })
313}
314
315fn to_command_name(kind: &str, key: &str) -> Result<String, String> {
316 let name = host_function_command_name(key);
317 if is_command_name(&name) {
318 Ok(name)
319 } else {
320 Err(format!(
321 "{kind} name \"{key}\" must be alphanumeric, written in camelCase or with single hyphen separators"
322 ))
323 }
324}
325
326fn schema_description(input_schema: &serde_json::Value) -> String {
328 input_schema
329 .get("description")
330 .and_then(serde_json::Value::as_str)
331 .unwrap_or_default()
332 .to_string()
333}
334
335pub fn resolve_host_functions(
339 collections: &HostFunctionCollections,
340) -> Result<Vec<ResolvedHostFunctions>, String> {
341 let mut resolved = Vec::with_capacity(collections.len());
342 let mut seen_collections: BTreeMap<String, String> = BTreeMap::new();
343
344 for (collection_key, collection) in collections {
345 let name = to_command_name("Host function collection", collection_key)?;
346 if let Some(collided) = seen_collections.get(&name) {
347 return Err(format!(
348 "Host function collections \"{collided}\" and \"{collection_key}\" both resolve to the command name \"{name}\""
349 ));
350 }
351 seen_collections.insert(name.clone(), collection_key.clone());
352
353 let mut functions = Vec::with_capacity(collection.len());
354 let mut seen_functions: BTreeMap<String, String> = BTreeMap::new();
355 for (function_key, definition) in collection {
356 let function_name = to_command_name("Host function", function_key)?;
357 if let Some(collided) = seen_functions.get(&function_name) {
358 return Err(format!(
359 "Host functions \"{collided}\" and \"{function_key}\" in collection \"{collection_key}\" both resolve to the command name \"{function_name}\""
360 ));
361 }
362 seen_functions.insert(function_name.clone(), function_key.clone());
363 functions.push(ResolvedHostFunction {
364 name: function_name,
365 description: schema_description(&definition.input_schema),
366 input_schema: definition.input_schema.clone(),
367 timeout_ms: definition.timeout_ms,
368 execute: definition.execute.clone(),
369 });
370 }
371
372 resolved.push(ResolvedHostFunctions { name, functions });
373 }
374
375 Ok(resolved)
376}
377
378#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
385pub struct AgentOsLimits {
386 #[serde(default, skip_serializing_if = "Option::is_none")]
387 pub resources: Option<ResourceLimits>,
388 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub http: Option<HttpLimits>,
390 #[serde(
391 default,
392 rename = "hostFunctions",
393 skip_serializing_if = "Option::is_none"
394 )]
395 pub host_functions: Option<HostFunctionLimits>,
396 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub plugins: Option<PluginLimits>,
398 #[serde(default, skip_serializing_if = "Option::is_none")]
399 pub acp: Option<AcpLimits>,
400 #[serde(default, skip_serializing_if = "Option::is_none")]
401 pub sqlite: Option<SqliteLimits>,
402 #[serde(default, rename = "jsRuntime", skip_serializing_if = "Option::is_none")]
403 pub js_runtime: Option<JsRuntimeLimits>,
404 #[serde(default, skip_serializing_if = "Option::is_none")]
405 pub python: Option<PythonLimits>,
406 #[serde(default, skip_serializing_if = "Option::is_none")]
407 pub wasm: Option<WasmLimits>,
408 #[serde(default, skip_serializing_if = "Option::is_none")]
409 pub process: Option<ProcessLimits>,
410}
411
412#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
413pub struct ResourceLimits {
414 #[serde(default, rename = "cpuCount", skip_serializing_if = "Option::is_none")]
415 pub cpu_count: Option<u64>,
416 #[serde(
417 default,
418 rename = "maxProcesses",
419 skip_serializing_if = "Option::is_none"
420 )]
421 pub max_processes: Option<u64>,
422 #[serde(
423 default,
424 rename = "maxOpenFds",
425 skip_serializing_if = "Option::is_none"
426 )]
427 pub max_open_fds: Option<u64>,
428 #[serde(default, rename = "maxPipes", skip_serializing_if = "Option::is_none")]
429 pub max_pipes: Option<u64>,
430 #[serde(default, rename = "maxPtys", skip_serializing_if = "Option::is_none")]
431 pub max_ptys: Option<u64>,
432 #[serde(
433 default,
434 rename = "maxSockets",
435 skip_serializing_if = "Option::is_none"
436 )]
437 pub max_sockets: Option<u64>,
438 #[serde(
439 default,
440 rename = "maxConnections",
441 skip_serializing_if = "Option::is_none"
442 )]
443 pub max_connections: Option<u64>,
444 #[serde(
445 default,
446 rename = "maxSocketBufferedBytes",
447 skip_serializing_if = "Option::is_none"
448 )]
449 pub max_socket_buffered_bytes: Option<u64>,
450 #[serde(
451 default,
452 rename = "maxSocketDatagramQueueLen",
453 skip_serializing_if = "Option::is_none"
454 )]
455 pub max_socket_datagram_queue_len: Option<u64>,
456 #[serde(
457 default,
458 rename = "maxFilesystemBytes",
459 skip_serializing_if = "Option::is_none"
460 )]
461 pub max_filesystem_bytes: Option<u64>,
462 #[serde(
463 default,
464 rename = "maxInodeCount",
465 skip_serializing_if = "Option::is_none"
466 )]
467 pub max_inode_count: Option<u64>,
468 #[serde(
469 default,
470 rename = "maxBlockingReadMs",
471 skip_serializing_if = "Option::is_none"
472 )]
473 pub max_blocking_read_ms: Option<u64>,
474 #[serde(
475 default,
476 rename = "maxPreadBytes",
477 skip_serializing_if = "Option::is_none"
478 )]
479 pub max_pread_bytes: Option<u64>,
480 #[serde(
481 default,
482 rename = "maxFdWriteBytes",
483 skip_serializing_if = "Option::is_none"
484 )]
485 pub max_fd_write_bytes: Option<u64>,
486 #[serde(
487 default,
488 rename = "maxProcessArgvBytes",
489 skip_serializing_if = "Option::is_none"
490 )]
491 pub max_process_argv_bytes: Option<u64>,
492 #[serde(
493 default,
494 rename = "maxProcessEnvBytes",
495 skip_serializing_if = "Option::is_none"
496 )]
497 pub max_process_env_bytes: Option<u64>,
498 #[serde(
499 default,
500 rename = "maxReaddirEntries",
501 skip_serializing_if = "Option::is_none"
502 )]
503 pub max_readdir_entries: Option<u64>,
504 #[serde(
505 default,
506 rename = "maxWasmFuel",
507 skip_serializing_if = "Option::is_none"
508 )]
509 pub max_wasm_fuel: Option<u64>,
510 #[serde(
511 default,
512 rename = "maxWasmMemoryBytes",
513 skip_serializing_if = "Option::is_none"
514 )]
515 pub max_wasm_memory_bytes: Option<u64>,
516 #[serde(
517 default,
518 rename = "maxWasmStackBytes",
519 skip_serializing_if = "Option::is_none"
520 )]
521 pub max_wasm_stack_bytes: Option<u64>,
522}
523
524#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
525pub struct HttpLimits {
526 #[serde(
527 default,
528 rename = "maxFetchResponseBytes",
529 skip_serializing_if = "Option::is_none"
530 )]
531 pub max_fetch_response_bytes: Option<u64>,
532}
533
534#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
535pub struct HostFunctionLimits {
536 #[serde(
537 default,
538 rename = "defaultTimeoutMs",
539 skip_serializing_if = "Option::is_none"
540 )]
541 pub default_timeout_ms: Option<u64>,
542 #[serde(
543 default,
544 rename = "maxTimeoutMs",
545 skip_serializing_if = "Option::is_none"
546 )]
547 pub max_timeout_ms: Option<u64>,
548 #[serde(
549 default,
550 rename = "maxRegisteredCollections",
551 skip_serializing_if = "Option::is_none"
552 )]
553 pub max_registered_collections: Option<u64>,
554 #[serde(
555 default,
556 rename = "maxRegisteredFunctionsPerVm",
557 skip_serializing_if = "Option::is_none"
558 )]
559 pub max_registered_functions_per_vm: Option<u64>,
560 #[serde(
561 default,
562 rename = "maxFunctionsPerCollection",
563 skip_serializing_if = "Option::is_none"
564 )]
565 pub max_functions_per_collection: Option<u64>,
566 #[serde(
567 default,
568 rename = "maxSchemaBytes",
569 skip_serializing_if = "Option::is_none"
570 )]
571 pub max_schema_bytes: Option<u64>,
572 #[serde(
573 default,
574 rename = "maxExamplesPerFunction",
575 skip_serializing_if = "Option::is_none"
576 )]
577 pub max_examples_per_function: Option<u64>,
578 #[serde(
579 default,
580 rename = "maxExampleInputBytes",
581 skip_serializing_if = "Option::is_none"
582 )]
583 pub max_example_input_bytes: Option<u64>,
584}
585
586#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
587pub struct PluginLimits {
588 #[serde(
589 default,
590 rename = "maxPersistedManifestBytes",
591 skip_serializing_if = "Option::is_none"
592 )]
593 pub max_persisted_manifest_bytes: Option<u64>,
594 #[serde(
595 default,
596 rename = "maxPersistedManifestFileBytes",
597 skip_serializing_if = "Option::is_none"
598 )]
599 pub max_persisted_manifest_file_bytes: Option<u64>,
600}
601
602#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
603pub struct AcpLimits {
604 #[serde(
605 default,
606 rename = "maxReadLineBytes",
607 skip_serializing_if = "Option::is_none"
608 )]
609 pub max_read_line_bytes: Option<u64>,
610 #[serde(
611 default,
612 rename = "stdoutBufferByteLimit",
613 skip_serializing_if = "Option::is_none"
614 )]
615 pub stdout_buffer_byte_limit: Option<u64>,
616 #[serde(
617 default,
618 rename = "maxCompletedMessageBytes",
619 skip_serializing_if = "Option::is_none"
620 )]
621 pub max_completed_message_bytes: Option<u64>,
622 #[serde(
623 default,
624 rename = "maxTurnOutputBytes",
625 skip_serializing_if = "Option::is_none"
626 )]
627 pub max_turn_output_bytes: Option<u64>,
628 #[serde(
629 default,
630 rename = "maxPromptBytes",
631 skip_serializing_if = "Option::is_none"
632 )]
633 pub max_prompt_bytes: Option<u64>,
634 #[serde(
635 default,
636 rename = "maxPromptBlocks",
637 skip_serializing_if = "Option::is_none"
638 )]
639 pub max_prompt_blocks: Option<u64>,
640 #[serde(
641 default,
642 rename = "maxFallbackContinuationBytes",
643 skip_serializing_if = "Option::is_none"
644 )]
645 pub max_fallback_continuation_bytes: Option<u64>,
646 #[serde(
647 default,
648 rename = "maxSessionHistoryBytes",
649 skip_serializing_if = "Option::is_none"
650 )]
651 pub max_session_history_bytes: Option<u64>,
652 #[serde(
653 default,
654 rename = "maxSessionHistoryEvents",
655 skip_serializing_if = "Option::is_none"
656 )]
657 pub max_session_history_events: Option<u64>,
658 #[serde(
659 default,
660 rename = "maxHistoryPageEntries",
661 skip_serializing_if = "Option::is_none"
662 )]
663 pub max_history_page_entries: Option<u64>,
664 #[serde(
665 default,
666 rename = "maxSessionListEntries",
667 skip_serializing_if = "Option::is_none"
668 )]
669 pub max_session_list_entries: Option<u64>,
670 #[serde(
671 default,
672 rename = "maxSessionsPerVm",
673 skip_serializing_if = "Option::is_none"
674 )]
675 pub max_sessions_per_vm: Option<u64>,
676 #[serde(
677 default,
678 rename = "maxPromptsPerSession",
679 skip_serializing_if = "Option::is_none"
680 )]
681 pub max_prompts_per_session: Option<u64>,
682 #[serde(
683 default,
684 rename = "maxPromptsPerVm",
685 skip_serializing_if = "Option::is_none"
686 )]
687 pub max_prompts_per_vm: Option<u64>,
688 #[serde(
689 default,
690 rename = "maxPendingPermissionsPerSession",
691 skip_serializing_if = "Option::is_none"
692 )]
693 pub max_pending_permissions_per_session: Option<u64>,
694 #[serde(
695 default,
696 rename = "maxPendingPermissionsPerVm",
697 skip_serializing_if = "Option::is_none"
698 )]
699 pub max_pending_permissions_per_vm: Option<u64>,
700 #[serde(
701 default,
702 rename = "maxPermissionOutcomesPerSession",
703 skip_serializing_if = "Option::is_none"
704 )]
705 pub max_permission_outcomes_per_session: Option<u64>,
706 #[serde(
707 default,
708 rename = "maxPermissionOutcomesPerVm",
709 skip_serializing_if = "Option::is_none"
710 )]
711 pub max_permission_outcomes_per_vm: Option<u64>,
712}
713
714#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
715pub struct SqliteLimits {
716 #[serde(
717 default,
718 rename = "maxResultBytes",
719 skip_serializing_if = "Option::is_none"
720 )]
721 pub max_result_bytes: Option<u64>,
722}
723
724#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
725pub struct JsRuntimeLimits {
726 #[serde(
727 default,
728 rename = "v8HeapLimitMb",
729 skip_serializing_if = "Option::is_none"
730 )]
731 pub v8_heap_limit_mb: Option<u64>,
732 #[serde(
733 default,
734 rename = "syncRpcWaitTimeoutMs",
735 skip_serializing_if = "Option::is_none"
736 )]
737 pub sync_rpc_wait_timeout_ms: Option<u64>,
738 #[serde(
739 default,
740 rename = "cpuTimeLimitMs",
741 skip_serializing_if = "Option::is_none"
742 )]
743 pub cpu_time_limit_ms: Option<u64>,
744 #[serde(
745 default,
746 rename = "wallClockLimitMs",
747 skip_serializing_if = "Option::is_none"
748 )]
749 pub wall_clock_limit_ms: Option<u64>,
750 #[serde(
751 default,
752 rename = "importCacheMaterializeTimeoutMs",
753 skip_serializing_if = "Option::is_none"
754 )]
755 pub import_cache_materialize_timeout_ms: Option<u64>,
756 #[serde(
757 default,
758 rename = "capturedOutputLimitBytes",
759 skip_serializing_if = "Option::is_none"
760 )]
761 pub captured_output_limit_bytes: Option<u64>,
762 #[serde(
763 default,
764 rename = "stdinBufferLimitBytes",
765 skip_serializing_if = "Option::is_none"
766 )]
767 pub stdin_buffer_limit_bytes: Option<u64>,
768 #[serde(
769 default,
770 rename = "eventPayloadLimitBytes",
771 skip_serializing_if = "Option::is_none"
772 )]
773 pub event_payload_limit_bytes: Option<u64>,
774 #[serde(
775 default,
776 rename = "v8IpcMaxFrameBytes",
777 skip_serializing_if = "Option::is_none"
778 )]
779 pub v8_ipc_max_frame_bytes: Option<u64>,
780}
781
782#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
783pub struct PythonLimits {
784 #[serde(
785 default,
786 rename = "outputBufferMaxBytes",
787 skip_serializing_if = "Option::is_none"
788 )]
789 pub output_buffer_max_bytes: Option<u64>,
790 #[serde(
791 default,
792 rename = "executionTimeoutMs",
793 skip_serializing_if = "Option::is_none"
794 )]
795 pub execution_timeout_ms: Option<u64>,
796 #[serde(
797 default,
798 rename = "maxOldSpaceMb",
799 skip_serializing_if = "Option::is_none"
800 )]
801 pub max_old_space_mb: Option<u64>,
802 #[serde(
803 default,
804 rename = "vfsRpcTimeoutMs",
805 skip_serializing_if = "Option::is_none"
806 )]
807 pub vfs_rpc_timeout_ms: Option<u64>,
808}
809
810#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
811pub struct WasmLimits {
812 #[serde(
813 default,
814 rename = "maxModuleFileBytes",
815 skip_serializing_if = "Option::is_none"
816 )]
817 pub max_module_file_bytes: Option<u64>,
818 #[serde(
819 default,
820 rename = "capturedOutputLimitBytes",
821 skip_serializing_if = "Option::is_none"
822 )]
823 pub captured_output_limit_bytes: Option<u64>,
824 #[serde(
825 default,
826 rename = "syncReadLimitBytes",
827 skip_serializing_if = "Option::is_none"
828 )]
829 pub sync_read_limit_bytes: Option<u64>,
830 #[serde(
831 default,
832 rename = "prewarmTimeoutMs",
833 skip_serializing_if = "Option::is_none"
834 )]
835 pub prewarm_timeout_ms: Option<u64>,
836 #[serde(
837 default,
838 rename = "runnerHeapLimitMb",
839 skip_serializing_if = "Option::is_none"
840 )]
841 pub runner_heap_limit_mb: Option<u64>,
842 #[serde(
843 default,
844 rename = "runnerCpuTimeLimitMs",
845 skip_serializing_if = "Option::is_none"
846 )]
847 pub runner_cpu_time_limit_ms: Option<u64>,
848}
849
850#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
851pub struct ProcessLimits {
852 #[serde(
853 default,
854 rename = "maxSpawnFileActions",
855 skip_serializing_if = "Option::is_none"
856 )]
857 pub max_spawn_file_actions: Option<u64>,
858 #[serde(
859 default,
860 rename = "maxSpawnFileActionBytes",
861 skip_serializing_if = "Option::is_none"
862 )]
863 pub max_spawn_file_action_bytes: Option<u64>,
864 #[serde(
865 default,
866 rename = "pendingStdinBytes",
867 skip_serializing_if = "Option::is_none"
868 )]
869 pub pending_stdin_bytes: Option<u64>,
870 #[serde(
871 default,
872 rename = "pendingEventCount",
873 skip_serializing_if = "Option::is_none"
874 )]
875 pub pending_event_count: Option<u64>,
876 #[serde(
877 default,
878 rename = "pendingEventBytes",
879 skip_serializing_if = "Option::is_none"
880 )]
881 pub pending_event_bytes: Option<u64>,
882}
883
884#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
890pub struct Permissions {
891 #[serde(default, skip_serializing_if = "Option::is_none")]
892 pub fs: Option<FsPermissions>,
893 #[serde(default, skip_serializing_if = "Option::is_none")]
894 pub network: Option<PatternPermissions>,
895 #[serde(
896 default,
897 rename = "childProcess",
898 skip_serializing_if = "Option::is_none"
899 )]
900 pub child_process: Option<PatternPermissions>,
901 #[serde(default, skip_serializing_if = "Option::is_none")]
902 pub process: Option<PatternPermissions>,
903 #[serde(default, skip_serializing_if = "Option::is_none")]
904 pub env: Option<PatternPermissions>,
905 #[serde(
906 default,
907 rename = "hostFunction",
908 skip_serializing_if = "Option::is_none"
909 )]
910 pub host_function: Option<PatternPermissions>,
911}
912
913#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
915#[serde(rename_all = "lowercase")]
916pub enum PermissionMode {
917 Allow,
918 Deny,
919}
920
921#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
923#[serde(untagged)]
924pub enum FsPermissions {
925 Mode(PermissionMode),
926 Rules(RulePermissions<FsPermissionRule>),
927}
928
929#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
931#[serde(untagged)]
932pub enum PatternPermissions {
933 Mode(PermissionMode),
934 Rules(RulePermissions<PatternPermissionRule>),
935}
936
937#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
939pub struct RulePermissions<T> {
940 #[serde(default, skip_serializing_if = "Option::is_none")]
941 pub default: Option<PermissionMode>,
942 pub rules: Vec<T>,
943}
944
945#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
947pub struct FsPermissionRule {
948 pub mode: PermissionMode,
949 #[serde(default, skip_serializing_if = "Option::is_none")]
950 pub operations: Option<Vec<String>>,
951 #[serde(default, skip_serializing_if = "Option::is_none")]
952 pub paths: Option<Vec<String>>,
953}
954
955#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
957pub struct PatternPermissionRule {
958 pub mode: PermissionMode,
959 #[serde(default, skip_serializing_if = "Option::is_none")]
960 pub operations: Option<Vec<String>>,
961 #[serde(default, skip_serializing_if = "Option::is_none")]
962 pub patterns: Option<Vec<String>>,
963}
964
965#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
971pub struct RootFilesystemConfig {
972 #[serde(default, rename = "type")]
973 pub kind: RootFilesystemKind,
974 #[serde(default, skip_serializing_if = "Option::is_none")]
975 pub mode: Option<RootFilesystemMode>,
976 #[serde(
977 default,
978 rename = "nativePlugin",
979 skip_serializing_if = "Option::is_none"
980 )]
981 pub native_plugin: Option<MountPlugin>,
982 #[serde(default, rename = "disableDefaultBaseLayer")]
983 pub disable_default_base_layer: bool,
984 #[serde(default)]
985 pub lowers: Vec<RootLowerInput>,
986}
987
988impl Default for RootFilesystemConfig {
989 fn default() -> Self {
990 Self {
991 kind: RootFilesystemKind::Overlay,
992 mode: None,
993 native_plugin: None,
994 disable_default_base_layer: false,
995 lowers: Vec::new(),
996 }
997 }
998}
999
1000#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1002#[serde(rename_all = "lowercase")]
1003pub enum RootFilesystemKind {
1004 #[default]
1005 Overlay,
1006 Native,
1007}
1008
1009#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1011#[serde(rename_all = "kebab-case")]
1012pub enum RootFilesystemMode {
1013 Ephemeral,
1014 ReadOnly,
1015}
1016
1017#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1019#[serde(tag = "kind", rename_all = "kebab-case")]
1020pub enum RootLowerInput {
1021 BundledBaseFilesystem,
1023 #[serde(untagged)]
1025 SnapshotExport(crate::fs::RootSnapshotExport),
1026}
1027
1028pub enum MountConfig {
1034 Plain {
1036 path: String,
1037 driver: Arc<dyn VirtualFileSystem>,
1038 guest_source: Option<String>,
1039 guest_fstype: Option<String>,
1040 read_only: bool,
1041 },
1042 Native {
1044 path: String,
1045 plugin: MountPlugin,
1046 guest_source: Option<String>,
1047 guest_fstype: Option<String>,
1048 read_only: bool,
1049 },
1050 Overlay {
1052 path: String,
1053 filesystem: OverlayMountConfig,
1054 },
1055}
1056
1057#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1059pub struct MountPlugin {
1060 pub id: String,
1061 #[serde(default, skip_serializing_if = "Option::is_none")]
1062 pub config: Option<serde_json::Value>,
1063}
1064
1065pub fn node_modules_mount(host_node_modules_dir: impl Into<String>) -> MountConfig {
1073 MountConfig::Native {
1074 path: "/root/node_modules".to_string(),
1075 plugin: MountPlugin {
1076 id: "host_dir".to_string(),
1077 config: Some(serde_json::json!({
1078 "hostPath": host_node_modules_dir.into(),
1079 "readOnly": true,
1080 })),
1081 },
1082 guest_source: Some(String::from("host_dir")),
1083 guest_fstype: Some(String::from("host_dir")),
1084 read_only: true,
1085 }
1086}
1087
1088#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1090pub struct OverlayMountConfig {
1091 #[serde(rename = "type")]
1092 pub kind: String,
1093 pub store: serde_json::Value,
1094 #[serde(default, skip_serializing_if = "Option::is_none")]
1095 pub mode: Option<RootFilesystemMode>,
1096 pub lowers: Vec<RootLowerInput>,
1097}
1098
1099pub enum AgentOsSidecarConfig {
1105 Shared { pool: Option<String> },
1107 Explicit {
1109 handle: Arc<crate::sidecar::AgentOsSidecar>,
1110 },
1111}
1112
1113pub type ScheduleCallback = Arc<dyn Fn() -> futures::future::BoxFuture<'static, ()> + Send + Sync>;
1123
1124#[derive(Clone)]
1127pub struct ScheduleEntry {
1128 pub id: String,
1130 pub schedule: String,
1132 pub callback: ScheduleCallback,
1134}
1135
1136pub trait ScheduleDriver: Send + Sync {
1142 fn schedule(&self, entry: ScheduleEntry) -> ScheduleHandle;
1145
1146 fn cancel(&self, handle: &ScheduleHandle);
1148
1149 fn dispose(&self);
1151}
1152
1153#[derive(Clone)]
1156pub struct ScheduleHandle {
1157 pub id: String,
1158}
1159
1160#[derive(Default)]
1167pub struct TimerScheduleDriver {
1168 timers: Arc<scc::HashMap<String, tokio_util::sync::CancellationToken>>,
1169}
1170
1171impl TimerScheduleDriver {
1172 pub fn new() -> Self {
1173 Self {
1174 timers: Arc::new(scc::HashMap::new()),
1175 }
1176 }
1177
1178 fn schedule_next(
1182 timers: Arc<scc::HashMap<String, tokio_util::sync::CancellationToken>>,
1183 entry: ScheduleEntry,
1184 cancel: tokio_util::sync::CancellationToken,
1185 ) {
1186 let now = chrono::Utc::now();
1187 let parsed = match crate::cron::parse_schedule(&entry.schedule) {
1188 Ok(parsed) => parsed,
1189 Err(_) => {
1190 let _ = timers.remove(&entry.id);
1191 return;
1192 }
1193 };
1194 let is_cron = parsed.is_cron();
1195 let next = match crate::cron::resolve_next_run(&parsed, now) {
1196 Some(next) => next,
1197 None => {
1198 let _ = timers.remove(&entry.id);
1200 return;
1201 }
1202 };
1203
1204 let delay = (next - now).to_std().unwrap_or(std::time::Duration::ZERO);
1205
1206 tokio::spawn(async move {
1207 tokio::select! {
1208 _ = cancel.cancelled() => {
1209 return;
1210 }
1211 _ = tokio::time::sleep(delay) => {}
1212 }
1213 if cancel.is_cancelled() {
1214 return;
1215 }
1216 (entry.callback)().await;
1218
1219 if is_cron && timers.contains(&entry.id) {
1220 Self::schedule_next(Arc::clone(&timers), entry, cancel);
1221 } else {
1222 let _ = timers.remove(&entry.id);
1223 }
1224 });
1225 }
1226}
1227
1228impl ScheduleDriver for TimerScheduleDriver {
1229 fn schedule(&self, entry: ScheduleEntry) -> ScheduleHandle {
1230 let id = entry.id.clone();
1231 let cancel = tokio_util::sync::CancellationToken::new();
1232 if let Some((_, old)) = self.timers.remove(&id) {
1234 old.cancel();
1235 }
1236 let _ = self.timers.insert(id.clone(), cancel.clone());
1237
1238 Self::schedule_next(Arc::clone(&self.timers), entry, cancel);
1239
1240 ScheduleHandle { id }
1241 }
1242
1243 fn cancel(&self, handle: &ScheduleHandle) {
1244 if let Some((_, cancel)) = self.timers.remove(&handle.id) {
1245 cancel.cancel();
1246 }
1247 }
1248
1249 fn dispose(&self) {
1250 self.timers.scan(|_, cancel| cancel.cancel());
1251 self.timers.clear();
1252 }
1253}