Skip to content

v0.6.0 — Provider integration

Pre-release
Pre-release

Choose a tag to compare

@jamesgober jamesgober released this 06 Jun 01:23
· 8 commits to main since this release

throttle-net v0.6.0 — Provider integration

Be a good API citizen out of the box. Downstreams already tell you your remaining budget — in their response headers — and the well-known APIs each spell it differently. v0.6.0 reads those headers, reconciles your limiter with the server's view, and gives you provider tier presets to start from. No breaking changes.

What is throttle-net?

A general-purpose outbound throttling and resilience library. Where rate-net protects your service from being overwhelmed (inbound), throttle-net protects your service from overwhelming the downstreams it calls — and from being banned by them. It paces outbound work, composes limits across dimensions and scopes, retries transient failures, fails fast when a dependency is sick, queues callers fairly, adapts its concurrency to what the downstream can take, and — as of this release — listens to what the downstream tells it about its own limits.

What's new in 0.6.0

Response-header parsing — the provider module

Every provider advertises rate-limit state differently. A HeaderProfile captures one convention; ready-made profiles cover the common ones, and parse turns a header set into a normalized RateLimitInfo with separate requests and tokens windows plus a retry_after.

use throttle_net::provider::HeaderProfile;

let headers = [
    ("x-ratelimit-limit-requests", "5000"),
    ("x-ratelimit-remaining-requests", "4999"),
    ("x-ratelimit-reset-requests", "6m0s"),
];
let info = HeaderProfile::OPENAI.parse(&headers);
assert_eq!(info.requests.unwrap().remaining, Some(4999));
Profile Reset encoding
OPENAI duration string (6m0s, 100ms)
ANTHROPIC RFC 3339 instant
GITHUB absolute Unix timestamp
RFC (IETF RateLimit draft) delta-seconds
STRIPE, AWS Retry-After only

All four reset encodings are parsed with no date-library dependency (the RFC 3339 and Unix-timestamp paths reuse the same civil-date math as the Retry-After parser, now shared internally). Parsing is defensive: header names match case-insensitively, and malformed values are dropped rather than panicking. parse_at(headers, now) takes an explicit current time for deterministic testing.

State synchronization

RateLimitInfo::sync_requests (and sync_tokens) reconcile a Throttle with the server's reported remaining count:

let drained = info.sync_requests(&throttle);

It drains the local budget down to the server's number — and only ever reduces, never adds — so it corrects client/server drift without any chance of raising the throttle above its hard limit.

LLM tier presets — the presets module

Pre-wired MultiLimiter configurations for the common LLM tiers, so you start from a sensible shape instead of hand-building the three dimensions:

use throttle_net::presets;

let limiter = presets::anthropic::tier_2(); // requests + input/output token budgets

presets::anthropic::{tier_1, tier_2, tier_4} and presets::openai::{tier_1, tier_2} are provided. The numbers are illustrative starting points — providers change tier limits and meter per model — so confirm them against current docs.

Breaking changes

None. The new modules are additive and feature-gated (provider-headers, provider-llm).

Verification

Run on Windows x86_64, Rust stable 1.93.1; the same commands run in the CI matrix on Linux, macOS, and Windows across stable and MSRV 1.85:

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --all-targets --no-default-features -- -D warnings
cargo test --all-features
cargo test --no-default-features
cargo doc --no-deps && RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo deny check
cargo audit

All green. Counts at this tag (--all-features): 129 unit tests, 7 property tests, 3 circuit-breaker integration tests, 2 retry integration tests, 67 doctests. Each provider profile is tested against a representative recorded header set (the exit criterion), and synchronization is tested to drain to the server's count while never raising the local budget above the hard limit.

This release also fixes a latent issue: the token-bucket module imported the error type unconditionally, so the std-without-tokio feature combination (now reachable via default-features = false, features = ["provider-llm"]) failed to build under -D warnings. The import is now correctly gated, and that combination builds clean.

What's next

  • v0.7.0 — Observability. Metrics (throttle_acquired_total, throttle_wait_duration, throttle_queue_depth, throttle_circuit_state, throttle_rate_current) and tracing spans around acquire(), feature-gated and zero-cost when disabled, plus structured events for circuit transitions, adaptive rate changes, queue overflow, and deadline exhaustion.

Installation

[dependencies]
throttle-net = "0.6"

# Header parsing and LLM presets are behind feature flags:
throttle-net = { version = "0.6", features = ["provider-llm"] }

MSRV: Rust 1.85.

Documentation


Changelog: CHANGELOG.md.

Full Changelog: v0.5.0...v0.6.0