Module: Axn::Tools
- Defined in:
- lib/axn/tools.rb,
lib/axn/exceptions.rb,
lib/axn/tools/invoker.rb,
lib/axn/tools/registry.rb,
lib/axn/tools/adapter_roots.rb,
lib/axn/tools/version_group.rb
Overview
The tool surface: registering an adapter, enumerating its tools, and validating their contracts.
This module is what an adapter gem names for all three. Registry and VersionGroup beneath it are
storage an adapter reaches THROUGH these methods rather than naming directly — the registry in
particular is free to change how membership is stored, and a VersionGroup arrives as versions'
return value.
The two other constants beneath it ARE adapter-facing, and an adapter names each directly: Invoker,
the sanctioned path for running an Axn as a tool (docs/reference/tool-invoker.md), and AdapterRoots,
the opt-in directory-membership mixin an adapter extends onto its own config module
(docs/recipes/authoring-tool-adapters.md).
for is a keyword in statement position, so every call inside axn writes the receiver
(Axn::Tools.for(...)); a receiverless for(...) would parse as a loop.
Defined Under Namespace
Modules: AdapterRoots, Registry Classes: InvalidContract, Invoker, VersionGroup
Class Method Summary collapse
-
.adapters ⇒ Object
The registered adapter keys.
-
.for(adapter, all_versions: false) ⇒ Object
An adapter's tools: the latest version per
tool_nameby default, sorted bytool_name; every version (by name, then ascending version) withall_versions: true. -
.register_adapter(key, config_source = nil) ⇒ Object
Registers an adapter key, optionally with the config source the registry reads
tool_rootsfrom. -
.validate_contracts! ⇒ Object
Validates every tool axn's contract, once each, and raises on the first invalid one.
-
.versions(adapter, tool_name) ⇒ Object
One logical tool's version group under
adapter(.allascending,.latest), or nil when nothing matches — for an adapter resolving a single name rather than walking the enumeration.
Class Method Details
.adapters ⇒ Object
The registered adapter keys. The read-companion to register_adapter, and the set every
method here validates against.
34 |
# File 'lib/axn/tools.rb', line 34 def adapters = Registry.adapters |
.for(adapter, all_versions: false) ⇒ Object
An adapter's tools: the latest version per tool_name by default, sorted by tool_name;
every version (by name, then ascending version) with all_versions: true.
38 39 40 |
# File 'lib/axn/tools.rb', line 38 def for(adapter, all_versions: false) Registry.members(_registered_adapter!(adapter), all_versions:) end |
.register_adapter(key, config_source = nil) ⇒ Object
Registers an adapter key, optionally with the config source the registry reads tool_roots
from. Idempotent, and a source-less re-registration never wipes a source already supplied
(see Registry#register_adapter).
28 29 30 |
# File 'lib/axn/tools.rb', line 28 def register_adapter(key, config_source = nil) Registry.register_adapter(key, config_source) end |
.validate_contracts! ⇒ Object
Validates every tool axn's contract, once each, and raises on the first invalid one.
A colliding or unrenderable property name is only harmful to a JSON projection, and for a tool axn the
projection is what an adapter hands a model — so the moment to learn about it is app setup, not a user's
tool call. This loads the configured tool directories and projects each tool once; the per-class memo means a
later input_schema from an adapter pays nothing.
Under Rails this runs automatically (config.after_initialize, and again on each config.to_prepare so a
dev reload re-validates). Without Rails there is no boot to hook, so an app calls this itself — typically
right after requiring its action files. Nothing else changes if it is never called: the same errors still
raise on first projection.
WHAT THIS COVERS, precisely — the guarantee is only as wide as enumeration.
Membership is the union of a directory grant and a DECLARATION grant (Registry#member?), and enumeration
honors both: a class that declares tool is enumerated with no tool root configured at all. What it cannot
see is a class that is not LOADED yet, since it walks the classes the registry has recorded. So:
- Nothing at all is validated unless at least one tool adapter is registered. With no adapter there are no tool roots and no membership to test, so this is a no-op — an app that expects setup validation must register the adapter its tools are for.
- A tool inside a configured tool root is loaded here (
ensure_loaded!) and validated, declaration-granted or directory-granted alike. - A
tool-DSL axn OUTSIDE every configured root is validated only if something already loaded it. Under eager loading (production) everything is loaded, so it is covered; in a lazily-loading development environment it is not, and falls back to validating on first projection. - Under Rails, Zeitwerk's
eager_load_dirloads a directory as one unit (it has no public API to load a managed file in isolation), so a file that raises aborts the rest of THAT directory — warn-logged by the registry, and the siblings it skipped are not validated here.
None of these makes an invalid contract reachable with no error at all: every gap falls back to the first projection, which is where every non-tool axn is validated anyway.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/axn/tools.rb', line 80 def validate_contracts! Registry.tool_classes.each do |klass| # BOTH sides go through PropertyNames rather than through `input_schema`/`output_schema`. Those names # belong to the class, and an adapter base that already defines them keeps them (see # Core::SchemaReflection) — so a tool subclassing its adapter's base class, which is the ordinary shape # of one, would have had its transport reader called and its contract validated by nothing at all. # PropertyNames performs the same builds and the same validations against axn's own projections, and the # outbound call additionally records the verdict `render` reads — so a tool validated at setup also # renders without paying for an output-schema build on its first result. Axn::Internal::Reflection::PropertyNames.validate_inbound!(klass) Axn::Internal::Reflection::PropertyNames.validate_outbound!(klass) rescue Axn::ContractViolation, ArgumentError => e # Named, because this runs over every tool at once: the underlying error describes the property and the # declarations that collide, but at boot the first thing an author needs is WHICH tool. Both families are # caught: a collision is an Axn::ContractViolation, an unrenderable name or an oversized schema an # ArgumentError. Either is reported as ITSELF, renamed — except where renaming would mean running the # exception's own code, which surfaces as Axn::Tools::InvalidContract (see _named_invalid_contract). # `cause:` explicitly, rather than leaving it to `$!`: reading a hostile `#message` means rescuing inside # this rescue, and Ruby does not restore `$!` to `e` afterwards — so the implicit cause was nil on exactly # the degraded paths where knowing the original matters most. raise _named_invalid_contract(klass, e), cause: e end nil end |
.versions(adapter, tool_name) ⇒ Object
One logical tool's version group under adapter (.all ascending, .latest), or nil when
nothing matches — for an adapter resolving a single name rather than walking the enumeration.
44 45 46 |
# File 'lib/axn/tools.rb', line 44 def versions(adapter, tool_name) Registry.version_group(_registered_adapter!(adapter), tool_name) end |