{"version":3,"file":"headless-tools.cjs","names":[],"sources":["../src/headless-tools.ts"],"sourcesContent":["import type { Interrupt } from \"./schema.js\";\n\n/**\n * Represents a headless tool interrupt payload emitted by LangChain's\n * schema-only `tool({ ... })` overload.\n *\n * Servers may serialize the nested tool call as `toolCall` (JS) or\n * `tool_call` (Python). Use {@link parseHeadlessToolInterruptPayload} to\n * normalize either shape before reading fields.\n */\nexport interface HeadlessToolInterrupt {\n  type: \"tool\";\n  toolCall: {\n    id: string | undefined;\n    name: string;\n    args: unknown;\n  };\n}\n\n/**\n * Parses a headless-tool interrupt `value` from the graph. Accepts both\n * `toolCall` (LangChain JS) and `tool_call` (Python / JSON snake_case).\n */\nexport function parseHeadlessToolInterruptPayload(\n  value: unknown\n): HeadlessToolInterrupt | null {\n  if (typeof value !== \"object\" || value == null) {\n    return null;\n  }\n  const v = value as Record<string, unknown>;\n  if (v.type !== \"tool\") {\n    return null;\n  }\n\n  const rawTc = v.toolCall ?? v.tool_call;\n  if (typeof rawTc !== \"object\" || rawTc == null) {\n    return null;\n  }\n  const tc = rawTc as Record<string, unknown>;\n  if (typeof tc.name !== \"string\") {\n    return null;\n  }\n\n  const id = typeof tc.id === \"string\" ? tc.id : undefined;\n\n  return {\n    type: \"tool\",\n    toolCall: {\n      id,\n      name: tc.name,\n      args: tc.args,\n    },\n  };\n}\n\n/**\n * Client-side implementation returned by `headlessTool.implement(...)`.\n */\nexport interface HeadlessToolImplementation<Args = unknown, Output = unknown> {\n  tool: {\n    name: string;\n  };\n  execute: (args: Args) => Promise<Output>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyHeadlessToolImplementation = HeadlessToolImplementation<\n  any,\n  any\n>;\n\nexport interface ToolEvent {\n  phase: \"start\" | \"success\" | \"error\";\n  name: string;\n  args: unknown;\n  result?: unknown;\n  error?: Error;\n  duration?: number;\n}\n\nexport type OnToolCallback = (event: ToolEvent) => void;\n\n/**\n * Strip headless-tool interrupts from a user-facing interrupt list.\n */\nexport function filterOutHeadlessToolInterrupts<T extends { value?: unknown }>(\n  interrupts: readonly T[]\n): T[] {\n  return interrupts.filter(\n    (interrupt) =>\n      interrupt.value == null || !isHeadlessToolInterrupt(interrupt.value)\n  );\n}\n\nexport function isHeadlessToolInterrupt(\n  interrupt: unknown\n): interrupt is HeadlessToolInterrupt {\n  return parseHeadlessToolInterruptPayload(interrupt) != null;\n}\n\nexport function findHeadlessTool<Args = unknown, Output = unknown>(\n  tools: HeadlessToolImplementation[],\n  name: string\n): HeadlessToolImplementation<Args, Output> | undefined {\n  return tools.find((tool) => tool.tool.name === name) as\n    | HeadlessToolImplementation<Args, Output>\n    | undefined;\n}\n\nexport async function executeHeadlessTool<Args = unknown, Output = unknown>(\n  implementation: HeadlessToolImplementation<Args, Output>,\n  args: Args,\n  onTool?: OnToolCallback\n): Promise<\n  { success: true; result: Output } | { success: false; error: Error }\n> {\n  const startTime = Date.now();\n\n  onTool?.({\n    phase: \"start\",\n    name: implementation.tool.name,\n    args,\n  });\n\n  try {\n    const result = await implementation.execute(args);\n    const duration = Date.now() - startTime;\n\n    onTool?.({\n      phase: \"success\",\n      name: implementation.tool.name,\n      args,\n      result,\n      duration,\n    });\n\n    return { success: true, result };\n  } catch (err) {\n    // oxlint-disable-next-line no-instanceof/no-instanceof\n    const error = err instanceof Error ? err : new Error(String(err));\n    const duration = Date.now() - startTime;\n\n    onTool?.({\n      phase: \"error\",\n      name: implementation.tool.name,\n      args,\n      error,\n      duration,\n    });\n\n    return { success: false, error };\n  }\n}\n\nexport async function handleHeadlessToolInterrupt(\n  interrupt: HeadlessToolInterrupt,\n  tools: HeadlessToolImplementation[],\n  onTool?: OnToolCallback\n): Promise<{ toolCallId: string | undefined; value: unknown }> {\n  const { toolCall } = interrupt;\n  const implementation = findHeadlessTool(tools, toolCall.name);\n\n  if (!implementation) {\n    const error = new Error(\n      `Headless tool \"${toolCall.name}\" is not registered. ` +\n        `Available tools: ${tools.map((tool) => tool.tool.name).join(\", \") || \"none\"}`\n    );\n\n    onTool?.({\n      phase: \"error\",\n      name: toolCall.name,\n      args: toolCall.args,\n      error,\n      duration: 0,\n    });\n\n    return {\n      toolCallId: toolCall.id,\n      value: { error: error.message },\n    };\n  }\n\n  const result = await executeHeadlessTool(\n    implementation,\n    toolCall.args as never,\n    onTool\n  );\n\n  if (result.success) {\n    return {\n      toolCallId: toolCall.id,\n      value: result.result,\n    };\n  }\n\n  return {\n    toolCallId: toolCall.id,\n    value: { error: result.error.message },\n  };\n}\n\nexport function headlessToolResumeCommand(result: {\n  toolCallId: string | undefined;\n  value: unknown;\n}): { resume: unknown } {\n  return {\n    resume: result.toolCallId\n      ? { [result.toolCallId]: result.value }\n      : result.value,\n  };\n}\n\n/**\n * Merge headless-tool results into one resume command. Use interrupt-id keys\n * whenever the stream provided them so the resume does not need to rediscover\n * a pending interrupt from mutable client state.\n */\nexport function headlessToolsBatchResumeCommand(\n  entries: ReadonlyArray<{\n    interruptId: string;\n    toolCallId: string | undefined;\n    value: unknown;\n  }>\n): { resume: unknown } {\n  if (entries.length === 0) {\n    return { resume: {} };\n  }\n\n  const hasInterruptIds = entries.every(\n    (entry) => entry.interruptId.length > 0\n  );\n  if (!hasInterruptIds && entries.length === 1) {\n    const [entry] = entries;\n    return headlessToolResumeCommand({\n      toolCallId: entry.toolCallId,\n      value: entry.value,\n    });\n  }\n\n  const resume: Record<string, unknown> = {};\n  for (const entry of entries) {\n    if (entry.interruptId.length === 0) continue;\n    resume[entry.interruptId] =\n      entry.toolCallId != null && entry.toolCallId.length > 0\n        ? { [entry.toolCallId]: entry.value }\n        : entry.value;\n  }\n  return { resume };\n}\n\n/**\n * True when every top-level resume key is a graph task interrupt id\n * (32-char hex from `values.__interrupt__`).\n */\nexport function isInterruptIdKeyedResume(resume: unknown): boolean {\n  if (resume == null || typeof resume !== \"object\" || Array.isArray(resume)) {\n    return false;\n  }\n  const keys = Object.keys(resume as Record<string, unknown>);\n  if (keys.length === 0) return false;\n  return keys.every((key) => /^[0-9a-f]{32}$/i.test(key));\n}\n\n/**\n * Normalize `command.resume` into the `run.start` input the API turns\n * into `Command({ resume })`. Interrupt-id keyed payloads pass through;\n * tool-call-keyed and generic payloads are wrapped under the matching\n * protocol interrupt id.\n */\nexport function buildResumeRunInput(\n  resume: unknown,\n  interrupts: readonly ProtocolInterruptEntry[],\n  resolvedInterruptIds: ReadonlySet<string>\n): Record<string, unknown> | null {\n  if (resume == null) return null;\n  if (isInterruptIdKeyedResume(resume)) {\n    return resume as Record<string, unknown>;\n  }\n\n  const target = resolveInterruptTargetForHeadlessResume(\n    resume,\n    interrupts,\n    resolvedInterruptIds\n  );\n  if (target == null) return null;\n\n  return { [target.interruptId]: resume };\n}\n\n/**\n * Reads the tool-call id from a headless-tool resume command shaped as\n * `{ [toolCallId]: result }`.\n */\nexport function extractHeadlessToolCallIdFromResumeCommand(\n  resume: unknown\n): string | undefined {\n  if (resume == null || typeof resume !== \"object\" || Array.isArray(resume)) {\n    return undefined;\n  }\n  const keys = Object.keys(resume as Record<string, unknown>);\n  if (keys.length !== 1) return undefined;\n  return keys[0];\n}\n\nexport interface ProtocolInterruptEntry {\n  interruptId: string;\n  namespace: string[];\n  payload: unknown;\n}\n\n/**\n * Pick the protocol interrupt that matches a headless-tool resume payload.\n * Falls back to the newest unresolved interrupt for non-keyed resumes.\n */\nexport function resolveInterruptTargetForHeadlessResume(\n  resume: unknown,\n  interrupts: readonly ProtocolInterruptEntry[],\n  resolvedInterruptIds: ReadonlySet<string>\n): { interruptId: string; namespace: string[] } | null {\n  const toolCallId = extractHeadlessToolCallIdFromResumeCommand(resume);\n  if (toolCallId != null) {\n    for (let i = interrupts.length - 1; i >= 0; i -= 1) {\n      const entry = interrupts[i];\n      if (entry == null || resolvedInterruptIds.has(entry.interruptId)) {\n        continue;\n      }\n      const headless = parseHeadlessToolInterruptPayload(entry.payload);\n      if (headless?.toolCall.id === toolCallId) {\n        return {\n          interruptId: entry.interruptId,\n          namespace: [...entry.namespace],\n        };\n      }\n    }\n  }\n\n  for (let i = interrupts.length - 1; i >= 0; i -= 1) {\n    const entry = interrupts[i];\n    if (entry == null || resolvedInterruptIds.has(entry.interruptId)) {\n      continue;\n    }\n    return {\n      interruptId: entry.interruptId,\n      namespace: [...entry.namespace],\n    };\n  }\n  return null;\n}\n\nexport interface FlushPendingHeadlessToolInterruptsOptions {\n  onTool?: OnToolCallback;\n  resumeSubmit: (command: { resume: unknown }) => void | Promise<void>;\n  defer?: (run: () => void) => void;\n}\n\nconst coalescedHeadlessFlushes = new WeakMap<\n  Set<string>,\n  { scheduled: boolean; run: () => void }\n>();\n\n/**\n * Coalesce rapid headless-tool flush triggers into one microtask so parallel\n * `input.requested` events observed back-to-back batch into a single resume.\n * Vue/Svelte/Angular watchers run synchronously per event; without this,\n * the first interrupt can be claimed before the second arrives and resume\n * splits into staggered single-tool commands.\n */\nexport function scheduleCoalescedHeadlessToolFlush(\n  handledIds: Set<string>,\n  run: () => void\n): void {\n  let state = coalescedHeadlessFlushes.get(handledIds);\n  if (state == null) {\n    state = { scheduled: false, run: () => {} };\n    coalescedHeadlessFlushes.set(handledIds, state);\n  }\n  state.run = run;\n  if (state.scheduled) return;\n  state.scheduled = true;\n  void Promise.resolve().then(() => {\n    state!.scheduled = false;\n    state!.run();\n  });\n}\n\n/**\n * Execute and resume all newly seen headless-tool interrupts from a values\n * payload. Callers own `handledIds` and should clear it when the thread changes.\n */\nexport function flushPendingHeadlessToolInterrupts(\n  values: Record<string, unknown> | null | undefined,\n  tools: HeadlessToolImplementation[] | undefined,\n  handledIds: Set<string>,\n  options: FlushPendingHeadlessToolInterruptsOptions\n): void {\n  if (!tools?.length || !values) return;\n\n  const interrupts = values.__interrupt__;\n  if (!Array.isArray(interrupts) || interrupts.length === 0) return;\n\n  const defer = options.defer ?? ((run) => run());\n  const pending: Array<{\n    interruptId: string;\n    headlessInterrupt: HeadlessToolInterrupt;\n    toolCallId: string;\n  }> = [];\n  const seenToolCallIds = new Set<string>();\n\n  for (const interrupt of interrupts as Interrupt[]) {\n    const headlessInterrupt = parseHeadlessToolInterruptPayload(\n      interrupt.value\n    );\n    if (!headlessInterrupt) continue;\n\n    const interruptId = interrupt.id ?? headlessInterrupt.toolCall.id ?? \"\";\n    const toolCallId = headlessInterrupt.toolCall.id ?? \"\";\n    if (handledIds.has(interruptId)) continue;\n    // v2 protocol runs mirror the same headless-tool interrupt in both\n    // `values.__interrupt__` and `rootStore.interrupts` with different\n    // ids (graph/task id vs protocol interrupt_id). The headless-tool\n    // effect can also re-run after the first resume clears\n    // `rootStore.interrupts` while `values.__interrupt__` is still\n    // present — persist tool call ids in the caller-owned set so we\n    // only execute + resume once per pending tool call.\n    if (toolCallId && handledIds.has(toolCallId)) continue;\n    if (toolCallId && seenToolCallIds.has(toolCallId)) continue;\n    if (toolCallId) seenToolCallIds.add(toolCallId);\n\n    // Claim before defer so a second flush in the same tick cannot\n    // schedule a duplicate execute/resume for the same interrupt.\n    handledIds.add(interruptId);\n    if (toolCallId) handledIds.add(toolCallId);\n\n    pending.push({ interruptId, headlessInterrupt, toolCallId });\n  }\n\n  if (pending.length === 0) return;\n\n  defer(() => {\n    void (async () => {\n      const results = await Promise.all(\n        pending.map(async ({ interruptId, headlessInterrupt, toolCallId }) => {\n          const result = await handleHeadlessToolInterrupt(\n            headlessInterrupt,\n            tools,\n            options.onTool\n          );\n          return {\n            interruptId,\n            toolCallId: result.toolCallId ?? toolCallId,\n            value: result.value,\n          };\n        })\n      );\n      await Promise.resolve(\n        options.resumeSubmit(headlessToolsBatchResumeCommand(results))\n      );\n    })();\n  });\n}\n"],"mappings":";;;;;AAuBA,SAAgB,kCACd,OAC8B;AAC9B,KAAI,OAAO,UAAU,YAAY,SAAS,KACxC,QAAO;CAET,MAAM,IAAI;AACV,KAAI,EAAE,SAAS,OACb,QAAO;CAGT,MAAM,QAAQ,EAAE,YAAY,EAAE;AAC9B,KAAI,OAAO,UAAU,YAAY,SAAS,KACxC,QAAO;CAET,MAAM,KAAK;AACX,KAAI,OAAO,GAAG,SAAS,SACrB,QAAO;AAKT,QAAO;EACL,MAAM;EACN,UAAU;GACR,IALO,OAAO,GAAG,OAAO,WAAW,GAAG,KAAK,KAAA;GAM3C,MAAM,GAAG;GACT,MAAM,GAAG;GACV;EACF;;;;;AAiCH,SAAgB,gCACd,YACK;AACL,QAAO,WAAW,QACf,cACC,UAAU,SAAS,QAAQ,CAAC,wBAAwB,UAAU,MAAM,CACvE;;AAGH,SAAgB,wBACd,WACoC;AACpC,QAAO,kCAAkC,UAAU,IAAI;;AAGzD,SAAgB,iBACd,OACA,MACsD;AACtD,QAAO,MAAM,MAAM,SAAS,KAAK,KAAK,SAAS,KAAK;;AAKtD,eAAsB,oBACpB,gBACA,MACA,QAGA;CACA,MAAM,YAAY,KAAK,KAAK;AAE5B,UAAS;EACP,OAAO;EACP,MAAM,eAAe,KAAK;EAC1B;EACD,CAAC;AAEF,KAAI;EACF,MAAM,SAAS,MAAM,eAAe,QAAQ,KAAK;EACjD,MAAM,WAAW,KAAK,KAAK,GAAG;AAE9B,WAAS;GACP,OAAO;GACP,MAAM,eAAe,KAAK;GAC1B;GACA;GACA;GACD,CAAC;AAEF,SAAO;GAAE,SAAS;GAAM;GAAQ;UACzB,KAAK;EAEZ,MAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;EACjE,MAAM,WAAW,KAAK,KAAK,GAAG;AAE9B,WAAS;GACP,OAAO;GACP,MAAM,eAAe,KAAK;GAC1B;GACA;GACA;GACD,CAAC;AAEF,SAAO;GAAE,SAAS;GAAO;GAAO;;;AAIpC,eAAsB,4BACpB,WACA,OACA,QAC6D;CAC7D,MAAM,EAAE,aAAa;CACrB,MAAM,iBAAiB,iBAAiB,OAAO,SAAS,KAAK;AAE7D,KAAI,CAAC,gBAAgB;EACnB,MAAM,wBAAQ,IAAI,MAChB,kBAAkB,SAAS,KAAK,wCACV,MAAM,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC,KAAK,KAAK,IAAI,SACzE;AAED,WAAS;GACP,OAAO;GACP,MAAM,SAAS;GACf,MAAM,SAAS;GACf;GACA,UAAU;GACX,CAAC;AAEF,SAAO;GACL,YAAY,SAAS;GACrB,OAAO,EAAE,OAAO,MAAM,SAAS;GAChC;;CAGH,MAAM,SAAS,MAAM,oBACnB,gBACA,SAAS,MACT,OACD;AAED,KAAI,OAAO,QACT,QAAO;EACL,YAAY,SAAS;EACrB,OAAO,OAAO;EACf;AAGH,QAAO;EACL,YAAY,SAAS;EACrB,OAAO,EAAE,OAAO,OAAO,MAAM,SAAS;EACvC;;AAGH,SAAgB,0BAA0B,QAGlB;AACtB,QAAO,EACL,QAAQ,OAAO,aACX,GAAG,OAAO,aAAa,OAAO,OAAO,GACrC,OAAO,OACZ;;;;;;;AAQH,SAAgB,gCACd,SAKqB;AACrB,KAAI,QAAQ,WAAW,EACrB,QAAO,EAAE,QAAQ,EAAE,EAAE;AAMvB,KAAI,CAHoB,QAAQ,OAC7B,UAAU,MAAM,YAAY,SAAS,EACvC,IACuB,QAAQ,WAAW,GAAG;EAC5C,MAAM,CAAC,SAAS;AAChB,SAAO,0BAA0B;GAC/B,YAAY,MAAM;GAClB,OAAO,MAAM;GACd,CAAC;;CAGJ,MAAM,SAAkC,EAAE;AAC1C,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,MAAM,YAAY,WAAW,EAAG;AACpC,SAAO,MAAM,eACX,MAAM,cAAc,QAAQ,MAAM,WAAW,SAAS,IAClD,GAAG,MAAM,aAAa,MAAM,OAAO,GACnC,MAAM;;AAEd,QAAO,EAAE,QAAQ;;;;;;AAOnB,SAAgB,yBAAyB,QAA0B;AACjE,KAAI,UAAU,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,CACvE,QAAO;CAET,MAAM,OAAO,OAAO,KAAK,OAAkC;AAC3D,KAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAO,KAAK,OAAO,QAAQ,kBAAkB,KAAK,IAAI,CAAC;;;;;;;;AASzD,SAAgB,oBACd,QACA,YACA,sBACgC;AAChC,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,yBAAyB,OAAO,CAClC,QAAO;CAGT,MAAM,SAAS,wCACb,QACA,YACA,qBACD;AACD,KAAI,UAAU,KAAM,QAAO;AAE3B,QAAO,GAAG,OAAO,cAAc,QAAQ;;;;;;AAOzC,SAAgB,2CACd,QACoB;AACpB,KAAI,UAAU,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,CACvE;CAEF,MAAM,OAAO,OAAO,KAAK,OAAkC;AAC3D,KAAI,KAAK,WAAW,EAAG,QAAO,KAAA;AAC9B,QAAO,KAAK;;;;;;AAad,SAAgB,wCACd,QACA,YACA,sBACqD;CACrD,MAAM,aAAa,2CAA2C,OAAO;AACrE,KAAI,cAAc,KAChB,MAAK,IAAI,IAAI,WAAW,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;EAClD,MAAM,QAAQ,WAAW;AACzB,MAAI,SAAS,QAAQ,qBAAqB,IAAI,MAAM,YAAY,CAC9D;AAGF,MADiB,kCAAkC,MAAM,QAAQ,EACnD,SAAS,OAAO,WAC5B,QAAO;GACL,aAAa,MAAM;GACnB,WAAW,CAAC,GAAG,MAAM,UAAU;GAChC;;AAKP,MAAK,IAAI,IAAI,WAAW,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;EAClD,MAAM,QAAQ,WAAW;AACzB,MAAI,SAAS,QAAQ,qBAAqB,IAAI,MAAM,YAAY,CAC9D;AAEF,SAAO;GACL,aAAa,MAAM;GACnB,WAAW,CAAC,GAAG,MAAM,UAAU;GAChC;;AAEH,QAAO;;AAST,MAAM,2CAA2B,IAAI,SAGlC;;;;;;;;AASH,SAAgB,mCACd,YACA,KACM;CACN,IAAI,QAAQ,yBAAyB,IAAI,WAAW;AACpD,KAAI,SAAS,MAAM;AACjB,UAAQ;GAAE,WAAW;GAAO,WAAW;GAAI;AAC3C,2BAAyB,IAAI,YAAY,MAAM;;AAEjD,OAAM,MAAM;AACZ,KAAI,MAAM,UAAW;AACrB,OAAM,YAAY;AACb,SAAQ,SAAS,CAAC,WAAW;AAChC,QAAO,YAAY;AACnB,QAAO,KAAK;GACZ;;;;;;AAOJ,SAAgB,mCACd,QACA,OACA,YACA,SACM;AACN,KAAI,CAAC,OAAO,UAAU,CAAC,OAAQ;CAE/B,MAAM,aAAa,OAAO;AAC1B,KAAI,CAAC,MAAM,QAAQ,WAAW,IAAI,WAAW,WAAW,EAAG;CAE3D,MAAM,QAAQ,QAAQ,WAAW,QAAQ,KAAK;CAC9C,MAAM,UAID,EAAE;CACP,MAAM,kCAAkB,IAAI,KAAa;AAEzC,MAAK,MAAM,aAAa,YAA2B;EACjD,MAAM,oBAAoB,kCACxB,UAAU,MACX;AACD,MAAI,CAAC,kBAAmB;EAExB,MAAM,cAAc,UAAU,MAAM,kBAAkB,SAAS,MAAM;EACrE,MAAM,aAAa,kBAAkB,SAAS,MAAM;AACpD,MAAI,WAAW,IAAI,YAAY,CAAE;AAQjC,MAAI,cAAc,WAAW,IAAI,WAAW,CAAE;AAC9C,MAAI,cAAc,gBAAgB,IAAI,WAAW,CAAE;AACnD,MAAI,WAAY,iBAAgB,IAAI,WAAW;AAI/C,aAAW,IAAI,YAAY;AAC3B,MAAI,WAAY,YAAW,IAAI,WAAW;AAE1C,UAAQ,KAAK;GAAE;GAAa;GAAmB;GAAY,CAAC;;AAG9D,KAAI,QAAQ,WAAW,EAAG;AAE1B,aAAY;AACV,GAAM,YAAY;GAChB,MAAM,UAAU,MAAM,QAAQ,IAC5B,QAAQ,IAAI,OAAO,EAAE,aAAa,mBAAmB,iBAAiB;IACpE,MAAM,SAAS,MAAM,4BACnB,mBACA,OACA,QAAQ,OACT;AACD,WAAO;KACL;KACA,YAAY,OAAO,cAAc;KACjC,OAAO,OAAO;KACf;KACD,CACH;AACD,SAAM,QAAQ,QACZ,QAAQ,aAAa,gCAAgC,QAAQ,CAAC,CAC/D;MACC;GACJ"}