@librechat/agents 3.4.3 → 3.4.5

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 (99) hide show
  1. package/dist/cjs/graphs/Graph.cjs +27 -14
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  4. package/dist/cjs/hitl/askUserQuestions.cjs +66 -0
  5. package/dist/cjs/hitl/askUserQuestions.cjs.map +1 -0
  6. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs +46 -0
  7. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs.map +1 -0
  8. package/dist/cjs/hitl/index.cjs +2 -0
  9. package/dist/cjs/instrumentation.cjs +3 -3
  10. package/dist/cjs/langfuse.cjs +3 -3
  11. package/dist/cjs/langfuseRuntimeScope.cjs +1 -1
  12. package/dist/cjs/langfuseToolOutputTracing.cjs +2 -2
  13. package/dist/cjs/main.cjs +11 -1
  14. package/dist/cjs/messages/assistantPhase.cjs +59 -0
  15. package/dist/cjs/messages/assistantPhase.cjs.map +1 -0
  16. package/dist/cjs/messages/index.cjs +1 -0
  17. package/dist/cjs/prompts/activityLabel.cjs +76 -0
  18. package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
  19. package/dist/cjs/run.cjs +200 -10
  20. package/dist/cjs/run.cjs.map +1 -1
  21. package/dist/cjs/session/AgentSession.cjs +1 -1
  22. package/dist/cjs/stream.cjs +45 -8
  23. package/dist/cjs/stream.cjs.map +1 -1
  24. package/dist/cjs/tools/ToolNode.cjs +3 -3
  25. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +81 -6
  26. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  27. package/dist/cjs/types/hitl.cjs +13 -0
  28. package/dist/cjs/types/hitl.cjs.map +1 -0
  29. package/dist/cjs/utils/callbacks.cjs +8 -0
  30. package/dist/cjs/utils/callbacks.cjs.map +1 -1
  31. package/dist/esm/graphs/Graph.mjs +27 -14
  32. package/dist/esm/graphs/Graph.mjs.map +1 -1
  33. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  34. package/dist/esm/hitl/askUserQuestions.mjs +66 -0
  35. package/dist/esm/hitl/askUserQuestions.mjs.map +1 -0
  36. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs +43 -0
  37. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs.map +1 -0
  38. package/dist/esm/hitl/index.mjs +2 -0
  39. package/dist/esm/instrumentation.mjs +3 -3
  40. package/dist/esm/langfuse.mjs +3 -3
  41. package/dist/esm/langfuseRuntimeScope.mjs +1 -1
  42. package/dist/esm/langfuseToolOutputTracing.mjs +2 -2
  43. package/dist/esm/main.mjs +5 -2
  44. package/dist/esm/messages/assistantPhase.mjs +57 -0
  45. package/dist/esm/messages/assistantPhase.mjs.map +1 -0
  46. package/dist/esm/messages/index.mjs +1 -0
  47. package/dist/esm/prompts/activityLabel.mjs +74 -1
  48. package/dist/esm/prompts/activityLabel.mjs.map +1 -1
  49. package/dist/esm/run.mjs +202 -12
  50. package/dist/esm/run.mjs.map +1 -1
  51. package/dist/esm/session/AgentSession.mjs +1 -1
  52. package/dist/esm/stream.mjs +45 -8
  53. package/dist/esm/stream.mjs.map +1 -1
  54. package/dist/esm/tools/ToolNode.mjs +3 -3
  55. package/dist/esm/tools/subagent/SubagentExecutor.mjs +81 -6
  56. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  57. package/dist/esm/types/hitl.mjs +13 -0
  58. package/dist/esm/types/hitl.mjs.map +1 -0
  59. package/dist/esm/utils/callbacks.mjs +8 -1
  60. package/dist/esm/utils/callbacks.mjs.map +1 -1
  61. package/dist/types/hitl/askUserQuestions.d.ts +24 -0
  62. package/dist/types/hitl/askUserQuestionsInterrupt.d.ts +11 -0
  63. package/dist/types/hitl/index.d.ts +2 -0
  64. package/dist/types/messages/assistantPhase.d.ts +22 -0
  65. package/dist/types/messages/index.d.ts +1 -0
  66. package/dist/types/prompts/activityLabel.d.ts +21 -1
  67. package/dist/types/run.d.ts +15 -2
  68. package/dist/types/types/activityLabel.d.ts +63 -0
  69. package/dist/types/types/assistantPhase.d.ts +6 -0
  70. package/dist/types/types/graph.d.ts +8 -1
  71. package/dist/types/types/hitl.d.ts +31 -2
  72. package/dist/types/types/index.d.ts +1 -0
  73. package/dist/types/types/stream.d.ts +11 -0
  74. package/dist/types/utils/callbacks.d.ts +1 -0
  75. package/package.json +2 -1
  76. package/src/graphs/Graph.ts +33 -9
  77. package/src/graphs/__tests__/Graph.reasoning.test.ts +57 -0
  78. package/src/hitl/askUserQuestions.ts +126 -0
  79. package/src/hitl/askUserQuestionsInterrupt.ts +115 -0
  80. package/src/hitl/index.ts +6 -0
  81. package/src/messages/assistantPhase.test.ts +75 -0
  82. package/src/messages/assistantPhase.ts +91 -0
  83. package/src/messages/index.ts +1 -0
  84. package/src/prompts/activityLabel.ts +177 -1
  85. package/src/run.ts +403 -21
  86. package/src/specs/activity-label-prompt.test.ts +123 -1
  87. package/src/specs/activity-phase-label.test.ts +306 -0
  88. package/src/specs/ask-user-questions.live.test.ts +185 -0
  89. package/src/specs/ask-user-questions.test.ts +293 -0
  90. package/src/stream.ts +69 -12
  91. package/src/tools/__tests__/SubagentExecutor.test.ts +436 -0
  92. package/src/tools/subagent/SubagentExecutor.ts +160 -8
  93. package/src/types/activityLabel.ts +65 -0
  94. package/src/types/assistantPhase.ts +6 -0
  95. package/src/types/graph.ts +8 -0
  96. package/src/types/hitl.ts +36 -2
  97. package/src/types/index.ts +1 -0
  98. package/src/types/stream.ts +9 -0
  99. package/src/utils/callbacks.ts +21 -0
