agent_block_core/embedded.rs
1//! The modules baked into the binary, as a list a caller outside this crate
2//! can read.
3//!
4//! The host needs the embedded sources as two tables keyed by `require` name —
5//! that is what [`crate::host`] holds them as, and it is the only shape the
6//! require registry wants. A tool that writes one of them to disk needs
7//! something else: the whole set, each entry knowing which directory it belongs
8//! in and what its source is. Exposing the host's constants would answer that
9//! by handing out the registry's internals; this module answers it with one
10//! list and nothing else.
11//!
12//! `agent-block vendor` is the caller. It expands an entry into a project's
13//! `.agent-block/` directory, which is the first tier both lookups search (see
14//! [`crate::host::lib_roots`] / [`crate::host::block_roots`]), so the copy is
15//! what the project resolves from then on.
16//!
17//! Two things are deliberately not here. `knl_types` is embedded but generated
18//! at start from the Rust syscall surface, so it has no static source to hand
19//! out — and it is sealed, so the only correct answer to a request for it is
20//! the refusal [`is_sealed`] produces. And nothing here writes: what a copy
21//! should say at the top of it, and where it may land, belong to the tool doing
22//! the writing, not to the list.
23
24use std::sync::OnceLock;
25
26use crate::host::{EMBEDDED_BLOCKS, EMBEDDED_LIBS, SEALED};
27
28/// Which of the host's two embedded lists an entry came from.
29///
30/// `Block` means the module is also reported by `inspect_tools` as a tool
31/// surface; both kinds are `require`d by name and neither is a file on disk, so
32/// **this does not say where a copy of one belongs**. A project's copy is always
33/// a module — `agent` is reached by `require("agent")` like the rest — and
34/// `blocks/` holds a project's own entry points, which the binary has none of.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Kind {
37 /// An entry point — `blocks/<name>.lua`.
38 Block,
39 /// A module — `lib/<name>/init.lua`.
40 Lib,
41}
42
43impl Kind {
44 /// The word a listing prints.
45 pub fn as_str(self) -> &'static str {
46 match self {
47 Kind::Block => "block",
48 Kind::Lib => "lib",
49 }
50 }
51}
52
53/// One embedded module: the name `require` resolves, which kind it is, and the
54/// Lua source compiled into the binary.
55///
56/// A sub-module carries its dotted name whole (`lshape.t`), the same string the
57/// require registry is keyed by, so a caller can tell a root from a part of one
58/// by looking for the dot.
59#[derive(Debug, Clone, Copy)]
60pub struct Entry {
61 /// The `require` name.
62 pub name: &'static str,
63 /// Entry point or module.
64 pub kind: Kind,
65 /// The source, verbatim.
66 pub source: &'static str,
67}
68
69/// Every embedded entry: the blocks first, then the modules, each in the order
70/// the binary lists them.
71pub fn entries() -> &'static [Entry] {
72 static ENTRIES: OnceLock<Vec<Entry>> = OnceLock::new();
73 ENTRIES
74 .get_or_init(|| {
75 let blocks = EMBEDDED_BLOCKS.iter().map(|(name, source)| Entry {
76 name,
77 kind: Kind::Block,
78 source,
79 });
80 let libs = EMBEDDED_LIBS.iter().map(|(name, source)| Entry {
81 name,
82 kind: Kind::Lib,
83 source,
84 });
85 blocks.chain(libs).collect()
86 })
87 .as_slice()
88}
89
90/// The entry named `name`, if the binary carries one.
91pub fn find(name: &str) -> Option<&'static Entry> {
92 entries().iter().find(|e| e.name == name)
93}
94
95/// Whether `name` is a module a project may not shadow — the kernel, its
96/// declaration layer, and the `lshape` those are written in.
97///
98/// True for a sub-module of a sealed root as well as for the names listed
99/// outright: sealing `lshape` and leaving `lshape.t` open would seal nothing,
100/// and the run-time check ([`crate::host`]) reads the same list the same way.
101pub fn is_sealed(name: &str) -> bool {
102 let root = name.split('.').next().unwrap_or(name);
103 SEALED.iter().any(|s| *s == name || *s == root)
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 /// The list is the two tables and nothing else, in that order — a caller
111 /// printing it is printing what the binary carries.
112 #[test]
113 fn the_entries_are_the_blocks_then_the_modules() {
114 let names: Vec<&str> = entries().iter().map(|e| e.name).collect();
115 assert_eq!(&names[..2], ["agent", "coding"]);
116 assert!(names.contains(&"lshape.t"), "{names:?}");
117
118 let blocks = entries().iter().filter(|e| e.kind == Kind::Block).count();
119 assert_eq!(blocks, EMBEDDED_BLOCKS.len());
120 assert_eq!(entries().len(), EMBEDDED_BLOCKS.len() + EMBEDDED_LIBS.len());
121 }
122
123 /// Every entry hands back the source that is compiled in, not a name to go
124 /// looking for.
125 #[test]
126 fn an_entry_carries_its_source() {
127 let session = find("session").expect("session is embedded");
128 assert_eq!(session.kind, Kind::Lib);
129 assert!(session.source.contains("return M"), "{}", session.source);
130 assert!(find("no_such_module").is_none());
131 }
132
133 /// A sub-module of a sealed root is sealed, whether or not it is spelled
134 /// out in the list.
135 #[test]
136 fn the_seal_covers_a_root_and_its_parts() {
137 assert!(is_sealed("knl"));
138 assert!(is_sealed("knl_types"));
139 assert!(is_sealed("lshape"));
140 assert!(is_sealed("lshape.t"));
141 assert!(is_sealed("lshape.whatever_comes_next"));
142 assert!(!is_sealed("agent"));
143 assert!(!is_sealed("llm_proto.openai"));
144 }
145}