@librechat/agents 3.3.13 → 3.4.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.
Files changed (94) hide show
  1. package/dist/cjs/graphs/Graph.cjs +10 -0
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/index.cjs +35 -206
  4. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/index.cjs +121 -241
  6. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  7. package/dist/cjs/llm/google/index.cjs +19 -12
  8. package/dist/cjs/llm/google/index.cjs.map +1 -1
  9. package/dist/cjs/llm/mistral/index.cjs +26 -0
  10. package/dist/cjs/llm/mistral/index.cjs.map +1 -0
  11. package/dist/cjs/llm/openai/index.cjs +82 -80
  12. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  13. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  14. package/dist/cjs/llm/providers.cjs +19 -19
  15. package/dist/cjs/llm/providers.cjs.map +1 -1
  16. package/dist/cjs/llm/stream/chunkAdapters.cjs +198 -0
  17. package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -0
  18. package/dist/cjs/llm/stream/smoother.cjs +369 -0
  19. package/dist/cjs/llm/stream/smoother.cjs.map +1 -0
  20. package/dist/cjs/llm/vertexai/index.cjs +13 -1
  21. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  22. package/dist/cjs/main.cjs +18 -10
  23. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +34 -2
  24. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  25. package/dist/cjs/utils/tokens.cjs +76 -29
  26. package/dist/cjs/utils/tokens.cjs.map +1 -1
  27. package/dist/esm/graphs/Graph.mjs +10 -0
  28. package/dist/esm/graphs/Graph.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/index.mjs +34 -205
  30. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  31. package/dist/esm/llm/bedrock/index.mjs +120 -240
  32. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  33. package/dist/esm/llm/google/index.mjs +19 -12
  34. package/dist/esm/llm/google/index.mjs.map +1 -1
  35. package/dist/esm/llm/mistral/index.mjs +26 -0
  36. package/dist/esm/llm/mistral/index.mjs.map +1 -0
  37. package/dist/esm/llm/openai/index.mjs +82 -80
  38. package/dist/esm/llm/openai/index.mjs.map +1 -1
  39. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  40. package/dist/esm/llm/providers.mjs +3 -3
  41. package/dist/esm/llm/providers.mjs.map +1 -1
  42. package/dist/esm/llm/stream/chunkAdapters.mjs +195 -0
  43. package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -0
  44. package/dist/esm/llm/stream/smoother.mjs +365 -0
  45. package/dist/esm/llm/stream/smoother.mjs.map +1 -0
  46. package/dist/esm/llm/vertexai/index.mjs +13 -1
  47. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  48. package/dist/esm/main.mjs +4 -2
  49. package/dist/esm/tools/subagent/SubagentExecutor.mjs +34 -2
  50. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  51. package/dist/esm/utils/tokens.mjs +76 -30
  52. package/dist/esm/utils/tokens.mjs.map +1 -1
  53. package/dist/types/graphs/Graph.d.ts +3 -0
  54. package/dist/types/index.d.ts +3 -0
  55. package/dist/types/llm/google/index.d.ts +2 -0
  56. package/dist/types/llm/mistral/index.d.ts +11 -0
  57. package/dist/types/llm/openai/index.d.ts +20 -4
  58. package/dist/types/llm/openrouter/index.d.ts +4 -1
  59. package/dist/types/llm/stream/chunkAdapters.d.ts +48 -0
  60. package/dist/types/llm/stream/smoother.d.ts +95 -0
  61. package/dist/types/llm/vertexai/index.d.ts +2 -0
  62. package/dist/types/tools/subagent/SubagentExecutor.d.ts +3 -0
  63. package/dist/types/types/llm.d.ts +21 -14
  64. package/dist/types/utils/tokens.d.ts +10 -0
  65. package/package.json +2 -2
  66. package/src/graphs/Graph.ts +11 -0
  67. package/src/index.ts +8 -0
  68. package/src/llm/anthropic/index.ts +85 -354
  69. package/src/llm/bedrock/index.ts +174 -390
  70. package/src/llm/bedrock/llm.spec.ts +2 -0
  71. package/src/llm/bedrock/streamSealDispatch.test.ts +76 -0
  72. package/src/llm/custom-chat-models.smoke.test.ts +16 -1
  73. package/src/llm/google/index.ts +17 -18
  74. package/src/llm/google/streamSmoothing.test.ts +121 -0
  75. package/src/llm/mistral/index.ts +33 -0
  76. package/src/llm/mistral/streamSmoothing.test.ts +97 -0
  77. package/src/llm/openai/deepseek.test.ts +56 -0
  78. package/src/llm/openai/index.ts +119 -126
  79. package/src/llm/openrouter/index.ts +4 -1
  80. package/src/llm/providers.ts +3 -3
  81. package/src/llm/stream/chunkAdapters.test.ts +202 -0
  82. package/src/llm/stream/chunkAdapters.ts +317 -0
  83. package/src/llm/stream/reassembly.test.ts +241 -0
  84. package/src/llm/stream/smoother.bench.test.ts +155 -0
  85. package/src/llm/stream/smoother.test.ts +519 -0
  86. package/src/llm/stream/smoother.ts +574 -0
  87. package/src/llm/vertexai/index.ts +17 -1
  88. package/src/llm/vertexai/streamSmoothing.test.ts +109 -0
  89. package/src/specs/subagent.test.ts +44 -0
  90. package/src/specs/tokens.test.ts +159 -14
  91. package/src/tools/__tests__/SubagentExecutor.test.ts +48 -1
  92. package/src/tools/subagent/SubagentExecutor.ts +69 -5
  93. package/src/types/llm.ts +53 -36
  94. package/src/utils/tokens.ts +115 -30