@@ -0,0 +1,293 @@
1
+ import { z } from 'zod';
2
+ import { tool } from '@langchain/core/tools';
3
+ import { describe, expect, it } from '@jest/globals';
4
+ import { AIMessage, ToolMessage } from '@langchain/core/messages';
5
+ import {
6
+ END,
7
+ START,
8
+ Command,
9
+ StateGraph,
10
+ MemorySaver,
11
+ isInterrupted,
12
+ MessagesAnnotation,
13
+ } from '@langchain/langgraph';
14
+ import type { BaseMessage } from '@langchain/core/messages';
15
+ import type * as t from '@/types';
16
+ import {
17
+ ASK_USER_QUESTION_ID_PATTERN,
18
+ askUserQuestions,
19
+ isAskUserQuestionsInterrupt,
20
+ MAX_ASK_USER_QUESTIONS,
21
+ } from '@/hitl';
22
+ import { ToolNode } from '@/tools/ToolNode';
23
+
24
+ type MessagesUpdate = { messages: BaseMessage[] };
25
+ const questions = [
26
+ {
27
+ id: 'metric',
28
+ header: 'Metric',
29
+ question: 'Which performance cost should be analyzed?',
30
+ options: [
31
+ { label: 'ClickHouse workload', value: 'workload' },
32
+ { label: 'Website experience', value: 'website' },
33
+ ],
34
+ },
35
+ {
36
+ id: 'window',
37
+ header: 'Window',
38
+ question: 'Which time window should be used?',
39
+ options: [
40
+ { label: 'Last 24 hours', value: '24h' },
41
+ { label: 'Last 7 days', value: '7d' },
42
+ ],
43
+ },
44
+ ] satisfies t.AskUserQuestionBatchItem[];
45
+
46
+ const questionSchema = z.object({
47
+ id: z.string().regex(ASK_USER_QUESTION_ID_PATTERN),
48
+ header: z.string().optional(),
49
+ question: z.string(),
50
+ options: z
51
+ .array(z.object({ label: z.string(), value: z.string() }))
52
+ .optional(),
53
+ });
54
+ const askUserQuestionsSchema = z.object({
55
+ questions: z.array(questionSchema).min(1).max(MAX_ASK_USER_QUESTIONS),
56
+ });
57
+ type AskUserQuestionsInput = z.infer<typeof askUserQuestionsSchema>;
58
+
59
+ function buildGraph() {
60
+ const askTool = tool(
61
+ async (input: AskUserQuestionsInput, config) => {
62
+ const resolution = askUserQuestions(input, {
63
+ toolCallId: config.toolCall?.id,
64
+ });
65
+ return JSON.stringify(resolution);
66
+ },
67
+ {
68
+ name: 'ask_user_question',
69
+ description: 'Ask several related questions in one interaction.',
70
+ schema: askUserQuestionsSchema,
71
+ }
72
+ );
73
+ const node = new ToolNode({
74
+ tools: [askTool],
75
+ directToolNames: new Set(['ask_user_question']),
76
+ interruptingToolNames: new Set(['ask_user_question']),
77
+ });
78
+
79
+ return new StateGraph(MessagesAnnotation)
80
+ .addNode(
81
+ 'agent',
82
+ (): MessagesUpdate => ({
83
+ messages: [
84
+ new AIMessage({
85
+ content: '',
86
+ tool_calls: [
87
+ {
88
+ id: 'batched-ask-call',
89
+ name: 'ask_user_question',
90
+ args: { questions },
91
+ },
92
+ ],
93
+ }),
94
+ ],
95
+ })
96
+ )
97
+ .addNode('tools', node)
98
+ .addEdge(START, 'agent')
99
+ .addEdge('agent', 'tools')
100
+ .addEdge('tools', END)
101
+ .compile({
102
+ checkpointer: new MemorySaver(),
103
+ });
104
+ }
105
+
106
+ describe('askUserQuestions', () => {
107
+ it('pauses once for a batch and resumes with keyed answers', async () => {
108
+ const graph = buildGraph();
109
+ const config = { configurable: { thread_id: 'batched-questions' } };
110
+
111
+ const first = await graph.invoke({ messages: [] }, config);
112
+ expect(isInterrupted<t.HumanInterruptPayload>(first)).toBe(true);
113
+ if (!isInterrupted<t.HumanInterruptPayload>(first)) {
114
+ throw new Error('expected batched question interrupt');
115
+ }
116
+ expect(first.__interrupt__).toHaveLength(1);
117
+ const payload = first.__interrupt__[0].value;
118
+ expect(isAskUserQuestionsInterrupt(payload)).toBe(true);
119
+ expect(payload).toMatchObject({
120
+ type: 'ask_user_question',
121
+ tool_call_id: 'batched-ask-call',
122
+ question: { question: questions[0].question },
123
+ questions,
124
+ });
125
+
126
+ const answers: t.AskUserQuestionsResolution = {
127
+ answers: { metric: 'workload', window: '7d' },
128
+ };
129
+ const second = (await graph.invoke(
130
+ new Command({ resume: answers }),
131
+ config
132
+ )) as MessagesUpdate;
133
+ const result = second.messages.find(
134
+ (message): message is ToolMessage =>
135
+ message.getType() === 'tool' &&
136
+ (message as ToolMessage).name === 'ask_user_question'
137
+ );
138
+ expect(result).toBeDefined();
139
+ expect(JSON.parse(String(result!.content))).toEqual(answers);
140
+ });
141
+
142
+ it('rejects duplicate question ids before raising an interrupt', () => {
143
+ const request: t.AskUserQuestionsRequest = {
144
+ questions: [questions[0], { ...questions[1], id: questions[0].id }],
145
+ };
146
+
147
+ expect(() => askUserQuestions(request)).toThrow(
148
+ 'requires unique question ids'
149
+ );
150
+ });
151
+
152
+ it('distinguishes singular ask payloads from batched payloads', () => {
153
+ const sparseOptions: unknown[] = [];
154
+ sparseOptions.length = 2;
155
+
156
+ expect(
157
+ isAskUserQuestionsInterrupt({
158
+ type: 'ask_user_question',
159
+ question: { question: 'Proceed?' },
160
+ })
161
+ ).toBe(false);
162
+ expect(
163
+ isAskUserQuestionsInterrupt({
164
+ type: 'ask_user_question',
165
+ question: { question: 'Proceed?' },
166
+ questions: [],
167
+ })
168
+ ).toBe(false);
169
+ expect(
170
+ isAskUserQuestionsInterrupt({
171
+ type: 'ask_user_question',
172
+ question: { question: 'Proceed?' },
173
+ questions: [null],
174
+ })
175
+ ).toBe(false);
176
+ expect(
177
+ isAskUserQuestionsInterrupt({
178
+ type: 'ask_user_question',
179
+ question: { question: 'Proceed?' },
180
+ questions: [{ id: '__proto__', question: 'Unsafe key?' }],
181
+ })
182
+ ).toBe(false);
183
+ expect(
184
+ isAskUserQuestionsInterrupt({
185
+ type: 'ask_user_question',
186
+ question: { question: 'Proceed?' },
187
+ questions: Array.from(
188
+ { length: MAX_ASK_USER_QUESTIONS + 1 },
189
+ (_, index) => ({
190
+ id: `question-${index}`,
191
+ question: `Question ${index}?`,
192
+ })
193
+ ),
194
+ })
195
+ ).toBe(false);
196
+ expect(
197
+ isAskUserQuestionsInterrupt({
198
+ type: 'ask_user_question',
199
+ question: { question: 'Proceed?' },
200
+ questions: [
201
+ {
202
+ id: 'choice',
203
+ question: 'Choose?',
204
+ options: [{ label: 'Missing value' }],
205
+ },
206
+ ],
207
+ })
208
+ ).toBe(false);
209
+ expect(
210
+ isAskUserQuestionsInterrupt({
211
+ type: 'ask_user_question',
212
+ question: { question: 'Proceed?' },
213
+ questions: [
214
+ {
215
+ id: 'choice',
216
+ question: 'Choose?',
217
+ options: sparseOptions,
218
+ },
219
+ ],
220
+ })
221
+ ).toBe(false);
222
+ });
223
+
224
+ it('returns a tool error when a resumed batch omits an answer', async () => {
225
+ const graph = buildGraph();
226
+ const config = { configurable: { thread_id: 'incomplete-answers' } };
227
+
228
+ await graph.invoke({ messages: [] }, config);
229
+ const resumed = (await graph.invoke(
230
+ new Command({ resume: { answers: { metric: 'workload' } } }),
231
+ config
232
+ )) as MessagesUpdate;
233
+ const result = resumed.messages.find(
234
+ (message): message is ToolMessage =>
235
+ message.getType() === 'tool' &&
236
+ (message as ToolMessage).name === 'ask_user_question'
237
+ );
238
+
239
+ expect(result?.status).toBe('error');
240
+ expect(String(result?.content)).toContain(
241
+ 'requires a string answer for question id "window"'
242
+ );
243
+ });
244
+
245
+ it('rejects empty question ids before raising an interrupt', () => {
246
+ const request: t.AskUserQuestionsRequest = {
247
+ questions: [{ ...questions[0], id: ' ' }],
248
+ };
249
+
250
+ expect(() => askUserQuestions(request)).toThrow(
251
+ 'requires each question id to match'
252
+ );
253
+ });
254
+
255
+ it('rejects unsafe answer-map keys before raising an interrupt', () => {
256
+ const request: t.AskUserQuestionsRequest = {
257
+ questions: [{ ...questions[0], id: '__proto__' }],
258
+ };
259
+
260
+ expect(() => askUserQuestions(request)).toThrow(
261
+ 'requires each question id to match'
262
+ );
263
+ });
264
+
265
+ it('rejects sparse option arrays before raising an interrupt', () => {
266
+ const sparseOptions: t.AskUserQuestionOption[] = [];
267
+ sparseOptions.length = 2;
268
+ const request: t.AskUserQuestionsRequest = {
269
+ questions: [{ ...questions[0], options: sparseOptions }],
270
+ };
271
+
272
+ expect(() => askUserQuestions(request)).toThrow(
273
+ 'requires each question and option to have valid string fields'
274
+ );
275
+ });
276
+
277
+ it('rejects batches larger than four questions', () => {
278
+ expect(MAX_ASK_USER_QUESTIONS).toBe(4);
279
+ const request: t.AskUserQuestionsRequest = {
280
+ questions: [
281
+ questions[0],
282
+ questions[1],
283
+ { ...questions[0], id: 'third' },
284
+ { ...questions[0], id: 'fourth' },
285
+ { ...questions[0], id: 'fifth' },
286
+ ],
287
+ };
288
+
289
+ expect(() => askUserQuestions(request)).toThrow(
290
+ 'accepts at most 4 questions'
291
+ );
292
+ });
293
+ });
package/src/stream.ts CHANGED
@@ -3,8 +3,19 @@ import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
3
3
  import type { ChatOpenAIReasoningSummary } from '@langchain/openai';
