{"version":3,"file":"forge.cjs","names":[],"sources":["../../../src/batteries/media/forge.ts"],"sourcesContent":["/**\n * Agent glue: forge ADK tools from a media pipeline, so a model can drive local media work.\n *\n * @module @nhtio/adk/batteries/media/forge\n *\n * @remarks\n * The pipeline core (`@nhtio/adk/batteries/media`) is agent-agnostic; this module is the layer\n * that knows the ADK loop. {@link forgeMediaTools} mints `Tool` instances over a configured\n * {@link @nhtio/adk/batteries/media!MediaPipeline} in one of two surfaces (the consumer picks\n * per deployment):\n *\n * - `'composite'` — one `media_query` tool taking `{ media_id, q }` (a pipe expression — the\n *   headline LLM DSL) or `{ media_id, ops }` (structured form), plus `list_media`. The tool\n *   description embeds the engine-narrowed verb grammar so the model never sees a verb the\n *   deployment can't run. One round-trip for multi-step work.\n * - `'granular'` — one narrow tool per available verb (`doc_select`, `sheet_update_cells`, …),\n *   each internally a one-verb plan. Friendlier to small models; bigger tool list.\n *\n * Media flows in by reference: the model passes `media_id` values it discovered via the\n * `list_media` tool (or inline id markers, where the LLM battery renders them); the resolver\n * scans `ctx.turnMessages[].attachments` and `ctx.turnToolCalls[].results` by default. Outputs\n * are persisted through `ctx.storeMediaBytes` and returned as `Media.toolGenerated(...)`, so\n * file results land on `ToolCall.results` as first-class media.\n *\n * Processing failures return readable strings (`Error (CODE): …`) the model can act on; the\n * pipe DSL's own syntax/semantic errors render the same way, so a model can repair its\n * statement and retry. An optional {@link ToolGateFn} runs before every execution — the seam\n * for human-approval/RBAC flows built on `ctx.waitFor` (the ADK gates primitive).\n */\n\nimport { toPipe } from './plan'\nimport { EMPTY_MIME } from './contracts'\nimport { isError } from '@nhtio/adk/guards'\nimport { availableVerbs } from './validate'\nimport { validator } from '@nhtio/validation'\nimport { VERB_INDEX, foldVerb } from './verbs'\nimport { E_INVALID_MEDIA_PIPELINE_CONFIG } from './exceptions'\n// Documented exception (see CONTRIBUTING.md → Design Decisions → #13 Battery design): this module\n// mints ADK `Tool` instances (`new Tool(...)`), calls `Media` static factories (`Media.isMedia`,\n// `Media.toolGenerated`), and hands `SpooledJsonArtifact` to the forge as a constructor value\n// (`artifactConstructor: () => SpooledJsonArtifact`) — all genuine runtime construction, not\n// type-position use, so these stay value imports.\nimport { Tool, Media, SpooledJsonArtifact } from '@nhtio/adk/common'\nimport type { MediaPipeline } from './index'\nimport type { EngineRegistry } from './registry'\nimport type { MediaOp, MediaArgValue } from './plan'\nimport type { VerbSpec, VerbArgSpec } from './verbs'\nimport type { DispatchContext } from '@nhtio/adk/types'\nimport type { StepPayload, PlanResult } from './runtime'\n\n/**\n * Resolves a `media_id` to a {@link @nhtio/adk!Media} visible in the current dispatch.\n * The default implementation scans turn messages' attachments and prior tool-call results.\n */\nexport type MediaResolverFn = (\n  ctx: DispatchContext,\n  mediaId: string\n) => Media | undefined | Promise<Media | undefined>\n\n/**\n * Optional per-call gate run before any pipeline execution. Throwing aborts the call and\n * surfaces through the standard tool-error path. The canonical implementation awaits\n * `ctx.waitFor({ reason: 'tool_approval', payload: call })` and throws on denial — WHO\n * approves and HOW is the consumer's contract; this is the seam.\n */\nexport type ToolGateFn = (\n  ctx: DispatchContext,\n  call: { tool: string; args: unknown }\n) => void | Promise<void>\n\n/** Options for {@link forgeMediaTools}. */\nexport interface ForgeMediaToolsOptions {\n  /** Which tool surface to mint. */\n  surface: 'composite' | 'granular'\n  /** Media-id resolution. Default: scan turn attachments + tool-call Media results. */\n  resolveMedia?: MediaResolverFn\n  /** Optional pre-execution gate (see {@link ToolGateFn}). */\n  gate?: ToolGateFn\n  /** Per-tool name/description overrides, keyed by the minted tool's default name. */\n  overrides?: Record<string, { name?: string; description?: string }>\n}\n\n/**\n * The default media resolver: scan `ctx.turnMessages[].attachments`, then\n * `ctx.turnToolCalls[].results`, for a Media with the given id.\n *\n * @param ctx - The dispatch context.\n * @param mediaId - The id to find.\n * @returns The Media, or `undefined` when nothing in the turn carries that id.\n */\nexport const defaultResolveMedia: MediaResolverFn = (ctx, mediaId) => {\n  for (const message of ctx.turnMessages) {\n    for (const attachment of message.attachments ?? []) {\n      if (attachment.id === mediaId) return attachment\n    }\n  }\n  for (const toolCall of ctx.turnToolCalls) {\n    const results = toolCall.results\n    if (Media.isMedia(results) && results.id === mediaId) return results\n    if (Array.isArray(results)) {\n      const hit = results.find((r) => Media.isMedia(r) && r.id === mediaId)\n      if (hit) return hit as Media\n    }\n  }\n  return undefined\n}\n\n/** Enumerate every Media visible in the turn (for `list_media` and MEDIA_NOT_FOUND hints). */\nconst enumerateMedia = (\n  ctx: DispatchContext\n): Array<{\n  id: string\n  filename: string\n  kind: string\n  mimeType: string\n  source?: string\n  origin: string\n}> => {\n  const out: Array<{\n    id: string\n    filename: string\n    kind: string\n    mimeType: string\n    source?: string\n    origin: string\n  }> = []\n  for (const message of ctx.turnMessages) {\n    for (const attachment of message.attachments ?? []) {\n      out.push({\n        id: attachment.id,\n        filename: attachment.filename,\n        kind: attachment.kind,\n        mimeType: attachment.mimeType,\n        source: attachment.source,\n        origin: 'attachment',\n      })\n    }\n  }\n  for (const toolCall of ctx.turnToolCalls) {\n    const results = toolCall.results\n    const mediaList = Media.isMedia(results)\n      ? [results]\n      : Array.isArray(results)\n        ? results.filter((r): r is Media => Media.isMedia(r))\n        : []\n    for (const media of mediaList) {\n      out.push({\n        id: media.id,\n        filename: media.filename,\n        kind: media.kind,\n        mimeType: media.mimeType,\n        source: media.source,\n        origin: 'tool-result',\n      })\n    }\n  }\n  return out\n}\n\nconst failure = (code: string, message: string): string => `Error (${code}): ${message}`\n\n// ── media generation (the empty:<format> sentinel) ───────────────────────────\n\n/**\n * Extract the format token from an `empty:<format>` sentinel id, or undefined for normal ids.\n * Harness-minted ids are UUIDs, which can never start with `empty:` — the sentinel occupies\n * input space that was previously a guaranteed MEDIA_NOT_FOUND.\n */\nconst emptyFormatOf = (mediaId: string): string | undefined => {\n  if (!mediaId.toLowerCase().startsWith('empty:')) return undefined\n  return mediaId.slice('empty:'.length).trim().toLowerCase()\n}\n\n/** The format tokens creatable in this deployment (reachable from {@link EMPTY_MIME}). */\nconst creatableFormats = (registry: EngineRegistry): readonly string[] =>\n  registry.convertTargets(EMPTY_MIME)\n\n/** Materialize a new blank media payload for the sentinel, or a model-actionable failure. */\nconst materializeEmpty = async (\n  registry: EngineRegistry,\n  format: string,\n  signal?: AbortSignal\n): Promise<{ ok: true; payload: StepPayload } | { ok: false; failure: string }> => {\n  const creatable = creatableFormats(registry)\n  if (!creatable.includes(format)) {\n    return {\n      ok: false,\n      failure: failure(\n        'EMPTY_FORMAT_UNAVAILABLE',\n        `cannot create \"${format}\" media in this deployment. Creatable formats: ${creatable.join(', ') || '(none)'}. Do not retry this format here.`\n      ),\n    }\n  }\n  const result = await registry.convert({\n    bytes: new Uint8Array(0),\n    mimeType: EMPTY_MIME,\n    filename: 'untitled',\n    to: format,\n    signal,\n  })\n  const output = result.outputs[0]\n  if (!output) {\n    return {\n      ok: false,\n      failure: failure('EMPTY_GENERATION_FAILED', `generating \"${format}\" produced no output`),\n    }\n  }\n  return {\n    ok: true,\n    payload: {\n      bytes: output.bytes,\n      mimeType: output.mimeType,\n      filename: `untitled.${format}`,\n    },\n  }\n}\n\nconst mediaKindOf = (mimeType: string): 'image' | 'audio' | 'video' | 'document' => {\n  const mime = mimeType.toLowerCase()\n  if (mime.startsWith('image/')) return 'image'\n  if (mime.startsWith('audio/')) return 'audio'\n  if (mime.startsWith('video/')) return 'video'\n  return 'document'\n}\n\n/** Load a resolved Media's bytes into a pipeline payload. */\nconst toPayload = async (media: Media): Promise<StepPayload> => ({\n  bytes: await media.asBytes(),\n  mimeType: media.mimeType,\n  filename: media.filename,\n})\n\n/** Persist a payload through the dispatch byte conduit and mint a first-party Media. */\nconst toMedia = async (\n  ctx: DispatchContext,\n  payload: StepPayload,\n  toolName: string\n): Promise<Media> => {\n  const id = crypto.randomUUID()\n  const reader = await ctx.storeMediaBytes(id, payload.bytes)\n  return Media.toolGenerated({\n    id,\n    kind: mediaKindOf(payload.mimeType),\n    mimeType: payload.mimeType,\n    filename: payload.filename,\n    reader,\n    source: `tool:${toolName}`,\n  })\n}\n\n/** Render a plan result as a tool handler return value. */\nconst renderResult = async (\n  ctx: DispatchContext,\n  result: PlanResult,\n  toolName: string\n): Promise<string | Media | Media[]> => {\n  if (result.kind === 'media') return toMedia(ctx, result.payload, toolName)\n  if (result.kind === 'media-list') {\n    return Promise.all(result.payloads.map((p) => toMedia(ctx, p, toolName)))\n  }\n  return typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2)\n}\n\n/** Resolve + acquire the input media or produce a model-actionable failure string. */\nconst acquire = async (\n  ctx: DispatchContext,\n  registry: EngineRegistry,\n  resolveMedia: MediaResolverFn,\n  mediaId: string\n): Promise<{ ok: true; payload: StepPayload } | { ok: false; failure: string }> => {\n  // The empty:<format> sentinel creates NEW media instead of resolving existing bytes.\n  const format = emptyFormatOf(mediaId)\n  if (format !== undefined) return materializeEmpty(registry, format, ctx.abortSignal)\n  const media = await resolveMedia(ctx, mediaId)\n  if (!media) {\n    const visible = enumerateMedia(ctx)\n    const creatable = creatableFormats(registry)\n    const createHint =\n      creatable.length > 0\n        ? ` To create NEW media instead, pass media_id \"empty:<format>\" (available: ${creatable.join(', ')}).`\n        : ''\n    const hint =\n      visible.length > 0\n        ? `Visible media ids: ${visible.map((m) => `${m.id} (${m.filename})`).join(', ')}`\n        : 'No media is visible in this turn — ask the user to attach a file, or call list_media to check.'\n    return {\n      ok: false,\n      failure: failure('MEDIA_NOT_FOUND', `no media with id \"${mediaId}\". ${hint}${createHint}`),\n    }\n  }\n  return { ok: true, payload: await toPayload(media) }\n}\n\n/** Build a per-run @id ref resolver bound to this dispatch's visible media. */\nconst refResolverFor =\n  (ctx: DispatchContext, registry: EngineRegistry, resolveMedia: MediaResolverFn) =>\n  async (id: string): Promise<StepPayload> => {\n    // @empty:<format> refs mint a blank file inline (merge with=@empty:xlsx, …).\n    const format = emptyFormatOf(id)\n    if (format !== undefined) {\n      const made = await materializeEmpty(registry, format, ctx.abortSignal)\n      if (!made.ok) throw new Error(made.failure)\n      return made.payload\n    }\n    const media = await resolveMedia(ctx, id)\n    if (!media) {\n      const visible = enumerateMedia(ctx)\n        .map((m) => m.id)\n        .join(', ')\n      throw new Error(\n        `referenced media @${id} was not found in this turn. Visible ids: ${visible || '(none)'}`\n      )\n    }\n    return toPayload(media)\n  }\n\n/** Run the optional gate; a throw becomes the tool's error (standard downstream wrapping). */\nconst runGate = async (\n  gate: ToolGateFn | undefined,\n  ctx: DispatchContext,\n  tool: string,\n  args: unknown\n): Promise<void> => {\n  if (gate) await gate(ctx, { tool, args })\n}\n\n/** Map a thrown pipeline error to a readable failure string the model can act on. */\nconst renderError = (err: unknown): string => {\n  if (isError(err) && err.name.startsWith('E_MEDIA_')) {\n    return failure(err.name.replace(/^E_MEDIA_/, ''), err.message)\n  }\n  throw err\n}\n\n// ── grammar text generation (composite surface) ──────────────────────────────\n\nconst argHelp = (name: string, arg: VerbArgSpec): string => {\n  const req = arg.required ? '' : '?'\n  const type =\n    arg.type === 'enum' ? (arg.values ?? []).join('|') : arg.type === 'json' ? \"'<JSON>'\" : arg.type\n  return `${name}${req}=${type}`\n}\n\nconst verbHelp = (spec: VerbSpec): string => {\n  const verb = spec.id.replace(/\\./g, ' ')\n  const args = Object.entries(spec.args)\n    .map(([n, a]) => argHelp(n, a))\n    .join(' ')\n  return args.length > 0 ? `${verb} ${args}` : verb\n}\n\n/**\n * Generate the model-facing grammar text for the configured deployment: only available verbs,\n * with arg signatures, plus few-shot examples rendered from real plans via `toPipe` so they\n * can never drift from the parser.\n */\nconst grammarText = (pipeline: MediaPipeline): string => {\n  const available = new Set(availableVerbs(pipeline.capabilities))\n  const lines: string[] = []\n  for (const spec of VERB_INDEX.values()) {\n    if (!available.has(spec.id.replace(/[._]+/g, ' '))) continue\n    lines.push(`- ${verbHelp(spec)} — ${spec.description}`)\n  }\n  const examples: string[] = [\n    toPipe({\n      steps: [\n        { verb: 'select', args: { pages: [2, 3, 4, 5] } },\n        { verb: 'extract.text', args: {} },\n      ],\n    }),\n    toPipe({\n      steps: [\n        { verb: 'extract.text', args: {} },\n        { verb: 'chunk', args: { by: 'sentence', size: 512 } },\n      ],\n    }),\n  ]\n  if (available.has('redact')) {\n    examples.push(\n      toPipe({\n        steps: [\n          {\n            verb: 'redact',\n            args: { match: [{ source: '\\\\d{3}-\\\\d{2}-\\\\d{4}', flags: '' }], replace: '[SSN]' },\n          },\n        ],\n      })\n    )\n  }\n  const creatable = creatableFormats(pipeline.capabilities)\n  const creating: string[] =\n    creatable.length > 0\n      ? [\n          '',\n          'Creating new media:',\n          `Pass media_id \"empty:<format>\" to create a NEW blank file instead of processing an existing one (available: ${creatable.join(', ')}).`,\n          `Example: media_id \"empty:xlsx\" with q \"${toPipe({\n            steps: [\n              {\n                verb: 'sheet.update_cells',\n                args: { updates: [{ address: 'A1', value: 'Title' }] },\n              },\n            ],\n          })}\"`,\n          'Blank images are 1024×1024 white — resize in the same statement (empty:png + \"image resize width=64\").',\n          'The same sentinel works as a ref: merge with=@empty:xlsx.',\n        ]\n      : []\n  return [\n    'Statements are pipe expressions: verb name=value ... | verb name=value ...',\n    'Named args only. Indices are 1-based. Quote values containing spaces or dashes.',\n    'Structured payloads are quoted JSON: updates=\\'[{\"address\":\"B2\",\"value\":3}]\\'.',\n    'Reference other media inline by id: merge with=@<media id> (ids come from list_media).',\n    '',\n    'Available verbs:',\n    ...lines,\n    ...creating,\n    '',\n    'Examples (remember: inside JSON tool args, backslashes must be doubled — \\\\\\\\d):',\n    ...examples.map((e) => `  ${e}`),\n  ].join('\\n')\n}\n\n// ── the tools ────────────────────────────────────────────────────────────────\n\nconst buildListMediaTool = (overrides: ForgeMediaToolsOptions['overrides']): Tool => {\n  const name = overrides?.list_media?.name ?? 'list_media'\n  return new Tool({\n    name,\n    description:\n      overrides?.list_media?.description ??\n      'Lists every media file visible in this conversation turn (user attachments and files produced by prior tool calls), with the media ids other media tools require.',\n    inputSchema: validator.object({}),\n    artifactConstructor: () => SpooledJsonArtifact,\n    handler: async (_args, ctx) => {\n      const media = enumerateMedia(ctx as DispatchContext)\n      return JSON.stringify(media, null, 2)\n    },\n  })\n}\n\nconst buildCompositeTool = (\n  pipeline: MediaPipeline,\n  resolveMedia: MediaResolverFn,\n  gate: ToolGateFn | undefined,\n  overrides: ForgeMediaToolsOptions['overrides']\n): Tool => {\n  const name = overrides?.media_query?.name ?? 'media_query'\n  const description =\n    overrides?.media_query?.description ??\n    `Runs a media-processing statement against a file from this conversation, locally (no external services). Provide media_id (from list_media) and either q (a pipe statement) or ops (structured steps).\n\n${grammarText(pipeline)}`\n  const creatable = creatableFormats(pipeline.capabilities)\n  const mediaIdDescription =\n    creatable.length > 0\n      ? `The id of the media to process (call list_media to discover ids), or \"empty:<format>\" to create a new blank file (available: ${creatable.join(', ')}).`\n      : 'The id of the media to process (call list_media to discover ids).'\n  return new Tool({\n    name,\n    description,\n    inputSchema: validator.object({\n      media_id: validator.string().required().description(mediaIdDescription),\n      q: validator\n        .string()\n        .description('A pipe statement, e.g. \"select pages=2-5 | extract text\".'),\n      ops: validator\n        .array()\n        .items(\n          validator.object({\n            verb: validator.string().required(),\n            args: validator.object().unknown(true).required(),\n          })\n        )\n        .description('Structured alternative to q: [{ verb, args }] steps.'),\n    }),\n    handler: async (args, ctx) => {\n      const {\n        media_id: mediaId,\n        q,\n        ops,\n      } = args as { media_id: string; q?: string; ops?: MediaOp[] }\n      if ((q === undefined) === (ops === undefined)) {\n        return failure(\n          'BAD_REQUEST',\n          'provide exactly one of q (pipe statement) or ops (structured steps)'\n        )\n      }\n      const dispatch = ctx as DispatchContext\n      await runGate(gate, dispatch, name, args)\n      const acquired = await acquire(dispatch, pipeline.capabilities, resolveMedia, mediaId)\n      if (!acquired.ok) return acquired.failure\n      try {\n        const runOptions = {\n          signal: dispatch.abortSignal,\n          resolveRef: refResolverFor(dispatch, pipeline.capabilities, resolveMedia),\n        }\n        const result =\n          q !== undefined\n            ? await pipeline.query(acquired.payload, q, runOptions)\n            : await pipeline.ops(acquired.payload, ops as MediaOp[], runOptions)\n        return await renderResult(dispatch, result, name)\n      } catch (err) {\n        return renderError(err)\n      }\n    },\n  })\n}\n\n/** Convert a granular tool's flat args into the verb's op args (drops media_id). */\nconst opArgsFrom = (args: Record<string, unknown>): Record<string, MediaArgValue> => {\n  const out: Record<string, MediaArgValue> = {}\n  for (const [k, v] of Object.entries(args)) {\n    if (k === 'media_id' || v === undefined) continue\n    out[k] = v as MediaArgValue\n  }\n  return out\n}\n\nconst granularSchemaFor = (\n  spec: VerbSpec,\n  creatable: readonly string[]\n): ReturnType<typeof validator.object> => {\n  const mediaIdDescription =\n    creatable.length > 0\n      ? `The id of the media to process (call list_media to discover ids), or \"empty:<format>\" to create a new blank file (available: ${creatable.join(', ')}).`\n      : 'The id of the media to process (call list_media to discover ids).'\n  const shape: Record<string, ReturnType<typeof validator.any>> = {\n    media_id: validator.string().required().description(mediaIdDescription),\n  }\n  for (const [argName, arg] of Object.entries(spec.args)) {\n    let schema\n    switch (arg.type) {\n      case 'number':\n        schema = validator.number()\n        if (arg.min !== undefined) schema = schema.min(arg.min)\n        if (arg.max !== undefined) schema = schema.max(arg.max)\n        break\n      case 'boolean':\n        schema = validator.boolean()\n        break\n      case 'enum':\n        schema = validator.string().valid(...(arg.values ?? []))\n        break\n      case 'number-list':\n        schema = validator.array().items(validator.number().min(arg.min ?? 1))\n        break\n      case 'string-list':\n        schema = arg.values\n          ? validator.array().items(validator.string().valid(...arg.values))\n          : validator.array().items(validator.string())\n        break\n      case 'media-ref':\n        schema = validator\n          .string()\n          .description('A media id (from list_media) for the other media in this operation.')\n        break\n      case 'media-ref-list':\n        schema = validator.array().items(validator.string())\n        break\n      case 'name-or-index':\n        schema = validator\n          .alternatives()\n          .try(validator.number().integer().min(1), validator.string())\n        break\n      case 'regex-or-string-list':\n        schema = validator\n          .alternatives()\n          .try(validator.string(), validator.array().items(validator.string()))\n        break\n      case 'json':\n        schema = validator.any().optional()\n        break\n      default:\n        schema = validator.string().allow('')\n    }\n    schema = schema.description(arg.description)\n    if (arg.required) schema = schema.required()\n    shape[argName] = schema\n  }\n  return validator.object(shape)\n}\n\n/** Normalize a granular tool's media-ref string args to MediaRef IR values. */\nconst normalizeRefArgs = (spec: VerbSpec, args: Record<string, MediaArgValue>): void => {\n  for (const [argName, arg] of Object.entries(spec.args)) {\n    const value = args[argName]\n    if (value === undefined) continue\n    if (arg.type === 'media-ref' && typeof value === 'string') {\n      args[argName] = { kind: 'id', id: value }\n    }\n    if (arg.type === 'media-ref-list' && Array.isArray(value)) {\n      args[argName] = (value as unknown[]).map((v) =>\n        typeof v === 'string' ? { kind: 'id', id: v } : (v as MediaArgValue)\n      ) as MediaArgValue\n    }\n  }\n}\n\nconst buildGranularTool = (\n  pipeline: MediaPipeline,\n  spec: VerbSpec,\n  resolveMedia: MediaResolverFn,\n  gate: ToolGateFn | undefined,\n  overrides: ForgeMediaToolsOptions['overrides']\n): Tool => {\n  const defaultName = spec.id.replace(/\\./g, '_')\n  const name = overrides?.[defaultName]?.name ?? defaultName\n  return new Tool({\n    name,\n    description:\n      overrides?.[defaultName]?.description ??\n      `${spec.description} Processes a media file from this conversation locally. Indices are 1-based.`,\n    inputSchema: granularSchemaFor(spec, creatableFormats(pipeline.capabilities)),\n    handler: async (args, ctx) => {\n      const dispatch = ctx as DispatchContext\n      const { media_id: mediaId } = args as { media_id: string }\n      await runGate(gate, dispatch, name, args)\n      const acquired = await acquire(dispatch, pipeline.capabilities, resolveMedia, mediaId)\n      if (!acquired.ok) return acquired.failure\n      const opArgs = opArgsFrom(args as Record<string, unknown>)\n      normalizeRefArgs(spec, opArgs)\n      try {\n        const result = await pipeline.ops(acquired.payload, [{ verb: spec.id, args: opArgs }], {\n          signal: dispatch.abortSignal,\n          resolveRef: refResolverFor(dispatch, pipeline.capabilities, resolveMedia),\n        })\n        return await renderResult(dispatch, result, name)\n      } catch (err) {\n        return renderError(err)\n      }\n    },\n  })\n}\n\n/**\n * Forge agent tools over a configured media pipeline.\n *\n * @remarks\n * The minted set follows the pipeline's configured engines: in the composite surface the\n * `media_query` grammar text only advertises available verbs; in the granular surface a tool\n * is minted only for verbs whose engine (if any) is configured. Either way `list_media` is\n * included — it is the model's entry point for discovering `media_id` values.\n *\n * The returned record is keyed by tool name so consumers can register selectively or pass\n * `Object.values(tools)` to `TurnRunnerConfig.tools`.\n *\n * @param pipeline - A pipeline from `createMediaPipeline`.\n * @param options - Surface, resolver, gate, and overrides.\n * @returns The minted tools, keyed by name.\n */\nexport const forgeMediaTools = (\n  pipeline: MediaPipeline,\n  options: ForgeMediaToolsOptions\n): Record<string, Tool> => {\n  if (options?.surface !== 'composite' && options?.surface !== 'granular') {\n    throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n      `forgeMediaTools requires surface: 'composite' | 'granular'`,\n    ])\n  }\n  const resolveMedia = options.resolveMedia ?? defaultResolveMedia\n  const tools: Record<string, Tool> = {}\n  const listTool = buildListMediaTool(options.overrides)\n  tools[listTool.name] = listTool\n\n  if (options.surface === 'composite') {\n    const composite = buildCompositeTool(pipeline, resolveMedia, options.gate, options.overrides)\n    tools[composite.name] = composite\n    return tools\n  }\n\n  const available = new Set(availableVerbs(pipeline.capabilities))\n  for (const spec of VERB_INDEX.values()) {\n    if (!available.has(spec.id.replace(/[._]+/g, ' '))) continue\n    const tool = buildGranularTool(pipeline, spec, resolveMedia, options.gate, options.overrides)\n    tools[tool.name] = tool\n  }\n  return tools\n}\n\n/** Re-exported so consumers can fold verbs the same way the forge does. */\nexport { foldVerb }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,IAAa,uBAAwC,KAAK,YAAY;CACpE,KAAK,MAAM,WAAW,IAAI,cACxB,KAAK,MAAM,cAAc,QAAQ,eAAe,CAAC,GAC/C,IAAI,WAAW,OAAO,SAAS,OAAO;CAG1C,KAAK,MAAM,YAAY,IAAI,eAAe;EACxC,MAAM,UAAU,SAAS;EACzB,IAAI,kBAAA,MAAM,QAAQ,OAAO,KAAK,QAAQ,OAAO,SAAS,OAAO;EAC7D,IAAI,MAAM,QAAQ,OAAO,GAAG;GAC1B,MAAM,MAAM,QAAQ,MAAM,MAAM,kBAAA,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,OAAO;GACpE,IAAI,KAAK,OAAO;EAClB;CACF;AAEF;;AAGA,IAAM,kBACJ,QAQI;CACJ,MAAM,MAOD,CAAC;CACN,KAAK,MAAM,WAAW,IAAI,cACxB,KAAK,MAAM,cAAc,QAAQ,eAAe,CAAC,GAC/C,IAAI,KAAK;EACP,IAAI,WAAW;EACf,UAAU,WAAW;EACrB,MAAM,WAAW;EACjB,UAAU,WAAW;EACrB,QAAQ,WAAW;EACnB,QAAQ;CACV,CAAC;CAGL,KAAK,MAAM,YAAY,IAAI,eAAe;EACxC,MAAM,UAAU,SAAS;EACzB,MAAM,YAAY,kBAAA,MAAM,QAAQ,OAAO,IACnC,CAAC,OAAO,IACR,MAAM,QAAQ,OAAO,IACnB,QAAQ,QAAQ,MAAkB,kBAAA,MAAM,QAAQ,CAAC,CAAC,IAClD,CAAC;EACP,KAAK,MAAM,SAAS,WAClB,IAAI,KAAK;GACP,IAAI,MAAM;GACV,UAAU,MAAM;GAChB,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,QAAQ,MAAM;GACd,QAAQ;EACV,CAAC;CAEL;CACA,OAAO;AACT;AAEA,IAAM,WAAW,MAAc,YAA4B,UAAU,KAAK,KAAK;;;;;;AAS/E,IAAM,iBAAiB,YAAwC;CAC7D,IAAI,CAAC,QAAQ,YAAY,EAAE,WAAW,QAAQ,GAAG,OAAO,KAAA;CACxD,OAAO,QAAQ,MAAM,CAAe,EAAE,KAAK,EAAE,YAAY;AAC3D;;AAGA,IAAM,oBAAoB,aACxB,SAAS,eAAe,kCAAA,UAAU;;AAGpC,IAAM,mBAAmB,OACvB,UACA,QACA,WACiF;CACjF,MAAM,YAAY,iBAAiB,QAAQ;CAC3C,IAAI,CAAC,UAAU,SAAS,MAAM,GAC5B,OAAO;EACL,IAAI;EACJ,SAAS,QACP,4BACA,kBAAkB,OAAO,iDAAiD,UAAU,KAAK,IAAI,KAAK,SAAS,iCAC7G;CACF;CASF,MAAM,UAAS,MAPM,SAAS,QAAQ;EACpC,OAAO,IAAI,WAAW,CAAC;EACvB,UAAU,kCAAA;EACV,UAAU;EACV,IAAI;EACJ;CACF,CAAC,GACqB,QAAQ;CAC9B,IAAI,CAAC,QACH,OAAO;EACL,IAAI;EACJ,SAAS,QAAQ,2BAA2B,eAAe,OAAO,qBAAqB;CACzF;CAEF,OAAO;EACL,IAAI;EACJ,SAAS;GACP,OAAO,OAAO;GACd,UAAU,OAAO;GACjB,UAAU,YAAY;EACxB;CACF;AACF;AAEA,IAAM,eAAe,aAA+D;CAClF,MAAM,OAAO,SAAS,YAAY;CAClC,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO;CACtC,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO;CACtC,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO;CACtC,OAAO;AACT;;AAGA,IAAM,YAAY,OAAO,WAAwC;CAC/D,OAAO,MAAM,MAAM,QAAQ;CAC3B,UAAU,MAAM;CAChB,UAAU,MAAM;AAClB;;AAGA,IAAM,UAAU,OACd,KACA,SACA,aACmB;CACnB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,SAAS,MAAM,IAAI,gBAAgB,IAAI,QAAQ,KAAK;CAC1D,OAAO,kBAAA,MAAM,cAAc;EACzB;EACA,MAAM,YAAY,QAAQ,QAAQ;EAClC,UAAU,QAAQ;EAClB,UAAU,QAAQ;EAClB;EACA,QAAQ,QAAQ;CAClB,CAAC;AACH;;AAGA,IAAM,eAAe,OACnB,KACA,QACA,aACsC;CACtC,IAAI,OAAO,SAAS,SAAS,OAAO,QAAQ,KAAK,OAAO,SAAS,QAAQ;CACzE,IAAI,OAAO,SAAS,cAClB,OAAO,QAAQ,IAAI,OAAO,SAAS,KAAK,MAAM,QAAQ,KAAK,GAAG,QAAQ,CAAC,CAAC;CAE1E,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO,KAAK,UAAU,OAAO,MAAM,MAAM,CAAC;AAC5F;;AAGA,IAAM,UAAU,OACd,KACA,UACA,cACA,YACiF;CAEjF,MAAM,SAAS,cAAc,OAAO;CACpC,IAAI,WAAW,KAAA,GAAW,OAAO,iBAAiB,UAAU,QAAQ,IAAI,WAAW;CACnF,MAAM,QAAQ,MAAM,aAAa,KAAK,OAAO;CAC7C,IAAI,CAAC,OAAO;EACV,MAAM,UAAU,eAAe,GAAG;EAClC,MAAM,YAAY,iBAAiB,QAAQ;EAC3C,MAAM,aACJ,UAAU,SAAS,IACf,4EAA4E,UAAU,KAAK,IAAI,EAAE,MACjG;EAKN,OAAO;GACL,IAAI;GACJ,SAAS,QAAQ,mBAAmB,qBAAqB,QAAQ,KALjE,QAAQ,SAAS,IACb,sBAAsB,QAAQ,KAAK,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,IAAI,MAC7E,mGAGyE,YAAY;EAC3F;CACF;CACA,OAAO;EAAE,IAAI;EAAM,SAAS,MAAM,UAAU,KAAK;CAAE;AACrD;;AAGA,IAAM,kBACH,KAAsB,UAA0B,iBACjD,OAAO,OAAqC;CAE1C,MAAM,SAAS,cAAc,EAAE;CAC/B,IAAI,WAAW,KAAA,GAAW;EACxB,MAAM,OAAO,MAAM,iBAAiB,UAAU,QAAQ,IAAI,WAAW;EACrE,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,KAAK,OAAO;EAC1C,OAAO,KAAK;CACd;CACA,MAAM,QAAQ,MAAM,aAAa,KAAK,EAAE;CACxC,IAAI,CAAC,OAAO;EACV,MAAM,UAAU,eAAe,GAAG,EAC/B,KAAK,MAAM,EAAE,EAAE,EACf,KAAK,IAAI;EACZ,MAAM,IAAI,MACR,qBAAqB,GAAG,4CAA4C,WAAW,UACjF;CACF;CACA,OAAO,UAAU,KAAK;AACxB;;AAGF,IAAM,UAAU,OACd,MACA,KACA,MACA,SACkB;CAClB,IAAI,MAAM,MAAM,KAAK,KAAK;EAAE;EAAM;CAAK,CAAC;AAC1C;;AAGA,IAAM,eAAe,QAAyB;CAC5C,IAAI,eAAA,QAAQ,GAAG,KAAK,IAAI,KAAK,WAAW,UAAU,GAChD,OAAO,QAAQ,IAAI,KAAK,QAAQ,aAAa,EAAE,GAAG,IAAI,OAAO;CAE/D,MAAM;AACR;AAIA,IAAM,WAAW,MAAc,QAA6B;CAI1D,OAAO,GAAG,OAHE,IAAI,WAAW,KAAK,IAGX,GADnB,IAAI,SAAS,UAAU,IAAI,UAAU,CAAC,GAAG,KAAK,GAAG,IAAI,IAAI,SAAS,SAAS,aAAa,IAAI;AAEhG;AAEA,IAAM,YAAY,SAA2B;CAC3C,MAAM,OAAO,KAAK,GAAG,QAAQ,OAAO,GAAG;CACvC,MAAM,OAAO,OAAO,QAAQ,KAAK,IAAI,EAClC,KAAK,CAAC,GAAG,OAAO,QAAQ,GAAG,CAAC,CAAC,EAC7B,KAAK,GAAG;CACX,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,GAAG,SAAS;AAC/C;;;;;;AAOA,IAAM,eAAe,aAAoC;CACvD,MAAM,YAAY,IAAI,IAAI,iBAAA,eAAe,SAAS,YAAY,CAAC;CAC/D,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,QAAQ,iBAAA,WAAW,OAAO,GAAG;EACtC,IAAI,CAAC,UAAU,IAAI,KAAK,GAAG,QAAQ,UAAU,GAAG,CAAC,GAAG;EACpD,MAAM,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,KAAK,aAAa;CACxD;CACA,MAAM,WAAqB,CACzB,iBAAA,OAAO,EACL,OAAO,CACL;EAAE,MAAM;EAAU,MAAM,EAAE,OAAO;GAAC;GAAG;GAAG;GAAG;EAAC,EAAE;CAAE,GAChD;EAAE,MAAM;EAAgB,MAAM,CAAC;CAAE,CACnC,EACF,CAAC,GACD,iBAAA,OAAO,EACL,OAAO,CACL;EAAE,MAAM;EAAgB,MAAM,CAAC;CAAE,GACjC;EAAE,MAAM;EAAS,MAAM;GAAE,IAAI;GAAY,MAAM;EAAI;CAAE,CACvD,EACF,CAAC,CACH;CACA,IAAI,UAAU,IAAI,QAAQ,GACxB,SAAS,KACP,iBAAA,OAAO,EACL,OAAO,CACL;EACE,MAAM;EACN,MAAM;GAAE,OAAO,CAAC;IAAE,QAAQ;IAAwB,OAAO;GAAG,CAAC;GAAG,SAAS;EAAQ;CACnF,CACF,EACF,CAAC,CACH;CAEF,MAAM,YAAY,iBAAiB,SAAS,YAAY;CACxD,MAAM,WACJ,UAAU,SAAS,IACf;EACE;EACA;EACA,+GAA+G,UAAU,KAAK,IAAI,EAAE;EACpI,0CAA0C,iBAAA,OAAO,EAC/C,OAAO,CACL;GACE,MAAM;GACN,MAAM,EAAE,SAAS,CAAC;IAAE,SAAS;IAAM,OAAO;GAAQ,CAAC,EAAE;EACvD,CACF,EACF,CAAC,EAAE;EACH;EACA;CACF,IACA,CAAC;CACP,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;EACH,GAAG;EACH;EACA;EACA,GAAG,SAAS,KAAK,MAAM,KAAK,GAAG;CACjC,EAAE,KAAK,IAAI;AACb;AAIA,IAAM,sBAAsB,cAAyD;CAEnF,OAAO,IAAI,yBAAA,KAAK;EACd,MAFW,WAAW,YAAY,QAAQ;EAG1C,aACE,WAAW,YAAY,eACvB;EACF,aAAa,kBAAA,UAAU,OAAO,CAAC,CAAC;EAChC,2BAA2B,kCAAA;EAC3B,SAAS,OAAO,OAAO,QAAQ;GAC7B,MAAM,QAAQ,eAAe,GAAsB;GACnD,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC;EACtC;CACF,CAAC;AACH;AAEA,IAAM,sBACJ,UACA,cACA,MACA,cACS;CACT,MAAM,OAAO,WAAW,aAAa,QAAQ;CAC7C,MAAM,cACJ,WAAW,aAAa,eACxB;;EAEF,YAAY,QAAQ;CACpB,MAAM,YAAY,iBAAiB,SAAS,YAAY;CACxD,MAAM,qBACJ,UAAU,SAAS,IACf,gIAAgI,UAAU,KAAK,IAAI,EAAE,MACrJ;CACN,OAAO,IAAI,yBAAA,KAAK;EACd;EACA;EACA,aAAa,kBAAA,UAAU,OAAO;GAC5B,UAAU,kBAAA,UAAU,OAAO,EAAE,SAAS,EAAE,YAAY,kBAAkB;GACtE,GAAG,kBAAA,UACA,OAAO,EACP,YAAY,6DAA2D;GAC1E,KAAK,kBAAA,UACF,MAAM,EACN,MACC,kBAAA,UAAU,OAAO;IACf,MAAM,kBAAA,UAAU,OAAO,EAAE,SAAS;IAClC,MAAM,kBAAA,UAAU,OAAO,EAAE,QAAQ,IAAI,EAAE,SAAS;GAClD,CAAC,CACH,EACC,YAAY,sDAAsD;EACvE,CAAC;EACD,SAAS,OAAO,MAAM,QAAQ;GAC5B,MAAM,EACJ,UAAU,SACV,GACA,QACE;GACJ,IAAK,MAAM,KAAA,OAAgB,QAAQ,KAAA,IACjC,OAAO,QACL,eACA,qEACF;GAEF,MAAM,WAAW;GACjB,MAAM,QAAQ,MAAM,UAAU,MAAM,IAAI;GACxC,MAAM,WAAW,MAAM,QAAQ,UAAU,SAAS,cAAc,cAAc,OAAO;GACrF,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS;GAClC,IAAI;IACF,MAAM,aAAa;KACjB,QAAQ,SAAS;KACjB,YAAY,eAAe,UAAU,SAAS,cAAc,YAAY;IAC1E;IAKA,OAAO,MAAM,aAAa,UAHxB,MAAM,KAAA,IACF,MAAM,SAAS,MAAM,SAAS,SAAS,GAAG,UAAU,IACpD,MAAM,SAAS,IAAI,SAAS,SAAS,KAAkB,UAAU,GAC3B,IAAI;GAClD,SAAS,KAAK;IACZ,OAAO,YAAY,GAAG;GACxB;EACF;CACF,CAAC;AACH;;AAGA,IAAM,cAAc,SAAiE;CACnF,MAAM,MAAqC,CAAC;CAC5C,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,GAAG;EACzC,IAAI,MAAM,cAAc,MAAM,KAAA,GAAW;EACzC,IAAI,KAAK;CACX;CACA,OAAO;AACT;AAEA,IAAM,qBACJ,MACA,cACwC;CACxC,MAAM,qBACJ,UAAU,SAAS,IACf,gIAAgI,UAAU,KAAK,IAAI,EAAE,MACrJ;CACN,MAAM,QAA0D,EAC9D,UAAU,kBAAA,UAAU,OAAO,EAAE,SAAS,EAAE,YAAY,kBAAkB,EACxE;CACA,KAAK,MAAM,CAAC,SAAS,QAAQ,OAAO,QAAQ,KAAK,IAAI,GAAG;EACtD,IAAI;EACJ,QAAQ,IAAI,MAAZ;GACE,KAAK;IACH,SAAS,kBAAA,UAAU,OAAO;IAC1B,IAAI,IAAI,QAAQ,KAAA,GAAW,SAAS,OAAO,IAAI,IAAI,GAAG;IACtD,IAAI,IAAI,QAAQ,KAAA,GAAW,SAAS,OAAO,IAAI,IAAI,GAAG;IACtD;GACF,KAAK;IACH,SAAS,kBAAA,UAAU,QAAQ;IAC3B;GACF,KAAK;IACH,SAAS,kBAAA,UAAU,OAAO,EAAE,MAAM,GAAI,IAAI,UAAU,CAAC,CAAE;IACvD;GACF,KAAK;IACH,SAAS,kBAAA,UAAU,MAAM,EAAE,MAAM,kBAAA,UAAU,OAAO,EAAE,IAAI,IAAI,OAAO,CAAC,CAAC;IACrE;GACF,KAAK;IACH,SAAS,IAAI,SACT,kBAAA,UAAU,MAAM,EAAE,MAAM,kBAAA,UAAU,OAAO,EAAE,MAAM,GAAG,IAAI,MAAM,CAAC,IAC/D,kBAAA,UAAU,MAAM,EAAE,MAAM,kBAAA,UAAU,OAAO,CAAC;IAC9C;GACF,KAAK;IACH,SAAS,kBAAA,UACN,OAAO,EACP,YAAY,qEAAqE;IACpF;GACF,KAAK;IACH,SAAS,kBAAA,UAAU,MAAM,EAAE,MAAM,kBAAA,UAAU,OAAO,CAAC;IACnD;GACF,KAAK;IACH,SAAS,kBAAA,UACN,aAAa,EACb,IAAI,kBAAA,UAAU,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,kBAAA,UAAU,OAAO,CAAC;IAC9D;GACF,KAAK;IACH,SAAS,kBAAA,UACN,aAAa,EACb,IAAI,kBAAA,UAAU,OAAO,GAAG,kBAAA,UAAU,MAAM,EAAE,MAAM,kBAAA,UAAU,OAAO,CAAC,CAAC;IACtE;GACF,KAAK;IACH,SAAS,kBAAA,UAAU,IAAI,EAAE,SAAS;IAClC;GACF,SACE,SAAS,kBAAA,UAAU,OAAO,EAAE,MAAM,EAAE;EACxC;EACA,SAAS,OAAO,YAAY,IAAI,WAAW;EAC3C,IAAI,IAAI,UAAU,SAAS,OAAO,SAAS;EAC3C,MAAM,WAAW;CACnB;CACA,OAAO,kBAAA,UAAU,OAAO,KAAK;AAC/B;;AAGA,IAAM,oBAAoB,MAAgB,SAA8C;CACtF,KAAK,MAAM,CAAC,SAAS,QAAQ,OAAO,QAAQ,KAAK,IAAI,GAAG;EACtD,MAAM,QAAQ,KAAK;EACnB,IAAI,UAAU,KAAA,GAAW;EACzB,IAAI,IAAI,SAAS,eAAe,OAAO,UAAU,UAC/C,KAAK,WAAW;GAAE,MAAM;GAAM,IAAI;EAAM;EAE1C,IAAI,IAAI,SAAS,oBAAoB,MAAM,QAAQ,KAAK,GACtD,KAAK,WAAY,MAAoB,KAAK,MACxC,OAAO,MAAM,WAAW;GAAE,MAAM;GAAM,IAAI;EAAE,IAAK,CACnD;CAEJ;AACF;AAEA,IAAM,qBACJ,UACA,MACA,cACA,MACA,cACS;CACT,MAAM,cAAc,KAAK,GAAG,QAAQ,OAAO,GAAG;CAC9C,MAAM,OAAO,YAAY,cAAc,QAAQ;CAC/C,OAAO,IAAI,yBAAA,KAAK;EACd;EACA,aACE,YAAY,cAAc,eAC1B,GAAG,KAAK,YAAY;EACtB,aAAa,kBAAkB,MAAM,iBAAiB,SAAS,YAAY,CAAC;EAC5E,SAAS,OAAO,MAAM,QAAQ;GAC5B,MAAM,WAAW;GACjB,MAAM,EAAE,UAAU,YAAY;GAC9B,MAAM,QAAQ,MAAM,UAAU,MAAM,IAAI;GACxC,MAAM,WAAW,MAAM,QAAQ,UAAU,SAAS,cAAc,cAAc,OAAO;GACrF,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS;GAClC,MAAM,SAAS,WAAW,IAA+B;GACzD,iBAAiB,MAAM,MAAM;GAC7B,IAAI;IAKF,OAAO,MAAM,aAAa,UAAU,MAJf,SAAS,IAAI,SAAS,SAAS,CAAC;KAAE,MAAM,KAAK;KAAI,MAAM;IAAO,CAAC,GAAG;KACrF,QAAQ,SAAS;KACjB,YAAY,eAAe,UAAU,SAAS,cAAc,YAAY;IAC1E,CAAC,GAC2C,IAAI;GAClD,SAAS,KAAK;IACZ,OAAO,YAAY,GAAG;GACxB;EACF;CACF,CAAC;AACH;;;;;;;;;;;;;;;;;AAkBA,IAAa,mBACX,UACA,YACyB;CACzB,IAAI,SAAS,YAAY,eAAe,SAAS,YAAY,YAC3D,MAAM,IAAI,mBAAA,gCAAgC,CACxC,4DACF,CAAC;CAEH,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,QAA8B,CAAC;CACrC,MAAM,WAAW,mBAAmB,QAAQ,SAAS;CACrD,MAAM,SAAS,QAAQ;CAEvB,IAAI,QAAQ,YAAY,aAAa;EACnC,MAAM,YAAY,mBAAmB,UAAU,cAAc,QAAQ,MAAM,QAAQ,SAAS;EAC5F,MAAM,UAAU,QAAQ;EACxB,OAAO;CACT;CAEA,MAAM,YAAY,IAAI,IAAI,iBAAA,eAAe,SAAS,YAAY,CAAC;CAC/D,KAAK,MAAM,QAAQ,iBAAA,WAAW,OAAO,GAAG;EACtC,IAAI,CAAC,UAAU,IAAI,KAAK,GAAG,QAAQ,UAAU,GAAG,CAAC,GAAG;EACpD,MAAM,OAAO,kBAAkB,UAAU,MAAM,cAAc,QAAQ,MAAM,QAAQ,SAAS;EAC5F,MAAM,KAAK,QAAQ;CACrB;CACA,OAAO;AACT"}