@librechat/agents 3.1.99 → 3.2.1

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.
@@ -89,6 +89,10 @@ const { AGENT, TOOLS, SUMMARIZE } = GraphNodeKeys;
89
89
  /** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
90
90
  const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
91
91
 
92
+ type ReasoningKey = 'reasoning_content' | 'reasoning';
93
+ type ReasoningSummary = { summary?: Array<{ text?: string }> };
94
+ type ReasoningDetail = { type?: string; text?: string };
95
+
92
96
  function getHandlerDispatchedEventKey(
93
97
  eventName: string,
94
98
  stepId: string
@@ -96,6 +100,197 @@ function getHandlerDispatchedEventKey(
96
100
  return `${eventName}:${stepId}`;
97
101
  }
98
102
 
103
+ function getReasoningText(
104
+ value: string | Partial<ReasoningSummary> | null | undefined
105
+ ): string | undefined {
106
+ if (typeof value === 'string') {
107
+ return value !== '' ? value : undefined;
108
+ }
109
+ const summaryText = value?.summary
110
+ ?.map((summary) => summary.text ?? '')
111
+ .filter((text) => text !== '')
112
+ .join('');
113
+ return summaryText != null && summaryText !== '' ? summaryText : undefined;
114
+ }
115
+
116
+ function getReasoningDetailsText(
117
+ value: ReasoningDetail[] | null | undefined
118
+ ): string | undefined {
119
+ if (!Array.isArray(value)) {
120
+ return undefined;
121
+ }
122
+ const reasoningText = value
123
+ .filter((detail) => detail.type === 'reasoning.text')
124
+ .map((detail) => detail.text ?? '')
125
+ .filter((text) => text !== '')
126
+ .join('');
127
+ return reasoningText !== '' ? reasoningText : undefined;
128
+ }
129
+
130
+ function getResponseReasoningContent({
131
+ responseMessage,
132
+ reasoningKey,
133
+ }: {
134
+ responseMessage?: Partial<AIMessageChunk>;
135
+ reasoningKey: ReasoningKey;
136
+ }): string | undefined {
137
+ const additionalKwargs = responseMessage?.additional_kwargs;
138
+ if (additionalKwargs == null) {
139
+ return undefined;
140
+ }
141
+
142
+ const keyedReasoning = getReasoningText(
143
+ additionalKwargs[reasoningKey] as
144
+ | string
145
+ | Partial<ReasoningSummary>
146
+ | null
147
+ | undefined
148
+ );
149
+ if (keyedReasoning != null) {
150
+ return keyedReasoning;
151
+ }
152
+
153
+ const reasoningContent = getReasoningText(
154
+ additionalKwargs.reasoning_content as
155
+ | string
156
+ | Partial<ReasoningSummary>
157
+ | null
158
+ | undefined
159
+ );
160
+ if (reasoningContent != null) {
161
+ return reasoningContent;
162
+ }
163
+
164
+ const reasoning = getReasoningText(
165
+ additionalKwargs.reasoning as
166
+ | string
167
+ | Partial<ReasoningSummary>
168
+ | null
169
+ | undefined
170
+ );
171
+ if (reasoning != null) {
172
+ return reasoning;
173
+ }
174
+
175
+ return getReasoningDetailsText(
176
+ additionalKwargs.reasoning_details as ReasoningDetail[] | null | undefined
177
+ );
178
+ }
179
+
180
+ function getTextMessageDeltaContent(
181
+ content: MessageContent | undefined
182
+ ): t.MessageDelta['content'] | undefined {
183
+ if (content == null) {
184
+ return undefined;
185
+ }
186
+ if (typeof content === 'string') {
187
+ return content !== ''
188
+ ? [{ type: ContentTypes.TEXT, text: content }]
189
+ : undefined;
190
+ }
191
+ if (content.length === 0) {
192
+ return undefined;
193
+ }
194
+ if (
195
+ !content.every(
196
+ (contentPart) =>
197
+ typeof contentPart === 'object' &&
198
+ 'type' in contentPart &&
199
+ typeof contentPart.type === 'string' &&
200
+ contentPart.type.startsWith('text')
201
+ )
202
+ ) {
203
+ return undefined;
204
+ }
205
+ return content as t.MessageDelta['content'];
206
+ }
207
+
208
+ async function dispatchTextMessageContent({
209
+ graph,
210
+ stepKey,
211
+ content,
212
+ metadata,
213
+ }: {
214
+ graph: Graph<t.BaseGraphState>;
215
+ stepKey: string;
216
+ content: t.MessageDelta['content'];
217
+ metadata: Record<string, unknown>;
218
+ }): Promise<boolean> {
219
+ const messageId = getMessageId(stepKey, graph) ?? '';
220
+ if (!messageId) {
221
+ return false;
222
+ }
223
+ await graph.dispatchRunStep(
224
+ stepKey,
225
+ {
226
+ type: StepTypes.MESSAGE_CREATION,
227
+ message_creation: { message_id: messageId },
228
+ },
229
+ metadata
230
+ );
231
+ const stepId = graph.getStepIdByKey(stepKey);
232
+ await graph.dispatchMessageDelta(stepId, { content }, metadata);
233
+ return true;
234
+ }
235
+
236
+ async function dispatchReasoningContent({
237
+ graph,
238
+ agentContext,
239
+ reasoningContent,
240
+ metadata,
241
+ }: {
242
+ graph: Graph<t.BaseGraphState>;
243
+ agentContext: AgentContext;
244
+ reasoningContent: string;
245
+ metadata: Record<string, unknown>;
246
+ }): Promise<boolean> {
247
+ const previousTokenType = agentContext.currentTokenType;
248
+ const previousTokenTypeSwitch = agentContext.tokenTypeSwitch;
249
+ const previousTransitionCount = agentContext.reasoningTransitionCount;
250
+
251
+ agentContext.currentTokenType = ContentTypes.THINK;
252
+ agentContext.tokenTypeSwitch = 'reasoning';
253
+
254
+ const stepKey = graph.getStepKey(metadata);
255
+ const messageId = getMessageId(stepKey, graph) ?? '';
256
+ if (!messageId) {
257
+ agentContext.currentTokenType = previousTokenType;
258
+ agentContext.tokenTypeSwitch = previousTokenTypeSwitch;
259
+ agentContext.reasoningTransitionCount = previousTransitionCount;
260
+ return false;
261
+ }
262
+
263
+ await graph.dispatchRunStep(
264
+ stepKey,
265
+ {
266
+ type: StepTypes.MESSAGE_CREATION,
267
+ message_creation: { message_id: messageId },
268
+ },
269
+ metadata
270
+ );
271
+ const stepId = graph.getStepIdByKey(stepKey);
272
+ await graph.dispatchReasoningDelta(
273
+ stepId,
274
+ {
275
+ content: [{ type: ContentTypes.THINK, think: reasoningContent }],
276
+ },
277
+ metadata
278
+ );
279
+ return true;
280
+ }
281
+
282
+ function markPostReasoningContent(agentContext: AgentContext): void {
283
+ if (
284
+ agentContext.tokenTypeSwitch !== 'reasoning' ||
285
+ agentContext.currentTokenType === ContentTypes.TEXT
286
+ ) {
287
+ return;
288
+ }
289
+ agentContext.currentTokenType = ContentTypes.TEXT;
290
+ agentContext.tokenTypeSwitch = 'content';
291
+ agentContext.reasoningTransitionCount++;
292
+ }
293
+
99
294
  export abstract class Graph<
100
295
  T extends t.BaseGraphState = t.BaseGraphState,
101
296
  _TNodeName extends string = string,
@@ -1472,60 +1667,37 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1472
1667
  const toolCalls = (responseMessage as AIMessageChunk | undefined)
1473
1668
  ?.tool_calls;
1474
1669
  const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
1670
+ const metadata = config.metadata as Record<string, unknown>;
1671
+ const responseReasoningContent = getResponseReasoningContent({
1672
+ responseMessage: responseMessage as Partial<AIMessageChunk> | undefined,
1673
+ reasoningKey: agentContext.reasoningKey,
1674
+ });
1675
+ const textMessageContent = getTextMessageDeltaContent(
1676
+ responseMessage?.content as MessageContent | undefined
1677
+ );
1475
1678
 
1476
1679
  if (hasToolCalls) {
1477
- const metadata = config.metadata as Record<string, unknown>;
1478
- const stepKey = this.getStepKey(metadata);
1479
- const content = responseMessage?.content as MessageContent | undefined;
1480
- const hasTextContent =
1481
- content != null &&
1482
- (typeof content === 'string'
1483
- ? content !== ''
1484
- : Array.isArray(content) && content.length > 0);
1485
-
1486
- /**
1487
- * Dispatch text content BEFORE creating TOOL_CALLS steps.
1488
- * getMessageId returns a new ID only on the first call for a step key;
1489
- * if the for-await consumer already claimed it, this is a no-op.
1490
- */
1491
- if (hasTextContent) {
1492
- const messageId = getMessageId(stepKey, this) ?? '';
1493
- if (messageId) {
1494
- await this.dispatchRunStep(
1495
- stepKey,
1496
- {
1497
- type: StepTypes.MESSAGE_CREATION,
1498
- message_creation: { message_id: messageId },
1499
- },
1500
- metadata
1501
- );
1502
- const stepId = this.getStepIdByKey(stepKey);
1503
- if (typeof content === 'string') {
1504
- await this.dispatchMessageDelta(
1505
- stepId,
1506
- {
1507
- content: [{ type: ContentTypes.TEXT, text: content }],
1508
- },
1509
- metadata
1510
- );
1511
- } else if (
1512
- Array.isArray(content) &&
1513
- content.every(
1514
- (c) =>
1515
- typeof c === 'object' &&
1516
- 'type' in c &&
1517
- typeof c.type === 'string' &&
1518
- c.type.startsWith('text')
1519
- )
1520
- ) {
1521
- await this.dispatchMessageDelta(
1522
- stepId,
1523
- {
1524
- content: content as t.MessageDelta['content'],
1525
- },
1526
- metadata
1527
- );
1528
- }
1680
+ const dispatchedReasoning =
1681
+ responseReasoningContent != null &&
1682
+ (await dispatchReasoningContent({
1683
+ graph: this,
1684
+ agentContext,
1685
+ reasoningContent: responseReasoningContent,
1686
+ metadata,
1687
+ }));
1688
+ if (dispatchedReasoning) {
1689
+ markPostReasoningContent(agentContext);
1690
+ }
1691
+ if (textMessageContent != null) {
1692
+ const stepKey = this.getStepKey(metadata);
1693
+ const dispatchedText = await dispatchTextMessageContent({
1694
+ graph: this,
1695
+ stepKey,
1696
+ content: textMessageContent,
1697
+ metadata,
1698
+ });
1699
+ if (dispatchedText) {
1700
+ markPostReasoningContent(agentContext);
1529
1701
  }
1530
1702
  }
1531
1703
 
@@ -1533,60 +1705,30 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1533
1705
  }
1534
1706
 
1535
1707
  /**
1536
- * When streaming is disabled, on_chat_model_stream events are never
1537
- * emitted so ChatModelStreamHandler never fires. Dispatch the text
1538
- * content as MESSAGE_CREATION + MESSAGE_DELTA here.
1708
+ * When streaming events are unavailable, ChatModelStreamHandler never
1709
+ * fires. Dispatch final reasoning/text content here. getMessageId makes
1710
+ * this a no-op when the streaming path already handled the same step.
1539
1711
  */
1540
- const disableStreaming =
1541
- (agentContext.clientOptions as t.OpenAIClientOptions | undefined)
1542
- ?.disableStreaming === true;
1543
-
1544
- if (
1545
- disableStreaming &&
1546
- !hasToolCalls &&
1547
- responseMessage != null &&
1548
- (responseMessage.content as MessageContent | undefined) != null
1549
- ) {
1550
- const metadata = config.metadata as Record<string, unknown>;
1551
- const stepKey = this.getStepKey(metadata);
1552
- const messageId = getMessageId(stepKey, this) ?? '';
1553
- if (messageId) {
1554
- await this.dispatchRunStep(
1712
+ if (!hasToolCalls && responseMessage != null) {
1713
+ const dispatchedReasoning =
1714
+ responseReasoningContent != null &&
1715
+ (await dispatchReasoningContent({
1716
+ graph: this,
1717
+ agentContext,
1718
+ reasoningContent: responseReasoningContent,
1719
+ metadata,
1720
+ }));
1721
+ if (dispatchedReasoning && textMessageContent != null) {
1722
+ markPostReasoningContent(agentContext);
1723
+ }
1724
+ if (textMessageContent != null) {
1725
+ const stepKey = this.getStepKey(metadata);
1726
+ await dispatchTextMessageContent({
1727
+ graph: this,
1555
1728
  stepKey,
1556
- {
1557
- type: StepTypes.MESSAGE_CREATION,
1558
- message_creation: { message_id: messageId },
1559
- },
1560
- metadata
1561
- );
1562
- const stepId = this.getStepIdByKey(stepKey);
1563
- const content = responseMessage.content;
1564
- if (typeof content === 'string') {
1565
- await this.dispatchMessageDelta(
1566
- stepId,
1567
- {
1568
- content: [{ type: ContentTypes.TEXT, text: content }],
1569
- },
1570
- metadata
1571
- );
1572
- } else if (
1573
- Array.isArray(content) &&
1574
- content.every(
1575
- (c) =>
1576
- typeof c === 'object' &&
1577
- 'type' in c &&
1578
- typeof c.type === 'string' &&
1579
- c.type.startsWith('text')
1580
- )
1581
- ) {
1582
- await this.dispatchMessageDelta(
1583
- stepId,
1584
- {
1585
- content: content as t.MessageDelta['content'],
1586
- },
1587
- metadata
1588
- );
1589
- }
1729
+ content: textMessageContent,
1730
+ metadata,
1731
+ });
1590
1732
  }
1591
1733
  }
1592
1734