package/src/types/llm.ts CHANGED
@@ -45,7 +45,8 @@ export type AzureClientOptions = Partial<OpenAIChatInput> &
45
45
  deploymentName?: string;
46
46
  } & BaseChatModelParams & {
47
47
  configuration?: OAIClientOptions;
48
- } & ManagedRequestOptions;
48
+ } & ManagedRequestOptions &
49
+ StreamSmoothingOptions;
49
50
  /**
50
51
  * Controls whether Claude's reasoning content is returned in adaptive
51
52
  * thinking responses. Added for Claude Opus 4.7, which omits thinking by
@@ -79,47 +80,63 @@ export type ManagedRequestOptions = {
79
80
  promptCacheExplicit?: boolean;
80
81
  safety_identifier?: string;
81
82
  };
82
- export type OpenAIClientOptions = ChatOpenAIFields & ManagedRequestOptions;
83
- export type AnthropicClientOptions = Omit<AnthropicInput, 'thinking'> & {
84
- thinking?: ThinkingConfig;
85
- promptCache?: boolean;
86
- /**
87
- * Prompt-cache breakpoint TTL. Defaults to `'1h'` (extended cache) when
88
- * `promptCache` is enabled; set `'5m'` to opt back into the legacy
89
- * 5-minute behavior.
90
- */
91
- promptCacheTtl?: PromptCacheTtl;
92
- };
93
- export type MistralAIClientOptions = ChatMistralAIInput;
94
- export type VertexAIClientOptions = ChatVertexAIInput & {
95
- includeThoughts?: boolean;
96
- thinkingConfig?: GoogleThinkingConfig;
97
- };
98
- export type BedrockAnthropicInput = ChatBedrockConverseInput & {
99
- additionalModelRequestFields?: ChatBedrockConverseInput['additionalModelRequestFields'] &
100
- AnthropicReasoning;
101
- promptCache?: boolean;
102
- /**
103
- * Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when
104
- * `promptCache` is enabled; set `'5m'` to opt into the legacy 5-minute
105
- * behavior. Bedrock models that don't support the 1-hour TTL downgrade to 5m
106
- * server-side, so the default is safe to leave on.
107
- */
108
- promptCacheTtl?: PromptCacheTtl;
83
+ /**
84
+ * Adaptive stream-smoothing configuration shared by every provider client.
85
+ */
86
+ export type StreamSmoothingOptions = {
109
87
  /**
110
88
  * Minimum delay in milliseconds between visible streamed content deltas.
89
+ * Defaults to 25; piece sizes adapt to the backlog so render lag stays
90
+ * bounded regardless of provider chunk size. Set 0 to disable smoothing.
111
91
  */
112
92
  _lc_stream_delay?: number;
113
93
  };
94
+
95
+ export type OpenAIClientOptions = ChatOpenAIFields &
96
+ ManagedRequestOptions &
97
+ StreamSmoothingOptions;
98
+ export type AnthropicClientOptions = Omit<AnthropicInput, 'thinking'> &
99
+ StreamSmoothingOptions & {
100
+ thinking?: ThinkingConfig;
101
+ promptCache?: boolean;
102
+ /**
103
+ * Prompt-cache breakpoint TTL. Defaults to `'1h'` (extended cache) when
104
+ * `promptCache` is enabled; set `'5m'` to opt back into the legacy
105
+ * 5-minute behavior.
106
+ */
107
+ promptCacheTtl?: PromptCacheTtl;
108
+ };
109
+ export type MistralAIClientOptions = ChatMistralAIInput &
110
+ StreamSmoothingOptions;
111
+ export type VertexAIClientOptions = ChatVertexAIInput &
112
+ StreamSmoothingOptions & {
113
+ includeThoughts?: boolean;
114
+ thinkingConfig?: GoogleThinkingConfig;
115
+ };
116
+ export type BedrockAnthropicInput = ChatBedrockConverseInput &
117
+ StreamSmoothingOptions & {
118
+ additionalModelRequestFields?: ChatBedrockConverseInput['additionalModelRequestFields'] &
119
+ AnthropicReasoning;
120
+ promptCache?: boolean;
121
+ /**
122
+ * Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when
123
+ * `promptCache` is enabled; set `'5m'` to opt into the legacy 5-minute
124
+ * behavior. Bedrock models that don't support the 1-hour TTL downgrade to 5m
125
+ * server-side, so the default is safe to leave on.
126
+ */
127
+ promptCacheTtl?: PromptCacheTtl;
128
+ };
114
129
  export type BedrockConverseClientOptions = BedrockAnthropicInput;
115
130
  export type BedrockAnthropicClientOptions = BedrockAnthropicInput;
116
- export type GoogleClientOptions = GoogleGenerativeAIChatInput & {
117
- customHeaders?: RequestOptions['customHeaders'];
118
- thinkingConfig?: GoogleThinkingConfig;
119
- includeServerSideToolInvocations?: boolean;
120
- };
121
- export type DeepSeekClientOptions = Partial<ChatDeepSeekInput>;
122
- export type XAIClientOptions = ChatXAIInput;
131
+ export type GoogleClientOptions = GoogleGenerativeAIChatInput &
132
+ StreamSmoothingOptions & {
133
+ customHeaders?: RequestOptions['customHeaders'];
134
+ thinkingConfig?: GoogleThinkingConfig;
135
+ includeServerSideToolInvocations?: boolean;
136
+ };
137
+ export type DeepSeekClientOptions = Partial<ChatDeepSeekInput> &
138
+ StreamSmoothingOptions;
139
+ export type XAIClientOptions = ChatXAIInput & StreamSmoothingOptions;
123
140
 
124
141
  export type ClientOptions =
125
142
  | OpenAIClientOptions
@@ -159,7 +176,7 @@ export type ProviderOptionsMap = {
159
176
  [Providers.ANTHROPIC]: AnthropicClientOptions;
160
177
  [Providers.MISTRALAI]: MistralAIClientOptions;
161
178
  [Providers.MISTRAL]: MistralAIClientOptions;
162
- [Providers.OPENROUTER]: ChatOpenRouterCallOptions;
179
+ [Providers.OPENROUTER]: ChatOpenRouterCallOptions & StreamSmoothingOptions;
163
180
  [Providers.BEDROCK]: BedrockAnthropicClientOptions;
164
181
  [Providers.XAI]: XAIClientOptions;
165
182
  [Providers.MOONSHOT]: OpenAIClientOptions;
@@ -10,6 +10,52 @@ import { ContentTypes } from '@/common/enum';
10
10
 
11
11
  export type EncodingName = 'o200k_base' | 'claude';
12
12
 
13
+ export type UnsafeTokenMeasurementReason =
14
+ | 'message_proxy'
15
+ | 'content_proxy'
16
+ | 'metadata_proxy'
17
+ | 'metadata_accessor'
18
+ | 'invalid_count';
19
+
20
+ export class UnsafeTokenMeasurementError extends Error {
21
+ readonly type = 'unsafe_token_measurement';
22
+ readonly reason: UnsafeTokenMeasurementReason;
23
+ readonly path: string;
24
+
25
+ constructor({
26
+ reason,
27
+ path,
28
+ }: {
29
+ reason: UnsafeTokenMeasurementReason;
30
+ path: string;
31
+ }) {
32
+ super(
33
+ JSON.stringify({
34
+ type: 'unsafe_token_measurement',
35
+ reason,
36
+ path,
37
+ })
38
+ );
39
+ this.name = 'UnsafeTokenMeasurementError';
40
+ this.reason = reason;
41
+ this.path = path;
42
+ }
43
+ }
44
+
45
+ function ensureSafeTokenMeasurement(value: number, path: string): number {
46
+ if (
47
+ !Number.isSafeInteger(value) ||
48
+ value < 0 ||
49
+ value >= Number.MAX_SAFE_INTEGER
50
+ ) {
51
+ throw new UnsafeTokenMeasurementError({
52
+ reason: 'invalid_count',
53
+ path,
54
+ });
55
+ }
56
+ return value;
57
+ }
58
+
13
59
  /** Anthropic minimum image token cost. */
14
60
  const ANTHROPIC_IMAGE_MIN_TOKENS = 1024;
15
61
  /** Anthropic divisor: tokens = width × height / 750. */
@@ -844,7 +890,10 @@ function getBoundedTextTokenCount(
844
890
  value.length > MAX_STRUCTURED_TOKENIZATION_CHARS
845
891
  ? value.slice(0, MAX_STRUCTURED_TOKENIZATION_CHARS)
846
892
  : value;
847
- const previewTokens = getTokenCount(preview);
893
+ const previewTokens = ensureSafeTokenMeasurement(
894
+ getTokenCount(preview),
895
+ 'tokenizer'
896
+ );
848
897
  const omittedChars = value.length - preview.length;
849
898
  if (omittedChars <= 0) {
850
899
  return previewTokens;
@@ -859,19 +908,25 @@ function getBoundedStructuredTokenCount(
859
908
  value: unknown,
860
909
  getTokenCount: (text: string) => number
861
910
  ): number {
862
- if (hasUnsafeStructuredSerialization(value)) {
863
- return Number.MAX_SAFE_INTEGER;
864
- }
865
911
  const serialized = serializeStructuredValueBounded(
866
912
  value,
867
913
  MAX_STRUCTURED_TOKENIZATION_CHARS
868
914
  );
869
- const previewTokens = getTokenCount(serialized.content);
915
+ const previewTokens = ensureSafeTokenMeasurement(
916
+ getTokenCount(serialized.content),
917
+ 'tokenizer'
918
+ );
870
919
  if (!serialized.truncated) {
871
920
  return previewTokens;
872
921
  }
873
- if (!Number.isSafeInteger(serialized.originalChars)) {
874
- return Number.MAX_SAFE_INTEGER;
922
+ // The bounded serializer uses MAX_SAFE_INTEGER as its unknown-size sentinel.
923
+ // Do not extrapolate from it: use its bounded preview and let the provider
924
+ // input projection preserve the same bounded behavior before invocation.
925
+ if (
926
+ !Number.isSafeInteger(serialized.originalChars) ||
927
+ serialized.originalChars >= Number.MAX_SAFE_INTEGER
928
+ ) {
929
+ return previewTokens;
875
930
  }
876
931
 
877
932
  const omittedChars = Math.max(
@@ -893,7 +948,10 @@ export function getTokenCountForMessage(
893
948
  const countText = (text: string): number =>
894
949
  getBoundedTextTokenCount(text, getTokenCount);
895
950
  if (isProxy(message)) {
896
- return Number.MAX_SAFE_INTEGER;
951
+ throw new UnsafeTokenMeasurementError({
952
+ reason: 'message_proxy',
953
+ path: 'message',
954
+ });
897
955
  }
898
956
 
899
957
  type ContentBlock = Record<string, unknown> & {
@@ -907,19 +965,22 @@ export function getTokenCountForMessage(
907
965
  };
908
966
  const representedToolCallIds = new Set<string>();
909
967
 
910
- const processValue = (value: unknown): void => {
968
+ const processValue = (value: unknown, path: string): void => {
911
969
  if (value != null && typeof value === 'object' && isProxy(value)) {
912
- numTokens = Number.MAX_SAFE_INTEGER;
913
- return;
970
+ throw new UnsafeTokenMeasurementError({
971
+ reason: 'content_proxy',
972
+ path,
973
+ });
914
974
  }
915
975
  if (Array.isArray(value)) {
916
- for (const raw of value) {
976
+ for (let index = 0; index < value.length; index++) {
977
+ const raw = value[index];
917
978
  if (
918
979
  typeof raw === 'string' ||
919
980
  typeof raw === 'number' ||
920
981
  typeof raw === 'boolean'
921
982
  ) {
922
- processValue(raw);
983
+ processValue(raw, `${path}[${index}]`);
923
984
  continue;
924
985
  }
925
986
  const item = raw as ContentBlock | null | undefined;
@@ -927,8 +988,10 @@ export function getTokenCountForMessage(
927
988
  continue;
928
989
  }
929
990
  if (isProxy(item)) {
930
- numTokens = Number.MAX_SAFE_INTEGER;
931
- return;
991
+ throw new UnsafeTokenMeasurementError({
992
+ reason: 'content_proxy',
993
+ path: `${path}[${index}]`,
994
+ });
932
995
  }
933
996
  if (typeof item.type !== 'string') {
934
997
  numTokens += getBoundedStructuredTokenCount(item, countText);
@@ -1010,7 +1073,7 @@ export function getTokenCountForMessage(
1010
1073
  }
1011
1074
  const output = item.tool_call.output;
1012
1075
  if (output != null) {
1013
- processValue(output);
1076
+ processValue(output, `${path}[${index}].tool_call.output`);
1014
1077
  }
1015
1078
  continue;
1016
1079
  }
@@ -1021,7 +1084,7 @@ export function getTokenCountForMessage(
1021
1084
  continue;
1022
1085
  }
1023
1086
 
1024
- processValue(nestedValue);
1087
+ processValue(nestedValue, `${path}[${index}].${item.type}`);
1025
1088
  }
1026
1089
  } else if (typeof value === 'string') {
1027
1090
  numTokens += countText(value);
@@ -1040,7 +1103,10 @@ export function getTokenCountForMessage(
1040
1103
  ? rawAdditionalKwargs
1041
1104
  : undefined;
1042
1105
  if (additionalKwargs != null && isProxy(additionalKwargs)) {
1043
- return Number.MAX_SAFE_INTEGER;
1106
+ throw new UnsafeTokenMeasurementError({
1107
+ reason: 'metadata_proxy',
1108
+ path: 'additional_kwargs',
1109
+ });
1044
1110
  }
1045
1111
  let additionalType: PropertyDescriptor | undefined;
1046
1112
  try {
@@ -1049,10 +1115,16 @@ export function getTokenCountForMessage(
1049
1115
  ? Object.getOwnPropertyDescriptor(additionalKwargs, 'type')
1050
1116
  : undefined;
1051
1117
  } catch {
1052
- return Number.MAX_SAFE_INTEGER;
1118
+ throw new UnsafeTokenMeasurementError({
1119
+ reason: 'metadata_accessor',
1120
+ path: 'additional_kwargs.type',
1121
+ });
1053
1122
  }
1054
1123
  if (additionalType != null && !('value' in additionalType)) {
1055
- return Number.MAX_SAFE_INTEGER;
1124
+ throw new UnsafeTokenMeasurementError({
1125
+ reason: 'metadata_accessor',
1126
+ path: 'additional_kwargs.type',
1127
+ });
1056
1128
  }
1057
1129
 
1058
1130
  let numTokens = tokensPerMessage;
@@ -1072,20 +1144,24 @@ export function getTokenCountForMessage(
1072
1144
  ) * IMAGE_TOKEN_SAFETY_MARGIN
1073
1145
  );
1074
1146
  } else {
1075
- processValue(message.content);
1076
- }
1077
- if (numTokens >= Number.MAX_SAFE_INTEGER) {
1078
- return Number.MAX_SAFE_INTEGER;
1147
+ processValue(message.content, 'content');
1079
1148
  }
1149
+ ensureSafeTokenMeasurement(numTokens, 'content');
1080
1150
  const messageRole = (message as BaseMessage & { role?: unknown }).role;
1081
1151
  if (messageType === 'ai' || messageRole === 'assistant') {
1082
1152
  const toolCalls = (message as AIMessage).tool_calls ?? [];
1083
1153
  if (isProxy(toolCalls)) {
1084
- return Number.MAX_SAFE_INTEGER;
1154
+ throw new UnsafeTokenMeasurementError({
1155
+ reason: 'metadata_proxy',
1156
+ path: 'tool_calls',
1157
+ });
1085
1158
  }
1086
1159
  for (const toolCall of toolCalls) {
1087
1160
  if (isProxy(toolCall)) {
1088
- return Number.MAX_SAFE_INTEGER;
1161
+ throw new UnsafeTokenMeasurementError({
1162
+ reason: 'metadata_proxy',
1163
+ path: 'tool_calls',
1164
+ });
1089
1165
  }
1090
1166
  if (
1091
1167
  typeof toolCall.id === 'string' &&
@@ -1111,11 +1187,17 @@ export function getTokenCountForMessage(
1111
1187
  ? Object.getOwnPropertyDescriptor(additionalKwargs, 'function_call')
1112
1188
  : undefined;
1113
1189
  } catch {
1114
- return Number.MAX_SAFE_INTEGER;
1190
+ throw new UnsafeTokenMeasurementError({
1191
+ reason: 'metadata_accessor',
1192
+ path: 'additional_kwargs.function_call',
1193
+ });
1115
1194
  }
1116
1195
  if (legacyFunctionCall != null) {
1117
1196
  if (!('value' in legacyFunctionCall)) {
1118
- return Number.MAX_SAFE_INTEGER;
1197
+ throw new UnsafeTokenMeasurementError({
1198
+ reason: 'metadata_accessor',
1199
+ path: 'additional_kwargs.function_call',
1200
+ });
1119
1201
  }
1120
1202
  if (legacyFunctionCall.value != null) {
1121
1203
  numTokens += getBoundedStructuredTokenCount(
@@ -1125,7 +1207,7 @@ export function getTokenCountForMessage(
1125
1207
  }
1126
1208
  }
1127
1209
  }
1128
- return Math.min(Number.MAX_SAFE_INTEGER, numTokens);
1210
+ return ensureSafeTokenMeasurement(numTokens, 'message');
1129
1211
  }
1130
1212
 
1131
1213
  /**
@@ -1180,7 +1262,10 @@ export const createTokenCounter = async (
1180
1262
  const isClaude = encoding === 'claude';
1181
1263
  return (message: BaseMessage): number => {
1182
1264
  const count = getTokenCountForMessage(message, countTokens, encoding);
1183
- return isClaude ? Math.ceil(count * CLAUDE_TOKEN_CORRECTION) : count;
1265
+ const correctedCount = isClaude
1266
+ ? Math.ceil(count * CLAUDE_TOKEN_CORRECTION)
1267
+ : count;
1268
+ return ensureSafeTokenMeasurement(correctedCount, 'message');
1184
1269
  };
1185
1270
  };
1186
1271