whiskers.git / crates / whiskersd / src / native.rs

The native backend: every port, as files and calls on this machine.

3use std::path::PathBuf;
5use log::error;
6use whiskers_ports::{
7    HasAllowance, HasChat, HasClock, HasEmbedder, HasHousehold, HasIcons, HasJournal, HasJudge, HasMemory, HasPictures, HasThinker, HasVoice,
8};
9
10use crate::allowance::FileAllowance;
11use crate::clock::SystemClock;
12use crate::embed::LlamaEmbedder;
13use crate::hubs::{FileChatHub, FileHouseholdHub, FileMemoryHub};
14use crate::journal::FileJournal;
15use crate::judge::JevJudge;
16use crate::model::GatewayThinker;
17use crate::pictures::DirShelf;
18use crate::secrets::EnvSecrets;
19use crate::speak::ElevenLabsVoice;

Why the service could not start. The process exits with exit_code.

22#[derive(Debug)]
23pub enum StartError {

A setting is missing or wrong: the operator has something to fix. Exit code 2.

25    Configuration(String),

What is stored cannot be opened. Exit code 1.

27    Storage(String),
28}
30impl StartError {
31    pub fn exit_code(&self) -> i32 {
32        match self {
33            StartError::Configuration(_) => 2,
34            StartError::Storage(_) => 1,
35        }
36    }
37}
38
39impl std::fmt::Display for StartError {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            StartError::Configuration(m) | StartError::Storage(m) => f.write_str(m),
43        }
44    }
45}
46
47pub struct Native {
48    memory: FileMemoryHub,
49    household: FileHouseholdHub,
50    chat: FileChatHub,
51    journal: FileJournal,
52    pictures: DirShelf,
53    allowance: FileAllowance,
54    judge: JevJudge,
55    embedder: LlamaEmbedder,
56    icons: whiskers_ports::IconVectors,
57    voice: ElevenLabsVoice<EnvSecrets>,
58    thinker: GatewayThinker,
59    clock: SystemClock,
60}

Where the hubs keep their documents: $HOME/.local/share/whiskers.

63pub fn hub_dir() -> PathBuf {
64    let dir = std::env::var_os("HOME").map(PathBuf::from).unwrap_or_else(|| ".".into()).join(".local/share/whiskers");
65    if let Err(e) = std::fs::create_dir_all(&dir) {
66        error!("cannot create the data directory {}: {e}", dir.display());
67    }
68    dir
69}
71impl Native {

Everything the service needs, from the environment and the data directory.

73    pub fn from_env() -> Result<Self, StartError> {
74        let secrets = EnvSecrets::process();
75        let judge = JevJudge::start(&secrets);
76        let voice = ElevenLabsVoice::new(secrets);
77        whiskers_ports::run_ready(voice.report_configuration());
78        let allowance = FileAllowance::from_env();
79        let thinker = GatewayThinker::from_env().map_err(|e| {
80            error!("cannot start: {e}");
81            StartError::Configuration(e)
82        })?;
83        let embedder = LlamaEmbedder::from_env();
84        let dir = hub_dir();
85        let memory = FileMemoryHub::open(&dir.join("memory-hub.json")).map_err(|e| StartError::Storage(format!("cannot open the shared memory: {e}")))?;
86        Ok(Self {
87            memory,
88            household: FileHouseholdHub::open(&dir.join("household-hub.json")),
89            chat: FileChatHub::open(&dir.join("chat-hub.json")),
90            journal: FileJournal::open(&dir.join("journal")),
91            pictures: DirShelf::open(&dir.join("pictures")),
92            allowance,
93            judge,
94            embedder,
95            icons: whiskers_ports::IconVectors::default(),
96            voice,
97            thinker,
98            clock: SystemClock,
99        })
100    }
101}

Every capability: the native binary is a complete Backend, and Service::complete holds it to that.

104impl HasMemory for Native {
105    type Memory = FileMemoryHub;
106    fn memory(&self) -> &FileMemoryHub {
107        &self.memory
108    }
109}
110impl HasHousehold for Native {
111    type Household = FileHouseholdHub;
112    fn household(&self) -> &FileHouseholdHub {
113        &self.household
114    }
115}
116impl HasChat for Native {
117    type Chat = FileChatHub;
118    fn chat(&self) -> &FileChatHub {
119        &self.chat
120    }
121}
122impl HasJournal for Native {
123    type Journal = FileJournal;
124    fn journal(&self) -> &FileJournal {
125        &self.journal
126    }
127}
128impl HasPictures for Native {
129    type Pictures = DirShelf;
130    fn pictures(&self) -> &DirShelf {
131        &self.pictures
132    }
133}
134impl HasAllowance for Native {
135    type Allowance = FileAllowance;
136    fn allowance(&self) -> &FileAllowance {
137        &self.allowance
138    }
139}
140impl HasJudge for Native {
141    type Judge = JevJudge;
142    fn judge(&self) -> &JevJudge {
143        &self.judge
144    }
145}
146impl HasEmbedder for Native {
147    type Embedder = LlamaEmbedder;
148    fn embedder(&self) -> &LlamaEmbedder {
149        &self.embedder
150    }
151}
152impl HasIcons for Native {
153    fn icon_vectors(&self) -> &whiskers_ports::IconVectors {
154        &self.icons
155    }
156}
157impl HasVoice for Native {
158    type Voice = ElevenLabsVoice<EnvSecrets>;
159    fn voice(&self) -> &ElevenLabsVoice<EnvSecrets> {
160        &self.voice
161    }
162}
163impl HasThinker for Native {
164    type Thinker = GatewayThinker;
165    fn thinker(&self) -> &GatewayThinker {
166        &self.thinker
167    }
168}
169impl HasClock for Native {
170    type Clock = SystemClock;
171    fn clock(&self) -> &SystemClock {
172        &self.clock
173    }
174}