Sebas Tin angelegt und Synology Chat connected

This commit is contained in:
Sithies
2026-03-16 21:13:37 +01:00
parent f686c4c6e2
commit 204b2d6eb1
19 changed files with 907 additions and 3 deletions
+22
View File
@@ -0,0 +1,22 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NazarickError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Config error: {0}")]
Config(String),
#[error("Agent error: {0}")]
Agent(String),
#[error("Memory error: {0}")]
Memory(String),
#[error("Skill error: {0}")]
Skill(String),
#[error("Api error: {0}")]
Api(String),
}
+32
View File
@@ -0,0 +1,32 @@
use crate::types::AgentId;
#[derive(Debug, Clone, PartialEq)]
pub enum Permission {
// LLM
LlmAccess,
// Image
ImageGeneration,
// Skills
SkillFileRead,
SkillFileWrite,
SkillWebSearch,
// Channels
ChannelSynology,
ChannelWebUi,
}
#[derive(Debug, Clone)]
pub struct AgentPermissions {
pub agent_id: AgentId,
pub allowed: Vec<Permission>,
}
impl AgentPermissions {
pub fn new(agent_id: AgentId, allowed: Vec<Permission>) -> Self {
Self { agent_id, allowed }
}
pub fn has(&self, permission: &Permission) -> bool {
self.allowed.contains(permission)
}
}
+60
View File
@@ -0,0 +1,60 @@
use crate::types::Result;
use crate::error::NazarickError;
/// Verantwortlich für das Zusammensetzen des System-Prompts.
/// Liest soul_core.md und soul_personality.md und kombiniert
/// sie in der richtigen Reihenfolge.
///
/// Reihenfolge ist bewusst gewählt:
/// 1. soul_core.md IMMER zuerst — Kernregeln haben höchste Priorität
/// 2. soul_personality.md danach — Ton und Stil
/// So kann soul_personality niemals soul_core überschreiben.
pub struct PromptBuilder {
/// Pfad zu soul_core.md — unveränderliche Kernregeln
soul_core_path: String,
/// Pfad zu soul_personality.md — entwickelbarer Persönlichkeitsteil
soul_personality_path: String,
}
impl PromptBuilder {
/// Erstellt einen neuen PromptBuilder mit den angegebenen Dateipfaden.
pub fn new(
soul_core_path: impl Into<String>,
soul_personality_path: impl Into<String>,
) -> Self {
Self {
soul_core_path: soul_core_path.into(),
soul_personality_path: soul_personality_path.into(),
}
}
/// Liest beide soul-Dateien und kombiniert sie zum finalen System-Prompt.
/// Fehlt soul_personality.md wird nur soul_core.md verwendet —
/// das System bleibt funktionsfähig auch ohne Persönlichkeitsdatei.
pub fn build(&self) -> Result<String> {
// soul_core.md ist Pflicht — ohne Kernregeln kein Start
let core = std::fs::read_to_string(&self.soul_core_path)
.map_err(|e| NazarickError::Config(
format!("soul_core.md nicht gefunden unter '{}': {}",
self.soul_core_path, e)
))?;
// soul_personality.md ist optional — graceful fallback
let personality = match std::fs::read_to_string(&self.soul_personality_path) {
Ok(content) => content,
Err(_) => {
// Kein Fehler — leere Persönlichkeit ist valid beim ersten Start
String::new()
}
};
// Zusammensetzen: Core immer zuerst
let system_prompt = if personality.is_empty() {
core
} else {
format!("{}\n\n---\n\n{}", core, personality)
};
Ok(system_prompt)
}
}
+16
View File
@@ -0,0 +1,16 @@
use crate::types::{AgentId, SkillId, MemoryId, Result};
pub trait Agent: Send + Sync {
fn id(&self) -> AgentId;
fn name(&self) -> &str;
}
pub trait Skill: Send + Sync {
fn id(&self) -> &SkillId;
fn name(&self) -> &str;
}
pub trait MemoryStore: Send + Sync {
fn store(&self, content: &str) -> Result<MemoryId>;
fn retrieve(&self, id: &MemoryId) -> Result<Option<String>>;
}
+7
View File
@@ -0,0 +1,7 @@
use uuid::Uuid;
use crate::error::NazarickError;
pub type Result<T> = std::result::Result<T, NazarickError>;
pub type AgentId = Uuid;
pub type SkillId = String;
pub type MemoryId = Uuid;
+50
View File
@@ -0,0 +1,50 @@
use crate::types::AgentId;
/// Trackt den Ressourcenverbrauch eines einzelnen Agenten.
/// Wird vom Hauptprozess (nazarick) pro Agent geführt und
/// ermöglicht späteres Monitoring, Limits und Kostenberechnung.
#[derive(Debug, Clone, Default)]
pub struct UsageRecord {
/// Eindeutige ID des Agenten dem dieser Record gehört
pub agent_id: AgentId,
/// Anzahl der Token die als Input an die LLM API gesendet wurden
pub tokens_input: u64,
/// Anzahl der Token die als Output von der LLM API empfangen wurden
pub tokens_output: u64,
/// Anzahl der Bildgenerierungs-Anfragen (ComfyUI)
pub image_requests: u64,
/// Gesamtanzahl aller API-Aufrufe (LLM + Bild)
pub api_calls: u64,
}
impl UsageRecord {
/// Erstellt einen neuen leeren UsageRecord für den angegebenen Agenten.
/// Alle Zähler starten bei 0.
pub fn new(agent_id: AgentId) -> Self {
Self {
agent_id,
..Default::default()
}
}
/// Registriert einen LLM API-Aufruf mit den entsprechenden Token-Zahlen.
/// Input und Output werden separat gezählt da sie unterschiedliche
/// Kosten haben können (z.B. bei Mistral API).
pub fn add_tokens(&mut self, input: u64, output: u64) {
self.tokens_input += input;
self.tokens_output += output;
self.api_calls += 1;
}
/// Registriert eine Bildgenerierungs-Anfrage (z.B. ComfyUI).
pub fn add_image_request(&mut self) {
self.image_requests += 1;
self.api_calls += 1;
}
/// Gibt die Gesamtanzahl der Token zurück (Input + Output).
/// Nützlich für schnelle Übersichten ohne Input/Output zu trennen.
pub fn total_tokens(&self) -> u64 {
self.tokens_input + self.tokens_output
}
}