kaish_types/kernel.rs
1//! Kernel-level execution options.
2//!
3//! `ExecuteOptions` is the input to a single kernel `execute` call. It collects
4//! the per-call knobs (variables, timeout, cancellation) so embedders don't need
5//! to manage half a dozen execute-method overloads.
6
7use std::collections::{BTreeMap, HashMap};
8use std::path::PathBuf;
9use std::time::Duration;
10
11use tokio_util::sync::CancellationToken;
12
13use crate::value::Value;
14
15/// Per-call options for `Kernel::execute_with_options`.
16///
17/// Construct with `ExecuteOptions::new()` and the chainable `with_*` builders,
18/// or via `Default`.
19///
20/// # Cancellation vs. timeout — embedder note
21///
22/// If a `cancel_token` is supplied, it is **raced** against the kernel's
23/// internal token. The kernel does NOT cancel the embedder's token on its
24/// own timeouts — it cancels its internal token and returns exit code 124.
25/// So `your_token.is_cancelled()` after the call returns reflects only
26/// whether *you* (or someone sharing your token) cancelled, not whether the
27/// kernel timed out. Distinguish via the returned `ExecResult.code`:
28/// `124` = kernel timeout, `130` = cancellation (Ctrl-C / `Kernel::cancel`).
29#[derive(Default, Clone)]
30pub struct ExecuteOptions {
31 /// Variables exported into this call's environment (per-call overlay).
32 pub vars: HashMap<String, Value>,
33 /// Per-call timeout. Overrides `KernelConfig::request_timeout`.
34 ///
35 /// `None` means no timeout (or whatever the kernel-config default is).
36 /// `Some(Duration::ZERO)` returns exit 124 immediately without spawning
37 /// anything — useful for tests and dry-run paths.
38 /// Any other `Some(d)` lets the kernel run for at most `d` before cancelling
39 /// (which kills external children with the configured grace) and returning 124.
40 pub timeout: Option<Duration>,
41 /// Optional externally-owned cancellation token, *raced* against the kernel's
42 /// internal token. Either firing cancels the request and kills any running
43 /// external children. The kernel does not store this token in its own state —
44 /// it's a per-call read-only input, so embedders are free to drop or reuse
45 /// the original token after the call returns. CancellationToken is internally
46 /// `Arc`-shared, so `clone()` it into the builder if you want to keep your
47 /// original handle.
48 pub cancel_token: Option<CancellationToken>,
49 /// Per-call working directory override.
50 ///
51 /// When `Some(path)`, the kernel runs this call as if `cd path` happened
52 /// first, then restores the prior cwd on return. Useful for embedders that
53 /// run scripts in workspace contexts (notebook cells, per-tool dirs)
54 /// without polluting the long-lived kernel's cwd.
55 pub cwd: Option<PathBuf>,
56 /// W3C `traceparent` of the embedder's active span, e.g.
57 /// `"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"`. When set,
58 /// the kernel's execution span parents onto it, so kaish's spans appear as
59 /// children of the embedder's trace rather than as orphan roots.
60 pub traceparent: Option<String>,
61 /// W3C `tracestate` (vendor-specific list) that rides alongside
62 /// `traceparent`. Per the W3C spec, `tracestate` is meaningless without a
63 /// `traceparent`, so the kernel ignores it unless `traceparent` is also set.
64 pub tracestate: Option<String>,
65 /// W3C baggage — cross-cutting identifiers (owner, connection, tenant, …)
66 /// the embedder wants stamped onto the trace. Propagated to every child
67 /// span. Independent of `traceparent`: baggage with no trace context starts
68 /// a fresh root that still carries the identifiers.
69 pub baggage: BTreeMap<String, String>,
70 /// Standard input for this call, consumed by the first top-level command
71 /// that reads stdin (shell semantics — a later reader sees nothing).
72 ///
73 /// Lets a non-interactive frontend feed piped input, e.g.
74 /// `printf '…' | kaish -c 'sort'`. Without it a bare top-level builtin
75 /// reading stdin has no input source and silently produces nothing.
76 /// Bytes-typed (GH #176) to match `ExecContext::set_stdin`: a byte-aware
77 /// builtin (`wc -c`, `cat`, `cmp`, `checksum`, …) sees binary content
78 /// exactly, while a text-only builtin (`grep`, `sed`, …) still refuses it
79 /// loudly at the point it asks for text (`read_stdin_to_text`), not here.
80 /// Embedders that already hold a complete buffer use this; one that wants
81 /// to avoid pre-draining an open process stdin should prefer
82 /// `Kernel::execute_with_pipe_stdin` instead.
83 pub stdin: Option<Vec<u8>>,
84 /// Polled interrupt check, for embedders whose thread cannot fire
85 /// `cancel_token` while execution runs — the motivating case is
86 /// single-threaded wasm, where the browser's main thread flips a
87 /// SharedArrayBuffer flag and this closure reads it. The kernel polls at
88 /// its cancellation checkpoints (each loop iteration, among others) and,
89 /// on `true`, fires its internal cancel — the same exit-130 path as
90 /// `Kernel::cancel()`. Keep the closure cheap: it runs on the hot path.
91 /// `None` (the default) polls nothing.
92 pub interrupt: Option<std::sync::Arc<dyn Fn() -> bool + Send + Sync>>,
93}
94
95impl ExecuteOptions {
96 pub fn new() -> Self {
97 Self::default()
98 }
99
100 /// Replace the entire vars overlay with the given map.
101 pub fn with_vars(mut self, vars: HashMap<String, Value>) -> Self {
102 self.vars = vars;
103 self
104 }
105
106 /// Add a single variable to the overlay (extending; last write wins).
107 pub fn with_var(mut self, name: impl Into<String>, value: Value) -> Self {
108 self.vars.insert(name.into(), value);
109 self
110 }
111
112 /// Install a polled interrupt check (see the `interrupt` field).
113 pub fn with_interrupt(
114 mut self,
115 check: std::sync::Arc<dyn Fn() -> bool + Send + Sync>,
116 ) -> Self {
117 self.interrupt = Some(check);
118 self
119 }
120
121 pub fn with_timeout(mut self, timeout: Duration) -> Self {
122 self.timeout = Some(timeout);
123 self
124 }
125
126 pub fn with_cancel_token(mut self, token: CancellationToken) -> Self {
127 self.cancel_token = Some(token);
128 self
129 }
130
131 /// Run this call as if `cd path` had happened first; the prior cwd is
132 /// restored on return.
133 pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
134 self.cwd = Some(cwd);
135 self
136 }
137
138 /// Set the W3C `traceparent` the kernel's execution span should parent onto.
139 pub fn with_traceparent(mut self, traceparent: impl Into<String>) -> Self {
140 self.traceparent = Some(traceparent.into());
141 self
142 }
143
144 /// Set the W3C `tracestate` that rides alongside `traceparent`. Ignored by
145 /// the kernel unless a `traceparent` is also present.
146 pub fn with_tracestate(mut self, tracestate: impl Into<String>) -> Self {
147 self.tracestate = Some(tracestate.into());
148 self
149 }
150
151 /// Replace the entire baggage map with the given identifiers.
152 pub fn with_baggage(mut self, baggage: BTreeMap<String, String>) -> Self {
153 self.baggage = baggage;
154 self
155 }
156
157 /// Add a single baggage identifier (extending; last write wins).
158 pub fn with_baggage_entry(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
159 self.baggage.insert(key.into(), value.into());
160 self
161 }
162
163 /// Set the standard input fed to this call's first stdin-reading command.
164 ///
165 /// Accepts anything `Into<Vec<u8>>` — a `&str`/`String` (the common text
166 /// case) or a raw `Vec<u8>` (binary, GH #176) both work.
167 pub fn with_stdin(mut self, stdin: impl Into<Vec<u8>>) -> Self {
168 self.stdin = Some(stdin.into());
169 self
170 }
171}