@sentientui/core 0.16.1 → 0.16.2
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.
- package/LICENSE +21 -0
- package/dist/{chunk-KZHVBFG7.mjs → chunk-CD2A55US.mjs} +1 -0
- package/dist/chunk-CD2A55US.mjs.map +1 -0
- package/dist/chunk-I2QGVQI6.mjs +2 -0
- package/dist/chunk-I2QGVQI6.mjs.map +1 -0
- package/dist/{chunk-P5ZTJLZE.mjs → chunk-L5TA3FAB.mjs} +2 -1
- package/dist/chunk-L5TA3FAB.mjs.map +1 -0
- package/dist/{chunk-HGGX55FR.mjs → chunk-TMCGHANO.mjs} +1 -0
- package/dist/chunk-TMCGHANO.mjs.map +1 -0
- package/dist/index-C247KMBw.d.ts +394 -0
- package/dist/index-CZLjrtM4.d.cts +394 -0
- package/dist/index-engagement.js +2 -1
- package/dist/index-engagement.js.map +1 -0
- package/dist/index-engagement.mjs +2 -1
- package/dist/index-engagement.mjs.map +1 -0
- package/dist/index-graph.d.cts +38 -3
- package/dist/index-graph.d.ts +38 -3
- package/dist/index-graph.js +2 -1
- package/dist/index-graph.js.map +1 -0
- package/dist/index-graph.mjs +2 -1
- package/dist/index-graph.mjs.map +1 -0
- package/dist/index-local-stub.js +1 -0
- package/dist/index-local-stub.js.map +1 -0
- package/dist/index-local-stub.mjs +2 -1
- package/dist/index-local-stub.mjs.map +1 -0
- package/dist/index-local.d.cts +1 -1
- package/dist/index-local.d.ts +1 -1
- package/dist/index-local.js +1 -0
- package/dist/index-local.js.map +1 -0
- package/dist/index-local.mjs +2 -1
- package/dist/index-local.mjs.map +1 -0
- package/dist/index-server.js +1 -0
- package/dist/index-server.js.map +1 -0
- package/dist/index-server.mjs +2 -1
- package/dist/index-server.mjs.map +1 -0
- package/dist/index.d.cts +2 -430
- package/dist/index.d.ts +2 -430
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -2
- package/dist/chunk-CWUFS37B.mjs +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index-local.ts"],"sourcesContent":["/**\n * @sentientui/core/local — the keyless local decision engine.\n *\n * Loaded ONLY under the `development` export condition (see the \"./local\"\n * exports map in package.json). Production bundles resolve this specifier to\n * `index-local-stub.ts`, so none of this code ships to production.\n *\n * Deterministic by construction: the same (sessionId, persona, declaration)\n * always yields the same decision — across calls, instances, tabs, and the\n * server/client boundary. No I/O, no randomness, no mutable state.\n */\nimport {\n PERSONAS,\n UNKNOWN_PERSONA,\n applyClusterHeuristic,\n fnv1a,\n pickDeterministicArm,\n type PersonaKey,\n} from '@sentientui/policy';\nimport type { DecideOutcome, SlotDeclInput, SlotResult } from './index.js';\n\n/**\n * Sentinel embedded so build-level tests can assert the production bundle\n * physically excludes the local engine (scripts/verify-local-exclusion.ts).\n * Do not rename without updating that script.\n */\nexport const LOCAL_ENGINE_SENTINEL = 'SENTIENT_LOCAL_ENGINE';\n\n/** True on the real engine module, false on the production stub. */\nexport const LOCAL_ENGINE_AVAILABLE = true;\n\n/** Simulated decisions carry a fixed mid confidence (band 'medium'). */\nexport const LOCAL_CONFIDENCE = 0.5;\n\n/**\n * Maps a section id to a semantic section type by substring so\n * `applyClusterHeuristic` gets a useful sectionTypes map without a DOM graph.\n * First matching rule wins, in exactly this order.\n */\nconst SECTION_TYPE_RULES: Array<[substr: string, type: string]> = [\n ['pricing', 'pricing'],\n ['hero', 'hero'],\n ['faq', 'faq'],\n ['cta', 'cta'],\n ['trust', 'trust'],\n ['social', 'social_proof'],\n ['testimonial', 'social_proof'],\n ['feature', 'features'],\n ['comparison', 'comparison'],\n ['compare', 'comparison'],\n ['nav', 'navigation'],\n];\n\nexport function inferSectionTypes(sections: string[]): Map<string, string> {\n const types = new Map<string, string>();\n for (const id of sections) {\n const lower = id.toLowerCase();\n const rule = SECTION_TYPE_RULES.find(([substr]) => lower.includes(substr));\n types.set(id, rule ? rule[1] : 'generic');\n }\n return types;\n}\n\nfunction resolvePersona(sessionId: string, forcedPersona?: string): PersonaKey {\n if (forcedPersona) {\n if ((PERSONAS as readonly string[]).includes(forcedPersona)) return forcedPersona as PersonaKey;\n if (forcedPersona === UNKNOWN_PERSONA) return UNKNOWN_PERSONA;\n }\n return PERSONAS[fnv1a(sessionId) % PERSONAS.length];\n}\n\nfunction decideSlot(sessionId: string, persona: PersonaKey, slot: SlotDeclInput): SlotResult | null {\n // Persona-salted session key — implements the spec's\n // stableHash(sessionId, slotId, sortedArms, forcedPersona) with the pinned\n // pickDeterministicArm signature, so forcing a persona visibly changes\n // tokens and arrangements.\n const saltedSession = `${sessionId}:${persona}`;\n if (slot.arms && slot.arms.length >= 2) {\n return pickDeterministicArm(saltedSession, slot.id, slot.arms);\n }\n if (slot.dims) {\n const result: Record<string, string> = {};\n for (const [dim, values] of Object.entries(slot.dims)) {\n if (!values || values.length < 2) return null;\n result[dim] = pickDeterministicArm(saltedSession, `${slot.id}.${dim}`, [...values]);\n }\n return result;\n }\n return null;\n}\n\nexport function createLocalEngine(opts: { sessionId: string; forcedPersona?: string }): {\n decide(input: {\n sections?: string[];\n components?: Array<{ id: string; variantIds?: string[] }>;\n slots?: SlotDeclInput[];\n }): DecideOutcome;\n} {\n const persona = resolvePersona(opts.sessionId, opts.forcedPersona);\n return {\n decide(input) {\n const layoutOrder =\n input.sections && input.sections.length > 0\n ? applyClusterHeuristic(input.sections, inferSectionTypes(input.sections), persona)\n : null;\n\n const assignments: Record<string, string> = {};\n for (const c of input.components ?? []) {\n if (c.variantIds && c.variantIds.length > 0) {\n assignments[c.id] = pickDeterministicArm(opts.sessionId, c.id, c.variantIds);\n }\n }\n\n const slots: Record<string, SlotResult> = {};\n for (const s of input.slots ?? []) {\n const result = decideSlot(opts.sessionId, persona, s);\n if (result !== null) slots[s.id] = result;\n }\n\n return { layoutOrder, assignments, slots, persona, confidence: LOCAL_CONFIDENCE };\n },\n };\n}\n"],"mappings":"6BAWA,OACE,YAAAA,EACA,mBAAAC,EACA,yBAAAC,EACA,SAAAC,EACA,wBAAAC,MAEK,qBAQA,IAAMC,EAAwB,wBAGxBC,EAAyB,GAGzBC,EAAmB,GAO1BC,EAA4D,CAChE,CAAC,UAAW,SAAS,EACrB,CAAC,OAAQ,MAAM,EACf,CAAC,MAAO,KAAK,EACb,CAAC,MAAO,KAAK,EACb,CAAC,QAAS,OAAO,EACjB,CAAC,SAAU,cAAc,EACzB,CAAC,cAAe,cAAc,EAC9B,CAAC,UAAW,UAAU,EACtB,CAAC,aAAc,YAAY,EAC3B,CAAC,UAAW,YAAY,EACxB,CAAC,MAAO,YAAY,CACtB,EAEO,SAASC,EAAkBC,EAAyC,CACzE,IAAMC,EAAQ,IAAI,IAClB,QAAWC,KAAMF,EAAU,CACzB,IAAMG,EAAQD,EAAG,YAAY,EACvBE,EAAON,EAAmB,KAAK,CAAC,CAACO,CAAM,IAAMF,EAAM,SAASE,CAAM,CAAC,EACzEJ,EAAM,IAAIC,EAAIE,EAAOA,EAAK,CAAC,EAAI,SAAS,CAC1C,CACA,OAAOH,CACT,CAEA,SAASK,EAAeC,EAAmBC,EAAoC,CAC7E,GAAIA,EAAe,CACjB,GAAKlB,EAA+B,SAASkB,CAAa,EAAG,OAAOA,EACpE,GAAIA,IAAkBjB,EAAiB,OAAOA,CAChD,CACA,OAAOD,EAASG,EAAMc,CAAS,EAAIjB,EAAS,MAAM,CACpD,CAEA,SAASmB,EAAWF,EAAmBG,EAAqBC,EAAwC,CAKlG,IAAMC,EAAgB,GAAGL,CAAS,IAAIG,CAAO,GAC7C,GAAIC,EAAK,MAAQA,EAAK,KAAK,QAAU,EACnC,OAAOjB,EAAqBkB,EAAeD,EAAK,GAAIA,EAAK,IAAI,EAE/D,GAAIA,EAAK,KAAM,CACb,IAAME,EAAiC,CAAC,EACxC,OAAW,CAACC,EAAKC,CAAM,IAAK,OAAO,QAAQJ,EAAK,IAAI,EAAG,CACrD,GAAI,CAACI,GAAUA,EAAO,OAAS,EAAG,OAAO,KACzCF,EAAOC,CAAG,EAAIpB,EAAqBkB,EAAe,GAAGD,EAAK,EAAE,IAAIG,CAAG,GAAI,CAAC,GAAGC,CAAM,CAAC,CACpF,CACA,OAAOF,CACT,CACA,OAAO,IACT,CAEO,SAASG,EAAkBC,EAMhC,CACA,IAAMP,EAAUJ,EAAeW,EAAK,UAAWA,EAAK,aAAa,EACjE,MAAO,CACL,OAAOC,EAAO,CApGlB,IAAAC,EAAAC,EAqGM,IAAMC,EACJH,EAAM,UAAYA,EAAM,SAAS,OAAS,EACtC1B,EAAsB0B,EAAM,SAAUnB,EAAkBmB,EAAM,QAAQ,EAAGR,CAAO,EAChF,KAEAY,EAAsC,CAAC,EAC7C,QAAWC,KAAKJ,EAAAD,EAAM,aAAN,KAAAC,EAAoB,CAAC,EAC/BI,EAAE,YAAcA,EAAE,WAAW,OAAS,IACxCD,EAAYC,EAAE,EAAE,EAAI7B,EAAqBuB,EAAK,UAAWM,EAAE,GAAIA,EAAE,UAAU,GAI/E,IAAMC,EAAoC,CAAC,EAC3C,QAAWC,KAAKL,EAAAF,EAAM,QAAN,KAAAE,EAAe,CAAC,EAAG,CACjC,IAAMP,EAASJ,EAAWQ,EAAK,UAAWP,EAASe,CAAC,EAChDZ,IAAW,OAAMW,EAAMC,EAAE,EAAE,EAAIZ,EACrC,CAEA,MAAO,CAAE,YAAAQ,EAAa,YAAAC,EAAa,MAAAE,EAAO,QAAAd,EAAS,WAAYb,CAAiB,CAClF,CACF,CACF","names":["PERSONAS","UNKNOWN_PERSONA","applyClusterHeuristic","fnv1a","pickDeterministicArm","LOCAL_ENGINE_SENTINEL","LOCAL_ENGINE_AVAILABLE","LOCAL_CONFIDENCE","SECTION_TYPE_RULES","inferSectionTypes","sections","types","id","lower","rule","substr","resolvePersona","sessionId","forcedPersona","decideSlot","persona","slot","saltedSession","result","dim","values","createLocalEngine","opts","input","_a","_b","layoutOrder","assignments","c","slots","s"]}
|
package/dist/index-server.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
1
|
"use strict";var R=Object.defineProperty,z=Object.defineProperties,G=Object.getOwnPropertyDescriptor,K=Object.getOwnPropertyDescriptors,q=Object.getOwnPropertyNames,j=Object.getOwnPropertySymbols;var M=Object.prototype.hasOwnProperty,H=Object.prototype.propertyIsEnumerable;var B=(t,e,r)=>e in t?R(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,p=(t,e)=>{for(var r in e||(e={}))M.call(e,r)&&B(t,r,e[r]);if(j)for(var r of j(e))H.call(e,r)&&B(t,r,e[r]);return t},L=(t,e)=>z(t,K(e));var Q=(t,e)=>{for(var r in e)R(t,r,{get:e[r],enumerable:!0})},V=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of q(e))!M.call(t,o)&&o!==r&&R(t,o,{get:()=>e[o],enumerable:!(s=G(e,o))||s.enumerable});return t};var X=t=>V(R({},"__esModule",{value:!0}),t);var te={};Q(te,{buildSessionUpsertPayload:()=>y,deriveSessionSegment:()=>_,detectDeviceClass:()=>A,detectTimeOfDay:()=>v,detectTrafficSource:()=>w,preloadAssignments:()=>F,preloadDecisions:()=>J,readSessionCookie:()=>W,referrerDomainFromReferer:()=>x});module.exports=X(te);var Y=["GPTBot","ChatGPT-User","OAI-SearchBot","ClaudeBot","Claude-User","Claude-SearchBot","PerplexityBot","Perplexity-User","Google-Extended","Applebot-Extended","Meta-ExternalAgent","Bytespider","CCBot","Amazonbot","cohere-ai","Diffbot"];function Z(t){return ee(t)!==null}function ee(t){var r;if(!t)return null;let e=t.toLowerCase();return(r=Y.find(s=>e.includes(s.toLowerCase())))!=null?r:null}function A(t){let e=t.toLowerCase();return/ipad|tablet|playbook|kindle|silk/.test(e)?"tablet":/mobi|iphone|ipod|android.*mobile|phone/.test(e)?"mobile":"desktop"}function w(t,e){if(!t)return"direct";try{let r=new URL(t);if(e)try{if(new URL(e).host===r.host)return"direct"}catch(o){}let s=r.hostname.toLowerCase();return/(^|\.)(google|bing|duckduckgo|yahoo)\./.test(s)?"search":/(^|\.)(twitter\.com|x\.com|facebook\.com|linkedin\.com|reddit\.com|t\.co)$/.test(s)?"social":"referral"}catch(r){return"direct"}}function x(t){if(!t)return null;try{return new URL(t).hostname}catch(e){return null}}function v(t){let e=t.getHours();return e<6?"night":e<12?"morning":e<18?"afternoon":"evening"}function _(t){let e=y("__segment__",t);return`${e.deviceClass}:${e.trafficSource}`}function y(t,e){var c,u,g,d,n,l,a;let r=(u=(c=e==null?void 0:e.userAgent)==null?void 0:c.trim())!=null?u:"",s=(d=(g=e==null?void 0:e.referer)==null?void 0:g.trim())!=null?d:"",o=(n=e==null?void 0:e.now)!=null?n:new Date;return{sessionId:t,ephemeral:!1,utmParams:(l=e==null?void 0:e.utmParams)!=null?l:{},deviceClass:r?A(r):"desktop",trafficSource:s?w(s,e==null?void 0:e.appOrigin):"direct",referrerDomain:x(s),timeOfDay:v(o),dayOfWeek:(a=["sun","mon","tue","wed","thu","fri","sat"][o.getDay()])!=null?a:"sun",automation:(e==null?void 0:e.webdriver)===!0||Z(r)}}var S=require("@sentientui/policy");function O(t){return p(p(p({id:t.id},t.arms?{arms:[...t.arms]}:{}),t.dims?{dims:Object.fromEntries(Object.entries(t.dims).map(([e,r])=>[e,[...r]]))}:{}),t.baseline!==void 0?{baseline:t.baseline}:{})}function U(t){let e=O(t);return(0,S.slotResultFor)(e,(0,S.slotBaselineArm)(e))}function N(t){let e={};for(let r of t)e[r.id]=U(r);return e}var E=1e3;function D(t,e,r){let s=new AbortController,o=setTimeout(()=>s.abort(),r);return fetch(t,L(p({},e),{signal:s.signal})).finally(()=>clearTimeout(o))}async function F(t,e,r){var d;if(r.doNotTrack)return{};let s=(d=r.timeoutMs)!=null?d:E,o={"Content-Type":"application/json",Authorization:`Bearer ${r.apiKey}`};r.origin&&(o.Origin=r.origin);let c=y(e,{userAgent:r.userAgent,referer:r.referer,utmParams:r.utmParams,appOrigin:r.origin});try{let n=await D(`${r.baseUrl}/sessions`,{method:"POST",headers:o,body:JSON.stringify(c)},s);if(n.status===402)console.warn("[SentientUI] Session limit exceeded for this project. The bandit will stop learning until the limit resets. Upgrade at sentient-ui.com/pricing");else if(!n.ok){let l=await n.json().catch(()=>({}));console.error(`[SentientUI] preloadAssignments: session upsert failed (${n.status})`,l)}}catch(n){console.error("[SentientUI] preloadAssignments: session upsert threw",n)}let u=await Promise.allSettled(t.map(async({id:n,variantIds:l})=>{let a=await D(`${r.baseUrl}/assign`,{method:"POST",headers:o,body:JSON.stringify({sessionId:e,componentId:n,variantIds:l})},s);if(!a.ok){let h=await a.json().catch(()=>({}));return console.error(`[SentientUI] preloadAssignments: assign failed for "${n}" (${a.status})`,h),null}let b=await a.json();return{id:n,variantId:b.variantId}})),g={};for(let n of u)n.status==="fulfilled"&&n.value&&(g[n.value.id]=n.value.variantId);return g}function W(t){var e,r;return(r=(e=t.get("_snt_uid"))==null?void 0:e.value)!=null?r:null}async function J(t,e,r){var d,n,l,a,b,h,P,k,I,T,C;let s=(d=r.timeoutMs)!=null?d:E,o=(n=t.slots)!=null?n:[],c={layoutOrder:(l=t.sections)!=null?l:[],assignments:{},slots:N(o),persona:"unknown",confidence:0};if(r.doNotTrack)return c;let u={"Content-Type":"application/json",Authorization:`Bearer ${r.apiKey}`};r.origin&&(u.Origin=r.origin);let g=y(e,{userAgent:r.userAgent,referer:r.referer,utmParams:r.utmParams,appOrigin:r.origin});try{let i=await D(`${r.baseUrl}/sessions`,{method:"POST",headers:u,body:JSON.stringify(g)},s);if(!i.ok){let m=await i.json().catch(()=>({}));console.error(`[SentientUI] preloadDecisions: session upsert failed (${i.status})`,m)}}catch(i){console.error("[SentientUI] preloadDecisions: session upsert threw",i)}try{let i=await D(`${r.baseUrl}/decide`,{method:"POST",headers:u,body:JSON.stringify(p({sessionId:e,sections:((a=t.sections)!=null?a:[]).map(f=>({id:f})),components:t.components},o.length>0?{slots:o.map(O)}:{}))},s);if(!i.ok){let f=await i.json().catch(()=>({}));return console.error(`[SentientUI] preloadDecisions: decide failed (${i.status})`,f),c}let m=await i.json(),$={};for(let f of o)$[f.id]=(h=(b=m.slots)==null?void 0:b[f.id])!=null?h:U(f);return{layoutOrder:(k=(P=m.layoutOrder)!=null?P:t.sections)!=null?k:[],assignments:(I=m.assignments)!=null?I:{},slots:$,persona:(T=m.persona)!=null?T:"unknown",confidence:(C=m.confidence)!=null?C:0}}catch(i){return console.error("[SentientUI] preloadDecisions: decide threw",i),c}}0&&(module.exports={buildSessionUpsertPayload,deriveSessionSegment,detectDeviceClass,detectTimeOfDay,detectTrafficSource,preloadAssignments,preloadDecisions,readSessionCookie,referrerDomainFromReferer});
|
|
2
|
+
//# sourceMappingURL=index-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index-server.ts","../src/session-meta.ts","../src/slots.ts","../src/server.ts"],"sourcesContent":["/**\n * Server-only entry (`@sentientui/core/server`). SSR preload helpers that are\n * never needed in the browser — kept out of the lean client bundle.\n */\n\nexport { preloadAssignments, readSessionCookie, preloadDecisions } from './server.js';\nexport type { ServerAssignConfig, ServerAssignments, DecideResult, SlotDeclInput, SlotResult } from './server.js';\n\nexport {\n deriveSessionSegment,\n buildSessionUpsertPayload,\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n referrerDomainFromReferer,\n} from './session-meta.js';\nexport type { SessionUpsertPayload } from './session-meta.js';\n","/** Session metadata helpers (browser + Node). No DOM APIs. */\n\n/**\n * Known AI-agent / crawler user-agent tokens. Matched case-insensitively as\n * substrings. This is maintained data — bot lists move monthly. Used both to\n * flag automation on sessions (agentic browsers that leak a token) and by the\n * server middleware / agent-feed route to identify crawler HTTP reads.\n */\nexport const agentUaList: readonly string[] = [\n 'GPTBot',\n 'ChatGPT-User',\n 'OAI-SearchBot',\n 'ClaudeBot',\n 'Claude-User',\n 'Claude-SearchBot',\n 'PerplexityBot',\n 'Perplexity-User',\n 'Google-Extended',\n 'Applebot-Extended',\n 'Meta-ExternalAgent',\n 'Bytespider',\n 'CCBot',\n 'Amazonbot',\n 'cohere-ai',\n 'Diffbot',\n];\n\n/** True when the user-agent contains a known AI-agent / crawler token. */\nexport function uaTokenMatch(userAgent: string): boolean {\n return matchedAgentToken(userAgent) !== null;\n}\n\n/** The first known agent token found in the user-agent, or null. */\nexport function matchedAgentToken(userAgent: string): string | null {\n if (!userAgent) return null;\n const s = userAgent.toLowerCase();\n return agentUaList.find((token) => s.includes(token.toLowerCase())) ?? null;\n}\n\nexport function detectDeviceClass(userAgent: string): string {\n const s = userAgent.toLowerCase();\n if (/ipad|tablet|playbook|kindle|silk/.test(s)) return 'tablet';\n if (/mobi|iphone|ipod|android.*mobile|phone/.test(s)) return 'mobile';\n return 'desktop';\n}\n\nexport function detectTrafficSource(referrer: string, appOrigin?: string): string {\n if (!referrer) return 'direct';\n try {\n const refUrl = new URL(referrer);\n if (appOrigin) {\n try {\n if (new URL(appOrigin).host === refUrl.host) return 'direct';\n } catch {\n /* ignore invalid appOrigin */\n }\n }\n const host = refUrl.hostname.toLowerCase();\n if (/(^|\\.)(google|bing|duckduckgo|yahoo)\\./.test(host)) return 'search';\n // Anchor to the registrable domain (exact host or a subdomain of it) so\n // hosts like `x.company.com`, `t.company.io` or `linkedinsights.com` are not\n // misclassified as social by an unbounded substring match.\n if (/(^|\\.)(twitter\\.com|x\\.com|facebook\\.com|linkedin\\.com|reddit\\.com|t\\.co)$/.test(host)) return 'social';\n return 'referral';\n } catch {\n return 'direct';\n }\n}\n\nexport function referrerDomainFromReferer(referrer: string): string | null {\n if (!referrer) return null;\n try {\n return new URL(referrer).hostname;\n } catch {\n return null;\n }\n}\n\nexport function detectTimeOfDay(d: Date): string {\n const h = d.getHours();\n if (h < 6) return 'night';\n if (h < 12) return 'morning';\n if (h < 18) return 'afternoon';\n return 'evening';\n}\n\nexport type SessionUpsertPayload = {\n sessionId: string;\n ephemeral: boolean;\n utmParams: Record<string, string>;\n deviceClass: string;\n trafficSource: string;\n referrerDomain: string | null;\n timeOfDay: string;\n dayOfWeek: string;\n /**\n * True when this session is likely driven by automation — either\n * `navigator.webdriver` was set, or the user-agent carried a known agent\n * token. Probabilistic: a flag for metrics + bandit exclusion, never a gate.\n */\n automation: boolean;\n};\n\n/** Bandit segment key: `<device_class>:<traffic_source>`. */\nexport function deriveSessionSegment(opts?: {\n userAgent?: string;\n referer?: string;\n appOrigin?: string;\n}): string {\n const body = buildSessionUpsertPayload('__segment__', opts);\n return `${body.deviceClass}:${body.trafficSource}`;\n}\n\n/**\n * Builds a session upsert body aligned with the browser SDK so SSR assign uses\n * the same segment key (`device:source`) as the client after hydration.\n */\nexport function buildSessionUpsertPayload(\n sessionId: string,\n opts?: {\n userAgent?: string;\n referer?: string;\n appOrigin?: string;\n utmParams?: Record<string, string>;\n now?: Date;\n /** `navigator.webdriver` value from the browser, when available. */\n webdriver?: boolean;\n },\n): SessionUpsertPayload {\n const ua = opts?.userAgent?.trim() ?? '';\n const referer = opts?.referer?.trim() ?? '';\n const now = opts?.now ?? new Date();\n return {\n sessionId,\n ephemeral: false,\n utmParams: opts?.utmParams ?? {},\n deviceClass: ua ? detectDeviceClass(ua) : 'desktop',\n trafficSource: referer\n ? detectTrafficSource(referer, opts?.appOrigin)\n : 'direct',\n referrerDomain: referrerDomainFromReferer(referer),\n timeOfDay: detectTimeOfDay(now),\n dayOfWeek: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][now.getDay()] ?? 'sun',\n automation: opts?.webdriver === true || uaTokenMatch(ua),\n };\n}\n","/**\n * Slot declaration helpers shared by the browser client (decide) and the\n * server preload path. Pure wrappers over @sentientui/policy.\n */\nimport {\n canonicalArm,\n slotBaselineArm,\n slotResultFor,\n type SlotDecl,\n type SlotResult,\n} from '@sentientui/policy';\n\n/** SDK-facing slot declaration. `dims` accepts readonly arrays (`as const`). */\nexport type SlotDeclInput = {\n id: string;\n arms?: string[];\n dims?: Record<string, readonly string[]>;\n baseline?: string | Record<string, string>;\n};\n\nexport type { SlotResult };\n\n/**\n * Whitelists the wire fields of a slot declaration. Anything an SDK layer\n * attached (goal configs, refs, …) is stripped so it never reaches the zod\n * schema on the API. Also normalizes readonly arrays to mutable ones.\n */\nexport function toWireSlot(d: SlotDeclInput): SlotDecl {\n return {\n id: d.id,\n ...(d.arms ? { arms: [...d.arms] } : {}),\n ...(d.dims\n ? {\n dims: Object.fromEntries(\n Object.entries(d.dims).map(([dim, values]) => [dim, [...values]]),\n ),\n }\n : {}),\n ...(d.baseline !== undefined ? { baseline: d.baseline } : {}),\n };\n}\n\n/** The declared (or default first-declared) baseline result for a slot. */\nexport function baselineResultFor(d: SlotDeclInput): SlotResult {\n const decl = toWireSlot(d);\n return slotResultFor(decl, slotBaselineArm(decl));\n}\n\n/** Baseline results for a whole declaration list, keyed by slot id. */\nexport function baselineSlots(decls: SlotDeclInput[]): Record<string, SlotResult> {\n const out: Record<string, SlotResult> = {};\n for (const d of decls) out[d.id] = baselineResultFor(d);\n return out;\n}\n\n/**\n * Canonical arm string of a slot result: dims results encode as sorted\n * `dim=value` pairs joined with '|'; arms results are the arm id verbatim.\n */\nexport function armOfResult(result: SlotResult): string {\n return typeof result === 'string' ? result : canonicalArm(result);\n}\n","/**\n * Server-side helpers for SSR variant pre-loading.\n * Pure fetch — no DOM APIs. Safe in Node.js, Edge, and Deno runtimes.\n */\nimport { buildSessionUpsertPayload } from './session-meta.js';\nimport {\n toWireSlot,\n baselineResultFor,\n baselineSlots,\n type SlotDeclInput,\n type SlotResult,\n} from './slots.js';\n\nexport type { SlotDeclInput, SlotResult };\n\nexport type ServerAssignConfig = {\n /** Public API key (pk_...). */\n apiKey: string;\n /**\n * Base URL of the Sentient API without trailing slash.\n * e.g. 'https://api.yourapp.com/v1'\n */\n baseUrl: string;\n /**\n * Browser origin of your app (e.g. `http://localhost:3001`). Required for `pk_`\n * keys — server-side fetch must send the same `Origin` the API allows.\n */\n origin?: string;\n /** From `User-Agent` request header (Next.js `headers()`). */\n userAgent?: string;\n /** From `Referer` request header. */\n referer?: string;\n utmParams?: Record<string, string>;\n /**\n * Set true when the request carries a tracking opt-out — `DNT: 1` or\n * `Sec-GPC: 1`. When set, the SSR helpers skip the session upsert and the\n * assign/decide call entirely: the page renders defaults/baseline and no\n * session row is minted for the visitor (audit P4). The client SDK already\n * no-ops for these visitors; this keeps the server from minting a session +\n * assignment on every page view before the client can.\n */\n doNotTrack?: boolean;\n /**\n * Milliseconds to wait for the API before returning default variants.\n * Prevents slow/cold API from blocking SSR. Defaults to 1000 — the hot path\n * is served from in-process caches and typically returns in well under\n * 150 ms. The full 1 s budget is only reached on a cold start or an API\n * geographically distant from your SSR host, after which defaults render\n * with no layout shift. Lower it if your API is co-located and warm.\n */\n timeoutMs?: number;\n};\n\n/** componentId → assigned variantId */\nexport type ServerAssignments = Record<string, string>;\n\ntype AssignResult = { variantId: string; assignmentTtlMs: number };\n\nconst DEFAULT_TIMEOUT_MS = 1000;\n\nfunction fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\n/**\n * Fetches variant assignments server-side for all listed components.\n * Returns a map suitable for passing as `initialAssignments` to `<AdaptiveProvider>`.\n *\n * Call from Next.js layout, Server Component, or getServerSideProps.\n *\n * @example\n * ```tsx\n * const assignments = await preloadAssignments(\n * [\n * { id: 'hero', variantIds: ['hero-a', 'hero-b'] },\n * { id: 'cta', variantIds: ['cta-short', 'cta-long'] },\n * ],\n * sessionId, // read from cookies() or req.cookies\n * { apiKey: process.env.NEXT_PUBLIC_SENTIENT_API_KEY!, baseUrl: 'https://api.sentient-ui.com/v1' },\n * );\n * return <AdaptiveProvider ... initialAssignments={assignments}>{children}</AdaptiveProvider>;\n * ```\n */\nexport async function preloadAssignments(\n components: Array<{ id: string; variantIds: string[] }>,\n sessionId: string,\n config: ServerAssignConfig,\n): Promise<ServerAssignments> {\n // Opted-out visitor (DNT/GPC): render defaults, mint no session (audit P4).\n if (config.doNotTrack) return {};\n\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n };\n if (config.origin) {\n headers.Origin = config.origin;\n }\n\n // Session metadata must match the browser SDK so assign seeds variant_weights\n // under the same segment (not `unknown:unknown`).\n const sessionBody = buildSessionUpsertPayload(sessionId, {\n userAgent: config.userAgent,\n referer: config.referer,\n utmParams: config.utmParams,\n appOrigin: config.origin,\n });\n\n try {\n const res = await fetchWithTimeout(\n `${config.baseUrl}/sessions`,\n { method: 'POST', headers, body: JSON.stringify(sessionBody) },\n timeoutMs,\n );\n if (res.status === 402) {\n console.warn(\n '[SentientUI] Session limit exceeded for this project. The bandit will stop learning until the limit resets. Upgrade at sentient-ui.com/pricing',\n );\n } else if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n console.error(`[SentientUI] preloadAssignments: session upsert failed (${res.status})`, body);\n }\n } catch (err) {\n console.error('[SentientUI] preloadAssignments: session upsert threw', err);\n }\n\n const results = await Promise.allSettled(\n components.map(async ({ id, variantIds }) => {\n const res = await fetchWithTimeout(\n `${config.baseUrl}/assign`,\n { method: 'POST', headers, body: JSON.stringify({ sessionId, componentId: id, variantIds }) },\n timeoutMs,\n );\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n console.error(`[SentientUI] preloadAssignments: assign failed for \"${id}\" (${res.status})`, body);\n return null;\n }\n const body = (await res.json()) as AssignResult;\n return { id, variantId: body.variantId };\n }),\n );\n\n const assignments: ServerAssignments = {};\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value) {\n assignments[result.value.id] = result.value.variantId;\n }\n }\n return assignments;\n}\n\n/**\n * Reads the Sentient session cookie from a Next.js `ReadonlyRequestCookies` object\n * (the return value of `cookies()` from `next/headers`), or any object with a\n * `get(name: string)` method. Returns null if the cookie is absent.\n */\nexport function readSessionCookie(\n cookies: { get(name: string): { value: string } | undefined },\n): string | null {\n return cookies.get('_snt_uid')?.value ?? null;\n}\n\nexport type DecideResult = {\n layoutOrder: string[];\n assignments: Record<string, string>;\n /** Slot results keyed by slot id. Baseline-resolved when the API omits/fails them. */\n slots: Record<string, SlotResult>;\n persona: string;\n confidence: number;\n};\n\n/**\n * Single-roundtrip SSR call returning layout order, component assignments,\n * and adaptive-slot results. Falls back to default section order + empty\n * assignments + baseline slots if the API is unavailable. A response without\n * a `slots` field means the server predates slots — every declared slot\n * resolves to its baseline (no retry).\n */\nexport async function preloadDecisions(\n params: {\n sections?: string[];\n components: Array<{ id: string; variantIds?: string[] }>;\n slots?: SlotDeclInput[];\n },\n sessionId: string,\n config: ServerAssignConfig,\n): Promise<DecideResult> {\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const declaredSlots = params.slots ?? [];\n const fallback: DecideResult = {\n layoutOrder: params.sections ?? [],\n assignments: {},\n slots: baselineSlots(declaredSlots),\n persona: 'unknown',\n confidence: 0,\n };\n\n // Opted-out visitor (DNT/GPC): baseline layout/slots, mint no session (audit P4).\n if (config.doNotTrack) return fallback;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n };\n if (config.origin) headers.Origin = config.origin;\n\n const sessionBody = buildSessionUpsertPayload(sessionId, {\n userAgent: config.userAgent,\n referer: config.referer,\n utmParams: config.utmParams,\n appOrigin: config.origin,\n });\n\n try {\n const sessionRes = await fetchWithTimeout(\n `${config.baseUrl}/sessions`,\n { method: 'POST', headers, body: JSON.stringify(sessionBody) },\n timeoutMs,\n );\n if (!sessionRes.ok) {\n const body = await sessionRes.json().catch(() => ({}));\n console.error(`[SentientUI] preloadDecisions: session upsert failed (${sessionRes.status})`, body);\n }\n } catch (err) {\n console.error('[SentientUI] preloadDecisions: session upsert threw', err);\n }\n\n try {\n const res = await fetchWithTimeout(\n `${config.baseUrl}/decide`,\n {\n method: 'POST',\n headers,\n body: JSON.stringify({\n sessionId,\n sections: (params.sections ?? []).map((id) => ({ id })),\n components: params.components,\n ...(declaredSlots.length > 0 ? { slots: declaredSlots.map(toWireSlot) } : {}),\n }),\n },\n timeoutMs,\n );\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n console.error(`[SentientUI] preloadDecisions: decide failed (${res.status})`, body);\n return fallback;\n }\n const data = (await res.json()) as {\n layoutOrder?: string[];\n assignments?: Record<string, string>;\n slots?: Record<string, SlotResult>;\n persona?: string;\n confidence?: number;\n };\n\n const slots: Record<string, SlotResult> = {};\n for (const d of declaredSlots) {\n // `data.slots === undefined` ⇒ server predates slots: baseline, no retry.\n slots[d.id] = data.slots?.[d.id] ?? baselineResultFor(d);\n }\n\n return {\n layoutOrder: data.layoutOrder ?? params.sections ?? [],\n assignments: data.assignments ?? {},\n slots,\n persona: data.persona ?? 'unknown',\n confidence: data.confidence ?? 0,\n };\n } catch (err) {\n console.error('[SentientUI] preloadDecisions: decide threw', err);\n return fallback;\n }\n}\n"],"mappings":"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","names":["index_server_exports","__export","buildSessionUpsertPayload","deriveSessionSegment","detectDeviceClass","detectTimeOfDay","detectTrafficSource","preloadAssignments","preloadDecisions","readSessionCookie","referrerDomainFromReferer","__toCommonJS","agentUaList","uaTokenMatch","userAgent","matchedAgentToken","_a","s","token","detectDeviceClass","detectTrafficSource","referrer","appOrigin","refUrl","e","host","referrerDomainFromReferer","detectTimeOfDay","d","h","deriveSessionSegment","opts","body","buildSessionUpsertPayload","sessionId","_b","_c","_d","_e","_f","_g","ua","referer","now","import_policy","toWireSlot","d","__spreadValues","dim","values","baselineResultFor","decl","baselineSlots","decls","out","DEFAULT_TIMEOUT_MS","fetchWithTimeout","url","init","timeoutMs","controller","timer","__spreadProps","__spreadValues","preloadAssignments","components","sessionId","config","_a","headers","sessionBody","buildSessionUpsertPayload","res","body","err","results","id","variantIds","assignments","result","readSessionCookie","cookies","_b","preloadDecisions","params","_c","_d","_e","_f","_g","_h","_i","_j","_k","declaredSlots","fallback","baselineSlots","sessionRes","toWireSlot","data","slots","d","baselineResultFor"]}
|
package/dist/index-server.mjs
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{d as x,e as j,f as $,g as C,h as M,i as f,j as U,k as D,l as P}from"./chunk-
|
|
1
|
+
import{d as x,e as j,f as $,g as C,h as M,i as f,j as U,k as D,l as P}from"./chunk-L5TA3FAB.mjs";import{a as A,b as T}from"./chunk-TMCGHANO.mjs";var k=1e3;function b(r,n,e){let i=new AbortController,o=setTimeout(()=>i.abort(),e);return fetch(r,T(A({},n),{signal:i.signal})).finally(()=>clearTimeout(o))}async function N(r,n,e){var p;if(e.doNotTrack)return{};let i=(p=e.timeoutMs)!=null?p:k,o={"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`};e.origin&&(o.Origin=e.origin);let u=f(n,{userAgent:e.userAgent,referer:e.referer,utmParams:e.utmParams,appOrigin:e.origin});try{let s=await b(`${e.baseUrl}/sessions`,{method:"POST",headers:o,body:JSON.stringify(u)},i);if(s.status===402)console.warn("[SentientUI] Session limit exceeded for this project. The bandit will stop learning until the limit resets. Upgrade at sentient-ui.com/pricing");else if(!s.ok){let d=await s.json().catch(()=>({}));console.error(`[SentientUI] preloadAssignments: session upsert failed (${s.status})`,d)}}catch(s){console.error("[SentientUI] preloadAssignments: session upsert threw",s)}let g=await Promise.allSettled(r.map(async({id:s,variantIds:d})=>{let a=await b(`${e.baseUrl}/assign`,{method:"POST",headers:o,body:JSON.stringify({sessionId:n,componentId:s,variantIds:d})},i);if(!a.ok){let y=await a.json().catch(()=>({}));return console.error(`[SentientUI] preloadAssignments: assign failed for "${s}" (${a.status})`,y),null}let S=await a.json();return{id:s,variantId:S.variantId}})),m={};for(let s of g)s.status==="fulfilled"&&s.value&&(m[s.value.id]=s.value.variantId);return m}function B(r){var n,e;return(e=(n=r.get("_snt_uid"))==null?void 0:n.value)!=null?e:null}async function J(r,n,e){var p,s,d,a,S,y,R,h,v,w,I;let i=(p=e.timeoutMs)!=null?p:k,o=(s=r.slots)!=null?s:[],u={layoutOrder:(d=r.sections)!=null?d:[],assignments:{},slots:P(o),persona:"unknown",confidence:0};if(e.doNotTrack)return u;let g={"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`};e.origin&&(g.Origin=e.origin);let m=f(n,{userAgent:e.userAgent,referer:e.referer,utmParams:e.utmParams,appOrigin:e.origin});try{let t=await b(`${e.baseUrl}/sessions`,{method:"POST",headers:g,body:JSON.stringify(m)},i);if(!t.ok){let l=await t.json().catch(()=>({}));console.error(`[SentientUI] preloadDecisions: session upsert failed (${t.status})`,l)}}catch(t){console.error("[SentientUI] preloadDecisions: session upsert threw",t)}try{let t=await b(`${e.baseUrl}/decide`,{method:"POST",headers:g,body:JSON.stringify(A({sessionId:n,sections:((a=r.sections)!=null?a:[]).map(c=>({id:c})),components:r.components},o.length>0?{slots:o.map(U)}:{}))},i);if(!t.ok){let c=await t.json().catch(()=>({}));return console.error(`[SentientUI] preloadDecisions: decide failed (${t.status})`,c),u}let l=await t.json(),O={};for(let c of o)O[c.id]=(y=(S=l.slots)==null?void 0:S[c.id])!=null?y:D(c);return{layoutOrder:(h=(R=l.layoutOrder)!=null?R:r.sections)!=null?h:[],assignments:(v=l.assignments)!=null?v:{},slots:O,persona:(w=l.persona)!=null?w:"unknown",confidence:(I=l.confidence)!=null?I:0}}catch(t){return console.error("[SentientUI] preloadDecisions: decide threw",t),u}}export{f as buildSessionUpsertPayload,M as deriveSessionSegment,x as detectDeviceClass,C as detectTimeOfDay,j as detectTrafficSource,N as preloadAssignments,J as preloadDecisions,B as readSessionCookie,$ as referrerDomainFromReferer};
|
|
2
|
+
//# sourceMappingURL=index-server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server-side helpers for SSR variant pre-loading.\n * Pure fetch — no DOM APIs. Safe in Node.js, Edge, and Deno runtimes.\n */\nimport { buildSessionUpsertPayload } from './session-meta.js';\nimport {\n toWireSlot,\n baselineResultFor,\n baselineSlots,\n type SlotDeclInput,\n type SlotResult,\n} from './slots.js';\n\nexport type { SlotDeclInput, SlotResult };\n\nexport type ServerAssignConfig = {\n /** Public API key (pk_...). */\n apiKey: string;\n /**\n * Base URL of the Sentient API without trailing slash.\n * e.g. 'https://api.yourapp.com/v1'\n */\n baseUrl: string;\n /**\n * Browser origin of your app (e.g. `http://localhost:3001`). Required for `pk_`\n * keys — server-side fetch must send the same `Origin` the API allows.\n */\n origin?: string;\n /** From `User-Agent` request header (Next.js `headers()`). */\n userAgent?: string;\n /** From `Referer` request header. */\n referer?: string;\n utmParams?: Record<string, string>;\n /**\n * Set true when the request carries a tracking opt-out — `DNT: 1` or\n * `Sec-GPC: 1`. When set, the SSR helpers skip the session upsert and the\n * assign/decide call entirely: the page renders defaults/baseline and no\n * session row is minted for the visitor (audit P4). The client SDK already\n * no-ops for these visitors; this keeps the server from minting a session +\n * assignment on every page view before the client can.\n */\n doNotTrack?: boolean;\n /**\n * Milliseconds to wait for the API before returning default variants.\n * Prevents slow/cold API from blocking SSR. Defaults to 1000 — the hot path\n * is served from in-process caches and typically returns in well under\n * 150 ms. The full 1 s budget is only reached on a cold start or an API\n * geographically distant from your SSR host, after which defaults render\n * with no layout shift. Lower it if your API is co-located and warm.\n */\n timeoutMs?: number;\n};\n\n/** componentId → assigned variantId */\nexport type ServerAssignments = Record<string, string>;\n\ntype AssignResult = { variantId: string; assignmentTtlMs: number };\n\nconst DEFAULT_TIMEOUT_MS = 1000;\n\nfunction fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\n/**\n * Fetches variant assignments server-side for all listed components.\n * Returns a map suitable for passing as `initialAssignments` to `<AdaptiveProvider>`.\n *\n * Call from Next.js layout, Server Component, or getServerSideProps.\n *\n * @example\n * ```tsx\n * const assignments = await preloadAssignments(\n * [\n * { id: 'hero', variantIds: ['hero-a', 'hero-b'] },\n * { id: 'cta', variantIds: ['cta-short', 'cta-long'] },\n * ],\n * sessionId, // read from cookies() or req.cookies\n * { apiKey: process.env.NEXT_PUBLIC_SENTIENT_API_KEY!, baseUrl: 'https://api.sentient-ui.com/v1' },\n * );\n * return <AdaptiveProvider ... initialAssignments={assignments}>{children}</AdaptiveProvider>;\n * ```\n */\nexport async function preloadAssignments(\n components: Array<{ id: string; variantIds: string[] }>,\n sessionId: string,\n config: ServerAssignConfig,\n): Promise<ServerAssignments> {\n // Opted-out visitor (DNT/GPC): render defaults, mint no session (audit P4).\n if (config.doNotTrack) return {};\n\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n };\n if (config.origin) {\n headers.Origin = config.origin;\n }\n\n // Session metadata must match the browser SDK so assign seeds variant_weights\n // under the same segment (not `unknown:unknown`).\n const sessionBody = buildSessionUpsertPayload(sessionId, {\n userAgent: config.userAgent,\n referer: config.referer,\n utmParams: config.utmParams,\n appOrigin: config.origin,\n });\n\n try {\n const res = await fetchWithTimeout(\n `${config.baseUrl}/sessions`,\n { method: 'POST', headers, body: JSON.stringify(sessionBody) },\n timeoutMs,\n );\n if (res.status === 402) {\n console.warn(\n '[SentientUI] Session limit exceeded for this project. The bandit will stop learning until the limit resets. Upgrade at sentient-ui.com/pricing',\n );\n } else if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n console.error(`[SentientUI] preloadAssignments: session upsert failed (${res.status})`, body);\n }\n } catch (err) {\n console.error('[SentientUI] preloadAssignments: session upsert threw', err);\n }\n\n const results = await Promise.allSettled(\n components.map(async ({ id, variantIds }) => {\n const res = await fetchWithTimeout(\n `${config.baseUrl}/assign`,\n { method: 'POST', headers, body: JSON.stringify({ sessionId, componentId: id, variantIds }) },\n timeoutMs,\n );\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n console.error(`[SentientUI] preloadAssignments: assign failed for \"${id}\" (${res.status})`, body);\n return null;\n }\n const body = (await res.json()) as AssignResult;\n return { id, variantId: body.variantId };\n }),\n );\n\n const assignments: ServerAssignments = {};\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value) {\n assignments[result.value.id] = result.value.variantId;\n }\n }\n return assignments;\n}\n\n/**\n * Reads the Sentient session cookie from a Next.js `ReadonlyRequestCookies` object\n * (the return value of `cookies()` from `next/headers`), or any object with a\n * `get(name: string)` method. Returns null if the cookie is absent.\n */\nexport function readSessionCookie(\n cookies: { get(name: string): { value: string } | undefined },\n): string | null {\n return cookies.get('_snt_uid')?.value ?? null;\n}\n\nexport type DecideResult = {\n layoutOrder: string[];\n assignments: Record<string, string>;\n /** Slot results keyed by slot id. Baseline-resolved when the API omits/fails them. */\n slots: Record<string, SlotResult>;\n persona: string;\n confidence: number;\n};\n\n/**\n * Single-roundtrip SSR call returning layout order, component assignments,\n * and adaptive-slot results. Falls back to default section order + empty\n * assignments + baseline slots if the API is unavailable. A response without\n * a `slots` field means the server predates slots — every declared slot\n * resolves to its baseline (no retry).\n */\nexport async function preloadDecisions(\n params: {\n sections?: string[];\n components: Array<{ id: string; variantIds?: string[] }>;\n slots?: SlotDeclInput[];\n },\n sessionId: string,\n config: ServerAssignConfig,\n): Promise<DecideResult> {\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const declaredSlots = params.slots ?? [];\n const fallback: DecideResult = {\n layoutOrder: params.sections ?? [],\n assignments: {},\n slots: baselineSlots(declaredSlots),\n persona: 'unknown',\n confidence: 0,\n };\n\n // Opted-out visitor (DNT/GPC): baseline layout/slots, mint no session (audit P4).\n if (config.doNotTrack) return fallback;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n };\n if (config.origin) headers.Origin = config.origin;\n\n const sessionBody = buildSessionUpsertPayload(sessionId, {\n userAgent: config.userAgent,\n referer: config.referer,\n utmParams: config.utmParams,\n appOrigin: config.origin,\n });\n\n try {\n const sessionRes = await fetchWithTimeout(\n `${config.baseUrl}/sessions`,\n { method: 'POST', headers, body: JSON.stringify(sessionBody) },\n timeoutMs,\n );\n if (!sessionRes.ok) {\n const body = await sessionRes.json().catch(() => ({}));\n console.error(`[SentientUI] preloadDecisions: session upsert failed (${sessionRes.status})`, body);\n }\n } catch (err) {\n console.error('[SentientUI] preloadDecisions: session upsert threw', err);\n }\n\n try {\n const res = await fetchWithTimeout(\n `${config.baseUrl}/decide`,\n {\n method: 'POST',\n headers,\n body: JSON.stringify({\n sessionId,\n sections: (params.sections ?? []).map((id) => ({ id })),\n components: params.components,\n ...(declaredSlots.length > 0 ? { slots: declaredSlots.map(toWireSlot) } : {}),\n }),\n },\n timeoutMs,\n );\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n console.error(`[SentientUI] preloadDecisions: decide failed (${res.status})`, body);\n return fallback;\n }\n const data = (await res.json()) as {\n layoutOrder?: string[];\n assignments?: Record<string, string>;\n slots?: Record<string, SlotResult>;\n persona?: string;\n confidence?: number;\n };\n\n const slots: Record<string, SlotResult> = {};\n for (const d of declaredSlots) {\n // `data.slots === undefined` ⇒ server predates slots: baseline, no retry.\n slots[d.id] = data.slots?.[d.id] ?? baselineResultFor(d);\n }\n\n return {\n layoutOrder: data.layoutOrder ?? params.sections ?? [],\n assignments: data.assignments ?? {},\n slots,\n persona: data.persona ?? 'unknown',\n confidence: data.confidence ?? 0,\n };\n } catch (err) {\n console.error('[SentientUI] preloadDecisions: decide threw', err);\n return fallback;\n }\n}\n"],"mappings":"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","names":["DEFAULT_TIMEOUT_MS","fetchWithTimeout","url","init","timeoutMs","controller","timer","__spreadProps","__spreadValues","preloadAssignments","components","sessionId","config","_a","headers","sessionBody","buildSessionUpsertPayload","res","body","err","results","id","variantIds","assignments","result","readSessionCookie","cookies","_b","preloadDecisions","params","_c","_d","_e","_f","_g","_h","_i","_j","_k","declaredSlots","fallback","baselineSlots","sessionRes","toWireSlot","data","slots","d","baselineResultFor"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,431 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export { b as agentUaList, c as armOfResult, d as baselineResultFor, e as baselineSlots, g as deriveSessionSegment, h as detectDeviceClass, i as detectTimeOfDay, j as detectTrafficSource, m as matchedAgentToken, r as referrerDomainFromReferer, t as toWireSlot, u as uaTokenMatch } from './session-meta-DU_3mY7U.cjs';
|
|
3
|
-
import { SlotResult } from '@sentientui/policy';
|
|
1
|
+
export { A as AssignResult, a as Assignment, C as ComponentGoalOptions, c as ComponentWeightEntry, d as CompoundLocator, D as DecideInput, e as DecideOutcome, f as DecisionSnapshot, g as EventType, G as GoalDefinition, i as GraphConfig, j as GraphSnapshot, L as LOCAL_MODE_BANNER, M as MicroSignalEmitter, k as MicroSignalType, P as PROD_KEYLESS_ERROR, Q as QueueConfig, S as SNAPSHOT_STORAGE_KEY_PREFIX, m as SentientClient, n as SentientConfig, o as SentientEvent, p as SessionConfig, q as SessionManager, r as SlotConfigEntry, s as SlotOps, W as WeightEntry, t as attachMicroSignalDetectors, u as grantConsent, v as init, w as isDoNotTrackEnabled, x as readSnapshot, y as renderPrePaintScript, z as writeSnapshot } from './index-CZLjrtM4.cjs';
|
|
2
|
+
export { a as SlotDeclInput, b as agentUaList, c as armOfResult, d as baselineResultFor, e as baselineSlots, g as deriveSessionSegment, h as detectDeviceClass, i as detectTimeOfDay, j as detectTrafficSource, m as matchedAgentToken, r as referrerDomainFromReferer, t as toWireSlot, u as uaTokenMatch } from './session-meta-DU_3mY7U.cjs';
|
|
4
3
|
export { SlotResult } from '@sentientui/policy';
|
|
5
|
-
|
|
6
|
-
/** Manages anonymous session identity with cookie + localStorage layers. */
|
|
7
|
-
type SessionConfig = {
|
|
8
|
-
cookieName?: string;
|
|
9
|
-
cookieTTLDays?: number;
|
|
10
|
-
/**
|
|
11
|
-
* Session ID generated during SSR (e.g. from `loadAdaptiveAssignments`).
|
|
12
|
-
* Used as the fallback when no existing cookie or localStorage entry is found,
|
|
13
|
-
* so the client adopts the same session the server used for variant assignment
|
|
14
|
-
* on first visit rather than generating a new, orphaned ID.
|
|
15
|
-
*/
|
|
16
|
-
ssrSessionId?: string;
|
|
17
|
-
};
|
|
18
|
-
type SessionManager = {
|
|
19
|
-
getSessionId(): string | null;
|
|
20
|
-
/** True when neither cookie nor localStorage could be written — id is in-memory only. */
|
|
21
|
-
isEphemeral(): boolean;
|
|
22
|
-
destroy(): void;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** Batched event queue with reliable transport (fetch + keepalive, localStorage retry). */
|
|
26
|
-
type EventType = 'variant_assigned' | 'goal_achieved' | 'scroll_depth' | 'dwell' | 'cursor_signal' | 'component_visible' | 'component_exited' | 'micro_signal';
|
|
27
|
-
type SentientEvent = {
|
|
28
|
-
id: string;
|
|
29
|
-
sessionId: string;
|
|
30
|
-
projectId: string;
|
|
31
|
-
componentId: string;
|
|
32
|
-
variantId?: string;
|
|
33
|
-
eventType: EventType;
|
|
34
|
-
goalType?: string;
|
|
35
|
-
payload: Record<string, unknown>;
|
|
36
|
-
timestamp: number;
|
|
37
|
-
timeInSession: number;
|
|
38
|
-
};
|
|
39
|
-
type QueueConfig = {
|
|
40
|
-
ingestUrl: string;
|
|
41
|
-
apiKey: string;
|
|
42
|
-
flushIntervalMs?: number;
|
|
43
|
-
maxBatchSize?: number;
|
|
44
|
-
maxRetrySize?: number;
|
|
45
|
-
};
|
|
46
|
-
type EventQueue = {
|
|
47
|
-
push(event: SentientEvent): void;
|
|
48
|
-
flush(): void;
|
|
49
|
-
destroy(): void;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/** Synchronous variant assignment cache (memory + localStorage). */
|
|
53
|
-
type Assignment = {
|
|
54
|
-
variantId: string;
|
|
55
|
-
assignedAt: number;
|
|
56
|
-
segment: string;
|
|
57
|
-
confidence: number;
|
|
58
|
-
content?: string;
|
|
59
|
-
};
|
|
60
|
-
type AssignmentCache = {
|
|
61
|
-
get(componentId: string, segment: string): Assignment | null;
|
|
62
|
-
set(componentId: string, segment: string, assignment: Assignment): void;
|
|
63
|
-
invalidate(componentId: string): void;
|
|
64
|
-
clear(): void;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/** Reads the rendered DOM to build the page-side context graph. */
|
|
68
|
-
type ScannedNode = {
|
|
69
|
-
componentId: string;
|
|
70
|
-
semanticType: string;
|
|
71
|
-
ariaLabel?: string;
|
|
72
|
-
headingText?: string;
|
|
73
|
-
isAboveFold: boolean;
|
|
74
|
-
prominenceScore: number;
|
|
75
|
-
depth: number;
|
|
76
|
-
reactComponentName?: string;
|
|
77
|
-
dataAttributes: Record<string, string>;
|
|
78
|
-
};
|
|
79
|
-
type StructuralEdge$1 = {
|
|
80
|
-
fromComponentId: string;
|
|
81
|
-
toComponentId: string;
|
|
82
|
-
/** 0.6 for direct parent → child, 0.3 for sibling (both directions emitted). */
|
|
83
|
-
weight: number;
|
|
84
|
-
};
|
|
85
|
-
type ScanResult = {
|
|
86
|
-
nodes: ScannedNode[];
|
|
87
|
-
edges: StructuralEdge$1[];
|
|
88
|
-
scannedAt: number;
|
|
89
|
-
};
|
|
90
|
-
type ContentAddedEvent = {
|
|
91
|
-
nodes: ScannedNode[];
|
|
92
|
-
edges: StructuralEdge$1[];
|
|
93
|
-
addedAt: number;
|
|
94
|
-
};
|
|
95
|
-
type DOMScanner = {
|
|
96
|
-
scan(): Promise<ScanResult>;
|
|
97
|
-
observe(onContentAdded: (event: ContentAddedEvent) => void): void;
|
|
98
|
-
getProminenceScore(element: Element): number;
|
|
99
|
-
destroy(): void;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/** In-memory context graph with persistence and backend sync. */
|
|
103
|
-
type PageNode = {
|
|
104
|
-
id: string;
|
|
105
|
-
componentId: string;
|
|
106
|
-
semanticType: string;
|
|
107
|
-
answers: string[];
|
|
108
|
-
prominenceScore: number;
|
|
109
|
-
depth: number;
|
|
110
|
-
};
|
|
111
|
-
type GraphSnapshot = {
|
|
112
|
-
pageNodes: PageNode[];
|
|
113
|
-
capturedAt: number;
|
|
114
|
-
};
|
|
115
|
-
type GraphConfig = {
|
|
116
|
-
syncUrl?: string;
|
|
117
|
-
apiKey?: string;
|
|
118
|
-
projectId?: string;
|
|
119
|
-
sessionId?: string;
|
|
120
|
-
};
|
|
121
|
-
type StructuralEdge = {
|
|
122
|
-
fromComponentId: string;
|
|
123
|
-
toComponentId: string;
|
|
124
|
-
weight: number;
|
|
125
|
-
};
|
|
126
|
-
type GraphClient = {
|
|
127
|
-
addPageNode(node: PageNode): void;
|
|
128
|
-
/** Record a DOM-derived parent/child or sibling relationship between two components. */
|
|
129
|
-
addStructuralEdge(edge: StructuralEdge): void;
|
|
130
|
-
/** One-shot batch sync of all current page nodes to the backend. */
|
|
131
|
-
syncOnce(): void;
|
|
132
|
-
snapshot(): GraphSnapshot;
|
|
133
|
-
serialize(): string;
|
|
134
|
-
restore(data: string): void;
|
|
135
|
-
destroy(): void;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Decision snapshot: the SPA / return-visit pre-paint source. Written after
|
|
140
|
-
* every successful decide; read by the inline pre-paint script (before any
|
|
141
|
-
* framework code runs) and by init() to seed slot/persona state.
|
|
142
|
-
*/
|
|
143
|
-
|
|
144
|
-
declare const SNAPSHOT_STORAGE_KEY_PREFIX = "_snt_snap:";
|
|
145
|
-
/** Versioned compound locator: resolve id → dataAttr → selector, then verify
|
|
146
|
-
* against fingerprint. Lets a slot survive DOM/markup drift. */
|
|
147
|
-
type CompoundLocator = {
|
|
148
|
-
v?: number;
|
|
149
|
-
id?: string;
|
|
150
|
-
dataAttr?: {
|
|
151
|
-
name: string;
|
|
152
|
-
value: string;
|
|
153
|
-
};
|
|
154
|
-
selector?: string;
|
|
155
|
-
urlMatch?: string;
|
|
156
|
-
fingerprint?: {
|
|
157
|
-
tag?: string;
|
|
158
|
-
text?: string;
|
|
159
|
-
};
|
|
160
|
-
semanticId?: string;
|
|
161
|
-
};
|
|
162
|
-
/** Bounded, declarative operations a registry arm may apply to its element.
|
|
163
|
-
* The style set is a fixed whitelist (validated server-side); no arbitrary CSS,
|
|
164
|
-
* HTML, or JS ever. `text` is applied via textContent; https-only URLs.
|
|
165
|
-
* moveBefore/moveAfter (exactly one) reposition the element relative to a
|
|
166
|
-
* uniquely-resolving sibling anchor — post-decide only, never pre-paint. */
|
|
167
|
-
type SlotOps = {
|
|
168
|
-
text?: string;
|
|
169
|
-
style?: Record<string, string>;
|
|
170
|
-
hidden?: boolean;
|
|
171
|
-
href?: string;
|
|
172
|
-
imageSrc?: string;
|
|
173
|
-
imageAlt?: string;
|
|
174
|
-
moveBefore?: CompoundLocator;
|
|
175
|
-
moveAfter?: CompoundLocator;
|
|
176
|
-
};
|
|
177
|
-
/** Registry-mode apply info per slot: where to apply and what to set. Stored so
|
|
178
|
-
* a returning visitor's pre-paint can reapply it. `target` is the Phase-2 bare
|
|
179
|
-
* selector; `locator` (Phase 3) is the compound locator, preferred when present. */
|
|
180
|
-
type SlotConfigEntry = {
|
|
181
|
-
kind: 'tokens' | 'arms';
|
|
182
|
-
target?: string;
|
|
183
|
-
locator?: CompoundLocator;
|
|
184
|
-
content?: string;
|
|
185
|
-
ops?: SlotOps;
|
|
186
|
-
};
|
|
187
|
-
type DecisionSnapshot = {
|
|
188
|
-
v: 1;
|
|
189
|
-
persona: string;
|
|
190
|
-
band: 'low' | 'medium' | 'high';
|
|
191
|
-
slots: Record<string, SlotResult>;
|
|
192
|
-
layoutOrder: string[] | null;
|
|
193
|
-
savedAt: number;
|
|
194
|
-
slotConfig?: Record<string, SlotConfigEntry>;
|
|
195
|
-
};
|
|
196
|
-
/** Returns null on missing, corrupt, or wrong-version data — never throws. */
|
|
197
|
-
declare function readSnapshot(apiKey: string): DecisionSnapshot | null;
|
|
198
|
-
/** Best-effort persist — storage failures are swallowed. */
|
|
199
|
-
declare function writeSnapshot(apiKey: string, snap: DecisionSnapshot): void;
|
|
200
|
-
/**
|
|
201
|
-
* Inline pre-paint script (Rung 1a): reads the snapshot and sets
|
|
202
|
-
* `data-sentient-persona` / `data-sentient-confidence` on <html> before
|
|
203
|
-
* first paint. Single-writer: it never overwrites attributes already set.
|
|
204
|
-
*
|
|
205
|
-
* Safety properties (pinned by tests):
|
|
206
|
-
* - apiKey goes through JSON.stringify, then '<' is escaped to <, so a
|
|
207
|
-
* hostile key can neither break the JS string nor terminate the <script>.
|
|
208
|
-
* - Built by string concatenation and contains no backticks, so the output
|
|
209
|
-
* survives being embedded in template-literal-based renderers.
|
|
210
|
-
*/
|
|
211
|
-
declare function renderPrePaintScript(apiKey: string): string;
|
|
212
|
-
|
|
213
|
-
declare const PROD_KEYLESS_ERROR = "[sentient] No API key configured \u2014 nothing is being learned. Set NEXT_PUBLIC_SENTIENT_API_KEY or pass localMode: true for local development.";
|
|
214
|
-
declare const LOCAL_MODE_BANNER = "[sentient] Local mode \u2014 decisions are simulated on-device and nothing is sent over the network. Add an API key to learn from real traffic.";
|
|
215
|
-
|
|
216
|
-
type MicroSignalEmitter = (signalType: 'rage_click' | 'text_copy' | 'scroll_hesitation' | 'tab_loss', extra?: Record<string, unknown>) => void;
|
|
217
|
-
type MicroSignalType = Parameters<MicroSignalEmitter>[0];
|
|
218
|
-
/**
|
|
219
|
-
* Attaches passive behavioral detectors to `node`. Calls `emit` when a signal
|
|
220
|
-
* fires. Each signal type fires at most once per call to this function.
|
|
221
|
-
* Returns a cleanup function that removes all listeners.
|
|
222
|
-
*/
|
|
223
|
-
declare function attachMicroSignalDetectors(emit: MicroSignalEmitter, node: Element, variantAssignedAt?: number): () => void;
|
|
224
|
-
|
|
225
|
-
type SentientConfig = {
|
|
226
|
-
apiKey: string;
|
|
227
|
-
context: 'landing' | 'ecommerce' | 'saas' | 'marketplace';
|
|
228
|
-
/** @internal — not exposed to users; defaults to the hosted SentientUI API. */
|
|
229
|
-
ingestUrl?: string;
|
|
230
|
-
debug?: boolean;
|
|
231
|
-
/**
|
|
232
|
-
* Pre-seeded assignments from `preloadAssignments()` (SSR).
|
|
233
|
-
* Seeds the local cache so `assign()` returns without a network call for
|
|
234
|
-
* listed code variants, guaranteeing server and client render the same
|
|
235
|
-
* variant on first paint. Managed-text components (assign with no
|
|
236
|
-
* variantIds) still fetch once when the seed carries no content.
|
|
237
|
-
*/
|
|
238
|
-
initialAssignments?: Record<string, string>;
|
|
239
|
-
/**
|
|
240
|
-
* Segment used for SSR preload (`device:source`). When set with `initialAssignments`,
|
|
241
|
-
* seeds the assignment cache under this key so hydration matches the server bandit row.
|
|
242
|
-
*/
|
|
243
|
-
sessionSegment?: string;
|
|
244
|
-
/**
|
|
245
|
-
* Consent gate. When `false`, returns a no-op client and performs no tracking.
|
|
246
|
-
* Defaults to `true`. Re-call `init()` (via `AdaptiveProvider` consent prop) when
|
|
247
|
-
* the user grants or revokes consent mid-session.
|
|
248
|
-
*/
|
|
249
|
-
consent?: boolean;
|
|
250
|
-
/**
|
|
251
|
-
* Behavior before consent is granted. `'statistical_winner'` fetches the
|
|
252
|
-
* best-performing variant via `GET /v1/winner` — no session or tracking data
|
|
253
|
-
* is stored. `'control'` (default) shows `variantIds[0]` with no API call.
|
|
254
|
-
* Applies when tracking is gated off — either `consent: false` or an active
|
|
255
|
-
* Do Not Track signal.
|
|
256
|
-
*/
|
|
257
|
-
preConsentBehavior?: 'statistical_winner' | 'control';
|
|
258
|
-
/**
|
|
259
|
-
* Whether to honor the browser's Do Not Track (DNT) signal. Defaults to `true`.
|
|
260
|
-
* When `true` and the visitor has DNT enabled, the SDK sets no cookies and
|
|
261
|
-
* sends no tracking data — behaving exactly as `consent: false` (still serving
|
|
262
|
-
* the read-only `preConsentBehavior` winner if configured), and `grantConsent()`
|
|
263
|
-
* will not upgrade it. Set `false` to make your own consent gate authoritative.
|
|
264
|
-
*/
|
|
265
|
-
respectDoNotTrack?: boolean;
|
|
266
|
-
userId?: string;
|
|
267
|
-
/**
|
|
268
|
-
* Session ID generated server-side (from `loadAdaptiveAssignments` / `loadAdaptiveDecision`).
|
|
269
|
-
* When provided, the client adopts this ID on first visit instead of generating a new one,
|
|
270
|
-
* ensuring events and goals are attributed to the same session the server used for assignment.
|
|
271
|
-
*/
|
|
272
|
-
ssrSessionId?: string;
|
|
273
|
-
/**
|
|
274
|
-
* ISO 3166-1 alpha-2 country code for the visitor. When provided (e.g. from
|
|
275
|
-
* the `CF-IPCountry` header in a Next.js server component), it is included in
|
|
276
|
-
* the session upsert so country-based segmentation works without client-side
|
|
277
|
-
* geo lookup.
|
|
278
|
-
*/
|
|
279
|
-
country?: string;
|
|
280
|
-
/**
|
|
281
|
-
* Pre-seeded slot results from `preloadDecisions()` / `loadAdaptiveDecision()`
|
|
282
|
-
* (SSR). Seeds the local slot state so `getSlotResult()` agrees with the
|
|
283
|
-
* server-rendered markup on first paint.
|
|
284
|
-
*/
|
|
285
|
-
initialSlots?: Record<string, SlotResult>;
|
|
286
|
-
/**
|
|
287
|
-
* Persona decided during SSR. Takes priority over the html-attribute
|
|
288
|
-
* adoption and the local snapshot.
|
|
289
|
-
*/
|
|
290
|
-
initialPersona?: {
|
|
291
|
-
persona: string;
|
|
292
|
-
confidence: number;
|
|
293
|
-
};
|
|
294
|
-
/**
|
|
295
|
-
* Keyless local mode. 'auto' (default) simulates decisions on-device when no
|
|
296
|
-
* valid API key is configured — but only in development builds (the
|
|
297
|
-
* `development` export condition); production bundles physically exclude the
|
|
298
|
-
* engine. `true` forces the local engine regardless of key (escape hatch);
|
|
299
|
-
* `false` restores the silent keyless no-op.
|
|
300
|
-
*/
|
|
301
|
-
localMode?: 'auto' | boolean;
|
|
302
|
-
};
|
|
303
|
-
type AssignResult = {
|
|
304
|
-
variantId: string;
|
|
305
|
-
assignmentTtlMs: number;
|
|
306
|
-
content?: string;
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
/** An editor-defined goal delivered with a registry-mode decision, for the
|
|
310
|
-
* snippet to install delegated listeners from. */
|
|
311
|
-
type GoalDefinition = {
|
|
312
|
-
goalId: string;
|
|
313
|
-
event: 'click' | 'form_submit' | 'url_reached';
|
|
314
|
-
locator?: CompoundLocator;
|
|
315
|
-
urlPattern?: string;
|
|
316
|
-
slotId?: string;
|
|
317
|
-
};
|
|
318
|
-
type DecideOutcome = {
|
|
319
|
-
layoutOrder: string[] | null;
|
|
320
|
-
assignments: Record<string, string>;
|
|
321
|
-
slots: Record<string, SlotResult>;
|
|
322
|
-
persona: string;
|
|
323
|
-
confidence: number;
|
|
324
|
-
slotConfig?: Record<string, SlotConfigEntry>;
|
|
325
|
-
goals?: GoalDefinition[];
|
|
326
|
-
};
|
|
327
|
-
type DecideInput = {
|
|
328
|
-
sections?: string[];
|
|
329
|
-
components?: Array<{
|
|
330
|
-
id: string;
|
|
331
|
-
variantIds?: string[];
|
|
332
|
-
}>;
|
|
333
|
-
slots?: SlotDeclInput[];
|
|
334
|
-
slotsFrom?: 'request' | 'registry';
|
|
335
|
-
/**
|
|
336
|
-
* Caller's build version (e.g. the snippet's `__SNIPPET_VERSION__`), sent
|
|
337
|
-
* as `v` on the wire. Additive/best-effort: the server persists it for
|
|
338
|
-
* version-skew reporting (see apps/api decide route) and ignores it
|
|
339
|
-
* entirely on older deployments. Omit if the caller has no version to report.
|
|
340
|
-
*/
|
|
341
|
-
v?: string;
|
|
342
|
-
};
|
|
343
|
-
type WeightEntry = {
|
|
344
|
-
variantId: string;
|
|
345
|
-
pulls: number;
|
|
346
|
-
avgReward: number | null;
|
|
347
|
-
};
|
|
348
|
-
type ComponentWeightEntry = {
|
|
349
|
-
componentId: string;
|
|
350
|
-
updatedAt: number;
|
|
351
|
-
variants: WeightEntry[];
|
|
352
|
-
};
|
|
353
|
-
type ComponentGoalOptions = {
|
|
354
|
-
/** Reward credited to the served variant (0–1). Defaults to 1. */
|
|
355
|
-
reward?: number;
|
|
356
|
-
/** Extra fields merged into the event payload. */
|
|
357
|
-
metadata?: Record<string, unknown>;
|
|
358
|
-
};
|
|
359
|
-
type SentientClient = {
|
|
360
|
-
track(event: Omit<SentientEvent, 'id' | 'sessionId' | 'timestamp' | 'timeInSession'>): void;
|
|
361
|
-
goal(name: string, metadata?: Record<string, unknown>, weight?: number, stepIndex?: number): void;
|
|
362
|
-
/**
|
|
363
|
-
* Records a conversion attributed to the variant currently served for
|
|
364
|
-
* `componentId`, so it feeds the per-variant CVR funnel. Resolves the served
|
|
365
|
-
* variant from the local assignment cache — no need to pass variantId or
|
|
366
|
-
* projectId. No-ops if the component has not been assigned yet (render its
|
|
367
|
-
* `<Adaptive>`/call `assign()` first). Prefer this over bare `goal()` for
|
|
368
|
-
* variant experiments; `goal()` is session-level only (no component attribution).
|
|
369
|
-
*/
|
|
370
|
-
componentGoal(componentId: string, goalType: string, opts?: ComponentGoalOptions): void;
|
|
371
|
-
identify(userId: string): void;
|
|
372
|
-
getAssignment(componentId: string, segment: string): Assignment | null;
|
|
373
|
-
/** Server-side variant assignment. Caches the result locally per (component, segment). */
|
|
374
|
-
assign(componentId: string, variantIds?: string[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): Promise<AssignResult | null>;
|
|
375
|
-
/**
|
|
376
|
-
* Single-roundtrip decision for layout sections, component variants, and
|
|
377
|
-
* adaptive slots. Awaits the session upsert (like `assign`) so the server
|
|
378
|
-
* never decides for a session row that doesn't exist yet. A response
|
|
379
|
-
* without a `slots` field means the server predates slots — every declared
|
|
380
|
-
* slot resolves to its baseline and no retry is made.
|
|
381
|
-
*/
|
|
382
|
-
decide(input: DecideInput): Promise<DecideOutcome | null>;
|
|
383
|
-
/** Slot result served this session (decide result, SSR seed, snapshot, or failure baseline). Null when unknown. */
|
|
384
|
-
getSlotResult(slotId: string): SlotResult | null;
|
|
385
|
-
/** Current persona estimate. Band is always `confidenceBand(confidence)`. Null when nothing is known yet. */
|
|
386
|
-
getPersona(): {
|
|
387
|
-
persona: string;
|
|
388
|
-
confidence: number;
|
|
389
|
-
band: 'low' | 'medium' | 'high';
|
|
390
|
-
} | null;
|
|
391
|
-
/** Fetches current bandit weights for all components in this project. Used by the provider to keep live-weight polling fresh. */
|
|
392
|
-
fetchWeights(): Promise<ComponentWeightEntry[]>;
|
|
393
|
-
getGraph(): GraphSnapshot;
|
|
394
|
-
/**
|
|
395
|
-
* Routine teardown: stops timers/listeners and flushes pending events, but
|
|
396
|
-
* KEEPS the visitor identity, decision snapshot, and retry bucket. Use for
|
|
397
|
-
* component unmount / re-init (framework providers call this on cleanup).
|
|
398
|
-
*/
|
|
399
|
-
dispose(): void;
|
|
400
|
-
/**
|
|
401
|
-
* Consent-revocation / forget-me teardown: everything `dispose()` does,
|
|
402
|
-
* plus deletion of the visitor identity (`_snt_uid`), the decision
|
|
403
|
-
* snapshot, and the persisted retry bucket. The next visit starts as a
|
|
404
|
-
* brand-new visitor.
|
|
405
|
-
*/
|
|
406
|
-
destroy(): void;
|
|
407
|
-
/** True when this client is the keyless local-mode client (dev only). */
|
|
408
|
-
readonly isLocal?: boolean;
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Detects whether the visitor has signalled a tracking opt-out. Honors Global
|
|
413
|
-
* Privacy Control (`navigator.globalPrivacyControl`) — the legally-enforceable
|
|
414
|
-
* CCPA/CPRA signal — as well as Do Not Track (`navigator.doNotTrack`, the legacy
|
|
415
|
-
* `window.doNotTrack` on older Firefox, and `navigator.msDoNotTrack` on old
|
|
416
|
-
* IE/Edge). GPC is a boolean; DNT is opt-out only when explicitly `'1'`/`'yes'`.
|
|
417
|
-
*/
|
|
418
|
-
declare function isDoNotTrackEnabled(): boolean;
|
|
419
|
-
/**
|
|
420
|
-
* Upgrades a pre-consent client (created with `consent: false, preConsentBehavior: 'statistical_winner'`)
|
|
421
|
-
* to a fully-tracking client. Call this from your consent management platform callback.
|
|
422
|
-
* For React apps, prefer updating the `consent` prop on `<AdaptiveProvider>`.
|
|
423
|
-
* Pass `apiKey` to target a specific project; omit to upgrade the most-recently-initialized client.
|
|
424
|
-
*/
|
|
425
|
-
declare function grantConsent(apiKey?: string): void;
|
|
426
|
-
/**
|
|
427
|
-
* Initializes the Sentient client. Returns a no-op client during SSR.
|
|
428
|
-
*/
|
|
429
|
-
declare function init(config: SentientConfig): SentientClient;
|
|
430
|
-
|
|
431
|
-
export { type AssignResult, type Assignment, type AssignmentCache, type ComponentGoalOptions, type ComponentWeightEntry, type CompoundLocator, type ContentAddedEvent, type DOMScanner, type DecideInput, type DecideOutcome, type DecisionSnapshot, type EventQueue, type EventType, type GoalDefinition, type GraphClient, type GraphConfig, type GraphSnapshot, LOCAL_MODE_BANNER, type MicroSignalEmitter, type MicroSignalType, PROD_KEYLESS_ERROR, type PageNode, type QueueConfig, SNAPSHOT_STORAGE_KEY_PREFIX, type ScanResult, type ScannedNode, type SentientClient, type SentientConfig, type SentientEvent, type SessionConfig, type SessionManager, type SlotConfigEntry, SlotDeclInput, type SlotOps, type WeightEntry, attachMicroSignalDetectors, grantConsent, init, isDoNotTrackEnabled, readSnapshot, renderPrePaintScript, writeSnapshot };
|