1use std::time::Duration;
16
17use serde_json::{Value, json};
18
19use crate::agents::{DEFAULT_PROMPT_TIMEOUT, DEFAULT_SIZE, PromptOptions, bedrock};
20use crate::control::artifact::WrapOptions;
21use crate::control::ensure::EnsureImageRequest;
22use crate::control::{CreateImageRequest, DEFAULT_AGENT_PORT};
23use crate::cost::{DEFAULT_REPORT_CYCLES, DEFAULT_RESIDENCY_CYCLES};
24use crate::sandbox::{
25 DEFAULT_LIFECYCLE_TIMEOUT, LIFECYCLE_POLL_INTERVAL, RunRequest, TeardownOpts,
26};
27use crate::session::{DEFAULT_CLIENT_GRACE, DEFAULT_EXEC_WAIT, StreamOptions};
28use crate::sizing::SizeClass;
29
30pub fn client_defaults() -> Value {
36 let launch = RunRequest::default();
37 let start = protocol::exec::StartRequest::new(String::new(), Vec::new());
38 let stream = StreamOptions::default();
39 let teardown = TeardownOpts::default();
40 let prompt = PromptOptions::default();
41 let plan = crate::cost::PlanUsage::default();
42 let usage = crate::cost::RunUsage::default();
43 let image = CreateImageRequest::new(String::new(), Vec::new(), String::new(), String::new());
44 let ensured = EnsureImageRequest::new(
45 String::new(),
46 Vec::new(),
47 String::new(),
48 String::new(),
49 String::new(),
50 );
51 json!({
52 "sizeMib": SizeClass::DEFAULT.baseline_mib(),
53 "agentSizeMib": DEFAULT_SIZE.baseline_mib(),
54 "agentPort": DEFAULT_AGENT_PORT,
55 "listener": {
56 "bindAddress": crate::session::DEFAULT_BIND.to_string(),
57 "bindIp": crate::session::DEFAULT_BIND.ip().to_string(),
58 },
59 "execWaitSeconds": seconds(DEFAULT_EXEC_WAIT),
60 "readyTimeoutSeconds": seconds(crate::session::DEFAULT_BOOTSTRAP_TIMEOUT),
62 "launchTimeoutSeconds": seconds(crate::sandbox::DEFAULT_RUNNING_TIMEOUT),
63 "lifecycleTimeoutSeconds": seconds(DEFAULT_LIFECYCLE_TIMEOUT),
64 "lifecyclePollIntervalSeconds": seconds(LIFECYCLE_POLL_INTERVAL),
65 "promptTimeoutSeconds": seconds(DEFAULT_PROMPT_TIMEOUT),
66 "clientGraceSeconds": seconds(DEFAULT_CLIENT_GRACE),
67 "bedrockTokenTtlHours": bedrock::MAX_LIFETIME.as_secs() / 3600,
68 "residencyCycles": DEFAULT_RESIDENCY_CYCLES,
69 "reportCycles": DEFAULT_REPORT_CYCLES,
70 "keepAwakeToleratedErrors": crate::session::keepalive::DEFAULT_TOLERATED_ERRORS,
71 "syncDeleteTimeoutSeconds": seconds(crate::workspace::DEFAULT_SYNC_DELETE_TIMEOUT),
72 "launch": {
73 "maxIdleSeconds": launch.max_idle_sec,
74 "suspendedSeconds": launch.suspended_sec,
75 "maxDurationSeconds": launch.max_duration_sec,
76 "autoResume": launch.auto_resume,
77 "egress": launch.egress,
78 "denyEgress": launch.deny_egress,
79 "identity": launch.identity,
80 "shell": launch.shell,
81 "wait": launch.wait,
82 "loggingDisabled": matches!(
85 launch.logging,
86 Some(crate::control::ops::Logging::Disabled { .. })
87 ),
88 },
89 "exec": {
90 "cwd": start.cwd,
91 "user": start.user,
92 "group": start.group,
93 "timeoutSeconds": start.timeout_sec,
94 "shell": matches!(start.shell, protocol::exec::Shell::Flag(true)),
95 "stdin": start.stdin,
96 "reapGroupOnExit": start.reap_group_on_exit,
97 "poweroffOnExit": start.poweroff_on_exit,
98 "inheritImageEnv": start.inherit_image_env,
99 },
100 "stream": {
101 "offset": stream.offset,
102 "reconnect": stream.reconnect,
103 "errorOnGap": stream.error_on_gap,
104 },
105 "teardown": {
106 "deleteImage": teardown.delete_image,
107 "deleteLogGroup": teardown.delete_log_group,
108 "waitForTerminated": teardown.wait_for_terminated.is_some(),
109 },
110 "prompt": {
111 "permissionMode": prompt.permission_mode.as_str(),
112 "execId": prompt.exec_id,
113 "timeoutSeconds": prompt.timeout.map(seconds),
114 "reapGroupOnExit": prompt.reap_group_on_exit,
115 },
116 "cost": {
117 "runningSeconds": plan.running_seconds,
118 "suspendedSeconds": plan.suspended_seconds,
119 "running": usage.running.map(crate::cost::DurationP::seconds_f64),
120 "suspended": usage.suspended.map(crate::cost::DurationP::seconds_f64),
121 "imageBuild": usage.image_build.map(crate::cost::DurationP::seconds_f64),
122 "imageGb": usage.image_gb,
123 "snapshotGb": usage.snapshot_gb,
124 },
125 "image": {
126 "workingDirectory": crate::control::artifact::BaseImage::al2023().working_dir,
127 "workdir": WrapOptions::default().workdir,
128 "inheritWorkdir": WrapOptions::default().inherit_workdir,
129 "repairGuestIdentity": image.repair_guest_identity,
130 "force": ensured.force,
131 },
132 })
133}
134
135fn seconds(duration: Duration) -> f64 {
137 duration.as_secs_f64()
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
151 fn the_client_defaults_are_cores_own_values() {
152 let defaults = client_defaults();
153 assert_eq!(
154 defaults["execWaitSeconds"],
155 DEFAULT_EXEC_WAIT.as_secs_f64(),
156 "execWaitSeconds"
157 );
158 assert_eq!(defaults["agentPort"], DEFAULT_AGENT_PORT);
159 assert_eq!(defaults["sizeMib"], SizeClass::DEFAULT.baseline_mib());
160 let launch = RunRequest::default();
161 assert_eq!(defaults["launch"]["maxIdleSeconds"], launch.max_idle_sec);
162 assert_eq!(defaults["launch"]["wait"], launch.wait);
163 let start = protocol::exec::StartRequest::new("x", vec!["true".into()]);
164 assert_eq!(defaults["exec"]["cwd"], json!(start.cwd));
165 assert_eq!(defaults["exec"]["user"], json!(start.user));
166 assert_eq!(defaults["exec"]["group"], json!(start.group));
167 assert_eq!(defaults["exec"]["timeoutSeconds"], json!(start.timeout_sec));
168 assert_eq!(defaults["exec"]["stdin"], start.stdin);
169 assert_eq!(defaults["exec"]["shell"], false);
170 assert_eq!(
171 defaults["stream"]["offset"],
172 StreamOptions::default().offset
173 );
174 assert_eq!(
175 defaults["prompt"]["permissionMode"],
176 PromptOptions::default().permission_mode.as_str()
177 );
178 assert_eq!(
179 defaults["image"]["workingDirectory"],
180 crate::control::artifact::BaseImage::al2023().working_dir
181 );
182 assert_eq!(
183 defaults["syncDeleteTimeoutSeconds"],
184 crate::workspace::DEFAULT_SYNC_DELETE_TIMEOUT.as_secs_f64()
185 );
186 }
187}