{"version":3,"sources":["../src/hooks/use-coagent-state-render-bridge.tsx"],"sourcesContent":["import {\n  ReactCustomMessageRendererPosition,\n  useAgent,\n} from \"@copilotkitnext/react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { AgentSubscriber } from \"@ag-ui/client\";\nimport { useCoAgentStateRenders } from \"../context\";\nimport { parseJson } from \"@copilotkit/shared\";\nimport { RenderStatus } from \"./use-coagent-state-render-bridge.helpers\";\nimport { useStateRenderRegistry } from \"./use-coagent-state-render-registry\";\n\n/**\n * Bridge hook that connects agent state renders to chat messages.\n *\n * ## Purpose\n * This hook finds matching state render configurations (registered via useCoAgentStateRender)\n * and returns UI to render in chat.\n * It ensures each state render appears bound to a specific message, preventing duplicates while\n * allowing re-binding when the underlying state changes significantly.\n *\n * ## Message-ID-Based Claiming System\n *\n * ### The Problem\n * Multiple bridge component instances render simultaneously (one per message). Without coordination,\n * they would all try to render the same state render, causing duplicates.\n *\n * ### The Solution: Message-ID Claims with State Comparison\n * Each state render is \"claimed\" by exactly one **message ID** (not runId):\n *\n * **Claim Structure**: `claimsRef.current[messageId] = { stateRenderId, runId, stateSnapshot, locked }`\n *\n * **Primary binding is by messageId because**:\n * - runId is not always available immediately (starts as \"pending\")\n * - messageId is the stable identifier throughout the message lifecycle\n * - Claims persist across component remounts via context ref\n *\n * ### Claiming Logic Flow\n *\n * 1. **Message already has a claim**:\n *    - Check if the claim matches the current stateRenderId\n *    - If yes → render (this message owns this render)\n *    - Update runId if it was \"pending\" and now available\n *\n * 2. **State render claimed by another message**:\n *    - Compare state snapshots (ignoring constant keys: messages, tools, copilotkit)\n *    - If states are identical → block rendering (duplicate)\n *    - **If states are different → allow claiming** (new data, new message)\n *    - This handles cases where the same render type shows different states in different messages\n *\n * 3. **Unclaimed state render**:\n *    - Only allow claiming if runId is \"pending\" (initial render)\n *    - If runId is real but no claim exists → block (edge case protection)\n *    - Create new claim: `claimsRef.current[messageId] = { stateRenderId, runId }`\n *\n * ### State Snapshot Locking\n *\n * Once a state snapshot is captured and locked for a message:\n * - The UI always renders with the locked snapshot (not live agent.state)\n * - Prevents UI from appearing \"wiped\" during state transitions\n * - Locked when: stateSnapshot prop is available (from message persistence)\n * - Unlocked state: can still update from live agent.state\n *\n * ### Synchronous Claiming (Ref-based)\n *\n * Claims are stored in a context-level ref (not React state):\n * - Multiple bridges render in the same tick\n * - State updates are async - would allow duplicates before update completes\n * - Ref provides immediate, synchronous claim checking\n * - Survives component remounts (stored in context, not component)\n *\n * ## Flow Example\n *\n * ```\n * Time 1: Message A renders, runId=undefined, state={progress: 50%}\n *   → effectiveRunId = \"pending\"\n *   → Claims: claimsRef[\"msgA\"] = { stateRenderId: \"tasks\", runId: \"pending\", stateSnapshot: {progress: 50%} }\n *   → Renders UI with 50% progress\n *\n * Time 2: Message B renders, runId=undefined, same state\n *   → Checks: \"tasks\" already claimed by msgA with same state\n *   → Returns null (blocked - duplicate)\n *\n * Time 3: Real runId appears (e.g., \"run-123\")\n *   → Updates claim: claimsRef[\"msgA\"].runId = \"run-123\"\n *   → Message A continues rendering\n *\n * Time 4: Agent processes more, state={progress: 100%}\n *   → Message A: locked to 50% (stateSnapshot locked)\n *   → Message C renders with state={progress: 100%}\n *   → Checks: \"tasks\" claimed by msgA but state is DIFFERENT (50% vs 100%)\n *   → Allows new claim: claimsRef[\"msgC\"] = { stateRenderId: \"tasks\", runId: \"run-123\", stateSnapshot: {progress: 100%} }\n *   → Both messages render independently with their own snapshots\n * ```\n */\nexport interface CoAgentStateRenderBridgeProps {\n  message: any;\n  position: ReactCustomMessageRendererPosition;\n  runId: string;\n  messageIndex: number;\n  messageIndexInRun: number;\n  numberOfMessagesInRun: number;\n  agentId: string;\n  stateSnapshot: any;\n}\n\nexport function useCoagentStateRenderBridge(\n  agentId: string,\n  props: CoAgentStateRenderBridgeProps,\n) {\n  const { stateSnapshot, message } = props;\n  const { coAgentStateRenders, claimsRef } = useCoAgentStateRenders();\n  const { agent } = useAgent({ agentId });\n  const [nodeName, setNodeName] = useState<string | undefined>(undefined);\n  const [, forceUpdate] = useState(0);\n\n  useEffect(() => {\n    if (!agent) return;\n    const subscriber: AgentSubscriber = {\n      onStateChanged: () => {\n        forceUpdate((value) => value + 1);\n      },\n      onStepStartedEvent: ({ event }) => {\n        if (event.stepName !== nodeName) {\n          setNodeName(event.stepName);\n        }\n      },\n      onStepFinishedEvent: ({ event }) => {\n        if (event.stepName === nodeName) {\n          setNodeName(undefined);\n        }\n      },\n    };\n\n    const { unsubscribe } = agent.subscribe(subscriber);\n    return () => {\n      unsubscribe();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [agentId, nodeName]);\n\n  const getStateRender = useCallback(\n    (messageId: string) => {\n      return Object.entries(coAgentStateRenders).find(\n        ([stateRenderId, stateRender]) => {\n          if (claimsRef.current[messageId]) {\n            return stateRenderId === claimsRef.current[messageId].stateRenderId;\n          }\n          const matchingAgentName = stateRender.name === agentId;\n          const matchesNodeContext = stateRender.nodeName\n            ? stateRender.nodeName === nodeName\n            : true;\n          return matchingAgentName && matchesNodeContext;\n        },\n      );\n    },\n    [coAgentStateRenders, nodeName, agentId],\n  );\n  const stateRenderEntry = useMemo(\n    () => getStateRender(message.id),\n    [getStateRender, message.id],\n  );\n  const stateRenderId = stateRenderEntry?.[0];\n  const stateRender = stateRenderEntry?.[1];\n\n  const registryMessage = {\n    ...message,\n    runId: props.runId ?? message.runId,\n  };\n  const { canRender } = useStateRenderRegistry({\n    agentId,\n    stateRenderId,\n    message: registryMessage,\n    messageIndex: props.messageIndex,\n    stateSnapshot,\n    agentState: agent?.state,\n    agentMessages: agent?.messages,\n    claimsRef,\n  });\n\n  return useMemo(() => {\n    if (!stateRender || !stateRenderId) {\n      return null;\n    }\n    if (!canRender) {\n      return null;\n    }\n\n    if (stateRender.handler) {\n      stateRender.handler({\n        state: stateSnapshot\n          ? parseJson(stateSnapshot, stateSnapshot)\n          : (agent?.state ?? {}),\n        nodeName: nodeName ?? \"\",\n      });\n    }\n\n    if (stateRender.render) {\n      const status = agent?.isRunning\n        ? RenderStatus.InProgress\n        : RenderStatus.Complete;\n\n      if (typeof stateRender.render === \"string\") return stateRender.render;\n\n      return stateRender.render({\n        status,\n        // Always use state from claim, to make sure the state does not seem \"wiped\" for a fraction of a second\n        state: claimsRef.current[message.id].stateSnapshot ?? {},\n        nodeName: nodeName ?? \"\",\n      });\n    }\n  }, [\n    stateRender,\n    stateRenderId,\n    agent?.state,\n    agent?.isRunning,\n    nodeName,\n    message.id,\n    stateSnapshot,\n    canRender,\n  ]);\n}\n\nexport function CoAgentStateRenderBridge(props: CoAgentStateRenderBridgeProps) {\n  return useCoagentStateRenderBridge(props.agentId, props);\n}\n"],"mappings":"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","names":["stateRenderId","stateRender","_a"]}