@librechat/agents 3.0.56 → 3.0.61
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/cjs/messages/format.cjs +1 -24
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/stream.cjs +11 -6
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/esm/messages/format.mjs +1 -24
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/stream.mjs +11 -6
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/types/messages/format.d.ts +2 -8
- package/dist/types/types/stream.d.ts +2 -11
- package/package.json +1 -1
- package/src/messages/format.ts +1 -33
- package/src/messages/formatAgentMessages.test.ts +0 -247
- package/src/stream.ts +12 -6
- package/src/types/stream.ts +4 -12
|
@@ -1141,251 +1141,4 @@ describe('formatAgentMessages', () => {
|
|
|
1141
1141
|
expect(result.messages[1].name).toBe('search');
|
|
1142
1142
|
expect(result.messages[1].content).toBe('');
|
|
1143
1143
|
});
|
|
1144
|
-
|
|
1145
|
-
describe('targetAgentId filtering', () => {
|
|
1146
|
-
it('should filter content parts to only include those from targetAgentId', () => {
|
|
1147
|
-
const payload: TPayload = [
|
|
1148
|
-
{ role: 'user', content: 'Hello' },
|
|
1149
|
-
{
|
|
1150
|
-
role: 'assistant',
|
|
1151
|
-
content: [
|
|
1152
|
-
{ type: ContentTypes.TEXT, text: 'Response from agent_a' },
|
|
1153
|
-
{ type: ContentTypes.TEXT, text: 'Response from agent_b' },
|
|
1154
|
-
{ type: ContentTypes.TEXT, text: 'Another from agent_a' },
|
|
1155
|
-
],
|
|
1156
|
-
},
|
|
1157
|
-
];
|
|
1158
|
-
|
|
1159
|
-
const contentMetadataMap = new Map([
|
|
1160
|
-
[0, { agentId: 'agent_a' }],
|
|
1161
|
-
[1, { agentId: 'agent_b' }],
|
|
1162
|
-
[2, { agentId: 'agent_a' }],
|
|
1163
|
-
]);
|
|
1164
|
-
|
|
1165
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1166
|
-
targetAgentId: 'agent_a',
|
|
1167
|
-
contentMetadataMap,
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
// Should have user message + filtered assistant message
|
|
1171
|
-
expect(result.messages).toHaveLength(2);
|
|
1172
|
-
expect(result.messages[0]).toBeInstanceOf(HumanMessage);
|
|
1173
|
-
expect(result.messages[1]).toBeInstanceOf(AIMessage);
|
|
1174
|
-
|
|
1175
|
-
// The AIMessage should only have agent_a's content parts
|
|
1176
|
-
const aiMessage = result.messages[1] as AIMessage;
|
|
1177
|
-
expect(Array.isArray(aiMessage.content)).toBe(true);
|
|
1178
|
-
expect(aiMessage.content as Array<{ text: string }>).toHaveLength(2);
|
|
1179
|
-
expect((aiMessage.content as Array<{ text: string }>)[0].text).toBe(
|
|
1180
|
-
'Response from agent_a'
|
|
1181
|
-
);
|
|
1182
|
-
expect((aiMessage.content as Array<{ text: string }>)[1].text).toBe(
|
|
1183
|
-
'Another from agent_a'
|
|
1184
|
-
);
|
|
1185
|
-
});
|
|
1186
|
-
|
|
1187
|
-
it('should skip assistant message entirely if no content parts match targetAgentId', () => {
|
|
1188
|
-
const payload: TPayload = [
|
|
1189
|
-
{ role: 'user', content: 'Hello' },
|
|
1190
|
-
{
|
|
1191
|
-
role: 'assistant',
|
|
1192
|
-
content: [{ type: ContentTypes.TEXT, text: 'Response from agent_b' }],
|
|
1193
|
-
},
|
|
1194
|
-
];
|
|
1195
|
-
|
|
1196
|
-
const contentMetadataMap = new Map([[0, { agentId: 'agent_b' }]]);
|
|
1197
|
-
|
|
1198
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1199
|
-
targetAgentId: 'agent_a',
|
|
1200
|
-
contentMetadataMap,
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
// Should only have the user message, assistant message skipped
|
|
1204
|
-
expect(result.messages).toHaveLength(1);
|
|
1205
|
-
expect(result.messages[0]).toBeInstanceOf(HumanMessage);
|
|
1206
|
-
});
|
|
1207
|
-
|
|
1208
|
-
it('should not filter when targetAgentId is not provided', () => {
|
|
1209
|
-
const payload: TPayload = [
|
|
1210
|
-
{
|
|
1211
|
-
role: 'assistant',
|
|
1212
|
-
content: [
|
|
1213
|
-
{ type: ContentTypes.TEXT, text: 'Response from agent_a' },
|
|
1214
|
-
{ type: ContentTypes.TEXT, text: 'Response from agent_b' },
|
|
1215
|
-
],
|
|
1216
|
-
},
|
|
1217
|
-
];
|
|
1218
|
-
|
|
1219
|
-
const contentMetadataMap = new Map([
|
|
1220
|
-
[0, { agentId: 'agent_a' }],
|
|
1221
|
-
[1, { agentId: 'agent_b' }],
|
|
1222
|
-
]);
|
|
1223
|
-
|
|
1224
|
-
// No targetAgentId provided - should include all content
|
|
1225
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1226
|
-
contentMetadataMap,
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
|
-
expect(result.messages).toHaveLength(1);
|
|
1230
|
-
const aiMessage = result.messages[0] as AIMessage;
|
|
1231
|
-
expect(Array.isArray(aiMessage.content)).toBe(true);
|
|
1232
|
-
expect(aiMessage.content as Array<{ text: string }>).toHaveLength(2);
|
|
1233
|
-
});
|
|
1234
|
-
|
|
1235
|
-
it('should not filter when contentMetadataMap is not provided', () => {
|
|
1236
|
-
const payload: TPayload = [
|
|
1237
|
-
{
|
|
1238
|
-
role: 'assistant',
|
|
1239
|
-
content: [
|
|
1240
|
-
{ type: ContentTypes.TEXT, text: 'Response 1' },
|
|
1241
|
-
{ type: ContentTypes.TEXT, text: 'Response 2' },
|
|
1242
|
-
],
|
|
1243
|
-
},
|
|
1244
|
-
];
|
|
1245
|
-
|
|
1246
|
-
// targetAgentId provided but no contentMetadataMap - should include all content
|
|
1247
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1248
|
-
targetAgentId: 'agent_a',
|
|
1249
|
-
});
|
|
1250
|
-
|
|
1251
|
-
expect(result.messages).toHaveLength(1);
|
|
1252
|
-
const aiMessage = result.messages[0] as AIMessage;
|
|
1253
|
-
expect(Array.isArray(aiMessage.content)).toBe(true);
|
|
1254
|
-
expect(aiMessage.content as Array<{ text: string }>).toHaveLength(2);
|
|
1255
|
-
});
|
|
1256
|
-
|
|
1257
|
-
it('should filter content with groupId metadata (parallel execution)', () => {
|
|
1258
|
-
const payload: TPayload = [
|
|
1259
|
-
{ role: 'user', content: 'Analyze this' },
|
|
1260
|
-
{
|
|
1261
|
-
role: 'assistant',
|
|
1262
|
-
content: [
|
|
1263
|
-
{ type: ContentTypes.TEXT, text: 'Creative analysis' },
|
|
1264
|
-
{ type: ContentTypes.TEXT, text: 'Practical analysis' },
|
|
1265
|
-
],
|
|
1266
|
-
},
|
|
1267
|
-
];
|
|
1268
|
-
|
|
1269
|
-
const contentMetadataMap = new Map([
|
|
1270
|
-
[0, { agentId: 'creative_analyst', groupId: 1 }],
|
|
1271
|
-
[1, { agentId: 'practical_analyst', groupId: 1 }],
|
|
1272
|
-
]);
|
|
1273
|
-
|
|
1274
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1275
|
-
targetAgentId: 'creative_analyst',
|
|
1276
|
-
contentMetadataMap,
|
|
1277
|
-
});
|
|
1278
|
-
|
|
1279
|
-
expect(result.messages).toHaveLength(2);
|
|
1280
|
-
const aiMessage = result.messages[1] as AIMessage;
|
|
1281
|
-
expect(Array.isArray(aiMessage.content)).toBe(true);
|
|
1282
|
-
expect(aiMessage.content as Array<{ text: string }>).toHaveLength(1);
|
|
1283
|
-
expect((aiMessage.content as Array<{ text: string }>)[0].text).toBe(
|
|
1284
|
-
'Creative analysis'
|
|
1285
|
-
);
|
|
1286
|
-
});
|
|
1287
|
-
|
|
1288
|
-
it('should not affect non-assistant messages when filtering', () => {
|
|
1289
|
-
const payload: TPayload = [
|
|
1290
|
-
{ role: 'user', content: 'Hello from user' },
|
|
1291
|
-
{ role: 'system', content: 'System message' },
|
|
1292
|
-
{
|
|
1293
|
-
role: 'assistant',
|
|
1294
|
-
content: [{ type: ContentTypes.TEXT, text: 'From agent_a' }],
|
|
1295
|
-
},
|
|
1296
|
-
];
|
|
1297
|
-
|
|
1298
|
-
const contentMetadataMap = new Map([[0, { agentId: 'agent_a' }]]);
|
|
1299
|
-
|
|
1300
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1301
|
-
targetAgentId: 'agent_a',
|
|
1302
|
-
contentMetadataMap,
|
|
1303
|
-
});
|
|
1304
|
-
|
|
1305
|
-
// All three messages should be present
|
|
1306
|
-
expect(result.messages).toHaveLength(3);
|
|
1307
|
-
expect(result.messages[0]).toBeInstanceOf(HumanMessage);
|
|
1308
|
-
expect(result.messages[1]).toBeInstanceOf(SystemMessage);
|
|
1309
|
-
expect(result.messages[2]).toBeInstanceOf(AIMessage);
|
|
1310
|
-
});
|
|
1311
|
-
|
|
1312
|
-
it('should include content parts without metadata (unattributed) when filtering by targetAgentId', () => {
|
|
1313
|
-
const payload: TPayload = [
|
|
1314
|
-
{ role: 'user', content: 'Hello' },
|
|
1315
|
-
{
|
|
1316
|
-
role: 'assistant',
|
|
1317
|
-
content: [
|
|
1318
|
-
{ type: ContentTypes.TEXT, text: 'Unattributed content' },
|
|
1319
|
-
{ type: ContentTypes.TEXT, text: 'Response from agent_a' },
|
|
1320
|
-
{ type: ContentTypes.TEXT, text: 'Response from agent_b' },
|
|
1321
|
-
{ type: ContentTypes.TEXT, text: 'Another unattributed' },
|
|
1322
|
-
],
|
|
1323
|
-
},
|
|
1324
|
-
];
|
|
1325
|
-
|
|
1326
|
-
// Only indices 1 and 2 have metadata; 0 and 3 are unattributed
|
|
1327
|
-
const contentMetadataMap = new Map([
|
|
1328
|
-
[1, { agentId: 'agent_a' }],
|
|
1329
|
-
[2, { agentId: 'agent_b' }],
|
|
1330
|
-
]);
|
|
1331
|
-
|
|
1332
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1333
|
-
targetAgentId: 'agent_a',
|
|
1334
|
-
contentMetadataMap,
|
|
1335
|
-
});
|
|
1336
|
-
|
|
1337
|
-
// Should have user message + filtered assistant message
|
|
1338
|
-
expect(result.messages).toHaveLength(2);
|
|
1339
|
-
expect(result.messages[0]).toBeInstanceOf(HumanMessage);
|
|
1340
|
-
expect(result.messages[1]).toBeInstanceOf(AIMessage);
|
|
1341
|
-
|
|
1342
|
-
// The AIMessage should have: unattributed (index 0) + agent_a (index 1) + unattributed (index 3)
|
|
1343
|
-
const aiMessage = result.messages[1] as AIMessage;
|
|
1344
|
-
expect(Array.isArray(aiMessage.content)).toBe(true);
|
|
1345
|
-
expect(aiMessage.content as Array<{ text: string }>).toHaveLength(3);
|
|
1346
|
-
expect((aiMessage.content as Array<{ text: string }>)[0].text).toBe(
|
|
1347
|
-
'Unattributed content'
|
|
1348
|
-
);
|
|
1349
|
-
expect((aiMessage.content as Array<{ text: string }>)[1].text).toBe(
|
|
1350
|
-
'Response from agent_a'
|
|
1351
|
-
);
|
|
1352
|
-
expect((aiMessage.content as Array<{ text: string }>)[2].text).toBe(
|
|
1353
|
-
'Another unattributed'
|
|
1354
|
-
);
|
|
1355
|
-
});
|
|
1356
|
-
|
|
1357
|
-
it('should include all unattributed content even when no parts match targetAgentId', () => {
|
|
1358
|
-
const payload: TPayload = [
|
|
1359
|
-
{
|
|
1360
|
-
role: 'assistant',
|
|
1361
|
-
content: [
|
|
1362
|
-
{ type: ContentTypes.TEXT, text: 'Unattributed content 1' },
|
|
1363
|
-
{ type: ContentTypes.TEXT, text: 'Response from agent_b' },
|
|
1364
|
-
{ type: ContentTypes.TEXT, text: 'Unattributed content 2' },
|
|
1365
|
-
],
|
|
1366
|
-
},
|
|
1367
|
-
];
|
|
1368
|
-
|
|
1369
|
-
// Only index 1 has metadata (agent_b)
|
|
1370
|
-
const contentMetadataMap = new Map([[1, { agentId: 'agent_b' }]]);
|
|
1371
|
-
|
|
1372
|
-
const result = formatAgentMessages(payload, undefined, undefined, {
|
|
1373
|
-
targetAgentId: 'agent_a', // Looking for agent_a, but only agent_b content exists
|
|
1374
|
-
contentMetadataMap,
|
|
1375
|
-
});
|
|
1376
|
-
|
|
1377
|
-
// Should still have the message with unattributed content
|
|
1378
|
-
expect(result.messages).toHaveLength(1);
|
|
1379
|
-
const aiMessage = result.messages[0] as AIMessage;
|
|
1380
|
-
expect(Array.isArray(aiMessage.content)).toBe(true);
|
|
1381
|
-
// Should have the 2 unattributed parts (indices 0 and 2), but NOT agent_b's content
|
|
1382
|
-
expect(aiMessage.content as Array<{ text: string }>).toHaveLength(2);
|
|
1383
|
-
expect((aiMessage.content as Array<{ text: string }>)[0].text).toBe(
|
|
1384
|
-
'Unattributed content 1'
|
|
1385
|
-
);
|
|
1386
|
-
expect((aiMessage.content as Array<{ text: string }>)[1].text).toBe(
|
|
1387
|
-
'Unattributed content 2'
|
|
1388
|
-
);
|
|
1389
|
-
});
|
|
1390
|
-
});
|
|
1391
1144
|
});
|
package/src/stream.ts
CHANGED
|
@@ -583,6 +583,17 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
583
583
|
tool_call: newToolCall,
|
|
584
584
|
};
|
|
585
585
|
}
|
|
586
|
+
|
|
587
|
+
// Apply agentId (for MultiAgentGraph) and groupId (for parallel execution) to content parts
|
|
588
|
+
// - agentId present → MultiAgentGraph (show agent labels)
|
|
589
|
+
// - groupId present → parallel execution (render columns)
|
|
590
|
+
const meta = contentMetaMap.get(index);
|
|
591
|
+
if (meta?.agentId != null) {
|
|
592
|
+
(contentParts[index] as t.MessageContentComplex).agentId = meta.agentId;
|
|
593
|
+
}
|
|
594
|
+
if (meta?.groupId != null) {
|
|
595
|
+
(contentParts[index] as t.MessageContentComplex).groupId = meta.groupId;
|
|
596
|
+
}
|
|
586
597
|
};
|
|
587
598
|
|
|
588
599
|
const aggregateContent = ({
|
|
@@ -727,10 +738,5 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
727
738
|
}
|
|
728
739
|
};
|
|
729
740
|
|
|
730
|
-
return {
|
|
731
|
-
contentParts,
|
|
732
|
-
aggregateContent,
|
|
733
|
-
stepMap,
|
|
734
|
-
contentMetadataMap: contentMetaMap,
|
|
735
|
-
};
|
|
741
|
+
return { contentParts, aggregateContent, stepMap };
|
|
736
742
|
}
|
package/src/types/stream.ts
CHANGED
|
@@ -343,6 +343,10 @@ export type MessageContentComplex = (
|
|
|
343
343
|
})
|
|
344
344
|
) & {
|
|
345
345
|
tool_call_ids?: string[];
|
|
346
|
+
// Optional agentId for parallel execution attribution
|
|
347
|
+
agentId?: string;
|
|
348
|
+
// Optional groupId for parallel group attribution
|
|
349
|
+
groupId?: number;
|
|
346
350
|
};
|
|
347
351
|
|
|
348
352
|
export interface TMessage {
|
|
@@ -406,20 +410,8 @@ export type ContentAggregator = ({
|
|
|
406
410
|
result: ToolEndEvent;
|
|
407
411
|
};
|
|
408
412
|
}) => void;
|
|
409
|
-
/**
|
|
410
|
-
* Metadata for content parts in multi-agent runs.
|
|
411
|
-
* - agentId: present for all MultiAgentGraph runs (enables agent labels in UI)
|
|
412
|
-
* - groupId: present only for parallel execution (enables column rendering)
|
|
413
|
-
*/
|
|
414
|
-
export type ContentMetadata = {
|
|
415
|
-
agentId?: string;
|
|
416
|
-
groupId?: number;
|
|
417
|
-
};
|
|
418
|
-
|
|
419
413
|
export type ContentAggregatorResult = {
|
|
420
414
|
stepMap: Map<string, RunStep | undefined>;
|
|
421
415
|
contentParts: Array<MessageContentComplex | undefined>;
|
|
422
|
-
/** Map of content index to metadata (agentId, groupId). Only populated for MultiAgentGraph runs. */
|
|
423
|
-
contentMetadataMap: Map<number, ContentMetadata>;
|
|
424
416
|
aggregateContent: ContentAggregator;
|
|
425
417
|
};
|