use kiromi_ai_memory::{AppendOpts, Content, Partitions};
use tokio::io::AsyncReadExt;
use crate::cli::{AppendArgs, GlobalArgs};
use crate::error::{CliError, ExitCode};
use crate::runtime::Runtime;
pub(crate) async fn run(args: AppendArgs, globals: &GlobalArgs) -> Result<(), CliError> {
let rt = Runtime::open(globals).await?;
let body = read_body(&args).await?;
let content = match args.kind.as_str() {
"md" => Content::markdown(body),
"txt" => Content::text(body),
other => {
return Err(CliError {
kind: ExitCode::Config,
source: anyhow::anyhow!("unknown --kind {other:?}; expected 'md' or 'txt'"),
});
}
};
let mut opts = AppendOpts::default();
if let Some(path) = args.embedding {
let raw = tokio::fs::read(&path).await.map_err(|e| CliError {
kind: ExitCode::Config,
source: anyhow::anyhow!("read embedding {}: {e}", path.display()),
})?;
let v: Vec<f32> = serde_json::from_slice(&raw).map_err(|e| CliError {
kind: ExitCode::Config,
source: anyhow::anyhow!("parse embedding {}: {e}", path.display()),
})?;
opts = opts.with_embedding(v);
}
if !args.links.is_empty() {
return Err(CliError {
kind: ExitCode::Config,
source: anyhow::anyhow!(
"passing --link at append time requires a separate `link add` step in slice 1; use `kiromi-ai-memory link add <src> <dst>` after append"
),
});
}
let partitions = parse_partitions(&args.partition)?;
let r = rt.mem.append(partitions, content, opts).await?;
if globals.json {
let payload = serde_json::json!({
"id": r.id.to_string(),
"partition": r.partition.as_str(),
});
println!(
"{}",
serde_json::to_string_pretty(&payload).unwrap_or_default()
);
} else {
println!("{}", r.id);
}
rt.mem.close().await?;
Ok(())
}
async fn read_body(args: &AppendArgs) -> Result<String, CliError> {
if let Some(text) = &args.message {
return Ok(text.clone());
}
if let Some(p) = &args.file {
return tokio::fs::read_to_string(p).await.map_err(|e| CliError {
kind: ExitCode::Config,
source: anyhow::anyhow!("read --file {}: {e}", p.display()),
});
}
if args.stdin {
let mut s = String::new();
tokio::io::stdin()
.read_to_string(&mut s)
.await
.map_err(|e| CliError {
kind: ExitCode::Backend,
source: anyhow::anyhow!("read stdin: {e}"),
})?;
return Ok(s);
}
Err(CliError {
kind: ExitCode::Config,
source: anyhow::anyhow!("one of --file, --stdin, or --message is required"),
})
}
pub(crate) fn parse_partitions(spec: &str) -> Result<Partitions, CliError> {
let mut p = Partitions::new();
for kv in spec.split(',').filter(|s| !s.is_empty()) {
let (k, v) = kv.split_once('=').ok_or_else(|| CliError {
kind: ExitCode::Config,
source: anyhow::anyhow!("partition entry {kv:?} missing `=`"),
})?;
p = p.with(k, v);
}
Ok(p)
}