Turns a function's arguments and its @spec into JSON schema properties,
in the shape the Messages API expects for a tool's input_schema.
This is pure AST-to-schema mapping with no macro magic, kept separate
from Claudex.Tool so the type inference can be tested on its own. A
Mod.t() reference that isn't one of the built-in types (String.t(),
DateTime.t(), ...) is handed to Claudex.Tool.Schema.StructExpansion,
which expands it into a nested object when Mod is a struct or an Ecto
schema.
A typespec construct that can't be mapped raises
Claudex.Tool.SchemaError — see build/4.
Summary
Types
Threaded through every recursive call
A function parameter: its name and whether it has a default value.
Functions
Builds the input_schema properties for a function, given its argument
AST (as @on_definition receives it), the raw @spec entries
accumulated on the module so far, and the compile-time env (used to
resolve aliased struct references in the spec).
Extracts each parameter's name and whether it has a default value, from
the argument AST alone — no @spec involved, so this never raises.
Claudex.Tool uses this on its own when a tool is registered with an
explicit args_schema:, since that skips @spec inspection entirely.
Types
@type context() :: %{ visited: [module()], env: Macro.Env.t(), current_module: module() }
Threaded through every recursive call:
env— the compile-time environment, needed to resolve an aliased module reference (e.g.Ticket.t()afteralias My.App.Ticket) into its real module namevisited— struct modules already being expanded on the current path, so a self- or mutually-referential struct stops instead of recursing forevercurrent_module— which module a baret()(no module prefix) refers to right now. A struct's own@type tcan reference itself as plaint(), notMod.t()— this is how that resolves
A function parameter: its name and whether it has a default value.
Functions
@spec build(atom(), [Macro.t()], [tuple()], Macro.Env.t()) :: %{ properties: map(), params: [param()] }
Builds the input_schema properties for a function, given its argument
AST (as @on_definition receives it), the raw @spec entries
accumulated on the module so far, and the compile-time env (used to
resolve aliased struct references in the spec).
Also returns the parameter list in call order — the JSON schema alone
doesn't preserve it once properties becomes a map, and Claudex.Tool
needs the order later to dispatch tool calls correctly.
A parameter with no matching @spec at all gets an unconstrained
(%{}) property. A parameter whose @spec type Claudex can't map — an
unsupported typespec construct, or a Mod.t() that isn't a loaded struct
or Ecto schema — raises Claudex.Tool.SchemaError. Fix the spec, or pass
args_schema: for that tool.
Extracts each parameter's name and whether it has a default value, from
the argument AST alone — no @spec involved, so this never raises.
Claudex.Tool uses this on its own when a tool is registered with an
explicit args_schema:, since that skips @spec inspection entirely.