ci / check (push) Successful in 15m52s
- /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.
72 lines
2.4 KiB
Rust
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(())
|
|
}
|