{"version":3,"file":"UdpAiProvider.mjs","names":["React","ReactNode","createContext","useContext","useEffect","useMemo","useState","GatewayConfig","listModels","ConversationStore","ConversationStoreContext","AiChatItem","useUser","isDevelopment","UdpAiConfig","gateway","UdpAiAccess","models","AiConfigContext","UdpAiProviderProps","config","children","UdpAiProvider","tenantId","currentTenantId","headersKey","JSON","stringify","headers","entitled","setEntitled","live","then","ids","length","console","warn","catch","err","baseUrl","value","conversations","useAiAccess"],"sources":["../../../src/aiAssistant/provider/UdpAiProvider.tsx"],"sourcesContent":["import React, {\n  ReactNode,\n  createContext,\n  useContext,\n  useEffect,\n  useMemo,\n  useState\n} from 'react';\nimport { GatewayConfig, listModels } from '../openai';\nimport { ConversationStore } from '../conversationState';\nimport { ConversationStoreContext } from '../useConversation';\nimport type { AiChatItem } from '../types';\nimport { useUser } from '../../utilities/auth/useUser';\nimport { isDevelopment } from '../../utilities/env';\n\n/**\n * App-level AI connection, set once at the root. Every AI surface (grid\n * assistant, Shell assistant, builder panels) reads it from context — pages\n * never wire the gateway themselves. Model choice is not here: it is a\n * property of each function, stated on the surface that hosts it.\n */\nexport interface UdpAiConfig {\n  /** OpenAI-compatible gateway (udp-ai-gateway): base URL + extra headers */\n  gateway: GatewayConfig;\n}\n\n/**\n * What the gateway allows this tenant/product. This is the value every AI\n * surface reads: it exists only once the gateway has said yes, so\n * `useAiAccess() !== null` IS the \"render an AI control\" test.\n */\nexport interface UdpAiAccess extends UdpAiConfig {\n  /** The model ids the gateway offers this cell. */\n  models: string[];\n}\n\nconst AiConfigContext = createContext<UdpAiAccess | null>(null);\n\nexport interface UdpAiProviderProps {\n  config: UdpAiConfig;\n  children?: ReactNode;\n}\n\n/**\n * Resolves the app's AI access once per tenant. The gateway's tenant, product\n * and environment guards decide who gets AI; this asks them (`GET /v1/models`)\n * before any surface can render, so a tenant the gateway refuses never sees a\n * control that would only fail on use. Rejected, empty, or not yet answered all\n * read as null. Re-asked when the tenant changes, because the answer does.\n *\n * The probe waits for a signed-in user: the transport has no token before\n * that, and the tenant header comes from the same place.\n */\nexport const UdpAiProvider = ({ config, children }: UdpAiProviderProps) => {\n  const tenantId = useUser()?.currentTenantId;\n  const headersKey = JSON.stringify(config.gateway.headers ?? {});\n  const [entitled, setEntitled] = useState<string[] | null>(null);\n\n  useEffect(() => {\n    if (!tenantId) return;\n    let live = true;\n    setEntitled(null);\n    listModels(config.gateway)\n      .then((ids) => {\n        if (!live) return;\n        setEntitled(ids);\n        if (!ids.length && isDevelopment()) {\n          console.warn(\n            '[udp] UdpAiProvider: the gateway returned no models for this tenant/product — AI surfaces will not render.'\n          );\n        }\n      })\n      .catch((err: unknown) => {\n        if (!live) return;\n        setEntitled([]);\n        if (isDevelopment()) {\n          console.warn(\n            '[udp] UdpAiProvider: GET /v1/models failed — AI surfaces will not render.',\n            err\n          );\n        }\n      });\n    return () => {\n      live = false;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed by content, see headersKey\n  }, [tenantId, config.gateway.baseUrl, headersKey]);\n\n  const value = useMemo<UdpAiAccess | null>(\n    () => (entitled?.length ? { gateway: config.gateway, models: entitled } : null),\n    // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed by content, see headersKey\n    [entitled, config.gateway.baseUrl, headersKey]\n  );\n  // App-wide conversations: a surface that names a conversation id gets the\n  // same one back across remounts; a chat list can enumerate them.\n  const conversations = useMemo(() => new ConversationStore<AiChatItem>(), []);\n  return (\n    <AiConfigContext.Provider value={value}>\n      <ConversationStoreContext.Provider value={conversations}>\n        {children}\n      </ConversationStoreContext.Provider>\n    </AiConfigContext.Provider>\n  );\n};\n\n/**\n * The app's AI access, or null when there is none: no UdpAiProvider mounted,\n * the gateway not yet asked, or the gateway refused this tenant. AI surfaces\n * render nothing on null.\n */\nexport const useAiAccess = (): UdpAiAccess | null =>\n  useContext(AiConfigContext);\n"],"mappings":";;;;;;;AAoCA,MAAMkB,kBAAkBhB,cAAkC,KAAK;;;;;;;;;;;AAiB/D,MAAaoB,iBAAiB,EAAEF,QAAQC,eAAmC;CACzE,MAAME,WAAWX,SAAS,EAAEY;CAC5B,MAAMC,aAAaC,KAAKC,UAAUP,OAAOL,QAAQa,WAAW,EAAE,CAAC;CAC/D,MAAM,CAACC,UAAUC,eAAexB,SAA0B,KAAK;AAE/DF,iBAAgB;AACd,MAAI,CAACmB,SAAU;EACf,IAAIQ,OAAO;AACXD,cAAY,KAAK;AACjBtB,aAAWY,OAAOL,QAAQ,CACvBiB,MAAMC,QAAQ;AACb,OAAI,CAACF,KAAM;AACXD,eAAYG,IAAI;AAChB,OAAI,CAACA,IAAIC,UAAUrB,eAAe,CAChCsB,SAAQC,KACN,6GACD;IAEH,CACDC,OAAOC,QAAiB;AACvB,OAAI,CAACP,KAAM;AACXD,eAAY,EAAE,CAAC;AACf,OAAIjB,eAAe,CACjBsB,SAAQC,KACN,6EACAE,IACD;IAEH;AACJ,eAAa;AACXP,UAAO;;IAGR;EAACR;EAAUH,OAAOL,QAAQwB;EAASd;EAAW,CAAC;CAElD,MAAMe,QAAQnC,cACLwB,UAAUK,SAAS;EAAEnB,SAASK,OAAOL;EAASE,QAAQY;EAAU,GAAG,MAE1E;EAACA;EAAUT,OAAOL,QAAQwB;EAASd;EACrC,CAAC;CAGD,MAAMgB,gBAAgBpC,cAAc,IAAII,mBAA+B,EAAE,EAAE,CAAC;AAC5E,QACE,sBAAA,cAAC,gBAAgB,UAAjB,EAAiC+B,OAIN,EAHzB,sBAAA,cAAC,yBAAyB,UAA1B,EAAmC,OAAOC,eAEP,EADhCpB,SACgC,CACV;;;;;;;AAS/B,MAAaqB,oBAAc;AAAA,QACzBvC,WAAWe,gBAAgB"}