# `ReqManagedAgents.Providers.BedrockAgentCore.Client`
[🔗](https://github.com/cash-mckeeman/req_managed_agents/blob/v0.13.0/req_managed_agents/lib/req_managed_agents/providers/bedrock_agent_core/client.ex#L1)

SigV4-signed REST client for AWS AgentCore (`bedrock-agentcore`). Covers the
control-plane harness lifecycle (`create_harness`/`get_harness`/`get_harness_endpoint`/`delete_harness`),
the AgentCore Identity token-vault (`create_api_key_credential_provider`), and the
data-plane `invoke_harness` (returns a decoded `vnd.amazon.eventstream`).

Infra-agnostic: it knows AgentCore's model-config surface, nothing about what
sits behind a `liteLlmModelConfig.apiBase`. Build with `new/1`; pass as the
first arg to every call. Transport is injectable via `:req_options` for tests.

The invoke data plane streams incrementally: `receive_timeout` on this struct governs
control-plane calls only, while `invoke_harness/2` uses the per-invoke `:idle_timeout`
(default 300_000 ms) as an inter-chunk liveness guard — a healthy turn may run
arbitrarily long; only silence fails it.

# `t`

```elixir
@type t() :: %ReqManagedAgents.Providers.BedrockAgentCore.Client{
  base_url: String.t(),
  control_base_url: String.t(),
  credentials: ReqManagedAgents.AWS.SigV4.creds(),
  receive_timeout: timeout(),
  req_options: keyword(),
  service: String.t()
}
```

# `control_plane_attempts`

```elixir
@spec control_plane_attempts(:get | :post | :delete) :: pos_integer()
```

Worst-case number of HTTP attempts one control-plane call makes, by method.

`receive_timeout` applies per attempt, so a caller bounding a call by a
wall-clock budget has to divide by this — otherwise a hung request spends the
whole budget once per attempt.

`:get` and `:delete` pass `retry: :transient` and so make the initial request
plus `2` retries. `:post` (`CreateHarness`, credential providers)
passes no retry option and inherits Req's `:safe_transient` policy, which does
not retry a non-idempotent method — one attempt.

The count covers attempts only. Req also sleeps between them (exponential
backoff, or a `Retry-After` header), and Finch's connect timeout sits outside
`receive_timeout` — so a budget divided by this bounds the *receive* phase, not
the total wall-clock of a retried call.

# `create_api_key_credential_provider`

```elixir
@spec create_api_key_credential_provider(
  t() | ReqManagedAgents.AgentCore.Client.t(),
  map()
) ::
  {:ok, map()} | {:error, term()}
```

# `create_harness`

```elixir
@spec create_harness(
  t() | ReqManagedAgents.AgentCore.Client.t(),
  ReqManagedAgents.Providers.BedrockAgentCore.HarnessSpec.t() | map()
) :: {:ok, map()} | {:error, term()}
```

# `delete_harness`

```elixir
@spec delete_harness(t() | ReqManagedAgents.AgentCore.Client.t(), String.t()) ::
  {:ok, map()} | {:error, term()}
```

# `get_harness`

```elixir
@spec get_harness(t() | ReqManagedAgents.AgentCore.Client.t(), String.t()) ::
  {:ok, map()} | {:error, term()}
```

# `get_harness_endpoint`

```elixir
@spec get_harness_endpoint(
  t() | ReqManagedAgents.AgentCore.Client.t(),
  String.t(),
  String.t()
) ::
  {:ok, map()} | {:error, term()}
```

`GetHarnessEndpoint` (control plane). Returns the decoded body
`%{"endpoint" => %{"endpointName", "status", "arn", "liveVersion", ...}}`.

A harness and each of its endpoints carry **separate** statuses and reach READY
at different times, so a READY harness is not yet invokable. The data plane's
`qualifier` defaults to the `DEFAULT` endpoint, which makes that endpoint the
one a provision has to wait on.

# `invoke_agent_runtime_command`

```elixir
@spec invoke_agent_runtime_command(t() | ReqManagedAgents.AgentCore.Client.t(), map()) ::
  {:ok, ReqManagedAgents.Providers.BedrockAgentCore.CommandResult.t()}
  | {:error, term()}
```

Data-plane `InvokeAgentRuntimeCommand` — run a shell command inside the session's
microVM (no model loop, no token cost) and collect its streamed output.

`inv` requires `:agent_runtime_arn` (the harness/runtime ARN — it rides the URI
path), `:runtime_session_id`, and `:command`. Optional: `:timeout_seconds`
(server-side cap, service default 300, max 3600), `:idle_timeout` (inter-chunk
liveness guard, default 300_000 ms), `:on_output` (fn `(:stdout | :stderr, chunk)`
called per delta, in order), `:qualifier` (endpoint name).

Returns `{:ok, %ReqManagedAgents.Providers.BedrockAgentCore.CommandResult{}}` — a non-zero
`exit_code` is a RESULT, not an error. Transport/stream failures return
`{:error, term()}`.

# `invoke_harness`

```elixir
@spec invoke_harness(t() | ReqManagedAgents.AgentCore.Client.t(), map()) ::
  {:ok, [map()]} | {:error, term()}
```

Data-plane `InvokeHarness`. Sends `messages` with `harnessArn` in the query
string and the required `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` header.
Returns the decoded event-stream maps. Resume is just another call with the
resume messages (the profile assembles them).

Note: AWS requires `runtimeSessionId` to be at least 33 characters long
(pattern `[a-zA-Z0-9][a-zA-Z0-9-_]*`, max 100). The client passes it through
as-is — the caller is responsible for supplying a conforming value.

# `list_harnesses`

```elixir
@spec list_harnesses(
  t() | ReqManagedAgents.AgentCore.Client.t(),
  keyword()
) :: {:ok, map()} | {:error, term()}
```

One `ListHarnesses` page (control plane). Returns the decoded body
`%{"harnesses" => [%{"harnessName", "harnessId", "arn", "status", ...}], "nextToken" => ...}`.
Pass `:next_token` to continue and `:max_results` (1..100) to set the page size.
Tokens are opaque; the caller owns traversal and response validation.

# `new`

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

---

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