HIGH: - H1: truncate() uses floor_char_boundary (was panicking on multibyte boundaries) - H2: hand-written Debug for Client/ClientBuilder/AppToken redacts bearer (was leaking via dbg!()/tracing) - H3: revoke_token validates name client-side (rejects path traversal sequences) MEDIUM: - M1: From<reqwest::Error> maps timeouts to Error::Timeout (was always Transport) - M2: revoke_token accepts 2xx empty body (was rejecting RFC-correct 204 No Content) - M3: tests use assert!(matches!) instead of matches!().then_some().unwrap() - M4: ClientBuilder.max_upload_bytes optional cap - M5: lib.rs deny(missing_docs) LOW: - L1: cargo fmt - L2: drop dead AUTHORIZATION import Audit: memory/clawdforge-audits/rust-062d405.md
77 lines
2.2 KiB
Rust
77 lines
2.2 KiB
Rust
//! End-to-end usage example.
|
|
//!
|
|
//! Run against a live clawdforge:
|
|
//!
|
|
//! ```sh
|
|
//! CLAWDFORGE_URL=http://localhost:8800 \
|
|
//! CLAWDFORGE_TOKEN=cf_xxxxxxxxxxxxxxxx \
|
|
//! cargo run --example basic
|
|
//! ```
|
|
|
|
use clawdforge::{Client, RunRequest};
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct Hello {
|
|
hello: String,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let url =
|
|
std::env::var("CLAWDFORGE_URL").unwrap_or_else(|_| "http://localhost:8800".to_string());
|
|
let token = std::env::var("CLAWDFORGE_TOKEN")
|
|
.map_err(|_| "set CLAWDFORGE_TOKEN to a cf_... bearer minted via /admin/tokens")?;
|
|
|
|
let client = Client::builder().base_url(url).token(token).build()?;
|
|
|
|
// 1) liveness
|
|
let h = client.healthz().await?;
|
|
println!(
|
|
"healthz: ok={} claude_present={} version={:?}",
|
|
h.ok, h.claude_present, h.claude_version
|
|
);
|
|
|
|
// 2) JSON-shaped run
|
|
let r = client
|
|
.run(RunRequest {
|
|
prompt: r#"Reply with JSON: {"hello": "world"}"#.into(),
|
|
model: Some("sonnet".into()),
|
|
timeout_secs: Some(30),
|
|
..Default::default()
|
|
})
|
|
.await?;
|
|
|
|
println!(
|
|
"duration_ms={} stop_reason={:?}",
|
|
r.duration_ms, r.stop_reason
|
|
);
|
|
|
|
match r.as_json::<Hello>() {
|
|
Ok(v) => println!("parsed: hello = {}", v.hello),
|
|
Err(_) => match r.as_text() {
|
|
Some(t) => println!("text reply: {t}"),
|
|
None => println!("unparseable reply: {:?}", r.result),
|
|
},
|
|
}
|
|
|
|
// 3) optional file upload — only if a path is given.
|
|
if let Ok(path) = std::env::var("CLAWDFORGE_DEMO_FILE") {
|
|
let ft = client.upload_file(&path, Some(3600)).await?;
|
|
println!(
|
|
"uploaded {} bytes -> {} (ttl {}s)",
|
|
ft.size, ft.file_token, ft.ttl_secs
|
|
);
|
|
|
|
let r2 = client
|
|
.run(RunRequest {
|
|
prompt: "Describe the attached file in one sentence.".into(),
|
|
files: Some(vec![ft.file_token]),
|
|
..Default::default()
|
|
})
|
|
.await?;
|
|
println!("file-run reply: {:?}", r2.result);
|
|
}
|
|
|
|
Ok(())
|
|
}
|