@ensembleapp/client-sdk 0.0.54 → 0.0.55

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/README.md CHANGED
@@ -63,6 +63,80 @@ function CustomChat() {
63
63
  }
64
64
  ```
65
65
 
66
+ ### Reading token usage
67
+
68
+ The server streams token/usage stats as a `data-usage` part. You can read them
69
+ three ways.
70
+
71
+ **1. `<ChatWidget>`: the `onUsage` callback**
72
+
73
+ ```tsx
74
+ import { ChatWidget, type ChatUsageData } from '@ensembleapp/client-sdk'
75
+
76
+ <ChatWidget
77
+ api={{ baseUrl: '/api', token: 'your-jwt-token' }}
78
+ agentId="agent-234"
79
+ threadId="user-123"
80
+ onUsage={(usage: ChatUsageData) => {
81
+ console.log(usage.inputTokens, usage.outputTokens)
82
+ console.log(usage.totalMessageCount, usage.messageHistoryLimit)
83
+ }}
84
+ />
85
+ ```
86
+
87
+ Or, to render the built-in counter (`↑ N in · ↓ N out`) above the input without
88
+ writing any code, set `showTokenUsage`:
89
+
90
+ ```tsx
91
+ <ChatWidget api={/* ... */} agentId="..." threadId="..." showTokenUsage />
92
+ ```
93
+
94
+ **2. `useChat` hook: the `usage` return value**
95
+
96
+ `usage` is reactive state (the latest turn), so it's the simplest way to render:
97
+
98
+ ```tsx
99
+ import { useChat, type ChatUsageData } from '@ensembleapp/client-sdk'
100
+
101
+ function CustomChat() {
102
+ const { messages, sendMessage, usage } = useChat({ /* ...config */ })
103
+ // ^^^^^ ChatUsageData | null
104
+
105
+ return <div>{usage && `${usage.inputTokens} in / ${usage.outputTokens} out`}</div>
106
+ }
107
+ ```
108
+
109
+ You can also pass `onUsage` to the hook if you prefer the event style.
110
+
111
+ **3. `extractUsageData`: for custom stream handling**
112
+
113
+ If you handle the stream at a lower level (e.g. via `onData`), pull usage out of
114
+ any data part:
115
+
116
+ ```tsx
117
+ import { extractUsageData } from '@ensembleapp/client-sdk'
118
+
119
+ onData: (dataPart) => {
120
+ const usage = extractUsageData(dataPart) // ChatUsageData | null
121
+ if (usage) { /* ... */ }
122
+ }
123
+ ```
124
+
125
+ **`ChatUsageData` shape**
126
+
127
+ ```ts
128
+ interface ChatUsageData {
129
+ inputTokens: number
130
+ outputTokens: number
131
+ maxInputTokens?: number
132
+ maxOutputTokens?: number
133
+ inputTokensPercent?: number
134
+ outputTokensPercent?: number
135
+ totalMessageCount?: number
136
+ messageHistoryLimit?: number
137
+ }
138
+ ```
139
+
66
140
  ## API Reference
67
141
 
68
142
  ### ChatWidget Props
@@ -72,6 +146,9 @@ function CustomChat() {
72
146
  - `onMessage?` (function): Callback when a new message is received
73
147
  - `onError?` (function): Callback when an error occurs
74
148
  - `onFinish?` (function): Callback when a conversation finishes
149
+ - `onUsage?` ((usage: ChatUsageData) => void): Callback with token/usage stats as they stream
150
+ - `onData?` ((dataPart) => void): Callback for every raw stream data part
151
+ - `showTokenUsage?` (boolean): Show the built-in input/output token counter above the input (default: false)
75
152
  - `className?` (string): Additional CSS classes
76
153
  - `placeholder?` (string): Input placeholder text
77
154
  - `welcomeMessage?` (string): Initial welcome message
@@ -81,10 +158,10 @@ function CustomChat() {
81
158
  ```tsx
82
159
  const {
83
160
  messages, // Array of chat messages
84
- isLoading, // Boolean indicating if request is in progress
85
- isError, // Boolean indicating if there's an error
161
+ status, // Current request status
86
162
  sendMessage, // Function to send a message
87
163
  stop, // Function to stop current request
88
164
  setMessages, // Function to manually set messages
165
+ usage, // ChatUsageData | null (latest token/usage stats)
89
166
  } = useChat(config)
