typesafe_sdk/constants.rs
1//! The names and defaults the SDK's behaviour is pinned to: the environment
2//! variables, headers, API paths and defaults.
3
4use std::{sync::LazyLock, time::Duration};
5
6use http::{HeaderMap, HeaderName, HeaderValue, header};
7
8/// The response header carrying the server's identifier for a request.
9pub(crate) const REQUEST_ID_HEADER: &str = "x-typesafe-request-id";
10
11/// The server's identifier for a request, when `headers` carry one as text.
12pub(crate) fn request_id(headers: &HeaderMap) -> Option<&str> {
13 headers.get(REQUEST_ID_HEADER).and_then(|value| value.to_str().ok())
14}
15
16/// The non-standard millisecond-precision companion to `Retry-After`.
17pub(crate) const RETRY_AFTER_MS_HEADER: &str = "retry-after-ms";
18
19// ------------------------------------------------- environment and defaults
20
21/// The environment variable a client reads its API key from when none is
22/// passed explicitly.
23pub const API_KEY_ENV: &str = "TYPESAFE_API_KEY";
24
25/// The environment variable a client reads its base URL from when none is
26/// passed explicitly.
27pub const BASE_URL_ENV: &str = "TYPESAFE_BASE_URL";
28
29/// The environment variable a client reads its default model from when none
30/// is passed explicitly.
31pub const DEFAULT_MODEL_ENV: &str = "TYPESAFE_DEFAULT_MODEL";
32
33/// The API root a client talks to when neither the caller nor
34/// [`BASE_URL_ENV`] names one.
35pub const DEFAULT_BASE_URL: &str = "https://api.typesafe.ai";
36
37/// The model a request names when neither the call, the client nor
38/// [`DEFAULT_MODEL_ENV`] names one.
39pub const DEFAULT_MODEL: &str = "jev-latest";
40
41/// The deadline each attempt of a request gets when the caller sets none.
42///
43/// It bounds one attempt from the first byte sent to the last byte received,
44/// not the whole call: a retried call can take several of these.
45pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
46
47/// The largest response body a client reads before giving up on it, in bytes.
48///
49/// A body is collected in memory before it is decoded, so without a cap a
50/// broken or hostile endpoint could make one call hold as much memory as it
51/// cared to send.
52pub const DEFAULT_MAX_RESPONSE_BYTES: usize = 16 * 1024 * 1024;
53
54// ------------------------------------------------------------ API paths
55
56/// The path of the System One endpoint, appended to the base URL.
57pub(crate) const SYSTEM_ONE_PATH: &str = "/v1/systemone";
58
59/// The path of the model listing endpoint, appended to the base URL.
60pub(crate) const MODELS_PATH: &str = "/v1/models";
61
62// ------------------------------------------------------ request headers
63//
64// `http` keeps header names lower-cased, so these are spelled that way; on the
65// wire HTTP/1.1 header names are case-insensitive and HTTP/2 requires lower
66// case, so nothing is lost. The names `http` already defines (`authorization`,
67// `accept`, `content-type`, `user-agent`) are used from `http::header` directly
68// rather than restated here.
69
70/// The header naming the SDK and its version on every request.
71pub(crate) const SDK_HEADER: HeaderName = HeaderName::from_static("x-typesafe-sdk");
72
73/// The header naming the language runtime, operating system and architecture
74/// on every request.
75pub(crate) const RUNTIME_HEADER: HeaderName = HeaderName::from_static("x-typesafe-runtime");
76
77/// The header counting how many times a request has been retried, set by the
78/// SDK on retries only. A caller-supplied one is dropped.
79pub(crate) const RETRY_COUNT_HEADER: HeaderName = HeaderName::from_static("x-typesafe-retry-count");
80
81/// The headers that frame a message or manage its connection, which belong
82/// to the transport and are dropped from client defaults and per-call headers
83/// on every protocol.
84///
85/// A caller's value for one of them disagrees with the body the SDK sends or
86/// with how the transport runs the connection. HTTP/2 forbids the
87/// connection-specific ones (RFC 9113, section 8.2.2), so hyper strips them
88/// there; a `Content-Length` that disagrees with the body fails an HTTP/2
89/// stream and leaves an HTTP/1.1 exchange waiting for bytes that never come,
90/// on a connection other calls share. `Host` is not among them; see
91/// [`ClientBuilder::default_header`](crate::ClientBuilder::default_header).
92pub(crate) const TRANSPORT_HEADERS: [HeaderName; 8] = [
93 header::CONTENT_LENGTH,
94 header::TRANSFER_ENCODING,
95 header::CONNECTION,
96 HeaderName::from_static("keep-alive"),
97 HeaderName::from_static("proxy-connection"),
98 header::TE,
99 header::TRAILER,
100 header::UPGRADE,
101];
102
103/// The media type of every request body and of every response the SDK accepts.
104pub(crate) const JSON_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/json");
105
106/// The headers the SDK sets on every request, which neither a client default
107/// nor a per-call header can replace.
108///
109/// They say who is calling and with which credential; a caller who could
110/// override them could send a request the SDK cannot vouch for. The Python
111/// SDK protects the same five (`_core/transport.py`), and `Content-Type` is
112/// forced on a request with a body on top of them.
113pub(crate) const PROTECTED_HEADERS: [HeaderName; 5] =
114 [header::AUTHORIZATION, header::ACCEPT, header::USER_AGENT, SDK_HEADER, RUNTIME_HEADER];
115
116/// The header names whose values are credentials, and so are never logged.
117///
118/// Lower-cased, because that is how `http` stores every name. A name that
119/// merely contains `token` or `secret` is treated the same way; that rule lives
120/// with the redaction that applies it, which exists only when events do.
121#[cfg(any(test, feature = "tracing"))]
122pub(crate) const SECRET_HEADERS: [&str; 6] =
123 ["authorization", "proxy-authorization", "x-api-key", "api-key", "cookie", "set-cookie"];
124
125// ------------------------------------------------------ SDK identification
126
127/// What the SDK calls itself in `User-Agent` and [`SDK_HEADER`], as
128/// `typesafe-sdk-rust/<version>`.
129///
130/// Deliberately not the official Python SDK's `typesafe-sdk/<version>`: the
131/// server may count or treat SDKs by this value, and a port must not be
132/// mistaken for the SDK it is a port of.
133pub(crate) const SDK_IDENTIFIER: HeaderValue =
134 HeaderValue::from_static(concat!("typesafe-sdk-rust/", env!("CARGO_PKG_VERSION")));
135
136/// What the SDK sends in [`RUNTIME_HEADER`], as `rust (<os>; <arch>)`.
137///
138/// `std::env::consts` holds the target the crate was compiled for, such as
139/// `macos` and `aarch64`. Those are constants but not literals, and `concat!`
140/// takes only literals, so the value is built on first use and kept for the
141/// life of the process.
142pub(crate) static RUNTIME_IDENTIFIER: LazyLock<HeaderValue> = LazyLock::new(|| {
143 let text = format!("rust ({}; {})", std::env::consts::OS, std::env::consts::ARCH);
144 HeaderValue::from_str(&text)
145 .expect("invariant: target OS and architecture names are printable ASCII")
146});
147
148#[cfg(test)]
149#[path = "constants_tests.rs"]
150mod tests;