@sendbird/ai-agent-messenger-react 1.32.0 → 1.34.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.cjs +184 -184
- package/dist/index.d.ts +1613 -98
- package/dist/index.js +8012 -5822
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -331,7 +331,7 @@ declare abstract class AIAgentBaseStats {
|
|
|
331
331
|
const existing = this.timers.get(key);
|
|
332
332
|
if (existing?.startTime !== null && existing?.startTime !== undefined) return this;
|
|
333
333
|
|
|
334
|
-
this.timers.set(key, { startTime:
|
|
334
|
+
this.timers.set(key, { startTime: defaultStatsClock.now(), endTime: null });
|
|
335
335
|
return this;
|
|
336
336
|
}
|
|
337
337
|
|
|
@@ -341,7 +341,7 @@ declare abstract class AIAgentBaseStats {
|
|
|
341
341
|
const existing = this.timers.get(key);
|
|
342
342
|
if (!existing || existing.startTime === null || existing.endTime !== null) return this;
|
|
343
343
|
|
|
344
|
-
this.timers.set(key, { ...existing, endTime:
|
|
344
|
+
this.timers.set(key, { ...existing, endTime: defaultStatsClock.now() });
|
|
345
345
|
return this;
|
|
346
346
|
}
|
|
347
347
|
|
|
@@ -396,6 +396,12 @@ declare interface AIAgentClientHandlers {
|
|
|
396
396
|
* `Linking.openURL` on React Native.
|
|
397
397
|
*/
|
|
398
398
|
onClickLink?: (params: { url: string }) => void;
|
|
399
|
+
/**
|
|
400
|
+
* Called whenever the SDK emits a live metric — connection, conversation lifecycle,
|
|
401
|
+
* handoff, and message events. Use for real-time analytics. Implementations should be
|
|
402
|
+
* efficient and non-blocking, as this may be called frequently.
|
|
403
|
+
*/
|
|
404
|
+
onLiveMetric?: (metric: LiveMetric) => void;
|
|
399
405
|
}
|
|
400
406
|
|
|
401
407
|
declare type AIAgentCommonIconName =
|
|
@@ -719,10 +725,11 @@ declare interface AIAgentInterface {
|
|
|
719
725
|
/** @internal Agent version for conversation initialization */
|
|
720
726
|
agentVersion?: number;
|
|
721
727
|
|
|
722
|
-
/** @internal
|
|
723
|
-
readonly
|
|
724
|
-
|
|
725
|
-
|
|
728
|
+
/** @internal Schedules metrics work that should only start while the app is foregrounded. */
|
|
729
|
+
readonly scheduleForegroundTask?: ForegroundTaskScheduler;
|
|
730
|
+
|
|
731
|
+
/** @internal Stats trackers owned by this SDK instance. */
|
|
732
|
+
readonly statsTrackers: AIAgentStatsTrackers;
|
|
726
733
|
|
|
727
734
|
/** Authenticates with the AI Agent server and establishes a session */
|
|
728
735
|
authenticate: (sessionInfo: ManualSessionInfo | AnonymousSessionInfo) => Promise<MessengerSettingsResponse>;
|
|
@@ -754,6 +761,12 @@ declare interface AIAgentInterface {
|
|
|
754
761
|
/** @internal Initializes a conversation on the channel if needed (AI Agent channel without conversation). */
|
|
755
762
|
initConversationIfNeeded: (channel: ChatSDKGroupChannel) => Promise<void>;
|
|
756
763
|
|
|
764
|
+
/** @internal Live metric handler; bridged from {@link AIAgentClientHandlers.onLiveMetric}. */
|
|
765
|
+
onLiveMetric?: LiveMetricDispatch;
|
|
766
|
+
|
|
767
|
+
/** @internal Live metric manager owned by this SDK instance. */
|
|
768
|
+
readonly liveMetricManager: LiveMetricManager;
|
|
769
|
+
|
|
757
770
|
/** Disposes all resources held by this instance (WebSocket, desk auth, event handlers). */
|
|
758
771
|
dispose: () => void;
|
|
759
772
|
|
|
@@ -797,12 +810,6 @@ declare interface AIAgentMessengerSessionContextValue {
|
|
|
797
810
|
deauthenticate: () => Promise<void>;
|
|
798
811
|
|
|
799
812
|
createAttachmentRules: (params: { channel?: ChatSDKGroupChannel; uploadSizeLimit?: number }) => AttachmentRules;
|
|
800
|
-
/**
|
|
801
|
-
* @internal
|
|
802
|
-
*/
|
|
803
|
-
statsTrackers: {
|
|
804
|
-
initialRender: ConversationInitialRenderStatsTracker;
|
|
805
|
-
};
|
|
806
813
|
}
|
|
807
814
|
|
|
808
815
|
declare interface AIAgentMessengerSessionRef {
|
|
@@ -1070,6 +1077,23 @@ declare interface AIAgentStatPayload {
|
|
|
1070
1077
|
extra?: Record<string, unknown>;
|
|
1071
1078
|
}
|
|
1072
1079
|
|
|
1080
|
+
declare type AIAgentStatsTrackerCollection = {
|
|
1081
|
+
conversationInitialRender: ConversationInitialRenderStatsTracker;
|
|
1082
|
+
conversationNotInteractiveTimeout: ConversationNotInteractiveTimeoutStatsTracker;
|
|
1083
|
+
recoveryNotInteractiveTimeout: RecoveryNotInteractiveTimeoutStatsTracker;
|
|
1084
|
+
conversationListInitialRender: ConversationListInitialRenderStatsTracker;
|
|
1085
|
+
aievAuthExpiredUnhandled: AIEVAuthExpiredUnhandledStatsTracker;
|
|
1086
|
+
messagePendingStuck: MessagePendingStuckStatsTracker;
|
|
1087
|
+
userPerceivedResponseTime: UserPerceivedResponseTimeStatsTracker;
|
|
1088
|
+
streamStalled: StreamStalledStatsTracker;
|
|
1089
|
+
typingIndicatorAbsentTimeout: TypingIndicatorAbsentTimeoutStatsTracker;
|
|
1090
|
+
renderedContentUnusable: RenderedContentUnusableStatsTracker;
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
declare type AIAgentStatsTrackers = AIAgentStatsTrackerCollection & {
|
|
1094
|
+
clear: () => void;
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1073
1097
|
/**
|
|
1074
1098
|
* Common string set interface shared between react and react-native packages
|
|
1075
1099
|
* These are the base strings that both packages use
|
|
@@ -1254,6 +1278,33 @@ declare interface AIAgentStringSet {
|
|
|
1254
1278
|
};
|
|
1255
1279
|
}
|
|
1256
1280
|
|
|
1281
|
+
declare class AIEVAuthExpiredUnhandledStatsTracker {
|
|
1282
|
+
private readonly callback: StatsAppendCallback;
|
|
1283
|
+
|
|
1284
|
+
constructor(callback: StatsAppendCallback) {
|
|
1285
|
+
this.callback = callback;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
onPayloadParseFailed(params: { aiAgentId: string }): void {
|
|
1289
|
+
append(this.callback, METRIC_KEY_AIEV_AUTH_EXPIRED_UNHANDLED, 'payload_parse_failed', {
|
|
1290
|
+
extra: { agent_id: params.aiAgentId },
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
onCallbackNotFound(params: { aiAgentId: string }): void {
|
|
1295
|
+
append(this.callback, METRIC_KEY_AIEV_AUTH_EXPIRED_UNHANDLED, 'callback_not_found', {
|
|
1296
|
+
extra: { agent_id: params.aiAgentId },
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
clear(): void {}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
declare type AIResponseMessageMetadata = {
|
|
1304
|
+
isStreaming: boolean;
|
|
1305
|
+
userMessageId?: number;
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1257
1308
|
declare type AnnouncementQueueMode = 'latestOnly' | 'ordered';
|
|
1258
1309
|
|
|
1259
1310
|
declare interface AnnounceOptions {
|
|
@@ -1526,13 +1577,20 @@ export declare const ConnectionDelayLayout: {
|
|
|
1526
1577
|
}) => null;
|
|
1527
1578
|
};
|
|
1528
1579
|
|
|
1580
|
+
declare type ConnectionEventDetail =
|
|
1581
|
+
| 'connected'
|
|
1582
|
+
| 'reconnectStarted'
|
|
1583
|
+
| 'reconnectSucceeded'
|
|
1584
|
+
| 'reconnectFailed'
|
|
1585
|
+
| 'disconnected';
|
|
1586
|
+
|
|
1529
1587
|
declare interface ContextObject {
|
|
1530
1588
|
language?: string;
|
|
1531
1589
|
country?: string;
|
|
1532
1590
|
context: Record<string, string>;
|
|
1533
1591
|
}
|
|
1534
1592
|
|
|
1535
|
-
export declare const Conversation: ({ children, onNavigateToConversationList, channelUrl, onClearChannelUrl, shouldMarkAsRead, announcementsEnabled, initialFocusTarget, style, closedChannelUrl, onClearClosedChannelUrl, }: ConversationProps) => JSX.Element;
|
|
1593
|
+
export declare const Conversation: ({ children, onNavigateToConversationList, channelUrl, onClearChannelUrl, shouldMarkAsRead, announcementsEnabled, conversationStatsEnabled, initialFocusTarget, style, closedChannelUrl, onClearClosedChannelUrl, }: ConversationProps) => JSX.Element;
|
|
1536
1594
|
|
|
1537
1595
|
export declare const ConversationContext: Context<ConversationContextValue | null>;
|
|
1538
1596
|
|
|
@@ -1541,11 +1599,13 @@ declare interface ConversationContextProps {
|
|
|
1541
1599
|
channelUrl?: string;
|
|
1542
1600
|
onClearChannelUrl?: () => void;
|
|
1543
1601
|
shouldMarkAsRead?: boolean;
|
|
1602
|
+
conversationStatsEnabled?: boolean;
|
|
1544
1603
|
}
|
|
1545
1604
|
|
|
1546
1605
|
export declare const ConversationContextProvider: ({ children, ...props }: PropsWithChildren<ConversationContextProps>) => JSX.Element;
|
|
1547
1606
|
|
|
1548
1607
|
declare interface ConversationContextValue extends AIAgentConversationContextValue {
|
|
1608
|
+
conversationStatsEnabled: boolean;
|
|
1549
1609
|
scrollSource: ConversationScrollContextValue;
|
|
1550
1610
|
goToActiveConversation: () => Promise<void>;
|
|
1551
1611
|
onNavigateToConversationList?: () => void;
|
|
@@ -1664,24 +1724,17 @@ declare class ConversationInitialRenderStats extends AIAgentBaseStats {
|
|
|
1664
1724
|
const extra: Record<string, unknown> = {
|
|
1665
1725
|
...this.extraData,
|
|
1666
1726
|
present_method: this.presentMethod,
|
|
1667
|
-
...(authDuration !== null && {
|
|
1668
|
-
...(getChannelDuration !== null && {
|
|
1669
|
-
...(getMessagesDuration !== null && {
|
|
1670
|
-
...(totalDurationFromCache !== null && {
|
|
1671
|
-
};
|
|
1672
|
-
|
|
1673
|
-
const payload: AIAgentStatPayload = {
|
|
1674
|
-
key: this.getMetricKey(),
|
|
1675
|
-
// Use "0" when totalDuration is null (error case)
|
|
1676
|
-
value: totalDuration !== null ? String(totalDuration) : '0',
|
|
1727
|
+
...(authDuration !== null && { [DurationKey.AUTH]: authDuration }),
|
|
1728
|
+
...(getChannelDuration !== null && { [DurationKey.GET_CHANNEL]: getChannelDuration }),
|
|
1729
|
+
...(getMessagesDuration !== null && { [DurationKey.GET_MESSAGES]: getMessagesDuration }),
|
|
1730
|
+
...(totalDurationFromCache !== null && { [DurationKey.TOTAL_DURATION_FROM_CACHE]: totalDurationFromCache }),
|
|
1677
1731
|
};
|
|
1678
1732
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
return payload;
|
|
1733
|
+
return createStatsPayload(this.getMetricKey(), totalDuration ?? 0, {
|
|
1734
|
+
conversationId: this.conversationId ?? undefined,
|
|
1735
|
+
error: this.errorCode !== null ? { code: this.errorCode, message: this.errorDescription ?? '' } : undefined,
|
|
1736
|
+
extra,
|
|
1737
|
+
});
|
|
1685
1738
|
}
|
|
1686
1739
|
}
|
|
1687
1740
|
|
|
@@ -1690,100 +1743,193 @@ declare class ConversationInitialRenderStats extends AIAgentBaseStats {
|
|
|
1690
1743
|
*/
|
|
1691
1744
|
declare class ConversationInitialRenderStatsTracker {
|
|
1692
1745
|
private readonly commitCallback: StatsAppendCallback;
|
|
1746
|
+
private apiResultReceived = false;
|
|
1747
|
+
private channelUrl?: string;
|
|
1748
|
+
private consumed = false;
|
|
1749
|
+
private consumedByAuthError = false;
|
|
1693
1750
|
private presentMethod: PresentMethod = 'direct_present';
|
|
1751
|
+
private renderedAfterApiResult = false;
|
|
1694
1752
|
private stats: ConversationInitialRenderStats | null = null;
|
|
1695
1753
|
|
|
1696
1754
|
constructor(commitCallback: StatsAppendCallback) {
|
|
1697
1755
|
this.commitCallback = commitCallback;
|
|
1698
1756
|
}
|
|
1699
1757
|
|
|
1700
|
-
private getOrCreateStats(): ConversationInitialRenderStats {
|
|
1701
|
-
if (
|
|
1758
|
+
private getOrCreateStats(): ConversationInitialRenderStats | null {
|
|
1759
|
+
if (this.consumed) return null;
|
|
1760
|
+
if (this.stats?.isCommitted()) {
|
|
1761
|
+
this.consumed = true;
|
|
1762
|
+
return null;
|
|
1763
|
+
}
|
|
1764
|
+
if (!this.stats) {
|
|
1702
1765
|
this.stats = new ConversationInitialRenderStats()
|
|
1703
1766
|
.setCommitCallback(this.commitCallback)
|
|
1704
1767
|
.setPresentMethod(this.presentMethod);
|
|
1768
|
+
this.apiResultReceived = false;
|
|
1769
|
+
this.renderedAfterApiResult = false;
|
|
1705
1770
|
}
|
|
1706
1771
|
return this.stats;
|
|
1707
1772
|
}
|
|
1708
1773
|
|
|
1774
|
+
private getActiveStats(): ConversationInitialRenderStats | null {
|
|
1775
|
+
if (this.consumed || !this.stats || this.stats.isCommitted()) return null;
|
|
1776
|
+
return this.stats;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
private matchesChannelUrl(channelUrl?: string): boolean {
|
|
1780
|
+
return channelUrl === undefined || this.channelUrl === undefined || channelUrl === this.channelUrl;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
private pinChannelUrl(channelUrl?: string): boolean {
|
|
1784
|
+
if (channelUrl === undefined) return true;
|
|
1785
|
+
if (!this.matchesChannelUrl(channelUrl)) return false;
|
|
1786
|
+
|
|
1787
|
+
this.channelUrl = channelUrl;
|
|
1788
|
+
this.stats?.setChannelUrl(channelUrl);
|
|
1789
|
+
return true;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
private consume(commitResult: boolean): boolean {
|
|
1793
|
+
if (commitResult) {
|
|
1794
|
+
this.consumed = true;
|
|
1795
|
+
}
|
|
1796
|
+
return commitResult;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
private commitIfReady(channelUrl?: string): boolean {
|
|
1800
|
+
if (this.consumed || !this.stats || this.stats.isCommitted()) return true;
|
|
1801
|
+
if (!this.matchesChannelUrl(channelUrl)) return false;
|
|
1802
|
+
if (!this.apiResultReceived || !this.renderedAfterApiResult) return false;
|
|
1803
|
+
return this.consume(this.stats.stopTimer(DurationKey.TOTAL_DURATION).commit());
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
start(channelUrl?: string): void {
|
|
1807
|
+
const stats = this.getOrCreateStats();
|
|
1808
|
+
if (!stats || !this.pinChannelUrl(channelUrl)) return;
|
|
1809
|
+
|
|
1810
|
+
stats.startTimer(DurationKey.TOTAL_DURATION).startTimer(DurationKey.TOTAL_DURATION_FROM_CACHE);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1709
1813
|
onAuthStart(): void {
|
|
1710
|
-
this.
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
.startTimer(DurationKey.AUTH);
|
|
1814
|
+
if (this.consumedByAuthError) {
|
|
1815
|
+
this.clear();
|
|
1816
|
+
}
|
|
1817
|
+
this.getOrCreateStats()?.startTimer(DurationKey.AUTH);
|
|
1714
1818
|
}
|
|
1715
1819
|
|
|
1716
1820
|
onAuthComplete(): void {
|
|
1717
|
-
this.
|
|
1821
|
+
this.getActiveStats()?.stopTimer(DurationKey.AUTH);
|
|
1718
1822
|
}
|
|
1719
1823
|
|
|
1720
1824
|
onAuthError(error: Error): void {
|
|
1721
|
-
|
|
1722
|
-
|
|
1825
|
+
const stats = this.getActiveStats();
|
|
1826
|
+
if (stats) {
|
|
1827
|
+
const committed = this.consume(
|
|
1828
|
+
stats
|
|
1723
1829
|
.stopTimer(DurationKey.AUTH)
|
|
1724
1830
|
.setError(error instanceof SendbirdError ? error.code : undefined, error.message)
|
|
1725
1831
|
.stopTimer(DurationKey.TOTAL_DURATION)
|
|
1726
|
-
.commit()
|
|
1832
|
+
.commit(),
|
|
1833
|
+
);
|
|
1834
|
+
if (committed) {
|
|
1835
|
+
this.consumedByAuthError = true;
|
|
1836
|
+
}
|
|
1727
1837
|
}
|
|
1728
1838
|
}
|
|
1729
1839
|
|
|
1730
|
-
onGetChannelStart(): void {
|
|
1731
|
-
this.
|
|
1840
|
+
onGetChannelStart(channelUrl?: string): void {
|
|
1841
|
+
const stats = this.getActiveStats();
|
|
1842
|
+
if (!stats || !this.pinChannelUrl(channelUrl)) return;
|
|
1843
|
+
|
|
1844
|
+
stats.startTimer(DurationKey.GET_CHANNEL);
|
|
1732
1845
|
}
|
|
1733
1846
|
|
|
1734
|
-
onGetChannelComplete(
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1847
|
+
onGetChannelComplete(params?: number | GetChannelCompleteParams): void {
|
|
1848
|
+
const channelUrl = typeof params === 'object' ? params.channelUrl : undefined;
|
|
1849
|
+
const conversationId = typeof params === 'object' ? params.conversationId : params;
|
|
1850
|
+
const stats = this.getActiveStats();
|
|
1851
|
+
if (!stats || !this.pinChannelUrl(channelUrl)) return;
|
|
1852
|
+
|
|
1853
|
+
stats.stopTimer(DurationKey.GET_CHANNEL);
|
|
1854
|
+
if (conversationId !== undefined) {
|
|
1855
|
+
stats.setConversationId(conversationId);
|
|
1741
1856
|
}
|
|
1857
|
+
stats.startTimer(DurationKey.GET_MESSAGES);
|
|
1742
1858
|
}
|
|
1743
1859
|
|
|
1744
|
-
onGetChannelError(
|
|
1745
|
-
|
|
1746
|
-
|
|
1860
|
+
onGetChannelError(params: Error | GetChannelErrorParams): void {
|
|
1861
|
+
const channelUrl = params instanceof Error ? undefined : params.channelUrl;
|
|
1862
|
+
const error = params instanceof Error ? params : params.error;
|
|
1863
|
+
const stats = this.getActiveStats();
|
|
1864
|
+
if (stats && this.pinChannelUrl(channelUrl)) {
|
|
1865
|
+
this.consume(
|
|
1866
|
+
stats
|
|
1747
1867
|
.stopTimer(DurationKey.GET_CHANNEL)
|
|
1748
1868
|
.setError(error instanceof SendbirdError ? error.code : undefined, error.message)
|
|
1749
1869
|
.stopTimer(DurationKey.TOTAL_DURATION)
|
|
1750
|
-
.commit()
|
|
1870
|
+
.commit(),
|
|
1871
|
+
);
|
|
1751
1872
|
}
|
|
1752
1873
|
}
|
|
1753
1874
|
|
|
1754
1875
|
onCacheResult(error: Error | null): void {
|
|
1755
|
-
|
|
1876
|
+
const stats = this.getActiveStats();
|
|
1877
|
+
if (stats) {
|
|
1756
1878
|
if (error) {
|
|
1757
|
-
|
|
1879
|
+
stats.setError(error instanceof SendbirdError ? error.code : undefined, error.message);
|
|
1758
1880
|
}
|
|
1759
|
-
|
|
1881
|
+
stats.stopTimer(DurationKey.TOTAL_DURATION_FROM_CACHE);
|
|
1760
1882
|
}
|
|
1761
1883
|
}
|
|
1762
1884
|
|
|
1763
1885
|
onApiResult(error: Error | null): void {
|
|
1764
|
-
|
|
1886
|
+
const stats = this.getActiveStats();
|
|
1887
|
+
if (stats) {
|
|
1765
1888
|
if (error) {
|
|
1766
|
-
this.
|
|
1889
|
+
this.consume(
|
|
1890
|
+
stats
|
|
1891
|
+
.setError(error instanceof SendbirdError ? error.code : undefined, error.message)
|
|
1892
|
+
.stopTimer(DurationKey.TOTAL_DURATION)
|
|
1893
|
+
.stopTimer(DurationKey.GET_MESSAGES)
|
|
1894
|
+
.commit(),
|
|
1895
|
+
);
|
|
1896
|
+
return;
|
|
1767
1897
|
}
|
|
1768
|
-
this.
|
|
1898
|
+
this.apiResultReceived = true;
|
|
1899
|
+
this.renderedAfterApiResult = false;
|
|
1900
|
+
stats.stopTimer(DurationKey.GET_MESSAGES);
|
|
1901
|
+
this.commitIfReady();
|
|
1769
1902
|
}
|
|
1770
1903
|
}
|
|
1771
1904
|
|
|
1772
1905
|
setChannelUrl(url: string): void {
|
|
1773
|
-
this.
|
|
1906
|
+
this.pinChannelUrl(url);
|
|
1774
1907
|
}
|
|
1775
1908
|
|
|
1776
|
-
|
|
1777
|
-
this.
|
|
1909
|
+
commitPending(): boolean {
|
|
1910
|
+
const stats = this.getActiveStats();
|
|
1911
|
+
return stats ? this.consume(stats.stopTimer(DurationKey.TOTAL_DURATION).commit()) : true;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
markRendered(channelUrl?: string): boolean {
|
|
1915
|
+
if (!this.matchesChannelUrl(channelUrl)) return false;
|
|
1916
|
+
|
|
1917
|
+
this.renderedAfterApiResult = true;
|
|
1918
|
+
return this.commitIfReady(channelUrl);
|
|
1778
1919
|
}
|
|
1779
1920
|
|
|
1780
1921
|
setPresentMethod(method: PresentMethod): void {
|
|
1781
1922
|
this.presentMethod = method;
|
|
1782
|
-
this.
|
|
1923
|
+
this.getActiveStats()?.setPresentMethod(method);
|
|
1783
1924
|
}
|
|
1784
1925
|
|
|
1785
1926
|
clear(): void {
|
|
1786
1927
|
this.stats = null;
|
|
1928
|
+
this.apiResultReceived = false;
|
|
1929
|
+
this.channelUrl = undefined;
|
|
1930
|
+
this.consumed = false;
|
|
1931
|
+
this.consumedByAuthError = false;
|
|
1932
|
+
this.renderedAfterApiResult = false;
|
|
1787
1933
|
}
|
|
1788
1934
|
}
|
|
1789
1935
|
|
|
@@ -1826,7 +1972,7 @@ export declare const ConversationLayout: {
|
|
|
1826
1972
|
declare interface ConversationLayoutTemplateProps {
|
|
1827
1973
|
}
|
|
1828
1974
|
|
|
1829
|
-
export declare const ConversationList: ({ conversationListLimit, conversationListFilter, children, onOpenConversationView, announcementsEnabled, style, }: ConversationListProps) => JSX.Element;
|
|
1975
|
+
export declare const ConversationList: ({ conversationListLimit, conversationListFilter, children, onOpenConversationView, announcementsEnabled, conversationListStatsEnabled, style, }: ConversationListProps) => JSX.Element;
|
|
1830
1976
|
|
|
1831
1977
|
/**
|
|
1832
1978
|
* Public interface for ConversationListCollection.
|
|
@@ -1855,10 +2001,11 @@ export declare const ConversationListContext: Context<ConversationListContextVal
|
|
|
1855
2001
|
declare interface ConversationListContextProps {
|
|
1856
2002
|
conversationListLimit?: number;
|
|
1857
2003
|
conversationListFilter?: Partial<AIAgentGroupChannelFilter>;
|
|
2004
|
+
conversationListStatsEnabled?: boolean;
|
|
1858
2005
|
onOpenConversationView?: (channelUrl: string, status: 'open' | 'closed', options?: ConversationListOpenOptions) => void;
|
|
1859
2006
|
}
|
|
1860
2007
|
|
|
1861
|
-
export declare function ConversationListContextProvider({ conversationListLimit, conversationListFilter, onOpenConversationView, children, }: PropsWithChildren<ConversationListContextProps>): JSX.Element;
|
|
2008
|
+
export declare function ConversationListContextProvider({ conversationListLimit, conversationListFilter, conversationListStatsEnabled, onOpenConversationView, children, }: PropsWithChildren<ConversationListContextProps>): JSX.Element;
|
|
1862
2009
|
|
|
1863
2010
|
declare interface ConversationListContextValue extends AIAgentConversationListContextValue {
|
|
1864
2011
|
onOpenConversationView: (channelUrl: string, status: 'open' | 'closed', options?: ConversationListOpenOptions) => void;
|
|
@@ -1916,6 +2063,156 @@ declare interface ConversationListHeaderTemplateProps {
|
|
|
1916
2063
|
titleAlign?: 'start' | 'center' | 'end';
|
|
1917
2064
|
}
|
|
1918
2065
|
|
|
2066
|
+
declare class ConversationListInitialRenderStatsTracker {
|
|
2067
|
+
private readonly callback: StatsAppendCallback;
|
|
2068
|
+
private readonly clock: StatsClock;
|
|
2069
|
+
private measurement = createEmptyProgress();
|
|
2070
|
+
private renderedCommitted = false;
|
|
2071
|
+
private startedAt: number | null = null;
|
|
2072
|
+
private listStartedAt: number | null = null;
|
|
2073
|
+
private authStartedAt: number | null = null;
|
|
2074
|
+
private authDuration: number | null = null;
|
|
2075
|
+
private listDuration: number | null = null;
|
|
2076
|
+
private presentMethod: PresentMethod = 'direct_present';
|
|
2077
|
+
private committed = false;
|
|
2078
|
+
|
|
2079
|
+
constructor(callback: StatsAppendCallback, options?: TrackerOptions) {
|
|
2080
|
+
this.callback = callback;
|
|
2081
|
+
this.clock = getClock(options);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
onInitializeStart(): void {
|
|
2085
|
+
if (this.measurement.rendered) return;
|
|
2086
|
+
if (this.measurement.initialized) {
|
|
2087
|
+
this.clearMeasurementState();
|
|
2088
|
+
}
|
|
2089
|
+
this.renderedCommitted = false;
|
|
2090
|
+
this.startMeasurement();
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
onListRequestStart(): void {
|
|
2094
|
+
if (this.measurement.listStarted && !this.measurement.listCompleted && !this.measurement.rendered) {
|
|
2095
|
+
this.clearMeasurementState();
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
this.markListRequestStartOnce();
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
onListRequestComplete(): void {
|
|
2102
|
+
this.markListRequestCompleteOnce();
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
onRendered(): void {
|
|
2106
|
+
this.markRenderedOnce();
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
setPresentMethod(method: PresentMethod): void {
|
|
2110
|
+
this.presentMethod = method;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
start(presentMethod?: PresentMethod): void {
|
|
2114
|
+
if (this.committed) return;
|
|
2115
|
+
this.startedAt = this.clock.now();
|
|
2116
|
+
if (presentMethod) this.presentMethod = presentMethod;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
onAuthStart(): void {
|
|
2120
|
+
if (this.committed) {
|
|
2121
|
+
this.clearMeasurementState();
|
|
2122
|
+
this.renderedCommitted = false;
|
|
2123
|
+
}
|
|
2124
|
+
this.authDuration = null;
|
|
2125
|
+
this.authStartedAt = this.clock.now();
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
onAuthComplete(): void {
|
|
2129
|
+
if (this.committed || this.authDuration !== null) return;
|
|
2130
|
+
if (this.authStartedAt === null) {
|
|
2131
|
+
this.authDuration = 0;
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
this.authDuration = this.clock.now() - this.authStartedAt;
|
|
2135
|
+
this.authStartedAt = null;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
markListRequestStart(): void {
|
|
2139
|
+
if (this.committed) return;
|
|
2140
|
+
if (this.startedAt === null) this.start();
|
|
2141
|
+
this.listStartedAt = this.clock.now();
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
markListRequestComplete(): void {
|
|
2145
|
+
if (this.committed || this.listStartedAt === null) return;
|
|
2146
|
+
this.listDuration = this.clock.now() - this.listStartedAt;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
markRendered(): void {
|
|
2150
|
+
if (this.committed || this.startedAt === null) return;
|
|
2151
|
+
this.committed = true;
|
|
2152
|
+
append(this.callback, METRIC_KEY_CONVERSATION_LIST_INITIAL_RENDER, this.clock.now() - this.startedAt, {
|
|
2153
|
+
extra: {
|
|
2154
|
+
...(this.authDuration !== null && { auth_duration_ms: this.authDuration }),
|
|
2155
|
+
...(this.listDuration !== null && { get_list_duration_ms: this.listDuration }),
|
|
2156
|
+
present_method: this.presentMethod,
|
|
2157
|
+
},
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
clear(): void {
|
|
2162
|
+
this.clearMeasurementState();
|
|
2163
|
+
this.clearAuthState();
|
|
2164
|
+
this.renderedCommitted = false;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
private clearMeasurementState(): void {
|
|
2168
|
+
this.startedAt = null;
|
|
2169
|
+
this.listStartedAt = null;
|
|
2170
|
+
this.listDuration = null;
|
|
2171
|
+
this.committed = false;
|
|
2172
|
+
this.measurement = createEmptyProgress();
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
private clearAuthState(): void {
|
|
2176
|
+
this.authStartedAt = null;
|
|
2177
|
+
this.authDuration = null;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
private startMeasurement(): void {
|
|
2181
|
+
if (this.measurement.initialized) return;
|
|
2182
|
+
|
|
2183
|
+
this.clearMeasurementState();
|
|
2184
|
+
this.start();
|
|
2185
|
+
this.measurement.initialized = true;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
private markListRequestStartOnce(): void {
|
|
2189
|
+
this.startMeasurement();
|
|
2190
|
+
if (this.measurement.listStarted) return;
|
|
2191
|
+
|
|
2192
|
+
this.markListRequestStart();
|
|
2193
|
+
this.measurement.listStarted = true;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
private markListRequestCompleteOnce(): void {
|
|
2197
|
+
this.markListRequestStartOnce();
|
|
2198
|
+
if (this.measurement.listCompleted) return;
|
|
2199
|
+
|
|
2200
|
+
this.markListRequestComplete();
|
|
2201
|
+
this.measurement.listCompleted = true;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
private markRenderedOnce(): void {
|
|
2205
|
+
if (this.renderedCommitted) return;
|
|
2206
|
+
|
|
2207
|
+
this.startMeasurement();
|
|
2208
|
+
if (this.measurement.rendered) return;
|
|
2209
|
+
|
|
2210
|
+
this.markRendered();
|
|
2211
|
+
this.measurement.rendered = true;
|
|
2212
|
+
this.renderedCommitted = true;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
1919
2216
|
export declare const ConversationListItemLayout: {
|
|
1920
2217
|
(props: PropsWithChildren): ReactNode;
|
|
1921
2218
|
defaults: {
|
|
@@ -1985,6 +2282,7 @@ export declare type ConversationListProps = PropsWithChildren<{
|
|
|
1985
2282
|
conversationListFilter?: Partial<AIAgentGroupChannelFilter>;
|
|
1986
2283
|
onOpenConversationView?: (channelUrl: string, status: 'open' | 'closed', options?: ConversationListOpenOptions) => void;
|
|
1987
2284
|
announcementsEnabled?: boolean;
|
|
2285
|
+
conversationListStatsEnabled?: boolean;
|
|
1988
2286
|
/** Custom styles for the conversation list container. */
|
|
1989
2287
|
style?: CSSProperties;
|
|
1990
2288
|
}>;
|
|
@@ -2008,12 +2306,138 @@ declare type ConversationMessageEventPayload =
|
|
|
2008
2306
|
messages: ChatSDKBaseMessage[];
|
|
2009
2307
|
};
|
|
2010
2308
|
|
|
2309
|
+
declare type ConversationNotInteractiveMeasurement = {
|
|
2310
|
+
timer: TimeoutId | null;
|
|
2311
|
+
startedAt: number;
|
|
2312
|
+
committed: boolean;
|
|
2313
|
+
inputEnabled: boolean;
|
|
2314
|
+
phaseReached: ConversationNotInteractivePhase;
|
|
2315
|
+
historyLoadResult: HistoryLoadResult;
|
|
2316
|
+
conversationId?: number;
|
|
2317
|
+
channelUrl?: string;
|
|
2318
|
+
};
|
|
2319
|
+
|
|
2320
|
+
declare type ConversationNotInteractivePhase = 'auth' | 'channel_join' | 'history_load' | 'input_enable' | 'render';
|
|
2321
|
+
|
|
2322
|
+
declare class ConversationNotInteractiveTimeoutStatsTracker {
|
|
2323
|
+
private readonly callback: StatsAppendCallback;
|
|
2324
|
+
private readonly clock: StatsClock;
|
|
2325
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
2326
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
2327
|
+
private readonly measurements = new Map<string, ConversationNotInteractiveMeasurement>();
|
|
2328
|
+
|
|
2329
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
2330
|
+
this.callback = callback;
|
|
2331
|
+
this.clock = getClock(options);
|
|
2332
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
2333
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
start(params?: { channelUrl?: string; conversationId?: number }): void {
|
|
2337
|
+
const key = this.getKey(params?.channelUrl);
|
|
2338
|
+
this.clearKey(key);
|
|
2339
|
+
|
|
2340
|
+
const measurement = {
|
|
2341
|
+
startedAt: this.clock.now(),
|
|
2342
|
+
channelUrl: params?.channelUrl,
|
|
2343
|
+
conversationId: params?.conversationId,
|
|
2344
|
+
committed: false,
|
|
2345
|
+
inputEnabled: false,
|
|
2346
|
+
phaseReached: 'auth' as ConversationNotInteractivePhase,
|
|
2347
|
+
historyLoadResult: 'pending' as HistoryLoadResult,
|
|
2348
|
+
timer: null as TimeoutId | null,
|
|
2349
|
+
};
|
|
2350
|
+
measurement.timer = this.setTimeoutFn(() => this.commitTimeout(key), 10_000);
|
|
2351
|
+
this.measurements.set(key, measurement);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
markChannelJoined(conversationId?: number, channelUrl?: string): void {
|
|
2355
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2356
|
+
if (!measurement || measurement.committed) return;
|
|
2357
|
+
measurement.phaseReached = 'channel_join';
|
|
2358
|
+
if (conversationId !== undefined) measurement.conversationId = conversationId;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
markHistoryLoaded(result: HistoryLoadResult, channelUrl?: string): void {
|
|
2362
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2363
|
+
if (!measurement || measurement.committed) return;
|
|
2364
|
+
measurement.phaseReached = 'history_load';
|
|
2365
|
+
measurement.historyLoadResult = result;
|
|
2366
|
+
this.cancelIfReady(channelUrl);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
markInputEnabled(channelUrl?: string): void {
|
|
2370
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2371
|
+
if (!measurement || measurement.committed) return;
|
|
2372
|
+
measurement.phaseReached = 'input_enable';
|
|
2373
|
+
measurement.inputEnabled = true;
|
|
2374
|
+
this.cancelIfReady(channelUrl);
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
markRendered(channelUrl?: string): void {
|
|
2378
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2379
|
+
if (!measurement || measurement.committed) return;
|
|
2380
|
+
measurement.phaseReached = 'render';
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
clear(): void {
|
|
2384
|
+
this.measurements.forEach((measurement) => clearTimer(measurement.timer, this.clearTimeoutFn));
|
|
2385
|
+
this.measurements.clear();
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
clearChannel(channelUrl?: string): void {
|
|
2389
|
+
if (channelUrl === undefined) return;
|
|
2390
|
+
this.clearKey(this.getKey(channelUrl));
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
private clearKey(key: string): void {
|
|
2394
|
+
const measurement = this.measurements.get(key);
|
|
2395
|
+
if (!measurement) return;
|
|
2396
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
2397
|
+
this.measurements.delete(key);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
private cancelIfReady(channelUrl?: string): void {
|
|
2401
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2402
|
+
if (!measurement || !measurement.inputEnabled || measurement.historyLoadResult === 'pending') return;
|
|
2403
|
+
this.clearKey(this.getKey(measurement.channelUrl));
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
private commitTimeout(key: string): void {
|
|
2407
|
+
const measurement = this.measurements.get(key);
|
|
2408
|
+
if (!measurement || measurement.committed) return;
|
|
2409
|
+
measurement.committed = true;
|
|
2410
|
+
measurement.timer = null;
|
|
2411
|
+
append(this.callback, METRIC_KEY_CONVERSATION_NOT_INTERACTIVE_TIMEOUT, this.clock.now() - measurement.startedAt, {
|
|
2412
|
+
conversationId: measurement.conversationId,
|
|
2413
|
+
error: { message: METRIC_KEY_CONVERSATION_NOT_INTERACTIVE_TIMEOUT },
|
|
2414
|
+
extra: {
|
|
2415
|
+
outcome: 'timeout',
|
|
2416
|
+
phase_reached: measurement.phaseReached,
|
|
2417
|
+
history_load_result: measurement.historyLoadResult,
|
|
2418
|
+
},
|
|
2419
|
+
});
|
|
2420
|
+
this.measurements.delete(key);
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
private getMeasurement(channelUrl?: string): ConversationNotInteractiveMeasurement | undefined {
|
|
2424
|
+
if (channelUrl !== undefined) return this.measurements.get(this.getKey(channelUrl));
|
|
2425
|
+
if (this.measurements.size === 1) return Array.from(this.measurements.values())[0];
|
|
2426
|
+
return this.measurements.get(this.getKey());
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
private getKey(channelUrl?: string): string {
|
|
2430
|
+
return channelUrl ?? '__default__';
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2011
2434
|
export declare type ConversationProps = PropsWithChildren<{
|
|
2012
2435
|
channelUrl?: string;
|
|
2013
2436
|
onClearChannelUrl?: () => void;
|
|
2014
2437
|
onNavigateToConversationList?: () => void;
|
|
2015
2438
|
shouldMarkAsRead?: boolean;
|
|
2016
2439
|
announcementsEnabled?: boolean;
|
|
2440
|
+
conversationStatsEnabled?: boolean;
|
|
2017
2441
|
initialFocusTarget?: ConversationInitialFocusTarget;
|
|
2018
2442
|
/** Custom styles for the conversation container. */
|
|
2019
2443
|
style?: CSSProperties;
|
|
@@ -2209,17 +2633,31 @@ declare interface Dispatcher {
|
|
|
2209
2633
|
): void;
|
|
2210
2634
|
}
|
|
2211
2635
|
|
|
2636
|
+
declare enum DurationKey {
|
|
2637
|
+
AUTH = 'auth_duration_ms',
|
|
2638
|
+
GET_CHANNEL = 'get_channel_duration_ms',
|
|
2639
|
+
GET_MESSAGES = 'get_messages_duration_ms',
|
|
2640
|
+
TOTAL_DURATION = 'total_duration_ms',
|
|
2641
|
+
TOTAL_DURATION_FROM_CACHE = 'total_duration_from_cache_ms',
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2212
2644
|
declare type ErrorAnnouncementParams =
|
|
2213
2645
|
| { type: 'fileSizeExceeded'; maxSizeMB?: number }
|
|
2214
2646
|
| { type: 'sendFailed' }
|
|
2215
2647
|
| { type: 'csatFormError' }
|
|
2216
2648
|
| { type: 'generic'; message: string };
|
|
2217
2649
|
|
|
2650
|
+
declare type ErrorLike = StatsPayloadError;
|
|
2651
|
+
|
|
2218
2652
|
/**
|
|
2219
2653
|
* For better understanding: https://sendbird.atlassian.net/wiki/spaces/AA/pages/3075014695/Extended+Message+Payload+Spec
|
|
2220
2654
|
*/
|
|
2221
2655
|
export declare interface ExtendedMessagePayload {
|
|
2222
2656
|
is_thinking: boolean;
|
|
2657
|
+
// Whether this message is the last bubble in a multi-bubble turn.
|
|
2658
|
+
// Sent as `true` only on the last bubble; omitted (undefined) on non-last bubbles.
|
|
2659
|
+
// Only reliable once the server stream has completed (data.stream = false).
|
|
2660
|
+
is_last_in_turn?: boolean;
|
|
2223
2661
|
cta_button: {
|
|
2224
2662
|
label: string;
|
|
2225
2663
|
url: string;
|
|
@@ -2348,6 +2786,8 @@ declare interface FixedMessengerStyleProps {
|
|
|
2348
2786
|
launcherSize?: number;
|
|
2349
2787
|
}
|
|
2350
2788
|
|
|
2789
|
+
declare type ForegroundTaskScheduler = (start: () => void) => void | (() => void);
|
|
2790
|
+
|
|
2351
2791
|
declare interface Form {
|
|
2352
2792
|
key: string;
|
|
2353
2793
|
version: number;
|
|
@@ -2377,6 +2817,16 @@ export declare interface FunctionCallsInfo {
|
|
|
2377
2817
|
};
|
|
2378
2818
|
}
|
|
2379
2819
|
|
|
2820
|
+
declare type GetChannelCompleteParams = {
|
|
2821
|
+
channelUrl?: string;
|
|
2822
|
+
conversationId?: number;
|
|
2823
|
+
};
|
|
2824
|
+
|
|
2825
|
+
declare type GetChannelErrorParams = {
|
|
2826
|
+
channelUrl?: string;
|
|
2827
|
+
error: Error;
|
|
2828
|
+
};
|
|
2829
|
+
|
|
2380
2830
|
export declare interface GroundednessInfo {
|
|
2381
2831
|
id: number;
|
|
2382
2832
|
source_type:
|
|
@@ -2399,6 +2849,8 @@ export declare interface GroundednessInfo {
|
|
|
2399
2849
|
*/
|
|
2400
2850
|
declare type HexColor = `#${string}`;
|
|
2401
2851
|
|
|
2852
|
+
declare type HistoryLoadResult = 'success' | 'verified_empty' | 'failed' | 'pending';
|
|
2853
|
+
|
|
2402
2854
|
export declare type IconComponent = AIAgentIconComponent;
|
|
2403
2855
|
|
|
2404
2856
|
export declare type IconComponentProps = AIAgentIconComponentProps;
|
|
@@ -2480,13 +2932,8 @@ export declare const IncomingMessageLayout: {
|
|
|
2480
2932
|
};
|
|
2481
2933
|
}) => ReactNode;
|
|
2482
2934
|
SuggestedReplies: ({ extendedMessagePayload, onClickSuggestedReply, suggestedRepliesDirection, }: IncomingMessageProps) => ReactNode;
|
|
2483
|
-
MessageTemplate: (props: {
|
|
2484
|
-
|
|
2485
|
-
onRequestMessageTemplate?: ((templateKey: string) => Promise<string>) | undefined;
|
|
2486
|
-
onHandleTemplateInternalAction?: ((action: Action) => void) | undefined;
|
|
2487
|
-
messageTemplateErrorFallback?: ReactNode;
|
|
2488
|
-
messageTemplateLoadingFallback?: ReactNode;
|
|
2489
|
-
extendedMessagePayload?: Partial<ExtendedMessagePayload> | undefined;
|
|
2935
|
+
MessageTemplate: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
2936
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
2490
2937
|
}) => ReactNode;
|
|
2491
2938
|
CustomMessageTemplate: (_: ({
|
|
2492
2939
|
messageType: "user";
|
|
@@ -2950,7 +3397,9 @@ export declare const IncomingMessageLayout: {
|
|
|
2950
3397
|
})) => ReactNode;
|
|
2951
3398
|
CTAButton: ({ extendedMessagePayload, onClickCTA, }: IncomingMessageProps) => ReactNode;
|
|
2952
3399
|
Citation: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
2953
|
-
Form: (props: IncomingMessageProps
|
|
3400
|
+
Form: (props: IncomingMessageProps & {
|
|
3401
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
3402
|
+
}) => ReactNode;
|
|
2954
3403
|
Feedback: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
2955
3404
|
MessageLogs: (_: IncomingMessageProps) => ReactNode;
|
|
2956
3405
|
};
|
|
@@ -2975,13 +3424,8 @@ export declare const IncomingMessageLayout: {
|
|
|
2975
3424
|
};
|
|
2976
3425
|
}) => ReactNode;
|
|
2977
3426
|
SuggestedReplies: ({ extendedMessagePayload, onClickSuggestedReply, suggestedRepliesDirection, }: IncomingMessageProps) => ReactNode;
|
|
2978
|
-
MessageTemplate: (props: {
|
|
2979
|
-
|
|
2980
|
-
onRequestMessageTemplate?: ((templateKey: string) => Promise<string>) | undefined;
|
|
2981
|
-
onHandleTemplateInternalAction?: ((action: Action) => void) | undefined;
|
|
2982
|
-
messageTemplateErrorFallback?: ReactNode;
|
|
2983
|
-
messageTemplateLoadingFallback?: ReactNode;
|
|
2984
|
-
extendedMessagePayload?: Partial<ExtendedMessagePayload> | undefined;
|
|
3427
|
+
MessageTemplate: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
3428
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
2985
3429
|
}) => ReactNode;
|
|
2986
3430
|
CustomMessageTemplate: (_: ({
|
|
2987
3431
|
messageType: "user";
|
|
@@ -3445,7 +3889,9 @@ export declare const IncomingMessageLayout: {
|
|
|
3445
3889
|
})) => ReactNode;
|
|
3446
3890
|
CTAButton: ({ extendedMessagePayload, onClickCTA, }: IncomingMessageProps) => ReactNode;
|
|
3447
3891
|
Citation: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
3448
|
-
Form: (props: IncomingMessageProps
|
|
3892
|
+
Form: (props: IncomingMessageProps & {
|
|
3893
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
3894
|
+
}) => ReactNode;
|
|
3449
3895
|
Feedback: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
3450
3896
|
MessageLogs: (_: IncomingMessageProps) => ReactNode;
|
|
3451
3897
|
}>>;
|
|
@@ -3465,13 +3911,8 @@ export declare const IncomingMessageLayout: {
|
|
|
3465
3911
|
};
|
|
3466
3912
|
}) => ReactNode;
|
|
3467
3913
|
SuggestedReplies: ({ extendedMessagePayload, onClickSuggestedReply, suggestedRepliesDirection, }: IncomingMessageProps) => ReactNode;
|
|
3468
|
-
MessageTemplate: (props: {
|
|
3469
|
-
|
|
3470
|
-
onRequestMessageTemplate?: ((templateKey: string) => Promise<string>) | undefined;
|
|
3471
|
-
onHandleTemplateInternalAction?: ((action: Action) => void) | undefined;
|
|
3472
|
-
messageTemplateErrorFallback?: ReactNode;
|
|
3473
|
-
messageTemplateLoadingFallback?: ReactNode;
|
|
3474
|
-
extendedMessagePayload?: Partial<ExtendedMessagePayload> | undefined;
|
|
3914
|
+
MessageTemplate: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
3915
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
3475
3916
|
}) => ReactNode;
|
|
3476
3917
|
CustomMessageTemplate: (_: ({
|
|
3477
3918
|
messageType: "user";
|
|
@@ -3935,19 +4376,16 @@ export declare const IncomingMessageLayout: {
|
|
|
3935
4376
|
})) => ReactNode;
|
|
3936
4377
|
CTAButton: ({ extendedMessagePayload, onClickCTA, }: IncomingMessageProps) => ReactNode;
|
|
3937
4378
|
Citation: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
3938
|
-
Form: (props: IncomingMessageProps
|
|
4379
|
+
Form: (props: IncomingMessageProps & {
|
|
4380
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
4381
|
+
}) => ReactNode;
|
|
3939
4382
|
Feedback: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
3940
4383
|
MessageLogs: (_: IncomingMessageProps) => ReactNode;
|
|
3941
4384
|
}>;
|
|
3942
4385
|
} & {
|
|
3943
4386
|
MessageTemplate: (props: {
|
|
3944
|
-
component: (props: {
|
|
3945
|
-
|
|
3946
|
-
onRequestMessageTemplate?: ((templateKey: string) => Promise<string>) | undefined;
|
|
3947
|
-
onHandleTemplateInternalAction?: ((action: Action) => void) | undefined;
|
|
3948
|
-
messageTemplateErrorFallback?: ReactNode;
|
|
3949
|
-
messageTemplateLoadingFallback?: ReactNode;
|
|
3950
|
-
extendedMessagePayload?: Partial<ExtendedMessagePayload> | undefined;
|
|
4387
|
+
component: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
4388
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
3951
4389
|
}) => ReactNode;
|
|
3952
4390
|
}) => null;
|
|
3953
4391
|
SenderName: (props: {
|
|
@@ -4456,7 +4894,9 @@ export declare const IncomingMessageLayout: {
|
|
|
4456
4894
|
component: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
4457
4895
|
}) => null;
|
|
4458
4896
|
Form: (props: {
|
|
4459
|
-
component: (props: IncomingMessageProps
|
|
4897
|
+
component: (props: IncomingMessageProps & {
|
|
4898
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
4899
|
+
}) => ReactNode;
|
|
4460
4900
|
}) => null;
|
|
4461
4901
|
Feedback: (props: {
|
|
4462
4902
|
component: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
@@ -4469,6 +4909,13 @@ export declare const IncomingMessageLayout: {
|
|
|
4469
4909
|
export declare type IncomingMessageProps<T extends IncomingMessageUnion['messageType'] = IncomingMessageUnion['messageType']> =
|
|
4470
4910
|
PickMessageProps<IncomingMessageUnion, T>;
|
|
4471
4911
|
|
|
4912
|
+
declare type IncomingMessageStatsCallbacks = {
|
|
4913
|
+
onVisibleTextProgress?: (displayedLength: number) => void;
|
|
4914
|
+
onRenderedTextProgress?: (displayedLength: number) => void;
|
|
4915
|
+
onMessageTemplateError?: (error: Error) => void;
|
|
4916
|
+
onFormRenderError?: (error: Error) => void;
|
|
4917
|
+
};
|
|
4918
|
+
|
|
4472
4919
|
declare type IncomingMessageTemplateProps = IncomingMessageProps & Partial<InternalExtraProps>;
|
|
4473
4920
|
|
|
4474
4921
|
declare type IncomingMessageUnion =
|
|
@@ -4508,7 +4955,7 @@ declare type InputState =
|
|
|
4508
4955
|
|
|
4509
4956
|
declare type InternalExtraProps = {
|
|
4510
4957
|
maxBodyWidth?: number;
|
|
4511
|
-
|
|
4958
|
+
statsCallbacks?: IncomingMessageStatsCallbacks;
|
|
4512
4959
|
};
|
|
4513
4960
|
|
|
4514
4961
|
declare type InternalExtraProps_2 = {
|
|
@@ -4562,6 +5009,110 @@ declare interface LayoutContextValue<Props, Components extends Record<string, Co
|
|
|
4562
5009
|
updateComponent: <K extends keyof Components>(key: K, component: Components[K]) => void;
|
|
4563
5010
|
}
|
|
4564
5011
|
|
|
5012
|
+
/**
|
|
5013
|
+
* Base contract for every metric emitted by the Live Metric module.
|
|
5014
|
+
* The injected `dispatch` boundary receives values of this type.
|
|
5015
|
+
*/
|
|
5016
|
+
export declare interface LiveMetric {
|
|
5017
|
+
readonly category: MetricCategory;
|
|
5018
|
+
readonly metricKey: string;
|
|
5019
|
+
readonly data: Record<string, string>;
|
|
5020
|
+
}
|
|
5021
|
+
|
|
5022
|
+
/** Outward boundary: emitters dispatch built metrics through this callback. */
|
|
5023
|
+
declare type LiveMetricDispatch = (metric: LiveMetric) => void;
|
|
5024
|
+
|
|
5025
|
+
/**
|
|
5026
|
+
* Category emitter contract. Each emitter accepts its own source variant,
|
|
5027
|
+
* decides whether to emit based on its state cache, and dispatches metrics.
|
|
5028
|
+
*/
|
|
5029
|
+
declare interface LiveMetricEmitter<S extends MetricSource = MetricSource> {
|
|
5030
|
+
handleEvent(source: S): void;
|
|
5031
|
+
}
|
|
5032
|
+
|
|
5033
|
+
/**
|
|
5034
|
+
* Central manager for the Live Metric module.
|
|
5035
|
+
*
|
|
5036
|
+
* Registers Sendbird SDK handlers, wraps raw callbacks into {@link MetricSource}s,
|
|
5037
|
+
* routes each source to its category emitter, and lets emitters dispatch public
|
|
5038
|
+
* metrics through the settable {@link LiveMetricManager.dispatch} sink. Outgoing message
|
|
5039
|
+
* and explicit conversation-initialized sources are injected via {@link LiveMetricManager.track}.
|
|
5040
|
+
*/
|
|
5041
|
+
declare class LiveMetricManager {
|
|
5042
|
+
private readonly id = 'aiagent_livemetric';
|
|
5043
|
+
|
|
5044
|
+
/** Sink for emitted metrics. Set by the owner; metrics are dropped while unset. */
|
|
5045
|
+
dispatch?: LiveMetricDispatch;
|
|
5046
|
+
|
|
5047
|
+
// Project to the plain public shape: drops internal fields and resolves the
|
|
5048
|
+
// non-enumerable `data` getter so the payload survives JSON.stringify/spread.
|
|
5049
|
+
private readonly emit: LiveMetricDispatch = ({ category, metricKey, data }) => {
|
|
5050
|
+
this.dispatch?.({ category, metricKey, data });
|
|
5051
|
+
};
|
|
5052
|
+
private readonly emitters: Record<MetricSource['type'], LiveMetricEmitter>;
|
|
5053
|
+
|
|
5054
|
+
constructor(private readonly chatSDK: ChatSDKType) {
|
|
5055
|
+
const getCurrentUserId = () => this.chatSDK.currentUser?.userId;
|
|
5056
|
+
this.emitters = {
|
|
5057
|
+
connection: new ConnectionMetricEmitter(this.emit),
|
|
5058
|
+
conversation: new ConversationMetricEmitter(this.emit),
|
|
5059
|
+
handoff: new HandoffMetricEmitter(this.emit),
|
|
5060
|
+
incomingMessage: new IncomingMessageMetricEmitter(this.emit, getCurrentUserId),
|
|
5061
|
+
outgoingMessage: new OutgoingMessageMetricEmitter(this.emit, getCurrentUserId),
|
|
5062
|
+
};
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
start(): void {
|
|
5066
|
+
const connectionHandler = new ConnectionHandler({
|
|
5067
|
+
onConnected: (userId) => this.trackConnection('connected', userId),
|
|
5068
|
+
onDisconnected: (userId) => this.trackConnection('disconnected', userId),
|
|
5069
|
+
onReconnectStarted: () => this.trackConnection('reconnectStarted'),
|
|
5070
|
+
onReconnectSucceeded: () => this.trackConnection('reconnectSucceeded'),
|
|
5071
|
+
onReconnectFailed: () => this.trackConnection('reconnectFailed'),
|
|
5072
|
+
});
|
|
5073
|
+
|
|
5074
|
+
const channelHandler = new GroupChannelHandler({
|
|
5075
|
+
onMessageReceived: (channel, message) => {
|
|
5076
|
+
if (!channel.isGroupChannel()) return;
|
|
5077
|
+
this.track({ type: 'incomingMessage', channel, message });
|
|
5078
|
+
},
|
|
5079
|
+
onChannelChanged: (channel) => {
|
|
5080
|
+
if (!channel.isGroupChannel()) return;
|
|
5081
|
+
|
|
5082
|
+
// Note: onChannelChanged can be triggered by various events (message received, user joined/left, etc.)
|
|
5083
|
+
this.track({ type: 'handoff', channel });
|
|
5084
|
+
this.track({ type: 'conversation', channel });
|
|
5085
|
+
},
|
|
5086
|
+
onUserJoined: (channel) => this.track({ type: 'handoff', channel }),
|
|
5087
|
+
onUserLeft: (channel) => this.track({ type: 'handoff', channel }),
|
|
5088
|
+
});
|
|
5089
|
+
|
|
5090
|
+
this.chatSDK.addConnectionHandler(this.id, connectionHandler);
|
|
5091
|
+
this.chatSDK.groupChannel.addGroupChannelHandler(this.id, channelHandler);
|
|
5092
|
+
}
|
|
5093
|
+
|
|
5094
|
+
/**
|
|
5095
|
+
* Routes a source to its emitter. Also the entry point for externally injected
|
|
5096
|
+
* sources (outgoing message status, explicit conversation-initialized).
|
|
5097
|
+
*/
|
|
5098
|
+
track(source: MetricSource): void {
|
|
5099
|
+
try {
|
|
5100
|
+
this.emitters[source.type].handleEvent(source);
|
|
5101
|
+
} catch {
|
|
5102
|
+
// no-op
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
|
|
5106
|
+
dispose(): void {
|
|
5107
|
+
this.chatSDK.removeConnectionHandler(this.id);
|
|
5108
|
+
this.chatSDK.groupChannel.removeGroupChannelHandler(this.id);
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
private trackConnection(detail: ConnectionEventDetail, userId = this.chatSDK.currentUser?.userId): void {
|
|
5112
|
+
if (userId) this.track({ type: 'connection', detail, userId });
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
5115
|
+
|
|
4565
5116
|
declare interface LocalizationContextValue extends LanguageSet {
|
|
4566
5117
|
format: typeof format;
|
|
4567
5118
|
defaultStringSet: StringSet;
|
|
@@ -4651,6 +5202,95 @@ export declare const MessageListUILayout: {
|
|
|
4651
5202
|
|
|
4652
5203
|
export declare const MessageLogs: ({ actionbook, functionCalls, groundedness, agentMessageTemplates, flaggedTypes, onClickActionbook, onClickFunctionCall, onClickFunctionCallDetail, onClickGroundedness, onClickAgentMessageTemplate, topContent, bottomContent, renderCustomGroundednessIcon, style, }: Props_2) => JSX.Element;
|
|
4653
5204
|
|
|
5205
|
+
declare class MessagePendingStuckStatsTracker {
|
|
5206
|
+
private readonly callback: StatsAppendCallback;
|
|
5207
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
5208
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
5209
|
+
private readonly measurements = new Map<string, PendingMeasurement>();
|
|
5210
|
+
|
|
5211
|
+
constructor(callback: StatsAppendCallback, options?: PendingStuckTrackerOptions) {
|
|
5212
|
+
this.callback = callback;
|
|
5213
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
5214
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
start(params: {
|
|
5218
|
+
requestId: string;
|
|
5219
|
+
channelUrl?: string;
|
|
5220
|
+
messageType: PendingMessageType;
|
|
5221
|
+
tracksUploadProgress?: boolean;
|
|
5222
|
+
}): void {
|
|
5223
|
+
if (!params.requestId || this.measurements.has(params.requestId)) return;
|
|
5224
|
+
|
|
5225
|
+
const usesUploadProgress = params.messageType !== 'text' && params.tracksUploadProgress === true;
|
|
5226
|
+
const measurement: PendingMeasurement = {
|
|
5227
|
+
channelUrl: params.channelUrl,
|
|
5228
|
+
messageType: params.messageType,
|
|
5229
|
+
stuckReason: usesUploadProgress ? 'upload_no_progress' : 'pending_too_long',
|
|
5230
|
+
timer: null,
|
|
5231
|
+
};
|
|
5232
|
+
const timeoutMs = usesUploadProgress ? FILE_UPLOAD_NO_PROGRESS_TIMEOUT_MS : TEXT_PENDING_TIMEOUT_MS;
|
|
5233
|
+
measurement.timer = this.setTimeoutFn(() => this.detect(params.requestId), timeoutMs);
|
|
5234
|
+
this.measurements.set(params.requestId, measurement);
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
resolve(params: { requestId: string }): void {
|
|
5238
|
+
const measurement = this.measurements.get(params.requestId);
|
|
5239
|
+
if (!measurement) return;
|
|
5240
|
+
|
|
5241
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5242
|
+
this.measurements.delete(params.requestId);
|
|
5243
|
+
}
|
|
5244
|
+
|
|
5245
|
+
disappear(params: { requestId: string }): void {
|
|
5246
|
+
const measurement = this.measurements.get(params.requestId);
|
|
5247
|
+
if (!measurement) return;
|
|
5248
|
+
|
|
5249
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5250
|
+
this.measurements.delete(params.requestId);
|
|
5251
|
+
}
|
|
5252
|
+
|
|
5253
|
+
markUploadProgress(requestId: string): void {
|
|
5254
|
+
const measurement = this.measurements.get(requestId);
|
|
5255
|
+
if (!measurement) return;
|
|
5256
|
+
if (measurement.stuckReason !== 'upload_no_progress') return;
|
|
5257
|
+
|
|
5258
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5259
|
+
measurement.timer = this.setTimeoutFn(() => this.detect(requestId), FILE_UPLOAD_NO_PROGRESS_TIMEOUT_MS);
|
|
5260
|
+
}
|
|
5261
|
+
|
|
5262
|
+
clear(): void {
|
|
5263
|
+
this.measurements.forEach((measurement) => clearTimer(measurement.timer, this.clearTimeoutFn));
|
|
5264
|
+
this.measurements.clear();
|
|
5265
|
+
}
|
|
5266
|
+
|
|
5267
|
+
clearChannel(channelUrl?: string): void {
|
|
5268
|
+
if (!channelUrl) return;
|
|
5269
|
+
this.measurements.forEach((measurement, requestId) => {
|
|
5270
|
+
if (measurement.channelUrl !== channelUrl) return;
|
|
5271
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5272
|
+
this.measurements.delete(requestId);
|
|
5273
|
+
});
|
|
5274
|
+
}
|
|
5275
|
+
|
|
5276
|
+
private detect(requestId: string): void {
|
|
5277
|
+
const measurement = this.measurements.get(requestId);
|
|
5278
|
+
if (!measurement) return;
|
|
5279
|
+
|
|
5280
|
+
this.measurements.delete(requestId);
|
|
5281
|
+
this.commit(requestId, measurement);
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
private commit(requestId: string, measurement: PendingMeasurement): void {
|
|
5285
|
+
append(this.callback, METRIC_KEY_MESSAGE_PENDING_STUCK, measurement.stuckReason, {
|
|
5286
|
+
extra: {
|
|
5287
|
+
message_type: measurement.messageType,
|
|
5288
|
+
request_id: requestId,
|
|
5289
|
+
},
|
|
5290
|
+
});
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
|
|
4654
5294
|
declare interface MessageTemplateCache {
|
|
4655
5295
|
set(key: string, value: string): void;
|
|
4656
5296
|
get(key: string): string | null;
|
|
@@ -4840,6 +5480,18 @@ declare type MessengerSettingsUpdatedParams =
|
|
|
4840
5480
|
| { settings: MessengerSettingsResponse; source: 'server' }
|
|
4841
5481
|
| { settings: CachedMessengerSettings; source: 'cache' };
|
|
4842
5482
|
|
|
5483
|
+
declare type MetricCategory = 'handoff' | 'conversation' | 'message' | 'connection';
|
|
5484
|
+
|
|
5485
|
+
/**
|
|
5486
|
+
* An event + action represented as a single value. Input to the emitters.
|
|
5487
|
+
*/
|
|
5488
|
+
declare type MetricSource =
|
|
5489
|
+
| { type: 'connection'; detail: ConnectionEventDetail; userId: string }
|
|
5490
|
+
| { type: 'handoff'; channel: ChatSDKGroupChannel }
|
|
5491
|
+
| { type: 'conversation'; channel: ChatSDKGroupChannel }
|
|
5492
|
+
| { type: 'incomingMessage'; channel: ChatSDKGroupChannel; message: ChatSDKBaseMessage }
|
|
5493
|
+
| { type: 'outgoingMessage'; channel: ChatSDKGroupChannel; message: ChatSDKBaseMessage };
|
|
5494
|
+
|
|
4843
5495
|
declare type MultiSelectField = {
|
|
4844
5496
|
key: string;
|
|
4845
5497
|
type: 'multi-select';
|
|
@@ -4915,6 +5567,7 @@ export declare const OutgoingMessageLayout: {
|
|
|
4915
5567
|
MediaMessageBody: typeof OutgoingImageBody;
|
|
4916
5568
|
FileMessageBody: typeof OutgoingFileBody;
|
|
4917
5569
|
MultipleFilesMessageBody: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
5570
|
+
MessageLogs: (_: OutgoingMessageProps) => ReactNode;
|
|
4918
5571
|
};
|
|
4919
5572
|
};
|
|
4920
5573
|
Template: ({ template, children }: {
|
|
@@ -4929,6 +5582,7 @@ export declare const OutgoingMessageLayout: {
|
|
|
4929
5582
|
MediaMessageBody: typeof OutgoingImageBody;
|
|
4930
5583
|
FileMessageBody: typeof OutgoingFileBody;
|
|
4931
5584
|
MultipleFilesMessageBody: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
5585
|
+
MessageLogs: (_: OutgoingMessageProps) => ReactNode;
|
|
4932
5586
|
}>>;
|
|
4933
5587
|
useContext: () => LayoutContextValue<OutgoingMessageProps, {
|
|
4934
5588
|
SendingStatus: ({ sendingStatus }: OutgoingMessageProps) => ReactNode;
|
|
@@ -4938,6 +5592,7 @@ export declare const OutgoingMessageLayout: {
|
|
|
4938
5592
|
MediaMessageBody: typeof OutgoingImageBody;
|
|
4939
5593
|
FileMessageBody: typeof OutgoingFileBody;
|
|
4940
5594
|
MultipleFilesMessageBody: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
5595
|
+
MessageLogs: (_: OutgoingMessageProps) => ReactNode;
|
|
4941
5596
|
}>;
|
|
4942
5597
|
} & {
|
|
4943
5598
|
SentTime: (props: {
|
|
@@ -4958,6 +5613,9 @@ export declare const OutgoingMessageLayout: {
|
|
|
4958
5613
|
MultipleFilesMessageBody: (props: {
|
|
4959
5614
|
component: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
4960
5615
|
}) => null;
|
|
5616
|
+
MessageLogs: (props: {
|
|
5617
|
+
component: (_: OutgoingMessageProps) => ReactNode;
|
|
5618
|
+
}) => null;
|
|
4961
5619
|
SendingStatus: (props: {
|
|
4962
5620
|
component: ({ sendingStatus }: OutgoingMessageProps) => ReactNode;
|
|
4963
5621
|
}) => null;
|
|
@@ -5035,6 +5693,31 @@ declare interface PaletteTextEmphasis {
|
|
|
5035
5693
|
textDisabled: string;
|
|
5036
5694
|
}
|
|
5037
5695
|
|
|
5696
|
+
declare type PendingAIResponseRaw = {
|
|
5697
|
+
messageId: number;
|
|
5698
|
+
channelUrl?: string;
|
|
5699
|
+
aiResponse: AIResponseMessageMetadata;
|
|
5700
|
+
firstChunkAt: number;
|
|
5701
|
+
};
|
|
5702
|
+
|
|
5703
|
+
declare type PendingAIResponseStart = {
|
|
5704
|
+
channelUrl?: string;
|
|
5705
|
+
startedAt: number;
|
|
5706
|
+
};
|
|
5707
|
+
|
|
5708
|
+
declare type PendingMeasurement = {
|
|
5709
|
+
channelUrl?: string;
|
|
5710
|
+
messageType: PendingMessageType;
|
|
5711
|
+
stuckReason: PendingStuckReason;
|
|
5712
|
+
timer: TimeoutId | null;
|
|
5713
|
+
};
|
|
5714
|
+
|
|
5715
|
+
declare type PendingMessageType = 'text' | 'file_image' | 'file_video' | 'file_document' | 'file_audio';
|
|
5716
|
+
|
|
5717
|
+
declare type PendingStuckReason = 'pending_too_long' | 'upload_no_progress';
|
|
5718
|
+
|
|
5719
|
+
declare type PendingStuckTrackerOptions = Pick<TimedTrackerOptions, 'setTimeout' | 'clearTimeout'>;
|
|
5720
|
+
|
|
5038
5721
|
declare type PickMessageProps<
|
|
5039
5722
|
Union,
|
|
5040
5723
|
T extends Union extends { messageType: infer MT } ? MT : never = Union extends { messageType: infer MT } ? MT : never,
|
|
@@ -5115,7 +5798,7 @@ export declare type PositionHorizontal = 'start' | 'end';
|
|
|
5115
5798
|
|
|
5116
5799
|
export declare type PositionVertical = 'top' | 'bottom';
|
|
5117
5800
|
|
|
5118
|
-
declare type PresentMethod = 'launcher_toggle' | 'direct_present';
|
|
5801
|
+
declare type PresentMethod = 'launcher_toggle' | 'direct_present' | 'embedded';
|
|
5119
5802
|
|
|
5120
5803
|
declare type Props = {
|
|
5121
5804
|
children: ReactNode;
|
|
@@ -5172,6 +5855,120 @@ declare type Props_2 = {
|
|
|
5172
5855
|
|
|
5173
5856
|
declare type ReactOnlyIconName = 'expand' | 'collapse' | 'chevron-right' | 'attach' | 'close-filled' | 'actionbook' | 'function' | 'confluence' | 'zendesk' | 'salesforce' | 'sprinklr' | 'website' | 'snippet' | 'template' | 'show' | 'mute' | 'activity';
|
|
5174
5857
|
|
|
5858
|
+
declare type RecoveryMeasurement = {
|
|
5859
|
+
timer: TimeoutId | null;
|
|
5860
|
+
startedAt: number | null;
|
|
5861
|
+
triggerType: RecoveryTriggerType | null;
|
|
5862
|
+
inputEnabled: boolean;
|
|
5863
|
+
};
|
|
5864
|
+
|
|
5865
|
+
declare class RecoveryNotInteractiveTimeoutStatsTracker {
|
|
5866
|
+
private readonly callback: StatsAppendCallback;
|
|
5867
|
+
private readonly clock: StatsClock;
|
|
5868
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
5869
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
5870
|
+
private readonly measurements = new Map<string, RecoveryMeasurement>();
|
|
5871
|
+
|
|
5872
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
5873
|
+
this.callback = callback;
|
|
5874
|
+
this.clock = getClock(options);
|
|
5875
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
5876
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
start(triggerType: RecoveryTriggerType, channelUrl?: string): void {
|
|
5880
|
+
const key = this.getKey(channelUrl);
|
|
5881
|
+
let measurement = this.measurements.get(key);
|
|
5882
|
+
if (measurement && measurement.startedAt !== null) return;
|
|
5883
|
+
if (measurement?.inputEnabled) return;
|
|
5884
|
+
|
|
5885
|
+
measurement = {
|
|
5886
|
+
startedAt: this.clock.now(),
|
|
5887
|
+
triggerType,
|
|
5888
|
+
inputEnabled: measurement?.inputEnabled ?? false,
|
|
5889
|
+
timer: this.setTimeoutFn(() => this.commitTimeout(key), 10_000),
|
|
5890
|
+
};
|
|
5891
|
+
this.measurements.set(key, measurement);
|
|
5892
|
+
}
|
|
5893
|
+
|
|
5894
|
+
setInputEnabled(enabled: boolean, channelUrl?: string): void {
|
|
5895
|
+
const key = this.getKey(channelUrl);
|
|
5896
|
+
let measurement = this.measurements.get(key);
|
|
5897
|
+
|
|
5898
|
+
if (!measurement) {
|
|
5899
|
+
if (!enabled) return;
|
|
5900
|
+
measurement = {
|
|
5901
|
+
startedAt: null,
|
|
5902
|
+
triggerType: null,
|
|
5903
|
+
inputEnabled: enabled,
|
|
5904
|
+
timer: null,
|
|
5905
|
+
};
|
|
5906
|
+
this.measurements.set(key, measurement);
|
|
5907
|
+
}
|
|
5908
|
+
|
|
5909
|
+
measurement.inputEnabled = enabled;
|
|
5910
|
+
if (enabled) {
|
|
5911
|
+
this.cancelMeasurement(measurement);
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
cancelPending(channelUrl: string): void {
|
|
5916
|
+
const key = this.getKey(channelUrl);
|
|
5917
|
+
const measurement = this.measurements.get(key);
|
|
5918
|
+
if (!measurement) return;
|
|
5919
|
+
this.cancelMeasurement(measurement);
|
|
5920
|
+
if (!measurement.inputEnabled) this.measurements.delete(key);
|
|
5921
|
+
}
|
|
5922
|
+
|
|
5923
|
+
clear(): void {
|
|
5924
|
+
this.measurements.forEach((measurement) => clearTimer(measurement.timer, this.clearTimeoutFn));
|
|
5925
|
+
this.measurements.clear();
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5928
|
+
clearChannel(channelUrl?: string): void {
|
|
5929
|
+
if (channelUrl === undefined) return;
|
|
5930
|
+
this.clearKey(this.getKey(channelUrl));
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5933
|
+
private clearKey(key: string): void {
|
|
5934
|
+
const measurement = this.measurements.get(key);
|
|
5935
|
+
if (!measurement) return;
|
|
5936
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5937
|
+
this.measurements.delete(key);
|
|
5938
|
+
}
|
|
5939
|
+
|
|
5940
|
+
private commitTimeout(key: string): void {
|
|
5941
|
+
const measurement = this.measurements.get(key);
|
|
5942
|
+
if (!measurement || measurement.startedAt === null || measurement.triggerType === null) return;
|
|
5943
|
+
if (measurement.inputEnabled) {
|
|
5944
|
+
this.cancelMeasurement(measurement);
|
|
5945
|
+
return;
|
|
5946
|
+
}
|
|
5947
|
+
const duration = this.clock.now() - measurement.startedAt;
|
|
5948
|
+
const triggerType = measurement.triggerType;
|
|
5949
|
+
// Drop the entry so a subsequent trigger can record a fresh cycle.
|
|
5950
|
+
this.measurements.delete(key);
|
|
5951
|
+
append(this.callback, METRIC_KEY_RECOVERY_NOT_INTERACTIVE_TIMEOUT, duration, {
|
|
5952
|
+
extra: {
|
|
5953
|
+
trigger_type: triggerType,
|
|
5954
|
+
},
|
|
5955
|
+
});
|
|
5956
|
+
}
|
|
5957
|
+
|
|
5958
|
+
private cancelMeasurement(measurement: RecoveryMeasurement): void {
|
|
5959
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5960
|
+
measurement.timer = null;
|
|
5961
|
+
measurement.startedAt = null;
|
|
5962
|
+
measurement.triggerType = null;
|
|
5963
|
+
}
|
|
5964
|
+
|
|
5965
|
+
private getKey(channelUrl?: string): string {
|
|
5966
|
+
return channelUrl ?? '__default__';
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
|
|
5970
|
+
declare type RecoveryTriggerType = 'foreground_resume' | 'ws_reconnected';
|
|
5971
|
+
|
|
5175
5972
|
declare type ReleaseDisabledByValue =
|
|
5176
5973
|
| 'unavailable'
|
|
5177
5974
|
| 'form_active'
|
|
@@ -5182,6 +5979,39 @@ declare type ReleaseDisabledByValue =
|
|
|
5182
5979
|
| 'reconnecting'
|
|
5183
5980
|
| 'handoff_pending';
|
|
5184
5981
|
|
|
5982
|
+
declare class RenderedContentUnusableStatsTracker {
|
|
5983
|
+
private readonly callback: StatsAppendCallback;
|
|
5984
|
+
private readonly committed = new Set<string>();
|
|
5985
|
+
|
|
5986
|
+
constructor(callback: StatsAppendCallback) {
|
|
5987
|
+
this.callback = callback;
|
|
5988
|
+
}
|
|
5989
|
+
|
|
5990
|
+
commit(params: {
|
|
5991
|
+
messageId: string | number;
|
|
5992
|
+
contentKind: 'text' | 'image' | 'video' | 'audio' | 'template' | 'form';
|
|
5993
|
+
errorDomain: 'empty_render' | 'template_error' | 'form_error';
|
|
5994
|
+
error?: ErrorLike;
|
|
5995
|
+
}): void {
|
|
5996
|
+
const key = `${params.messageId}:${params.contentKind}:${params.errorDomain}`;
|
|
5997
|
+
if (this.committed.has(key)) return;
|
|
5998
|
+
evictOldestIfAtLimit(this.committed, RENDERED_CONTENT_UNUSABLE_COMMITTED_LIMIT);
|
|
5999
|
+
this.committed.add(key);
|
|
6000
|
+
|
|
6001
|
+
append(this.callback, METRIC_KEY_RENDERED_CONTENT_UNUSABLE, params.errorDomain, {
|
|
6002
|
+
error: params.error ? { code: params.error.code, message: params.errorDomain } : undefined,
|
|
6003
|
+
extra: {
|
|
6004
|
+
content_kind: params.contentKind,
|
|
6005
|
+
message_id: String(params.messageId),
|
|
6006
|
+
},
|
|
6007
|
+
});
|
|
6008
|
+
}
|
|
6009
|
+
|
|
6010
|
+
clear(): void {
|
|
6011
|
+
this.committed.clear();
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
|
|
5185
6015
|
declare interface ResolvedHandlers extends AgentClientHandlers {
|
|
5186
6016
|
onClickLink: (params: {
|
|
5187
6017
|
url: string;
|
|
@@ -5223,6 +6053,15 @@ declare type SingleSelectField = {
|
|
|
5223
6053
|
|
|
5224
6054
|
declare type StatsAppendCallback = (type: string, data: AIAgentStatPayload) => boolean;
|
|
5225
6055
|
|
|
6056
|
+
declare interface StatsClock {
|
|
6057
|
+
now: () => number;
|
|
6058
|
+
}
|
|
6059
|
+
|
|
6060
|
+
declare type StatsPayloadError = {
|
|
6061
|
+
code?: number;
|
|
6062
|
+
message?: string;
|
|
6063
|
+
};
|
|
6064
|
+
|
|
5226
6065
|
/** Per-event override for the platform `announceStatus` wrappers — same shape as `AnnounceOptions`. */
|
|
5227
6066
|
declare type StatusAnnouncementOptions = AnnounceOptions;
|
|
5228
6067
|
|
|
@@ -5276,6 +6115,251 @@ declare enum StewardTaskStatus {
|
|
|
5276
6115
|
CANCELED = 'CANCELED',
|
|
5277
6116
|
}
|
|
5278
6117
|
|
|
6118
|
+
declare class StreamStalledStatsTracker {
|
|
6119
|
+
private readonly callback: StatsAppendCallback;
|
|
6120
|
+
private readonly clock: StatsClock;
|
|
6121
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
6122
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
6123
|
+
private readonly streams = new Map<number, StreamState>();
|
|
6124
|
+
private readonly emittedMessageChannels = new Map<number, string | undefined>();
|
|
6125
|
+
|
|
6126
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
6127
|
+
this.callback = callback;
|
|
6128
|
+
this.clock = getClock(options);
|
|
6129
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
6130
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
6131
|
+
}
|
|
6132
|
+
|
|
6133
|
+
markRawProgress(params: {
|
|
6134
|
+
messageId: number;
|
|
6135
|
+
channelUrl?: string;
|
|
6136
|
+
textLength: number;
|
|
6137
|
+
aiResponse: AIResponseMessageMetadata;
|
|
6138
|
+
}): void {
|
|
6139
|
+
if (!params.aiResponse.isStreaming) {
|
|
6140
|
+
this.completeDelivery(params.messageId, params.textLength);
|
|
6141
|
+
return;
|
|
6142
|
+
}
|
|
6143
|
+
|
|
6144
|
+
if (this.emittedMessageChannels.has(params.messageId)) return;
|
|
6145
|
+
|
|
6146
|
+
const stream = this.getOrCreate(params.messageId, params.channelUrl);
|
|
6147
|
+
|
|
6148
|
+
if (params.textLength > stream.lastRawLength) {
|
|
6149
|
+
stream.chunksReceived += 1;
|
|
6150
|
+
stream.lastRawLength = params.textLength;
|
|
6151
|
+
stream.lastRawAt = this.clock.now();
|
|
6152
|
+
|
|
6153
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6154
|
+
stream.deliveryTimer = null;
|
|
6155
|
+
this.scheduleDeliveryStallCheck(params.messageId, stream);
|
|
6156
|
+
} else {
|
|
6157
|
+
this.scheduleDeliveryStallCheck(params.messageId, stream);
|
|
6158
|
+
}
|
|
6159
|
+
|
|
6160
|
+
if (stream.lastRenderedLength < stream.lastRawLength && stream.renderTimer === null) {
|
|
6161
|
+
stream.lastRenderAt = this.clock.now();
|
|
6162
|
+
this.scheduleRenderStallCheck(params.messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS);
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
|
|
6166
|
+
markRenderedProgress(params: { messageId: number; displayedLength: number }): void {
|
|
6167
|
+
const stream = this.streams.get(params.messageId);
|
|
6168
|
+
if (!stream || params.displayedLength <= stream.lastRenderedLength) return;
|
|
6169
|
+
|
|
6170
|
+
stream.lastRenderedLength = params.displayedLength;
|
|
6171
|
+
stream.lastRenderAt = this.clock.now();
|
|
6172
|
+
|
|
6173
|
+
if (stream.lastRenderedLength >= stream.lastRawLength) {
|
|
6174
|
+
if (stream.deliveryTimer === null) {
|
|
6175
|
+
this.clearStream(params.messageId, stream);
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
|
|
6179
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6180
|
+
stream.renderTimer = null;
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
|
|
6184
|
+
this.scheduleRenderStallCheck(params.messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS);
|
|
6185
|
+
}
|
|
6186
|
+
|
|
6187
|
+
hasPendingStream(messageId: number): boolean {
|
|
6188
|
+
return this.streams.has(messageId);
|
|
6189
|
+
}
|
|
6190
|
+
|
|
6191
|
+
complete(messageId: number): void {
|
|
6192
|
+
const stream = this.streams.get(messageId);
|
|
6193
|
+
if (stream) this.clearStream(messageId, stream);
|
|
6194
|
+
}
|
|
6195
|
+
|
|
6196
|
+
// Final stream update: no more raw chunks are coming, so stop delivery-stall
|
|
6197
|
+
// tracking. The animation can still be catching up after streaming ends
|
|
6198
|
+
// (useStreamingText keeps animating while displayedLength < text.length), so
|
|
6199
|
+
// keep the render-stall watch alive until rendered progress reaches the last
|
|
6200
|
+
// raw length — otherwise a renderer that freezes mid-catch-up never emits a
|
|
6201
|
+
// 'render' stall.
|
|
6202
|
+
private completeDelivery(messageId: number, finalRawLength: number): void {
|
|
6203
|
+
const stream = this.streams.get(messageId);
|
|
6204
|
+
if (!stream) return;
|
|
6205
|
+
|
|
6206
|
+
// The final stream:false payload can carry text beyond the last streamed
|
|
6207
|
+
// chunk; fold it into the raw length first so the render watch targets the
|
|
6208
|
+
// full text the animation still has to reveal (otherwise the stream could be
|
|
6209
|
+
// cleared while a freeze on that trailing text would go unreported).
|
|
6210
|
+
if (finalRawLength > stream.lastRawLength) {
|
|
6211
|
+
stream.lastRawLength = finalRawLength;
|
|
6212
|
+
stream.lastRawAt = this.clock.now();
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6215
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6216
|
+
stream.deliveryTimer = null;
|
|
6217
|
+
|
|
6218
|
+
if (stream.lastRenderedLength >= stream.lastRawLength) {
|
|
6219
|
+
this.clearStream(messageId, stream);
|
|
6220
|
+
return;
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6223
|
+
if (stream.renderTimer === null) {
|
|
6224
|
+
stream.lastRenderAt = this.clock.now();
|
|
6225
|
+
this.scheduleRenderStallCheck(messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS);
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
|
|
6229
|
+
clearMessage(messageId: number): void {
|
|
6230
|
+
const stream = this.streams.get(messageId);
|
|
6231
|
+
if (stream) this.clearStream(messageId, stream);
|
|
6232
|
+
this.emittedMessageChannels.delete(messageId);
|
|
6233
|
+
}
|
|
6234
|
+
|
|
6235
|
+
clear(): void {
|
|
6236
|
+
this.streams.forEach((stream) => {
|
|
6237
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6238
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6239
|
+
});
|
|
6240
|
+
this.streams.clear();
|
|
6241
|
+
this.emittedMessageChannels.clear();
|
|
6242
|
+
}
|
|
6243
|
+
|
|
6244
|
+
clearChannel(channelUrl?: string): void {
|
|
6245
|
+
if (!channelUrl) return;
|
|
6246
|
+
this.streams.forEach((stream, messageId) => {
|
|
6247
|
+
if (stream.channelUrl !== channelUrl && stream.channelUrl !== undefined) return;
|
|
6248
|
+
this.clearStream(messageId, stream);
|
|
6249
|
+
});
|
|
6250
|
+
this.emittedMessageChannels.forEach((emittedChannelUrl, messageId) => {
|
|
6251
|
+
if (emittedChannelUrl === channelUrl || emittedChannelUrl === undefined)
|
|
6252
|
+
this.emittedMessageChannels.delete(messageId);
|
|
6253
|
+
});
|
|
6254
|
+
}
|
|
6255
|
+
|
|
6256
|
+
private detectDelivery(messageId: number): void {
|
|
6257
|
+
const stream = this.streams.get(messageId);
|
|
6258
|
+
if (!stream) return;
|
|
6259
|
+
stream.deliveryTimer = null;
|
|
6260
|
+
// Mutex: cancel any in-flight render stall check so delivery wins this turn.
|
|
6261
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6262
|
+
stream.renderTimer = null;
|
|
6263
|
+
this.emit(messageId, stream, 'delivery');
|
|
6264
|
+
}
|
|
6265
|
+
|
|
6266
|
+
private detectRender(messageId: number): void {
|
|
6267
|
+
const stream = this.streams.get(messageId);
|
|
6268
|
+
if (!stream) return;
|
|
6269
|
+
stream.renderTimer = null;
|
|
6270
|
+
if (stream.lastRenderedLength >= stream.lastRawLength) return;
|
|
6271
|
+
|
|
6272
|
+
const elapsedSinceRender = this.clock.now() - stream.lastRenderAt;
|
|
6273
|
+
if (elapsedSinceRender < STREAM_RENDER_STALL_THRESHOLD_MS) {
|
|
6274
|
+
this.scheduleRenderStallCheck(messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS - elapsedSinceRender);
|
|
6275
|
+
return;
|
|
6276
|
+
}
|
|
6277
|
+
|
|
6278
|
+
// Mutex rule: when raw progress has been silent for at least the render threshold,
|
|
6279
|
+
// delivery is the root cause and will win. Defer the render emit until delivery has
|
|
6280
|
+
// had a chance to fire so we don't surface render as the symptom.
|
|
6281
|
+
const elapsedSinceRaw = this.clock.now() - stream.lastRawAt;
|
|
6282
|
+
if (elapsedSinceRaw >= STREAM_RENDER_STALL_THRESHOLD_MS && stream.deliveryTimer !== null) {
|
|
6283
|
+
const untilDelivery = STREAM_DELIVERY_STALL_THRESHOLD_MS - elapsedSinceRaw;
|
|
6284
|
+
if (untilDelivery > 0) {
|
|
6285
|
+
this.scheduleRenderStallCheck(messageId, stream, untilDelivery);
|
|
6286
|
+
return;
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
|
|
6290
|
+
this.emit(messageId, stream, 'render');
|
|
6291
|
+
}
|
|
6292
|
+
|
|
6293
|
+
private scheduleRenderStallCheck(messageId: number, stream: StreamState, delayMs: number): void {
|
|
6294
|
+
if (stream.renderTimer !== null) return;
|
|
6295
|
+
stream.renderTimer = this.setTimeoutFn(() => this.detectRender(messageId), delayMs);
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6298
|
+
private scheduleDeliveryStallCheck(messageId: number, stream: StreamState): void {
|
|
6299
|
+
if (stream.deliveryTimer !== null) return;
|
|
6300
|
+
stream.deliveryTimer = this.setTimeoutFn(() => this.detectDelivery(messageId), STREAM_DELIVERY_STALL_THRESHOLD_MS);
|
|
6301
|
+
}
|
|
6302
|
+
|
|
6303
|
+
private emit(messageId: number, stream: StreamState, stallKind: StreamStallKind): void {
|
|
6304
|
+
if (this.emittedMessageChannels.has(messageId)) return;
|
|
6305
|
+
|
|
6306
|
+
const stallStartedAt = stallKind === 'delivery' ? stream.lastRawAt : stream.lastRenderAt;
|
|
6307
|
+
append(this.callback, METRIC_KEY_STREAM_STALLED, this.clock.now() - stallStartedAt, {
|
|
6308
|
+
extra: {
|
|
6309
|
+
message_id: String(messageId),
|
|
6310
|
+
stall_kind: stallKind,
|
|
6311
|
+
},
|
|
6312
|
+
});
|
|
6313
|
+
this.rememberEmitted(messageId, stream.channelUrl);
|
|
6314
|
+
this.clearStream(messageId, stream);
|
|
6315
|
+
}
|
|
6316
|
+
|
|
6317
|
+
private getOrCreate(messageId: number, channelUrl: string | undefined): StreamState {
|
|
6318
|
+
const existing = this.streams.get(messageId);
|
|
6319
|
+
if (existing) return existing;
|
|
6320
|
+
|
|
6321
|
+
const now = this.clock.now();
|
|
6322
|
+
const stream: StreamState = {
|
|
6323
|
+
channelUrl,
|
|
6324
|
+
chunksReceived: 0,
|
|
6325
|
+
lastRawAt: now,
|
|
6326
|
+
lastRenderAt: now,
|
|
6327
|
+
lastRawLength: 0,
|
|
6328
|
+
lastRenderedLength: 0,
|
|
6329
|
+
deliveryTimer: null,
|
|
6330
|
+
renderTimer: null,
|
|
6331
|
+
};
|
|
6332
|
+
this.streams.set(messageId, stream);
|
|
6333
|
+
return stream;
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
private rememberEmitted(messageId: number, channelUrl: string | undefined): void {
|
|
6337
|
+
if (!this.emittedMessageChannels.has(messageId)) {
|
|
6338
|
+
evictOldestIfAtLimit(this.emittedMessageChannels, STREAM_STALLED_EMITTED_LIMIT);
|
|
6339
|
+
}
|
|
6340
|
+
this.emittedMessageChannels.set(messageId, channelUrl);
|
|
6341
|
+
}
|
|
6342
|
+
|
|
6343
|
+
private clearStream(messageId: number, stream: StreamState): void {
|
|
6344
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6345
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6346
|
+
this.streams.delete(messageId);
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
|
|
6350
|
+
declare type StreamStallKind = 'delivery' | 'render';
|
|
6351
|
+
|
|
6352
|
+
declare type StreamState = {
|
|
6353
|
+
channelUrl?: string;
|
|
6354
|
+
chunksReceived: number;
|
|
6355
|
+
lastRawAt: number;
|
|
6356
|
+
lastRenderAt: number;
|
|
6357
|
+
lastRawLength: number;
|
|
6358
|
+
lastRenderedLength: number;
|
|
6359
|
+
deliveryTimer: TimeoutId | null;
|
|
6360
|
+
renderTimer: TimeoutId | null;
|
|
6361
|
+
};
|
|
6362
|
+
|
|
5279
6363
|
/**
|
|
5280
6364
|
* StringSet type for React components
|
|
5281
6365
|
* Uses SNAKE_CASE keys for backward compatibility
|
|
@@ -5550,11 +6634,248 @@ declare type TextField = {
|
|
|
5550
6634
|
};
|
|
5551
6635
|
};
|
|
5552
6636
|
|
|
6637
|
+
declare type TimedTrackerOptions = TrackerOptions & {
|
|
6638
|
+
setTimeout?: typeof setTimeout;
|
|
6639
|
+
clearTimeout?: typeof clearTimeout;
|
|
6640
|
+
};
|
|
6641
|
+
|
|
6642
|
+
declare type TimeoutId = ReturnType<typeof setTimeout>;
|
|
6643
|
+
|
|
5553
6644
|
declare interface TimerData {
|
|
5554
6645
|
startTime: number | null;
|
|
5555
6646
|
endTime: number | null;
|
|
5556
6647
|
}
|
|
5557
6648
|
|
|
6649
|
+
declare type TrackerOptions = {
|
|
6650
|
+
clock?: StatsClock;
|
|
6651
|
+
};
|
|
6652
|
+
|
|
6653
|
+
declare class TypingIndicatorAbsentTimeoutStatsTracker {
|
|
6654
|
+
private readonly callback: StatsAppendCallback;
|
|
6655
|
+
private readonly clock: StatsClock;
|
|
6656
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
6657
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
6658
|
+
private readonly turns = new Map<string, TypingTurn>();
|
|
6659
|
+
private readonly pendingAIResponseStartsByUserMessageId = new Map<number, PendingAIResponseStart>();
|
|
6660
|
+
|
|
6661
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
6662
|
+
this.callback = callback;
|
|
6663
|
+
this.clock = getClock(options);
|
|
6664
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
6665
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
6666
|
+
}
|
|
6667
|
+
|
|
6668
|
+
startTurn(params: { requestId: string; channelUrl?: string }): void {
|
|
6669
|
+
this.cancelTurn(params.requestId);
|
|
6670
|
+
const turn: TypingTurn = {
|
|
6671
|
+
requestId: params.requestId,
|
|
6672
|
+
channelUrl: params.channelUrl,
|
|
6673
|
+
beforeTpstStartedAt: this.clock.now(),
|
|
6674
|
+
afterTpenStartedAt: null,
|
|
6675
|
+
timer: null,
|
|
6676
|
+
};
|
|
6677
|
+
turn.timer = this.setTimeoutFn(() => this.commitTimeout(turn.requestId), TYPING_COLLECTION_TIMEOUT_MS);
|
|
6678
|
+
this.turns.set(params.requestId, turn);
|
|
6679
|
+
}
|
|
6680
|
+
|
|
6681
|
+
markUserMessageAck(params: { requestId: string; messageId: number }): void {
|
|
6682
|
+
const turn = this.turns.get(params.requestId);
|
|
6683
|
+
if (!turn) return;
|
|
6684
|
+
turn.userMessageId = params.messageId;
|
|
6685
|
+
|
|
6686
|
+
const pending = this.pendingAIResponseStartsByUserMessageId.get(params.messageId);
|
|
6687
|
+
if (!pending) return;
|
|
6688
|
+
this.pendingAIResponseStartsByUserMessageId.delete(params.messageId);
|
|
6689
|
+
this.applyAIResponseStarted(
|
|
6690
|
+
{ channelUrl: pending.channelUrl, matchesUserMessage: true, userMessageId: params.messageId },
|
|
6691
|
+
pending.startedAt,
|
|
6692
|
+
);
|
|
6693
|
+
}
|
|
6694
|
+
|
|
6695
|
+
replaceTurnRequestId(currentRequestId: string, nextRequestId: string): void {
|
|
6696
|
+
if (currentRequestId === nextRequestId) return;
|
|
6697
|
+
|
|
6698
|
+
const turn = this.turns.get(currentRequestId);
|
|
6699
|
+
if (!turn) return;
|
|
6700
|
+
|
|
6701
|
+
this.turns.delete(currentRequestId);
|
|
6702
|
+
turn.requestId = nextRequestId;
|
|
6703
|
+
this.turns.set(nextRequestId, turn);
|
|
6704
|
+
}
|
|
6705
|
+
|
|
6706
|
+
cancelTurn(requestId: string): void {
|
|
6707
|
+
this.cleanupTurn(requestId);
|
|
6708
|
+
}
|
|
6709
|
+
|
|
6710
|
+
markTypingStarted(channelUrl?: string): void {
|
|
6711
|
+
const now = this.clock.now();
|
|
6712
|
+
this.turns.forEach((turn) => {
|
|
6713
|
+
if (!matchesChannel(turn, channelUrl)) return;
|
|
6714
|
+
this.recordAfterTpen(turn, now);
|
|
6715
|
+
this.recordBeforeTpst(turn, now);
|
|
6716
|
+
});
|
|
6717
|
+
}
|
|
6718
|
+
|
|
6719
|
+
markTypingEnded(channelUrl?: string): void {
|
|
6720
|
+
const now = this.clock.now();
|
|
6721
|
+
this.turns.forEach((turn) => {
|
|
6722
|
+
if (!matchesChannel(turn, channelUrl)) return;
|
|
6723
|
+
this.recordBeforeTpst(turn, now);
|
|
6724
|
+
turn.afterTpenStartedAt = now;
|
|
6725
|
+
});
|
|
6726
|
+
}
|
|
6727
|
+
|
|
6728
|
+
markAIResponseStarted(params: { channelUrl?: string; userMessageId?: number } = {}): void {
|
|
6729
|
+
const channelUrl = params.channelUrl;
|
|
6730
|
+
const userMessageId = params.userMessageId;
|
|
6731
|
+
const matchesUserMessage = userMessageId !== undefined;
|
|
6732
|
+
const now = this.clock.now();
|
|
6733
|
+
const matched = this.applyAIResponseStarted({ channelUrl, matchesUserMessage, userMessageId }, now);
|
|
6734
|
+
if (matched || !matchesUserMessage || userMessageId === undefined || !this.hasUnackedTurn(channelUrl)) return;
|
|
6735
|
+
|
|
6736
|
+
if (!this.pendingAIResponseStartsByUserMessageId.has(userMessageId)) {
|
|
6737
|
+
evictOldestIfAtLimit(this.pendingAIResponseStartsByUserMessageId, 100);
|
|
6738
|
+
this.pendingAIResponseStartsByUserMessageId.set(userMessageId, { channelUrl, startedAt: now });
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
|
|
6742
|
+
private applyAIResponseStarted(
|
|
6743
|
+
params: { channelUrl?: string; matchesUserMessage: boolean; userMessageId?: number },
|
|
6744
|
+
startedAt: number,
|
|
6745
|
+
): boolean {
|
|
6746
|
+
if (!params.matchesUserMessage) {
|
|
6747
|
+
const match = this.findSingleTurn(params.channelUrl);
|
|
6748
|
+
if (!match) return false;
|
|
6749
|
+
this.completeTurn(match.requestId, match.turn, startedAt);
|
|
6750
|
+
return true;
|
|
6751
|
+
}
|
|
6752
|
+
|
|
6753
|
+
let matched = false;
|
|
6754
|
+
this.turns.forEach((turn, requestId) => {
|
|
6755
|
+
if (!matchesChannel(turn, params.channelUrl)) return;
|
|
6756
|
+
if (params.matchesUserMessage && turn.userMessageId !== params.userMessageId) return;
|
|
6757
|
+
matched = true;
|
|
6758
|
+
this.completeTurn(requestId, turn, startedAt);
|
|
6759
|
+
});
|
|
6760
|
+
return matched;
|
|
6761
|
+
}
|
|
6762
|
+
|
|
6763
|
+
private findSingleTurn(channelUrl?: string): TypingTurnEntry | undefined {
|
|
6764
|
+
let match: TypingTurnEntry | undefined;
|
|
6765
|
+
let matchedCount = 0;
|
|
6766
|
+
this.turns.forEach((turn, requestId) => {
|
|
6767
|
+
if (!matchesChannel(turn, channelUrl)) return;
|
|
6768
|
+
matchedCount += 1;
|
|
6769
|
+
if (matchedCount > 1) return;
|
|
6770
|
+
match = { requestId, turn };
|
|
6771
|
+
});
|
|
6772
|
+
return matchedCount === 1 ? match : undefined;
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6775
|
+
private completeTurn(requestId: string, turn: TypingTurn, startedAt: number): void {
|
|
6776
|
+
this.recordBeforeTpst(turn, startedAt);
|
|
6777
|
+
this.recordAfterTpen(turn, startedAt);
|
|
6778
|
+
this.commitIfAbsent(requestId, turn);
|
|
6779
|
+
}
|
|
6780
|
+
|
|
6781
|
+
private hasUnackedTurn(channelUrl?: string): boolean {
|
|
6782
|
+
return hasUnackedChannelTurn(this.turns.values(), channelUrl);
|
|
6783
|
+
}
|
|
6784
|
+
|
|
6785
|
+
clear(): void {
|
|
6786
|
+
this.turns.forEach((turn) => {
|
|
6787
|
+
clearTimer(turn.timer, this.clearTimeoutFn);
|
|
6788
|
+
});
|
|
6789
|
+
this.turns.clear();
|
|
6790
|
+
this.pendingAIResponseStartsByUserMessageId.clear();
|
|
6791
|
+
}
|
|
6792
|
+
|
|
6793
|
+
clearChannel(channelUrl?: string): void {
|
|
6794
|
+
if (!channelUrl) return;
|
|
6795
|
+
this.turns.forEach((turn, requestId) => {
|
|
6796
|
+
if (turn.channelUrl !== channelUrl) return;
|
|
6797
|
+
this.cleanupTurn(requestId);
|
|
6798
|
+
});
|
|
6799
|
+
this.pendingAIResponseStartsByUserMessageId.forEach((pending, userMessageId) => {
|
|
6800
|
+
if (pending.channelUrl === channelUrl) this.pendingAIResponseStartsByUserMessageId.delete(userMessageId);
|
|
6801
|
+
});
|
|
6802
|
+
}
|
|
6803
|
+
|
|
6804
|
+
private commitTimeout(requestId: string): void {
|
|
6805
|
+
const turn = this.turns.get(requestId);
|
|
6806
|
+
if (!turn) return;
|
|
6807
|
+
const now = this.clock.now();
|
|
6808
|
+
this.recordBeforeTpst(turn, now);
|
|
6809
|
+
this.recordAfterTpen(turn, now);
|
|
6810
|
+
this.commitIfAbsent(requestId, turn);
|
|
6811
|
+
}
|
|
6812
|
+
|
|
6813
|
+
private recordBeforeTpst(
|
|
6814
|
+
turn: { beforeTpstStartedAt: number | null; beforeTpstAbsentMs?: number },
|
|
6815
|
+
endedAt: number,
|
|
6816
|
+
): void {
|
|
6817
|
+
if (turn.beforeTpstStartedAt === null) return;
|
|
6818
|
+
const duration = endedAt - turn.beforeTpstStartedAt;
|
|
6819
|
+
if (duration >= TYPING_ABSENT_THRESHOLD_MS) turn.beforeTpstAbsentMs = duration;
|
|
6820
|
+
turn.beforeTpstStartedAt = null;
|
|
6821
|
+
}
|
|
6822
|
+
|
|
6823
|
+
private recordAfterTpen(
|
|
6824
|
+
turn: { afterTpenStartedAt: number | null; afterTpenAbsentMs?: number },
|
|
6825
|
+
endedAt: number,
|
|
6826
|
+
): void {
|
|
6827
|
+
if (turn.afterTpenStartedAt === null) return;
|
|
6828
|
+
const duration = endedAt - turn.afterTpenStartedAt;
|
|
6829
|
+
if (duration >= TYPING_ABSENT_THRESHOLD_MS) turn.afterTpenAbsentMs = duration;
|
|
6830
|
+
turn.afterTpenStartedAt = null;
|
|
6831
|
+
}
|
|
6832
|
+
|
|
6833
|
+
private commitIfAbsent(requestId: string, turn: { beforeTpstAbsentMs?: number; afterTpenAbsentMs?: number }): void {
|
|
6834
|
+
const beforeTpstAbsentMs = turn.beforeTpstAbsentMs;
|
|
6835
|
+
const afterTpenAbsentMs = turn.afterTpenAbsentMs;
|
|
6836
|
+
if (beforeTpstAbsentMs === undefined && afterTpenAbsentMs === undefined) {
|
|
6837
|
+
this.cleanupTurn(requestId);
|
|
6838
|
+
return;
|
|
6839
|
+
}
|
|
6840
|
+
|
|
6841
|
+
append(
|
|
6842
|
+
this.callback,
|
|
6843
|
+
METRIC_KEY_AI_TYPING_INDICATOR_ABSENT_TIMEOUT,
|
|
6844
|
+
(beforeTpstAbsentMs ?? 0) + (afterTpenAbsentMs ?? 0),
|
|
6845
|
+
{
|
|
6846
|
+
extra: {
|
|
6847
|
+
...(beforeTpstAbsentMs !== undefined && { before_tpst_absent_ms: beforeTpstAbsentMs }),
|
|
6848
|
+
...(afterTpenAbsentMs !== undefined && { after_tpen_absent_ms: afterTpenAbsentMs }),
|
|
6849
|
+
},
|
|
6850
|
+
},
|
|
6851
|
+
);
|
|
6852
|
+
this.cleanupTurn(requestId);
|
|
6853
|
+
}
|
|
6854
|
+
|
|
6855
|
+
private cleanupTurn(requestId: string): void {
|
|
6856
|
+
const turn = this.turns.get(requestId);
|
|
6857
|
+
if (!turn) return;
|
|
6858
|
+
clearTimer(turn.timer, this.clearTimeoutFn);
|
|
6859
|
+
this.turns.delete(requestId);
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
|
|
6863
|
+
declare type TypingTurn = {
|
|
6864
|
+
requestId: string;
|
|
6865
|
+
channelUrl?: string;
|
|
6866
|
+
userMessageId?: number;
|
|
6867
|
+
beforeTpstStartedAt: number | null;
|
|
6868
|
+
afterTpenStartedAt: number | null;
|
|
6869
|
+
beforeTpstAbsentMs?: number;
|
|
6870
|
+
afterTpenAbsentMs?: number;
|
|
6871
|
+
timer: TimeoutId | null;
|
|
6872
|
+
};
|
|
6873
|
+
|
|
6874
|
+
declare type TypingTurnEntry = {
|
|
6875
|
+
requestId: string;
|
|
6876
|
+
turn: TypingTurn;
|
|
6877
|
+
};
|
|
6878
|
+
|
|
5558
6879
|
declare interface TypographyShape {
|
|
5559
6880
|
h1: TypographyVariant;
|
|
5560
6881
|
h2: TypographyVariant;
|
|
@@ -5630,6 +6951,189 @@ export declare type UserActionStatus =
|
|
|
5630
6951
|
|
|
5631
6952
|
export declare const useRefreshActiveChannel: (updater: () => Promise<void>) => () => Promise<void>;
|
|
5632
6953
|
|
|
6954
|
+
declare class UserPerceivedResponseTimeStatsTracker {
|
|
6955
|
+
private readonly callback: StatsAppendCallback;
|
|
6956
|
+
private readonly clock: StatsClock;
|
|
6957
|
+
private readonly random: () => number;
|
|
6958
|
+
private readonly sampledByRequestId = new Map<string, UserTurn>();
|
|
6959
|
+
private readonly sampledByAIMessageId = new Map<number, UserTurn>();
|
|
6960
|
+
private readonly pendingAIResponseByUserMessageId = new Map<number, PendingAIResponseRaw>();
|
|
6961
|
+
private collectedCount = 0;
|
|
6962
|
+
|
|
6963
|
+
constructor(callback: StatsAppendCallback, options?: TrackerOptions & { random?: () => number }) {
|
|
6964
|
+
this.callback = callback;
|
|
6965
|
+
this.clock = getClock(options);
|
|
6966
|
+
this.random = options?.random ?? Math.random;
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6969
|
+
startTurn(params: { requestId: string; channelUrl?: string }): void {
|
|
6970
|
+
if (this.collectedCount >= 10) return;
|
|
6971
|
+
if (this.random() >= 0.15) return;
|
|
6972
|
+
this.collectedCount += 1;
|
|
6973
|
+
this.sampledByRequestId.set(params.requestId, {
|
|
6974
|
+
requestId: params.requestId,
|
|
6975
|
+
channelUrl: params.channelUrl,
|
|
6976
|
+
startedAt: this.clock.now(),
|
|
6977
|
+
});
|
|
6978
|
+
}
|
|
6979
|
+
|
|
6980
|
+
markUserMessageAck(params: { requestId: string; messageId: number }): void {
|
|
6981
|
+
const turn = this.sampledByRequestId.get(params.requestId);
|
|
6982
|
+
if (!turn) return;
|
|
6983
|
+
|
|
6984
|
+
turn.ackAt = this.clock.now();
|
|
6985
|
+
turn.userMessageId = params.messageId;
|
|
6986
|
+
|
|
6987
|
+
const pending = this.pendingAIResponseByUserMessageId.get(params.messageId);
|
|
6988
|
+
if (!pending) return;
|
|
6989
|
+
this.pendingAIResponseByUserMessageId.delete(params.messageId);
|
|
6990
|
+
this.applyAIResponseRaw(turn, pending);
|
|
6991
|
+
}
|
|
6992
|
+
|
|
6993
|
+
replaceTurnRequestId(currentRequestId: string, nextRequestId: string): void {
|
|
6994
|
+
if (currentRequestId === nextRequestId) return;
|
|
6995
|
+
|
|
6996
|
+
const turn = this.sampledByRequestId.get(currentRequestId);
|
|
6997
|
+
if (!turn) return;
|
|
6998
|
+
|
|
6999
|
+
this.sampledByRequestId.delete(currentRequestId);
|
|
7000
|
+
turn.requestId = nextRequestId;
|
|
7001
|
+
this.sampledByRequestId.set(nextRequestId, turn);
|
|
7002
|
+
}
|
|
7003
|
+
|
|
7004
|
+
cancelTurn(requestId: string): void {
|
|
7005
|
+
const turn = this.sampledByRequestId.get(requestId);
|
|
7006
|
+
if (!turn) return;
|
|
7007
|
+
this.deleteTurn(turn);
|
|
7008
|
+
// Return the unused sample slot so cancelled turns don't burn the per-session cap.
|
|
7009
|
+
if (this.collectedCount > 0) this.collectedCount -= 1;
|
|
7010
|
+
}
|
|
7011
|
+
|
|
7012
|
+
markAIResponseRaw(params: { messageId: number; channelUrl?: string; aiResponse: AIResponseMessageMetadata }): void {
|
|
7013
|
+
const firstChunkAt = this.clock.now();
|
|
7014
|
+
const userMessageId = params.aiResponse.userMessageId;
|
|
7015
|
+
if (userMessageId === undefined && this.hasUnackedTurn(params.channelUrl)) return;
|
|
7016
|
+
|
|
7017
|
+
const turn = this.findPendingTurn(params.channelUrl, userMessageId);
|
|
7018
|
+
if (turn) {
|
|
7019
|
+
this.applyAIResponseRaw(turn, { ...params, firstChunkAt });
|
|
7020
|
+
return;
|
|
7021
|
+
}
|
|
7022
|
+
|
|
7023
|
+
if (userMessageId === undefined || !this.hasUnackedTurn(params.channelUrl)) return;
|
|
7024
|
+
this.bufferAIResponseRaw(
|
|
7025
|
+
{
|
|
7026
|
+
messageId: params.messageId,
|
|
7027
|
+
...(params.channelUrl !== undefined && { channelUrl: params.channelUrl }),
|
|
7028
|
+
aiResponse: params.aiResponse,
|
|
7029
|
+
firstChunkAt,
|
|
7030
|
+
},
|
|
7031
|
+
userMessageId,
|
|
7032
|
+
);
|
|
7033
|
+
}
|
|
7034
|
+
|
|
7035
|
+
markAIResponseVisible(aiMessageId: number): boolean {
|
|
7036
|
+
const turn = this.sampledByAIMessageId.get(aiMessageId);
|
|
7037
|
+
if (!turn || turn.ackAt === undefined || turn.firstChunkAt === undefined || turn.userMessageId === undefined)
|
|
7038
|
+
return false;
|
|
7039
|
+
|
|
7040
|
+
const renderedAt = this.clock.now();
|
|
7041
|
+
append(this.callback, METRIC_KEY_USER_PERCEIVED_RESPONSE_TIME, renderedAt - turn.startedAt, {
|
|
7042
|
+
extra: {
|
|
7043
|
+
ack_to_first_chunk_ms: turn.firstChunkAt - turn.ackAt,
|
|
7044
|
+
first_chunk_to_render_ms: renderedAt - turn.firstChunkAt,
|
|
7045
|
+
is_streaming: Boolean(turn.isStreaming),
|
|
7046
|
+
send_to_ack_ms: turn.ackAt - turn.startedAt,
|
|
7047
|
+
},
|
|
7048
|
+
});
|
|
7049
|
+
|
|
7050
|
+
this.deleteTurn(turn);
|
|
7051
|
+
return true;
|
|
7052
|
+
}
|
|
7053
|
+
|
|
7054
|
+
hasPendingAIResponse(aiMessageId: number): boolean {
|
|
7055
|
+
return this.sampledByAIMessageId.has(aiMessageId);
|
|
7056
|
+
}
|
|
7057
|
+
|
|
7058
|
+
clearMessage(aiMessageId: number): void {
|
|
7059
|
+
this.pendingAIResponseByUserMessageId.forEach((pending, userMessageId) => {
|
|
7060
|
+
if (pending.messageId === aiMessageId) this.pendingAIResponseByUserMessageId.delete(userMessageId);
|
|
7061
|
+
});
|
|
7062
|
+
|
|
7063
|
+
const turn = this.sampledByAIMessageId.get(aiMessageId);
|
|
7064
|
+
if (!turn) return;
|
|
7065
|
+
|
|
7066
|
+
this.deleteTurn(turn);
|
|
7067
|
+
if (this.collectedCount > 0) this.collectedCount -= 1;
|
|
7068
|
+
}
|
|
7069
|
+
|
|
7070
|
+
clearChannel(channelUrl?: string): void {
|
|
7071
|
+
if (!channelUrl) return;
|
|
7072
|
+
this.sampledByRequestId.forEach((turn) => {
|
|
7073
|
+
if (turn.channelUrl !== channelUrl) return;
|
|
7074
|
+
this.deleteTurn(turn);
|
|
7075
|
+
if (this.collectedCount > 0) this.collectedCount -= 1;
|
|
7076
|
+
});
|
|
7077
|
+
this.pendingAIResponseByUserMessageId.forEach((pending, userMessageId) => {
|
|
7078
|
+
if (pending.channelUrl === channelUrl) this.pendingAIResponseByUserMessageId.delete(userMessageId);
|
|
7079
|
+
});
|
|
7080
|
+
}
|
|
7081
|
+
|
|
7082
|
+
clear(): void {
|
|
7083
|
+
this.sampledByRequestId.clear();
|
|
7084
|
+
this.sampledByAIMessageId.clear();
|
|
7085
|
+
this.pendingAIResponseByUserMessageId.clear();
|
|
7086
|
+
this.collectedCount = 0;
|
|
7087
|
+
}
|
|
7088
|
+
|
|
7089
|
+
private findPendingTurn(channelUrl?: string, userMessageId?: number): UserTurn | undefined {
|
|
7090
|
+
if (userMessageId !== undefined) {
|
|
7091
|
+
for (const turn of this.sampledByRequestId.values()) {
|
|
7092
|
+
if (turn.ackAt === undefined || turn.firstChunkAt !== undefined) continue;
|
|
7093
|
+
if (!matchesChannel(turn, channelUrl)) continue;
|
|
7094
|
+
if (turn.userMessageId === userMessageId) return turn;
|
|
7095
|
+
}
|
|
7096
|
+
return undefined;
|
|
7097
|
+
}
|
|
7098
|
+
|
|
7099
|
+
let match: UserTurn | undefined;
|
|
7100
|
+
for (const turn of this.sampledByRequestId.values()) {
|
|
7101
|
+
if (turn.ackAt === undefined || turn.firstChunkAt !== undefined) continue;
|
|
7102
|
+
if (!matchesChannel(turn, channelUrl)) continue;
|
|
7103
|
+
if (match) return undefined;
|
|
7104
|
+
match = turn;
|
|
7105
|
+
}
|
|
7106
|
+
return match;
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7109
|
+
private applyAIResponseRaw(turn: UserTurn, params: PendingAIResponseRaw): void {
|
|
7110
|
+
if (turn.firstChunkAt !== undefined) return;
|
|
7111
|
+
|
|
7112
|
+
turn.firstChunkAt = turn.ackAt === undefined ? params.firstChunkAt : Math.max(params.firstChunkAt, turn.ackAt);
|
|
7113
|
+
turn.aiMessageId = params.messageId;
|
|
7114
|
+
turn.channelUrl = turn.channelUrl ?? params.channelUrl;
|
|
7115
|
+
turn.isStreaming = params.aiResponse.isStreaming;
|
|
7116
|
+
this.sampledByAIMessageId.set(params.messageId, turn);
|
|
7117
|
+
}
|
|
7118
|
+
|
|
7119
|
+
private bufferAIResponseRaw(params: PendingAIResponseRaw, userMessageId: number): void {
|
|
7120
|
+
if (!this.pendingAIResponseByUserMessageId.has(userMessageId)) {
|
|
7121
|
+
evictOldestIfAtLimit(this.pendingAIResponseByUserMessageId, 100);
|
|
7122
|
+
this.pendingAIResponseByUserMessageId.set(userMessageId, params);
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
|
|
7126
|
+
private hasUnackedTurn(channelUrl?: string): boolean {
|
|
7127
|
+
return hasUnackedChannelTurn(this.sampledByRequestId.values(), channelUrl);
|
|
7128
|
+
}
|
|
7129
|
+
|
|
7130
|
+
private deleteTurn(turn: UserTurn): void {
|
|
7131
|
+
this.sampledByRequestId.delete(turn.requestId);
|
|
7132
|
+
if (turn.aiMessageId !== undefined) this.sampledByAIMessageId.delete(turn.aiMessageId);
|
|
7133
|
+
if (turn.userMessageId !== undefined) this.pendingAIResponseByUserMessageId.delete(turn.userMessageId);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
|
|
5633
7137
|
/**
|
|
5634
7138
|
* User session containing authentication credentials.
|
|
5635
7139
|
*/
|
|
@@ -5647,6 +7151,17 @@ declare interface UserSessionInfo {
|
|
|
5647
7151
|
sessionHandler: AIAgentSessionHandler;
|
|
5648
7152
|
}
|
|
5649
7153
|
|
|
7154
|
+
declare type UserTurn = {
|
|
7155
|
+
requestId: string;
|
|
7156
|
+
channelUrl?: string;
|
|
7157
|
+
startedAt: number;
|
|
7158
|
+
ackAt?: number;
|
|
7159
|
+
firstChunkAt?: number;
|
|
7160
|
+
userMessageId?: number;
|
|
7161
|
+
aiMessageId?: number;
|
|
7162
|
+
isStreaming?: boolean;
|
|
7163
|
+
};
|
|
7164
|
+
|
|
5650
7165
|
export { }
|
|
5651
7166
|
|
|
5652
7167
|
|