use std::io::Write;
use elph_ai::{AssistantContentBlock, AssistantMessageEvent, Context, FauxModelDefinition, FauxResponseStep, Message};
use elph_ai::{Models, RegisterFauxProviderOptions, StopReason, UserContent};
use elph_ai::{calculate_cost, create_models, faux_assistant_message, faux_provider, faux_text, faux_thinking};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let stream_mode = std::env::args().any(|a| a == "--stream");
let models = setup_faux_provider();
let model = models.get_model("demo", "demo-model").expect("demo-model registered");
let context = Context {
system_prompt: Some("You are a concise Rust expert.".into()),
messages: vec![Message::User {
content: UserContent::Text("What makes Rust unique?".into()),
timestamp: timestamp(),
}],
tools: None,
};
if stream_mode {
run_streaming(&models, &model, &context).await?;
} else {
run_buffered(&models, &model, &context).await?;
}
Ok(())
}
fn setup_faux_provider() -> elph_ai::MutableModels {
let mut models = create_models(None);
let faux = faux_provider(RegisterFauxProviderOptions {
provider: Some("demo".to_string()),
models: Some(vec![FauxModelDefinition {
id: "demo-model".to_string(),
name: Some("Demo Model".to_string()),
reasoning: Some(true),
input: None,
context_window: None,
max_tokens: None,
}]),
..Default::default()
});
faux.set_responses(vec![FauxResponseStep::Static(faux_assistant_message(
vec![
faux_thinking("The user is asking about Rust's core innovation."),
faux_text(
"Rust's ownership system enforces memory safety at compile time \
without a garbage collector. Every value has exactly one owner; \
references borrow temporarily. The compiler checks lifetimes, \
preventing use-after-free, double-free, and data races.",
),
],
Some(StopReason::Stop),
))]);
models.set_provider(faux.provider);
models
}
async fn run_streaming(models: &Models, model: &elph_ai::Model, context: &Context) -> anyhow::Result<()> {
let mut events = models.stream(model, context, None).into_stream();
print!("Assistant: ");
let _ = std::io::stdout().flush();
while let Some(event) = events.next().await {
match event {
AssistantMessageEvent::TextDelta { delta, .. } => {
print!("{delta}");
let _ = std::io::stdout().flush();
}
AssistantMessageEvent::ThinkingDelta { delta, .. } => {
eprint!("{delta}");
let _ = std::io::stderr().flush();
}
AssistantMessageEvent::Done { reason, message } => {
println!();
print_usage(&message, reason);
}
AssistantMessageEvent::Error { error, .. } => {
anyhow::bail!("stream error: {}", error.error_message.unwrap_or_default());
}
_ => {}
}
}
Ok(())
}
async fn run_buffered(models: &Models, model: &elph_ai::Model, context: &Context) -> anyhow::Result<()> {
let message = models.complete(model, context, None).await;
if let Some(err) = &message.error_message {
anyhow::bail!("error: {err}");
}
print!("Assistant: ");
for block in &message.content {
match block {
AssistantContentBlock::Text(t) => print!("{}", t.text),
AssistantContentBlock::Thinking(t) => eprint!("{}", t.thinking),
AssistantContentBlock::ToolCall(tc) => {
println!("\n[Tool call: {}]", tc.name);
}
}
}
println!();
let mut usage = message.usage.clone();
calculate_cost(model, &mut usage);
print_usage(&message, message.stop_reason);
if usage.cost.total > 0.0 {
println!("Cost: ${:.6}", usage.cost.total);
}
Ok(())
}
fn print_usage(message: &elph_ai::AssistantMessage, reason: StopReason) {
println!("Stop: {reason:?}");
println!(
"Tokens: {} in / {} out / {} cache read / {} cache write ({} total)",
message.usage.input,
message.usage.output,
message.usage.cache_read,
message.usage.cache_write,
message.usage.total_tokens
);
if let Some(r) = message.usage.reasoning {
println!("Reasoning: {r}");
}
}
fn timestamp() -> i64 {
chrono::Utc::now().timestamp_millis()
}