Skip to main content

pamin_kit/
module.rs

1//! What a module is to the kit: a manifest that describes it, the operations
2//! it registers and the rule its name follows.
3
4use std::fmt;
5
6use serde::{Deserialize, Serialize};
7
8use crate::ops::Ops;
9
10/// A module: an engine plus its operation contract, served by the kit.
11pub trait Module: Send + Sync + Sized + 'static {
12    /// How hosts route to, scale and present this module.
13    fn manifest(&self) -> Manifest;
14
15    /// Registers every operation, each with the feature it needs.
16    fn register(&self, ops: &mut Ops<Self>);
17}
18
19/// What a module says about itself, so that gateways and hosts never branch
20/// on its name.
21///
22/// A module declares it with `&'static str`s; a host reads one back from its
23/// JSON as `Manifest<String, ModuleName>`, which refuses a name that breaks
24/// the rule.
25#[derive(Clone, Debug, Serialize, Deserialize)]
26pub struct Manifest<S = &'static str, N = S> {
27    pub name: N,
28    /// The version of the operation contract, not of the crate.
29    pub contract_version: S,
30    pub routing: Routing,
31    /// Whether operations may run long enough to need a task.
32    pub long_running: bool,
33    /// The OAuth scopes a caller needs.
34    pub scopes: Vec<S>,
35    /// What one metered call counts as, for example `call`.
36    pub metering_unit: S,
37    pub hardware: Hardware,
38    pub audience: Audience,
39}
40
41/// How a gateway spreads calls over instances.
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum Routing {
45    /// One instance owns a project's data; calls for it go there.
46    ShardedByProject,
47    /// Any instance serves any call.
48    Stateless,
49    /// Instances on GPU machines, shared by every caller.
50    GpuPool,
51}
52
53/// What one instance needs to run.
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
55pub struct Hardware {
56    /// Resident memory one instance needs, in MiB.
57    pub memory_mib: u32,
58    pub gpu: bool,
59}
60
61/// Who a module's tools are for.
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum Audience {
65    /// Listed to the model, which decides when to call them.
66    #[default]
67    Model,
68    /// Called by a host itself and never listed to a model, such as moderation.
69    Host,
70}
71
72/// A module's name: a lowercase ASCII letter, then lowercase ASCII letters and
73/// digits. Its tools, prompts, resources and app pages are named after it, and
74/// the conformance suite holds a module to it; a host reading a manifest back
75/// parses its name into this, so the rule is written once.
76#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
77#[serde(try_from = "String")]
78pub struct ModuleName(String);
79
80/// A name [`ModuleName`] refused, as given.
81#[derive(Debug)]
82pub struct BadModuleName(String);
83
84impl ModuleName {
85    pub fn as_str(&self) -> &str {
86        &self.0
87    }
88}
89
90impl TryFrom<String> for ModuleName {
91    type Error = BadModuleName;
92
93    fn try_from(name: String) -> Result<Self, Self::Error> {
94        if name.starts_with(|c: char| c.is_ascii_lowercase()) && name.chars().all(name_char) {
95            Ok(Self(name))
96        } else {
97            Err(BadModuleName(name))
98        }
99    }
100}
101
102/// Written as the plain string it is read from, in every format.
103impl Serialize for ModuleName {
104    fn serialize<Z: serde::Serializer>(&self, serializer: Z) -> Result<Z::Ok, Z::Error> {
105        serializer.serialize_str(&self.0)
106    }
107}
108
109impl fmt::Display for BadModuleName {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(
112            f,
113            "module name {:?} is not a lowercase letter, then lowercase letters and digits",
114            self.0
115        )
116    }
117}
118
119impl std::error::Error for BadModuleName {}
120
121/// Whether `c` may appear in a module's name, and so in its operations' names.
122pub(crate) fn name_char(c: char) -> bool {
123    c.is_ascii_lowercase() || c.is_ascii_digit()
124}