jev-sdk 0.1.0

Unofficial Rust client for the TypeSafe AI System One API (Jev): typed decisions with calibrated probabilities
Documentation

jev-sdk

An unofficial Rust client for the TypeSafe AI System One API.

Jev is a System One model: instead of generating text, it evaluates typed questions against a state and returns structured decisions with calibrated probabilities and confidence scores — in ~70–500ms. Think of it as a frontier-intelligence function call: unstructured state in, typed probabilistic decisions out. It can't hallucinate schema violations, because the possible outputs are defined in advance.

Features

  • All three question primitives: Choice, Score, Noul — mixable in a single parallel call
  • Typed answers with full probability distributions and confidence
  • Async client built on reqwest (rustls, no OpenSSL)
  • Config via environment variables or a builder
  • Automatic retries with exponential backoff + Retry-After for 429/529/connection failures
  • Structured error types for auth, validation, and rate-limit failures

Install

[dependencies]
jev-sdk = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Quick start

Get an API key from the dashboard and export it:

export TYPESAFE_API_KEY=apikey_...
use jev_sdk::{Choice, Noul, Question, Score, TypeSafeClient};

#[tokio::main]
async fn main() -> Result<(), jev_sdk::Error> {
    let client = TypeSafeClient::from_env()?;

    let response = client
        .system_one(
            "Hi, I've been trying to connect my Stripe account for 3 days \
             and it keeps failing. I'm losing sales. Please help ASAP.",
            [
                (
                    "department",
                    Question::from(Choice::new(
                        "Which team should handle this",
                        [
                            ("billing", Some("Payment or subscription issues")),
                            ("technical", Some("Bugs or integration problems")),
                            ("sales", Some("Pricing or account questions")),
                        ],
                    )),
                ),
                (
                    "frustration",
                    Question::from(Score::new(
                        "How frustrated the customer appears",
                        ["Calm, just stating facts", "Frustrated but civil", "Very angry"],
                    )),
                ),
                (
                    "is_urgent",
                    Question::from(Noul::new("The message conveys urgency or time-sensitivity")),
                ),
            ],
        )
        .await?;

    let department = response.choice("department").unwrap();
    println!("route to {} (confidence {:.2})", department.choice, department.confidence);
    println!("frustration: {:.2}", response.score("frustration").unwrap().score);
    println!("urgency: {:.3}", response.noul("is_urgent").unwrap().noul);
    Ok(())
}

Run the included version against the live API:

TYPESAFE_API_KEY=apikey_... cargo run --example triage

Question types

Type Purpose Answer
Noul Is this statement true? noul: probability of "yes", 0–1
Choice Pick one option from a set choice, probabilities, confidence
Score Rate against ordered levels score (probability-weighted), legend, probabilities, confidence
  • State can be a string or any serializable structure (objects, arrays — chat logs, records, application state).
  • Instructions accept strings or structured JSON.
  • Choice criteria map option → optional description (Choice::options for bare names).
  • Noul criteria optionally describe what "yes" and "no" mean.
  • Ask many questions in one call: they're evaluated in parallel, and adding more barely changes latency.
use jev_sdk::{Choice, NoulCriteria};

Choice::options("Sentiment?", ["positive", "negative", "mixed"]); // no descriptions

Noul::new("Does this convey urgency?").criteria(NoulCriteria::new(
    "Explicitly time-sensitive", // what "yes" means
    "No urgency expressed",      // what "no" means
));

Using the answers

SystemOneResponse.answers is keyed by your question ids. Typed accessors return Options so you can't mix up question types:

# let response: jev_sdk::SystemOneResponse = todo!();
if let Some(ans) = response.choice("department") {
    if ans.confidence >= 0.8 {
        // act autonomously
    } else {
        // escalate to a human
    }
}
for (id, ans) in response.nouls() { /* ... */ }

The model never makes type errors; every Choice/Score answer also carries a calibrated confidence you can gate automation on.

Configuration

Setting Environment variable Default
API key TYPESAFE_API_KEY — (required)
Model TYPESAFE_DEFAULT_MODEL jev-latest
Base URL TYPESAFE_BASE_URL https://api.typesafe.ai

Explicit builder options take precedence over environment variables:

use std::time::Duration;
use jev_sdk::{RetryPolicy, TypeSafeClient};

let client = TypeSafeClient::builder()
    .api_key("apikey_...")
    .model("jev-latest")
    .timeout(Duration::from_secs(30))
    .retry(RetryPolicy::default())      // or RetryPolicy::none()
    .build()?;

// Per-call model override:
// client.system_one_with_model("jev-preview", state, questions).await?;

List available models with client.list_models().await?.

Errors

use jev_sdk::Error;

match client.system_one("hi", [("q", jev_sdk::Noul::new("test?").into())]).await {
    Err(e) if e.is_authentication() => { /* 401: check the key */ }
    Err(e) if e.is_rate_limited() => { /* 429/529: the SDK already retried; back off longer */ }
    Err(e) if e.is_validation() => { /* 400/422: fix the request */ }
    Err(Error::Api(api)) => eprintln!("status {}: {}", api.status, api.message),
    Err(e) => return Err(e),
    Ok(_) => {}
}

Security

The client is hardened by default:

  • Credential protection — the API key is stored in a sensitive HeaderValue and redacted from all Debug output; keys containing header control characters are rejected (no CRLF injection).
  • TLS-only transport — base URLs must use HTTPS; plain HTTP is accepted only for loopback hosts (local testing). TLS uses rustls with certificate verification; the crate never exposes a way to disable it.
  • No redirects — the built-in HTTP client never follows redirects, so the Authorization header cannot be forwarded to an unexpected host.
  • Bounded memory — response bodies are size-capped (16 MiB success / 64 KiB error) and read incrementally, so a hostile or buggy endpoint cannot exhaust memory. Automatic decompression is not enabled (no gzip bombs).
  • Bounded waits — retries use saturating exponential backoff with jitter; server Retry-After hints are honored but capped at 60s.
  • Dependencies — minimal, mainstream crates only (reqwest, serde, serde_json, indexmap, thiserror, tokio), vetted with cargo audit.

Supplying your own reqwest client via ClientBuilder::http_client transfers responsibility for transport hardening (TLS verification, redirect policy, timeouts) to you.

Testing

cargo test                              # unit tests
TYPESAFE_API_KEY=... cargo test         # also runs live API integration tests

Notes

  • This crate is async (tokio). There is no blocking façade yet.
  • MSRV: 1.75.

Links

License

Dual-licensed under MIT or Apache-2.0, at your option.