{"version":3,"file":"xml-CYseDztH.mjs","names":["#attributeNamePrefix","#ignoreAttributes","#resolveParsed","#parsed"],"sources":["../src/batteries/artifacts/xml/exceptions.ts","../src/batteries/artifacts/xml/index.ts"],"sourcesContent":["/**\n * Battery-scoped exceptions for the XML artifact battery.\n *\n * @remarks\n * Internal sibling of the `@nhtio/adk/batteries/artifacts/xml` entry — re-exported from the battery's\n * own barrel per the battery-scoped-exceptions rule. These are the typed errors the\n * implementor-facing API throws; the agent-facing forge catches them and renders readable\n * failure strings the model can act on.\n */\n\nimport { createException } from '@nhtio/adk/factories'\n\n/**\n * Thrown when the fast-xml-parser optional peer is not installed or fails to load.\n *\n * @remarks\n * The message template includes the package name, a description of its purpose, the underlying\n * error, and the exact install command, so the consumer sees a complete, actionable message\n * regardless of call site.\n */\nexport const E_XML_PARSER_PEER_MISSING = createException<[string]>(\n  'E_XML_PARSER_PEER_MISSING',\n  'the XML battery could not load its peer dependency \"fast-xml-parser\" (needed for XML format artifact queries): %s — install it (pnpm add fast-xml-parser)',\n  'E_XML_PARSER_PEER_MISSING',\n  500,\n  true\n)\n\n/**\n * Thrown when XML parsing fails due to malformed markup.\n *\n * @remarks\n * Printf args: `[errorDetail]` — includes the parser's own reason for the failure.\n */\nexport const E_XML_PARSE_FAILED = createException<[string]>(\n  'E_XML_PARSE_FAILED',\n  'XML parse failed: %s',\n  'E_XML_PARSE_FAILED',\n  422,\n  false\n)\n","/**\n * A structured artifact battery for XML documents.\n *\n * @module @nhtio/adk/batteries/artifacts/xml\n *\n * @remarks\n * Provides {@link SpooledXmlArtifact} — a `SpooledArtifact` specialisation that parses XML\n * into a JSON projection and offers path-based query tools over that projection. The XML-to-JSON\n * mapping defaults to `ignoreAttributes: false` and `attributeNamePrefix: '_'`, both settable\n * per instance through the constructor; `preserveOrder: false` is fixed. Under the default\n * mapping:\n *\n * - Element attributes become keys prefixed with `_` (e.g., `root-element` with `href=\"x\"`\n *   becomes `{ a: { _href: 'x', ... } }`).\n * - Element text content becomes a `#text` key when the element also has attributes or siblings.\n * - Repeated sibling elements collapse into an array.\n * - The full projection is queryable via JSONPath expressions (e.g., `$..name` finds all name\n *   elements anywhere in the tree). Attributes are accessed via underscore-prefixed keys,\n *   e.g. `$..[\"_href\"]` for the href attribute (recursive descent).\n *\n * The battery also exports two converter `Tool` constants: `xmlToJsonTool` and `jsonToXmlTool`.\n * These accept either inline XML/JSON text or a reference to an artifact produced earlier in\n * the turn, and return the converted result as a new `SpooledJsonArtifact` or\n * `SpooledXmlArtifact`.\n *\n * Battery exceptions are defined in this module's `exceptions.ts` file — `createException`\n * re-exported from `@nhtio/adk/factories`. Decoding a {@link SpooledXmlArtifact} instance via\n * `decode()` throws until `registerArtifactEncodables()` has run (see\n * {@link @nhtio/adk/batteries/artifacts!registerArtifactEncodables}).\n */\n\nimport { v6 as uuidv6 } from 'uuid'\nimport { JSONPath } from 'jsonpath-plus'\nimport { validator } from '@nhtio/validation'\nimport { resolveSpoolReader } from '@nhtio/adk/common'\nimport { SpooledJsonArtifact } from '@nhtio/adk/common'\nimport { isInstanceOf, isError, isObject } from '@nhtio/adk/guards'\nimport { E_XML_PARSER_PEER_MISSING, E_XML_PARSE_FAILED } from './exceptions'\nimport { ArtifactTool, Tool, ToolRegistry, ReaderDescriptor } from '@nhtio/adk/common'\n\n// Well-known @nhtio/encoder contract keys, resolved through the global symbol registry.\n// These are identical to the symbols core uses, with no import edge on the optional peer.\nconst ENCODE_METHOD: unique symbol = Symbol.for('@nhtio/encoder:toEncoded')\nconst DECODE_METHOD: unique symbol = Symbol.for('@nhtio/encoder:fromEncoded')\nimport {\n  SpooledArtifact,\n  collectArtifactCompatibleIds,\n  resolveArtifactById,\n  defaultSerialise,\n} from '@nhtio/adk/spooled_artifact'\nimport type { SpoolReader } from '@nhtio/adk/types'\nimport type { XMLParser, XMLBuilder } from 'fast-xml-parser'\nimport type { ToolMethodDescriptor, DispatchContext } from '@nhtio/adk/types'\n\n/** Snapshot payload for the encoder contract; the encoder treats it as opaque. */\ntype AdkEncodableSnapshot = unknown\n\n/**\n * Lazy-loaded promise for the `fast-xml-parser` module, cached at module scope.\n */\nlet xmlParserPromise:\n  | Promise<{ XMLParser: typeof XMLParser; XMLBuilder: typeof XMLBuilder }>\n  | undefined\n\n/**\n * Lazily import and cache the `fast-xml-parser` module with battery-scoped error handling.\n *\n * @returns The imported module.\n * @throws {@link E_XML_PARSER_PEER_MISSING} when the module is unavailable.\n */\nasync function getXmlParser(): Promise<{\n  XMLParser: typeof XMLParser\n  XMLBuilder: typeof XMLBuilder\n}> {\n  xmlParserPromise ??= import('fast-xml-parser').catch((err) => {\n    const detail = isError(err) ? err.message : String(err)\n    throw new E_XML_PARSER_PEER_MISSING([detail])\n  })\n  return xmlParserPromise\n}\n\n/**\n * A {@link @nhtio/adk!SpooledArtifact} specialisation that adds XML-aware read operations.\n *\n * @remarks\n * The artifact parses XML into a JSON projection on first access and caches it for the\n * lifetime of the instance. The projection uses `ignoreAttributes` and `attributeNamePrefix` as\n * supplied to the constructor — defaulting to `false` and `'_'` — with `preserveOrder: false`.\n *\n * Under this mapping, an XML element like:\n *\n * `root-element` with an `href` attribute and text content becomes a JSON object like:\n * `{ root: { element: { _href: 'value', '#text': 'content' } } }`\n *\n * Attributes are prefixed with `_` by default rather than the conventional `@_`, because\n * `jsonpath-plus` reads a leading `@` in a path segment as its type-selector sigil before\n * honouring quotes: `$..[\"@_href\"]` throws `Unknown value type _hr`, while `$..[\"_href\"]`\n * matches. Text nodes use the `#text` key.\n *\n * All XML methods are async, consistent with {@link @nhtio/adk!SpooledArtifact}.\n *\n * Path-based methods (`xml_get`, `xml_filter`, `xml_pluck`) use\n * [JSONPath-Plus](https://github.com/JSONPath-Plus/JSONPath) expressions over the projection.\n * Full JSONPath syntax is supported, including recursive descent (`..`), filter expressions,\n * and union selectors.\n */\nexport class SpooledXmlArtifact extends SpooledArtifact {\n  #parsed: Record<string, unknown> | undefined\n  #attributeNamePrefix: string\n  #ignoreAttributes: boolean\n\n  /**\n   * @param reader - The backing store to read from.\n   * @param options - Optional parser configuration.\n   * @param options.attributeNamePrefix - Prefix for attribute keys (default: '_'). Set to '@_'\n   *   if you need the conventional XML-to-JSON mapping, but be aware that naming such a key in a\n   *   JSONPath segment fails — `$..[\"@_attr\"]` throws `Unknown value type _at`, and quoting does\n   *   not escape it. A filter expression still reaches it (`$..[?(@['@_attr'])]`); a wildcard\n   *   does too, but only when placed exactly one level above the key, so the working path\n   *   depends on whether repeated elements collapsed into an array.\n   * @param options.ignoreAttributes - When true, ignore element attributes (default: false).\n   */\n  constructor(\n    reader: SpoolReader,\n    options?: {\n      attributeNamePrefix?: string\n      ignoreAttributes?: boolean\n    }\n  ) {\n    super(reader)\n    this.#attributeNamePrefix = options?.attributeNamePrefix ?? '_'\n    this.#ignoreAttributes = options?.ignoreAttributes ?? false\n  }\n\n  /**\n   * Returns `true` if `value` is a {@link SpooledXmlArtifact} instance.\n   *\n   * @remarks\n   * Uses the cross-realm-safe {@link @nhtio/adk!isInstanceOf} guard. Safe against the\n   * dual-module-copy case where two distinct `SpooledXmlArtifact` classes coexist in the same\n   * realm.\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is a {@link SpooledXmlArtifact} instance.\n   */\n  public static isSpooledXmlArtifact(value: unknown): value is SpooledXmlArtifact {\n    return isInstanceOf(value, 'SpooledXmlArtifact', SpooledXmlArtifact)\n  }\n\n  /**\n   * Returns the effective XML-to-JSON parser configuration for this artifact.\n   *\n   * @remarks\n   * When converting an XML artifact to JSON by call_id, the converter inherits the source\n   * artifact's attribute prefix and ignoreAttributes setting. This accessor exposes those\n   * options so the converter can apply them consistently.\n   *\n   * @returns An object with `attributeNamePrefix` and `ignoreAttributes` keys.\n   */\n  public getParserOptions(): {\n    attributeNamePrefix: string\n    ignoreAttributes: boolean\n  } {\n    return {\n      attributeNamePrefix: this.#attributeNamePrefix,\n      ignoreAttributes: this.#ignoreAttributes,\n    }\n  }\n\n  /**\n   * The XML-specific artifact-query descriptors this class adds on top of the base set.\n   *\n   * @remarks\n   * Lists `artifact_xml_root`, `artifact_xml_keys`, `artifact_xml_tags`, `artifact_xml_length`,\n   * `artifact_xml_get`, `artifact_xml_filter`, `artifact_xml_pluck`. The base seven descriptors\n   * (`artifact_head`, etc.) are NOT included here — they are forged separately by\n   * {@link SpooledXmlArtifact.forgeTools}.\n   */\n  public static toolMethods: ReadonlyArray<ToolMethodDescriptor> = Object.freeze([\n    {\n      name: 'artifact_xml_root',\n      method: 'xml_root',\n      description: 'The root element name of the XML document produced earlier in this turn.',\n    },\n    {\n      name: 'artifact_xml_keys',\n      method: 'xml_keys',\n      description:\n        'Keys directly under the root element of the XML document produced earlier in this turn.',\n    },\n    {\n      name: 'artifact_xml_tags',\n      method: 'xml_tags',\n      description:\n        'Every distinct element name in the XML document produced earlier in this turn, deduplicated. ' +\n        'Call this before writing a path to an unfamiliar document — it tells you what elements are present.',\n    },\n    {\n      name: 'artifact_xml_length',\n      method: 'xml_length',\n      description:\n        'Element count when the root element contains an array of children; otherwise 1. ' +\n        'The XML document was produced earlier in this turn.',\n    },\n    {\n      name: 'artifact_xml_get',\n      method: 'xml_get',\n      argsSchema: validator.object({\n        path: validator\n          .string()\n          .required()\n          .description(\n            'JSONPath expression to query the document. Attributes appear with a leading underscore (e.g., _href). Use bracket notation for recursive descent: $..[\"_href\"].'\n          ),\n      }),\n      description:\n        'Query the XML document via JSONPath. The document was produced earlier in this turn. ' +\n        'Attributes appear as underscore-prefixed keys (e.g., _href for an href attribute). Use bracket notation in recursive-descent queries: $..[\"_href\"].',\n    },\n    {\n      name: 'artifact_xml_filter',\n      method: 'xml_filter',\n      argsSchema: validator.object({\n        path: validator\n          .string()\n          .required()\n          .description(\n            'JSONPath expression to filter the document. Attributes appear with a leading underscore (e.g., _href).'\n          ),\n      }),\n      description:\n        'Return XML elements whose content matches a JSONPath filter. Differs from artifact_xml_get: get returns matched values; filter returns the element objects containing them. The document was produced earlier in this turn. Attributes appear as underscore-prefixed keys (e.g., _href).',\n    },\n    {\n      name: 'artifact_xml_pluck',\n      method: 'xml_pluck',\n      argsSchema: validator.object({\n        path: validator\n          .string()\n          .required()\n          .description(\n            'JSONPath expression to pluck values. Attributes appear with a leading underscore (e.g., _href).'\n          ),\n      }),\n      description:\n        'Alias for artifact_xml_get — extract values matching a JSONPath. The document was produced earlier in this turn. ' +\n        'Attributes appear as underscore-prefixed keys (e.g., _href).',\n    },\n  ])\n\n  /**\n   * The root element name of the XML document.\n   *\n   * @returns The name of the root element.\n   * @throws Error when the document is malformed or empty.\n   */\n  async xml_root(): Promise<string> {\n    const parsed = await this.#resolveParsed()\n    const keys = Object.keys(parsed)\n    if (keys.length === 0) {\n      throw new Error('XML document is empty')\n    }\n    return keys[0]\n  }\n\n  /**\n   * Keys directly under the root element.\n   *\n   * @returns Array of key names at the root level.\n   */\n  async xml_keys(): Promise<string[]> {\n    const parsed = await this.#resolveParsed()\n    const rootKey = Object.keys(parsed)[0]\n    if (!rootKey) return []\n    const rootContent = parsed[rootKey]\n    if (!isObject(rootContent)) return []\n    return Object.keys(rootContent)\n  }\n\n  /**\n   * Every distinct element name in the document, deduplicated.\n   *\n   * @remarks\n   * Walks the entire projection recursively, excluding attribute keys (prefixed with the\n   * configured `attributeNamePrefix`, default `_`) and `#text` keys. This is what a model\n   * calls before it can write a path into an unfamiliar document.\n   *\n   * @returns Sorted array of unique element names.\n   */\n  async xml_tags(): Promise<string[]> {\n    const parsed = await this.#resolveParsed()\n    const tags = new Set<string>()\n    const attributePrefix = this.#attributeNamePrefix\n\n    function walk(obj: unknown): void {\n      if (!isObject(obj)) return\n      for (const [key, value] of Object.entries(obj)) {\n        // Skip attribute keys and text nodes\n        if (key.startsWith(attributePrefix) || key === '#text') {\n          continue\n        }\n        tags.add(key)\n        if (Array.isArray(value)) {\n          for (const item of value) {\n            walk(item)\n          }\n        } else {\n          walk(value)\n        }\n      }\n    }\n\n    walk(parsed)\n    return Array.from(tags).sort()\n  }\n\n  /**\n   * Element count when the root contains an array of children; otherwise 1.\n   *\n   * @returns Number of elements.\n   */\n  async xml_length(): Promise<number> {\n    const parsed = await this.#resolveParsed()\n    const rootKey = Object.keys(parsed)[0]\n    if (!rootKey) return 0\n    const rootContent = parsed[rootKey]\n    if (Array.isArray(rootContent)) {\n      return rootContent.length\n    }\n    return 1\n  }\n\n  /**\n   * Query the document via JSONPath expression.\n   *\n   * @param path - A JSONPath expression (e.g., `'$..name'`).\n   * @returns Array of matched values.\n   */\n  async xml_get(path: string): Promise<unknown[]> {\n    const parsed = await this.#resolveParsed()\n    const results = JSONPath({ path, json: parsed })\n    return results\n  }\n\n  /**\n   * Returns the elements (subtrees) that match a JSONPath expression.\n   *\n   * @remarks\n   * Evaluates the path against the XML projection and returns the elements that\n   * contain matching values. Unlike xml_get (which returns matched values), xml_filter\n   * returns the element objects containing those matches.\n   *\n   * Candidate set definition for XML's single-rooted projection:\n   * - If the root element contains an array of repeated siblings (e.g., root.item\n   *   where item is an array property), filters across those siblings and returns\n   *   elements that have matching content.\n   * - If the root element contains a single object, evaluates the path against it\n   *   and returns it when matched.\n   *\n   * The path is evaluated using JSONPath-Plus with resultType 'all' to extract\n   * parent elements of matched values. The immediate parent object of any matched\n   * value is included in the result, deduplicating elements.\n   *\n   * Example: for XML with root containing multiple 'item' elements each with an\n   * 'id' attribute, xml_filter('$.root.item[*]._id') returns the item elements\n   * that have an id attribute, whereas xml_get would return the id values themselves.\n   *\n   * @param path - A JSONPath expression (e.g. '$.root.item[*]._id' or '$[?(@.status)]').\n   * @returns Array of matching element subtrees. Empty array when no matches found.\n   */\n  async xml_filter(path: string): Promise<unknown[]> {\n    const parsed = await this.#resolveParsed()\n    const rootKey = Object.keys(parsed)[0]\n    if (!rootKey) return []\n\n    const rootContent = parsed[rootKey]\n\n    // If the root content is an array of siblings, filter the array elements\n    if (Array.isArray(rootContent)) {\n      // Evaluate the path against each sibling element to see if it matches\n      return rootContent.filter((element) => {\n        const matches = JSONPath({ path, json: element as object })\n        return Array.isArray(matches) && matches.length > 0\n      })\n    }\n\n    // If the root content is a single object, use 'all' resultType to get parent objects\n    if (isObject(rootContent)) {\n      // Evaluate the path against the full parsed document to get match information\n      const allMatches = JSONPath({\n        path,\n        json: parsed,\n        resultType: 'all',\n      }) as unknown\n\n      if (!Array.isArray(allMatches) || allMatches.length === 0) {\n        return []\n      }\n\n      // Extract parent objects, deduplicating by reference\n      const parentSet = new Set<object>()\n      for (const match of allMatches) {\n        const m = match as Record<string, unknown>\n        if (isObject(m.parent)) {\n          parentSet.add(m.parent as object)\n        }\n      }\n\n      return Array.from(parentSet)\n    }\n\n    // For scalar or other types, return empty\n    return []\n  }\n\n  /**\n   * Alias for xml_get — extract values matching a JSONPath.\n   *\n   * @param path - A JSONPath expression (e.g., `'$..name'`).\n   * @returns Array of matched values.\n   */\n  async xml_pluck(path: string): Promise<unknown[]> {\n    return this.xml_get(path)\n  }\n\n  /**\n   * Standard subclass extension pattern: call `SpooledArtifact.forgeTools(ctx)` to produce\n   * the base seven `artifact_*` tools narrowed to any `SpooledArtifact` in the turn, then\n   * register one `ArtifactTool` per XML-specific descriptor narrowed to XML artifacts.\n   */\n  public static override forgeTools(ctx: DispatchContext): ToolRegistry {\n    const registry = SpooledArtifact.forgeTools(ctx)\n    const requires = SpooledXmlArtifact\n    const compatibleIds = collectArtifactCompatibleIds(ctx, requires)\n    if (compatibleIds.length === 0) return registry\n\n    for (const descriptor of this.toolMethods) {\n      const callIdSchema = validator\n        .string()\n        .valid(...compatibleIds)\n        .required()\n        .description('ToolCall id of the artifact to query.')\n\n      const argsSchema = (\n        descriptor.argsSchema ?? validator.object<Record<string, never>>({})\n      ).append({\n        callId: callIdSchema,\n      })\n\n      const tool = new ArtifactTool({\n        name: descriptor.name,\n        description: descriptor.description,\n        inputSchema: argsSchema,\n        ephemeral: true,\n        onCollision: 'replace',\n        handler: async (rawArgs, ctxInner) => {\n          const args = rawArgs as Record<string, unknown> & { callId: string }\n          const resolved = resolveArtifactById(ctxInner, args.callId, requires)\n          if (!resolved) return `Error: no artifact with id ${args.callId} in this turn`\n          const artifact = resolved.artifact\n          const methodArgs: unknown[] = []\n          if (\n            descriptor.method === 'xml_get' ||\n            descriptor.method === 'xml_filter' ||\n            descriptor.method === 'xml_pluck'\n          ) {\n            methodArgs.push(args.path as string)\n          }\n          const fn = (artifact as unknown as Record<string, (...a: unknown[]) => unknown>)[\n            descriptor.method\n          ]\n          if (typeof fn !== 'function') {\n            return `Error: artifact has no method ${descriptor.method}`\n          }\n          const result = await Promise.resolve(fn.apply(artifact, methodArgs))\n          const serialise = descriptor.serialise ?? defaultSerialise\n          return serialise(result)\n        },\n      })\n      registry.register(tool)\n    }\n    return registry\n  }\n\n  /**\n   * Parses and caches the XML projection.\n   *\n   * @returns The parsed projection as a JSON object.\n   * @throws {@link E_XML_PARSE_FAILED} when XML parsing fails.\n   */\n  async #resolveParsed(): Promise<Record<string, unknown>> {\n    if (this.#parsed !== undefined) {\n      return this.#parsed\n    }\n\n    const parser = await getXmlParser()\n    const xmlParser = new parser.XMLParser({\n      ignoreAttributes: this.#ignoreAttributes,\n      attributeNamePrefix: this.#attributeNamePrefix,\n      preserveOrder: false,\n    })\n\n    const content = await this.asString()\n    try {\n      this.#parsed = xmlParser.parse(content) as Record<string, unknown>\n    } catch (err) {\n      const detail = isError(err) ? err.message : String(err)\n      throw new E_XML_PARSE_FAILED([detail])\n    }\n    return this.#parsed\n  }\n\n  /**\n   * Serialise this SpooledXmlArtifact into an `@nhtio/encoder` snapshot — the reader **handle**\n   * plus the constructor options for `attributeNamePrefix` and `ignoreAttributes`.\n   *\n   * @remarks\n   * Overrides {@link SpooledArtifact.[ENCODE_METHOD]} to carry the constructor's options\n   * (the parsed projection cache is derived and not encoded). Round-trips via\n   * {@link SpooledXmlArtifact.[DECODE_METHOD]}.\n   *\n   * @returns A snapshot consumed by {@link SpooledXmlArtifact.[DECODE_METHOD]}.\n   */\n  [ENCODE_METHOD](): AdkEncodableSnapshot {\n    return {\n      reader: this.readerDescriptor(),\n      attributeNamePrefix: this.#attributeNamePrefix,\n      ignoreAttributes: this.#ignoreAttributes,\n    }\n  }\n\n  /**\n   * Reconstruct a {@link SpooledXmlArtifact} from a {@link SpooledXmlArtifact.[ENCODE_METHOD]}\n   * snapshot.\n   *\n   * @param data - The snapshot produced by {@link SpooledXmlArtifact.[ENCODE_METHOD]}.\n   * @returns A fresh {@link SpooledXmlArtifact} backed by a freshly-resolved reader.\n   */\n  static [DECODE_METHOD](data: AdkEncodableSnapshot): SpooledXmlArtifact {\n    const snapshot = data as {\n      reader: ReaderDescriptor\n      attributeNamePrefix?: string\n      ignoreAttributes?: boolean\n    }\n    return new SpooledXmlArtifact(resolveSpoolReader(snapshot.reader), {\n      attributeNamePrefix: snapshot.attributeNamePrefix,\n      ignoreAttributes: snapshot.ignoreAttributes,\n    })\n  }\n}\n\n/**\n * A tool that converts XML (inline or from an artifact) to JSON.\n *\n * @remarks\n * Input is either `text` (inline XML) or `call_id` (an XML artifact from earlier in this turn).\n * Provide exactly one. Returns a new {@link SpooledJsonArtifact}.\n */\nexport const xmlToJsonTool = new Tool({\n  name: 'xml_to_json',\n  description:\n    'Convert XML (inline or from an artifact produced earlier in this turn) to JSON. ' +\n    'Returns a queryable JSON artifact.',\n  inputSchema: validator.object({\n    text: validator\n      .string()\n      .optional()\n      .allow('')\n      .description('Inline XML text. Provide this or call_id, not both.'),\n    call_id: validator\n      .string()\n      .optional()\n      .allow('')\n      .description('ToolCall id of an XML artifact produced earlier in this turn.'),\n  }),\n  artifactConstructor: () => SpooledJsonArtifact,\n  handler: async (rawArgs, ctx) => {\n    const args = rawArgs as { text?: string; call_id?: string }\n    const text = (args.text ?? '').trim()\n    const callId = (args.call_id ?? '').trim()\n\n    // Validate input\n    if (!text && !callId) {\n      return 'Error: provide either text or call_id, not both or neither'\n    }\n    if (text && callId) {\n      return 'Error: provide either text or call_id, not both'\n    }\n\n    let xmlContent: string\n    let sourceId: string\n    let attributeNamePrefix: string\n    let ignoreAttributes: boolean\n\n    if (callId) {\n      const resolved = resolveArtifactById(ctx, callId, SpooledXmlArtifact)\n      if (!resolved) {\n        return `Error: no artifact with id ${callId} in this turn`\n      }\n      const xmlArtifact = resolved.artifact as SpooledXmlArtifact\n      xmlContent = await xmlArtifact.asString()\n      sourceId = callId\n      // Inherit the source artifact's parser configuration\n      const sourceOptions = xmlArtifact.getParserOptions()\n      attributeNamePrefix = sourceOptions.attributeNamePrefix\n      ignoreAttributes = sourceOptions.ignoreAttributes\n    } else {\n      xmlContent = text\n      sourceId = uuidv6()\n      // Use defaults for inline conversions\n      attributeNamePrefix = '_'\n      ignoreAttributes = false\n    }\n\n    // Parse XML to JSON projection\n    const parser = await getXmlParser()\n    const xmlParser = new parser.XMLParser({\n      ignoreAttributes,\n      attributeNamePrefix,\n      preserveOrder: false,\n    })\n\n    let parsed: unknown\n    try {\n      parsed = xmlParser.parse(xmlContent)\n    } catch (err) {\n      const detail = isError(err) ? err.message : String(err)\n      return `Error: XML parse failed: ${detail}`\n    }\n\n    // Convert to JSON string\n    const jsonString = JSON.stringify(parsed)\n\n    // Spool and return\n    const reader = await ctx.storeRetrievableBytes(`${ctx.id}:xml_to_json:${sourceId}`, jsonString)\n    return new SpooledJsonArtifact(reader)\n  },\n})\n\n/**\n * A tool that converts JSON (inline or from an artifact) to XML.\n *\n * @remarks\n * Input is either `text` (inline JSON) or `call_id` (a JSON artifact from earlier in this turn).\n * Provide exactly one. Returns a new {@link SpooledXmlArtifact}.\n *\n * Note: XML has no faithful JSON inverse. A JSON document that never came from XML may not\n * rebuild into sensible markup. This tool converts on a best-effort basis; the result may not\n * round-trip perfectly back to the original JSON.\n */\nexport const jsonToXmlTool = new Tool({\n  name: 'json_to_xml',\n  description:\n    'Convert JSON (inline or from an artifact produced earlier in this turn) to XML. ' +\n    'Returns a queryable XML artifact. ' +\n    'Note: XML has no faithful JSON inverse — a JSON document that never came from XML may not rebuild into sensible markup.',\n  inputSchema: validator.object({\n    text: validator\n      .string()\n      .optional()\n      .allow('')\n      .description('Inline JSON text. Provide this or call_id, not both.'),\n    call_id: validator\n      .string()\n      .optional()\n      .allow('')\n      .description('ToolCall id of a JSON artifact produced earlier in this turn.'),\n  }),\n  artifactConstructor: () => SpooledXmlArtifact,\n  handler: async (rawArgs, ctx) => {\n    const args = rawArgs as { text?: string; call_id?: string }\n    const text = (args.text ?? '').trim()\n    const callId = (args.call_id ?? '').trim()\n\n    // Validate input\n    if (!text && !callId) {\n      return 'Error: provide either text or call_id, not both or neither'\n    }\n    if (text && callId) {\n      return 'Error: provide either text or call_id, not both'\n    }\n\n    let jsonContent: unknown\n    let sourceId: string\n\n    if (callId) {\n      const resolved = resolveArtifactById(ctx, callId, SpooledJsonArtifact)\n      if (!resolved) {\n        return `Error: no artifact with id ${callId} in this turn`\n      }\n      const jsonString = await resolved.artifact.asString()\n      try {\n        jsonContent = JSON.parse(jsonString)\n      } catch (err) {\n        const detail = isError(err) ? err.message : String(err)\n        return `Error: JSON parse failed: ${detail}`\n      }\n      sourceId = callId\n    } else {\n      try {\n        jsonContent = JSON.parse(text)\n      } catch (err) {\n        const detail = isError(err) ? err.message : String(err)\n        return `Error: JSON parse failed: ${detail}`\n      }\n      sourceId = uuidv6()\n    }\n\n    // Convert JSON to XML via builder\n    const parser = await getXmlParser()\n    const xmlBuilder = new parser.XMLBuilder({\n      ignoreAttributes: false,\n      attributeNamePrefix: '_',\n      format: false,\n    })\n\n    let xmlString: string\n    try {\n      xmlString = xmlBuilder.build(jsonContent) as string\n    } catch (err) {\n      const detail = isError(err) ? err.message : String(err)\n      return `Error: XML build failed: ${detail}`\n    }\n\n    // Spool and return\n    const reader = await ctx.storeRetrievableBytes(`${ctx.id}:json_to_xml:${sourceId}`, xmlString)\n    return new SpooledXmlArtifact(reader)\n  },\n})\n\n/**\n * Re-export the exceptions for battery-scoped error handling.\n *\n * @remarks\n * Battery exceptions are defined in `exceptions.ts` and re-exported here per the\n * battery-scoped-exceptions pattern — consumers of `@nhtio/adk/batteries/artifacts/xml`\n * can import these exception classes directly.\n */\nexport { E_XML_PARSER_PEER_MISSING, E_XML_PARSE_FAILED } from './exceptions'\n"],"mappings":"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"}