Skip to content
4 min read · 857 words

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.

MethodPurpose
artifact_toon_typeTOON format
artifact_toon_keysTop-level keys
artifact_toon_lengthElement 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).

MethodPurpose
artifact_yaml_type'single-document' or 'multi-document'
artifact_yaml_keysTop-level keys (deduplicated union for multi-document)
artifact_yaml_lengthDocument 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.

MethodPurpose
artifact_xml_rootRoot element name
artifact_xml_keysTop-level keys of root
artifact_xml_tagsAll distinct tag names in document
artifact_xml_lengthElement 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/.ctsts, .mjs/.cjsjs); defaults to ts.

Requires typescript@^5.9.3.

MethodPurpose
artifact_es_symbolsTop-level declarations with { kind, name, exported, startLine, endLine }; optional kind filter
artifact_es_importsImports with { moduleSpecifier, named[], default?, namespace?, typeOnly, line }
artifact_es_exportsExport declarations and re-exports
artifact_es_outlineClasses/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:

typescript
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:

typescript
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:

typescript
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:

typescript
{ text: 'inline content', call_id: 'call_xyz' }  // one or the other

The handler resolves, converts, and returns a new spooled artifact. When the model requests a converter, the executor invokes it and persists the result:

typescript
// 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

BatteryPeerVersion
TOON@toon-format/toon^4.1.1
YAMLjs-yamlalready core
XMLfast-xml-parser^5.11.1
EcmaScripttypescript^5.9.3

All peers optional. Query methods throw with installation instructions if peer is missing.