typesafe-sdk-rust 0.2.0

Async Rust SDK for the TypeSafe AI System One API
Documentation
[workspace]
# The root package is a workspace member as well, so "." is listed explicitly
# next to the helper crates. `fuzz/` is a workspace of its own, run with the
# nightly toolchain only.
members = [".", "crates/live-tests", "crates/macros", "crates/test-support"]
# Naming the default set explicitly is what keeps a crate whose tests need a
# live API key out of a bare `cargo test` and out of CI: such a crate is added
# to `members` and left out here, so no command has to remember to exclude it.
default-members = [".", "crates/macros", "crates/test-support"]
exclude = ["fuzz"]
# Resolver 3 is the edition-2024 default; stating it silences the warning that
# a virtual workspace manifest has no edition to infer it from.
resolver = "3"

[workspace.package]
edition = "2024"
rust-version = "1.98"
license = "Apache-2.0"
repository = "https://github.com/zchee/typesafe-sdk-rust"

# Lints are declared once here and inherited by every member with
# `[lints] workspace = true`, so a member cannot quietly opt out of them.
[workspace.lints.rust]
unsafe_code = "forbid"
missing_docs = "warn"
missing_debug_implementations = "warn"
unreachable_pub = "warn"

[workspace.lints.clippy]
# `.expect("invariant ...")` stays available for conditions that cannot fail;
# a bare `.unwrap()` carries no such statement, so it is rejected outright.
unwrap_used = "deny"

# Versions are exact-as-of-publication minimums; cargo treats `"1.11.1"` as
# `^1.11.1`, so patch and compatible minor upgrades still resolve.
[workspace.dependencies]
bytes = "1.12.1"
# No `serde` feature: the names it holds are decoded and serialized by the
# SDK's own code, which reads and writes them as plain strings.
compact_str = { version = "0.10.0", default-features = false, features = ["std"] }
fastrand = "2.5.0"
http = "1.5.0"
http-body = "1.1.0"
http-body-util = "0.1.5"
httpdate = "1.0.3"
hyper = { version = "1.11.1", features = ["client", "http1", "http2"] }
# hyper-rustls does NOT enable `http2` by default, so ALPN would negotiate
# HTTP/1.1 only. `native-tokio` (its default root source) is dropped because
# the trust anchors come from rustls-platform-verifier instead.
hyper-rustls = { version = "0.27.9", default-features = false, features = [
    "http1",
    "http2",
    "tls12",
    "aws-lc-rs",
] }
hyper-util = { version = "0.1.20", features = [
    "client-legacy",
    "http1",
    "http2",
    "tokio",
] }
# The derive's parser and code generator.
proc-macro2 = "1.0.107"
quote = "1.0.47"
# rustls' defaults are exactly the wanted set: aws_lc_rs, logging,
# prefer-post-quantum, std, tls12.
rustls = "0.23.45"
rustls-platform-verifier = "0.7.0"
secrecy = "0.10.3"
serde = { version = "1.0.229", features = ["derive"] }
sonic-rs = "0.5.10"
# syn 3 is the major the rest of the graph (serde_derive, thiserror-impl)
# already builds, so the derive adds no second copy; only the features it
# uses are named.
syn = { version = "3.0.6", default-features = false, features = [
    "derive",
    "parsing",
    "printing",
    "proc-macro",
] }
thiserror = "2.0.20"
tokio = { version = "1.53.1", features = ["rt", "time"] }
tokio-rustls = { version = "0.26.5", default-features = false, features = [
    "aws_lc_rs",
    "logging",
    "tls12",
] }
tower-service = "0.3.3"
tracing = "0.1.44"

codspeed-divan-compat = "5.0.2"
dhat = "0.3.3"
proptest = "1.11.0"
# `aws_lc_rs` reuses the crypto backend rustls already links, so the test
# certificate generator does not drag a second implementation into the graph.
rcgen = { version = "0.14.10", default-features = false, features = [
    "aws_lc_rs",
    "crypto",
] }
# `float_roundtrip` makes serde_json parse f64 exactly, which is what the
# differential tests against the primary codec compare against.
serde_json = { version = "1.0.151", features = ["float_roundtrip", "raw_value"] }
serde_path_to_error = "0.1.20"
trybuild = "1.0.121"

[package]
name = "typesafe-sdk-rust"
version = "0.2.0"
description = "Async Rust SDK for the TypeSafe AI System One API"
readme = "README.md"
keywords = ["typesafe", "ai", "llm", "sdk", "async"]
categories = ["api-bindings", "asynchronous"]
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
# A release ships the library, its examples, the README the crate docs embed
# and the licenses; nothing else of the repository. The tests and benches stay
# here: they build against the test-support crate, which is never published.
include = ["/src", "/examples", "/README.md", "/LICENSE", "/LICENSE-THIRD-PARTY"]

