inferd_client/lib.rs
1//! Rust client for the inferd local-inference daemon.
2//!
3//! Wire protocol is NDJSON over Unix socket / Windows named pipe /
4//! loopback TCP. Spec is frozen as protocol v1; see the inferd
5//! repository's `docs/protocol-v1.md`.
6//!
7//! Two patterns for waiting on the daemon to come up; pick based on
8//! whether you need progress UX:
9//!
10//! - **Pattern A (passive)** — [`dial_and_wait_ready`] retries
11//! connect against the inference transport with exponential
12//! backoff. Successful connect is the ready signal because the
13//! daemon's inference socket only exists when the backend is ready
14//! (THREAT_MODEL F-13 in the upstream repo). Standard
15//! Postgres/Redis/etcd client shape.
16//! - **Pattern B (active)** — [`AdminClient`] subscribes to the
17//! admin socket and yields lifecycle events
18//! (`starting`/`loading_model`/`ready`/`restarting`/`draining`).
19//! Use this for installer GUIs, dashboards, or middleware that
20//! wants to display download progress during first-boot
21//! bootstrap.
22//!
23//! ## Quickstart
24//!
25//! ```no_run
26//! use inferd_client::{Client, Request, Message, Role, Response};
27//! use tokio_stream::StreamExt;
28//!
29//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
30//! let mut client = inferd_client::dial_and_wait_ready(
31//! std::time::Duration::from_secs(30),
32//! || Client::dial_tcp("127.0.0.1:47321"),
33//! )
34//! .await?;
35//!
36//! let mut stream = client.generate(Request {
37//! id: "demo-1".into(),
38//! messages: vec![Message {
39//! role: Role::User,
40//! content: "hello".into(),
41//! }],
42//! ..Default::default()
43//! })
44//! .await?;
45//!
46//! while let Some(frame) = stream.next().await {
47//! match frame? {
48//! Response::Token { content, .. } => print!("{content}"),
49//! Response::Done { stop_reason, backend, .. } => {
50//! println!("\n[done; backend={backend}, stop={stop_reason:?}]");
51//! }
52//! Response::Error { code, message, .. } => {
53//! eprintln!("[error {code:?}: {message}]");
54//! }
55//! Response::Status { .. } => {}
56//! }
57//! }
58//! # Ok(())
59//! # }
60//! ```
61
62#![forbid(unsafe_code)]
63#![warn(missing_docs, rust_2018_idioms)]
64
65mod admin;
66mod client;
67mod wait;
68
69pub use admin::{AdminClient, AdminEvent};
70pub use client::{Client, ClientError, FrameStream};
71pub use wait::{WaitError, default_admin_addr, dial_and_wait_ready, is_transient_dial_error};
72
73/// Re-exports from `inferd-proto` so consumers don't need a separate
74/// `inferd-proto` dep for the wire types. The proto crate IS the
75/// version-pin contract for protocol compatibility — `inferd-client
76/// 0.1` always uses `inferd-proto 0.1`.
77pub use inferd_proto::{
78 ErrorCode, ImageTokenBudget, MAX_FRAME_BYTES, Message, ProtoError, Request, Resolved, Response,
79 Role, StopReason, Usage, VALID_IMAGE_TOKEN_BUDGETS,
80};