Artifact batteries
Structured queries over spooled artifacts let the model navigate formatted output without materialising full bytes. Six families: JSON and Markdown (core), plus TOON, YAML, XML, and EcmaScript (batteries).
TOON artifacts
TOON is a token-efficient JSON encoding. SpooledToonArtifact uses JSONPath for all queries (same surface as JSON).
Requires @toon-format/toon@^4.1.1.
| Method | Purpose |
|---|---|
artifact_toon_type | TOON format |
artifact_toon_keys | Top-level keys |
artifact_toon_length | Element count |
artifact_toon_get(path) | JSONPath evaluation |
artifact_toon_filter(path) | JSONPath filter matches |
artifact_toon_slice(start?, end?) | Array slice |
artifact_toon_pluck(path) | Collect values at path |
Converters
toonToJsonTool and jsonToToonTool accept either inline text or call_id, returning a new queryable artifact on the next iteration. Use jsonToToonTool to shrink large artifacts.
YAML artifacts
SpooledYamlArtifact queries both single and multi-document streams. Multi-document streams (delimited by ---) report count via yaml_length and evaluate paths against all documents.
Requires js-yaml@^4.1.1 (already core; install if missing).
| Method | Purpose |
|---|---|
artifact_yaml_type | 'single-document' or 'multi-document' |
artifact_yaml_keys | Top-level keys (deduplicated union for multi-document) |
artifact_yaml_length | Document count |
artifact_yaml_get(path) | JSONPath evaluation |
artifact_yaml_filter(path) | JSONPath filter matches |
artifact_yaml_slice(start?, end?) | Array slice |
artifact_yaml_pluck(path) | Collect values at path |
Converters
yamlToJsonTool and jsonToYamlTool accept inline text or call_id. Non-finite numbers (.NaN, .inf, -.inf) are preserved through conversion.
XML artifacts
SpooledXmlArtifact parses to JSON with attributes prefixed _ by default. An attribute href="x" becomes _href; use $..["_href"] for recursive queries.
The prefix is settable per instance via the constructor's attributeNamePrefix, but @_ is a poor choice and the default deliberately avoids it. jsonpath-plus reads a leading @ in a path segment as its type-selector sigil (@string(), @number(), @path), and it does so before honouring quotes — so $..["@_href"] throws Unknown value type _hr rather than matching. Quoting is not an escape: single quotes, double quotes and dot notation all fail identically.
Such a key is not wholly unreachable, but the workarounds are awkward enough to be worth avoiding. A filter expression works in every case, because inside it the key is an ordinary JavaScript string: $..[?(@['@_href'])]. A wildcard works only when it lands exactly one level above the key, which means the correct path depends on the document's shape — repeated sibling elements collapse into an array, so $.root.a[*]["@_href"] matches where $.root.*["@_href"] throws, and for non-repeated siblings it is the other way round. Recursive descent, the query style a model reaches for when exploring an unfamiliar document, never works. The restriction is specific to @: #text, $ref and _href all query normally.
Requires fast-xml-parser@^5.11.1.
| Method | Purpose |
|---|---|
artifact_xml_root | Root element name |
artifact_xml_keys | Top-level keys of root |
artifact_xml_tags | All distinct tag names in document |
artifact_xml_length | Element count |
artifact_xml_get(path) | JSONPath evaluation |
artifact_xml_filter(path) | JSONPath filter matches |
artifact_xml_pluck(path) | Collect values at path |
Converters
xmlToJsonTool and jsonToXmlTool accept inline text or call_id. Conversions are not lossless — XML attribute/element/text distinctions do not round-trip; assert what converters do rather than expecting fidelity.
EcmaScript artifacts
SpooledEcmaScriptArtifact queries source code structure via TypeScript compiler API. No converters. Infers scriptKind from extension (.mts/.cts → ts, .mjs/.cjs → js); defaults to ts.
Requires typescript@^5.9.3.
| Method | Purpose |
|---|---|
artifact_es_symbols | Top-level declarations with { kind, name, exported, startLine, endLine }; optional kind filter |
artifact_es_imports | Imports with { moduleSpecifier, named[], default?, namespace?, typeOnly, line } |
artifact_es_exports | Export declarations and re-exports |
artifact_es_outline | Classes/interfaces with nested members[] |
artifact_es_signature(name) | Signature text (parameters, return type, type parameters) |
artifact_es_jsdoc(name) | Attached JSDoc comment |
artifact_es_references(name) | Lines and columns where identifier appears (syntactic only, no scope resolution) |
Import Paths
Import subclasses and converters from their respective battery barrels:
import { SpooledToonArtifact, toonToJsonTool, jsonToToonTool } from '@nhtio/adk/batteries/artifacts/toon'
import { SpooledYamlArtifact, yamlToJsonTool, jsonToYamlTool } from '@nhtio/adk/batteries/artifacts/yaml'
import { SpooledXmlArtifact, xmlToJsonTool, jsonToXmlTool } from '@nhtio/adk/batteries/artifacts/xml'
import { SpooledEcmaScriptArtifact } from '@nhtio/adk/batteries/artifacts/ecmascript'The aggregate battery barrel also re-exports all four classes:
import {
SpooledToonArtifact,
SpooledYamlArtifact,
SpooledXmlArtifact,
SpooledEcmaScriptArtifact,
} from '@nhtio/adk/batteries/artifacts'Converters are available from both the aggregate barrel and their format-specific subpaths; prefer the format-specific subpath to avoid bundling unused batteries' module graphs.
Decoding
Call registerArtifactEncodables once at startup to enable decoding:
import { registerArtifactEncodables } from '@nhtio/adk/batteries/artifacts'
await registerArtifactEncodables()Idempotent; safe to call multiple times. Encoding requires no setup.
Converter workflow
Converters accept either inline text or an artifact reference — provide exactly one non-empty value:
{ text: 'inline content', call_id: 'call_xyz' } // one or the otherThe handler resolves, converts, and returns a new spooled artifact. When the model requests a converter, the executor invokes it and persists the result:
// Inside the executor: model requested jsonToToonTool
const tool = ctx.tools.get('json_to_toon')
const result = await tool.executor(ctx)({ text: largeJson })
await ctx.storeToolCall(toolCall) // with result spooled
// Iteration 2: artifact is forged; model can query it
// artifact_toon_get, artifact_toon_filter, etc.DispatchRunner picks up the new artifact on the next iteration and forges its query tools.
Peer Dependencies
| Battery | Peer | Version |
|---|---|---|
| TOON | @toon-format/toon | ^4.1.1 |
| YAML | js-yaml | already core |
| XML | fast-xml-parser | ^5.11.1 |
| EcmaScript | typescript | ^5.9.3 |
All peers optional. Query methods throw with installation instructions if peer is missing.