4
4
  import type { AIMessageChunk } from '@langchain/core/messages';
5
5
  import type { AgentContext } from '@/agents/AgentContext';
6
+ import type { RunBreakerScope } from '@/llm/streamLimits';
6
7
  import type { StandardGraph } from '@/graphs';
7
8
  import type * as t from '@/types';
9
+ import {
10
+ claimStreamLimitCharge,
11
+ combineCompleteToolCalls,
12
+ enforceCompleteToolCallArgLimit,
13
+ enforceStreamedToolCallArgLimit,
14
+ enforceStreamDeltaEventLimit,
15
+ requiresStreamLimitAccounting,
16
+ StreamLimitExceededError,
17
+ STREAM_LIMIT_EPOCH_KEY,
18
+ } from '@/llm/streamLimits';
8
19
  import {
9
20
  getStreamedToolCallSeal,
10
21
  getStreamedToolCallAdapter,
@@ -21,6 +32,10 @@ import {
21
32
  CODE_EXECUTION_TOOLS,
22
33
  LOCAL_CODING_BUNDLE_NAMES,
23
34
  } from '@/common';
35
+ import {
36
+ getMessageCreationContentMetadata,
37
+ splitAssistantTextContentByPhase,
38
+ } from '@/messages/assistantPhase';
24
39
  import {
25
40
  buildToolExecutionRequestPlan,
26
41
  coerceRecordArgs,
@@ -39,17 +54,6 @@ import {
39
54
  calculateMaxToolResultChars,
40
55
  truncateToolResultContent,
41
56
  } from '@/utils/truncation';
42
- import type { RunBreakerScope } from '@/llm/streamLimits';
43
- import {
44
- claimStreamLimitCharge,
45
- combineCompleteToolCalls,
46
- enforceCompleteToolCallArgLimit,
47
- enforceStreamedToolCallArgLimit,
48
- enforceStreamDeltaEventLimit,
49
- requiresStreamLimitAccounting,
50
- StreamLimitExceededError,
51
- STREAM_LIMIT_EPOCH_KEY,
52
- } from '@/llm/streamLimits';
53
57
  import { resolveToolOutcome, outcomeFieldsFromResult } from '@/tools/intentArg';
54
58
  import { TOOL_OUTPUT_REF_PATTERN } from '@/tools/toolOutputReferences';
55
59
  import { safeDispatchCustomEvent } from '@/utils/events';
@@ -521,10 +525,14 @@ function shouldStartFreshMessageStepAfterGoogleServerSideTool({
521
525
  async function dispatchMessageCreationStep({
522
526
  graph,
523
527
  stepKey,
528
+ content,
529
+ contentType,
524
530
  metadata,
525
531
  }: {
526
532
  graph: StandardGraph;
527
533
  stepKey: string;
534
+ content?: string | t.MessageContentComplex[];
535
+ contentType?: ContentTypes.TEXT | ContentTypes.THINK;
528
536
  metadata?: Record<string, unknown>;
529
537
  }): Promise<string> {
530
538
  const messageId = getMessageId(stepKey, graph, true) ?? '';
@@ -534,6 +542,7 @@ async function dispatchMessageCreationStep({
534
542
  type: StepTypes.MESSAGE_CREATION,
535
543
  message_creation: {
536
544
  message_id: messageId,
545
+ ...getMessageCreationContentMetadata(content, contentType),
537
546
  },
538
547
  },
539
548
  metadata
@@ -555,6 +564,7 @@ async function dispatchMessageContentParts({
555
564
  const currentStepId = await dispatchMessageCreationStep({
556
565
  graph,
557
566
  stepKey,
567
+ content: [contentPart],
558
568
  metadata,
559
569
  });
560
570
  if (isGoogleServerSideToolContentPart(contentPart)) {
@@ -587,6 +597,8 @@ async function dispatchReasoningContentParts({
587
597
  const currentStepId = await dispatchMessageCreationStep({
588
598
  graph,
589
599
  stepKey,
600
+ content,
601
+ contentType: ContentTypes.THINK,
590
602
  metadata,
591
603
  });
592
604
  await graph.dispatchReasoningDelta(
@@ -1811,14 +1823,53 @@ export class ChatModelStreamHandler implements t.EventHandler {
1811
1823
  return;
1812
1824
  }
1813
1825
 
1826
+ if (Array.isArray(content) && content.every(isTextContentPart)) {
1827
+ const contentGroups = splitAssistantTextContentByPhase(content);
1828
+ const currentStepId = graph.stepKeyIds?.get(stepKey)?.at(-1);
1829
+ const currentStep =
1830
+ currentStepId == null ? undefined : graph.getRunStep(currentStepId);
1831
+ const currentPhase =
1832
+ currentStep?.stepDetails.type === StepTypes.MESSAGE_CREATION
1833
+ ? currentStep.stepDetails.message_creation.phase
1834
+ : undefined;
1835
+ const nextPhase = getMessageCreationContentMetadata(
1836
+ contentGroups[0]
1837
+ ).phase;
1838
+ const phaseChanged =
1839
+ currentPhase != null &&
1840
+ nextPhase != null &&
1841
+ currentPhase !== nextPhase;
1842
+ if (contentGroups.length > 1 || phaseChanged) {
1843
+ for (const contentGroup of contentGroups) {
1844
+ const currentStepId = await dispatchMessageCreationStep({
1845
+ graph,
1846
+ stepKey,
1847
+ content: contentGroup,
1848
+ metadata,
1849
+ });
1850
+ await graph.dispatchMessageDelta(
1851
+ currentStepId,
1852
+ { content: contentGroup },
1853
+ metadata
1854
+ );
1855
+ }
1856
+ return;
1857
+ }
1858
+ }
1859
+
1814
1860
  const message_id = getMessageId(stepKey, graph) ?? '';
1815
1861
  if (message_id) {
1862
+ const fallbackContentType =
1863
+ agentContext.currentTokenType === ContentTypes.TEXT
1864
+ ? ContentTypes.TEXT
1865
+ : ContentTypes.THINK;
1816
1866
  await graph.dispatchRunStep(
1817
1867
  stepKey,
1818
1868
  {
1819
1869
  type: StepTypes.MESSAGE_CREATION,
1820
1870
  message_creation: {
1821
1871
  message_id,
1872
+ ...getMessageCreationContentMetadata(content, fallbackContentType),
1822
1873
  },
1823
1874
  },
1824
1875
  metadata
@@ -1835,7 +1886,12 @@ export class ChatModelStreamHandler implements t.EventHandler {
1835
1886
  content,
1836
1887
  })
1837
1888
  ) {
1838
- stepId = await dispatchMessageCreationStep({ graph, stepKey, metadata });
1889
+ stepId = await dispatchMessageCreationStep({
1890
+ graph,
1891
+ stepKey,
1892
+ content,
1893
+ metadata,
1894
+ });
1839
1895
  runStep = graph.getRunStep(stepId);
1840
1896
  }
1841
1897
  if (!runStep) {
@@ -1906,6 +1962,7 @@ hasToolCallChunks: ${hasToolCallChunks}
1906
1962
  type: StepTypes.MESSAGE_CREATION,
1907
1963
  message_creation: {
1908
1964
  message_id,
1965
+ content_type: ContentTypes.TEXT,
1909
1966
  },
1910
1967
  },
1911
1968
  metadata