Normandy.Tools.Input (normandy v2.2.0)

View Source

Validates a tool call's input before the tool runs.

The dispatcher checks the model's input here and passes the result to Normandy.Tool.run/2 as context.input; it never goes into the tool struct.

  • A tool that declared input: Schema (use Normandy.Tool) gets a %Schema{}. Every key (string or atom) must name a field of the schema; the values are checked by Normandy.Schema.Validator (types, required fields, enums, ranges, formats; nothing is coerced, so "5" for an integer field is an error); missing optional fields take their defaults.
  • Any other tool gets a string-keyed map checked against its Normandy.Tool.input_schema/1 map (string or atom keys): every required key present, each value's basic JSON type (string, integer, number, boolean, object, array, null) where its property declares one, and unknown keys as follows. A schema that declares properties is closed: no key outside them unless additionalProperties is true or a schema (an absent additionalProperties counts as false here, unlike JSON Schema's default). A schema with no properties key declares no keys, so it limits none: any key is accepted (an MCP server's loose {"type": "object"} keeps working). Nested schemas, enums and formats are the tool's to check. A type name outside the seven above (a typo such as "intger") is not checked: any value passes it. An input_schema/1 that returns something other than a map, or a map whose properties is not a map or whose required is not a list of key names, is an error (constraint: :schema).

An unknown key is an error for a declared input schema and for a map that declares properties, and keys are matched against declared names, so model input never creates an atom. A key that is neither a string nor an atom is always an unknown key, named by its type ("a tuple key"). Errors are %{path: [key], message: String.t(), constraint: atom()}; message/1 joins them into one line for the model.

Summary

Functions

One line for the model: errors joined by "; ", each as "<path> <message>" (a path-less error is its message alone).

Validates input against tool's input schema; see the moduledoc.

Types

error()

@type error() :: %{
  path: [atom() | String.t()],
  message: String.t(),
  constraint: atom()
}

Functions

message(errors)

@spec message([error()]) :: String.t()

One line for the model: errors joined by "; ", each as "<path> <message>" (a path-less error is its message alone).

iex> Normandy.Tools.Input.message([%{path: [:query], message: "is required", constraint: :required}])
"query is required"

validate(tool, input)

@spec validate(
  struct(),
  term()
) :: {:ok, struct() | map()} | {:error, [error()]}

Validates input against tool's input schema; see the moduledoc.