Configuration¶
Defaults¶
With TYPESAFE_API_KEY set, a no-argument JevClient() is ready to use:
// With TYPESAFE_API_KEY set, the defaults are all you need.
JevClient().use { jev ->
println(jev.config) // the key is redacted
}
JevClient holds a Ktor HTTP client, so close it when you're done. use { } does that for you.
The configuration builder¶
val jev =
JevClient {
apiKey = System.getenv("MY_TYPESAFE_KEY") // default: TYPESAFE_API_KEY
baseUrl = "https://api.typesafe.ai" // default: TYPESAFE_BASE_URL, then this
defaultModel = "jev-1.14.0" // default: TYPESAFE_DEFAULT_MODEL, then "jev-latest"
timeout = 15.seconds // per HTTP attempt; default 10 s
retry = RetryPolicy(maxRetries = 4) // see "Retries & Errors"
headers["X-Request-Source"] = "support-triage" // sent with every request
}
Each setting resolves in this order: the explicit value, then the environment variable, then the default. String settings are trimmed, so the trailing newline of a value read from a file is harmless, and a value that is blank after trimming counts as unset.
| Setting | Environment variable | Default |
|---|---|---|
apiKey |
TYPESAFE_API_KEY |
none; required |
baseUrl |
TYPESAFE_BASE_URL |
https://api.typesafe.ai |
defaultModel |
TYPESAFE_DEFAULT_MODEL |
jev-latest |
timeout |
10 seconds per HTTP attempt | |
retry |
RetryPolicy(), matching the JS SDK; see Retries & Errors |
|
engine |
the platform's default engine, created by the client (CIO on the JVM) | |
headers |
none; extra headers sent with every request | |
allowInsecureHttp |
false; allows a plain http:// baseUrl on a host other than this machine |
A missing API key throws a JevConfigException that names TYPESAFE_API_KEY, on its own; the other settings are
checked, and reported together, once there is a key. JevConfig.toString() redacts the key, so it's safe to log.
Running with Ollaya¶
jev4k works with Ollaya, which serves the same API from your own machine. Ollaya runs Laya, a different model from Jev, so answers can differ from TypeSafe's. No code changes are needed; point the client at it with three environment variables:
For an Ollaya server on another host, such as http://ollaya:11435 in Docker, also set allowInsecureHttp = true:
plain http:// is accepted without it only for this machine (localhost, 127.x.x.x, ::1).
Retry policies¶
// More patience for a batch job:
JevClient {
retry =
RetryPolicy(
maxRetries = 5,
initialBackoff = 1.seconds,
maxBackoff = 20.seconds,
)
},
// Fail fast in an interactive request path:
JevClient { retry = RetryPolicy.NONE },
// Retry rate limits and overloads, but not other server errors:
JevClient { retry = RetryPolicy(retryStatuses = setOf(429, 529)) },
To use a different timeout, retry policy or headers for some calls only, pass per-call options rather than building a second client.
A custom HTTP engine¶
The client creates its own engine unless you pass one: CIO on the JVM, Darwin on Apple platforms, Curl on Linux,
WinHttp on Windows, and the fetch-based Js engine on Node.js. To tune connection pooling, TLS, or proxies, pass
your own Ktor engine:
// Supply a tuned Ktor engine instead of the default CIO one. The client won't close an engine you pass in.
val jev =
JevClient {
engine =
CIO.create {
maxConnectionsCount = 64
endpoint.maxConnectionsPerRoute = 16
endpoint.connectTimeout = 2_000 // milliseconds
}
}
The client doesn't close an engine you pass in, so one engine can be shared by several clients. Close it
yourself when you're done with it. timeout still bounds each whole attempt, but the connect and socket
timeouts are left to an engine you supply, so settings like endpoint.connectTimeout above take effect.
The builder checks the settings that would otherwise fail every request, or that engines would send differently. With
a key present, it throws a JevConfigException listing every problem it found at once:
baseUrlmust be an absolutehttps://URL with a host and, optionally, a path. Plainhttp://is accepted for this machine, or anywhere withallowInsecureHttp = true. Credentials, a query or a fragment are refused; send what a gateway needs inheadersinstead.apiKeymust be printable ASCII with no spaces: a zero-width space or other invisible character pasted with it would make the server see a different key. A leading byte-order mark on any setting, as in a file saved with one, is dropped.headersmust have HTTP token names and ASCII values without control characters (a tab is allowed).Content-Length,Content-Type,Transfer-EncodingandUpgradecan't be set, since jev4k or the engine sets them.timeoutmust be at least a millisecond.
The messages never quote the key, a header value, or a URL that could hold credentials.
Security¶
- Keep API keys on the server. Never ship them in a browser or mobile client; an Apple-platform app should reach
Jev through a backend of your own (
baseUrl) that adds the real key. - The key is sent as
Authorization: Bearer ...on every request, and redacted fromJevConfig.toString(). - Every request carries a
User-Agentofjev4k/<version>. Aheadersentry of the same name replaces the built-in one rather than adding a second value, so a gateway that needs its ownAuthorizationorAcceptcan have it. - Redirects are never followed. A 3xx response is reported as a
JevApiExceptionwith its status, because following one would send your headers, and on Node.js the request body, to whatever host it names. - A response whose declared
Content-Lengthis over 16 MiB is refused before its body is read into memory. A body without a declared length isn't checked; the per-attempttimeoutbounds how long it can stream. On Linux, Apple platforms and Node.js a compressed response has no declared length either: Curl,NSURLSessionand fetch ask for compression, decompress the body themselves and drop its length. On the JVM and Windows the engine doesn't ask for it.