Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@syncagent/js

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@syncagent/js - npm Package Compare versions

Comparing version
0.4.0
to
0.5.0
+47
-1
dist/index.d.mts

@@ -472,2 +472,48 @@ interface ToolParameter {

export { type ChatOptions, type ChatResult, type CollectionSchema, type CustomerChatOptions, type CustomerChatResult, type DualChatOptions, type DualChatReturn, type DualClient, type DualClientConfig, type FieldValidationResult, type GuestFormConfig, GuestIdentificationRequiredError, type GuestIdentity, GuestStorageManager, type Message, type SchemaField, SyncAgentClient, type SyncAgentConfig, type ToolData, type ToolDefinition, type ToolParameter, type UnifiedConfig, detectPageContext, generateGuestIdentifier, validateEmail, validateGuestForm, validateName };
/**
* Theme computation for customer chat components.
*
* Generates all color tokens from an accent color and dark mode flag,
* ensuring WCAG AA compliance:
* - 4.5:1 contrast ratio for text against backgrounds
* - 3:1 contrast ratio for focus indicators against adjacent colors
*/
interface ThemeColors {
background: string;
surface: string;
text: string;
textSecondary: string;
border: string;
accent: string;
accentHover: string;
userBubble: string;
userBubbleText: string;
assistantBubble: string;
assistantBubbleText: string;
inputBackground: string;
inputBorder: string;
}
/**
* Compute relative luminance per WCAG 2.1.
* https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
*/
declare function relativeLuminance(r: number, g: number, b: number): number;
/**
* Compute contrast ratio between two colors (each as [r, g, b]).
* Returns a value between 1 and 21.
*/
declare function contrastRatio(color1: [number, number, number], color2: [number, number, number]): number;
/**
* Compute a complete theme color palette from an accent color and dark mode flag.
*
* Ensures:
* - WCAG AA 4.5:1 contrast ratio for text colors against their backgrounds
* - 3:1 contrast ratio for focus indicators (accent against background)
*
* @param accentColor - CSS color string (hex, rgb, or hsl). Falls back to default if invalid.
* @param darkMode - Whether to use dark color scheme.
* @returns Complete ThemeColors object with all tokens.
*/
declare function computeTheme(accentColor?: string, darkMode?: boolean): ThemeColors;
export { type ChatOptions, type ChatResult, type CollectionSchema, type CustomerChatOptions, type CustomerChatResult, type DualChatOptions, type DualChatReturn, type DualClient, type DualClientConfig, type FieldValidationResult, type GuestFormConfig, GuestIdentificationRequiredError, type GuestIdentity, GuestStorageManager, type Message, type SchemaField, SyncAgentClient, type SyncAgentConfig, type ThemeColors, type ToolData, type ToolDefinition, type ToolParameter, type UnifiedConfig, computeTheme, contrastRatio, detectPageContext, generateGuestIdentifier, relativeLuminance, validateEmail, validateGuestForm, validateName };

@@ -472,2 +472,48 @@ interface ToolParameter {

export { type ChatOptions, type ChatResult, type CollectionSchema, type CustomerChatOptions, type CustomerChatResult, type DualChatOptions, type DualChatReturn, type DualClient, type DualClientConfig, type FieldValidationResult, type GuestFormConfig, GuestIdentificationRequiredError, type GuestIdentity, GuestStorageManager, type Message, type SchemaField, SyncAgentClient, type SyncAgentConfig, type ToolData, type ToolDefinition, type ToolParameter, type UnifiedConfig, detectPageContext, generateGuestIdentifier, validateEmail, validateGuestForm, validateName };
/**
* Theme computation for customer chat components.
*
* Generates all color tokens from an accent color and dark mode flag,
* ensuring WCAG AA compliance:
* - 4.5:1 contrast ratio for text against backgrounds
* - 3:1 contrast ratio for focus indicators against adjacent colors
*/
interface ThemeColors {
background: string;
surface: string;
text: string;
textSecondary: string;
border: string;
accent: string;
accentHover: string;
userBubble: string;
userBubbleText: string;
assistantBubble: string;
assistantBubbleText: string;
inputBackground: string;
inputBorder: string;
}
/**
* Compute relative luminance per WCAG 2.1.
* https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
*/
declare function relativeLuminance(r: number, g: number, b: number): number;
/**
* Compute contrast ratio between two colors (each as [r, g, b]).
* Returns a value between 1 and 21.
*/
declare function contrastRatio(color1: [number, number, number], color2: [number, number, number]): number;
/**
* Compute a complete theme color palette from an accent color and dark mode flag.
*
* Ensures:
* - WCAG AA 4.5:1 contrast ratio for text colors against their backgrounds
* - 3:1 contrast ratio for focus indicators (accent against background)
*
* @param accentColor - CSS color string (hex, rgb, or hsl). Falls back to default if invalid.
* @param darkMode - Whether to use dark color scheme.
* @returns Complete ThemeColors object with all tokens.
*/
declare function computeTheme(accentColor?: string, darkMode?: boolean): ThemeColors;
export { type ChatOptions, type ChatResult, type CollectionSchema, type CustomerChatOptions, type CustomerChatResult, type DualChatOptions, type DualChatReturn, type DualClient, type DualClientConfig, type FieldValidationResult, type GuestFormConfig, GuestIdentificationRequiredError, type GuestIdentity, GuestStorageManager, type Message, type SchemaField, SyncAgentClient, type SyncAgentConfig, type ThemeColors, type ToolData, type ToolDefinition, type ToolParameter, type UnifiedConfig, computeTheme, contrastRatio, detectPageContext, generateGuestIdentifier, relativeLuminance, validateEmail, validateGuestForm, validateName };

@@ -26,4 +26,7 @@ "use strict";

SyncAgentClient: () => SyncAgentClient,
computeTheme: () => computeTheme,
contrastRatio: () => contrastRatio,
detectPageContext: () => detectPageContext,
generateGuestIdentifier: () => generateGuestIdentifier,
relativeLuminance: () => relativeLuminance,
validateEmail: () => validateEmail,

@@ -631,2 +634,222 @@ validateGuestForm: () => validateGuestForm,

}
// src/theme.ts
function parseHex(hex) {
const h = hex.replace("#", "");
if (h.length === 3) {
const r = parseInt(h[0] + h[0], 16);
const g = parseInt(h[1] + h[1], 16);
const b = parseInt(h[2] + h[2], 16);
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
return [r, g, b];
}
if (h.length === 6) {
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
return [r, g, b];
}
return null;
}
function parseRgb(color) {
const m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
if (!m) return null;
return [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])];
}
function parseHsl(color) {
const m = color.match(/hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/);
if (!m) return null;
const h = parseFloat(m[1]) / 360;
const s = parseFloat(m[2]) / 100;
const l = parseFloat(m[3]) / 100;
return hslToRgb(h, s, l);
}
function hslToRgb(h, s, l) {
if (s === 0) {
const v = Math.round(l * 255);
return [v, v, v];
}
const hue2rgb = (p2, q2, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
if (t < 1 / 2) return q2;
if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
return p2;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
return [
Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
Math.round(hue2rgb(p, q, h) * 255),
Math.round(hue2rgb(p, q, h - 1 / 3) * 255)
];
}
function rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s = 0;
const l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
break;
case g:
h = ((b - r) / d + 2) / 6;
break;
case b:
h = ((r - g) / d + 4) / 6;
break;
}
}
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
}
function parseColor(color) {
const trimmed = color.trim().toLowerCase();
if (trimmed.startsWith("#")) return parseHex(trimmed);
if (trimmed.startsWith("rgb")) return parseRgb(trimmed);
if (trimmed.startsWith("hsl")) return parseHsl(trimmed);
return null;
}
function toHex(r, g, b) {
return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)}`;
}
function relativeLuminance(r, g, b) {
const [rs, gs, bs] = [r / 255, g / 255, b / 255].map(
(c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
);
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
function contrastRatio(color1, color2) {
const l1 = relativeLuminance(...color1);
const l2 = relativeLuminance(...color2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function adjustLightness(rgb, delta) {
const [h, s, l] = rgbToHsl(...rgb);
const newL = clamp(l + delta, 0, 100);
return hslToRgb(h / 360, s / 100, newL / 100);
}
function ensureContrast(textRgb, bgRgb, targetRatio) {
let current = textRgb;
const bgLum = relativeLuminance(...bgRgb);
const direction = bgLum < 0.5 ? 1 : -1;
for (let i = 0; i < 50; i++) {
if (contrastRatio(current, bgRgb) >= targetRatio) {
return current;
}
current = adjustLightness(current, direction * 5);
}
return bgLum < 0.5 ? [255, 255, 255] : [0, 0, 0];
}
var DEFAULT_ACCENT = "#6366f1";
function computeTheme(accentColor, darkMode = false) {
let accentRgb = parseColor(accentColor || DEFAULT_ACCENT);
if (!accentRgb) {
accentRgb = parseColor(DEFAULT_ACCENT);
}
if (darkMode) {
return computeDarkTheme(accentRgb);
}
return computeLightTheme(accentRgb);
}
function ensureBubbleContrast(bubbleRgb) {
const white = [255, 255, 255];
const black = [0, 0, 0];
if (contrastRatio(white, bubbleRgb) >= 4.5) {
return { bubble: bubbleRgb, text: white };
}
let darkened = bubbleRgb;
for (let i = 0; i < 30; i++) {
darkened = adjustLightness(darkened, -3);
if (contrastRatio(white, darkened) >= 4.5) {
return { bubble: darkened, text: white };
}
}
if (contrastRatio(black, bubbleRgb) >= 4.5) {
return { bubble: bubbleRgb, text: black };
}
return { bubble: bubbleRgb, text: white };
}
function computeLightTheme(accentRgb) {
const background = [255, 255, 255];
const surface = [249, 250, 251];
const border = [229, 231, 235];
const inputBackground = [255, 255, 255];
const inputBorder = [209, 213, 219];
let text = [17, 24, 39];
let textSecondary = [107, 114, 128];
text = ensureContrast(text, background, 4.5);
textSecondary = ensureContrast(textSecondary, background, 4.5);
let accent = accentRgb;
if (contrastRatio(accent, background) < 3) {
accent = ensureContrast(accent, background, 3);
}
const accentHover = adjustLightness(accent, -8);
const { bubble: userBubble, text: userBubbleText } = ensureBubbleContrast(accent);
const assistantBubble = [243, 244, 246];
let assistantBubbleText = [31, 41, 55];
assistantBubbleText = ensureContrast(assistantBubbleText, assistantBubble, 4.5);
return {
background: toHex(...background),
surface: toHex(...surface),
text: toHex(...text),
textSecondary: toHex(...textSecondary),
border: toHex(...border),
accent: toHex(...accent),
accentHover: toHex(...accentHover),
userBubble: toHex(...userBubble),
userBubbleText: toHex(...userBubbleText),
assistantBubble: toHex(...assistantBubble),
assistantBubbleText: toHex(...assistantBubbleText),
inputBackground: toHex(...inputBackground),
inputBorder: toHex(...inputBorder)
};
}
function computeDarkTheme(accentRgb) {
const background = [24, 24, 27];
const surface = [39, 39, 42];
const border = [63, 63, 70];
const inputBackground = [39, 39, 42];
const inputBorder = [63, 63, 70];
let text = [244, 244, 245];
let textSecondary = [161, 161, 170];
text = ensureContrast(text, background, 4.5);
textSecondary = ensureContrast(textSecondary, background, 4.5);
let accent = accentRgb;
if (contrastRatio(accent, background) < 3) {
accent = ensureContrast(accent, background, 3);
}
const accentHover = adjustLightness(accent, 8);
const { bubble: userBubble, text: userBubbleText } = ensureBubbleContrast(accent);
const assistantBubble = [52, 52, 56];
let assistantBubbleText = [228, 228, 231];
assistantBubbleText = ensureContrast(assistantBubbleText, assistantBubble, 4.5);
return {
background: toHex(...background),
surface: toHex(...surface),
text: toHex(...text),
textSecondary: toHex(...textSecondary),
border: toHex(...border),
accent: toHex(...accent),
accentHover: toHex(...accentHover),
userBubble: toHex(...userBubble),
userBubbleText: toHex(...userBubbleText),
assistantBubble: toHex(...assistantBubble),
assistantBubbleText: toHex(...assistantBubbleText),
inputBackground: toHex(...inputBackground),
inputBorder: toHex(...inputBorder)
};
}
// Annotate the CommonJS export names for ESM import in node:

@@ -637,4 +860,7 @@ 0 && (module.exports = {

SyncAgentClient,
computeTheme,
contrastRatio,
detectPageContext,
generateGuestIdentifier,
relativeLuminance,
validateEmail,

@@ -641,0 +867,0 @@ validateGuestForm,

@@ -597,2 +597,222 @@ // src/stream.ts

}
// src/theme.ts
function parseHex(hex) {
const h = hex.replace("#", "");
if (h.length === 3) {
const r = parseInt(h[0] + h[0], 16);
const g = parseInt(h[1] + h[1], 16);
const b = parseInt(h[2] + h[2], 16);
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
return [r, g, b];
}
if (h.length === 6) {
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
return [r, g, b];
}
return null;
}
function parseRgb(color) {
const m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
if (!m) return null;
return [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])];
}
function parseHsl(color) {
const m = color.match(/hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/);
if (!m) return null;
const h = parseFloat(m[1]) / 360;
const s = parseFloat(m[2]) / 100;
const l = parseFloat(m[3]) / 100;
return hslToRgb(h, s, l);
}
function hslToRgb(h, s, l) {
if (s === 0) {
const v = Math.round(l * 255);
return [v, v, v];
}
const hue2rgb = (p2, q2, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
if (t < 1 / 2) return q2;
if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
return p2;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
return [
Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
Math.round(hue2rgb(p, q, h) * 255),
Math.round(hue2rgb(p, q, h - 1 / 3) * 255)
];
}
function rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s = 0;
const l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
break;
case g:
h = ((b - r) / d + 2) / 6;
break;
case b:
h = ((r - g) / d + 4) / 6;
break;
}
}
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
}
function parseColor(color) {
const trimmed = color.trim().toLowerCase();
if (trimmed.startsWith("#")) return parseHex(trimmed);
if (trimmed.startsWith("rgb")) return parseRgb(trimmed);
if (trimmed.startsWith("hsl")) return parseHsl(trimmed);
return null;
}
function toHex(r, g, b) {
return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)}`;
}
function relativeLuminance(r, g, b) {
const [rs, gs, bs] = [r / 255, g / 255, b / 255].map(
(c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
);
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
function contrastRatio(color1, color2) {
const l1 = relativeLuminance(...color1);
const l2 = relativeLuminance(...color2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function adjustLightness(rgb, delta) {
const [h, s, l] = rgbToHsl(...rgb);
const newL = clamp(l + delta, 0, 100);
return hslToRgb(h / 360, s / 100, newL / 100);
}
function ensureContrast(textRgb, bgRgb, targetRatio) {
let current = textRgb;
const bgLum = relativeLuminance(...bgRgb);
const direction = bgLum < 0.5 ? 1 : -1;
for (let i = 0; i < 50; i++) {
if (contrastRatio(current, bgRgb) >= targetRatio) {
return current;
}
current = adjustLightness(current, direction * 5);
}
return bgLum < 0.5 ? [255, 255, 255] : [0, 0, 0];
}
var DEFAULT_ACCENT = "#6366f1";
function computeTheme(accentColor, darkMode = false) {
let accentRgb = parseColor(accentColor || DEFAULT_ACCENT);
if (!accentRgb) {
accentRgb = parseColor(DEFAULT_ACCENT);
}
if (darkMode) {
return computeDarkTheme(accentRgb);
}
return computeLightTheme(accentRgb);
}
function ensureBubbleContrast(bubbleRgb) {
const white = [255, 255, 255];
const black = [0, 0, 0];
if (contrastRatio(white, bubbleRgb) >= 4.5) {
return { bubble: bubbleRgb, text: white };
}
let darkened = bubbleRgb;
for (let i = 0; i < 30; i++) {
darkened = adjustLightness(darkened, -3);
if (contrastRatio(white, darkened) >= 4.5) {
return { bubble: darkened, text: white };
}
}
if (contrastRatio(black, bubbleRgb) >= 4.5) {
return { bubble: bubbleRgb, text: black };
}
return { bubble: bubbleRgb, text: white };
}
function computeLightTheme(accentRgb) {
const background = [255, 255, 255];
const surface = [249, 250, 251];
const border = [229, 231, 235];
const inputBackground = [255, 255, 255];
const inputBorder = [209, 213, 219];
let text = [17, 24, 39];
let textSecondary = [107, 114, 128];
text = ensureContrast(text, background, 4.5);
textSecondary = ensureContrast(textSecondary, background, 4.5);
let accent = accentRgb;
if (contrastRatio(accent, background) < 3) {
accent = ensureContrast(accent, background, 3);
}
const accentHover = adjustLightness(accent, -8);
const { bubble: userBubble, text: userBubbleText } = ensureBubbleContrast(accent);
const assistantBubble = [243, 244, 246];
let assistantBubbleText = [31, 41, 55];
assistantBubbleText = ensureContrast(assistantBubbleText, assistantBubble, 4.5);
return {
background: toHex(...background),
surface: toHex(...surface),
text: toHex(...text),
textSecondary: toHex(...textSecondary),
border: toHex(...border),
accent: toHex(...accent),
accentHover: toHex(...accentHover),
userBubble: toHex(...userBubble),
userBubbleText: toHex(...userBubbleText),
assistantBubble: toHex(...assistantBubble),
assistantBubbleText: toHex(...assistantBubbleText),
inputBackground: toHex(...inputBackground),
inputBorder: toHex(...inputBorder)
};
}
function computeDarkTheme(accentRgb) {
const background = [24, 24, 27];
const surface = [39, 39, 42];
const border = [63, 63, 70];
const inputBackground = [39, 39, 42];
const inputBorder = [63, 63, 70];
let text = [244, 244, 245];
let textSecondary = [161, 161, 170];
text = ensureContrast(text, background, 4.5);
textSecondary = ensureContrast(textSecondary, background, 4.5);
let accent = accentRgb;
if (contrastRatio(accent, background) < 3) {
accent = ensureContrast(accent, background, 3);
}
const accentHover = adjustLightness(accent, 8);
const { bubble: userBubble, text: userBubbleText } = ensureBubbleContrast(accent);
const assistantBubble = [52, 52, 56];
let assistantBubbleText = [228, 228, 231];
assistantBubbleText = ensureContrast(assistantBubbleText, assistantBubble, 4.5);
return {
background: toHex(...background),
surface: toHex(...surface),
text: toHex(...text),
textSecondary: toHex(...textSecondary),
border: toHex(...border),
accent: toHex(...accent),
accentHover: toHex(...accentHover),
userBubble: toHex(...userBubble),
userBubbleText: toHex(...userBubbleText),
assistantBubble: toHex(...assistantBubble),
assistantBubbleText: toHex(...assistantBubbleText),
inputBackground: toHex(...inputBackground),
inputBorder: toHex(...inputBorder)
};
}
export {

@@ -602,4 +822,7 @@ GuestIdentificationRequiredError,

SyncAgentClient,
computeTheme,
contrastRatio,
detectPageContext,
generateGuestIdentifier,
relativeLuminance,
validateEmail,

@@ -606,0 +829,0 @@ validateGuestForm,

+1
-1
{
"name": "@syncagent/js",
"version": "0.4.0",
"version": "0.5.0",
"description": "SyncAgent JavaScript SDK — AI database agent for any app",

@@ -5,0 +5,0 @@ "homepage": "https://syncagentdev.vercel.app/docs",

+181
-1

@@ -237,6 +237,15 @@ # @syncagent/js

## Customer Agent Mode
## Customer Chat
Route messages through the customer support pipeline — with persona, knowledge base, conversation flows, escalation, and AI fallback — instead of the direct database agent.
The customer chat API surface includes:
- **`client.customerChat()`** — send messages through the support pipeline
- **`client.rateConversation()`** — submit satisfaction ratings
- **Guest identification** — `getGuestIdentity()`, `setGuestIdentity()`, `validateGuestForm()`, `generateGuestIdentifier()`
- **Theme engine** — `computeTheme()` for WCAG-compliant color generation
- **Types** — `CustomerChatResult`, `CustomerChatOptions`, `GuestIdentity`, `GuestFormConfig`, `FieldValidationResult`
### Initialization
```typescript

@@ -253,2 +262,74 @@ import { SyncAgentClient } from "@syncagent/js";

### Full Lifecycle Example
```typescript
import { SyncAgentClient } from "@syncagent/js";
// 1. Initialize client in customer mode
const client = new SyncAgentClient({
apiKey: "sa_your_api_key",
externalUserId: "customer_123",
});
// 2. Send a message
const result = await client.customerChat("How do I reset my password?");
console.log(result.response);
// "To reset your password, go to Settings > Security > Reset Password..."
// 3. Continue the conversation
const followUp = await client.customerChat("That didn't work", {
conversationId: result.conversationId,
onEscalated: () => {
console.log("Conversation escalated to a human agent");
// Connect to Pusher for real-time agent messages
},
onResolved: (conversationId) => {
console.log(`Conversation ${conversationId} resolved`);
},
});
// 4. Rate the conversation after resolution
if (followUp.resolved) {
await client.rateConversation(followUp.conversationId, 5);
}
```
### Guest Mode (No External User ID)
For anonymous visitors, initialize with `customerMode: true` and no `externalUserId`. The SDK will require guest identification before chatting:
```typescript
import { SyncAgentClient, generateGuestIdentifier, validateGuestForm } from "@syncagent/js";
const client = new SyncAgentClient({
apiKey: "sa_your_api_key",
customerMode: true,
guestForm: {
title: "Welcome!",
subtitle: "Tell us who you are to get started",
submitButtonText: "Start Chat",
},
});
// Validate form data before submitting
const validation = validateGuestForm({
name: "Jane Doe",
email: "jane@example.com",
});
if (validation.valid) {
// Set guest identity — persists to localStorage
client.setGuestIdentity({
name: "Jane Doe",
email: "jane@example.com",
phone: null,
guestId: generateGuestIdentifier("jane@example.com"),
});
// Now customerChat() will work
const result = await client.customerChat("I need help with my order");
console.log(result.response);
}
```
### Configuration

@@ -293,2 +374,3 @@

| `onResolved` | `(conversationId: string) => void` | Called when the conversation is resolved |
| `onGuestIdentified` | `(identity: GuestIdentity) => void` | Called when a guest completes identification (JS-only usage) |

@@ -593,2 +675,86 @@ #### `CustomerChatResult`

## Theme Engine
The `computeTheme()` function generates a complete, WCAG-compliant color palette from an accent color and dark mode flag. Used internally by the pre-built `<SyncAgentCustomerChat>` components across all framework packages (React, Angular, Vue).
### `computeTheme(accentColor?, darkMode?)`
```typescript
import { computeTheme } from "@syncagent/js";
const theme = computeTheme("#6366f1", false);
// ThemeColors object with all color tokens
```
**Parameters**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `accentColor` | `string` | `"#6366f1"` | CSS color string (hex `#RGB`/`#RRGGBB`, `rgb()`, or `hsl()`). Falls back to default if invalid. |
| `darkMode` | `boolean` | `false` | Whether to generate a dark color scheme |
**Returns** `ThemeColors`
### `ThemeColors`
| Field | Type | Description |
|-------|------|-------------|
| `background` | `string` | Main background color |
| `surface` | `string` | Elevated surface color (cards, panels) |
| `text` | `string` | Primary text color (4.5:1 contrast against background) |
| `textSecondary` | `string` | Secondary text color (4.5:1 contrast against background) |
| `border` | `string` | Border color for dividers and outlines |
| `accent` | `string` | Primary accent color (3:1 contrast against background) |
| `accentHover` | `string` | Accent color on hover state |
| `userBubble` | `string` | User message bubble background |
| `userBubbleText` | `string` | User message bubble text (4.5:1 contrast against userBubble) |
| `assistantBubble` | `string` | Assistant message bubble background |
| `assistantBubbleText` | `string` | Assistant message bubble text (4.5:1 contrast against assistantBubble) |
| `inputBackground` | `string` | Text input background color |
| `inputBorder` | `string` | Text input border color |
### WCAG Contrast Guarantees
`computeTheme()` ensures the following minimum contrast ratios for any valid input:
- **4.5:1** — `text` against `background`
- **4.5:1** — `textSecondary` against `background`
- **4.5:1** — `userBubbleText` against `userBubble`
- **4.5:1** — `assistantBubbleText` against `assistantBubble`
- **3:1** — `accent` against `background`
### Contrast Utilities
```typescript
import { relativeLuminance, contrastRatio } from "@syncagent/js";
// Compute relative luminance (WCAG 2.1) for an RGB color
const lum = relativeLuminance(99, 102, 241); // 0-1
// Compute contrast ratio between two RGB colors
const ratio = contrastRatio([255, 255, 255], [24, 24, 27]); // 1-21
```
| Function | Signature | Description |
|----------|-----------|-------------|
| `relativeLuminance` | `(r: number, g: number, b: number) => number` | WCAG 2.1 relative luminance (0–1) for an RGB color (0–255 per channel) |
| `contrastRatio` | `(color1: [r,g,b], color2: [r,g,b]) => number` | Contrast ratio (1–21) between two RGB colors |
### Example: Custom Theme Integration
```typescript
import { computeTheme, type ThemeColors } from "@syncagent/js";
// Light mode with custom brand color
const lightTheme: ThemeColors = computeTheme("#e11d48", false);
// Dark mode
const darkTheme: ThemeColors = computeTheme("#e11d48", true);
// Apply to your UI
document.documentElement.style.setProperty("--chat-bg", lightTheme.background);
document.documentElement.style.setProperty("--chat-text", lightTheme.text);
document.documentElement.style.setProperty("--chat-accent", lightTheme.accent);
```
## Unified Dual Mode

@@ -667,5 +833,19 @@

DualChatReturn, DualChatOptions, UnifiedConfig,
GuestIdentity, GuestFormConfig, FieldValidationResult,
} from "@syncagent/js";
import type { ThemeColors } from "@syncagent/js";
```
### Customer Chat Types
| Type | Description |
|------|-------------|
| `CustomerChatResult` | Response from `customerChat()` — contains `conversationId`, `response`, `escalated`, `resolved`, `flowActive`, `welcomeMessage`, `sources`, `flowSession` |
| `CustomerChatOptions` | Options for `customerChat()` — includes `conversationId`, `metadata`, `onEscalated`, `onResolved`, `onGuestIdentified` |
| `GuestIdentity` | Guest user data — `name`, `email`, `phone`, `guestId` |
| `GuestFormConfig` | Guest form customization — `title`, `subtitle`, `submitButtonText`, placeholders, `className`, `onSubmit` |
| `FieldValidationResult` | Validation result — `valid` boolean and optional `error` message |
| `ThemeColors` | Complete color palette from `computeTheme()` — 13 color tokens for backgrounds, text, accents, and bubbles |
### New Types Reference

@@ -672,0 +852,0 @@