@librechat/agents 3.2.0 → 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.
@@ -11,7 +11,7 @@ import { partitionAndMarkAnthropicToolCache, makeIsDeferred } from '../messages/
11
11
  import { formatContentStrings } from '../messages/content.mjs';
12
12
  import { extractToolDiscoveries } from '../messages/tools.mjs';
13
13
  import { messagesStateReducer } from '../messages/reducer.mjs';
14
- import { GraphNodeKeys, ContentTypes, Providers, StepTypes, GraphEvents } from '../common/enum.mjs';
14
+ import { GraphNodeKeys, ContentTypes, Providers, GraphEvents, StepTypes } from '../common/enum.mjs';
15
15
  import { resetIfNotEmpty, joinKeys } from '../utils/graph.mjs';
16
16
  import { isAnthropicLike, isOpenAILike, isGoogleLike } from '../utils/llm.mjs';
17
17
  import '../stream.mjs';
@@ -64,6 +64,112 @@ const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
64
64
  function getHandlerDispatchedEventKey(eventName, stepId) {
65
65
  return `${eventName}:${stepId}`;
66
66
  }
67
+ function getReasoningText(value) {
68
+ if (typeof value === 'string') {
69
+ return value !== '' ? value : undefined;
70
+ }
71
+ const summaryText = value?.summary
72
+ ?.map((summary) => summary.text ?? '')
73
+ .filter((text) => text !== '')
74
+ .join('');
75
+ return summaryText != null && summaryText !== '' ? summaryText : undefined;
76
+ }
77
+ function getReasoningDetailsText(value) {
78
+ if (!Array.isArray(value)) {
79
+ return undefined;
80
+ }
81
+ const reasoningText = value
82
+ .filter((detail) => detail.type === 'reasoning.text')
83
+ .map((detail) => detail.text ?? '')
84
+ .filter((text) => text !== '')
85
+ .join('');
86
+ return reasoningText !== '' ? reasoningText : undefined;
87
+ }
88
+ function getResponseReasoningContent({ responseMessage, reasoningKey, }) {
89
+ const additionalKwargs = responseMessage?.additional_kwargs;
90
+ if (additionalKwargs == null) {
91
+ return undefined;
92
+ }
93
+ const keyedReasoning = getReasoningText(additionalKwargs[reasoningKey]);
94
+ if (keyedReasoning != null) {
95
+ return keyedReasoning;
96
+ }
97
+ const reasoningContent = getReasoningText(additionalKwargs.reasoning_content);
98
+ if (reasoningContent != null) {
99
+ return reasoningContent;
100
+ }
101
+ const reasoning = getReasoningText(additionalKwargs.reasoning);
102
+ if (reasoning != null) {
103
+ return reasoning;
104
+ }
105
+ return getReasoningDetailsText(additionalKwargs.reasoning_details);
106
+ }
107
+ function getTextMessageDeltaContent(content) {
108
+ if (content == null) {
109
+ return undefined;
110
+ }
111
+ if (typeof content === 'string') {
112
+ return content !== ''
113
+ ? [{ type: ContentTypes.TEXT, text: content }]
114
+ : undefined;
115
+ }
116
+ if (content.length === 0) {
117
+ return undefined;
118
+ }
119
+ if (!content.every((contentPart) => typeof contentPart === 'object' &&
120
+ 'type' in contentPart &&
121
+ typeof contentPart.type === 'string' &&
122
+ contentPart.type.startsWith('text'))) {
123
+ return undefined;
124
+ }
125
+ return content;
126
+ }
127
+ async function dispatchTextMessageContent({ graph, stepKey, content, metadata, }) {
128
+ const messageId = getMessageId(stepKey, graph) ?? '';
129
+ if (!messageId) {
130
+ return false;
131
+ }
132
+ await graph.dispatchRunStep(stepKey, {
133
+ type: StepTypes.MESSAGE_CREATION,
134
+ message_creation: { message_id: messageId },
135
+ }, metadata);
136
+ const stepId = graph.getStepIdByKey(stepKey);
137
+ await graph.dispatchMessageDelta(stepId, { content }, metadata);
138
+ return true;
139
+ }
140
+ async function dispatchReasoningContent({ graph, agentContext, reasoningContent, metadata, }) {
141
+ const previousTokenType = agentContext.currentTokenType;
142
+ const previousTokenTypeSwitch = agentContext.tokenTypeSwitch;
143
+ const previousTransitionCount = agentContext.reasoningTransitionCount;
144
+ agentContext.currentTokenType = ContentTypes.THINK;
145
+ agentContext.tokenTypeSwitch = 'reasoning';
146
+ const stepKey = graph.getStepKey(metadata);
147
+ const messageId = getMessageId(stepKey, graph) ?? '';
148
+ if (!messageId) {
149
+ agentContext.currentTokenType = previousTokenType;
150
+ agentContext.tokenTypeSwitch = previousTokenTypeSwitch;
151
+ agentContext.reasoningTransitionCount = previousTransitionCount;
152
+ return false;
153
+ }
154
+ await graph.dispatchRunStep(stepKey, {
155
+ type: StepTypes.MESSAGE_CREATION,
156
+ message_creation: { message_id: messageId },
157
+ }, metadata);
158
+ const stepId = graph.getStepIdByKey(stepKey);
159
+ await graph.dispatchReasoningDelta(stepId, {
160
+ content: [{ type: ContentTypes.THINK, think: reasoningContent }],
161
+ }, metadata);
162
+ return true;
163
+ }
164
+ function markPostReasoningContent(agentContext) {
165
+ if (agentContext.tokenTypeSwitch !== 'reasoning' ||
166
+ agentContext.currentTokenType === ContentTypes.TEXT) {
167
+ return;
168
+ }
169
+ agentContext.currentTokenType = ContentTypes.TEXT;
170
+ agentContext.tokenTypeSwitch = 'content';
171
+ agentContext.reasoningTransitionCount++;
172
+ }
67
173
  class Graph {
68
174
  messageStepHasToolCalls = new Map();
69
175
  messageIdsByStepKey = new Map();
@@ -1072,80 +1178,61 @@ class StandardGraph extends Graph {
1072
1178
  const toolCalls = responseMessage
1073
1179
  ?.tool_calls;
1074
1180
  const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
1181
+ const metadata = config.metadata;
1182
+ const responseReasoningContent = getResponseReasoningContent({
1183
+ responseMessage: responseMessage,
1184
+ reasoningKey: agentContext.reasoningKey,
1185
+ });
1186
+ const textMessageContent = getTextMessageDeltaContent(responseMessage?.content);
1075
1187
  if (hasToolCalls) {
1076
- const metadata = config.metadata;
1077
- const stepKey = this.getStepKey(metadata);
1078
- const content = responseMessage?.content;
1079
- const hasTextContent = content != null &&
1080
- (typeof content === 'string'
1081
- ? content !== ''
1082
- : Array.isArray(content) && content.length > 0);
1083
- /**
1084
- * Dispatch text content BEFORE creating TOOL_CALLS steps.
1085
- * getMessageId returns a new ID only on the first call for a step key;
1086
- * if the for-await consumer already claimed it, this is a no-op.
1087
- */
1088
- if (hasTextContent) {
1089
- const messageId = getMessageId(stepKey, this) ?? '';
1090
- if (messageId) {
1091
- await this.dispatchRunStep(stepKey, {
1092
- type: StepTypes.MESSAGE_CREATION,
1093
- message_creation: { message_id: messageId },
1094
- }, metadata);
1095
- const stepId = this.getStepIdByKey(stepKey);
1096
- if (typeof content === 'string') {
1097
- await this.dispatchMessageDelta(stepId, {
1098
- content: [{ type: ContentTypes.TEXT, text: content }],
1099
- }, metadata);
1100
- }
1101
- else if (Array.isArray(content) &&
1102
- content.every((c) => typeof c === 'object' &&
1103
- 'type' in c &&
1104
- typeof c.type === 'string' &&
1105
- c.type.startsWith('text'))) {
1106
- await this.dispatchMessageDelta(stepId, {
1107
- content: content,
1108
- }, metadata);
1109
- }
1188
+ const dispatchedReasoning = responseReasoningContent != null &&
1189
+ (await dispatchReasoningContent({
1190
+ graph: this,
1191
+ agentContext,
1192
+ reasoningContent: responseReasoningContent,
1193
+ metadata,
1194
+ }));
1195
+ if (dispatchedReasoning) {
1196
+ markPostReasoningContent(agentContext);
1197
+ }
1198
+ if (textMessageContent != null) {
1199
+ const stepKey = this.getStepKey(metadata);
1200
+ const dispatchedText = await dispatchTextMessageContent({
1201
+ graph: this,
1202
+ stepKey,
1203
+ content: textMessageContent,
1204
+ metadata,
1205
+ });
1206
+ if (dispatchedText) {
1207
+ markPostReasoningContent(agentContext);
1110
1208
  }
1111
1209
  }
1112
1210
  await handleToolCalls(toolCalls, metadata, this);
1113
1211
  }
1114
1212
  /**
1115
- * When streaming is disabled, on_chat_model_stream events are never
1116
- * emitted so ChatModelStreamHandler never fires. Dispatch the text
1117
- * content as MESSAGE_CREATION + MESSAGE_DELTA here.
1213
+ * When streaming events are unavailable, ChatModelStreamHandler never
1214
+ * fires. Dispatch final reasoning/text content here. getMessageId makes
1215
+ * this a no-op when the streaming path already handled the same step.
1118
1216
  */
1119
- const disableStreaming = agentContext.clientOptions
1120
- ?.disableStreaming === true;
1121
- if (disableStreaming &&
1122
- !hasToolCalls &&
1123
- responseMessage != null &&
1124
- responseMessage.content != null) {
1125
- const metadata = config.metadata;
1126
- const stepKey = this.getStepKey(metadata);
1127
- const messageId = getMessageId(stepKey, this) ?? '';
1128
- if (messageId) {
1129
- await this.dispatchRunStep(stepKey, {
1130
- type: StepTypes.MESSAGE_CREATION,
1131
- message_creation: { message_id: messageId },
1132
- }, metadata);
1133
- const stepId = this.getStepIdByKey(stepKey);
1134
- const content = responseMessage.content;
1135
- if (typeof content === 'string') {
1136
- await this.dispatchMessageDelta(stepId, {
1137
- content: [{ type: ContentTypes.TEXT, text: content }],
1138
- }, metadata);
1139
- }
1140
- else if (Array.isArray(content) &&
1141
- content.every((c) => typeof c === 'object' &&
1142
- 'type' in c &&
1143
- typeof c.type === 'string' &&
1144
- c.type.startsWith('text'))) {
1145
- await this.dispatchMessageDelta(stepId, {
1146
- content: content,
1147
- }, metadata);
1148
- }
1217
+ if (!hasToolCalls && responseMessage != null) {
1218
+ const dispatchedReasoning = responseReasoningContent != null &&
1219
+ (await dispatchReasoningContent({
1220
+ graph: this,
1221
+ agentContext,
1222
+ reasoningContent: responseReasoningContent,
1223
+ metadata,
1224
+ }));
1225
+ if (dispatchedReasoning && textMessageContent != null) {
1226
+ markPostReasoningContent(agentContext);
1227
+ }
1228
+ if (textMessageContent != null) {
1229
+ const stepKey = this.getStepKey(metadata);
1230
+ await dispatchTextMessageContent({
1231
+ graph: this,
1232
+ stepKey,
1233
+ content: textMessageContent,
1234
+ metadata,
1235
+ });
1149
1236
  }
1150
1237
  }
1151
1238
  const invokeElapsed = ((Date.now() - invokeStart) / 1000).toFixed(2);