Files
surma b536beb069
ci / check (push) Successful in 15m52s
YAML configuration, init without a wizard, and openai-chat-completions
- /config/agent.yaml and /config/boot.yaml replace the JSON files, read
  with serde_yaml_ng (also in the agent, inside WASM).
- sausage init asks nothing and reads no SAUSAGE_* variables. It writes
  commented templates with an example for each API, and prints the path
  to edit. Until the agent is configured, agent.chat names the missing
  field. allow_no_token: true permits a local model without a key.
- The agent speaks openai-chat-completions, the format of OpenRouter,
  Ollama, and most providers. base_url follows each SDK: with /v1 for
  the OpenAI shapes, without it for Anthropic.
- Checked against the proxy with glm-5.3-flash: the agent wrote, built,
  and activated a counter plugin in 55 s.
2026-10-23 11:27:48 +01:00

72 lines
2.4 KiB
Rust

//! World folders and atomic build output helpers.
use anyhow::{Result, bail};
use std::path::{Path, PathBuf};
#[derive(Clone)]
/// The absolute host folder shared by all processes.
pub struct World {
/// The absolute path of the world on the host.
pub root: PathBuf,
}
impl World {
/// Validate reserved root names and resolve the world path.
pub fn open(path: &Path) -> Result<Self> {
for name in ["proc", "run", "self"] {
if path.join(name).symlink_metadata().is_ok() {
bail!("reserved world path: {name}");
}
}
Ok(Self {
root: path.canonicalize()?,
})
}
/// Create the folders and empty boot configuration of a new world.
pub fn create(path: &Path) -> Result<Self> {
if path.exists() {
bail!("world already exists: {}", path.display());
}
for dir in ["plugins", "state/core", "config"] {
std::fs::create_dir_all(path.join(dir))?;
}
std::fs::write(path.join("config/boot.yaml"), "[]\n")?;
Self::open(path)
}
/// Return the folder that contains a plugin component.
pub fn plugin_dir(&self, name: &str) -> PathBuf {
self.root.join("plugins").join(name)
}
/// Return the persistent state folder of a plugin.
pub fn state_dir(&self, name: &str) -> PathBuf {
self.root.join("state").join(name)
}
}
/// Write a temporary sibling and rename it over the destination.
pub fn write_atomic(path: &Path, bytes: &[u8]) -> Result<()> {
std::fs::create_dir_all(path.parent().unwrap())?;
let tmp = path.with_extension("tmp");
std::fs::write(&tmp, bytes)?;
std::fs::rename(tmp, path)?;
Ok(())
}
/// Replace a build copy with a source tree, without target folders.
pub fn replace_source(from: &Path, to: &Path) -> Result<()> {
if to.exists() {
std::fs::remove_dir_all(to)?;
}
copy_tree(from, to)
}
fn copy_tree(from: &Path, to: &Path) -> Result<()> {
std::fs::create_dir_all(to)?;
for entry in std::fs::read_dir(from)? {
let entry = entry?;
if entry.file_name() == "target" {
continue;
}
if entry.file_type()?.is_dir() {
copy_tree(&entry.path(), &to.join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), to.join(entry.file_name()))?;
}
}
Ok(())
}