# `TypeSafeAPI.Client`
[🔗](https://github.com/typesend/typesafe_ai/blob/v0.1.0-alpha.3/lib/typesafe_api/client.ex#L1)

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

`api_key`, `base_url`, `model`, `timeout`, `connect_timeout` and `finch` are
resolved from, in order:

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

`retry` and `req_options` are read from `new/1` only. They are structured
values, not strings, and a retry policy in config would be resolved at a
point where nothing can tell you it is wrong.

Values are validated wherever they come from, so a stray
`config :typesafe_api, base_url: :production` fails with a configuration
error at `new/1` rather than a `FunctionClauseError` on the first request.
Blank strings are ignored the same way in both, after trimming.

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 client's
`connect_timeout` is merged underneath, so it survives unless you set
`timeout:` inside `connect_options` yourself.

For that reason the connect timeout is a client setting, `connect_timeout`
(5 000 ms by default), and not the per-call `:timeout`. The per-call
`:timeout` bounds waiting for the response (`receive_timeout`) only, so
calls that differ in timeout still share one pool. See `TypeSafeAPI.HTTP` for
the full list of options that do and do not affect pool identity.

To size the pool yourself, start a `Finch` in your own supervision tree and
name it with `finch:`:

    children = [{Finch, name: MyApp.Finch, pools: %{default: [size: 50, count: 2]}}]
    TypeSafeAPI.new(api_key: key, finch: MyApp.Finch)

`Req` refuses `:finch` and `:connect_options` together, so a client with
`finch:` sends neither `connect_options` nor `connect_timeout`: connection
settings for a pool you own belong in its own child spec. `new/1` raises
rather than letting the two be set at once.

`req_options` is an escape hatch for `Req`, not a second way to configure
this library. `:retry`, `:auth`, `:base_url` and `:finch` are rejected there
with a message naming the client option to use instead; a `retry:` that
slipped through would run `Req`'s own retry loop nested inside this
library's, multiplying attempts and escaping the wall-clock budget.

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 `TypeSafeAPI.Error` bodies either;
those carry the request as sent minus the `Authorization` header.

# `t`

```elixir
@type t() :: %TypeSafeAPI.Client{
  api_key: String.t(),
  base_url: String.t(),
  connect_timeout: pos_integer(),
  finch: atom(),
  model: String.t(),
  req_options: keyword(),
  retry: TypeSafeAPI.Retry.t(),
  timeout: pos_integer()
}
```

# `new`

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

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

## Options

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

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

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

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

* `:connect_timeout` (`t:pos_integer/0`) - Timeout in milliseconds for establishing a connection. Defaults to 5 000. Client-level on purpose: it feeds `connect_options`, which selects the Finch pool, so it must not vary per call. Ignored when `finch:` is set, and overridden by a `timeout:` inside your own `connect_options`.

* `:finch` (`t:atom/0`) - Name of a `Finch` pool you start and supervise yourself, for control over pool size. Suppresses `connect_options` and `connect_timeout`, which `Req` refuses to combine with a named pool.

* `:retry` - Retry policy; see `TypeSafeAPI.Retry.new/1`. The default value is `[]`.

* `:req_options` - Escape hatch: options merged into the underlying `Req.Request`. `:retry`, `:auth`, `:base_url` and `:finch` are rejected; each has a client option of its own. The default value is `[]`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
