@sentientui/react 0.8.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,13 @@ type AdaptiveProviderProps = {
39
39
  * @see SentientConfig.preConsentBehavior
40
40
  */
41
41
  preConsentBehavior?: 'statistical_winner' | 'control';
42
+ /**
43
+ * Honor the browser's Do Not Track signal. Defaults to `true`: when DNT is
44
+ * enabled the SDK sets no cookies and sends no tracking data (overriding
45
+ * `consent: true`). Set `false` to make your own consent gate authoritative.
46
+ * @see SentientConfig.respectDoNotTrack
47
+ */
48
+ respectDoNotTrack?: boolean;
42
49
  /**
43
50
  * Called once per component the first time a variant is resolved for that
44
51
  * component in this session. Use to forward assignments to your own analytics
@@ -59,9 +66,90 @@ type AdaptiveProviderProps = {
59
66
  * used for variant assignment.
60
67
  */
61
68
  ssrSessionId?: string;
69
+ /**
70
+ * ISO 3166-1 alpha-2 country code. Pass the value of the `CF-IPCountry`
71
+ * header from your Next.js server component to populate country on landing
72
+ * sessions without client-side geo lookup.
73
+ */
74
+ country?: string;
75
+ /**
76
+ * Enable DOM graph scanning + page-structure sync. When `true`, the provider
77
+ * dynamically loads `@sentientui/core/graph` and uses its graph-capable
78
+ * `init()` for the single client, so the SDK scans your page structure and
79
+ * syncs it to power the dashboard graph page. Left off (default), the lean
80
+ * bundle is used and the graph entry is never loaded.
81
+ */
82
+ enableGraph?: boolean;
62
83
  children: ReactNode;
63
84
  };
64
85
 
86
+ /**
87
+ * Agent-readable content feed. Merges SDK-known data (winning variants, layout
88
+ * order) with developer-supplied page content, and renders it either as a
89
+ * server-rendered inline JSON-LD block (read by passive AI crawlers, which do
90
+ * not run JS) or as Markdown (for agents that content-negotiate `text/markdown`).
91
+ *
92
+ * No React or DOM APIs — safe to import in server components, route handlers,
93
+ * and middleware.
94
+ */
95
+ type AgentBlock = {
96
+ /** Component ID. */
97
+ id: string;
98
+ /** Winning variant ID currently served. */
99
+ variant: string;
100
+ /** Agent-readable data attached to the served variant, if any. */
101
+ content?: unknown;
102
+ };
103
+ type AgentFeed = {
104
+ page: string;
105
+ title?: string;
106
+ summary?: string;
107
+ layoutOrder?: string[];
108
+ blocks: AgentBlock[];
109
+ /** Developer-supplied extra fields (products, specs, arbitrary JSON). */
110
+ [key: string]: unknown;
111
+ };
112
+ /**
113
+ * Register page-level structured content the SDK can't infer (title, summary,
114
+ * product fields, arbitrary JSON), keyed by page path. Call at module load.
115
+ */
116
+ declare function defineAgentContent(page: string, content: Record<string, unknown>): void;
117
+ /**
118
+ * Merge SDK-known data with developer-supplied content into a single feed.
119
+ * Developer content fills in title/summary/etc. but can never overwrite the
120
+ * SDK-authoritative fields (`page`, `blocks`, `layoutOrder`).
121
+ */
122
+ declare function buildAgentFeed(input: {
123
+ page: string;
124
+ blocks: AgentBlock[];
125
+ layoutOrder?: string[];
126
+ content?: Record<string, unknown>;
127
+ }): AgentFeed;
128
+
129
+ type AgentFeedReadEntry = {
130
+ path: string;
131
+ userAgent: string | null;
132
+ botName: string | null;
133
+ };
134
+ type AgentFeedRouteConfig = {
135
+ /** Resolve the feed for a request (winning variants + layout + dev content). */
136
+ getFeed: (request: Request) => Promise<AgentFeed> | AgentFeed;
137
+ /** Best-effort per-fetch hook — log to `crawler_requests` here. Never awaited into the response. */
138
+ onRead?: (entry: AgentFeedReadEntry) => void | Promise<void>;
139
+ };
140
+ declare function createAgentFeed(config: AgentFeedRouteConfig): (request: Request) => Promise<Response>;
141
+
142
+ type CrawlerRequestEntry = {
143
+ path: string;
144
+ userAgent: string | null;
145
+ botName: string;
146
+ };
147
+ type SentientAgentMiddlewareConfig = {
148
+ /** Best-effort sink — persist to `crawler_requests` here. Failures are swallowed. */
149
+ onCrawler: (entry: CrawlerRequestEntry) => void | Promise<void>;
150
+ };
151
+ declare function sentientAgentMiddleware(config: SentientAgentMiddlewareConfig): (request: Request) => Promise<boolean>;
152
+
65
153
  type PreloadComponent = {
66
154
  id: string;
67
155
  variantIds: string[];
@@ -87,6 +175,18 @@ type AdaptiveRootProps = Omit<AdaptiveProviderProps, 'initialAssignments' | 'onA
87
175
  * Defaults to 1500. Increase if you see unexpected fallbacks in production.
88
176
  */
89
177
  timeoutMs?: number;
178
+ /**
179
+ * When set, AdaptiveRoot emits a server-rendered inline JSON-LD block
180
+ * describing the page's winning variants, layout order, and developer-supplied
181
+ * content — readable by passive AI crawlers (which do not run JS). Invisible to
182
+ * humans (a JSON-LD script renders no visible DOM). Omit to emit nothing.
183
+ */
184
+ agentFeed?: {
185
+ /** Page path for the feed. Falls back to the `x-pathname` request header, then '/'. */
186
+ path?: string;
187
+ /** Structured page content. Falls back to the `defineAgentContent` registry for this path. */
188
+ content?: Record<string, unknown>;
189
+ };
90
190
  children: ReactNode;
91
191
  };
92
192
  /**
@@ -121,4 +221,4 @@ type AdaptiveRootProps = Omit<AdaptiveProviderProps, 'initialAssignments' | 'onA
121
221
  */
122
222
  declare function AdaptiveRoot(props: AdaptiveRootProps): Promise<JSX.Element>;
123
223
 
