@sendbird/ai-agent-messenger-react 1.32.0 → 1.33.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 +187 -187
- package/dist/index.d.ts +1474 -98
- package/dist/index.js +7511 -5655
- 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
|
|
|
@@ -719,10 +719,11 @@ declare interface AIAgentInterface {
|
|
|
719
719
|
/** @internal Agent version for conversation initialization */
|
|
720
720
|
agentVersion?: number;
|
|
721
721
|
|
|
722
|
-
/** @internal
|
|
723
|
-
readonly
|
|
724
|
-
|
|
725
|
-
|
|
722
|
+
/** @internal Schedules metrics work that should only start while the app is foregrounded. */
|
|
723
|
+
readonly scheduleForegroundTask?: ForegroundTaskScheduler;
|
|
724
|
+
|
|
725
|
+
/** @internal Stats trackers owned by this SDK instance. */
|
|
726
|
+
readonly statsTrackers: AIAgentStatsTrackers;
|
|
726
727
|
|
|
727
728
|
/** Authenticates with the AI Agent server and establishes a session */
|
|
728
729
|
authenticate: (sessionInfo: ManualSessionInfo | AnonymousSessionInfo) => Promise<MessengerSettingsResponse>;
|
|
@@ -797,12 +798,6 @@ declare interface AIAgentMessengerSessionContextValue {
|
|
|
797
798
|
deauthenticate: () => Promise<void>;
|
|
798
799
|
|
|
799
800
|
createAttachmentRules: (params: { channel?: ChatSDKGroupChannel; uploadSizeLimit?: number }) => AttachmentRules;
|
|
800
|
-
/**
|
|
801
|
-
* @internal
|
|
802
|
-
*/
|
|
803
|
-
statsTrackers: {
|
|
804
|
-
initialRender: ConversationInitialRenderStatsTracker;
|
|
805
|
-
};
|
|
806
801
|
}
|
|
807
802
|
|
|
808
803
|
declare interface AIAgentMessengerSessionRef {
|
|
@@ -1070,6 +1065,23 @@ declare interface AIAgentStatPayload {
|
|
|
1070
1065
|
extra?: Record<string, unknown>;
|
|
1071
1066
|
}
|
|
1072
1067
|
|
|
1068
|
+
declare type AIAgentStatsTrackerCollection = {
|
|
1069
|
+
conversationInitialRender: ConversationInitialRenderStatsTracker;
|
|
1070
|
+
conversationNotInteractiveTimeout: ConversationNotInteractiveTimeoutStatsTracker;
|
|
1071
|
+
recoveryNotInteractiveTimeout: RecoveryNotInteractiveTimeoutStatsTracker;
|
|
1072
|
+
conversationListInitialRender: ConversationListInitialRenderStatsTracker;
|
|
1073
|
+
aievAuthExpiredUnhandled: AIEVAuthExpiredUnhandledStatsTracker;
|
|
1074
|
+
messagePendingStuck: MessagePendingStuckStatsTracker;
|
|
1075
|
+
userPerceivedResponseTime: UserPerceivedResponseTimeStatsTracker;
|
|
1076
|
+
streamStalled: StreamStalledStatsTracker;
|
|
1077
|
+
typingIndicatorAbsentTimeout: TypingIndicatorAbsentTimeoutStatsTracker;
|
|
1078
|
+
renderedContentUnusable: RenderedContentUnusableStatsTracker;
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
declare type AIAgentStatsTrackers = AIAgentStatsTrackerCollection & {
|
|
1082
|
+
clear: () => void;
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1073
1085
|
/**
|
|
1074
1086
|
* Common string set interface shared between react and react-native packages
|
|
1075
1087
|
* These are the base strings that both packages use
|
|
@@ -1254,6 +1266,33 @@ declare interface AIAgentStringSet {
|
|
|
1254
1266
|
};
|
|
1255
1267
|
}
|
|
1256
1268
|
|
|
1269
|
+
declare class AIEVAuthExpiredUnhandledStatsTracker {
|
|
1270
|
+
private readonly callback: StatsAppendCallback;
|
|
1271
|
+
|
|
1272
|
+
constructor(callback: StatsAppendCallback) {
|
|
1273
|
+
this.callback = callback;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
onPayloadParseFailed(params: { aiAgentId: string }): void {
|
|
1277
|
+
append(this.callback, METRIC_KEY_AIEV_AUTH_EXPIRED_UNHANDLED, 'payload_parse_failed', {
|
|
1278
|
+
extra: { agent_id: params.aiAgentId },
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
onCallbackNotFound(params: { aiAgentId: string }): void {
|
|
1283
|
+
append(this.callback, METRIC_KEY_AIEV_AUTH_EXPIRED_UNHANDLED, 'callback_not_found', {
|
|
1284
|
+
extra: { agent_id: params.aiAgentId },
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
clear(): void {}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
declare type AIResponseMessageMetadata = {
|
|
1292
|
+
isStreaming: boolean;
|
|
1293
|
+
userMessageId?: number;
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1257
1296
|
declare type AnnouncementQueueMode = 'latestOnly' | 'ordered';
|
|
1258
1297
|
|
|
1259
1298
|
declare interface AnnounceOptions {
|
|
@@ -1532,7 +1571,7 @@ declare interface ContextObject {
|
|
|
1532
1571
|
context: Record<string, string>;
|
|
1533
1572
|
}
|
|
1534
1573
|
|
|
1535
|
-
export declare const Conversation: ({ children, onNavigateToConversationList, channelUrl, onClearChannelUrl, shouldMarkAsRead, announcementsEnabled, initialFocusTarget, style, closedChannelUrl, onClearClosedChannelUrl, }: ConversationProps) => JSX.Element;
|
|
1574
|
+
export declare const Conversation: ({ children, onNavigateToConversationList, channelUrl, onClearChannelUrl, shouldMarkAsRead, announcementsEnabled, conversationStatsEnabled, initialFocusTarget, style, closedChannelUrl, onClearClosedChannelUrl, }: ConversationProps) => JSX.Element;
|
|
1536
1575
|
|
|
1537
1576
|
export declare const ConversationContext: Context<ConversationContextValue | null>;
|
|
1538
1577
|
|
|
@@ -1541,11 +1580,13 @@ declare interface ConversationContextProps {
|
|
|
1541
1580
|
channelUrl?: string;
|
|
1542
1581
|
onClearChannelUrl?: () => void;
|
|
1543
1582
|
shouldMarkAsRead?: boolean;
|
|
1583
|
+
conversationStatsEnabled?: boolean;
|
|
1544
1584
|
}
|
|
1545
1585
|
|
|
1546
1586
|
export declare const ConversationContextProvider: ({ children, ...props }: PropsWithChildren<ConversationContextProps>) => JSX.Element;
|
|
1547
1587
|
|
|
1548
1588
|
declare interface ConversationContextValue extends AIAgentConversationContextValue {
|
|
1589
|
+
conversationStatsEnabled: boolean;
|
|
1549
1590
|
scrollSource: ConversationScrollContextValue;
|
|
1550
1591
|
goToActiveConversation: () => Promise<void>;
|
|
1551
1592
|
onNavigateToConversationList?: () => void;
|
|
@@ -1664,24 +1705,17 @@ declare class ConversationInitialRenderStats extends AIAgentBaseStats {
|
|
|
1664
1705
|
const extra: Record<string, unknown> = {
|
|
1665
1706
|
...this.extraData,
|
|
1666
1707
|
present_method: this.presentMethod,
|
|
1667
|
-
...(authDuration !== null && {
|
|
1668
|
-
...(getChannelDuration !== null && {
|
|
1669
|
-
...(getMessagesDuration !== null && {
|
|
1670
|
-
...(totalDurationFromCache !== null && {
|
|
1708
|
+
...(authDuration !== null && { [DurationKey.AUTH]: authDuration }),
|
|
1709
|
+
...(getChannelDuration !== null && { [DurationKey.GET_CHANNEL]: getChannelDuration }),
|
|
1710
|
+
...(getMessagesDuration !== null && { [DurationKey.GET_MESSAGES]: getMessagesDuration }),
|
|
1711
|
+
...(totalDurationFromCache !== null && { [DurationKey.TOTAL_DURATION_FROM_CACHE]: totalDurationFromCache }),
|
|
1671
1712
|
};
|
|
1672
1713
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
};
|
|
1678
|
-
|
|
1679
|
-
if (this.conversationId !== null) payload.conversation_id = this.conversationId;
|
|
1680
|
-
if (this.errorCode !== null) payload.error_code = this.errorCode;
|
|
1681
|
-
if (this.errorDescription !== null) payload.error_description = this.errorDescription;
|
|
1682
|
-
if (Object.keys(extra).length > 0) payload.extra = extra;
|
|
1683
|
-
|
|
1684
|
-
return payload;
|
|
1714
|
+
return createStatsPayload(this.getMetricKey(), totalDuration ?? 0, {
|
|
1715
|
+
conversationId: this.conversationId ?? undefined,
|
|
1716
|
+
error: this.errorCode !== null ? { code: this.errorCode, message: this.errorDescription ?? '' } : undefined,
|
|
1717
|
+
extra,
|
|
1718
|
+
});
|
|
1685
1719
|
}
|
|
1686
1720
|
}
|
|
1687
1721
|
|
|
@@ -1690,100 +1724,193 @@ declare class ConversationInitialRenderStats extends AIAgentBaseStats {
|
|
|
1690
1724
|
*/
|
|
1691
1725
|
declare class ConversationInitialRenderStatsTracker {
|
|
1692
1726
|
private readonly commitCallback: StatsAppendCallback;
|
|
1727
|
+
private apiResultReceived = false;
|
|
1728
|
+
private channelUrl?: string;
|
|
1729
|
+
private consumed = false;
|
|
1730
|
+
private consumedByAuthError = false;
|
|
1693
1731
|
private presentMethod: PresentMethod = 'direct_present';
|
|
1732
|
+
private renderedAfterApiResult = false;
|
|
1694
1733
|
private stats: ConversationInitialRenderStats | null = null;
|
|
1695
1734
|
|
|
1696
1735
|
constructor(commitCallback: StatsAppendCallback) {
|
|
1697
1736
|
this.commitCallback = commitCallback;
|
|
1698
1737
|
}
|
|
1699
1738
|
|
|
1700
|
-
private getOrCreateStats(): ConversationInitialRenderStats {
|
|
1701
|
-
if (
|
|
1739
|
+
private getOrCreateStats(): ConversationInitialRenderStats | null {
|
|
1740
|
+
if (this.consumed) return null;
|
|
1741
|
+
if (this.stats?.isCommitted()) {
|
|
1742
|
+
this.consumed = true;
|
|
1743
|
+
return null;
|
|
1744
|
+
}
|
|
1745
|
+
if (!this.stats) {
|
|
1702
1746
|
this.stats = new ConversationInitialRenderStats()
|
|
1703
1747
|
.setCommitCallback(this.commitCallback)
|
|
1704
1748
|
.setPresentMethod(this.presentMethod);
|
|
1749
|
+
this.apiResultReceived = false;
|
|
1750
|
+
this.renderedAfterApiResult = false;
|
|
1705
1751
|
}
|
|
1706
1752
|
return this.stats;
|
|
1707
1753
|
}
|
|
1708
1754
|
|
|
1755
|
+
private getActiveStats(): ConversationInitialRenderStats | null {
|
|
1756
|
+
if (this.consumed || !this.stats || this.stats.isCommitted()) return null;
|
|
1757
|
+
return this.stats;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
private matchesChannelUrl(channelUrl?: string): boolean {
|
|
1761
|
+
return channelUrl === undefined || this.channelUrl === undefined || channelUrl === this.channelUrl;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
private pinChannelUrl(channelUrl?: string): boolean {
|
|
1765
|
+
if (channelUrl === undefined) return true;
|
|
1766
|
+
if (!this.matchesChannelUrl(channelUrl)) return false;
|
|
1767
|
+
|
|
1768
|
+
this.channelUrl = channelUrl;
|
|
1769
|
+
this.stats?.setChannelUrl(channelUrl);
|
|
1770
|
+
return true;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
private consume(commitResult: boolean): boolean {
|
|
1774
|
+
if (commitResult) {
|
|
1775
|
+
this.consumed = true;
|
|
1776
|
+
}
|
|
1777
|
+
return commitResult;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
private commitIfReady(channelUrl?: string): boolean {
|
|
1781
|
+
if (this.consumed || !this.stats || this.stats.isCommitted()) return true;
|
|
1782
|
+
if (!this.matchesChannelUrl(channelUrl)) return false;
|
|
1783
|
+
if (!this.apiResultReceived || !this.renderedAfterApiResult) return false;
|
|
1784
|
+
return this.consume(this.stats.stopTimer(DurationKey.TOTAL_DURATION).commit());
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
start(channelUrl?: string): void {
|
|
1788
|
+
const stats = this.getOrCreateStats();
|
|
1789
|
+
if (!stats || !this.pinChannelUrl(channelUrl)) return;
|
|
1790
|
+
|
|
1791
|
+
stats.startTimer(DurationKey.TOTAL_DURATION).startTimer(DurationKey.TOTAL_DURATION_FROM_CACHE);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1709
1794
|
onAuthStart(): void {
|
|
1710
|
-
this.
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
.startTimer(DurationKey.AUTH);
|
|
1795
|
+
if (this.consumedByAuthError) {
|
|
1796
|
+
this.clear();
|
|
1797
|
+
}
|
|
1798
|
+
this.getOrCreateStats()?.startTimer(DurationKey.AUTH);
|
|
1714
1799
|
}
|
|
1715
1800
|
|
|
1716
1801
|
onAuthComplete(): void {
|
|
1717
|
-
this.
|
|
1802
|
+
this.getActiveStats()?.stopTimer(DurationKey.AUTH);
|
|
1718
1803
|
}
|
|
1719
1804
|
|
|
1720
1805
|
onAuthError(error: Error): void {
|
|
1721
|
-
|
|
1722
|
-
|
|
1806
|
+
const stats = this.getActiveStats();
|
|
1807
|
+
if (stats) {
|
|
1808
|
+
const committed = this.consume(
|
|
1809
|
+
stats
|
|
1723
1810
|
.stopTimer(DurationKey.AUTH)
|
|
1724
1811
|
.setError(error instanceof SendbirdError ? error.code : undefined, error.message)
|
|
1725
1812
|
.stopTimer(DurationKey.TOTAL_DURATION)
|
|
1726
|
-
.commit()
|
|
1813
|
+
.commit(),
|
|
1814
|
+
);
|
|
1815
|
+
if (committed) {
|
|
1816
|
+
this.consumedByAuthError = true;
|
|
1817
|
+
}
|
|
1727
1818
|
}
|
|
1728
1819
|
}
|
|
1729
1820
|
|
|
1730
|
-
onGetChannelStart(): void {
|
|
1731
|
-
this.
|
|
1821
|
+
onGetChannelStart(channelUrl?: string): void {
|
|
1822
|
+
const stats = this.getActiveStats();
|
|
1823
|
+
if (!stats || !this.pinChannelUrl(channelUrl)) return;
|
|
1824
|
+
|
|
1825
|
+
stats.startTimer(DurationKey.GET_CHANNEL);
|
|
1732
1826
|
}
|
|
1733
1827
|
|
|
1734
|
-
onGetChannelComplete(
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1828
|
+
onGetChannelComplete(params?: number | GetChannelCompleteParams): void {
|
|
1829
|
+
const channelUrl = typeof params === 'object' ? params.channelUrl : undefined;
|
|
1830
|
+
const conversationId = typeof params === 'object' ? params.conversationId : params;
|
|
1831
|
+
const stats = this.getActiveStats();
|
|
1832
|
+
if (!stats || !this.pinChannelUrl(channelUrl)) return;
|
|
1833
|
+
|
|
1834
|
+
stats.stopTimer(DurationKey.GET_CHANNEL);
|
|
1835
|
+
if (conversationId !== undefined) {
|
|
1836
|
+
stats.setConversationId(conversationId);
|
|
1741
1837
|
}
|
|
1838
|
+
stats.startTimer(DurationKey.GET_MESSAGES);
|
|
1742
1839
|
}
|
|
1743
1840
|
|
|
1744
|
-
onGetChannelError(
|
|
1745
|
-
|
|
1746
|
-
|
|
1841
|
+
onGetChannelError(params: Error | GetChannelErrorParams): void {
|
|
1842
|
+
const channelUrl = params instanceof Error ? undefined : params.channelUrl;
|
|
1843
|
+
const error = params instanceof Error ? params : params.error;
|
|
1844
|
+
const stats = this.getActiveStats();
|
|
1845
|
+
if (stats && this.pinChannelUrl(channelUrl)) {
|
|
1846
|
+
this.consume(
|
|
1847
|
+
stats
|
|
1747
1848
|
.stopTimer(DurationKey.GET_CHANNEL)
|
|
1748
1849
|
.setError(error instanceof SendbirdError ? error.code : undefined, error.message)
|
|
1749
1850
|
.stopTimer(DurationKey.TOTAL_DURATION)
|
|
1750
|
-
.commit()
|
|
1851
|
+
.commit(),
|
|
1852
|
+
);
|
|
1751
1853
|
}
|
|
1752
1854
|
}
|
|
1753
1855
|
|
|
1754
1856
|
onCacheResult(error: Error | null): void {
|
|
1755
|
-
|
|
1857
|
+
const stats = this.getActiveStats();
|
|
1858
|
+
if (stats) {
|
|
1756
1859
|
if (error) {
|
|
1757
|
-
|
|
1860
|
+
stats.setError(error instanceof SendbirdError ? error.code : undefined, error.message);
|
|
1758
1861
|
}
|
|
1759
|
-
|
|
1862
|
+
stats.stopTimer(DurationKey.TOTAL_DURATION_FROM_CACHE);
|
|
1760
1863
|
}
|
|
1761
1864
|
}
|
|
1762
1865
|
|
|
1763
1866
|
onApiResult(error: Error | null): void {
|
|
1764
|
-
|
|
1867
|
+
const stats = this.getActiveStats();
|
|
1868
|
+
if (stats) {
|
|
1765
1869
|
if (error) {
|
|
1766
|
-
this.
|
|
1870
|
+
this.consume(
|
|
1871
|
+
stats
|
|
1872
|
+
.setError(error instanceof SendbirdError ? error.code : undefined, error.message)
|
|
1873
|
+
.stopTimer(DurationKey.TOTAL_DURATION)
|
|
1874
|
+
.stopTimer(DurationKey.GET_MESSAGES)
|
|
1875
|
+
.commit(),
|
|
1876
|
+
);
|
|
1877
|
+
return;
|
|
1767
1878
|
}
|
|
1768
|
-
this.
|
|
1879
|
+
this.apiResultReceived = true;
|
|
1880
|
+
this.renderedAfterApiResult = false;
|
|
1881
|
+
stats.stopTimer(DurationKey.GET_MESSAGES);
|
|
1882
|
+
this.commitIfReady();
|
|
1769
1883
|
}
|
|
1770
1884
|
}
|
|
1771
1885
|
|
|
1772
1886
|
setChannelUrl(url: string): void {
|
|
1773
|
-
this.
|
|
1887
|
+
this.pinChannelUrl(url);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
commitPending(): boolean {
|
|
1891
|
+
const stats = this.getActiveStats();
|
|
1892
|
+
return stats ? this.consume(stats.stopTimer(DurationKey.TOTAL_DURATION).commit()) : true;
|
|
1774
1893
|
}
|
|
1775
1894
|
|
|
1776
|
-
|
|
1777
|
-
this.
|
|
1895
|
+
markRendered(channelUrl?: string): boolean {
|
|
1896
|
+
if (!this.matchesChannelUrl(channelUrl)) return false;
|
|
1897
|
+
|
|
1898
|
+
this.renderedAfterApiResult = true;
|
|
1899
|
+
return this.commitIfReady(channelUrl);
|
|
1778
1900
|
}
|
|
1779
1901
|
|
|
1780
1902
|
setPresentMethod(method: PresentMethod): void {
|
|
1781
1903
|
this.presentMethod = method;
|
|
1782
|
-
this.
|
|
1904
|
+
this.getActiveStats()?.setPresentMethod(method);
|
|
1783
1905
|
}
|
|
1784
1906
|
|
|
1785
1907
|
clear(): void {
|
|
1786
1908
|
this.stats = null;
|
|
1909
|
+
this.apiResultReceived = false;
|
|
1910
|
+
this.channelUrl = undefined;
|
|
1911
|
+
this.consumed = false;
|
|
1912
|
+
this.consumedByAuthError = false;
|
|
1913
|
+
this.renderedAfterApiResult = false;
|
|
1787
1914
|
}
|
|
1788
1915
|
}
|
|
1789
1916
|
|
|
@@ -1826,7 +1953,7 @@ export declare const ConversationLayout: {
|
|
|
1826
1953
|
declare interface ConversationLayoutTemplateProps {
|
|
1827
1954
|
}
|
|
1828
1955
|
|
|
1829
|
-
export declare const ConversationList: ({ conversationListLimit, conversationListFilter, children, onOpenConversationView, announcementsEnabled, style, }: ConversationListProps) => JSX.Element;
|
|
1956
|
+
export declare const ConversationList: ({ conversationListLimit, conversationListFilter, children, onOpenConversationView, announcementsEnabled, conversationListStatsEnabled, style, }: ConversationListProps) => JSX.Element;
|
|
1830
1957
|
|
|
1831
1958
|
/**
|
|
1832
1959
|
* Public interface for ConversationListCollection.
|
|
@@ -1855,10 +1982,11 @@ export declare const ConversationListContext: Context<ConversationListContextVal
|
|
|
1855
1982
|
declare interface ConversationListContextProps {
|
|
1856
1983
|
conversationListLimit?: number;
|
|
1857
1984
|
conversationListFilter?: Partial<AIAgentGroupChannelFilter>;
|
|
1985
|
+
conversationListStatsEnabled?: boolean;
|
|
1858
1986
|
onOpenConversationView?: (channelUrl: string, status: 'open' | 'closed', options?: ConversationListOpenOptions) => void;
|
|
1859
1987
|
}
|
|
1860
1988
|
|
|
1861
|
-
export declare function ConversationListContextProvider({ conversationListLimit, conversationListFilter, onOpenConversationView, children, }: PropsWithChildren<ConversationListContextProps>): JSX.Element;
|
|
1989
|
+
export declare function ConversationListContextProvider({ conversationListLimit, conversationListFilter, conversationListStatsEnabled, onOpenConversationView, children, }: PropsWithChildren<ConversationListContextProps>): JSX.Element;
|
|
1862
1990
|
|
|
1863
1991
|
declare interface ConversationListContextValue extends AIAgentConversationListContextValue {
|
|
1864
1992
|
onOpenConversationView: (channelUrl: string, status: 'open' | 'closed', options?: ConversationListOpenOptions) => void;
|
|
@@ -1916,6 +2044,156 @@ declare interface ConversationListHeaderTemplateProps {
|
|
|
1916
2044
|
titleAlign?: 'start' | 'center' | 'end';
|
|
1917
2045
|
}
|
|
1918
2046
|
|
|
2047
|
+
declare class ConversationListInitialRenderStatsTracker {
|
|
2048
|
+
private readonly callback: StatsAppendCallback;
|
|
2049
|
+
private readonly clock: StatsClock;
|
|
2050
|
+
private measurement = createEmptyProgress();
|
|
2051
|
+
private renderedCommitted = false;
|
|
2052
|
+
private startedAt: number | null = null;
|
|
2053
|
+
private listStartedAt: number | null = null;
|
|
2054
|
+
private authStartedAt: number | null = null;
|
|
2055
|
+
private authDuration: number | null = null;
|
|
2056
|
+
private listDuration: number | null = null;
|
|
2057
|
+
private presentMethod: PresentMethod = 'direct_present';
|
|
2058
|
+
private committed = false;
|
|
2059
|
+
|
|
2060
|
+
constructor(callback: StatsAppendCallback, options?: TrackerOptions) {
|
|
2061
|
+
this.callback = callback;
|
|
2062
|
+
this.clock = getClock(options);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
onInitializeStart(): void {
|
|
2066
|
+
if (this.measurement.rendered) return;
|
|
2067
|
+
if (this.measurement.initialized) {
|
|
2068
|
+
this.clearMeasurementState();
|
|
2069
|
+
}
|
|
2070
|
+
this.renderedCommitted = false;
|
|
2071
|
+
this.startMeasurement();
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
onListRequestStart(): void {
|
|
2075
|
+
if (this.measurement.listStarted && !this.measurement.listCompleted && !this.measurement.rendered) {
|
|
2076
|
+
this.clearMeasurementState();
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
this.markListRequestStartOnce();
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
onListRequestComplete(): void {
|
|
2083
|
+
this.markListRequestCompleteOnce();
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
onRendered(): void {
|
|
2087
|
+
this.markRenderedOnce();
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
setPresentMethod(method: PresentMethod): void {
|
|
2091
|
+
this.presentMethod = method;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
start(presentMethod?: PresentMethod): void {
|
|
2095
|
+
if (this.committed) return;
|
|
2096
|
+
this.startedAt = this.clock.now();
|
|
2097
|
+
if (presentMethod) this.presentMethod = presentMethod;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
onAuthStart(): void {
|
|
2101
|
+
if (this.committed) {
|
|
2102
|
+
this.clearMeasurementState();
|
|
2103
|
+
this.renderedCommitted = false;
|
|
2104
|
+
}
|
|
2105
|
+
this.authDuration = null;
|
|
2106
|
+
this.authStartedAt = this.clock.now();
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
onAuthComplete(): void {
|
|
2110
|
+
if (this.committed || this.authDuration !== null) return;
|
|
2111
|
+
if (this.authStartedAt === null) {
|
|
2112
|
+
this.authDuration = 0;
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
this.authDuration = this.clock.now() - this.authStartedAt;
|
|
2116
|
+
this.authStartedAt = null;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
markListRequestStart(): void {
|
|
2120
|
+
if (this.committed) return;
|
|
2121
|
+
if (this.startedAt === null) this.start();
|
|
2122
|
+
this.listStartedAt = this.clock.now();
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
markListRequestComplete(): void {
|
|
2126
|
+
if (this.committed || this.listStartedAt === null) return;
|
|
2127
|
+
this.listDuration = this.clock.now() - this.listStartedAt;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
markRendered(): void {
|
|
2131
|
+
if (this.committed || this.startedAt === null) return;
|
|
2132
|
+
this.committed = true;
|
|
2133
|
+
append(this.callback, METRIC_KEY_CONVERSATION_LIST_INITIAL_RENDER, this.clock.now() - this.startedAt, {
|
|
2134
|
+
extra: {
|
|
2135
|
+
...(this.authDuration !== null && { auth_duration_ms: this.authDuration }),
|
|
2136
|
+
...(this.listDuration !== null && { get_list_duration_ms: this.listDuration }),
|
|
2137
|
+
present_method: this.presentMethod,
|
|
2138
|
+
},
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
clear(): void {
|
|
2143
|
+
this.clearMeasurementState();
|
|
2144
|
+
this.clearAuthState();
|
|
2145
|
+
this.renderedCommitted = false;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
private clearMeasurementState(): void {
|
|
2149
|
+
this.startedAt = null;
|
|
2150
|
+
this.listStartedAt = null;
|
|
2151
|
+
this.listDuration = null;
|
|
2152
|
+
this.committed = false;
|
|
2153
|
+
this.measurement = createEmptyProgress();
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
private clearAuthState(): void {
|
|
2157
|
+
this.authStartedAt = null;
|
|
2158
|
+
this.authDuration = null;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
private startMeasurement(): void {
|
|
2162
|
+
if (this.measurement.initialized) return;
|
|
2163
|
+
|
|
2164
|
+
this.clearMeasurementState();
|
|
2165
|
+
this.start();
|
|
2166
|
+
this.measurement.initialized = true;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
private markListRequestStartOnce(): void {
|
|
2170
|
+
this.startMeasurement();
|
|
2171
|
+
if (this.measurement.listStarted) return;
|
|
2172
|
+
|
|
2173
|
+
this.markListRequestStart();
|
|
2174
|
+
this.measurement.listStarted = true;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
private markListRequestCompleteOnce(): void {
|
|
2178
|
+
this.markListRequestStartOnce();
|
|
2179
|
+
if (this.measurement.listCompleted) return;
|
|
2180
|
+
|
|
2181
|
+
this.markListRequestComplete();
|
|
2182
|
+
this.measurement.listCompleted = true;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
private markRenderedOnce(): void {
|
|
2186
|
+
if (this.renderedCommitted) return;
|
|
2187
|
+
|
|
2188
|
+
this.startMeasurement();
|
|
2189
|
+
if (this.measurement.rendered) return;
|
|
2190
|
+
|
|
2191
|
+
this.markRendered();
|
|
2192
|
+
this.measurement.rendered = true;
|
|
2193
|
+
this.renderedCommitted = true;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
|
|
1919
2197
|
export declare const ConversationListItemLayout: {
|
|
1920
2198
|
(props: PropsWithChildren): ReactNode;
|
|
1921
2199
|
defaults: {
|
|
@@ -1985,6 +2263,7 @@ export declare type ConversationListProps = PropsWithChildren<{
|
|
|
1985
2263
|
conversationListFilter?: Partial<AIAgentGroupChannelFilter>;
|
|
1986
2264
|
onOpenConversationView?: (channelUrl: string, status: 'open' | 'closed', options?: ConversationListOpenOptions) => void;
|
|
1987
2265
|
announcementsEnabled?: boolean;
|
|
2266
|
+
conversationListStatsEnabled?: boolean;
|
|
1988
2267
|
/** Custom styles for the conversation list container. */
|
|
1989
2268
|
style?: CSSProperties;
|
|
1990
2269
|
}>;
|
|
@@ -2008,12 +2287,138 @@ declare type ConversationMessageEventPayload =
|
|
|
2008
2287
|
messages: ChatSDKBaseMessage[];
|
|
2009
2288
|
};
|
|
2010
2289
|
|
|
2290
|
+
declare type ConversationNotInteractiveMeasurement = {
|
|
2291
|
+
timer: TimeoutId | null;
|
|
2292
|
+
startedAt: number;
|
|
2293
|
+
committed: boolean;
|
|
2294
|
+
inputEnabled: boolean;
|
|
2295
|
+
phaseReached: ConversationNotInteractivePhase;
|
|
2296
|
+
historyLoadResult: HistoryLoadResult;
|
|
2297
|
+
conversationId?: number;
|
|
2298
|
+
channelUrl?: string;
|
|
2299
|
+
};
|
|
2300
|
+
|
|
2301
|
+
declare type ConversationNotInteractivePhase = 'auth' | 'channel_join' | 'history_load' | 'input_enable' | 'render';
|
|
2302
|
+
|
|
2303
|
+
declare class ConversationNotInteractiveTimeoutStatsTracker {
|
|
2304
|
+
private readonly callback: StatsAppendCallback;
|
|
2305
|
+
private readonly clock: StatsClock;
|
|
2306
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
2307
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
2308
|
+
private readonly measurements = new Map<string, ConversationNotInteractiveMeasurement>();
|
|
2309
|
+
|
|
2310
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
2311
|
+
this.callback = callback;
|
|
2312
|
+
this.clock = getClock(options);
|
|
2313
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
2314
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
start(params?: { channelUrl?: string; conversationId?: number }): void {
|
|
2318
|
+
const key = this.getKey(params?.channelUrl);
|
|
2319
|
+
this.clearKey(key);
|
|
2320
|
+
|
|
2321
|
+
const measurement = {
|
|
2322
|
+
startedAt: this.clock.now(),
|
|
2323
|
+
channelUrl: params?.channelUrl,
|
|
2324
|
+
conversationId: params?.conversationId,
|
|
2325
|
+
committed: false,
|
|
2326
|
+
inputEnabled: false,
|
|
2327
|
+
phaseReached: 'auth' as ConversationNotInteractivePhase,
|
|
2328
|
+
historyLoadResult: 'pending' as HistoryLoadResult,
|
|
2329
|
+
timer: null as TimeoutId | null,
|
|
2330
|
+
};
|
|
2331
|
+
measurement.timer = this.setTimeoutFn(() => this.commitTimeout(key), 10_000);
|
|
2332
|
+
this.measurements.set(key, measurement);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
markChannelJoined(conversationId?: number, channelUrl?: string): void {
|
|
2336
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2337
|
+
if (!measurement || measurement.committed) return;
|
|
2338
|
+
measurement.phaseReached = 'channel_join';
|
|
2339
|
+
if (conversationId !== undefined) measurement.conversationId = conversationId;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
markHistoryLoaded(result: HistoryLoadResult, channelUrl?: string): void {
|
|
2343
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2344
|
+
if (!measurement || measurement.committed) return;
|
|
2345
|
+
measurement.phaseReached = 'history_load';
|
|
2346
|
+
measurement.historyLoadResult = result;
|
|
2347
|
+
this.cancelIfReady(channelUrl);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
markInputEnabled(channelUrl?: string): void {
|
|
2351
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2352
|
+
if (!measurement || measurement.committed) return;
|
|
2353
|
+
measurement.phaseReached = 'input_enable';
|
|
2354
|
+
measurement.inputEnabled = true;
|
|
2355
|
+
this.cancelIfReady(channelUrl);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
markRendered(channelUrl?: string): void {
|
|
2359
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2360
|
+
if (!measurement || measurement.committed) return;
|
|
2361
|
+
measurement.phaseReached = 'render';
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
clear(): void {
|
|
2365
|
+
this.measurements.forEach((measurement) => clearTimer(measurement.timer, this.clearTimeoutFn));
|
|
2366
|
+
this.measurements.clear();
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
clearChannel(channelUrl?: string): void {
|
|
2370
|
+
if (channelUrl === undefined) return;
|
|
2371
|
+
this.clearKey(this.getKey(channelUrl));
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
private clearKey(key: string): void {
|
|
2375
|
+
const measurement = this.measurements.get(key);
|
|
2376
|
+
if (!measurement) return;
|
|
2377
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
2378
|
+
this.measurements.delete(key);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
private cancelIfReady(channelUrl?: string): void {
|
|
2382
|
+
const measurement = this.getMeasurement(channelUrl);
|
|
2383
|
+
if (!measurement || !measurement.inputEnabled || measurement.historyLoadResult === 'pending') return;
|
|
2384
|
+
this.clearKey(this.getKey(measurement.channelUrl));
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
private commitTimeout(key: string): void {
|
|
2388
|
+
const measurement = this.measurements.get(key);
|
|
2389
|
+
if (!measurement || measurement.committed) return;
|
|
2390
|
+
measurement.committed = true;
|
|
2391
|
+
measurement.timer = null;
|
|
2392
|
+
append(this.callback, METRIC_KEY_CONVERSATION_NOT_INTERACTIVE_TIMEOUT, this.clock.now() - measurement.startedAt, {
|
|
2393
|
+
conversationId: measurement.conversationId,
|
|
2394
|
+
error: { message: METRIC_KEY_CONVERSATION_NOT_INTERACTIVE_TIMEOUT },
|
|
2395
|
+
extra: {
|
|
2396
|
+
outcome: 'timeout',
|
|
2397
|
+
phase_reached: measurement.phaseReached,
|
|
2398
|
+
history_load_result: measurement.historyLoadResult,
|
|
2399
|
+
},
|
|
2400
|
+
});
|
|
2401
|
+
this.measurements.delete(key);
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
private getMeasurement(channelUrl?: string): ConversationNotInteractiveMeasurement | undefined {
|
|
2405
|
+
if (channelUrl !== undefined) return this.measurements.get(this.getKey(channelUrl));
|
|
2406
|
+
if (this.measurements.size === 1) return Array.from(this.measurements.values())[0];
|
|
2407
|
+
return this.measurements.get(this.getKey());
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
private getKey(channelUrl?: string): string {
|
|
2411
|
+
return channelUrl ?? '__default__';
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2011
2415
|
export declare type ConversationProps = PropsWithChildren<{
|
|
2012
2416
|
channelUrl?: string;
|
|
2013
2417
|
onClearChannelUrl?: () => void;
|
|
2014
2418
|
onNavigateToConversationList?: () => void;
|
|
2015
2419
|
shouldMarkAsRead?: boolean;
|
|
2016
2420
|
announcementsEnabled?: boolean;
|
|
2421
|
+
conversationStatsEnabled?: boolean;
|
|
2017
2422
|
initialFocusTarget?: ConversationInitialFocusTarget;
|
|
2018
2423
|
/** Custom styles for the conversation container. */
|
|
2019
2424
|
style?: CSSProperties;
|
|
@@ -2209,12 +2614,22 @@ declare interface Dispatcher {
|
|
|
2209
2614
|
): void;
|
|
2210
2615
|
}
|
|
2211
2616
|
|
|
2617
|
+
declare enum DurationKey {
|
|
2618
|
+
AUTH = 'auth_duration_ms',
|
|
2619
|
+
GET_CHANNEL = 'get_channel_duration_ms',
|
|
2620
|
+
GET_MESSAGES = 'get_messages_duration_ms',
|
|
2621
|
+
TOTAL_DURATION = 'total_duration_ms',
|
|
2622
|
+
TOTAL_DURATION_FROM_CACHE = 'total_duration_from_cache_ms',
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2212
2625
|
declare type ErrorAnnouncementParams =
|
|
2213
2626
|
| { type: 'fileSizeExceeded'; maxSizeMB?: number }
|
|
2214
2627
|
| { type: 'sendFailed' }
|
|
2215
2628
|
| { type: 'csatFormError' }
|
|
2216
2629
|
| { type: 'generic'; message: string };
|
|
2217
2630
|
|
|
2631
|
+
declare type ErrorLike = StatsPayloadError;
|
|
2632
|
+
|
|
2218
2633
|
/**
|
|
2219
2634
|
* For better understanding: https://sendbird.atlassian.net/wiki/spaces/AA/pages/3075014695/Extended+Message+Payload+Spec
|
|
2220
2635
|
*/
|
|
@@ -2348,6 +2763,8 @@ declare interface FixedMessengerStyleProps {
|
|
|
2348
2763
|
launcherSize?: number;
|
|
2349
2764
|
}
|
|
2350
2765
|
|
|
2766
|
+
declare type ForegroundTaskScheduler = (start: () => void) => void | (() => void);
|
|
2767
|
+
|
|
2351
2768
|
declare interface Form {
|
|
2352
2769
|
key: string;
|
|
2353
2770
|
version: number;
|
|
@@ -2377,6 +2794,16 @@ export declare interface FunctionCallsInfo {
|
|
|
2377
2794
|
};
|
|
2378
2795
|
}
|
|
2379
2796
|
|
|
2797
|
+
declare type GetChannelCompleteParams = {
|
|
2798
|
+
channelUrl?: string;
|
|
2799
|
+
conversationId?: number;
|
|
2800
|
+
};
|
|
2801
|
+
|
|
2802
|
+
declare type GetChannelErrorParams = {
|
|
2803
|
+
channelUrl?: string;
|
|
2804
|
+
error: Error;
|
|
2805
|
+
};
|
|
2806
|
+
|
|
2380
2807
|
export declare interface GroundednessInfo {
|
|
2381
2808
|
id: number;
|
|
2382
2809
|
source_type:
|
|
@@ -2399,6 +2826,8 @@ export declare interface GroundednessInfo {
|
|
|
2399
2826
|
*/
|
|
2400
2827
|
declare type HexColor = `#${string}`;
|
|
2401
2828
|
|
|
2829
|
+
declare type HistoryLoadResult = 'success' | 'verified_empty' | 'failed' | 'pending';
|
|
2830
|
+
|
|
2402
2831
|
export declare type IconComponent = AIAgentIconComponent;
|
|
2403
2832
|
|
|
2404
2833
|
export declare type IconComponentProps = AIAgentIconComponentProps;
|
|
@@ -2480,13 +2909,8 @@ export declare const IncomingMessageLayout: {
|
|
|
2480
2909
|
};
|
|
2481
2910
|
}) => ReactNode;
|
|
2482
2911
|
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;
|
|
2912
|
+
MessageTemplate: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
2913
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
2490
2914
|
}) => ReactNode;
|
|
2491
2915
|
CustomMessageTemplate: (_: ({
|
|
2492
2916
|
messageType: "user";
|
|
@@ -2950,7 +3374,9 @@ export declare const IncomingMessageLayout: {
|
|
|
2950
3374
|
})) => ReactNode;
|
|
2951
3375
|
CTAButton: ({ extendedMessagePayload, onClickCTA, }: IncomingMessageProps) => ReactNode;
|
|
2952
3376
|
Citation: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
2953
|
-
Form: (props: IncomingMessageProps
|
|
3377
|
+
Form: (props: IncomingMessageProps & {
|
|
3378
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
3379
|
+
}) => ReactNode;
|
|
2954
3380
|
Feedback: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
2955
3381
|
MessageLogs: (_: IncomingMessageProps) => ReactNode;
|
|
2956
3382
|
};
|
|
@@ -2975,13 +3401,8 @@ export declare const IncomingMessageLayout: {
|
|
|
2975
3401
|
};
|
|
2976
3402
|
}) => ReactNode;
|
|
2977
3403
|
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;
|
|
3404
|
+
MessageTemplate: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
3405
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
2985
3406
|
}) => ReactNode;
|
|
2986
3407
|
CustomMessageTemplate: (_: ({
|
|
2987
3408
|
messageType: "user";
|
|
@@ -3445,7 +3866,9 @@ export declare const IncomingMessageLayout: {
|
|
|
3445
3866
|
})) => ReactNode;
|
|
3446
3867
|
CTAButton: ({ extendedMessagePayload, onClickCTA, }: IncomingMessageProps) => ReactNode;
|
|
3447
3868
|
Citation: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
3448
|
-
Form: (props: IncomingMessageProps
|
|
3869
|
+
Form: (props: IncomingMessageProps & {
|
|
3870
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
3871
|
+
}) => ReactNode;
|
|
3449
3872
|
Feedback: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
3450
3873
|
MessageLogs: (_: IncomingMessageProps) => ReactNode;
|
|
3451
3874
|
}>>;
|
|
@@ -3465,13 +3888,8 @@ export declare const IncomingMessageLayout: {
|
|
|
3465
3888
|
};
|
|
3466
3889
|
}) => ReactNode;
|
|
3467
3890
|
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;
|
|
3891
|
+
MessageTemplate: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
3892
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
3475
3893
|
}) => ReactNode;
|
|
3476
3894
|
CustomMessageTemplate: (_: ({
|
|
3477
3895
|
messageType: "user";
|
|
@@ -3935,19 +4353,16 @@ export declare const IncomingMessageLayout: {
|
|
|
3935
4353
|
})) => ReactNode;
|
|
3936
4354
|
CTAButton: ({ extendedMessagePayload, onClickCTA, }: IncomingMessageProps) => ReactNode;
|
|
3937
4355
|
Citation: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
3938
|
-
Form: (props: IncomingMessageProps
|
|
4356
|
+
Form: (props: IncomingMessageProps & {
|
|
4357
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
4358
|
+
}) => ReactNode;
|
|
3939
4359
|
Feedback: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
3940
4360
|
MessageLogs: (_: IncomingMessageProps) => ReactNode;
|
|
3941
4361
|
}>;
|
|
3942
4362
|
} & {
|
|
3943
4363
|
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;
|
|
4364
|
+
component: (props: Pick<IncomingMessageProps, "onGetCachedMessageTemplate" | "onRequestMessageTemplate" | "onHandleTemplateInternalAction" | "messageTemplateErrorFallback" | "messageTemplateLoadingFallback" | "extendedMessagePayload"> & {
|
|
4365
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onMessageTemplateError">;
|
|
3951
4366
|
}) => ReactNode;
|
|
3952
4367
|
}) => null;
|
|
3953
4368
|
SenderName: (props: {
|
|
@@ -4456,7 +4871,9 @@ export declare const IncomingMessageLayout: {
|
|
|
4456
4871
|
component: ({ extendedMessagePayload, onClickCitation, }: IncomingMessageProps) => ReactNode;
|
|
4457
4872
|
}) => null;
|
|
4458
4873
|
Form: (props: {
|
|
4459
|
-
component: (props: IncomingMessageProps
|
|
4874
|
+
component: (props: IncomingMessageProps & {
|
|
4875
|
+
statsCallbacks?: Pick<IncomingMessageStatsCallbacks, "onFormRenderError">;
|
|
4876
|
+
}) => ReactNode;
|
|
4460
4877
|
}) => null;
|
|
4461
4878
|
Feedback: (props: {
|
|
4462
4879
|
component: ({ isBotMessage, isConversationClosed, isStreaming, isFeedbackEnabled, isFeedbackCommentEnabled, extendedMessagePayload, onFeedbackUpdate, }: IncomingMessageProps) => ReactNode;
|
|
@@ -4469,6 +4886,13 @@ export declare const IncomingMessageLayout: {
|
|
|
4469
4886
|
export declare type IncomingMessageProps<T extends IncomingMessageUnion['messageType'] = IncomingMessageUnion['messageType']> =
|
|
4470
4887
|
PickMessageProps<IncomingMessageUnion, T>;
|
|
4471
4888
|
|
|
4889
|
+
declare type IncomingMessageStatsCallbacks = {
|
|
4890
|
+
onVisibleTextProgress?: (displayedLength: number) => void;
|
|
4891
|
+
onRenderedTextProgress?: (displayedLength: number) => void;
|
|
4892
|
+
onMessageTemplateError?: (error: Error) => void;
|
|
4893
|
+
onFormRenderError?: (error: Error) => void;
|
|
4894
|
+
};
|
|
4895
|
+
|
|
4472
4896
|
declare type IncomingMessageTemplateProps = IncomingMessageProps & Partial<InternalExtraProps>;
|
|
4473
4897
|
|
|
4474
4898
|
declare type IncomingMessageUnion =
|
|
@@ -4508,7 +4932,7 @@ declare type InputState =
|
|
|
4508
4932
|
|
|
4509
4933
|
declare type InternalExtraProps = {
|
|
4510
4934
|
maxBodyWidth?: number;
|
|
4511
|
-
|
|
4935
|
+
statsCallbacks?: IncomingMessageStatsCallbacks;
|
|
4512
4936
|
};
|
|
4513
4937
|
|
|
4514
4938
|
declare type InternalExtraProps_2 = {
|
|
@@ -4651,6 +5075,95 @@ export declare const MessageListUILayout: {
|
|
|
4651
5075
|
|
|
4652
5076
|
export declare const MessageLogs: ({ actionbook, functionCalls, groundedness, agentMessageTemplates, flaggedTypes, onClickActionbook, onClickFunctionCall, onClickFunctionCallDetail, onClickGroundedness, onClickAgentMessageTemplate, topContent, bottomContent, renderCustomGroundednessIcon, style, }: Props_2) => JSX.Element;
|
|
4653
5077
|
|
|
5078
|
+
declare class MessagePendingStuckStatsTracker {
|
|
5079
|
+
private readonly callback: StatsAppendCallback;
|
|
5080
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
5081
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
5082
|
+
private readonly measurements = new Map<string, PendingMeasurement>();
|
|
5083
|
+
|
|
5084
|
+
constructor(callback: StatsAppendCallback, options?: PendingStuckTrackerOptions) {
|
|
5085
|
+
this.callback = callback;
|
|
5086
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
5087
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
5088
|
+
}
|
|
5089
|
+
|
|
5090
|
+
start(params: {
|
|
5091
|
+
requestId: string;
|
|
5092
|
+
channelUrl?: string;
|
|
5093
|
+
messageType: PendingMessageType;
|
|
5094
|
+
tracksUploadProgress?: boolean;
|
|
5095
|
+
}): void {
|
|
5096
|
+
if (!params.requestId || this.measurements.has(params.requestId)) return;
|
|
5097
|
+
|
|
5098
|
+
const usesUploadProgress = params.messageType !== 'text' && params.tracksUploadProgress === true;
|
|
5099
|
+
const measurement: PendingMeasurement = {
|
|
5100
|
+
channelUrl: params.channelUrl,
|
|
5101
|
+
messageType: params.messageType,
|
|
5102
|
+
stuckReason: usesUploadProgress ? 'upload_no_progress' : 'pending_too_long',
|
|
5103
|
+
timer: null,
|
|
5104
|
+
};
|
|
5105
|
+
const timeoutMs = usesUploadProgress ? FILE_UPLOAD_NO_PROGRESS_TIMEOUT_MS : TEXT_PENDING_TIMEOUT_MS;
|
|
5106
|
+
measurement.timer = this.setTimeoutFn(() => this.detect(params.requestId), timeoutMs);
|
|
5107
|
+
this.measurements.set(params.requestId, measurement);
|
|
5108
|
+
}
|
|
5109
|
+
|
|
5110
|
+
resolve(params: { requestId: string }): void {
|
|
5111
|
+
const measurement = this.measurements.get(params.requestId);
|
|
5112
|
+
if (!measurement) return;
|
|
5113
|
+
|
|
5114
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5115
|
+
this.measurements.delete(params.requestId);
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
disappear(params: { requestId: string }): void {
|
|
5119
|
+
const measurement = this.measurements.get(params.requestId);
|
|
5120
|
+
if (!measurement) return;
|
|
5121
|
+
|
|
5122
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5123
|
+
this.measurements.delete(params.requestId);
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
markUploadProgress(requestId: string): void {
|
|
5127
|
+
const measurement = this.measurements.get(requestId);
|
|
5128
|
+
if (!measurement) return;
|
|
5129
|
+
if (measurement.stuckReason !== 'upload_no_progress') return;
|
|
5130
|
+
|
|
5131
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5132
|
+
measurement.timer = this.setTimeoutFn(() => this.detect(requestId), FILE_UPLOAD_NO_PROGRESS_TIMEOUT_MS);
|
|
5133
|
+
}
|
|
5134
|
+
|
|
5135
|
+
clear(): void {
|
|
5136
|
+
this.measurements.forEach((measurement) => clearTimer(measurement.timer, this.clearTimeoutFn));
|
|
5137
|
+
this.measurements.clear();
|
|
5138
|
+
}
|
|
5139
|
+
|
|
5140
|
+
clearChannel(channelUrl?: string): void {
|
|
5141
|
+
if (!channelUrl) return;
|
|
5142
|
+
this.measurements.forEach((measurement, requestId) => {
|
|
5143
|
+
if (measurement.channelUrl !== channelUrl) return;
|
|
5144
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5145
|
+
this.measurements.delete(requestId);
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
|
|
5149
|
+
private detect(requestId: string): void {
|
|
5150
|
+
const measurement = this.measurements.get(requestId);
|
|
5151
|
+
if (!measurement) return;
|
|
5152
|
+
|
|
5153
|
+
this.measurements.delete(requestId);
|
|
5154
|
+
this.commit(requestId, measurement);
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5157
|
+
private commit(requestId: string, measurement: PendingMeasurement): void {
|
|
5158
|
+
append(this.callback, METRIC_KEY_MESSAGE_PENDING_STUCK, measurement.stuckReason, {
|
|
5159
|
+
extra: {
|
|
5160
|
+
message_type: measurement.messageType,
|
|
5161
|
+
request_id: requestId,
|
|
5162
|
+
},
|
|
5163
|
+
});
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
|
|
4654
5167
|
declare interface MessageTemplateCache {
|
|
4655
5168
|
set(key: string, value: string): void;
|
|
4656
5169
|
get(key: string): string | null;
|
|
@@ -4915,6 +5428,7 @@ export declare const OutgoingMessageLayout: {
|
|
|
4915
5428
|
MediaMessageBody: typeof OutgoingImageBody;
|
|
4916
5429
|
FileMessageBody: typeof OutgoingFileBody;
|
|
4917
5430
|
MultipleFilesMessageBody: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
5431
|
+
MessageLogs: (_: OutgoingMessageProps) => ReactNode;
|
|
4918
5432
|
};
|
|
4919
5433
|
};
|
|
4920
5434
|
Template: ({ template, children }: {
|
|
@@ -4929,6 +5443,7 @@ export declare const OutgoingMessageLayout: {
|
|
|
4929
5443
|
MediaMessageBody: typeof OutgoingImageBody;
|
|
4930
5444
|
FileMessageBody: typeof OutgoingFileBody;
|
|
4931
5445
|
MultipleFilesMessageBody: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
5446
|
+
MessageLogs: (_: OutgoingMessageProps) => ReactNode;
|
|
4932
5447
|
}>>;
|
|
4933
5448
|
useContext: () => LayoutContextValue<OutgoingMessageProps, {
|
|
4934
5449
|
SendingStatus: ({ sendingStatus }: OutgoingMessageProps) => ReactNode;
|
|
@@ -4938,6 +5453,7 @@ export declare const OutgoingMessageLayout: {
|
|
|
4938
5453
|
MediaMessageBody: typeof OutgoingImageBody;
|
|
4939
5454
|
FileMessageBody: typeof OutgoingFileBody;
|
|
4940
5455
|
MultipleFilesMessageBody: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
5456
|
+
MessageLogs: (_: OutgoingMessageProps) => ReactNode;
|
|
4941
5457
|
}>;
|
|
4942
5458
|
} & {
|
|
4943
5459
|
SentTime: (props: {
|
|
@@ -4958,6 +5474,9 @@ export declare const OutgoingMessageLayout: {
|
|
|
4958
5474
|
MultipleFilesMessageBody: (props: {
|
|
4959
5475
|
component: ({ sendingStatus, files, metadata, onClickMedia, onClickMediaFiles, children, }: OutgoingMessageBodyProps<"multipleFiles">) => JSX.Element;
|
|
4960
5476
|
}) => null;
|
|
5477
|
+
MessageLogs: (props: {
|
|
5478
|
+
component: (_: OutgoingMessageProps) => ReactNode;
|
|
5479
|
+
}) => null;
|
|
4961
5480
|
SendingStatus: (props: {
|
|
4962
5481
|
component: ({ sendingStatus }: OutgoingMessageProps) => ReactNode;
|
|
4963
5482
|
}) => null;
|
|
@@ -5035,6 +5554,31 @@ declare interface PaletteTextEmphasis {
|
|
|
5035
5554
|
textDisabled: string;
|
|
5036
5555
|
}
|
|
5037
5556
|
|
|
5557
|
+
declare type PendingAIResponseRaw = {
|
|
5558
|
+
messageId: number;
|
|
5559
|
+
channelUrl?: string;
|
|
5560
|
+
aiResponse: AIResponseMessageMetadata;
|
|
5561
|
+
firstChunkAt: number;
|
|
5562
|
+
};
|
|
5563
|
+
|
|
5564
|
+
declare type PendingAIResponseStart = {
|
|
5565
|
+
channelUrl?: string;
|
|
5566
|
+
startedAt: number;
|
|
5567
|
+
};
|
|
5568
|
+
|
|
5569
|
+
declare type PendingMeasurement = {
|
|
5570
|
+
channelUrl?: string;
|
|
5571
|
+
messageType: PendingMessageType;
|
|
5572
|
+
stuckReason: PendingStuckReason;
|
|
5573
|
+
timer: TimeoutId | null;
|
|
5574
|
+
};
|
|
5575
|
+
|
|
5576
|
+
declare type PendingMessageType = 'text' | 'file_image' | 'file_video' | 'file_document' | 'file_audio';
|
|
5577
|
+
|
|
5578
|
+
declare type PendingStuckReason = 'pending_too_long' | 'upload_no_progress';
|
|
5579
|
+
|
|
5580
|
+
declare type PendingStuckTrackerOptions = Pick<TimedTrackerOptions, 'setTimeout' | 'clearTimeout'>;
|
|
5581
|
+
|
|
5038
5582
|
declare type PickMessageProps<
|
|
5039
5583
|
Union,
|
|
5040
5584
|
T extends Union extends { messageType: infer MT } ? MT : never = Union extends { messageType: infer MT } ? MT : never,
|
|
@@ -5115,7 +5659,7 @@ export declare type PositionHorizontal = 'start' | 'end';
|
|
|
5115
5659
|
|
|
5116
5660
|
export declare type PositionVertical = 'top' | 'bottom';
|
|
5117
5661
|
|
|
5118
|
-
declare type PresentMethod = 'launcher_toggle' | 'direct_present';
|
|
5662
|
+
declare type PresentMethod = 'launcher_toggle' | 'direct_present' | 'embedded';
|
|
5119
5663
|
|
|
5120
5664
|
declare type Props = {
|
|
5121
5665
|
children: ReactNode;
|
|
@@ -5172,6 +5716,120 @@ declare type Props_2 = {
|
|
|
5172
5716
|
|
|
5173
5717
|
declare type ReactOnlyIconName = 'expand' | 'collapse' | 'chevron-right' | 'attach' | 'close-filled' | 'actionbook' | 'function' | 'confluence' | 'zendesk' | 'salesforce' | 'sprinklr' | 'website' | 'snippet' | 'template' | 'show' | 'mute' | 'activity';
|
|
5174
5718
|
|
|
5719
|
+
declare type RecoveryMeasurement = {
|
|
5720
|
+
timer: TimeoutId | null;
|
|
5721
|
+
startedAt: number | null;
|
|
5722
|
+
triggerType: RecoveryTriggerType | null;
|
|
5723
|
+
inputEnabled: boolean;
|
|
5724
|
+
};
|
|
5725
|
+
|
|
5726
|
+
declare class RecoveryNotInteractiveTimeoutStatsTracker {
|
|
5727
|
+
private readonly callback: StatsAppendCallback;
|
|
5728
|
+
private readonly clock: StatsClock;
|
|
5729
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
5730
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
5731
|
+
private readonly measurements = new Map<string, RecoveryMeasurement>();
|
|
5732
|
+
|
|
5733
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
5734
|
+
this.callback = callback;
|
|
5735
|
+
this.clock = getClock(options);
|
|
5736
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
5737
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
5738
|
+
}
|
|
5739
|
+
|
|
5740
|
+
start(triggerType: RecoveryTriggerType, channelUrl?: string): void {
|
|
5741
|
+
const key = this.getKey(channelUrl);
|
|
5742
|
+
let measurement = this.measurements.get(key);
|
|
5743
|
+
if (measurement && measurement.startedAt !== null) return;
|
|
5744
|
+
if (measurement?.inputEnabled) return;
|
|
5745
|
+
|
|
5746
|
+
measurement = {
|
|
5747
|
+
startedAt: this.clock.now(),
|
|
5748
|
+
triggerType,
|
|
5749
|
+
inputEnabled: measurement?.inputEnabled ?? false,
|
|
5750
|
+
timer: this.setTimeoutFn(() => this.commitTimeout(key), 10_000),
|
|
5751
|
+
};
|
|
5752
|
+
this.measurements.set(key, measurement);
|
|
5753
|
+
}
|
|
5754
|
+
|
|
5755
|
+
setInputEnabled(enabled: boolean, channelUrl?: string): void {
|
|
5756
|
+
const key = this.getKey(channelUrl);
|
|
5757
|
+
let measurement = this.measurements.get(key);
|
|
5758
|
+
|
|
5759
|
+
if (!measurement) {
|
|
5760
|
+
if (!enabled) return;
|
|
5761
|
+
measurement = {
|
|
5762
|
+
startedAt: null,
|
|
5763
|
+
triggerType: null,
|
|
5764
|
+
inputEnabled: enabled,
|
|
5765
|
+
timer: null,
|
|
5766
|
+
};
|
|
5767
|
+
this.measurements.set(key, measurement);
|
|
5768
|
+
}
|
|
5769
|
+
|
|
5770
|
+
measurement.inputEnabled = enabled;
|
|
5771
|
+
if (enabled) {
|
|
5772
|
+
this.cancelMeasurement(measurement);
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
|
|
5776
|
+
cancelPending(channelUrl: string): void {
|
|
5777
|
+
const key = this.getKey(channelUrl);
|
|
5778
|
+
const measurement = this.measurements.get(key);
|
|
5779
|
+
if (!measurement) return;
|
|
5780
|
+
this.cancelMeasurement(measurement);
|
|
5781
|
+
if (!measurement.inputEnabled) this.measurements.delete(key);
|
|
5782
|
+
}
|
|
5783
|
+
|
|
5784
|
+
clear(): void {
|
|
5785
|
+
this.measurements.forEach((measurement) => clearTimer(measurement.timer, this.clearTimeoutFn));
|
|
5786
|
+
this.measurements.clear();
|
|
5787
|
+
}
|
|
5788
|
+
|
|
5789
|
+
clearChannel(channelUrl?: string): void {
|
|
5790
|
+
if (channelUrl === undefined) return;
|
|
5791
|
+
this.clearKey(this.getKey(channelUrl));
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5794
|
+
private clearKey(key: string): void {
|
|
5795
|
+
const measurement = this.measurements.get(key);
|
|
5796
|
+
if (!measurement) return;
|
|
5797
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5798
|
+
this.measurements.delete(key);
|
|
5799
|
+
}
|
|
5800
|
+
|
|
5801
|
+
private commitTimeout(key: string): void {
|
|
5802
|
+
const measurement = this.measurements.get(key);
|
|
5803
|
+
if (!measurement || measurement.startedAt === null || measurement.triggerType === null) return;
|
|
5804
|
+
if (measurement.inputEnabled) {
|
|
5805
|
+
this.cancelMeasurement(measurement);
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
const duration = this.clock.now() - measurement.startedAt;
|
|
5809
|
+
const triggerType = measurement.triggerType;
|
|
5810
|
+
// Drop the entry so a subsequent trigger can record a fresh cycle.
|
|
5811
|
+
this.measurements.delete(key);
|
|
5812
|
+
append(this.callback, METRIC_KEY_RECOVERY_NOT_INTERACTIVE_TIMEOUT, duration, {
|
|
5813
|
+
extra: {
|
|
5814
|
+
trigger_type: triggerType,
|
|
5815
|
+
},
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
|
|
5819
|
+
private cancelMeasurement(measurement: RecoveryMeasurement): void {
|
|
5820
|
+
clearTimer(measurement.timer, this.clearTimeoutFn);
|
|
5821
|
+
measurement.timer = null;
|
|
5822
|
+
measurement.startedAt = null;
|
|
5823
|
+
measurement.triggerType = null;
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
private getKey(channelUrl?: string): string {
|
|
5827
|
+
return channelUrl ?? '__default__';
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5831
|
+
declare type RecoveryTriggerType = 'foreground_resume' | 'ws_reconnected';
|
|
5832
|
+
|
|
5175
5833
|
declare type ReleaseDisabledByValue =
|
|
5176
5834
|
| 'unavailable'
|
|
5177
5835
|
| 'form_active'
|
|
@@ -5182,6 +5840,39 @@ declare type ReleaseDisabledByValue =
|
|
|
5182
5840
|
| 'reconnecting'
|
|
5183
5841
|
| 'handoff_pending';
|
|
5184
5842
|
|
|
5843
|
+
declare class RenderedContentUnusableStatsTracker {
|
|
5844
|
+
private readonly callback: StatsAppendCallback;
|
|
5845
|
+
private readonly committed = new Set<string>();
|
|
5846
|
+
|
|
5847
|
+
constructor(callback: StatsAppendCallback) {
|
|
5848
|
+
this.callback = callback;
|
|
5849
|
+
}
|
|
5850
|
+
|
|
5851
|
+
commit(params: {
|
|
5852
|
+
messageId: string | number;
|
|
5853
|
+
contentKind: 'text' | 'image' | 'video' | 'audio' | 'template' | 'form';
|
|
5854
|
+
errorDomain: 'empty_render' | 'template_error' | 'form_error';
|
|
5855
|
+
error?: ErrorLike;
|
|
5856
|
+
}): void {
|
|
5857
|
+
const key = `${params.messageId}:${params.contentKind}:${params.errorDomain}`;
|
|
5858
|
+
if (this.committed.has(key)) return;
|
|
5859
|
+
evictOldestIfAtLimit(this.committed, RENDERED_CONTENT_UNUSABLE_COMMITTED_LIMIT);
|
|
5860
|
+
this.committed.add(key);
|
|
5861
|
+
|
|
5862
|
+
append(this.callback, METRIC_KEY_RENDERED_CONTENT_UNUSABLE, params.errorDomain, {
|
|
5863
|
+
error: params.error ? { code: params.error.code, message: params.errorDomain } : undefined,
|
|
5864
|
+
extra: {
|
|
5865
|
+
content_kind: params.contentKind,
|
|
5866
|
+
message_id: String(params.messageId),
|
|
5867
|
+
},
|
|
5868
|
+
});
|
|
5869
|
+
}
|
|
5870
|
+
|
|
5871
|
+
clear(): void {
|
|
5872
|
+
this.committed.clear();
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
|
|
5185
5876
|
declare interface ResolvedHandlers extends AgentClientHandlers {
|
|
5186
5877
|
onClickLink: (params: {
|
|
5187
5878
|
url: string;
|
|
@@ -5223,6 +5914,15 @@ declare type SingleSelectField = {
|
|
|
5223
5914
|
|
|
5224
5915
|
declare type StatsAppendCallback = (type: string, data: AIAgentStatPayload) => boolean;
|
|
5225
5916
|
|
|
5917
|
+
declare interface StatsClock {
|
|
5918
|
+
now: () => number;
|
|
5919
|
+
}
|
|
5920
|
+
|
|
5921
|
+
declare type StatsPayloadError = {
|
|
5922
|
+
code?: number;
|
|
5923
|
+
message?: string;
|
|
5924
|
+
};
|
|
5925
|
+
|
|
5226
5926
|
/** Per-event override for the platform `announceStatus` wrappers — same shape as `AnnounceOptions`. */
|
|
5227
5927
|
declare type StatusAnnouncementOptions = AnnounceOptions;
|
|
5228
5928
|
|
|
@@ -5276,6 +5976,251 @@ declare enum StewardTaskStatus {
|
|
|
5276
5976
|
CANCELED = 'CANCELED',
|
|
5277
5977
|
}
|
|
5278
5978
|
|
|
5979
|
+
declare class StreamStalledStatsTracker {
|
|
5980
|
+
private readonly callback: StatsAppendCallback;
|
|
5981
|
+
private readonly clock: StatsClock;
|
|
5982
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
5983
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
5984
|
+
private readonly streams = new Map<number, StreamState>();
|
|
5985
|
+
private readonly emittedMessageChannels = new Map<number, string | undefined>();
|
|
5986
|
+
|
|
5987
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
5988
|
+
this.callback = callback;
|
|
5989
|
+
this.clock = getClock(options);
|
|
5990
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
5991
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
5992
|
+
}
|
|
5993
|
+
|
|
5994
|
+
markRawProgress(params: {
|
|
5995
|
+
messageId: number;
|
|
5996
|
+
channelUrl?: string;
|
|
5997
|
+
textLength: number;
|
|
5998
|
+
aiResponse: AIResponseMessageMetadata;
|
|
5999
|
+
}): void {
|
|
6000
|
+
if (!params.aiResponse.isStreaming) {
|
|
6001
|
+
this.completeDelivery(params.messageId, params.textLength);
|
|
6002
|
+
return;
|
|
6003
|
+
}
|
|
6004
|
+
|
|
6005
|
+
if (this.emittedMessageChannels.has(params.messageId)) return;
|
|
6006
|
+
|
|
6007
|
+
const stream = this.getOrCreate(params.messageId, params.channelUrl);
|
|
6008
|
+
|
|
6009
|
+
if (params.textLength > stream.lastRawLength) {
|
|
6010
|
+
stream.chunksReceived += 1;
|
|
6011
|
+
stream.lastRawLength = params.textLength;
|
|
6012
|
+
stream.lastRawAt = this.clock.now();
|
|
6013
|
+
|
|
6014
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6015
|
+
stream.deliveryTimer = null;
|
|
6016
|
+
this.scheduleDeliveryStallCheck(params.messageId, stream);
|
|
6017
|
+
} else {
|
|
6018
|
+
this.scheduleDeliveryStallCheck(params.messageId, stream);
|
|
6019
|
+
}
|
|
6020
|
+
|
|
6021
|
+
if (stream.lastRenderedLength < stream.lastRawLength && stream.renderTimer === null) {
|
|
6022
|
+
stream.lastRenderAt = this.clock.now();
|
|
6023
|
+
this.scheduleRenderStallCheck(params.messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS);
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
|
|
6027
|
+
markRenderedProgress(params: { messageId: number; displayedLength: number }): void {
|
|
6028
|
+
const stream = this.streams.get(params.messageId);
|
|
6029
|
+
if (!stream || params.displayedLength <= stream.lastRenderedLength) return;
|
|
6030
|
+
|
|
6031
|
+
stream.lastRenderedLength = params.displayedLength;
|
|
6032
|
+
stream.lastRenderAt = this.clock.now();
|
|
6033
|
+
|
|
6034
|
+
if (stream.lastRenderedLength >= stream.lastRawLength) {
|
|
6035
|
+
if (stream.deliveryTimer === null) {
|
|
6036
|
+
this.clearStream(params.messageId, stream);
|
|
6037
|
+
return;
|
|
6038
|
+
}
|
|
6039
|
+
|
|
6040
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6041
|
+
stream.renderTimer = null;
|
|
6042
|
+
return;
|
|
6043
|
+
}
|
|
6044
|
+
|
|
6045
|
+
this.scheduleRenderStallCheck(params.messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS);
|
|
6046
|
+
}
|
|
6047
|
+
|
|
6048
|
+
hasPendingStream(messageId: number): boolean {
|
|
6049
|
+
return this.streams.has(messageId);
|
|
6050
|
+
}
|
|
6051
|
+
|
|
6052
|
+
complete(messageId: number): void {
|
|
6053
|
+
const stream = this.streams.get(messageId);
|
|
6054
|
+
if (stream) this.clearStream(messageId, stream);
|
|
6055
|
+
}
|
|
6056
|
+
|
|
6057
|
+
// Final stream update: no more raw chunks are coming, so stop delivery-stall
|
|
6058
|
+
// tracking. The animation can still be catching up after streaming ends
|
|
6059
|
+
// (useStreamingText keeps animating while displayedLength < text.length), so
|
|
6060
|
+
// keep the render-stall watch alive until rendered progress reaches the last
|
|
6061
|
+
// raw length — otherwise a renderer that freezes mid-catch-up never emits a
|
|
6062
|
+
// 'render' stall.
|
|
6063
|
+
private completeDelivery(messageId: number, finalRawLength: number): void {
|
|
6064
|
+
const stream = this.streams.get(messageId);
|
|
6065
|
+
if (!stream) return;
|
|
6066
|
+
|
|
6067
|
+
// The final stream:false payload can carry text beyond the last streamed
|
|
6068
|
+
// chunk; fold it into the raw length first so the render watch targets the
|
|
6069
|
+
// full text the animation still has to reveal (otherwise the stream could be
|
|
6070
|
+
// cleared while a freeze on that trailing text would go unreported).
|
|
6071
|
+
if (finalRawLength > stream.lastRawLength) {
|
|
6072
|
+
stream.lastRawLength = finalRawLength;
|
|
6073
|
+
stream.lastRawAt = this.clock.now();
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6077
|
+
stream.deliveryTimer = null;
|
|
6078
|
+
|
|
6079
|
+
if (stream.lastRenderedLength >= stream.lastRawLength) {
|
|
6080
|
+
this.clearStream(messageId, stream);
|
|
6081
|
+
return;
|
|
6082
|
+
}
|
|
6083
|
+
|
|
6084
|
+
if (stream.renderTimer === null) {
|
|
6085
|
+
stream.lastRenderAt = this.clock.now();
|
|
6086
|
+
this.scheduleRenderStallCheck(messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS);
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
|
|
6090
|
+
clearMessage(messageId: number): void {
|
|
6091
|
+
const stream = this.streams.get(messageId);
|
|
6092
|
+
if (stream) this.clearStream(messageId, stream);
|
|
6093
|
+
this.emittedMessageChannels.delete(messageId);
|
|
6094
|
+
}
|
|
6095
|
+
|
|
6096
|
+
clear(): void {
|
|
6097
|
+
this.streams.forEach((stream) => {
|
|
6098
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6099
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6100
|
+
});
|
|
6101
|
+
this.streams.clear();
|
|
6102
|
+
this.emittedMessageChannels.clear();
|
|
6103
|
+
}
|
|
6104
|
+
|
|
6105
|
+
clearChannel(channelUrl?: string): void {
|
|
6106
|
+
if (!channelUrl) return;
|
|
6107
|
+
this.streams.forEach((stream, messageId) => {
|
|
6108
|
+
if (stream.channelUrl !== channelUrl && stream.channelUrl !== undefined) return;
|
|
6109
|
+
this.clearStream(messageId, stream);
|
|
6110
|
+
});
|
|
6111
|
+
this.emittedMessageChannels.forEach((emittedChannelUrl, messageId) => {
|
|
6112
|
+
if (emittedChannelUrl === channelUrl || emittedChannelUrl === undefined)
|
|
6113
|
+
this.emittedMessageChannels.delete(messageId);
|
|
6114
|
+
});
|
|
6115
|
+
}
|
|
6116
|
+
|
|
6117
|
+
private detectDelivery(messageId: number): void {
|
|
6118
|
+
const stream = this.streams.get(messageId);
|
|
6119
|
+
if (!stream) return;
|
|
6120
|
+
stream.deliveryTimer = null;
|
|
6121
|
+
// Mutex: cancel any in-flight render stall check so delivery wins this turn.
|
|
6122
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6123
|
+
stream.renderTimer = null;
|
|
6124
|
+
this.emit(messageId, stream, 'delivery');
|
|
6125
|
+
}
|
|
6126
|
+
|
|
6127
|
+
private detectRender(messageId: number): void {
|
|
6128
|
+
const stream = this.streams.get(messageId);
|
|
6129
|
+
if (!stream) return;
|
|
6130
|
+
stream.renderTimer = null;
|
|
6131
|
+
if (stream.lastRenderedLength >= stream.lastRawLength) return;
|
|
6132
|
+
|
|
6133
|
+
const elapsedSinceRender = this.clock.now() - stream.lastRenderAt;
|
|
6134
|
+
if (elapsedSinceRender < STREAM_RENDER_STALL_THRESHOLD_MS) {
|
|
6135
|
+
this.scheduleRenderStallCheck(messageId, stream, STREAM_RENDER_STALL_THRESHOLD_MS - elapsedSinceRender);
|
|
6136
|
+
return;
|
|
6137
|
+
}
|
|
6138
|
+
|
|
6139
|
+
// Mutex rule: when raw progress has been silent for at least the render threshold,
|
|
6140
|
+
// delivery is the root cause and will win. Defer the render emit until delivery has
|
|
6141
|
+
// had a chance to fire so we don't surface render as the symptom.
|
|
6142
|
+
const elapsedSinceRaw = this.clock.now() - stream.lastRawAt;
|
|
6143
|
+
if (elapsedSinceRaw >= STREAM_RENDER_STALL_THRESHOLD_MS && stream.deliveryTimer !== null) {
|
|
6144
|
+
const untilDelivery = STREAM_DELIVERY_STALL_THRESHOLD_MS - elapsedSinceRaw;
|
|
6145
|
+
if (untilDelivery > 0) {
|
|
6146
|
+
this.scheduleRenderStallCheck(messageId, stream, untilDelivery);
|
|
6147
|
+
return;
|
|
6148
|
+
}
|
|
6149
|
+
}
|
|
6150
|
+
|
|
6151
|
+
this.emit(messageId, stream, 'render');
|
|
6152
|
+
}
|
|
6153
|
+
|
|
6154
|
+
private scheduleRenderStallCheck(messageId: number, stream: StreamState, delayMs: number): void {
|
|
6155
|
+
if (stream.renderTimer !== null) return;
|
|
6156
|
+
stream.renderTimer = this.setTimeoutFn(() => this.detectRender(messageId), delayMs);
|
|
6157
|
+
}
|
|
6158
|
+
|
|
6159
|
+
private scheduleDeliveryStallCheck(messageId: number, stream: StreamState): void {
|
|
6160
|
+
if (stream.deliveryTimer !== null) return;
|
|
6161
|
+
stream.deliveryTimer = this.setTimeoutFn(() => this.detectDelivery(messageId), STREAM_DELIVERY_STALL_THRESHOLD_MS);
|
|
6162
|
+
}
|
|
6163
|
+
|
|
6164
|
+
private emit(messageId: number, stream: StreamState, stallKind: StreamStallKind): void {
|
|
6165
|
+
if (this.emittedMessageChannels.has(messageId)) return;
|
|
6166
|
+
|
|
6167
|
+
const stallStartedAt = stallKind === 'delivery' ? stream.lastRawAt : stream.lastRenderAt;
|
|
6168
|
+
append(this.callback, METRIC_KEY_STREAM_STALLED, this.clock.now() - stallStartedAt, {
|
|
6169
|
+
extra: {
|
|
6170
|
+
message_id: String(messageId),
|
|
6171
|
+
stall_kind: stallKind,
|
|
6172
|
+
},
|
|
6173
|
+
});
|
|
6174
|
+
this.rememberEmitted(messageId, stream.channelUrl);
|
|
6175
|
+
this.clearStream(messageId, stream);
|
|
6176
|
+
}
|
|
6177
|
+
|
|
6178
|
+
private getOrCreate(messageId: number, channelUrl: string | undefined): StreamState {
|
|
6179
|
+
const existing = this.streams.get(messageId);
|
|
6180
|
+
if (existing) return existing;
|
|
6181
|
+
|
|
6182
|
+
const now = this.clock.now();
|
|
6183
|
+
const stream: StreamState = {
|
|
6184
|
+
channelUrl,
|
|
6185
|
+
chunksReceived: 0,
|
|
6186
|
+
lastRawAt: now,
|
|
6187
|
+
lastRenderAt: now,
|
|
6188
|
+
lastRawLength: 0,
|
|
6189
|
+
lastRenderedLength: 0,
|
|
6190
|
+
deliveryTimer: null,
|
|
6191
|
+
renderTimer: null,
|
|
6192
|
+
};
|
|
6193
|
+
this.streams.set(messageId, stream);
|
|
6194
|
+
return stream;
|
|
6195
|
+
}
|
|
6196
|
+
|
|
6197
|
+
private rememberEmitted(messageId: number, channelUrl: string | undefined): void {
|
|
6198
|
+
if (!this.emittedMessageChannels.has(messageId)) {
|
|
6199
|
+
evictOldestIfAtLimit(this.emittedMessageChannels, STREAM_STALLED_EMITTED_LIMIT);
|
|
6200
|
+
}
|
|
6201
|
+
this.emittedMessageChannels.set(messageId, channelUrl);
|
|
6202
|
+
}
|
|
6203
|
+
|
|
6204
|
+
private clearStream(messageId: number, stream: StreamState): void {
|
|
6205
|
+
clearTimer(stream.deliveryTimer, this.clearTimeoutFn);
|
|
6206
|
+
clearTimer(stream.renderTimer, this.clearTimeoutFn);
|
|
6207
|
+
this.streams.delete(messageId);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
|
|
6211
|
+
declare type StreamStallKind = 'delivery' | 'render';
|
|
6212
|
+
|
|
6213
|
+
declare type StreamState = {
|
|
6214
|
+
channelUrl?: string;
|
|
6215
|
+
chunksReceived: number;
|
|
6216
|
+
lastRawAt: number;
|
|
6217
|
+
lastRenderAt: number;
|
|
6218
|
+
lastRawLength: number;
|
|
6219
|
+
lastRenderedLength: number;
|
|
6220
|
+
deliveryTimer: TimeoutId | null;
|
|
6221
|
+
renderTimer: TimeoutId | null;
|
|
6222
|
+
};
|
|
6223
|
+
|
|
5279
6224
|
/**
|
|
5280
6225
|
* StringSet type for React components
|
|
5281
6226
|
* Uses SNAKE_CASE keys for backward compatibility
|
|
@@ -5550,11 +6495,248 @@ declare type TextField = {
|
|
|
5550
6495
|
};
|
|
5551
6496
|
};
|
|
5552
6497
|
|
|
6498
|
+
declare type TimedTrackerOptions = TrackerOptions & {
|
|
6499
|
+
setTimeout?: typeof setTimeout;
|
|
6500
|
+
clearTimeout?: typeof clearTimeout;
|
|
6501
|
+
};
|
|
6502
|
+
|
|
6503
|
+
declare type TimeoutId = ReturnType<typeof setTimeout>;
|
|
6504
|
+
|
|
5553
6505
|
declare interface TimerData {
|
|
5554
6506
|
startTime: number | null;
|
|
5555
6507
|
endTime: number | null;
|
|
5556
6508
|
}
|
|
5557
6509
|
|
|
6510
|
+
declare type TrackerOptions = {
|
|
6511
|
+
clock?: StatsClock;
|
|
6512
|
+
};
|
|
6513
|
+
|
|
6514
|
+
declare class TypingIndicatorAbsentTimeoutStatsTracker {
|
|
6515
|
+
private readonly callback: StatsAppendCallback;
|
|
6516
|
+
private readonly clock: StatsClock;
|
|
6517
|
+
private readonly setTimeoutFn: typeof setTimeout;
|
|
6518
|
+
private readonly clearTimeoutFn: typeof clearTimeout;
|
|
6519
|
+
private readonly turns = new Map<string, TypingTurn>();
|
|
6520
|
+
private readonly pendingAIResponseStartsByUserMessageId = new Map<number, PendingAIResponseStart>();
|
|
6521
|
+
|
|
6522
|
+
constructor(callback: StatsAppendCallback, options?: TimedTrackerOptions) {
|
|
6523
|
+
this.callback = callback;
|
|
6524
|
+
this.clock = getClock(options);
|
|
6525
|
+
this.setTimeoutFn = getSetTimeout(options);
|
|
6526
|
+
this.clearTimeoutFn = getClearTimeout(options);
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
startTurn(params: { requestId: string; channelUrl?: string }): void {
|
|
6530
|
+
this.cancelTurn(params.requestId);
|
|
6531
|
+
const turn: TypingTurn = {
|
|
6532
|
+
requestId: params.requestId,
|
|
6533
|
+
channelUrl: params.channelUrl,
|
|
6534
|
+
beforeTpstStartedAt: this.clock.now(),
|
|
6535
|
+
afterTpenStartedAt: null,
|
|
6536
|
+
timer: null,
|
|
6537
|
+
};
|
|
6538
|
+
turn.timer = this.setTimeoutFn(() => this.commitTimeout(turn.requestId), TYPING_COLLECTION_TIMEOUT_MS);
|
|
6539
|
+
this.turns.set(params.requestId, turn);
|
|
6540
|
+
}
|
|
6541
|
+
|
|
6542
|
+
markUserMessageAck(params: { requestId: string; messageId: number }): void {
|
|
6543
|
+
const turn = this.turns.get(params.requestId);
|
|
6544
|
+
if (!turn) return;
|
|
6545
|
+
turn.userMessageId = params.messageId;
|
|
6546
|
+
|
|
6547
|
+
const pending = this.pendingAIResponseStartsByUserMessageId.get(params.messageId);
|
|
6548
|
+
if (!pending) return;
|
|
6549
|
+
this.pendingAIResponseStartsByUserMessageId.delete(params.messageId);
|
|
6550
|
+
this.applyAIResponseStarted(
|
|
6551
|
+
{ channelUrl: pending.channelUrl, matchesUserMessage: true, userMessageId: params.messageId },
|
|
6552
|
+
pending.startedAt,
|
|
6553
|
+
);
|
|
6554
|
+
}
|
|
6555
|
+
|
|
6556
|
+
replaceTurnRequestId(currentRequestId: string, nextRequestId: string): void {
|
|
6557
|
+
if (currentRequestId === nextRequestId) return;
|
|
6558
|
+
|
|
6559
|
+
const turn = this.turns.get(currentRequestId);
|
|
6560
|
+
if (!turn) return;
|
|
6561
|
+
|
|
6562
|
+
this.turns.delete(currentRequestId);
|
|
6563
|
+
turn.requestId = nextRequestId;
|
|
6564
|
+
this.turns.set(nextRequestId, turn);
|
|
6565
|
+
}
|
|
6566
|
+
|
|
6567
|
+
cancelTurn(requestId: string): void {
|
|
6568
|
+
this.cleanupTurn(requestId);
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6571
|
+
markTypingStarted(channelUrl?: string): void {
|
|
6572
|
+
const now = this.clock.now();
|
|
6573
|
+
this.turns.forEach((turn) => {
|
|
6574
|
+
if (!matchesChannel(turn, channelUrl)) return;
|
|
6575
|
+
this.recordAfterTpen(turn, now);
|
|
6576
|
+
this.recordBeforeTpst(turn, now);
|
|
6577
|
+
});
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
markTypingEnded(channelUrl?: string): void {
|
|
6581
|
+
const now = this.clock.now();
|
|
6582
|
+
this.turns.forEach((turn) => {
|
|
6583
|
+
if (!matchesChannel(turn, channelUrl)) return;
|
|
6584
|
+
this.recordBeforeTpst(turn, now);
|
|
6585
|
+
turn.afterTpenStartedAt = now;
|
|
6586
|
+
});
|
|
6587
|
+
}
|
|
6588
|
+
|
|
6589
|
+
markAIResponseStarted(params: { channelUrl?: string; userMessageId?: number } = {}): void {
|
|
6590
|
+
const channelUrl = params.channelUrl;
|
|
6591
|
+
const userMessageId = params.userMessageId;
|
|
6592
|
+
const matchesUserMessage = userMessageId !== undefined;
|
|
6593
|
+
const now = this.clock.now();
|
|
6594
|
+
const matched = this.applyAIResponseStarted({ channelUrl, matchesUserMessage, userMessageId }, now);
|
|
6595
|
+
if (matched || !matchesUserMessage || userMessageId === undefined || !this.hasUnackedTurn(channelUrl)) return;
|
|
6596
|
+
|
|
6597
|
+
if (!this.pendingAIResponseStartsByUserMessageId.has(userMessageId)) {
|
|
6598
|
+
evictOldestIfAtLimit(this.pendingAIResponseStartsByUserMessageId, 100);
|
|
6599
|
+
this.pendingAIResponseStartsByUserMessageId.set(userMessageId, { channelUrl, startedAt: now });
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6603
|
+
private applyAIResponseStarted(
|
|
6604
|
+
params: { channelUrl?: string; matchesUserMessage: boolean; userMessageId?: number },
|
|
6605
|
+
startedAt: number,
|
|
6606
|
+
): boolean {
|
|
6607
|
+
if (!params.matchesUserMessage) {
|
|
6608
|
+
const match = this.findSingleTurn(params.channelUrl);
|
|
6609
|
+
if (!match) return false;
|
|
6610
|
+
this.completeTurn(match.requestId, match.turn, startedAt);
|
|
6611
|
+
return true;
|
|
6612
|
+
}
|
|
6613
|
+
|
|
6614
|
+
let matched = false;
|
|
6615
|
+
this.turns.forEach((turn, requestId) => {
|
|
6616
|
+
if (!matchesChannel(turn, params.channelUrl)) return;
|
|
6617
|
+
if (params.matchesUserMessage && turn.userMessageId !== params.userMessageId) return;
|
|
6618
|
+
matched = true;
|
|
6619
|
+
this.completeTurn(requestId, turn, startedAt);
|
|
6620
|
+
});
|
|
6621
|
+
return matched;
|
|
6622
|
+
}
|
|
6623
|
+
|
|
6624
|
+
private findSingleTurn(channelUrl?: string): TypingTurnEntry | undefined {
|
|
6625
|
+
let match: TypingTurnEntry | undefined;
|
|
6626
|
+
let matchedCount = 0;
|
|
6627
|
+
this.turns.forEach((turn, requestId) => {
|
|
6628
|
+
if (!matchesChannel(turn, channelUrl)) return;
|
|
6629
|
+
matchedCount += 1;
|
|
6630
|
+
if (matchedCount > 1) return;
|
|
6631
|
+
match = { requestId, turn };
|
|
6632
|
+
});
|
|
6633
|
+
return matchedCount === 1 ? match : undefined;
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6636
|
+
private completeTurn(requestId: string, turn: TypingTurn, startedAt: number): void {
|
|
6637
|
+
this.recordBeforeTpst(turn, startedAt);
|
|
6638
|
+
this.recordAfterTpen(turn, startedAt);
|
|
6639
|
+
this.commitIfAbsent(requestId, turn);
|
|
6640
|
+
}
|
|
6641
|
+
|
|
6642
|
+
private hasUnackedTurn(channelUrl?: string): boolean {
|
|
6643
|
+
return hasUnackedChannelTurn(this.turns.values(), channelUrl);
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
clear(): void {
|
|
6647
|
+
this.turns.forEach((turn) => {
|
|
6648
|
+
clearTimer(turn.timer, this.clearTimeoutFn);
|
|
6649
|
+
});
|
|
6650
|
+
this.turns.clear();
|
|
6651
|
+
this.pendingAIResponseStartsByUserMessageId.clear();
|
|
6652
|
+
}
|
|
6653
|
+
|
|
6654
|
+
clearChannel(channelUrl?: string): void {
|
|
6655
|
+
if (!channelUrl) return;
|
|
6656
|
+
this.turns.forEach((turn, requestId) => {
|
|
6657
|
+
if (turn.channelUrl !== channelUrl) return;
|
|
6658
|
+
this.cleanupTurn(requestId);
|
|
6659
|
+
});
|
|
6660
|
+
this.pendingAIResponseStartsByUserMessageId.forEach((pending, userMessageId) => {
|
|
6661
|
+
if (pending.channelUrl === channelUrl) this.pendingAIResponseStartsByUserMessageId.delete(userMessageId);
|
|
6662
|
+
});
|
|
6663
|
+
}
|
|
6664
|
+
|
|
6665
|
+
private commitTimeout(requestId: string): void {
|
|
6666
|
+
const turn = this.turns.get(requestId);
|
|
6667
|
+
if (!turn) return;
|
|
6668
|
+
const now = this.clock.now();
|
|
6669
|
+
this.recordBeforeTpst(turn, now);
|
|
6670
|
+
this.recordAfterTpen(turn, now);
|
|
6671
|
+
this.commitIfAbsent(requestId, turn);
|
|
6672
|
+
}
|
|
6673
|
+
|
|
6674
|
+
private recordBeforeTpst(
|
|
6675
|
+
turn: { beforeTpstStartedAt: number | null; beforeTpstAbsentMs?: number },
|
|
6676
|
+
endedAt: number,
|
|
6677
|
+
): void {
|
|
6678
|
+
if (turn.beforeTpstStartedAt === null) return;
|
|
6679
|
+
const duration = endedAt - turn.beforeTpstStartedAt;
|
|
6680
|
+
if (duration >= TYPING_ABSENT_THRESHOLD_MS) turn.beforeTpstAbsentMs = duration;
|
|
6681
|
+
turn.beforeTpstStartedAt = null;
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6684
|
+
private recordAfterTpen(
|
|
6685
|
+
turn: { afterTpenStartedAt: number | null; afterTpenAbsentMs?: number },
|
|
6686
|
+
endedAt: number,
|
|
6687
|
+
): void {
|
|
6688
|
+
if (turn.afterTpenStartedAt === null) return;
|
|
6689
|
+
const duration = endedAt - turn.afterTpenStartedAt;
|
|
6690
|
+
if (duration >= TYPING_ABSENT_THRESHOLD_MS) turn.afterTpenAbsentMs = duration;
|
|
6691
|
+
turn.afterTpenStartedAt = null;
|
|
6692
|
+
}
|
|
6693
|
+
|
|
6694
|
+
private commitIfAbsent(requestId: string, turn: { beforeTpstAbsentMs?: number; afterTpenAbsentMs?: number }): void {
|
|
6695
|
+
const beforeTpstAbsentMs = turn.beforeTpstAbsentMs;
|
|
6696
|
+
const afterTpenAbsentMs = turn.afterTpenAbsentMs;
|
|
6697
|
+
if (beforeTpstAbsentMs === undefined && afterTpenAbsentMs === undefined) {
|
|
6698
|
+
this.cleanupTurn(requestId);
|
|
6699
|
+
return;
|
|
6700
|
+
}
|
|
6701
|
+
|
|
6702
|
+
append(
|
|
6703
|
+
this.callback,
|
|
6704
|
+
METRIC_KEY_AI_TYPING_INDICATOR_ABSENT_TIMEOUT,
|
|
6705
|
+
(beforeTpstAbsentMs ?? 0) + (afterTpenAbsentMs ?? 0),
|
|
6706
|
+
{
|
|
6707
|
+
extra: {
|
|
6708
|
+
...(beforeTpstAbsentMs !== undefined && { before_tpst_absent_ms: beforeTpstAbsentMs }),
|
|
6709
|
+
...(afterTpenAbsentMs !== undefined && { after_tpen_absent_ms: afterTpenAbsentMs }),
|
|
6710
|
+
},
|
|
6711
|
+
},
|
|
6712
|
+
);
|
|
6713
|
+
this.cleanupTurn(requestId);
|
|
6714
|
+
}
|
|
6715
|
+
|
|
6716
|
+
private cleanupTurn(requestId: string): void {
|
|
6717
|
+
const turn = this.turns.get(requestId);
|
|
6718
|
+
if (!turn) return;
|
|
6719
|
+
clearTimer(turn.timer, this.clearTimeoutFn);
|
|
6720
|
+
this.turns.delete(requestId);
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
|
|
6724
|
+
declare type TypingTurn = {
|
|
6725
|
+
requestId: string;
|
|
6726
|
+
channelUrl?: string;
|
|
6727
|
+
userMessageId?: number;
|
|
6728
|
+
beforeTpstStartedAt: number | null;
|
|
6729
|
+
afterTpenStartedAt: number | null;
|
|
6730
|
+
beforeTpstAbsentMs?: number;
|
|
6731
|
+
afterTpenAbsentMs?: number;
|
|
6732
|
+
timer: TimeoutId | null;
|
|
6733
|
+
};
|
|
6734
|
+
|
|
6735
|
+
declare type TypingTurnEntry = {
|
|
6736
|
+
requestId: string;
|
|
6737
|
+
turn: TypingTurn;
|
|
6738
|
+
};
|
|
6739
|
+
|
|
5558
6740
|
declare interface TypographyShape {
|
|
5559
6741
|
h1: TypographyVariant;
|
|
5560
6742
|
h2: TypographyVariant;
|
|
@@ -5630,6 +6812,189 @@ export declare type UserActionStatus =
|
|
|
5630
6812
|
|
|
5631
6813
|
export declare const useRefreshActiveChannel: (updater: () => Promise<void>) => () => Promise<void>;
|
|
5632
6814
|
|
|
6815
|
+
declare class UserPerceivedResponseTimeStatsTracker {
|
|
6816
|
+
private readonly callback: StatsAppendCallback;
|
|
6817
|
+
private readonly clock: StatsClock;
|
|
6818
|
+
private readonly random: () => number;
|
|
6819
|
+
private readonly sampledByRequestId = new Map<string, UserTurn>();
|
|
6820
|
+
private readonly sampledByAIMessageId = new Map<number, UserTurn>();
|
|
6821
|
+
private readonly pendingAIResponseByUserMessageId = new Map<number, PendingAIResponseRaw>();
|
|
6822
|
+
private collectedCount = 0;
|
|
6823
|
+
|
|
6824
|
+
constructor(callback: StatsAppendCallback, options?: TrackerOptions & { random?: () => number }) {
|
|
6825
|
+
this.callback = callback;
|
|
6826
|
+
this.clock = getClock(options);
|
|
6827
|
+
this.random = options?.random ?? Math.random;
|
|
6828
|
+
}
|
|
6829
|
+
|
|
6830
|
+
startTurn(params: { requestId: string; channelUrl?: string }): void {
|
|
6831
|
+
if (this.collectedCount >= 10) return;
|
|
6832
|
+
if (this.random() >= 0.15) return;
|
|
6833
|
+
this.collectedCount += 1;
|
|
6834
|
+
this.sampledByRequestId.set(params.requestId, {
|
|
6835
|
+
requestId: params.requestId,
|
|
6836
|
+
channelUrl: params.channelUrl,
|
|
6837
|
+
startedAt: this.clock.now(),
|
|
6838
|
+
});
|
|
6839
|
+
}
|
|
6840
|
+
|
|
6841
|
+
markUserMessageAck(params: { requestId: string; messageId: number }): void {
|
|
6842
|
+
const turn = this.sampledByRequestId.get(params.requestId);
|
|
6843
|
+
if (!turn) return;
|
|
6844
|
+
|
|
6845
|
+
turn.ackAt = this.clock.now();
|
|
6846
|
+
turn.userMessageId = params.messageId;
|
|
6847
|
+
|
|
6848
|
+
const pending = this.pendingAIResponseByUserMessageId.get(params.messageId);
|
|
6849
|
+
if (!pending) return;
|
|
6850
|
+
this.pendingAIResponseByUserMessageId.delete(params.messageId);
|
|
6851
|
+
this.applyAIResponseRaw(turn, pending);
|
|
6852
|
+
}
|
|
6853
|
+
|
|
6854
|
+
replaceTurnRequestId(currentRequestId: string, nextRequestId: string): void {
|
|
6855
|
+
if (currentRequestId === nextRequestId) return;
|
|
6856
|
+
|
|
6857
|
+
const turn = this.sampledByRequestId.get(currentRequestId);
|
|
6858
|
+
if (!turn) return;
|
|
6859
|
+
|
|
6860
|
+
this.sampledByRequestId.delete(currentRequestId);
|
|
6861
|
+
turn.requestId = nextRequestId;
|
|
6862
|
+
this.sampledByRequestId.set(nextRequestId, turn);
|
|
6863
|
+
}
|
|
6864
|
+
|
|
6865
|
+
cancelTurn(requestId: string): void {
|
|
6866
|
+
const turn = this.sampledByRequestId.get(requestId);
|
|
6867
|
+
if (!turn) return;
|
|
6868
|
+
this.deleteTurn(turn);
|
|
6869
|
+
// Return the unused sample slot so cancelled turns don't burn the per-session cap.
|
|
6870
|
+
if (this.collectedCount > 0) this.collectedCount -= 1;
|
|
6871
|
+
}
|
|
6872
|
+
|
|
6873
|
+
markAIResponseRaw(params: { messageId: number; channelUrl?: string; aiResponse: AIResponseMessageMetadata }): void {
|
|
6874
|
+
const firstChunkAt = this.clock.now();
|
|
6875
|
+
const userMessageId = params.aiResponse.userMessageId;
|
|
6876
|
+
if (userMessageId === undefined && this.hasUnackedTurn(params.channelUrl)) return;
|
|
6877
|
+
|
|
6878
|
+
const turn = this.findPendingTurn(params.channelUrl, userMessageId);
|
|
6879
|
+
if (turn) {
|
|
6880
|
+
this.applyAIResponseRaw(turn, { ...params, firstChunkAt });
|
|
6881
|
+
return;
|
|
6882
|
+
}
|
|
6883
|
+
|
|
6884
|
+
if (userMessageId === undefined || !this.hasUnackedTurn(params.channelUrl)) return;
|
|
6885
|
+
this.bufferAIResponseRaw(
|
|
6886
|
+
{
|
|
6887
|
+
messageId: params.messageId,
|
|
6888
|
+
...(params.channelUrl !== undefined && { channelUrl: params.channelUrl }),
|
|
6889
|
+
aiResponse: params.aiResponse,
|
|
6890
|
+
firstChunkAt,
|
|
6891
|
+
},
|
|
6892
|
+
userMessageId,
|
|
6893
|
+
);
|
|
6894
|
+
}
|
|
6895
|
+
|
|
6896
|
+
markAIResponseVisible(aiMessageId: number): boolean {
|
|
6897
|
+
const turn = this.sampledByAIMessageId.get(aiMessageId);
|
|
6898
|
+
if (!turn || turn.ackAt === undefined || turn.firstChunkAt === undefined || turn.userMessageId === undefined)
|
|
6899
|
+
return false;
|
|
6900
|
+
|
|
6901
|
+
const renderedAt = this.clock.now();
|
|
6902
|
+
append(this.callback, METRIC_KEY_USER_PERCEIVED_RESPONSE_TIME, renderedAt - turn.startedAt, {
|
|
6903
|
+
extra: {
|
|
6904
|
+
ack_to_first_chunk_ms: turn.firstChunkAt - turn.ackAt,
|
|
6905
|
+
first_chunk_to_render_ms: renderedAt - turn.firstChunkAt,
|
|
6906
|
+
is_streaming: Boolean(turn.isStreaming),
|
|
6907
|
+
send_to_ack_ms: turn.ackAt - turn.startedAt,
|
|
6908
|
+
},
|
|
6909
|
+
});
|
|
6910
|
+
|
|
6911
|
+
this.deleteTurn(turn);
|
|
6912
|
+
return true;
|
|
6913
|
+
}
|
|
6914
|
+
|
|
6915
|
+
hasPendingAIResponse(aiMessageId: number): boolean {
|
|
6916
|
+
return this.sampledByAIMessageId.has(aiMessageId);
|
|
6917
|
+
}
|
|
6918
|
+
|
|
6919
|
+
clearMessage(aiMessageId: number): void {
|
|
6920
|
+
this.pendingAIResponseByUserMessageId.forEach((pending, userMessageId) => {
|
|
6921
|
+
if (pending.messageId === aiMessageId) this.pendingAIResponseByUserMessageId.delete(userMessageId);
|
|
6922
|
+
});
|
|
6923
|
+
|
|
6924
|
+
const turn = this.sampledByAIMessageId.get(aiMessageId);
|
|
6925
|
+
if (!turn) return;
|
|
6926
|
+
|
|
6927
|
+
this.deleteTurn(turn);
|
|
6928
|
+
if (this.collectedCount > 0) this.collectedCount -= 1;
|
|
6929
|
+
}
|
|
6930
|
+
|
|
6931
|
+
clearChannel(channelUrl?: string): void {
|
|
6932
|
+
if (!channelUrl) return;
|
|
6933
|
+
this.sampledByRequestId.forEach((turn) => {
|
|
6934
|
+
if (turn.channelUrl !== channelUrl) return;
|
|
6935
|
+
this.deleteTurn(turn);
|
|
6936
|
+
if (this.collectedCount > 0) this.collectedCount -= 1;
|
|
6937
|
+
});
|
|
6938
|
+
this.pendingAIResponseByUserMessageId.forEach((pending, userMessageId) => {
|
|
6939
|
+
if (pending.channelUrl === channelUrl) this.pendingAIResponseByUserMessageId.delete(userMessageId);
|
|
6940
|
+
});
|
|
6941
|
+
}
|
|
6942
|
+
|
|
6943
|
+
clear(): void {
|
|
6944
|
+
this.sampledByRequestId.clear();
|
|
6945
|
+
this.sampledByAIMessageId.clear();
|
|
6946
|
+
this.pendingAIResponseByUserMessageId.clear();
|
|
6947
|
+
this.collectedCount = 0;
|
|
6948
|
+
}
|
|
6949
|
+
|
|
6950
|
+
private findPendingTurn(channelUrl?: string, userMessageId?: number): UserTurn | undefined {
|
|
6951
|
+
if (userMessageId !== undefined) {
|
|
6952
|
+
for (const turn of this.sampledByRequestId.values()) {
|
|
6953
|
+
if (turn.ackAt === undefined || turn.firstChunkAt !== undefined) continue;
|
|
6954
|
+
if (!matchesChannel(turn, channelUrl)) continue;
|
|
6955
|
+
if (turn.userMessageId === userMessageId) return turn;
|
|
6956
|
+
}
|
|
6957
|
+
return undefined;
|
|
6958
|
+
}
|
|
6959
|
+
|
|
6960
|
+
let match: UserTurn | undefined;
|
|
6961
|
+
for (const turn of this.sampledByRequestId.values()) {
|
|
6962
|
+
if (turn.ackAt === undefined || turn.firstChunkAt !== undefined) continue;
|
|
6963
|
+
if (!matchesChannel(turn, channelUrl)) continue;
|
|
6964
|
+
if (match) return undefined;
|
|
6965
|
+
match = turn;
|
|
6966
|
+
}
|
|
6967
|
+
return match;
|
|
6968
|
+
}
|
|
6969
|
+
|
|
6970
|
+
private applyAIResponseRaw(turn: UserTurn, params: PendingAIResponseRaw): void {
|
|
6971
|
+
if (turn.firstChunkAt !== undefined) return;
|
|
6972
|
+
|
|
6973
|
+
turn.firstChunkAt = turn.ackAt === undefined ? params.firstChunkAt : Math.max(params.firstChunkAt, turn.ackAt);
|
|
6974
|
+
turn.aiMessageId = params.messageId;
|
|
6975
|
+
turn.channelUrl = turn.channelUrl ?? params.channelUrl;
|
|
6976
|
+
turn.isStreaming = params.aiResponse.isStreaming;
|
|
6977
|
+
this.sampledByAIMessageId.set(params.messageId, turn);
|
|
6978
|
+
}
|
|
6979
|
+
|
|
6980
|
+
private bufferAIResponseRaw(params: PendingAIResponseRaw, userMessageId: number): void {
|
|
6981
|
+
if (!this.pendingAIResponseByUserMessageId.has(userMessageId)) {
|
|
6982
|
+
evictOldestIfAtLimit(this.pendingAIResponseByUserMessageId, 100);
|
|
6983
|
+
this.pendingAIResponseByUserMessageId.set(userMessageId, params);
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
|
|
6987
|
+
private hasUnackedTurn(channelUrl?: string): boolean {
|
|
6988
|
+
return hasUnackedChannelTurn(this.sampledByRequestId.values(), channelUrl);
|
|
6989
|
+
}
|
|
6990
|
+
|
|
6991
|
+
private deleteTurn(turn: UserTurn): void {
|
|
6992
|
+
this.sampledByRequestId.delete(turn.requestId);
|
|
6993
|
+
if (turn.aiMessageId !== undefined) this.sampledByAIMessageId.delete(turn.aiMessageId);
|
|
6994
|
+
if (turn.userMessageId !== undefined) this.pendingAIResponseByUserMessageId.delete(turn.userMessageId);
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
|
|
5633
6998
|
/**
|
|
5634
6999
|
* User session containing authentication credentials.
|
|
5635
7000
|
*/
|
|
@@ -5647,6 +7012,17 @@ declare interface UserSessionInfo {
|
|
|
5647
7012
|
sessionHandler: AIAgentSessionHandler;
|
|
5648
7013
|
}
|
|
5649
7014
|
|
|
7015
|
+
declare type UserTurn = {
|
|
7016
|
+
requestId: string;
|
|
7017
|
+
channelUrl?: string;
|
|
7018
|
+
startedAt: number;
|
|
7019
|
+
ackAt?: number;
|
|
7020
|
+
firstChunkAt?: number;
|
|
7021
|
+
userMessageId?: number;
|
|
7022
|
+
aiMessageId?: number;
|
|
7023
|
+
isStreaming?: boolean;
|
|
7024
|
+
};
|
|
7025
|
+
|
|
5650
7026
|
export { }
|
|
5651
7027
|
|
|
5652
7028
|
|