1pub mod contracts;
17pub mod host_capabilities;
18pub mod llm_options;
19pub mod runtime_type_tags;
20pub mod shapes;
21pub mod signatures;
22
23pub use contracts::{
24 wire_identifier_key, BuiltinContract, BuiltinExposure, CapabilityId, EffectAccess,
25 EffectAuthorization, EffectKind, EffectSpec, ResourceSelector,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct BuiltinSignature {
33 pub name: &'static str,
35 pub params: &'static [Param],
39 pub returns: Ty,
42 pub type_params: &'static [&'static str],
45 pub has_rest: bool,
49 pub where_clauses: &'static [(&'static str, &'static str)],
52 pub projection: Option<RecordProjection>,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum RecordProjection {
59 Pick { source: usize, keys: usize },
60}
61
62impl BuiltinSignature {
63 pub const fn with_name(self, name: &'static str) -> Self {
65 Self { name, ..self }
66 }
67
68 pub const fn with_projection(self, projection: RecordProjection) -> Self {
69 Self {
70 projection: Some(projection),
71 ..self
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct Param {
79 pub name: &'static str,
80 pub ty: Ty,
81 pub optional: bool,
84}
85
86impl Param {
87 pub const fn new(name: &'static str, ty: Ty) -> Self {
88 Self {
89 name,
90 ty,
91 optional: false,
92 }
93 }
94
95 pub const fn optional(name: &'static str, ty: Ty) -> Self {
96 Self {
97 name,
98 ty,
99 optional: true,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum Ty {
110 Named(&'static str),
114 Generic(&'static str),
117 Any,
120 Optional(&'static Ty),
122 Apply(&'static str, &'static [Ty]),
126 Union(&'static [Ty]),
129 Fn(&'static [Ty], &'static Ty),
132 Shape(&'static [ShapeFieldDescriptor]),
135 OpenShape(&'static [ShapeFieldDescriptor], &'static [Ty]),
144 SchemaOf(&'static str),
148 Never,
150 LitInt(i64),
152 LitString(&'static str),
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub struct ShapeFieldDescriptor {
158 pub name: &'static str,
159 pub ty: Ty,
160 pub optional: bool,
161}
162
163impl ShapeFieldDescriptor {
164 pub const fn new(name: &'static str, ty: Ty) -> Self {
165 Self {
166 name,
167 ty,
168 optional: false,
169 }
170 }
171
172 pub const fn optional(name: &'static str, ty: Ty) -> Self {
173 Self {
174 name,
175 ty,
176 optional: true,
177 }
178 }
179}
180
181impl Ty {
182 pub const fn is_any(&self) -> bool {
184 matches!(self, Ty::Any)
185 }
186}
187
188fn write_shape_members(
194 f: &mut core::fmt::Formatter<'_>,
195 fields: &[ShapeFieldDescriptor],
196 rests: &[Ty],
197) -> core::fmt::Result {
198 for (i, fld) in fields.iter().enumerate() {
199 if i > 0 {
200 f.write_str(", ")?;
201 }
202 let name = fld.name;
203 let ty = &fld.ty;
204 let optional = if fld.optional { "?" } else { "" };
205 write!(f, "{name}{optional}: {ty}")?;
206 }
207 for (i, rest) in rests.iter().enumerate() {
208 if i > 0 || !fields.is_empty() {
209 f.write_str(", ")?;
210 }
211 write!(f, "...{rest}")?;
212 }
213 Ok(())
214}
215
216impl core::fmt::Display for Ty {
217 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
223 match self {
224 Ty::Named(s) | Ty::Generic(s) => f.write_str(s),
225 Ty::Any => f.write_str("any"),
226 Ty::Never => f.write_str("never"),
227 Ty::Optional(inner) => write!(f, "{inner}?"),
228 Ty::Apply(name, args) => {
229 f.write_str(name)?;
230 f.write_str("<")?;
231 for (i, a) in args.iter().enumerate() {
232 if i > 0 {
233 f.write_str(", ")?;
234 }
235 write!(f, "{a}")?;
236 }
237 f.write_str(">")
238 }
239 Ty::Union(parts) => {
240 if let [inner, Ty::Named("nil")] = parts {
244 if !matches!(inner, Ty::Named("nil")) {
245 return write!(f, "{inner}?");
246 }
247 }
248 if let [Ty::Named("int"), Ty::Named("float")] = parts {
249 return f.write_str("number");
250 }
251 for (i, p) in parts.iter().enumerate() {
252 if i > 0 {
253 f.write_str(" | ")?;
254 }
255 write!(f, "{p}")?;
256 }
257 Ok(())
258 }
259 Ty::Fn(params, ret) => {
260 f.write_str("(")?;
261 for (i, p) in params.iter().enumerate() {
262 if i > 0 {
263 f.write_str(", ")?;
264 }
265 write!(f, "{p}")?;
266 }
267 write!(f, ") -> {ret}")
268 }
269 Ty::Shape(fields) => {
270 f.write_str("{")?;
271 write_shape_members(f, fields, &[])?;
272 f.write_str("}")
273 }
274 Ty::OpenShape(fields, rests) => {
275 f.write_str("{")?;
276 write_shape_members(f, fields, rests)?;
277 f.write_str("}")
278 }
279 Ty::SchemaOf(t) => write!(f, "Schema<{t}>"),
280 Ty::LitInt(n) => write!(f, "{n}"),
281 Ty::LitString(s) => write!(f, "\"{s}\""),
282 }
283 }
284}
285
286impl BuiltinSignature {
287 pub const fn simple(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
291 Self {
292 name,
293 params,
294 returns,
295 type_params: &[],
296 has_rest: false,
297 where_clauses: &[],
298 projection: None,
299 }
300 }
301
302 pub const fn variadic(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
305 Self {
306 name,
307 params,
308 returns,
309 type_params: &[],
310 has_rest: true,
311 where_clauses: &[],
312 projection: None,
313 }
314 }
315
316 pub const fn generic(
320 name: &'static str,
321 type_params: &'static [&'static str],
322 params: &'static [Param],
323 returns: Ty,
324 ) -> Self {
325 Self {
326 name,
327 params,
328 returns,
329 type_params,
330 has_rest: false,
331 where_clauses: &[],
332 projection: None,
333 }
334 }
335
336 pub fn required_params(&self) -> usize {
338 self.params.iter().filter(|p| !p.optional).count()
339 }
340
341 pub fn is_type_param(&self, name: &str) -> bool {
344 self.type_params.contains(&name)
345 }
346
347 pub fn is_generic(&self) -> bool {
349 !self.type_params.is_empty()
350 }
351
352 pub fn type_param_names(&self) -> Vec<String> {
356 self.type_params.iter().map(|s| (*s).to_string()).collect()
357 }
358
359 pub fn where_clause_strings(&self) -> Vec<(String, String)> {
361 self.where_clauses
362 .iter()
363 .map(|(tp, iface)| ((*tp).to_string(), (*iface).to_string()))
364 .collect()
365 }
366}
367
368impl core::fmt::Display for BuiltinSignature {
369 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
374 if !self.type_params.is_empty() {
375 f.write_str("<")?;
376 for (i, tp) in self.type_params.iter().enumerate() {
377 if i > 0 {
378 f.write_str(", ")?;
379 }
380 f.write_str(tp)?;
381 }
382 if !self.where_clauses.is_empty() {
383 f.write_str(" where ")?;
384 for (i, (tp, iface)) in self.where_clauses.iter().enumerate() {
385 if i > 0 {
386 f.write_str(", ")?;
387 }
388 write!(f, "{tp}: {iface}")?;
389 }
390 }
391 f.write_str("> ")?;
392 }
393 f.write_str(self.name)?;
394 f.write_str("(")?;
395 let last_idx = self.params.len().saturating_sub(1);
396 for (i, p) in self.params.iter().enumerate() {
397 if i > 0 {
398 f.write_str(", ")?;
399 }
400 if self.has_rest && i == last_idx {
401 f.write_str("...")?;
402 }
403 f.write_str(p.name)?;
404 if p.optional {
405 f.write_str("?")?;
406 }
407 let ty = &p.ty;
408 write!(f, ": {ty}")?;
409 }
410 let ret = &self.returns;
411 write!(f, ") -> {ret}")
412 }
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub struct BuiltinMetadata {
419 pub name: &'static str,
420 pub return_types: &'static [&'static str],
421}
422
423pub const TY_ANY: Ty = Ty::Any;
430pub const TY_BOOL: Ty = Ty::Named("bool");
431pub const TY_BYTES: Ty = Ty::Named("bytes");
432pub const TY_CLOSURE: Ty = Ty::Named("closure");
433pub const TY_DECIMAL: Ty = Ty::Named("decimal");
434pub const TY_DICT: Ty = Ty::Named("dict");
435pub const TY_DURATION: Ty = Ty::Named("duration");
436pub const TY_FLOAT: Ty = Ty::Named("float");
437pub const TY_INT: Ty = Ty::Named("int");
438pub const TY_LIST: Ty = Ty::Named("list");
439pub const TY_NEVER: Ty = Ty::Never;
440pub const TY_NIL: Ty = Ty::Named("nil");
441pub const TY_RESOURCE: Ty = Ty::Named("resource");
442pub const TY_STRING: Ty = Ty::Named("string");
443
444pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
446pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
448pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
450pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
452pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 const APPLY_ARGS: &[Ty] = &[TY_DICT];
460 const FN_PARAMS: &[Ty] = &[TY_INT, TY_STRING];
461 const SHAPE_FIELDS: &[ShapeFieldDescriptor] = &[
462 ShapeFieldDescriptor::new("name", TY_STRING),
463 ShapeFieldDescriptor::optional("age", TY_INT),
464 ];
465
466 const OPEN_SHAPE_RESTS: &[Ty] = &[TY_DICT];
467
468 #[test]
469 fn wire_identifier_keys_stay_unique_across_capabilities() {
470 let mut seen = std::collections::HashMap::new();
475 for capability in CapabilityId::ALL {
476 let key = wire_identifier_key(capability.field_name());
477 if let Some(other) = seen.insert(key.clone(), capability.field_name()) {
478 panic!(
479 "`{other}` and `{}` both normalize to `{key}`",
480 capability.field_name()
481 );
482 }
483 }
484 }
485
486 #[test]
487 fn host_namespace_resolves_the_separatorless_spelling() {
488 assert_eq!(
489 CapabilityId::from_host_namespace("prmonitor"),
490 Some(CapabilityId::PrMonitor)
491 );
492 assert_eq!(
493 CapabilityId::from_host_namespace("PrMonitor"),
494 Some(CapabilityId::PrMonitor)
495 );
496 assert_eq!(
497 CapabilityId::from_host_namespace("pr_monitor"),
498 Some(CapabilityId::PrMonitor)
499 );
500 assert_eq!(CapabilityId::from_host_namespace("not_a_capability"), None);
501 assert_eq!(CapabilityId::from_field_name("prmonitor"), None);
504 }
505
506 #[test]
507 fn ty_display_atomic_and_compound() {
508 assert_eq!(format!("{TY_INT}"), "int");
509 assert_eq!(format!("{TY_ANY}"), "any");
510 assert_eq!(format!("{TY_NEVER}"), "never");
511 assert_eq!(format!("{TY_STRING_OR_NIL}"), "string?");
514 let opt_int = Ty::Optional(&TY_INT);
515 assert_eq!(format!("{opt_int}"), "int?");
516 assert_eq!(format!("{TY_NUMBER}"), "number");
518 let list_dict = Ty::Apply("list", APPLY_ARGS);
519 assert_eq!(format!("{list_dict}"), "list<dict>");
520 let lit_int = Ty::LitInt(42);
521 assert_eq!(format!("{lit_int}"), "42");
522 let lit_str = Ty::LitString("pass");
523 assert_eq!(format!("{lit_str}"), "\"pass\"");
524 let schema_t = Ty::SchemaOf("T");
525 assert_eq!(format!("{schema_t}"), "Schema<T>");
526 let fn_ty = Ty::Fn(FN_PARAMS, &TY_BOOL);
527 assert_eq!(format!("{fn_ty}"), "(int, string) -> bool");
528 let shape = Ty::Shape(SHAPE_FIELDS);
529 assert_eq!(format!("{shape}"), "{name: string, age?: int}");
534 let open = Ty::OpenShape(SHAPE_FIELDS, OPEN_SHAPE_RESTS);
535 assert_eq!(format!("{open}"), "{name: string, age?: int, ...dict}");
536 let tail_only = Ty::OpenShape(&[], OPEN_SHAPE_RESTS);
537 assert_eq!(format!("{tail_only}"), "{...dict}");
538 }
539
540 const BASIC_PARAMS: &[Param] = &[Param::new("a", TY_DICT), Param::new("b", TY_DICT)];
541 const REST_PARAMS: &[Param] = &[Param::new("prefix", TY_STRING), Param::new("args", TY_ANY)];
542 const OPT_PARAMS: &[Param] = &[
543 Param::new("receipt", TY_DICT),
544 Param::optional("candidate", TY_ANY),
545 ];
546 const GENERIC_PARAMS: &[Param] = &[Param::new("schema", Ty::SchemaOf("T"))];
547
548 #[test]
549 fn signature_display_basic() {
550 let sig = BuiltinSignature::simple("deep_merge", BASIC_PARAMS, TY_DICT);
551 assert_eq!(format!("{sig}"), "deep_merge(a: dict, b: dict) -> dict");
552 }
553
554 #[test]
555 fn signature_display_with_optional_and_rest() {
556 let sig = BuiltinSignature {
557 name: "io_println",
558 params: REST_PARAMS,
559 returns: TY_NIL,
560 type_params: &[],
561 has_rest: true,
562 where_clauses: &[],
563 projection: None,
564 };
565 assert_eq!(
566 format!("{sig}"),
567 "io_println(prefix: string, ...args: any) -> nil"
568 );
569
570 let opt_sig =
571 BuiltinSignature::simple("lifecycle_replay_resume_input", OPT_PARAMS, TY_DICT);
572 assert_eq!(
573 format!("{opt_sig}"),
574 "lifecycle_replay_resume_input(receipt: dict, candidate?: any) -> dict"
575 );
576 }
577
578 #[test]
579 fn signature_display_with_generics_and_where() {
580 let sig = BuiltinSignature {
581 name: "schema_parse",
582 params: GENERIC_PARAMS,
583 returns: Ty::Generic("T"),
584 type_params: &["T"],
585 has_rest: false,
586 where_clauses: &[("T", "Decode")],
587 projection: None,
588 };
589 assert_eq!(
590 format!("{sig}"),
591 "<T where T: Decode> schema_parse(schema: Schema<T>) -> T"
592 );
593 }
594}