# `A2A.Extension`
[🔗](https://github.com/actioncard/a2a-elixir/blob/main/lib/a2a/extension.ex#L1)

Behaviour for A2A v1.0 protocol extensions.

An extension module advertises a URI in its `c:declaration/1`, optionally
participates in per-request activation via `c:activate/3`, and can hook into
the request/response pipeline via `c:handle_request/3` and
`c:handle_response/3`. Configured on `A2A.Plug` and `A2A.Client` as a list
of `module()` or `{module(), keyword()}` tuples.

See the [A2A v1.0 extensions topic](https://a2a-protocol.org/dev/topics/extensions/)
for protocol-level semantics. This module implements the data-only and
profile extension categories; method extensions (registering new RPC
methods) and state-machine extensions are not yet supported.

## Defining an extension

    defmodule MyApp.TimestampExtension do
      @behaviour A2A.Extension
      @uri "https://example.com/ext/timestamp"

      @impl true
      def declaration(_state) do
        %A2A.AgentExtension{uri: @uri, description: "Adds request timestamps"}
      end

      @impl true
      def activate(_requested_uris, _ctx, _state) do
        {:ok, System.system_time(:millisecond)}
      end

      @impl true
      def handle_response(task, _params, started_at) do
        {:ok, A2A.Extension.put_metadata(task, __MODULE__, %{started_at: started_at}),
         started_at}
      end
    end

## Activation model

At Plug/Client init time each configured extension's `c:init/1` is called
with its opts; the returned state is kept alongside the module. Per
request, the server parses the `A2A-Extensions` header and for each
configured extension whose declared URI is in the requested set, calls
`c:activate/3` with the requested URI list, the request context, and the
init state. `c:activate/3` may return `{:ok, activation}` to participate,
`:skip` to opt out for this request, or `{:error, error}` to abort with
a JSON-RPC error. The list of activated URIs is echoed in the response
`A2A-Extensions` header.

Required extensions (`required: true` in `c:declaration/1`) that are not
declared in the client's request header trigger
`ExtensionSupportRequiredError` (-32008) before activation runs.

## Hooks

Activated extensions can mutate the request or response:

- `c:handle_request/3` — receives the decoded message, JSON-RPC params,
  and the extension's activation. Runs in declaration order before the
  request is dispatched to the agent.
- `c:handle_response/3` — receives the task, JSON-RPC params, and the
  activation. Runs in declaration order after the agent replies and
  before the task is encoded.

Both callbacks are optional; data-only extensions implement neither.

## Reading activations inside an agent

Activations are surfaced to the agent's `handle_message/2` as
`context.extensions`, a `%{uri => activation}` map. The
`A2A.Extension.fetch/2` and `A2A.Extension.activated?/2` helpers look up
by module rather than URI string.

    def handle_message(message, context) do
      case A2A.Extension.fetch(context, MyApp.TimestampExtension) do
        {:ok, started_at} -> use_timestamp(message, started_at)
        :error -> handle_without(message)
      end
    end

## Configuring extensions

Server side, pass `:extensions` to `A2A.Plug`. The configured
declarations are merged into `capabilities.extensions` on the served
agent card, and the negotiation pipeline runs around every JSON-RPC
dispatch.

    # Standalone with Bandit
    Bandit.start_link(
      plug: {A2A.Plug,
        agent: MyAgent,
        base_url: "http://localhost:4000",
        extensions: [A2A.Extension.Timestamp, {MyApp.Passport, issuer: "acme"}]}
    )

    # Or in a Phoenix router
    forward "/a2a", A2A.Plug,
      agent: MyAgent,
      base_url: "http://localhost:4000/a2a",
      extensions: [A2A.Extension.Timestamp]

Client side, pass `:extensions` to `A2A.Client.new/2`. The configured
URIs are sent in the `A2A-Extensions` request header on every call.
`A2A.Client.parse_extensions_header/1` and
`A2A.Client.activated/2` read the server's response header to discover
which extensions actually ran.

    client = A2A.Client.new("http://localhost:4000",
      extensions: [A2A.Extension.Timestamp])

    {:ok, task} = A2A.Client.send_message(client, "hi")
    task.metadata[A2A.Extension.Timestamp.uri()]
    #=> %{"received_at" => ..., "completed_at" => ...}

## Reference

`A2A.Extension.Timestamp` ships in-tree as a complete profile-extension
example covering `c:declaration/1`, `c:activate/3`,
`c:handle_request/3`, and `c:handle_response/3`. See
[`examples/extensions.exs`](https://github.com/actioncard/a2a-elixir/blob/main/examples/extensions.exs)
for an end-to-end runnable demo.

# `activation`

```elixir
@type activation() :: term()
```

An extension's per-request activation value (opaque to the framework).

# `activations`

```elixir
@type activations() :: [{module(), activation(), String.t()}]
```

Ordered list of activations for hook chaining.

# `compiled`

```elixir
@type compiled() :: {module(), state(), A2A.AgentExtension.t()}
```

Internal compiled extension entry. Pipeline state: the module, its init
state, and its (cached) declaration.

# `config_entry`

```elixir
@type config_entry() :: module() | {module(), keyword()}
```

User-facing extension configuration entry.

# `state`

```elixir
@type state() :: term()
```

An extension's init state (returned from `c:init/1`).

# `activate`
*optional* 

```elixir
@callback activate(requested_uris :: [String.t()], ctx :: map(), state()) ::
  {:ok, activation()} | :skip | {:error, A2A.JSONRPC.Error.t()}
```

Called once per request when the client has declared this extension in
its `A2A-Extensions` header. Returns the activation value that is
threaded through subsequent hooks, or `:skip` to opt out for this
request, or `{:error, error}` to abort the request.

Defaults to `{:ok, nil}` (always activate, no per-request state).

# `declaration`

```elixir
@callback declaration(state()) :: A2A.AgentExtension.t()
```

Returns the `%A2A.AgentExtension{}` describing this extension. The URI is
used both for agent-card advertisement and for matching against the
`A2A-Extensions` request header.

# `handle_request`
*optional* 

```elixir
@callback handle_request(A2A.Message.t(), params :: map(), activation()) ::
  {:ok, A2A.Message.t(), params :: map(), activation()}
  | {:error, A2A.JSONRPC.Error.t()}
```

Optional. Mutate the inbound message or JSON-RPC params before dispatch.
Runs in declaration order across activated extensions.

# `handle_response`
*optional* 

```elixir
@callback handle_response(A2A.Task.t() | A2A.Message.t(), params :: map(), activation()) ::
  {:ok, A2A.Task.t() | A2A.Message.t(), activation()}
```

Optional. Mutate the outbound task or message before encoding. Runs in
declaration order across activated extensions.

# `init`
*optional* 

```elixir
@callback init(opts :: keyword()) :: state()
```

Validates and compiles the extension's options. Called once when the
parent plug or client is initialised. The returned value is threaded
back into the remaining callbacks as the `state` argument.

Defaults to ignoring opts and returning `nil`.

# `activate`

```elixir
@spec activate([compiled()], [String.t()], map()) ::
  {:ok, activations(), [String.t()]} | {:error, A2A.JSONRPC.Error.t()}
```

Runs `activate/3` for each configured extension whose declared URI is in
the requested list. Returns an ordered list of `{module, activation, uri}`
tuples and the matching URI list, or an error if any extension's
`activate/3` returned `{:error, ...}`.

# `activated?`

```elixir
@spec activated?(map(), module()) :: boolean()
```

Returns `true` if the given extension module is activated in the context.

# `declarations`

```elixir
@spec declarations([compiled()]) :: [A2A.AgentExtension.t()]
```

Returns the declarations of all compiled extensions, in declaration order.

# `declared_uris`

```elixir
@spec declared_uris([compiled()]) :: [String.t()]
```

Returns the URIs declared by all compiled extensions, in declaration order.

# `fetch`

```elixir
@spec fetch(map(), module()) :: {:ok, activation()} | :error
```

Fetches the activation value for the given extension module from a
request context. Returns `{:ok, activation}` or `:error`.

# `parse_header`

```elixir
@spec parse_header([String.t()] | String.t() | nil) :: [String.t()]
```

Parses a list of raw `A2A-Extensions` header values (HTTP allows multiple
values, each potentially comma-separated) into a deduplicated list of
requested URIs.

# `put_metadata`

```elixir
@spec put_metadata(
  A2A.Message.t() | A2A.Artifact.t() | A2A.Task.t(),
  module(),
  term()
) ::
  A2A.Message.t() | A2A.Artifact.t() | A2A.Task.t()
```

Convenience for namespacing a value under an extension's URI inside the
`metadata` field of a `Message`, `Artifact`, or `Task`.

# `required_uris`

```elixir
@spec required_uris([compiled()]) :: [String.t()]
```

Returns the URIs of declarations that are marked `required: true`.

# `run_request`

```elixir
@spec run_request(activations(), A2A.Message.t(), map()) ::
  {:ok, A2A.Message.t(), map(), activations()} | {:error, A2A.JSONRPC.Error.t()}
```

Runs the `handle_request/3` chain over an activated extension list.
Returns possibly-mutated message, params, and the (possibly-updated)
activations list, or `{:error, error}` if any extension aborted.

# `run_response`

```elixir
@spec run_response(activations(), A2A.Task.t() | A2A.Message.t(), map()) ::
  {:ok, A2A.Task.t() | A2A.Message.t(), activations()}
```

Runs the `handle_response/3` chain over an activated extension list.
Returns the possibly-mutated task or message and the updated activations list.

# `to_context_map`

```elixir
@spec to_context_map(activations()) :: %{required(String.t()) =&gt; activation()}
```

Converts the ordered activations list into the `%{uri => activation}`
map exposed to the agent via `context.extensions`.

# `validate_required`

```elixir
@spec validate_required([compiled()], [String.t()]) :: :ok | {:error, [String.t()]}
```

Validates required-extension presence against the client's requested URIs.
Returns `:ok` or `{:error, missing_uris}`.

---

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