clients/rust: initial Rust SDK for clawdforge
Async client over reqwest+tokio with builder-pattern Client, serde RunRequest/RunResult/FileToken/AppToken types, thiserror Error enum, streaming multipart upload via tokio::fs::File, and 14 wiremock-backed integration tests covering healthz, run-success-json, run-success-text, run-502, run-with-files, file-upload, token mint/list/revoke, auth failure, missing-token short-circuit, transport timeout, and builder validation. Doc-tested. cargo test, cargo clippy --all-targets -D warnings, and cargo build --examples all clean.
This commit is contained in:
parent
b1d6e3f697
commit
062d405a9e
9 changed files with 1262 additions and 0 deletions
78
clients/rust/examples/basic.rs
Normal file
78
clients/rust/examples/basic.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
//! 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(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue