@cms.ai/brand_brain 0.0.1 → 0.1.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.
package/dist/index.mjs CHANGED
@@ -1385,7 +1385,11 @@ const SSEMessageType = {
1385
1385
  TextMessageEnd: "TEXT_MESSAGE_END",
1386
1386
  Custom: "CUSTOM"
1387
1387
  };
1388
- /** The `name` on `CUSTOM` guide events (skill payloads). */
1388
+ /**
1389
+ * The `name` on `CUSTOM` guide events (skill payloads). `SkillGate` and
1390
+ * `SkillError` never surface as `skill` stream events — the SDK re-maps them to
1391
+ * the dedicated `gate` / `error` {@link GuideStreamEvent} variants.
1392
+ */
1389
1393
  const GuideEvent = {
1390
1394
  SkillStart: "skill_start",
1391
1395
  SkillContent: "skill_content",
@@ -1405,8 +1409,7 @@ const GuideEvent = {
1405
1409
  SkillSolutionDesignDiagram: "skill_solutiondesign_diagram",
1406
1410
  SkillEnd: "skill_end",
1407
1411
  SkillError: "skill_error",
1408
- SkillGate: "skill_gate",
1409
- InputSuggestions: "input_suggestions"
1412
+ SkillGate: "skill_gate"
1410
1413
  };
1411
1414
  /** The guide skill types. `answer` is always on; the rest are enabled per-guide. */
1412
1415
  const SkillResponseType = {
@@ -1432,6 +1435,74 @@ const EmbedTrackingEvent = {
1432
1435
  EmbedSideTabClicked: "embed_side_tab_clicked",
1433
1436
  EmbedSideTabDismissed: "embed_side_tab_dismissed"
1434
1437
  };
1438
+ /** How a brand kit colors the guide's skill icons. */
1439
+ const SkillIconColorMode = {
1440
+ Default: "default",
1441
+ Mono: "mono",
1442
+ PerSkill: "per_skill"
1443
+ };
1444
+ /** Kind of content a document/playlist item points at. */
1445
+ const ContentType = {
1446
+ PDF: "pdf",
1447
+ DOCX: "docx",
1448
+ PPTX: "pptx",
1449
+ Image: "image",
1450
+ VideoNative: "video_native",
1451
+ VideoYouTube: "video_youtube",
1452
+ VideoWistia: "video_wistia",
1453
+ Document: "document",
1454
+ URL: "url",
1455
+ Text: "text",
1456
+ Markdown: "markdown"
1457
+ };
1458
+ /** Confidence level attached to a content recommendation. */
1459
+ const Confidence = {
1460
+ High: "high",
1461
+ Medium: "medium",
1462
+ Low: "low"
1463
+ };
1464
+ /** The render kind of a surfaced call-to-action. */
1465
+ const SkillCtaKind = {
1466
+ Url: "url",
1467
+ Form: "form",
1468
+ Product: "product",
1469
+ Scheduler: "scheduler"
1470
+ };
1471
+ /** Where a surfaced CTA renders. One slot today: below the skill response. */
1472
+ const CtaPlacement = { ResponseFooter: "response_footer" };
1473
+ /** Which scheduler embed a scheduler CTA loads. */
1474
+ const SchedulerProvider = {
1475
+ HubSpot: "hubspot",
1476
+ Calendly: "calendly"
1477
+ };
1478
+ /** Diagnose skill mode. */
1479
+ const DiagnoseSubtype = {
1480
+ Standalone: "standalone",
1481
+ ContextGathering: "context_gathering"
1482
+ };
1483
+ /** Answer option labels for diagnose questions. */
1484
+ const DiagnoseOptionLabel = {
1485
+ A: "A",
1486
+ B: "B",
1487
+ C: "C",
1488
+ D: "D"
1489
+ };
1490
+ /** Business case format sub-types. */
1491
+ const BusinessCaseSubType = {
1492
+ RoiAnalysis: "roi-analysis",
1493
+ StakeholderBrief: "stakeholder-brief",
1494
+ VendorComparison: "vendor-comparison"
1495
+ };
1496
+ /** Node types in a solution design diagram. */
1497
+ const SolutionDesignNodeType = {
1498
+ Process: "process",
1499
+ Decision: "decision",
1500
+ Datastore: "datastore",
1501
+ External: "external",
1502
+ Trigger: "trigger",
1503
+ Start: "start",
1504
+ End: "end"
1505
+ };
1435
1506
  //#endregion
1436
1507
  //#region src/chat-stream.ts
1437
1508
  const DATA_PREFIX = "data:";
@@ -1459,7 +1530,13 @@ async function* streamConversation(params) {
1459
1530
  body: JSON.stringify(body),
1460
1531
  signal
1461
1532
  });
1462
- if (!response.ok || !response.body) throw new Error(`[brand_brain] Conversation request failed with status ${response.status}`);
1533
+ if (!response.ok || !response.body) {
1534
+ let detail = "";
1535
+ try {
1536
+ detail = (await response.text()).trim();
1537
+ } catch {}
1538
+ throw new Error(`[brand_brain] Conversation request failed with status ${response.status}${detail ? `: ${detail}` : ""}`);
1539
+ }
1463
1540
  const reader = response.body.getReader();
1464
1541
  const decoder = new TextDecoder();
1465
1542
  let buffer = "";
@@ -1518,6 +1595,13 @@ function mapFrame(frame) {
1518
1595
  default: return null;
1519
1596
  }
1520
1597
  }
1598
+ /**
1599
+ * Names that surface as typed `skill` events. `SkillGate`/`SkillError` are
1600
+ * re-mapped to the `gate`/`error` variants below, so they are excluded here;
1601
+ * any name outside this set is a server event this SDK version predates and
1602
+ * flows through as `skill-unknown`.
1603
+ */
1604
+ const SKILL_EVENT_NAMES = new Set(Object.values(GuideEvent).filter((name) => name !== GuideEvent.SkillGate && name !== GuideEvent.SkillError));
1521
1605
  function mapCustomFrame(frame) {
1522
1606
  if (!frame.name) return null;
1523
1607
  const value = frame.value ?? {};
@@ -1530,20 +1614,42 @@ function mapCustomFrame(frame) {
1530
1614
  if (frame.name === GuideEvent.SkillError) return {
1531
1615
  type: "error",
1532
1616
  message: value.message ?? "Skill error",
1533
- code: value.errorType
1617
+ code: value.errorType,
1618
+ retryable: value.retryable,
1619
+ retryHint: value.retryHint
1534
1620
  };
1535
- return {
1621
+ if (SKILL_EVENT_NAMES.has(frame.name)) return {
1536
1622
  type: "skill",
1537
1623
  name: frame.name,
1538
1624
  value
1539
1625
  };
1626
+ return {
1627
+ type: "skill-unknown",
1628
+ name: frame.name,
1629
+ value
1630
+ };
1540
1631
  }
1541
1632
  //#endregion
1542
1633
  //#region src/GuideClient.ts
1634
+ /**
1635
+ * The account is resolved by the branded host the SDK talks to, and that host
1636
+ * must be same-site with `baseUrl` for the visitor cookies to attach — so the
1637
+ * `baseUrl` hostname IS the registered account domain. Derive it from there
1638
+ * rather than the host page's `window.location`, which is an unrelated origin
1639
+ * for cross-site embeds and produces a "Domain not found" 404 on resolve.
1640
+ */
1543
1641
  function resolveDomain(config) {
1544
1642
  if (config.domain) return config.domain;
1545
- if (typeof window !== "undefined") return window.location.hostname;
1546
- throw new Error("[brand_brain] `domain` is required when running without a browser `window`.");
1643
+ return hostnameFromBaseUrl(config.baseUrl);
1644
+ }
1645
+ /**
1646
+ * Normalize `baseUrl` to the bare host the server stores: lowercased, no port,
1647
+ * no leading `www.` (the domain write-path strips it, so a `www.` host can
1648
+ * never match). Accepts a bare host too, in case `baseUrl` omits the protocol.
1649
+ */
1650
+ function hostnameFromBaseUrl(baseUrl) {
1651
+ const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseUrl) ? baseUrl : `https://${baseUrl}`;
1652
+ return new URL(withProtocol).hostname.toLowerCase().replace(/^www\./, "");
1547
1653
  }
1548
1654
  /**
1549
1655
  * The client returned by {@link initGuide}. Holds the resolved guide, the
@@ -1597,7 +1703,9 @@ var GuideClient = class GuideClient {
1597
1703
  this.#api = createApiClient(this.apiBaseUrl, this.fetchImpl);
1598
1704
  }
1599
1705
  static async init(config) {
1600
- const fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
1706
+ const globalFetch = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
1707
+ const fetchImpl = config.fetch ?? globalFetch;
1708
+ if (!fetchImpl) throw new Error("[brand_brain] No `fetch` is available — pass `config.fetch` when running without a global fetch.");
1601
1709
  const apiBaseUrl = toApiBaseUrl(config.baseUrl);
1602
1710
  const domain = resolveDomain(config);
1603
1711
  const guide = await createApiClient(apiBaseUrl, fetchImpl).guides.resolve({
@@ -1736,6 +1844,149 @@ async function initGuide(config) {
1736
1844
  return GuideClient.init(config);
1737
1845
  }
1738
1846
  //#endregion
1739
- export { ConsentTier, EmbedTrackingEvent, GuideClient, GuideEvent, SSEMessageType, SkillResponseType, initGuide };
1847
+ //#region src/skill-state.ts
1848
+ /** A fresh, empty turn state. */
1849
+ function createSkillTurn() {
1850
+ return {
1851
+ skill: null,
1852
+ status: "streaming",
1853
+ messageId: null,
1854
+ diagnoseSubtype: null,
1855
+ followUpSkill: null,
1856
+ content: "",
1857
+ documents: [],
1858
+ contentRecommendations: [],
1859
+ followUpRecommendations: [],
1860
+ ctas: [],
1861
+ diagnoseHeader: null,
1862
+ diagnoseQuestions: [],
1863
+ playlistHeader: null,
1864
+ playlistItems: [],
1865
+ howToOutline: null,
1866
+ howToSteps: [],
1867
+ businessCaseFormats: [],
1868
+ businessCaseDrafts: {},
1869
+ businessCaseContent: {},
1870
+ solutionDesignHeader: null,
1871
+ solutionDesignNodes: [],
1872
+ solutionDesignEdges: [],
1873
+ skillInstanceId: null
1874
+ };
1875
+ }
1876
+ /** A recommendations payload is content cards iff its entries carry `targetSkill`. */
1877
+ function isContentRecommendations(recommendations) {
1878
+ return recommendations.length > 0 && "targetSkill" in recommendations[0];
1879
+ }
1880
+ /**
1881
+ * Fold one `skill` stream event into the turn state. Pure and immutable — the
1882
+ * returned object is a new reference whenever anything changed, so the result
1883
+ * can back React state directly:
1884
+ *
1885
+ * ```ts
1886
+ * let turn = createSkillTurn();
1887
+ * for await (const event of client.ask(message)) {
1888
+ * if (event.type === "skill") turn = reduceSkillEvent(turn, event);
1889
+ * }
1890
+ * ```
1891
+ */
1892
+ function reduceSkillEvent(turn, event) {
1893
+ switch (event.name) {
1894
+ case GuideEvent.SkillStart: return {
1895
+ ...event.value.skill === SkillResponseType.BusinessCase && turn.skill === SkillResponseType.BusinessCase ? {
1896
+ ...createSkillTurn(),
1897
+ businessCaseFormats: turn.businessCaseFormats,
1898
+ businessCaseDrafts: turn.businessCaseDrafts,
1899
+ businessCaseContent: turn.businessCaseContent
1900
+ } : createSkillTurn(),
1901
+ skill: event.value.skill,
1902
+ messageId: event.value.messageId,
1903
+ diagnoseSubtype: event.value.diagnoseSubtype ?? null,
1904
+ followUpSkill: event.value.followUpSkill ?? null
1905
+ };
1906
+ case GuideEvent.SkillContent: return {
1907
+ ...turn,
1908
+ content: turn.content + event.value.delta
1909
+ };
1910
+ case GuideEvent.SkillDocuments: return {
1911
+ ...turn,
1912
+ documents: event.value.documents
1913
+ };
1914
+ case GuideEvent.SkillRecommendations: return isContentRecommendations(event.value.recommendations) ? {
1915
+ ...turn,
1916
+ contentRecommendations: event.value.recommendations
1917
+ } : {
1918
+ ...turn,
1919
+ followUpRecommendations: event.value.recommendations
1920
+ };
1921
+ case GuideEvent.SkillCTAs: return {
1922
+ ...turn,
1923
+ ctas: event.value.ctas
1924
+ };
1925
+ case GuideEvent.SkillDiagnoseHeader: return {
1926
+ ...turn,
1927
+ diagnoseHeader: event.value.header
1928
+ };
1929
+ case GuideEvent.SkillDiagnoseQuestion: return {
1930
+ ...turn,
1931
+ diagnoseQuestions: [...turn.diagnoseQuestions, event.value.question]
1932
+ };
1933
+ case GuideEvent.SkillPlaylistHeader: return {
1934
+ ...turn,
1935
+ playlistHeader: event.value.header
1936
+ };
1937
+ case GuideEvent.SkillPlaylistItems: return {
1938
+ ...turn,
1939
+ playlistItems: event.value.items
1940
+ };
1941
+ case GuideEvent.SkillPlaylistRationale: return {
1942
+ ...turn,
1943
+ playlistItems: turn.playlistItems.map((item) => item.id === event.value.itemId ? {
1944
+ ...item,
1945
+ rationale: event.value.rationale
1946
+ } : item)
1947
+ };
1948
+ case GuideEvent.SkillHowToOutline: return {
1949
+ ...turn,
1950
+ howToOutline: event.value.outline,
1951
+ skillInstanceId: event.value.skillInstanceId ?? turn.skillInstanceId
1952
+ };
1953
+ case GuideEvent.SkillHowToSteps: return {
1954
+ ...turn,
1955
+ howToSteps: event.value.steps
1956
+ };
1957
+ case GuideEvent.SkillBusinessCaseChooser: return {
1958
+ ...turn,
1959
+ businessCaseFormats: event.value.formats,
1960
+ businessCaseDrafts: event.value.drafts
1961
+ };
1962
+ case GuideEvent.SkillBusinessCaseDelta: {
1963
+ const existing = turn.businessCaseContent[event.value.subType] ?? "";
1964
+ return {
1965
+ ...turn,
1966
+ businessCaseContent: {
1967
+ ...turn.businessCaseContent,
1968
+ [event.value.subType]: existing + event.value.delta
1969
+ }
1970
+ };
1971
+ }
1972
+ case GuideEvent.SkillSolutionDesignHeader: return {
1973
+ ...turn,
1974
+ solutionDesignHeader: event.value.header
1975
+ };
1976
+ case GuideEvent.SkillSolutionDesignDiagram: return {
1977
+ ...turn,
1978
+ solutionDesignNodes: event.value.nodes,
1979
+ solutionDesignEdges: event.value.edges
1980
+ };
1981
+ case GuideEvent.SkillEnd: return {
1982
+ ...turn,
1983
+ status: "complete",
1984
+ skillInstanceId: event.value.skillInstanceId ?? event.value.skillResponseId ?? turn.skillInstanceId
1985
+ };
1986
+ default: return event;
1987
+ }
1988
+ }
1989
+ //#endregion
1990
+ export { BusinessCaseSubType, Confidence, ConsentTier, ContentType, CtaPlacement, DiagnoseOptionLabel, DiagnoseSubtype, EmbedTrackingEvent, GuideClient, GuideEvent, SSEMessageType, SchedulerProvider, SkillCtaKind, SkillIconColorMode, SkillResponseType, SolutionDesignNodeType, createSkillTurn, initGuide, reduceSkillEvent };
1740
1991
 
1741
1992
  //# sourceMappingURL=index.mjs.map