Current section
Files
Jump to
Current section
Files
guides/error-handling.md
# Error Handling
The SDK returns tuples for execution outcomes and uses a single structured error envelope: `%AmpSdk.Error{}`.
## `run/2` Return Values
`AmpSdk.run/2` returns `{:ok, result}` or `{:error, %AmpSdk.Error{}}`:
```elixir
case AmpSdk.run("do something") do
{:ok, result} ->
IO.puts(result)
{:error, %AmpSdk.Error{kind: :no_result}} ->
IO.puts("No result received")
{:error, %AmpSdk.Error{kind: kind, message: message}} ->
IO.puts("#{kind}: #{message}")
end
```
Common `kind` values include `:cli_not_found`, `:command_timeout`,
`:stream_timeout`, `:run_deadline_exceeded`, `:unsupported_capability`,
`:task_timeout`, `:command_failed`, `:execution_failed`, and
`:invalid_configuration`.
## Exception Types
`AmpSdk.Error` can be raised in explicit bang APIs or direct constructor usage:
- `AmpSdk.Error` (primary exception envelope)
Use tuple returns where possible and pattern-match on `%AmpSdk.Error{}` in application code.
## Shared-Core Transport Errors
The SDK no longer exposes a separate Amp-owned raw transport wrapper. Shared
core transport failures still normalize into tagged tuples or exceptions under
the hood:
```elixir
{:error, {:transport, reason}}
```
Normalize these when you need the unified envelope:
```elixir
error = AmpSdk.Error.normalize({:transport, :timeout}, kind: :transport_error)
```
## Streaming Errors
When using `AmpSdk.execute/2`, errors are delivered inline as `ErrorResultMessage` structs rather than raising exceptions:
```elixir
AmpSdk.execute("prompt")
|> Enum.each(fn
%AmpSdk.Types.ErrorResultMessage{error: error, permission_denials: denials} ->
IO.puts("Error: #{error}")
if denials, do: IO.puts("Denied tools: #{inspect(denials)}")
%AmpSdk.Types.ResultMessage{result: result} ->
IO.puts("Success: #{result}")
_ -> :ok
end)
```
## Timeout Handling
Use `Options.stream_timeout_ms` for idle receive control and
`Options.run_deadline_ms` for a non-rearming total limit:
```elixir
alias AmpSdk.Types.Options
AmpSdk.execute("slow task", %Options{
stream_timeout_ms: 30_000,
run_deadline_ms: 180_000
})
|> Enum.to_list()
```
Requests for `completion_only: true` or a non-`nil` `output_schema` fail before
CLI resolution with `kind: :unsupported_capability`. The error details retain
the Core `provider`, `feature`, `option`, and `support_state` fields.
You can still wrap long-running calls in a `Task` if you need outer cancellation:
```elixir
task = Task.async(fn -> AmpSdk.run("slow task") end)
case Task.yield(task, 30_000) || Task.shutdown(task) do
{:ok, {:ok, result}} -> IO.puts(result)
{:ok, {:error, %AmpSdk.Error{message: msg}}} -> IO.puts("Error: #{msg}")
nil -> IO.puts("Timed out after 30s")
end
```