124
- export { AdaptiveRoot, type AdaptiveRootProps, type PreloadComponent };
224
+ export { AdaptiveRoot, type AdaptiveRootProps, type AgentFeed, type AgentFeedReadEntry, type AgentFeedRouteConfig, type CrawlerRequestEntry, type PreloadComponent, type SentientAgentMiddlewareConfig, buildAgentFeed as buildAgentFeedFor, createAgentFeed, defineAgentContent, sentientAgentMiddleware };
@@ -1,2 +1,4 @@
1
- var M=Object.defineProperty,E=Object.defineProperties;var N=Object.getOwnPropertyDescriptors;var l=Object.getOwnPropertySymbols;var U=Object.prototype.hasOwnProperty,x=Object.prototype.propertyIsEnumerable;var R=(e,s,i)=>s in e?M(e,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[s]=i,A=(e,s)=>{for(var i in s||(s={}))U.call(s,i)&&R(e,i,s[i]);if(l)for(var i of l(s))x.call(s,i)&&R(e,i,s[i]);return e},u=(e,s)=>E(e,N(s));var L=(e,s)=>{var i={};for(var t in e)U.call(e,t)&&s.indexOf(t)<0&&(i[t]=e[t]);if(e!=null&&l)for(var t of l(e))s.indexOf(t)<0&&x.call(e,t)&&(i[t]=e[t]);return i};import{deriveSessionSegment as V}from"@sentientui/core";import{cookies as B,headers as F}from"next/headers";import{preloadAssignments as _,readSessionCookie as $}from"@sentientui/core/server";import{preloadDecisions as z}from"@sentientui/core/server";function b(){return typeof crypto!="undefined"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`snt-${Date.now()}-${Math.random().toString(36).slice(2,11)}`}async function k(e,s){var r,o,n;let i=(n=(o=$(s.cookies))!=null?o:(r=s.createSessionId)==null?void 0:r.call(s))!=null?n:b();return{assignments:await _(e,i,{apiKey:s.apiKey,baseUrl:s.baseUrl,origin:s.origin,userAgent:s.userAgent,referer:s.referer,timeoutMs:s.timeoutMs}),sessionId:i}}async function w(e){var o,n,p,a;let{preloadDecisions:s,readSessionCookie:i}=await import("@sentientui/core/server"),t=(p=(n=i(e.cookies))!=null?n:(o=e.createSessionId)==null?void 0:o.call(e))!=null?p:b(),r=await s({sections:e.sections,components:(a=e.components)!=null?a:[]},t,{apiKey:e.apiKey,baseUrl:e.baseUrl,origin:e.origin,userAgent:e.userAgent,referer:e.referer,timeoutMs:e.timeoutMs});return u(A({},r),{sessionId:t})}import{AdaptiveRootClient as H}from"./adaptive-root-client.js";import{jsx as J}from"react/jsx-runtime";var K="https://api.sentient-ui.com/v1";async function ee(e){var h,P;let O=e,{components:s,sections:i,appOrigin:t,initialAssignments:r,ssrSessionId:o,timeoutMs:n,children:p}=O,a=L(O,["components","sections","appOrigin","initialAssignments","ssrSessionId","timeoutMs","children"]),v=await F(),g=v.get("host");!t&&process.env.NODE_ENV==="production"&&!g&&console.error("[SentientUI] AdaptiveRoot: Host header is absent and no appOrigin was provided. Pass appOrigin explicitly \u2014 the https://localhost fallback will likely fail allowed_origins validation.");let f=t!=null?t:process.env.NODE_ENV==="production"?`https://${g!=null?g:"localhost"}`:"http://localhost:3001",y=(h=v.get("user-agent"))!=null?h:void 0,S=(P=v.get("referer"))!=null?P:void 0,C=V({userAgent:y,referer:S,appOrigin:f}),I=await B(),c,D=null,m;if(r)c=r,m=o;else if(i&&i.length>0){let d=await w({sections:i,components:s,cookies:I,apiKey:a.apiKey,baseUrl:K,origin:f,userAgent:y,referer:S,timeoutMs:n});c=d.assignments,D=d.layoutOrder,m=d.sessionId}else{let d=await k(s,{cookies:I,apiKey:a.apiKey,baseUrl:K,origin:f,userAgent:y,referer:S,timeoutMs:n});c=d.assignments,m=d.sessionId}return J(H,u(A({},a),{initialAssignments:c,initialLayoutOrder:D,sessionSegment:C,ssrSessionId:m,children:p}))}export{ee as AdaptiveRoot};
1
+ var V=Object.defineProperty,H=Object.defineProperties;var W=Object.getOwnPropertyDescriptors;var y=Object.getOwnPropertySymbols;var L=Object.prototype.hasOwnProperty,D=Object.prototype.propertyIsEnumerable;var C=(e,t,n)=>t in e?V(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,d=(e,t)=>{for(var n in t||(t={}))L.call(t,n)&&C(e,n,t[n]);if(y)for(var n of y(t))D.call(t,n)&&C(e,n,t[n]);return e},c=(e,t)=>H(e,W(t));var E=(e,t)=>{var n={};for(var r in e)L.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&y)for(var r of y(e))t.indexOf(r)<0&&D.call(e,r)&&(n[r]=e[r]);return n};import{deriveSessionSegment as se}from"@sentientui/core";import{cookies as ie,headers as oe}from"next/headers";import{preloadAssignments as X,readSessionCookie as z}from"@sentientui/core/server";import{preloadDecisions as Ae}from"@sentientui/core/server";function M(){return typeof crypto!="undefined"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`snt-${Date.now()}-${Math.random().toString(36).slice(2,11)}`}async function U(e,t){var s,o,i;let n=(i=(o=z(t.cookies))!=null?o:(s=t.createSessionId)==null?void 0:s.call(t))!=null?i:M();return{assignments:await X(e,n,{apiKey:t.apiKey,baseUrl:t.baseUrl,origin:t.origin,userAgent:t.userAgent,referer:t.referer,timeoutMs:t.timeoutMs}),sessionId:n}}async function N(e){var o,i,a,p;let{preloadDecisions:t,readSessionCookie:n}=await import("@sentientui/core/server"),r=(a=(i=n(e.cookies))!=null?i:(o=e.createSessionId)==null?void 0:o.call(e))!=null?a:M(),s=await t({sections:e.sections,components:(p=e.components)!=null?p:[]},r,{apiKey:e.apiKey,baseUrl:e.baseUrl,origin:e.origin,userAgent:e.userAgent,referer:e.referer,timeoutMs:e.timeoutMs});return c(d({},s),{sessionId:r})}import{AdaptiveRootClient as ae}from"./adaptive-root-client.js";var G=["page","blocks","layoutOrder"],j=new Map;function Q(e,t){j.set(e,t)}function Y(e){return j.get(e)}function w(e){var s,o;let t=(o=(s=e.content)!=null?s:Y(e.page))!=null?o:{},n={};for(let[i,a]of Object.entries(t))G.includes(i)||(n[i]=a);let r=c(d({},n),{page:e.page,blocks:e.blocks});return e.layoutOrder&&(r.layoutOrder=e.layoutOrder),r}function B(e){return JSON.stringify(d({"@context":"https://schema.org","@type":"WebPage"},e)).replace(/</g,"\\u003c")}function K(e){let t=[];e.title&&t.push(`# ${e.title}`,""),e.summary&&t.push(String(e.summary),"");for(let[n,r]of Object.entries(e))["page","title","summary","blocks","layoutOrder"].includes(n)||t.push(`## ${n}`,"","```json",JSON.stringify(r,null,2),"```","");if(e.blocks.length>0){t.push("## blocks","");for(let n of e.blocks)t.push(`- **${n.id}** \u2192 variant \`${n.variant}\``),n.content!==void 0&&t.push(""," ```json",JSON.stringify(n.content,null,2)," ```");t.push("")}return t.join(`
2
+ `).trimEnd()+`
3
+ `}import{matchedAgentToken as Z}from"@sentientui/core";function ee(e,t){return e.pathname.endsWith(".md")||t.toLowerCase().includes("text/markdown")}function te(e){return async t=>{var a;let n=new URL(t.url),r=(a=t.headers.get("accept"))!=null?a:"",s=t.headers.get("user-agent"),o=n.pathname.replace(/\.(md|json)$/,"");if(e.onRead)try{Promise.resolve(e.onRead({path:o,userAgent:s,botName:Z(s!=null?s:"")})).catch(()=>{})}catch(p){}let i=await e.getFeed(t);return ee(n,r)?new Response(K(i),{headers:{"content-type":"text/markdown; charset=utf-8"}}):new Response(JSON.stringify(i),{headers:{"content-type":"application/json; charset=utf-8"}})}}import{matchedAgentToken as ne}from"@sentientui/core";function re(e){return async t=>{let n=t.headers.get("user-agent"),r=ne(n!=null?n:"");if(!r)return!1;try{let s=new URL(t.url).pathname;await Promise.resolve(e.onCrawler({path:s,userAgent:n,botName:r})).catch(()=>{})}catch(s){}return!0}}import{Fragment as ge,jsx as $,jsxs as ce}from"react/jsx-runtime";var _="https://api.sentient-ui.com/v1";function de(e){return Object.entries(e).map(([t,n])=>{var r;return{id:t,variant:typeof n=="string"?n:(r=n==null?void 0:n.variantId)!=null?r:""}})}async function Pe(e){var F,O,I,P;let b=e,{components:t,sections:n,appOrigin:r,initialAssignments:s,ssrSessionId:o,timeoutMs:i,agentFeed:a,children:p}=b,v=E(b,["components","sections","appOrigin","initialAssignments","ssrSessionId","timeoutMs","agentFeed","children"]),m=await oe(),A=m.get("host");!r&&process.env.NODE_ENV==="production"&&!A&&console.error("[SentientUI] AdaptiveRoot: Host header is absent and no appOrigin was provided. Pass appOrigin explicitly \u2014 the https://localhost fallback will likely fail allowed_origins validation.");let h=r!=null?r:process.env.NODE_ENV==="production"?`https://${A!=null?A:"localhost"}`:"http://localhost:3001",R=(F=m.get("user-agent"))!=null?F:void 0,k=(O=m.get("referer"))!=null?O:void 0,J=se({userAgent:R,referer:k,appOrigin:h}),S=await ie(),l,u=null,f;if(s)l=s,f=o;else if(n&&n.length>0){let g=await N({sections:n,components:t,cookies:S,apiKey:v.apiKey,baseUrl:_,origin:h,userAgent:R,referer:k,timeoutMs:i});l=g.assignments,u=g.layoutOrder,f=g.sessionId}else{let g=await U(t,{cookies:S,apiKey:v.apiKey,baseUrl:_,origin:h,userAgent:R,referer:k,timeoutMs:i});l=g.assignments,f=g.sessionId}let x=$(ae,c(d({},v),{initialAssignments:l,initialLayoutOrder:u,sessionSegment:J,ssrSessionId:f,children:p}));if(!a)return x;let q=(P=(I=a.path)!=null?I:m.get("x-pathname"))!=null?P:"/",T=w({page:q,blocks:de(l),layoutOrder:u!=null?u:void 0,content:a.content});return ce(ge,{children:[$("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:B(T)}}),x]})}export{Pe as AdaptiveRoot,w as buildAgentFeedFor,te as createAgentFeed,Q as defineAgentContent,re as sentientAgentMiddleware};
2
4
  //# sourceMappingURL=adaptive-root.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/adaptive-root.tsx","../../src/server.ts"],"sourcesContent":["import { deriveSessionSegment } from '@sentientui/core';\nimport { cookies, headers } from 'next/headers';\nimport type { ReactNode } from 'react';\nimport type { AdaptiveProviderProps } from '../provider.js';\nimport {\n loadAdaptiveAssignments,\n loadAdaptiveDecision,\n type ServerAssignments,\n} from '../server.js';\nimport { AdaptiveRootClient } from './adaptive-root-client.js';\n\nconst DEFAULT_API_BASE_URL = 'https://api.sentient-ui.com/v1';\n\nexport type PreloadComponent = { id: string; variantIds: string[] };\n\nexport type AdaptiveRootProps = Omit<AdaptiveProviderProps, 'initialAssignments' | 'onAssignment'> & {\n /** Components to assign server-side (SEO-safe). */\n components: PreloadComponent[];\n /**\n * Declare page section IDs in their default order. When provided,\n * AdaptiveRoot calls `/v1/decide` (single round trip) instead of\n * individual `/v1/assign` calls, and `useLayoutOrder()` returns the\n * persona-specific order on first render.\n *\n * @example sections={['hero', 'pricing', 'features', 'social_proof']}\n */\n sections?: string[];\n /** App origin — must be in the project's `allowed_origins`. */\n appOrigin?: string;\n /** Override: when set no network fetch is made. Useful for tests. */\n initialAssignments?: ServerAssignments;\n /**\n * Milliseconds to wait for the API before rendering default variants.\n * Defaults to 1500. Increase if you see unexpected fallbacks in production.\n */\n timeoutMs?: number;\n children: ReactNode;\n};\n\n/**\n * Next.js Server Component that resolves variant assignments (and optionally\n * section layout order) server-side for zero layout shift on first paint.\n *\n * When `sections` is provided, a single `POST /v1/decide` call returns both\n * the persona-specific section order and all component assignments.\n * Without `sections`, individual `/v1/assign` calls are made per component.\n *\n * @example\n * // app/page.tsx — with section layout\n * import { AdaptiveRoot } from '@sentientui/react/next';\n *\n * export default async function Page() {\n * return (\n * <AdaptiveRoot\n * apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}\n * context=\"landing\"\n * sections={['hero', 'pricing', 'features', 'social_proof']}\n * components={[\n * { id: 'hero_cta', variantIds: ['default', 'accent'] },\n * ]}\n * >\n * <HeroSection />\n * <PricingSection />\n * <FeaturesSection />\n * <SocialProofSection />\n * </AdaptiveRoot>\n * );\n * }\n */\nexport async function AdaptiveRoot(props: AdaptiveRootProps): Promise<JSX.Element> {\n const {\n components,\n sections,\n appOrigin,\n initialAssignments: initialAssignmentsOverride,\n ssrSessionId: ssrSessionIdProp,\n timeoutMs,\n children,\n ...providerProps\n } = props;\n\n const headerStore = await headers();\n const host = headerStore.get('host');\n if (!appOrigin && process.env.NODE_ENV === 'production' && !host) {\n console.error(\n '[SentientUI] AdaptiveRoot: Host header is absent and no appOrigin was provided. ' +\n 'Pass appOrigin explicitly — the https://localhost fallback will likely fail allowed_origins validation.',\n );\n }\n const resolvedOrigin = appOrigin ??\n (process.env.NODE_ENV === 'production'\n ? `https://${host ?? 'localhost'}`\n : 'http://localhost:3001');\n const userAgent = headerStore.get('user-agent') ?? undefined;\n const referer = headerStore.get('referer') ?? undefined;\n const sessionSegment = deriveSessionSegment({ userAgent, referer, appOrigin: resolvedOrigin });\n const cookieStore = await cookies();\n\n let initialAssignments: ServerAssignments;\n let initialLayoutOrder: string[] | null = null;\n let ssrSessionId: string | undefined;\n\n if (initialAssignmentsOverride) {\n initialAssignments = initialAssignmentsOverride;\n ssrSessionId = ssrSessionIdProp;\n } else if (sections && sections.length > 0) {\n const decision = await loadAdaptiveDecision({\n sections,\n components,\n cookies: cookieStore,\n apiKey: providerProps.apiKey,\n baseUrl: DEFAULT_API_BASE_URL,\n origin: resolvedOrigin,\n userAgent,\n referer,\n timeoutMs,\n });\n initialAssignments = decision.assignments;\n initialLayoutOrder = decision.layoutOrder;\n ssrSessionId = decision.sessionId;\n } else {\n const result = await loadAdaptiveAssignments(components, {\n cookies: cookieStore,\n apiKey: providerProps.apiKey,\n baseUrl: DEFAULT_API_BASE_URL,\n origin: resolvedOrigin,\n userAgent,\n referer,\n timeoutMs,\n });\n initialAssignments = result.assignments;\n ssrSessionId = result.sessionId;\n }\n\n return (\n <AdaptiveRootClient\n {...providerProps}\n initialAssignments={initialAssignments}\n initialLayoutOrder={initialLayoutOrder}\n sessionSegment={sessionSegment}\n ssrSessionId={ssrSessionId}\n >\n {children}\n </AdaptiveRootClient>\n );\n}\n","/**\n * Server-only helpers for Next.js / SSR. No React or DOM APIs.\n */\nimport {\n preloadAssignments,\n readSessionCookie,\n type ServerAssignConfig,\n type ServerAssignments,\n} from '@sentientui/core/server';\n\nexport { preloadAssignments, readSessionCookie };\nexport type { ServerAssignConfig, ServerAssignments };\n\n/** Return value of `loadAdaptiveAssignments` — includes the session ID used for SSR. */\nexport type LoadAdaptiveAssignmentsResult = {\n assignments: ServerAssignments;\n /** The session ID used for SSR assignment. Pass as `ssrSessionId` to `<AdaptiveProvider>`. */\n sessionId: string;\n};\n\nexport type LoadAdaptiveAssignmentsOptions = {\n /** Next.js `cookies()` return value, or any object with `get(name)`. */\n cookies: { get(name: string): { value: string } | undefined };\n apiKey: string;\n baseUrl: string;\n /** Used when `_snt_uid` is absent (e.g. first visit, many crawlers). */\n createSessionId?: () => string;\n /** Must match a value in the project's `allowed_origins` (e.g. `http://localhost:3001`). */\n origin?: string;\n /** From Next.js `headers().get('user-agent')` — aligns SSR segment with the client. */\n userAgent?: string;\n /** From Next.js `headers().get('referer')`. */\n referer?: string;\n /** Milliseconds to wait for the API before returning default variants. Defaults to 1500. */\n timeoutMs?: number;\n};\n\nfunction defaultSessionId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `snt-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * Fetches bandit assignments on the server for SEO-safe HTML.\n * Pass `assignments` as `initialAssignments` and `sessionId` as `ssrSessionId`\n * on `<AdaptiveProvider>` so the client adopts the same session on first visit.\n */\nexport async function loadAdaptiveAssignments(\n components: Array<{ id: string; variantIds: string[] }>,\n options: LoadAdaptiveAssignmentsOptions,\n): Promise<LoadAdaptiveAssignmentsResult> {\n const sessionId =\n readSessionCookie(options.cookies) ??\n options.createSessionId?.() ??\n defaultSessionId();\n\n const assignments = await preloadAssignments(components, sessionId, {\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n origin: options.origin,\n userAgent: options.userAgent,\n referer: options.referer,\n timeoutMs: options.timeoutMs,\n });\n\n return { assignments, sessionId };\n}\n\nexport { preloadDecisions } from '@sentientui/core/server';\nexport type { DecideResult } from '@sentientui/core/server';\n\n/** Return value of `loadAdaptiveDecision` — includes the session ID used for SSR. */\nexport type LoadAdaptiveDecisionResult = import('@sentientui/core/server').DecideResult & {\n sessionId: string;\n};\n\nexport type LoadAdaptiveDecisionOptions = LoadAdaptiveAssignmentsOptions & {\n /** Section IDs in default order. Passed to /v1/decide as the candidate layout. */\n sections: string[];\n /** Components to assign in the same decide call. */\n components?: Array<{ id: string; variantIds?: string[] }>;\n};\n\n/**\n * SSR helper for pages with a declared section layout. Calls `/v1/decide`\n * instead of multiple `/v1/assign` round trips.\n * Pass `sessionId` as `ssrSessionId` on `<AdaptiveProvider>` so the client\n * adopts the same session on first visit.\n */\nexport async function loadAdaptiveDecision(\n options: LoadAdaptiveDecisionOptions,\n): Promise<LoadAdaptiveDecisionResult> {\n const { preloadDecisions, readSessionCookie } = await import('@sentientui/core/server');\n\n const sessionId =\n readSessionCookie(options.cookies) ??\n options.createSessionId?.() ??\n defaultSessionId();\n\n const result = await preloadDecisions(\n {\n sections: options.sections,\n components: options.components ?? [],\n },\n sessionId,\n {\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n origin: options.origin,\n userAgent: options.userAgent,\n referer: options.referer,\n timeoutMs: options.timeoutMs,\n },\n );\n\n return { ...result, sessionId };\n}\n"],"mappings":"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","names":["deriveSessionSegment","cookies","headers","preloadAssignments","readSessionCookie","preloadDecisions","defaultSessionId","loadAdaptiveAssignments","components","options","_a","_b","_c","sessionId","readSessionCookie","preloadAssignments","loadAdaptiveDecision","_d","preloadDecisions","result","__spreadProps","__spreadValues","AdaptiveRootClient","jsx","DEFAULT_API_BASE_URL","AdaptiveRoot","props","_b","_c","_a","components","sections","appOrigin","initialAssignmentsOverride","ssrSessionIdProp","timeoutMs","children","providerProps","__objRest","headerStore","headers","host","resolvedOrigin","userAgent","referer","sessionSegment","deriveSessionSegment","cookieStore","cookies","initialAssignments","initialLayoutOrder","ssrSessionId","decision","loadAdaptiveDecision","result","loadAdaptiveAssignments","AdaptiveRootClient","__spreadProps","__spreadValues"]}
1
+ {"version":3,"sources":["../../src/next/adaptive-root.tsx","../../src/server.ts","../../src/agent-feed.ts","../../src/next/agent-feed-route.ts","../../src/next/agent-middleware.ts"],"sourcesContent":["import { deriveSessionSegment } from '@sentientui/core';\nimport { cookies, headers } from 'next/headers';\nimport type { ReactNode } from 'react';\nimport type { AdaptiveProviderProps } from '../provider.js';\nimport {\n loadAdaptiveAssignments,\n loadAdaptiveDecision,\n type ServerAssignments,\n} from '../server.js';\nimport { AdaptiveRootClient } from './adaptive-root-client.js';\nimport { buildAgentFeed, renderAgentJsonLdBody, type AgentBlock } from '../agent-feed.js';\n\nexport { createAgentFeed } from './agent-feed-route.js';\nexport type { AgentFeedRouteConfig, AgentFeedReadEntry } from './agent-feed-route.js';\nexport { sentientAgentMiddleware } from './agent-middleware.js';\nexport type { SentientAgentMiddlewareConfig, CrawlerRequestEntry } from './agent-middleware.js';\nexport { defineAgentContent, buildAgentFeed as buildAgentFeedFor } from '../agent-feed.js';\nexport type { AgentFeed } from '../agent-feed.js';\n\nconst DEFAULT_API_BASE_URL = 'https://api.sentient-ui.com/v1';\n\nexport type PreloadComponent = { id: string; variantIds: string[] };\n\nexport type AdaptiveRootProps = Omit<AdaptiveProviderProps, 'initialAssignments' | 'onAssignment'> & {\n /** Components to assign server-side (SEO-safe). */\n components: PreloadComponent[];\n /**\n * Declare page section IDs in their default order. When provided,\n * AdaptiveRoot calls `/v1/decide` (single round trip) instead of\n * individual `/v1/assign` calls, and `useLayoutOrder()` returns the\n * persona-specific order on first render.\n *\n * @example sections={['hero', 'pricing', 'features', 'social_proof']}\n */\n sections?: string[];\n /** App origin — must be in the project's `allowed_origins`. */\n appOrigin?: string;\n /** Override: when set no network fetch is made. Useful for tests. */\n initialAssignments?: ServerAssignments;\n /**\n * Milliseconds to wait for the API before rendering default variants.\n * Defaults to 1500. Increase if you see unexpected fallbacks in production.\n */\n timeoutMs?: number;\n /**\n * When set, AdaptiveRoot emits a server-rendered inline JSON-LD block\n * describing the page's winning variants, layout order, and developer-supplied\n * content — readable by passive AI crawlers (which do not run JS). Invisible to\n * humans (a JSON-LD script renders no visible DOM). Omit to emit nothing.\n */\n agentFeed?: {\n /** Page path for the feed. Falls back to the `x-pathname` request header, then '/'. */\n path?: string;\n /** Structured page content. Falls back to the `defineAgentContent` registry for this path. */\n content?: Record<string, unknown>;\n };\n children: ReactNode;\n};\n\n/** Build agent blocks from SSR assignments (Record<id, variantId | { variantId }>). */\nfunction assignmentsToBlocks(assignments: ServerAssignments): AgentBlock[] {\n return Object.entries(assignments as Record<string, unknown>).map(([id, v]) => ({\n id,\n variant: typeof v === 'string' ? v : ((v as { variantId?: string })?.variantId ?? ''),\n }));\n}\n\n/**\n * Next.js Server Component that resolves variant assignments (and optionally\n * section layout order) server-side for zero layout shift on first paint.\n *\n * When `sections` is provided, a single `POST /v1/decide` call returns both\n * the persona-specific section order and all component assignments.\n * Without `sections`, individual `/v1/assign` calls are made per component.\n *\n * @example\n * // app/page.tsx — with section layout\n * import { AdaptiveRoot } from '@sentientui/react/next';\n *\n * export default async function Page() {\n * return (\n * <AdaptiveRoot\n * apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}\n * context=\"landing\"\n * sections={['hero', 'pricing', 'features', 'social_proof']}\n * components={[\n * { id: 'hero_cta', variantIds: ['default', 'accent'] },\n * ]}\n * >\n * <HeroSection />\n * <PricingSection />\n * <FeaturesSection />\n * <SocialProofSection />\n * </AdaptiveRoot>\n * );\n * }\n */\nexport async function AdaptiveRoot(props: AdaptiveRootProps): Promise<JSX.Element> {\n const {\n components,\n sections,\n appOrigin,\n initialAssignments: initialAssignmentsOverride,\n ssrSessionId: ssrSessionIdProp,\n timeoutMs,\n agentFeed,\n children,\n ...providerProps\n } = props;\n\n const headerStore = await headers();\n const host = headerStore.get('host');\n if (!appOrigin && process.env.NODE_ENV === 'production' && !host) {\n console.error(\n '[SentientUI] AdaptiveRoot: Host header is absent and no appOrigin was provided. ' +\n 'Pass appOrigin explicitly — the https://localhost fallback will likely fail allowed_origins validation.',\n );\n }\n const resolvedOrigin = appOrigin ??\n (process.env.NODE_ENV === 'production'\n ? `https://${host ?? 'localhost'}`\n : 'http://localhost:3001');\n const userAgent = headerStore.get('user-agent') ?? undefined;\n const referer = headerStore.get('referer') ?? undefined;\n const sessionSegment = deriveSessionSegment({ userAgent, referer, appOrigin: resolvedOrigin });\n const cookieStore = await cookies();\n\n let initialAssignments: ServerAssignments;\n let initialLayoutOrder: string[] | null = null;\n let ssrSessionId: string | undefined;\n\n if (initialAssignmentsOverride) {\n initialAssignments = initialAssignmentsOverride;\n ssrSessionId = ssrSessionIdProp;\n } else if (sections && sections.length > 0) {\n const decision = await loadAdaptiveDecision({\n sections,\n components,\n cookies: cookieStore,\n apiKey: providerProps.apiKey,\n baseUrl: DEFAULT_API_BASE_URL,\n origin: resolvedOrigin,\n userAgent,\n referer,\n timeoutMs,\n });\n initialAssignments = decision.assignments;\n initialLayoutOrder = decision.layoutOrder;\n ssrSessionId = decision.sessionId;\n } else {\n const result = await loadAdaptiveAssignments(components, {\n cookies: cookieStore,\n apiKey: providerProps.apiKey,\n baseUrl: DEFAULT_API_BASE_URL,\n origin: resolvedOrigin,\n userAgent,\n referer,\n timeoutMs,\n });\n initialAssignments = result.assignments;\n ssrSessionId = result.sessionId;\n }\n\n const client = (\n <AdaptiveRootClient\n {...providerProps}\n initialAssignments={initialAssignments}\n initialLayoutOrder={initialLayoutOrder}\n sessionSegment={sessionSegment}\n ssrSessionId={ssrSessionId}\n >\n {children}\n </AdaptiveRootClient>\n );\n\n if (!agentFeed) return client;\n\n // Server-rendered inline JSON-LD for AI crawlers. Emitted only on the server\n // path so passive crawlers (no JS) see it in the raw HTML. The body escapes\n // '<' so page content cannot break out of the <script> element.\n const path = agentFeed.path ?? headerStore.get('x-pathname') ?? '/';\n const feed = buildAgentFeed({\n page: path,\n blocks: assignmentsToBlocks(initialAssignments),\n layoutOrder: initialLayoutOrder ?? undefined,\n content: agentFeed.content,\n });\n\n return (\n <>\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: renderAgentJsonLdBody(feed) }}\n />\n {client}\n </>\n );\n}\n","/**\n * Server-only helpers for Next.js / SSR. No React or DOM APIs.\n */\nimport {\n preloadAssignments,\n readSessionCookie,\n type ServerAssignConfig,\n type ServerAssignments,\n} from '@sentientui/core/server';\n\nexport { preloadAssignments, readSessionCookie };\nexport type { ServerAssignConfig, ServerAssignments };\n\n/** Return value of `loadAdaptiveAssignments` — includes the session ID used for SSR. */\nexport type LoadAdaptiveAssignmentsResult = {\n assignments: ServerAssignments;\n /** The session ID used for SSR assignment. Pass as `ssrSessionId` to `<AdaptiveProvider>`. */\n sessionId: string;\n};\n\nexport type LoadAdaptiveAssignmentsOptions = {\n /** Next.js `cookies()` return value, or any object with `get(name)`. */\n cookies: { get(name: string): { value: string } | undefined };\n apiKey: string;\n baseUrl: string;\n /** Used when `_snt_uid` is absent (e.g. first visit, many crawlers). */\n createSessionId?: () => string;\n /** Must match a value in the project's `allowed_origins` (e.g. `http://localhost:3001`). */\n origin?: string;\n /** From Next.js `headers().get('user-agent')` — aligns SSR segment with the client. */\n userAgent?: string;\n /** From Next.js `headers().get('referer')`. */\n referer?: string;\n /** Milliseconds to wait for the API before returning default variants. Defaults to 1500. */\n timeoutMs?: number;\n};\n\nfunction defaultSessionId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `snt-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * Fetches bandit assignments on the server for SEO-safe HTML.\n * Pass `assignments` as `initialAssignments` and `sessionId` as `ssrSessionId`\n * on `<AdaptiveProvider>` so the client adopts the same session on first visit.\n */\nexport async function loadAdaptiveAssignments(\n components: Array<{ id: string; variantIds: string[] }>,\n options: LoadAdaptiveAssignmentsOptions,\n): Promise<LoadAdaptiveAssignmentsResult> {\n const sessionId =\n readSessionCookie(options.cookies) ??\n options.createSessionId?.() ??\n defaultSessionId();\n\n const assignments = await preloadAssignments(components, sessionId, {\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n origin: options.origin,\n userAgent: options.userAgent,\n referer: options.referer,\n timeoutMs: options.timeoutMs,\n });\n\n return { assignments, sessionId };\n}\n\nexport { preloadDecisions } from '@sentientui/core/server';\nexport type { DecideResult } from '@sentientui/core/server';\n\n/** Return value of `loadAdaptiveDecision` — includes the session ID used for SSR. */\nexport type LoadAdaptiveDecisionResult = import('@sentientui/core/server').DecideResult & {\n sessionId: string;\n};\n\nexport type LoadAdaptiveDecisionOptions = LoadAdaptiveAssignmentsOptions & {\n /** Section IDs in default order. Passed to /v1/decide as the candidate layout. */\n sections: string[];\n /** Components to assign in the same decide call. */\n components?: Array<{ id: string; variantIds?: string[] }>;\n};\n\n/**\n * SSR helper for pages with a declared section layout. Calls `/v1/decide`\n * instead of multiple `/v1/assign` round trips.\n * Pass `sessionId` as `ssrSessionId` on `<AdaptiveProvider>` so the client\n * adopts the same session on first visit.\n */\nexport async function loadAdaptiveDecision(\n options: LoadAdaptiveDecisionOptions,\n): Promise<LoadAdaptiveDecisionResult> {\n const { preloadDecisions, readSessionCookie } = await import('@sentientui/core/server');\n\n const sessionId =\n readSessionCookie(options.cookies) ??\n options.createSessionId?.() ??\n defaultSessionId();\n\n const result = await preloadDecisions(\n {\n sections: options.sections,\n components: options.components ?? [],\n },\n sessionId,\n {\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n origin: options.origin,\n userAgent: options.userAgent,\n referer: options.referer,\n timeoutMs: options.timeoutMs,\n },\n );\n\n return { ...result, sessionId };\n}\n","/**\n * Agent-readable content feed. Merges SDK-known data (winning variants, layout\n * order) with developer-supplied page content, and renders it either as a\n * server-rendered inline JSON-LD block (read by passive AI crawlers, which do\n * not run JS) or as Markdown (for agents that content-negotiate `text/markdown`).\n *\n * No React or DOM APIs — safe to import in server components, route handlers,\n * and middleware.\n */\n\nexport type AgentBlock = {\n /** Component ID. */\n id: string;\n /** Winning variant ID currently served. */\n variant: string;\n /** Agent-readable data attached to the served variant, if any. */\n content?: unknown;\n};\n\nexport type AgentFeed = {\n page: string;\n title?: string;\n summary?: string;\n layoutOrder?: string[];\n blocks: AgentBlock[];\n /** Developer-supplied extra fields (products, specs, arbitrary JSON). */\n [key: string]: unknown;\n};\n\n/** Fields the SDK owns — developer content can never overwrite these. */\nconst RESERVED_FIELDS = ['page', 'blocks', 'layoutOrder'] as const;\n\nconst registry = new Map<string, Record<string, unknown>>();\n\n/**\n * Register page-level structured content the SDK can't infer (title, summary,\n * product fields, arbitrary JSON), keyed by page path. Call at module load.\n */\nexport function defineAgentContent(page: string, content: Record<string, unknown>): void {\n registry.set(page, content);\n}\n\n/** Look up registered content for a page. */\nexport function getAgentContent(page: string): Record<string, unknown> | undefined {\n return registry.get(page);\n}\n\n/** Clear the registry — intended for tests. */\nexport function clearAgentContent(): void {\n registry.clear();\n}\n\n/**\n * Merge SDK-known data with developer-supplied content into a single feed.\n * Developer content fills in title/summary/etc. but can never overwrite the\n * SDK-authoritative fields (`page`, `blocks`, `layoutOrder`).\n */\nexport function buildAgentFeed(input: {\n page: string;\n blocks: AgentBlock[];\n layoutOrder?: string[];\n content?: Record<string, unknown>;\n}): AgentFeed {\n const supplied = input.content ?? getAgentContent(input.page) ?? {};\n const safe: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(supplied)) {\n if (!(RESERVED_FIELDS as readonly string[]).includes(k)) safe[k] = v;\n }\n const feed: AgentFeed = {\n ...safe,\n page: input.page,\n blocks: input.blocks,\n };\n if (input.layoutOrder) feed.layoutOrder = input.layoutOrder;\n return feed;\n}\n\n/**\n * Render the feed as a server-rendered inline JSON-LD `<script>` string. `<` is\n * escaped to `<` so embedded content cannot break out of the script\n * element. MUST be emitted on the server — passive crawlers never run client JS.\n */\nexport function renderAgentJsonLd(feed: AgentFeed): string {\n return `<script type=\"application/ld+json\">${renderAgentJsonLdBody(feed)}</script>`;\n}\n\n/**\n * The escaped JSON-LD body only (no `<script>` wrapper). For React server\n * components, inject via `<script type=\"application/ld+json\"\n * dangerouslySetInnerHTML={{ __html: renderAgentJsonLdBody(feed) }} />`.\n */\nexport function renderAgentJsonLdBody(feed: AgentFeed): string {\n return JSON.stringify({ '@context': 'https://schema.org', '@type': 'WebPage', ...feed })\n .replace(/</g, '\\\\u003c');\n}\n\n/** Render the feed as Markdown for agents that negotiate `text/markdown`. */\nexport function renderAgentMarkdown(feed: AgentFeed): string {\n const lines: string[] = [];\n if (feed.title) lines.push(`# ${feed.title}`, '');\n if (feed.summary) lines.push(String(feed.summary), '');\n\n for (const [k, v] of Object.entries(feed)) {\n if (['page', 'title', 'summary', 'blocks', 'layoutOrder'].includes(k)) continue;\n lines.push(`## ${k}`, '', '```json', JSON.stringify(v, null, 2), '```', '');\n }\n\n if (feed.blocks.length > 0) {\n lines.push('## blocks', '');\n for (const b of feed.blocks) {\n lines.push(`- **${b.id}** → variant \\`${b.variant}\\``);\n if (b.content !== undefined) {\n lines.push('', ' ```json', JSON.stringify(b.content, null, 2), ' ```');\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n').trimEnd() + '\\n';\n}\n","/**\n * `createAgentFeed` — a framework-standard Route Handler (Web `Request` →\n * `Response`) that serves the agent-readable feed via HTTP content negotiation:\n * `text/markdown` (Accept header or `.md` URL) → Markdown, otherwise JSON.\n *\n * This is the secondary channel of the agent-readable design — for agents that\n * *ask* (coding agents, agents the customer controls, future crawlers). The\n * primary channel is the server-rendered inline JSON-LD from `<AdaptiveRoot>`.\n *\n * Every fetch is reported to `onRead` (best-effort) so it can be logged to\n * `crawler_requests` and counted in the dashboard traffic breakdown.\n */\nimport { matchedAgentToken } from '@sentientui/core';\nimport { renderAgentMarkdown, type AgentFeed } from '../agent-feed.js';\n\nexport type AgentFeedReadEntry = {\n path: string;\n userAgent: string | null;\n botName: string | null;\n};\n\nexport type AgentFeedRouteConfig = {\n /** Resolve the feed for a request (winning variants + layout + dev content). */\n getFeed: (request: Request) => Promise<AgentFeed> | AgentFeed;\n /** Best-effort per-fetch hook — log to `crawler_requests` here. Never awaited into the response. */\n onRead?: (entry: AgentFeedReadEntry) => void | Promise<void>;\n};\n\nfunction wantsMarkdown(url: URL, accept: string): boolean {\n return url.pathname.endsWith('.md') || accept.toLowerCase().includes('text/markdown');\n}\n\nexport function createAgentFeed(\n config: AgentFeedRouteConfig,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n const accept = request.headers.get('accept') ?? '';\n const userAgent = request.headers.get('user-agent');\n // Strip the .md/.json suffix so the feed path matches the page path.\n const path = url.pathname.replace(/\\.(md|json)$/, '');\n\n if (config.onRead) {\n try {\n void Promise.resolve(\n config.onRead({ path, userAgent, botName: matchedAgentToken(userAgent ?? '') }),\n ).catch(() => {});\n } catch {\n /* logging must never break the response */\n }\n }\n\n const feed = await config.getFeed(request);\n\n if (wantsMarkdown(url, accept)) {\n return new Response(renderAgentMarkdown(feed), {\n headers: { 'content-type': 'text/markdown; charset=utf-8' },\n });\n }\n return new Response(JSON.stringify(feed), {\n headers: { 'content-type': 'application/json; charset=utf-8' },\n });\n };\n}\n","/**\n * `sentientAgentMiddleware` — additive-discovery middleware. Detects known AI\n * crawler / agent user-agents on page requests and reports them so they can be\n * logged to `crawler_requests` (the only way to observe passive crawlers, which\n * run no JS and never create a session). It NEVER changes the response — the\n * caller composes it into their Next.js middleware and returns `NextResponse.next()`.\n *\n * export async function middleware(request: NextRequest) {\n * await logCrawler(request); // sentientAgentMiddleware instance\n * return NextResponse.next();\n * }\n */\nimport { matchedAgentToken } from '@sentientui/core';\n\nexport type CrawlerRequestEntry = {\n path: string;\n userAgent: string | null;\n botName: string;\n};\n\nexport type SentientAgentMiddlewareConfig = {\n /** Best-effort sink — persist to `crawler_requests` here. Failures are swallowed. */\n onCrawler: (entry: CrawlerRequestEntry) => void | Promise<void>;\n};\n\nexport function sentientAgentMiddleware(\n config: SentientAgentMiddlewareConfig,\n): (request: Request) => Promise<boolean> {\n return async (request: Request): Promise<boolean> => {\n const userAgent = request.headers.get('user-agent');\n const botName = matchedAgentToken(userAgent ?? '');\n if (!botName) return false;\n\n try {\n const path = new URL(request.url).pathname;\n await Promise.resolve(config.onCrawler({ path, userAgent, botName })).catch(() => {});\n } catch {\n /* detection/logging must never break the request */\n }\n return true;\n };\n}\n"],"mappings":"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","names":["deriveSessionSegment","cookies","headers","preloadAssignments","readSessionCookie","preloadDecisions","defaultSessionId","loadAdaptiveAssignments","components","options","_a","_b","_c","sessionId","readSessionCookie","preloadAssignments","loadAdaptiveDecision","_d","preloadDecisions","result","__spreadProps","__spreadValues","AdaptiveRootClient","RESERVED_FIELDS","registry","defineAgentContent","page","content","getAgentContent","buildAgentFeed","input","_a","_b","supplied","getAgentContent","safe","k","v","RESERVED_FIELDS","feed","__spreadProps","__spreadValues","renderAgentJsonLdBody","feed","__spreadValues","renderAgentMarkdown","lines","k","v","b","matchedAgentToken","wantsMarkdown","url","accept","createAgentFeed","config","request","_a","userAgent","path","matchedAgentToken","e","feed","renderAgentMarkdown","matchedAgentToken","sentientAgentMiddleware","config","request","userAgent","botName","path","e","Fragment","jsx","jsxs","DEFAULT_API_BASE_URL","assignmentsToBlocks","assignments","id","v","_a","AdaptiveRoot","props","_b","_c","_d","_e","components","sections","appOrigin","initialAssignmentsOverride","ssrSessionIdProp","timeoutMs","agentFeed","children","providerProps","__objRest","headerStore","headers","host","resolvedOrigin","userAgent","referer","sessionSegment","deriveSessionSegment","cookieStore","cookies","initialAssignments","initialLayoutOrder","ssrSessionId","decision","loadAdaptiveDecision","result","loadAdaptiveAssignments","client","AdaptiveRootClient","__spreadProps","__spreadValues","path","feed","buildAgentFeed","renderAgentJsonLdBody"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/react",
3
- "version": "0.8.4",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,7 +27,7 @@
27
27
  "dist"
28
28
  ],
29
29
  "dependencies": {
30
- "@sentientui/core": "0.8.2"
30
+ "@sentientui/core": "0.9.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": ">=18.0.0",