TypeSafe.Client (TypeSafe AI v0.1.0-alpha.1)

Copy Markdown View Source

Connection settings for the TypeSafe API, resolved once and passed around.

The client is a plain struct rather than a process: nothing about talking to the API needs shared state, so a struct keeps concurrency trivial (build once, use from any number of tasks) and keeps supervision trees out of your way.

Configuration precedence

Each setting is resolved from, in order:

  1. the options passed to new/1
  2. application config: config :typesafe_api, api_key: "...", model: "..."
  3. environment variables: TYPESAFE_API_KEY, TYPESAFE_BASE_URL, TYPESAFE_DEFAULT_MODEL
  4. the built-in defaults (https://api.typesafe.ai, jev-latest, 10 000 ms)

The API key has no default; new/1 raises ArgumentError when none is found.

Placement

Because it is a plain struct with no process behind it, a client is safe to share across processes: build one and pass it into every task, GenServer or controller that needs it. Build it in a function, not in a module attribute. A module attribute is evaluated at compile time, which reads the API key from whatever environment compiled the release rather than the one running it. A function called at application start, or a small Application.get_env lookup memoized in :persistent_term, keeps configuration at runtime where it belongs.

req_options merges rather than replaces. The client's req_options are applied first, then the ones passed to a single call, so a per-call option wins over the same key on the client. Anything the client sets and the call does not mention survives.

Connections

Req picks a Finch connection pool by the connect_options it is given, so two requests with different connect_options do not share a pool. Varying that option per call spins up a separate pool each time and throws away connection reuse. Set connect_options once on the client, in req_options, and leave it out of per-call options.

The API key is redacted by this module's Inspect implementation, so it does not leak through inspect/1, a crash dump or a logged struct. It is never copied into telemetry metadata or into TypeSafe.Error bodies either; those carry the request as sent minus the Authorization header.

Summary

Functions

Builds a client, resolving configuration as described in the module docs.

Types

t()

@type t() :: %TypeSafe.Client{
  api_key: String.t(),
  base_url: String.t(),
  model: String.t(),
  req_options: keyword(),
  retry: TypeSafe.Retry.t(),
  timeout: pos_integer()
}

Functions

new(opts \\ [])

@spec new(keyword()) :: t()

Builds a client, resolving configuration as described in the module docs.

Options

  • :api_key (String.t/0) - API key. Falls back to app config, then TYPESAFE_API_KEY.

  • :base_url (String.t/0) - API base URL. Falls back to TYPESAFE_BASE_URL, then the public API.

  • :model (String.t/0) - Default model. Falls back to TYPESAFE_DEFAULT_MODEL, then jev-latest.

  • :timeout (pos_integer/0) - Per-operation timeout in milliseconds. Defaults to 10 000.

  • :retry - Retry policy; see TypeSafe.Retry.new/1. The default value is [].

  • :req_options (keyword/0) - Escape hatch: options merged into the underlying Req.Request. The default value is [].