# The package is `typesafe-sdk-rust` because `typesafe-sdk` is taken on
# crates.io, but callers write `use typesafe_sdk::...`.
[lib]
name = "typesafe_sdk"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["hyper", "macros", "tracing"]
# The built-in transport and its TLS stack.
hyper = ["dep:hyper", "dep:hyper-rustls", "dep:hyper-util", "dep:rustls", "dep:rustls-platform-verifier"]
# `#[derive(QuestionSet)]`: questions declared as a struct and serialized at
# compile time.
macros = ["dep:typesafe-sdk-rust-macros"]
tracing = ["dep:tracing"]
# A SIMD parser that carries `unsafe`; off by default.
sonic = ["dep:sonic-rs"]
# Exposes `typesafe_sdk::__internals`, the seam the allocation tests and the
# benchmarks measure through. It carries no semver promise and is never on by
# default.
internals = []

[dependencies]
bytes = { workspace = true }
# The names a response owns (model, answer, choice pick and option names): a
# name of up to 24 bytes is stored inline, so decoding one allocates nothing.
compact_str = { workspace = true }
# The random draw of the retry backoff's jitter.
fastrand = { workspace = true }
http = { workspace = true }
# The request body type implements this crate's version of `http_body::Body`,
# the one bound a custom transport shares with the SDK.
http-body = { workspace = true }
# `Limited` collects a response body under the size cap.
http-body-util = { workspace = true }
# Used only to read `Retry-After` when a server answers it with an HTTP date
# rather than a count of seconds.
httpdate = { workspace = true }
# The default transport: hyper-util's pooled client over hyper-rustls, with the
# trust anchors from the operating system through rustls-platform-verifier.
hyper = { workspace = true, optional = true }
hyper-rustls = { workspace = true, optional = true }
hyper-util = { workspace = true, optional = true }
rustls = { workspace = true, optional = true }
rustls-platform-verifier = { workspace = true, optional = true }
# Held for the client's API key: it is kept as a secret until the one place
# that turns it into an `Authorization` header value, so no formatting of a
# client or its configuration can print it.
secrecy = { workspace = true }
serde = { workspace = true }
# The default JSON backend; its raw-value protocol preserves wire text.
serde_json = { workspace = true }
# A normal dependency rather than a dev one: the decode failure path uses it to
# turn a position into a field path, and that path is part of the errors the
# SDK reports at runtime.
serde_path_to_error = { workspace = true }
sonic-rs = { workspace = true, optional = true }
thiserror = { workspace = true }
# The per-attempt deadline, and the runtime the default transport runs on.
tokio = { workspace = true }
# The seam a custom transport is plugged into: `tower::Service` without the
# rest of `tower`.
tower-service = { workspace = true }
tracing = { workspace = true, optional = true }
# An exact version: the derive's expansion names this crate's hidden
# `__private` module, which carries no semver promise, so the two are only
# ever used at the versions they were released together.
typesafe-sdk-rust-macros = { version = "=0.1.0", path = "crates/macros", optional = true }

[dev-dependencies]
# divan with CodSpeed's instrumentation: it runs as plain divan under
# `cargo bench` and reports instruction counts under `cargo codspeed run`.
# The benches import it under the name `divan`.
codspeed-divan-compat = { workspace = true }
dhat = { workspace = true }
proptest = { workspace = true }
# The reference engine when serde_json is the SDK's backend.
sonic-rs = { workspace = true }
# `net` and `io-util` for the raw sockets the failure tests answer with.
tokio = { workspace = true, features = ["io-util", "macros", "net", "rt-multi-thread", "sync", "test-util"] }
# Compiles the derive's misuse cases and compares rustc's messages.
trybuild = { workspace = true }
# Path only, no version: cargo strips a path dev-dependency when it publishes,
# and this crate is never published.
typesafe-sdk-rust-test-support = { path = "crates/test-support" }

# dhat allows one profiler per process, so an allocation scenario gets a test
# binary of its own rather than a test function in a shared one.
[[test]]
name = "alloc_encode"
required-features = ["internals"]

[[test]]
name = "alloc_call"
required-features = ["internals"]

[[test]]
name = "alloc_decode"
required-features = ["internals"]

[[test]]
name = "differential"
required-features = ["internals"]

[[test]]
name = "client"
required-features = ["hyper"]

[[test]]
name = "retry"
required-features = ["hyper"]

[[test]]
name = "models"
required-features = ["hyper"]

[[test]]
name = "connection_reuse"
required-features = ["hyper"]

[[test]]
name = "malformed_body"
required-features = ["hyper"]

[[test]]
name = "derive"
required-features = ["macros", "hyper"]

[[test]]
name = "alloc_derive"
required-features = ["internals", "macros"]

# The examples use the built-in transport; typed_answers also needs the derive.
[[example]]
name = "typed_answers"
required-features = ["macros", "hyper"]

[[example]]
name = "quickstart"
required-features = ["hyper"]

[[example]]
name = "concurrency"
required-features = ["hyper"]

# divan supplies its own `main`, so the benches opt out of libtest's harness.
# `sdk` is the CPU-only set that CodSpeed measures; `loopback` is a real
# socket and stays out of that run (see the file).
[[bench]]
name = "sdk"
harness = false
required-features = ["internals", "macros"]

[[bench]]
name = "loopback"
harness = false
required-features = ["hyper"]

[lints]
workspace = true