90
167
  ```
package/dist/index.d.ts CHANGED
@@ -63,6 +63,29 @@ interface ApiConfig {
63
63
  /** Agent version selector: 'latest' (default), 'published', or a specific version number */
64
64
  type AgentVersion = 'latest' | 'published' | number;
65
65
  type StructuredMessagePayload = Record<string, unknown>;
66
+ /**
67
+ * Usage stats emitted by the server as a `data-usage` stream part and delivered
68
+ * to the {@link UseChatConfig.onUsage} callback.
69
+ */
70
+ interface ChatUsageData {
71
+ inputTokens: number;
72
+ outputTokens: number;
73
+ maxInputTokens?: number;
74
+ maxOutputTokens?: number;
75
+ inputTokensPercent?: number;
76
+ outputTokensPercent?: number;
77
+ /** Total number of messages in the conversation so far */
78
+ totalMessageCount?: number;
79
+ /** Maximum number of messages allowed in conversation history */
80
+ messageHistoryLimit?: number;
81
+ }
82
+ /**
83
+ * Extract usage stats from a stream data part. The server wraps usage as
84
+ * `{ type: 'data-usage', data: { ...ChatUsageData } }`; returns the full inner
85
+ * payload (including totalMessageCount / messageHistoryLimit) or null if the
86
+ * part is not a usage part.
87
+ */
88
+ declare function extractUsageData(dataPart: unknown): ChatUsageData | null;
66
89
  type UseChatConfig = DeprecatedChatConfig & {
67
90
  /** The server API configuration */
68
91
  api: ApiConfig;
@@ -80,15 +103,8 @@ type UseChatConfig = DeprecatedChatConfig & {
80
103
  onFinish?: (message: any) => void;
81
104
  onData?: (data: any) => void;
82
105
  onMessage?: (message: UIMessage) => void;
83
- /** Called when usage data is received (input/output token counts and percentages) */
84
- onUsage?: (usage: {
85
- inputTokens: number;
86
- outputTokens: number;
87
- maxInputTokens?: number;
88
- maxOutputTokens?: number;
89
- inputTokensPercent?: number;
90
- outputTokensPercent?: number;
91
- }) => void;
106
+ /** Called when usage data is received (token counts/percentages and message-history stats) */
107
+ onUsage?: (usage: ChatUsageData) => void;
92
108
  };
93
109
  declare function useChat({ api, threadId, agentId, agentVersion, agentExecutionId, dataContext, onError, onAuthError, onFinish, onData, onMessage, onUsage, }: UseChatConfig): {
94
110
  messages: ChatMessage[];
@@ -121,6 +137,7 @@ declare function useChat({ api, threadId, agentId, agentVersion, agentExecutionI
121
137
  addToolApprovalResponse: ai.ChatAddToolApproveResponseFunction;
122
138
  pendingToolOutput: PendingToolOutput | null;
123
139
  pendingToolApproval: PendingToolApproval | null;
140
+ usage: ChatUsageData | null;
124
141
  };
125
142
 
126
143
  /** Matches POST /api/tools/:toolId/invoke request body */
@@ -414,6 +431,8 @@ interface ChatWidgetConfig extends UseChatConfig {
414
431
  widgets?: AnyUIWidgetDefinition[];
415
432
  /** Feedback options for assistant messages. Enabled by default. */
416
433
  feedback?: ChatWidgetFeedbackOptions;
434
+ /** Show a compact input/output token counter (from the server's usage data). Default: false. */
435
+ showTokenUsage?: boolean;
417
436
  }
418
437
  declare const ChatWidget: React.ForwardRefExoticComponent<ChatWidgetConfig & React.RefAttributes<ChatWidgetHandle>>;
419
438
 
@@ -642,4 +661,4 @@ declare const getVendorCardsWidget: (isProd?: boolean) => SDKUIWidgetDefinition[
642
661
 
643
662
  declare function cn(...inputs: ClassValue[]): string;
644
663
 
645
- export { type AgentVersion, type AnyUIWidgetDefinition, type ApiConfig, type ApprovalUIConfig, BriefDisplayMode, BriefDisplayMode as BriefRenderer, type ChatMessage, type ChatMessageSection, ChatWidget, type ChatWidgetFeedbackOptions, type ChatWidgetHandle, type ChatWidgetConfig as ChatWidgetProps, type ChatWidgetSpeechToTextOptions, type ChatWidgetStyles, type ChatWidgetVoiceOptions, type CustomerReactDOM, type DisplayMode, type DisplayModeProps, type EmbeddableChatWidgetConfig, type EnrichedResults, type FeedbackConfig, type FeedbackHandlers, type FetchApprovalConfigOptions, FullDisplayMode, FullDisplayMode as FullRenderer, HideToolsDisplayMode, HideToolsDisplayMode as HideToolsRenderer, type InvokeTool, type MessageContext, MessageRenderer, type MessageRendererProps, type PendingToolApproval, type PendingToolOutput, type PopupAnchorConfig, PopupChatWidget, type PopupChatWidgetProps, type DisplayModeProps as RendererProps, type SDKUIWidgetDefinition, SDK_WIDGET_MARKER, type StructuredMessagePayload, type SubmitFeedbackParams, TagGroupDisplay, type TagGroupDisplayProps, ToolCallDisplay, type ToolCallDisplayProps, type ToolInvokeRequest, type ToolInvokeResult, type ToolPart, type UIMessagePart, type UIWidgetDefinition, type UseChatConfig, type UseFeedbackConfig, type WidgetRenderOptions, cn, createSDKWidget, createWidget, defaultChatWidgets, fetchApprovalConfig, getVendorCardsWidget, isSDKWidget, registerChatWidgets, transformMessage, useChat, useFeedback };
664
+ export { type AgentVersion, type AnyUIWidgetDefinition, type ApiConfig, type ApprovalUIConfig, BriefDisplayMode, BriefDisplayMode as BriefRenderer, type ChatMessage, type ChatMessageSection, type ChatUsageData, ChatWidget, type ChatWidgetFeedbackOptions, type ChatWidgetHandle, type ChatWidgetConfig as ChatWidgetProps, type ChatWidgetSpeechToTextOptions, type ChatWidgetStyles, type ChatWidgetVoiceOptions, type CustomerReactDOM, type DisplayMode, type DisplayModeProps, type EmbeddableChatWidgetConfig, type EnrichedResults, type FeedbackConfig, type FeedbackHandlers, type FetchApprovalConfigOptions, FullDisplayMode, FullDisplayMode as FullRenderer, HideToolsDisplayMode, HideToolsDisplayMode as HideToolsRenderer, type InvokeTool, type MessageContext, MessageRenderer, type MessageRendererProps, type PendingToolApproval, type PendingToolOutput, type PopupAnchorConfig, PopupChatWidget, type PopupChatWidgetProps, type DisplayModeProps as RendererProps, type SDKUIWidgetDefinition, SDK_WIDGET_MARKER, type StructuredMessagePayload, type SubmitFeedbackParams, TagGroupDisplay, type TagGroupDisplayProps, ToolCallDisplay, type ToolCallDisplayProps, type ToolInvokeRequest, type ToolInvokeResult, type ToolPart, type UIMessagePart, type UIWidgetDefinition, type UseChatConfig, type UseFeedbackConfig, type WidgetRenderOptions, cn, createSDKWidget, createWidget, defaultChatWidgets, extractUsageData, fetchApprovalConfig, getVendorCardsWidget, isSDKWidget, registerChatWidgets, transformMessage, useChat, useFeedback };
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ function styleInject(css, { insertAt } = {}) {
21
21
  }
22
22
 
23
23
  // lib/chat/ChatWidget.css
24
- styleInject(':root[data-chat-widget],\n[data-chat-widget] {\n --chat-primary: #3b82f6;\n --chat-primary-text: #ffffff;\n --chat-primary-hover: #2563eb;\n --chat-primary-subtle: rgba(59, 130, 246, 0.1);\n --chat-background: #ffffff;\n --chat-background-secondary: #f9fafb;\n --chat-background-tertiary: #f3f4f6;\n --chat-border: #e5e7eb;\n --chat-border-light: #f3f4f6;\n --chat-widget-border-width: 1px;\n --chat-text: #111827;\n --chat-text-secondary: #4b5563;\n --chat-text-muted: #6b7280;\n --chat-text-disabled: #9ca3af;\n --chat-link: #2563eb;\n --chat-link-hover: #1d4ed8;\n --chat-success: #22c55e;\n --chat-success-bg: rgba(34, 197, 94, 0.1);\n --chat-error: #ef4444;\n --chat-error-bg: rgba(239, 68, 68, 0.1);\n --chat-header-text: #ffffff;\n --chat-user-text: #ffffff;\n --chat-assistant-bg: transparent;\n --chat-assistant-text: var(--chat-text);\n --chat-input-bg: var(--chat-background);\n --chat-input-text: var(--chat-text);\n --chat-input-placeholder: var(--chat-text-muted);\n --chat-thought-border: var(--chat-border);\n --chat-font-family:\n "Inter",\n system-ui,\n -apple-system,\n BlinkMacSystemFont,\n sans-serif;\n --chat-radius: 0.75rem;\n --chat-font-size: 0.925rem;\n --chat-thought-font-size: 0.75rem;\n --chat-shadow: 0 20px 45px rgba(0, 0, 0, 0.12);\n --chat-shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.1);\n --chat-overlay-bg: rgba(0, 0, 0, 0.3);\n}\n.chat-widget {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 24rem;\n background: var(--chat-background);\n border: var(--chat-widget-border-width) solid var(--chat-border);\n border-radius: 0;\n box-shadow: var(--chat-shadow);\n overflow: hidden;\n font-family: var(--chat-font-family);\n color: var(--chat-assistant-text);\n}\n.chat-widget--popup-content {\n border: 0;\n border-radius: inherit;\n box-shadow: none;\n min-height: 0;\n}\n.chat-widget__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n height: 40px;\n padding: 0.75rem 1rem;\n background: var(--chat-primary);\n color: var(--chat-header-text);\n}\n.chat-widget__title {\n margin: 0;\n font-size: 0.875rem;\n font-weight: 600;\n}\n.chat-widget__messages {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n min-width: 0;\n}\n.chat-widget__message {\n display: flex;\n flex-direction: column;\n gap: 0;\n min-width: 0;\n}\n.chat-widget__message-row {\n display: flex;\n min-width: 0;\n}\n.chat-widget__message-row--user {\n justify-content: flex-end;\n}\n.chat-widget__message-row--assistant {\n justify-content: flex-start;\n}\n.chat-widget__bubble {\n max-width: 90%;\n padding: 0.5rem 0.25rem;\n border-radius: calc(var(--chat-radius) * 0.65);\n font-size: var(--chat-font-size);\n min-width: 0;\n overflow-x: hidden;\n}\n.chat-widget__bubble--user {\n background: var(--chat-primary);\n color: var(--chat-user-text);\n border-bottom-right-radius: 0.35rem;\n padding-left: 0.375rem;\n padding-right: 0.375rem;\n}\n.chat-widget__bubble--assistant {\n width: 90%;\n max-width: 90%;\n background: var(--chat-assistant-bg);\n color: var(--chat-assistant-text);\n border-bottom-left-radius: 0.35rem;\n}\n.chat-widget__widget {\n width: 100%;\n max-width: 100%;\n min-width: 0;\n overflow-x: hidden;\n}\n.widget-customer-root {\n width: 100%;\n max-width: 100%;\n min-width: 0;\n overflow-x: hidden;\n}\n.chat-widget__markdown {\n white-space: normal;\n font-size: var(--chat-font-size);\n}\n.chat-widget__markdown h1 {\n font-size: calc(var(--chat-font-size) * 1.4);\n}\n.chat-widget__markdown h2 {\n font-size: calc(var(--chat-font-size) * 1.25);\n}\n.chat-widget__markdown h3 {\n font-size: calc(var(--chat-font-size) * 1.1);\n}\n.chat-widget__markdown h4 {\n font-size: calc(var(--chat-font-size) * 1.05);\n}\n.chat-widget__markdown h5 {\n font-size: calc(var(--chat-font-size) * 1);\n}\n.chat-widget__markdown h6 {\n font-size: calc(var(--chat-font-size) * 0.95);\n}\n.chat-widget__anchor-button {\n position: fixed;\n bottom: 20px;\n right: 20px;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.65rem 0.85rem;\n background: var(--chat-primary);\n color: var(--chat-primary-text);\n border: none;\n border-radius: 999px;\n box-shadow: var(--chat-shadow);\n cursor: pointer;\n font-weight: 600;\n z-index: 99999;\n}\n.chat-widget__anchor-button:hover {\n background: var(--chat-primary-hover);\n}\n.chat-widget__anchor--left {\n right: auto;\n left: 20px;\n}\n.chat-widget__anchor-icon {\n width: 1.1rem;\n height: 1.1rem;\n}\n.chat-widget__popup {\n position: fixed;\n bottom: 80px;\n right: 20px;\n z-index: 99998;\n}\n.chat-widget__anchor--left.chat-widget__popup {\n right: auto;\n left: 20px;\n}\n.chat-widget__popup-inner {\n width: 384px;\n max-width: calc(100vw - 40px);\n height: 500px;\n max-height: calc(100vh - 140px);\n box-shadow: var(--chat-shadow);\n border-radius: 12px;\n overflow: hidden;\n}\n.chat-widget__markdown a {\n color: var(--chat-link);\n text-decoration: underline;\n}\n.chat-widget__markdown a:hover {\n color: var(--chat-link-hover);\n}\n.chat-widget__markdown table {\n width: 100%;\n border-collapse: collapse;\n margin: 0.5rem 0;\n}\n.chat-widget__markdown th,\n.chat-widget__markdown td {\n border: 1px solid var(--chat-border);\n padding: 0.5rem;\n text-align: left;\n vertical-align: top;\n}\n.chat-widget__markdown thead th {\n background: var(--chat-assistant-bg);\n color: var(--chat-assistant-text);\n font-weight: 600;\n}\n.chat-widget__thoughts {\n min-width: 0;\n --chat-font-size: var(--chat-thought-font-size);\n}\n.chat-widget__details {\n border: 0;\n}\n.chat-widget__thoughts-summary {\n list-style: none;\n display: flex;\n align-items: center;\n gap: 0.35rem;\n cursor: pointer;\n color: var(--chat-text-muted);\n font-size: 0.8125rem;\n}\n.chat-widget__thoughts-summary:hover {\n color: var(--chat-assistant-text);\n}\n.chat-widget__thoughts-icon-container {\n display: flex;\n border: 1px solid var(--chat-border);\n border-radius: 999px;\n align-items: center;\n justify-content: center;\n width: 1rem;\n height: 1rem;\n}\n.chat-widget__thoughts-icon {\n width: 0.75rem;\n height: 0.75rem;\n transition: transform 0.2s ease;\n}\n.chat-widget__details[open] .chat-widget__thoughts-icon {\n transform: rotate(90deg);\n}\n.chat-widget__thoughts-content {\n margin-top: 0.5rem;\n margin-left: 0.4rem;\n padding: 0.75rem;\n border-left: 2px solid var(--chat-thought-border);\n border-color: var(--chat-primary);\n max-width: 100%;\n overflow-x: auto;\n}\n.chat-widget__thoughts-text {\n margin: 0;\n line-height: 1.5;\n color: var(--chat-text-secondary);\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n "Liberation Mono",\n "Courier New",\n monospace;\n white-space: pre-wrap;\n word-break: break-word;\n overflow-wrap: anywhere;\n display: block;\n}\n.chat-widget__loading {\n display: flex;\n justify-content: flex-start;\n}\n.chat-widget__loading-bubble {\n background: var(--chat-assistant-bg);\n color: var(--chat-assistant-text);\n border-radius: calc(var(--chat-radius) * 0.65);\n border-bottom-left-radius: 0.35rem;\n padding: 0.5rem 0.75rem;\n}\n.chat-widget__loading-dots {\n display: flex;\n gap: 0.35rem;\n}\n.chat-widget__loading-dot {\n width: 0.5rem;\n height: 0.5rem;\n border-radius: 999px;\n background: var(--chat-text-disabled);\n animation: chat-widget-bounce 1s infinite ease-in-out;\n}\n.chat-widget__loading-dot[data-delay="1"] {\n animation-delay: 0.1s;\n}\n.chat-widget__loading-dot[data-delay="2"] {\n animation-delay: 0.2s;\n}\n@keyframes chat-widget-bounce {\n 0%, 80%, 100% {\n transform: scale(0.6);\n opacity: 0.4;\n }\n 40% {\n transform: scale(1);\n opacity: 1;\n }\n}\n.chat-widget__input {\n border-top: 1px solid var(--chat-border);\n padding: 0.25rem 0 0.75rem;\n background: var(--chat-input-bg);\n}\n.chat-widget__form {\n position: relative;\n}\n.chat-widget__textarea {\n width: 100%;\n border: 0;\n padding: 0.75rem 5.25rem 0.75rem 0.75rem;\n background: transparent;\n font-size: 0.925rem;\n resize: none;\n color: var(--chat-input-text);\n font-family: inherit;\n}\n.chat-widget__textarea::-moz-placeholder {\n color: var(--chat-input-placeholder);\n}\n.chat-widget__textarea::placeholder {\n color: var(--chat-input-placeholder);\n}\n.chat-widget__textarea:focus-visible {\n outline: none;\n}\n.chat-widget__textarea:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.chat-widget__toolbar {\n position: absolute;\n top: 0;\n right: 3.75rem;\n height: 100%;\n display: flex;\n align-items: center;\n padding: 0 0.5rem;\n border-right: 1px solid var(--chat-border);\n}\n.chat-widget__submit {\n position: absolute;\n top: 50%;\n right: 0.75rem;\n transform: translateY(-50%);\n background: transparent;\n border: 0;\n width: 2.5rem;\n height: 2.5rem;\n display: grid;\n place-items: center;\n color: var(--chat-primary);\n cursor: pointer;\n}\n.chat-widget__submit:disabled {\n color: var(--chat-text-disabled);\n cursor: not-allowed;\n}\n.chat-widget__submit:not(:disabled):hover {\n color: var(--chat-primary-hover);\n}\n.chat-widget__send-icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n.chat-widget__mic-button {\n position: relative;\n border: none;\n background: transparent;\n width: 2.25rem;\n height: 2.25rem;\n display: grid;\n place-items: center;\n color: var(--chat-primary);\n cursor: pointer;\n border-radius: 999px;\n}\n.chat-widget__mic-button:disabled {\n color: var(--chat-text-disabled);\n cursor: not-allowed;\n}\n.chat-widget__mic-button--active {\n color: var(--chat-primary-hover);\n background: var(--chat-primary-subtle);\n}\n.chat-widget__mic-icon {\n width: 1rem;\n height: 1rem;\n}\n.chat-widget__mic-bar {\n position: absolute;\n bottom: 0.1rem;\n left: 50%;\n width: 16px;\n height: 2px;\n border-radius: 999px;\n background: var(--chat-border);\n overflow: hidden;\n display: block;\n transform: translateX(-50%);\n pointer-events: none;\n}\n.chat-widget__mic-bar-fill {\n display: block;\n width: 100%;\n height: 100%;\n background: currentColor;\n transform-origin: left;\n transition: transform 0.12s linear;\n}\n.chat-widget__voice {\n display: flex;\n margin-top: 0.25rem;\n}\n.chat-widget__voice-button {\n border: none;\n background: none;\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n padding: 0.1rem 0;\n font-size: 0.8rem;\n font-weight: 500;\n color: var(--chat-primary);\n cursor: pointer;\n}\n.chat-widget__voice-button:disabled {\n color: var(--chat-text-disabled);\n cursor: not-allowed;\n}\n.chat-widget__voice-button--active {\n color: var(--chat-primary-hover);\n}\n.chat-widget__voice-icon {\n width: 0.95rem;\n height: 0.95rem;\n}\n.chat-widget__voice-icon--spin {\n animation: chat-widget-spin 1s linear infinite;\n}\n@keyframes chat-widget-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n.chat-widget__collapsed {\n position: relative;\n margin-bottom: 0.25rem;\n}\n.chat-widget__collapsed-content {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 300ms ease-out;\n}\n.chat-widget__collapsed-content--expanded {\n grid-template-rows: 1fr;\n}\n.chat-widget__collapsed-content-inner {\n overflow: hidden;\n}\n.chat-widget__collapsed-preview {\n position: relative;\n max-height: 1.5em;\n overflow: hidden;\n opacity: 0.6;\n pointer-events: none;\n transition: opacity 300ms ease-out, max-height 300ms ease-out;\n}\n.chat-widget__collapsed-preview--hidden {\n opacity: 0;\n max-height: 0;\n pointer-events: none;\n}\n.chat-widget__collapsed-gradient {\n position: absolute;\n top: 0;\n right: 0;\n width: 70%;\n height: 100%;\n background:\n linear-gradient(\n to right,\n transparent 0%,\n var(--chat-background) 90%);\n pointer-events: none;\n}\n.chat-widget__collapsed-toggle {\n display: inline-block;\n padding: 0;\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--chat-primary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.chat-widget__collapsed-toggle:hover {\n text-decoration: underline;\n}\n@keyframes chat-widget-slide-up {\n from {\n opacity: 1;\n transform: translateY(0);\n max-height: 200px;\n }\n to {\n opacity: 0.6;\n transform: translateY(-8px);\n max-height: 1.5em;\n }\n}\n.chat-widget__collapsed--animating .chat-widget__collapsed-preview {\n animation: chat-widget-slide-up 300ms ease-out forwards;\n}\n');
24
+ styleInject(':root[data-chat-widget],\n[data-chat-widget] {\n --chat-primary: #3b82f6;\n --chat-primary-text: #ffffff;\n --chat-primary-hover: #2563eb;\n --chat-primary-subtle: rgba(59, 130, 246, 0.1);\n --chat-background: #ffffff;\n --chat-background-secondary: #f9fafb;\n --chat-background-tertiary: #f3f4f6;\n --chat-border: #e5e7eb;\n --chat-border-light: #f3f4f6;\n --chat-widget-border-width: 1px;\n --chat-text: #111827;\n --chat-text-secondary: #4b5563;\n --chat-text-muted: #6b7280;\n --chat-text-disabled: #9ca3af;\n --chat-link: #2563eb;\n --chat-link-hover: #1d4ed8;\n --chat-success: #22c55e;\n --chat-success-bg: rgba(34, 197, 94, 0.1);\n --chat-error: #ef4444;\n --chat-error-bg: rgba(239, 68, 68, 0.1);\n --chat-header-text: #ffffff;\n --chat-user-text: #ffffff;\n --chat-assistant-bg: transparent;\n --chat-assistant-text: var(--chat-text);\n --chat-input-bg: var(--chat-background);\n --chat-input-text: var(--chat-text);\n --chat-input-placeholder: var(--chat-text-muted);\n --chat-thought-border: var(--chat-border);\n --chat-font-family:\n "Inter",\n system-ui,\n -apple-system,\n BlinkMacSystemFont,\n sans-serif;\n --chat-radius: 0.75rem;\n --chat-font-size: 0.925rem;\n --chat-thought-font-size: 0.75rem;\n --chat-shadow: 0 20px 45px rgba(0, 0, 0, 0.12);\n --chat-shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.1);\n --chat-overlay-bg: rgba(0, 0, 0, 0.3);\n}\n.chat-widget {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 24rem;\n background: var(--chat-background);\n border: var(--chat-widget-border-width) solid var(--chat-border);\n border-radius: 0;\n box-shadow: var(--chat-shadow);\n overflow: hidden;\n font-family: var(--chat-font-family);\n color: var(--chat-assistant-text);\n}\n.chat-widget--popup-content {\n border: 0;\n border-radius: inherit;\n box-shadow: none;\n min-height: 0;\n}\n.chat-widget__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n height: 40px;\n padding: 0.75rem 1rem;\n background: var(--chat-primary);\n color: var(--chat-header-text);\n}\n.chat-widget__title {\n margin: 0;\n font-size: 0.875rem;\n font-weight: 600;\n}\n.chat-widget__messages {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n min-width: 0;\n}\n.chat-widget__message {\n display: flex;\n flex-direction: column;\n gap: 0;\n min-width: 0;\n}\n.chat-widget__message-row {\n display: flex;\n min-width: 0;\n}\n.chat-widget__message-row--user {\n justify-content: flex-end;\n}\n.chat-widget__message-row--assistant {\n justify-content: flex-start;\n}\n.chat-widget__bubble {\n max-width: 90%;\n padding: 0.5rem 0.25rem;\n border-radius: calc(var(--chat-radius) * 0.65);\n font-size: var(--chat-font-size);\n min-width: 0;\n overflow-x: hidden;\n}\n.chat-widget__bubble--user {\n background: var(--chat-primary);\n color: var(--chat-user-text);\n border-bottom-right-radius: 0.35rem;\n padding-left: 0.375rem;\n padding-right: 0.375rem;\n}\n.chat-widget__bubble--assistant {\n width: 90%;\n max-width: 90%;\n background: var(--chat-assistant-bg);\n color: var(--chat-assistant-text);\n border-bottom-left-radius: 0.35rem;\n}\n.chat-widget__widget {\n width: 100%;\n max-width: 100%;\n min-width: 0;\n overflow-x: hidden;\n}\n.widget-customer-root {\n width: 100%;\n max-width: 100%;\n min-width: 0;\n overflow-x: hidden;\n}\n.chat-widget__markdown {\n white-space: normal;\n font-size: var(--chat-font-size);\n}\n.chat-widget__markdown h1 {\n font-size: calc(var(--chat-font-size) * 1.4);\n}\n.chat-widget__markdown h2 {\n font-size: calc(var(--chat-font-size) * 1.25);\n}\n.chat-widget__markdown h3 {\n font-size: calc(var(--chat-font-size) * 1.1);\n}\n.chat-widget__markdown h4 {\n font-size: calc(var(--chat-font-size) * 1.05);\n}\n.chat-widget__markdown h5 {\n font-size: calc(var(--chat-font-size) * 1);\n}\n.chat-widget__markdown h6 {\n font-size: calc(var(--chat-font-size) * 0.95);\n}\n.chat-widget__anchor-button {\n position: fixed;\n bottom: 20px;\n right: 20px;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.65rem 0.85rem;\n background: var(--chat-primary);\n color: var(--chat-primary-text);\n border: none;\n border-radius: 999px;\n box-shadow: var(--chat-shadow);\n cursor: pointer;\n font-weight: 600;\n z-index: 99999;\n}\n.chat-widget__anchor-button:hover {\n background: var(--chat-primary-hover);\n}\n.chat-widget__anchor--left {\n right: auto;\n left: 20px;\n}\n.chat-widget__anchor-icon {\n width: 1.1rem;\n height: 1.1rem;\n}\n.chat-widget__popup {\n position: fixed;\n bottom: 80px;\n right: 20px;\n z-index: 99998;\n}\n.chat-widget__anchor--left.chat-widget__popup {\n right: auto;\n left: 20px;\n}\n.chat-widget__popup-inner {\n width: 384px;\n max-width: calc(100vw - 40px);\n height: 500px;\n max-height: calc(100vh - 140px);\n box-shadow: var(--chat-shadow);\n border-radius: 12px;\n overflow: hidden;\n}\n.chat-widget__markdown a {\n color: var(--chat-link);\n text-decoration: underline;\n}\n.chat-widget__markdown a:hover {\n color: var(--chat-link-hover);\n}\n.chat-widget__markdown table {\n width: 100%;\n border-collapse: collapse;\n margin: 0.5rem 0;\n}\n.chat-widget__markdown th,\n.chat-widget__markdown td {\n border: 1px solid var(--chat-border);\n padding: 0.5rem;\n text-align: left;\n vertical-align: top;\n}\n.chat-widget__markdown thead th {\n background: var(--chat-assistant-bg);\n color: var(--chat-assistant-text);\n font-weight: 600;\n}\n.chat-widget__thoughts {\n min-width: 0;\n --chat-font-size: var(--chat-thought-font-size);\n}\n.chat-widget__details {\n border: 0;\n}\n.chat-widget__thoughts-summary {\n list-style: none;\n display: flex;\n align-items: center;\n gap: 0.35rem;\n cursor: pointer;\n color: var(--chat-text-muted);\n font-size: 0.8125rem;\n}\n.chat-widget__thoughts-summary:hover {\n color: var(--chat-assistant-text);\n}\n.chat-widget__thoughts-icon-container {\n display: flex;\n border: 1px solid var(--chat-border);\n border-radius: 999px;\n align-items: center;\n justify-content: center;\n width: 1rem;\n height: 1rem;\n}\n.chat-widget__thoughts-icon {\n width: 0.75rem;\n height: 0.75rem;\n transition: transform 0.2s ease;\n}\n.chat-widget__details[open] .chat-widget__thoughts-icon {\n transform: rotate(90deg);\n}\n.chat-widget__thoughts-content {\n margin-top: 0.5rem;\n margin-left: 0.4rem;\n padding: 0.75rem;\n border-left: 2px solid var(--chat-thought-border);\n border-color: var(--chat-primary);\n max-width: 100%;\n overflow-x: auto;\n}\n.chat-widget__thoughts-text {\n margin: 0;\n line-height: 1.5;\n color: var(--chat-text-secondary);\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n "Liberation Mono",\n "Courier New",\n monospace;\n white-space: pre-wrap;\n word-break: break-word;\n overflow-wrap: anywhere;\n display: block;\n}\n.chat-widget__loading {\n display: flex;\n justify-content: flex-start;\n}\n.chat-widget__loading-bubble {\n background: var(--chat-assistant-bg);\n color: var(--chat-assistant-text);\n border-radius: calc(var(--chat-radius) * 0.65);\n border-bottom-left-radius: 0.35rem;\n padding: 0.5rem 0.75rem;\n}\n.chat-widget__loading-dots {\n display: flex;\n gap: 0.35rem;\n}\n.chat-widget__loading-dot {\n width: 0.5rem;\n height: 0.5rem;\n border-radius: 999px;\n background: var(--chat-text-disabled);\n animation: chat-widget-bounce 1s infinite ease-in-out;\n}\n.chat-widget__loading-dot[data-delay="1"] {\n animation-delay: 0.1s;\n}\n.chat-widget__loading-dot[data-delay="2"] {\n animation-delay: 0.2s;\n}\n@keyframes chat-widget-bounce {\n 0%, 80%, 100% {\n transform: scale(0.6);\n opacity: 0.4;\n }\n 40% {\n transform: scale(1);\n opacity: 1;\n }\n}\n.chat-widget__token-usage {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n padding: 0.25rem 0.25rem 0;\n font-size: 0.6875rem;\n line-height: 1.2;\n color: var(--chat-text-muted);\n}\n.chat-widget__token-usage-item {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.chat-widget__input {\n border-top: 1px solid var(--chat-border);\n padding: 0.25rem 0 0.75rem;\n background: var(--chat-input-bg);\n}\n.chat-widget__form {\n position: relative;\n}\n.chat-widget__textarea {\n width: 100%;\n border: 0;\n padding: 0.75rem 5.25rem 0.75rem 0.75rem;\n background: transparent;\n font-size: 0.925rem;\n resize: none;\n color: var(--chat-input-text);\n font-family: inherit;\n}\n.chat-widget__textarea::-moz-placeholder {\n color: var(--chat-input-placeholder);\n}\n.chat-widget__textarea::placeholder {\n color: var(--chat-input-placeholder);\n}\n.chat-widget__textarea:focus-visible {\n outline: none;\n}\n.chat-widget__textarea:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.chat-widget__toolbar {\n position: absolute;\n top: 0;\n right: 3.75rem;\n height: 100%;\n display: flex;\n align-items: center;\n padding: 0 0.5rem;\n border-right: 1px solid var(--chat-border);\n}\n.chat-widget__submit {\n position: absolute;\n top: 50%;\n right: 0.75rem;\n transform: translateY(-50%);\n background: transparent;\n border: 0;\n width: 2.5rem;\n height: 2.5rem;\n display: grid;\n place-items: center;\n color: var(--chat-primary);\n cursor: pointer;\n}\n.chat-widget__submit:disabled {\n color: var(--chat-text-disabled);\n cursor: not-allowed;\n}\n.chat-widget__submit:not(:disabled):hover {\n color: var(--chat-primary-hover);\n}\n.chat-widget__send-icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n.chat-widget__mic-button {\n position: relative;\n border: none;\n background: transparent;\n width: 2.25rem;\n height: 2.25rem;\n display: grid;\n place-items: center;\n color: var(--chat-primary);\n cursor: pointer;\n border-radius: 999px;\n}\n.chat-widget__mic-button:disabled {\n color: var(--chat-text-disabled);\n cursor: not-allowed;\n}\n.chat-widget__mic-button--active {\n color: var(--chat-primary-hover);\n background: var(--chat-primary-subtle);\n}\n.chat-widget__mic-icon {\n width: 1rem;\n height: 1rem;\n}\n.chat-widget__mic-bar {\n position: absolute;\n bottom: 0.1rem;\n left: 50%;\n width: 16px;\n height: 2px;\n border-radius: 999px;\n background: var(--chat-border);\n overflow: hidden;\n display: block;\n transform: translateX(-50%);\n pointer-events: none;\n}\n.chat-widget__mic-bar-fill {\n display: block;\n width: 100%;\n height: 100%;\n background: currentColor;\n transform-origin: left;\n transition: transform 0.12s linear;\n}\n.chat-widget__voice {\n display: flex;\n margin-top: 0.25rem;\n}\n.chat-widget__voice-button {\n border: none;\n background: none;\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n padding: 0.1rem 0;\n font-size: 0.8rem;\n font-weight: 500;\n color: var(--chat-primary);\n cursor: pointer;\n}\n.chat-widget__voice-button:disabled {\n color: var(--chat-text-disabled);\n cursor: not-allowed;\n}\n.chat-widget__voice-button--active {\n color: var(--chat-primary-hover);\n}\n.chat-widget__voice-icon {\n width: 0.95rem;\n height: 0.95rem;\n}\n.chat-widget__voice-icon--spin {\n animation: chat-widget-spin 1s linear infinite;\n}\n@keyframes chat-widget-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n.chat-widget__collapsed {\n position: relative;\n margin-bottom: 0.25rem;\n}\n.chat-widget__collapsed-content {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 300ms ease-out;\n}\n.chat-widget__collapsed-content--expanded {\n grid-template-rows: 1fr;\n}\n.chat-widget__collapsed-content-inner {\n overflow: hidden;\n}\n.chat-widget__collapsed-preview {\n position: relative;\n max-height: 1.5em;\n overflow: hidden;\n opacity: 0.6;\n pointer-events: none;\n transition: opacity 300ms ease-out, max-height 300ms ease-out;\n}\n.chat-widget__collapsed-preview--hidden {\n opacity: 0;\n max-height: 0;\n pointer-events: none;\n}\n.chat-widget__collapsed-gradient {\n position: absolute;\n top: 0;\n right: 0;\n width: 70%;\n height: 100%;\n background:\n linear-gradient(\n to right,\n transparent 0%,\n var(--chat-background) 90%);\n pointer-events: none;\n}\n.chat-widget__collapsed-toggle {\n display: inline-block;\n padding: 0;\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--chat-primary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.chat-widget__collapsed-toggle:hover {\n text-decoration: underline;\n}\n@keyframes chat-widget-slide-up {\n from {\n opacity: 1;\n transform: translateY(0);\n max-height: 200px;\n }\n to {\n opacity: 0.6;\n transform: translateY(-8px);\n max-height: 1.5em;\n }\n}\n.chat-widget__collapsed--animating .chat-widget__collapsed-preview {\n animation: chat-widget-slide-up 300ms ease-out forwards;\n}\n');
25
25
 
26
26
  // lib/chat/ChatWidget.tsx
27
27
  import { Send, StopCircle } from "lucide-react";
@@ -4217,6 +4217,15 @@ function createAuthAwareFetch(tokenRef, onAuthError) {
4217
4217
  return response;
4218
4218
  };
4219
4219
  }
4220
+ function extractUsageData(dataPart) {
4221
+ if (dataPart && typeof dataPart === "object") {
4222
+ const part = dataPart;
4223
+ if (part.type === "data-usage" && part.data) {
4224
+ return part.data;
4225
+ }
4226
+ }
4227
+ return null;
4228
+ }
4220
4229
  var EMPTY_HEADERS = {};
4221
4230
  function useChat({
4222
4231
  api,
@@ -4295,11 +4304,10 @@ function useChat({
4295
4304
  },
4296
4305
  onData: (dataPart) => {
4297
4306
  onData?.(dataPart);
4298
- if (dataPart && typeof dataPart === "object") {
4299
- const part = dataPart;
4300
- if (part.type === "data-usage" && part.data) {
4301
- onUsage?.(part.data);
4302
- }
4307
+ const usageData = extractUsageData(dataPart);
4308
+ if (usageData) {
4309
+ setUsage(usageData);
4310
+ onUsage?.(usageData);
4303
4311
  }
4304
4312
  }
4305
4313
  });
@@ -4320,6 +4328,7 @@ function useChat({
4320
4328
  [sendMessage]
4321
4329
  );
4322
4330
  const [isLoadingInitial, setIsLoadingInitial] = useState(true);
4331
+ const [usage, setUsage] = useState(null);
4323
4332
  const hasFetchedRef = useRef(false);
4324
4333
  useEffect(() => {
4325
4334
  hasFetchedRef.current = false;
@@ -4416,7 +4425,8 @@ function useChat({
4416
4425
  addToolOutput,
4417
4426
  addToolApprovalResponse,
4418
4427
  pendingToolOutput,
4419
- pendingToolApproval
4428
+ pendingToolApproval,
4429
+ usage
4420
4430
  };
4421
4431
  }
4422
4432
 
@@ -9594,6 +9604,8 @@ var classNames2 = {
9594
9604
  header: "chat-widget__header",
9595
9605
  headerTitle: "chat-widget__title",
9596
9606
  messagesContainer: "chat-widget__messages",
9607
+ tokenUsage: "chat-widget__token-usage",
9608
+ tokenUsageItem: "chat-widget__token-usage-item",
9597
9609
  messageWrapper: "chat-widget__message",
9598
9610
  messageRow: "chat-widget__message-row",
9599
9611
  userMessageRow: "chat-widget__message-row--user",
@@ -9718,6 +9730,8 @@ var ChatWidget = forwardRef(function ChatWidget2({
9718
9730
  onAuthError,
9719
9731
  onFinish,
9720
9732
  onMessage,
9733
+ onUsage,
9734
+ onData,
9721
9735
  title,
9722
9736
  initialAssistantMessage,
9723
9737
  initialUserMessage,
@@ -9727,7 +9741,8 @@ var ChatWidget = forwardRef(function ChatWidget2({
9727
9741
  voice,
9728
9742
  speechToText,
9729
9743
  widgets,
9730
- feedback
9744
+ feedback,
9745
+ showTokenUsage = false
9731
9746
  }, ref) {
9732
9747
  const [detectedDarkMode, setDetectedDarkMode] = useState9(() => detectChatWidgetDarkMode());
9733
9748
  useEffect6(() => {
@@ -9809,7 +9824,8 @@ var ChatWidget = forwardRef(function ChatWidget2({
9809
9824
  addToolOutput,
9810
9825
  addToolApprovalResponse,
9811
9826
  pendingToolOutput,
9812
- pendingToolApproval
9827
+ pendingToolApproval,
9828
+ usage
9813
9829
  } = useChat({
9814
9830
  api,
9815
9831
  threadId,
@@ -9820,7 +9836,9 @@ var ChatWidget = forwardRef(function ChatWidget2({
9820
9836
  onError,
9821
9837
  onAuthError,
9822
9838
  onFinish,
9823
- onMessage
9839
+ onMessage,
9840
+ onUsage,
9841
+ onData
9824
9842
  });
9825
9843
  const handleToolOutput = useCallback7((result) => {
9826
9844
  if (pendingToolOutput && addToolOutput) {
@@ -10121,6 +10139,18 @@ var ChatWidget = forwardRef(function ChatWidget2({
10121
10139
  ]
10122
10140
  }
10123
10141
  ),
10142
+ showTokenUsage && usage && /* @__PURE__ */ jsxs15("div", { className: classNames2.tokenUsage, "aria-label": "Token usage", children: [
10143
+ /* @__PURE__ */ jsxs15("span", { className: classNames2.tokenUsageItem, title: "Input tokens", children: [
10144
+ "\u2191 ",
10145
+ usage.inputTokens.toLocaleString(),
10146
+ " in"
10147
+ ] }),
10148
+ /* @__PURE__ */ jsxs15("span", { className: classNames2.tokenUsageItem, title: "Output tokens", children: [
10149
+ "\u2193 ",
10150
+ usage.outputTokens.toLocaleString(),
10151
+ " out"
10152
+ ] })
10153
+ ] }),
10124
10154
  /* @__PURE__ */ jsx18("div", { className: classNames2.inputContainer, children: /* @__PURE__ */ jsxs15("form", { onSubmit: handleSubmit, className: classNames2.inputForm, children: [
10125
10155
  /* @__PURE__ */ jsx18(
10126
10156
  "textarea",
@@ -10932,6 +10962,7 @@ export {
10932
10962
  createSDKWidget,
10933
10963
  createWidget,
10934
10964
  defaultChatWidgets,
10965
+ extractUsageData,
10935
10966
  fetchApprovalConfig,
10936
10967
  getVendorCardsWidget,
10937
10968
  isSDKWidget,