# `PtcRunner.SubAgent.Signature`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/sub_agent/signature.ex#L1)

Signature parsing and validation for SubAgents.

Signatures define the contract between agents and tools:
- Input parameters - What the caller must provide
- Output type - What the callee will return

## Signature Format

Full format: `(params) -> output`
Shorthand: `output` (equivalent to `() -> output`)

## Types

- Primitives: `:string`, `:int`, `:float`, `:bool`, `:keyword`, `:any`, `:datetime`
- Collections: `[:type]` (list), `{field :type}` (map), `:map` (untyped map)
- Optional: `:type?` (nullable field or parameter)

## Examples

    iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("(name :string) -> {greeting :string}")
    iex> sig
    {:signature, [{"name", :string}], {:map, [{"greeting", :string}]}}

    iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("{count :int}")
    iex> sig
    {:signature, [], {:map, [{"count", :int}]}}

# `field`

```elixir
@type field() :: {String.t(), type()}
```

# `param`

```elixir
@type param() :: {String.t(), type()}
```

# `return_type`

```elixir
@type return_type() :: type()
```

# `signature`

```elixir
@type signature() :: {:signature, [param()], return_type()}
```

# `type`

```elixir
@type type() ::
  :string
  | :int
  | :float
  | :bool
  | :keyword
  | :any
  | :map
  | :datetime
  | {:optional, type()}
  | {:list, type()}
  | {:map, [field()]}
  | {:closed_map, [field()]}
```

# `validation_error`

```elixir
@type validation_error() :: %{
  path: [String.t() | non_neg_integer()],
  message: String.t()
}
```

# `from_json_schema`

```elixir
@spec from_json_schema(map()) :: {:ok, type()} | {:error, String.t()}
```

Convert a JSON Schema subset map to a PTC signature return type.

Inverse of `type_to_json_schema/1`. Supports scalar types (`string`,
`integer`, `number`, `boolean`), `array` with `items`, and `object`
with `properties`/`required`/`additionalProperties`. Unsupported JSON
Schema features (combinators, `$ref`, `enum`, `pattern`, etc.) return
`{:error, reason}`.

`additionalProperties: false` produces a closed map (`{:closed_map, ...}`)
whose validation rejects undeclared fields. `additionalProperties: true`
(or its absence) produces an open `{:map, ...}` that tolerates extras.
Every `required` entry must be a string naming a declared property,
otherwise an `{:error, reason}` is returned.

## Examples

    iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "integer"})
    {:ok, :int}

    iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "array", "items" => %{"type" => "string"}})
    {:ok, {:list, :string}}

    iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{"count" => %{"type" => "integer"}}, "required" => ["count"]})
    {:ok, {:map, [{"count", :int}]}}

    iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{"name" => %{"type" => "string"}}})
    {:ok, {:map, [{"name", {:optional, :string}}]}}

    iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{"x" => %{"type" => "integer"}}, "required" => ["x"], "additionalProperties" => false})
    {:ok, {:closed_map, [{"x", :int}]}}

    iex> match?({:error, _}, PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{}, "required" => ["missing"]}))
    true

# `parse`

```elixir
@spec parse(String.t()) :: {:ok, signature()} | {:error, String.t()}
```

Parse a signature string into internal format.

Returns `{:ok, signature()}` or `{:error, reason}`.

## Examples

    iex> PtcRunner.SubAgent.Signature.parse("(id :int) -> {name :string}")
    {:ok, {:signature, [{"id", :int}], {:map, [{"name", :string}]}}}

    iex> PtcRunner.SubAgent.Signature.parse("() -> :string")
    {:ok, {:signature, [], :string}}

    iex> PtcRunner.SubAgent.Signature.parse("{count :int}")
    {:ok, {:signature, [], {:map, [{"count", :int}]}}}

    iex> match?({:error, _}, PtcRunner.SubAgent.Signature.parse("invalid"))
    true

# `render`

```elixir
@spec render(signature()) :: String.t()
```

Format a signature back to string representation.

Used for rendering in prompts or debugging.

# `returns_list?`

```elixir
@spec returns_list?(signature()) :: boolean()
```

Check if signature returns a list type.

Used to determine if text mode response needs unwrapping.

# `to_json_schema`

```elixir
@spec to_json_schema(signature()) :: map()
```

Convert a signature to JSON Schema format.

Extracts the return type and converts it to a JSON Schema
that can be passed to LLM providers for structured output.

Note: Array return types are wrapped in an object with an "items" property
because most LLM providers require an object at the root level. Use
`returns_list?/1` to check if unwrapping is needed.

## Examples

    iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> {sentiment :string, score :float}")
    iex> PtcRunner.SubAgent.Signature.to_json_schema(sig)
    %{
      "type" => "object",
      "properties" => %{
        "sentiment" => %{"type" => "string"},
        "score" => %{"type" => "number"}
      },
      "required" => ["sentiment", "score"],
      "additionalProperties" => false
    }

    iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> [:int]")
    iex> PtcRunner.SubAgent.Signature.to_json_schema(sig)
    %{
      "type" => "object",
      "properties" => %{
        "items" => %{"type" => "array", "items" => %{"type" => "integer"}}
      },
      "required" => ["items"],
      "additionalProperties" => false
    }

# `validate`

```elixir
@spec validate(signature(), term()) :: :ok | {:error, [validation_error()]}
```

Validate data against a signature's return type.

Returns `:ok` or `{:error, [validation_error()]}`.

## Examples

    iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> {count :int, items [:string]}")
    iex> PtcRunner.SubAgent.Signature.validate(sig, %{count: 5, items: ["a", "b"]})
    :ok

    iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> :int")
    iex> PtcRunner.SubAgent.Signature.validate(sig, "not an int")
    {:error, [%{path: [], message: "expected int, got string"}]}

# `validate_input`

```elixir
@spec validate_input(signature(), map()) :: :ok | {:error, [validation_error()]}
```

Validate input parameters against a signature.

Returns `:ok` or `{:error, [validation_error()]}`.

---

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