@librechat/agents 3.2.67 → 3.3.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.
- package/dist/cjs/agents/AgentContext.cjs +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +14 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +4 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +2 -0
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +28 -0
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/messages/format.cjs +136 -4
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/prompts/activityLabel.cjs +101 -0
- package/dist/cjs/prompts/activityLabel.cjs.map +1 -0
- package/dist/cjs/run.cjs +162 -1
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +15 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +4 -1
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +3 -1
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +28 -1
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/format.mjs +136 -5
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/prompts/activityLabel.mjs +100 -0
- package/dist/esm/prompts/activityLabel.mjs.map +1 -0
- package/dist/esm/run.mjs +163 -2
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
- package/dist/types/common/enum.d.ts +3 -1
- package/dist/types/langfuseToolOutputTracing.d.ts +4 -0
- package/dist/types/llm/google/utils/common.d.ts +9 -0
- package/dist/types/messages/format.d.ts +22 -0
- package/dist/types/prompts/activityLabel.d.ts +31 -0
- package/dist/types/run.d.ts +14 -0
- package/dist/types/types/activityLabel.d.ts +53 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/stream.d.ts +2 -0
- package/package.json +1 -1
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +20 -0
- package/src/langfuseToolOutputTracing.ts +4 -1
- package/src/llm/google/index.ts +3 -0
- package/src/llm/google/utils/common.test.ts +57 -2
- package/src/llm/google/utils/common.ts +44 -0
- package/src/messages/foldToollessToolBlocks.test.ts +438 -0
- package/src/messages/format.ts +233 -5
- package/src/prompts/activityLabel.ts +177 -0
- package/src/run.ts +298 -2
- package/src/specs/activity-label-prompt.test.ts +128 -0
- package/src/specs/activity-label-trace-seed.test.ts +47 -0
- package/src/specs/bedrock-toolless.live.test.ts +123 -0
- package/src/types/activityLabel.ts +55 -0
- package/src/types/index.ts +1 -0
- package/src/types/stream.ts +2 -0
|
@@ -152,7 +152,7 @@ function getTextContent(part) {
|
|
|
152
152
|
}
|
|
153
153
|
function hasMeaningfulAssistantContent(part) {
|
|
154
154
|
if (part.type === "text") return getTextContent(part).trim().length > 0;
|
|
155
|
-
if (part.type === "tool_call" || part.type === "error" || part.type === "agent_update" || part.type === "summary") return false;
|
|
155
|
+
if (part.type === "tool_call" || part.type === "error" || part.type === "agent_update" || part.type === "summary" || part.type === "activity_label") return false;
|
|
156
156
|
if (part.type === "think" || part.type === "thinking" || part.type === "reasoning" || part.type === "reasoning_content" || part.type === "redacted_thinking") return extractReasoningContent(part).trim().length > 0;
|
|
157
157
|
return part.type != null && part.type !== "";
|
|
158
158
|
}
|
|
@@ -357,7 +357,7 @@ function formatAssistantMessage(message, options) {
|
|
|
357
357
|
* flushed above or intentionally dropped when not preserving). */
|
|
358
358
|
hasReasoning = false;
|
|
359
359
|
pendingReasoningContent = "";
|
|
360
|
-
} else if (part.type === "error" || part.type === "agent_update" || part.type === "summary") continue;
|
|
360
|
+
} else if (part.type === "error" || part.type === "agent_update" || part.type === "summary" || part.type === "activity_label") continue;
|
|
361
361
|
else {
|
|
362
362
|
if (part.type === "text" && !getTextContent(part).trim()) continue;
|
|
363
363
|
currentContent.push(part);
|
|
@@ -422,6 +422,12 @@ function labelAllAgentContent(contentParts, agentIdMap, agentNames) {
|
|
|
422
422
|
};
|
|
423
423
|
for (let i = 0; i < contentParts.length; i++) {
|
|
424
424
|
const part = contentParts[i];
|
|
425
|
+
/** UI-only progress headers are not agent content and must not disturb
|
|
426
|
+
* agent state: a label with no `agentIdMap` entry would otherwise read
|
|
427
|
+
* as an agent change and flush the buffer mid-agent, splitting one
|
|
428
|
+
* agent's contiguous content into two labeled blocks. Skipped before
|
|
429
|
+
* any state transition below (mirrors the transfer path). */
|
|
430
|
+
if (part.type === "activity_label") continue;
|
|
425
431
|
const agentId = agentIdMap[i];
|
|
426
432
|
if (agentId !== currentAgentId && currentAgentId !== void 0) flushAgentBuffer();
|
|
427
433
|
currentAgentId = agentId;
|
|
@@ -487,6 +493,12 @@ const labelContentByAgent = (contentParts, agentIdMap, agentNames, options) => {
|
|
|
487
493
|
};
|
|
488
494
|
for (let i = 0; i < contentParts.length; i++) {
|
|
489
495
|
const part = contentParts[i];
|
|
496
|
+
/** UI-only progress headers are not agent content and must not disturb
|
|
497
|
+
* agent state: a label with no `agentIdMap` entry would otherwise look
|
|
498
|
+
* like an agent change, flushing the buffer and resetting an open
|
|
499
|
+
* transfer capture so the transferred agent's following chunks lose
|
|
500
|
+
* their frame. Skipped before any state transition below. */
|
|
501
|
+
if (part.type === "activity_label") continue;
|
|
490
502
|
const agentId = agentIdMap[i];
|
|
491
503
|
const isTransferTool = (part.type === "tool_call" && part.tool_call?.name?.startsWith("lc_transfer_to_")) ?? false;
|
|
492
504
|
if (agentId !== currentAgentId && currentAgentId !== void 0) flushAgentBuffer();
|
|
@@ -902,6 +914,34 @@ function appendMessageContent(msg, role, textChunks, parts) {
|
|
|
902
914
|
textChunks.push(`${role}: [tool_use] ${String(block.name ?? "")} ${JSON.stringify(block.input ?? {})}`);
|
|
903
915
|
continue;
|
|
904
916
|
}
|
|
917
|
+
if (block.type === "tool_call") {
|
|
918
|
+
hasToolUseBlock = true;
|
|
919
|
+
const nested = block.tool_call;
|
|
920
|
+
const name = String(nested?.name ?? block.name ?? "");
|
|
921
|
+
const rawArgs = nested?.args ?? block.args ?? {};
|
|
922
|
+
const argsText = typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs);
|
|
923
|
+
textChunks.push(`${role}: [tool_use] ${name} ${argsText}`.trimEnd());
|
|
924
|
+
const output = nested?.output;
|
|
925
|
+
if (output != null && output !== "") textChunks.push(`Tool: ${String(output)}`);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
if (block.type === "tool_result") {
|
|
929
|
+
hasToolUseBlock = true;
|
|
930
|
+
const inner = block.content;
|
|
931
|
+
if (typeof inner === "string") {
|
|
932
|
+
if (inner) textChunks.push(`${role}: [tool_result] ${inner}`);
|
|
933
|
+
} else if (Array.isArray(inner)) for (const innerBlock of inner) if (typeof innerBlock === "string") {
|
|
934
|
+
if (innerBlock) textChunks.push(`${role}: [tool_result] ${innerBlock}`);
|
|
935
|
+
} else if (IMAGE_BLOCK_TYPES.has(innerBlock.type ?? "")) {
|
|
936
|
+
flushTextChunks(textChunks, parts);
|
|
937
|
+
parts.push({ ...innerBlock });
|
|
938
|
+
} else {
|
|
939
|
+
const innerText = innerBlock.text ?? innerBlock.input;
|
|
940
|
+
textChunks.push(`${role}: [tool_result] ${typeof innerText === "string" && innerText ? innerText : JSON.stringify(innerBlock)}`);
|
|
941
|
+
}
|
|
942
|
+
else if (inner != null) textChunks.push(`${role}: [tool_result] ${JSON.stringify(inner)}`);
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
905
945
|
const text = block.text ?? block.input;
|
|
906
946
|
if (typeof text === "string" && text) {
|
|
907
947
|
textChunks.push(`${role}: ${text}`);
|
|
@@ -914,8 +954,17 @@ function appendMessageContent(msg, role, textChunks, parts) {
|
|
|
914
954
|
function appendToolCalls(msg, role, textChunks) {
|
|
915
955
|
if (role !== "AI") return;
|
|
916
956
|
const aiMsg = msg;
|
|
917
|
-
if (
|
|
918
|
-
|
|
957
|
+
if (aiMsg.tool_calls && aiMsg.tool_calls.length > 0) {
|
|
958
|
+
for (const tc of aiMsg.tool_calls) textChunks.push(`AI: [tool_call] ${tc.name}(${JSON.stringify(tc.args)})`);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const rawToolCalls = aiMsg.additional_kwargs.tool_calls;
|
|
962
|
+
if (!Array.isArray(rawToolCalls)) return;
|
|
963
|
+
for (const tc of rawToolCalls) {
|
|
964
|
+
const fn = tc.function;
|
|
965
|
+
if (fn == null) continue;
|
|
966
|
+
textChunks.push(`AI: [tool_call] ${String(fn.name ?? "")}(${String(fn.arguments ?? "")})`);
|
|
967
|
+
}
|
|
919
968
|
}
|
|
920
969
|
/**
|
|
921
970
|
* Ensures compatibility when switching from a non-thinking agent to a thinking-enabled agent.
|
|
@@ -1005,6 +1054,88 @@ function ensureThinkingBlockInMessages(messages, _provider, config, runStartInde
|
|
|
1005
1054
|
}
|
|
1006
1055
|
return result;
|
|
1007
1056
|
}
|
|
1057
|
+
/** Whether a message carries tool content a tool-less agent cannot legally
|
|
1058
|
+
* send. Covers every representation a provider converter will serialize back
|
|
1059
|
+
* into a request: a ToolMessage, parsed `AIMessage.tool_calls`, raw
|
|
1060
|
+
* `additional_kwargs.tool_calls` (OpenAI keeps calls here when the parsed
|
|
1061
|
+
* array is empty), and `tool_use` / `tool_call` / `tool_result` content
|
|
1062
|
+
* blocks (`@langchain/aws` and the Anthropic converter map these to Converse
|
|
1063
|
+
* `toolUse` / `toolResult`). Missing the parent AI message is not just a
|
|
1064
|
+
* passthrough: folding its ToolMessage alone would leave an orphan
|
|
1065
|
+
* `assistant(tool_calls) -> user(...)` sequence. */
|
|
1066
|
+
function messageHasToolContent(msg) {
|
|
1067
|
+
if (isToolMessage(msg)) return true;
|
|
1068
|
+
const aiMsg = msg;
|
|
1069
|
+
if (aiMsg.tool_calls != null && aiMsg.tool_calls.length > 0) return true;
|
|
1070
|
+
const rawToolCalls = aiMsg.additional_kwargs.tool_calls;
|
|
1071
|
+
if (Array.isArray(rawToolCalls) && rawToolCalls.length > 0) return true;
|
|
1072
|
+
if (Array.isArray(msg.content)) {
|
|
1073
|
+
for (const block of msg.content) if (typeof block === "object" && (block.type === "tool_use" || block.type === "tool_call" || block.type === "tool_result")) return true;
|
|
1074
|
+
}
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
/** Whether a message carries a tool RESULT: a ToolMessage, or a message whose
|
|
1078
|
+
* content includes a `tool_result` block (the shape when a call/result pair is
|
|
1079
|
+
* split as `AIMessage(tool_call)` + `HumanMessage(tool_result)`). Such a result
|
|
1080
|
+
* belongs with the preceding tool call, so it is absorbed into the same fold
|
|
1081
|
+
* and labelled as tool output. */
|
|
1082
|
+
function isToolResultMessage(msg) {
|
|
1083
|
+
if (isToolMessage(msg)) return true;
|
|
1084
|
+
if (Array.isArray(msg.content)) return msg.content.some((block) => typeof block === "object" && block.type === "tool_result");
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Folds tool_use / tool_result content into plain text for an agent that binds
|
|
1089
|
+
* no tools.
|
|
1090
|
+
*
|
|
1091
|
+
* In a multi-agent graph, a tool-less destination still inherits the prior
|
|
1092
|
+
* agent's conversation history, which can contain toolUse/toolResult blocks.
|
|
1093
|
+
* Because it binds no tools, the model is invoked with no tool schema — and
|
|
1094
|
+
* Bedrock's Converse API rejects any request that carries toolUse/toolResult
|
|
1095
|
+
* blocks without a top-level toolConfig ("The toolConfig field must be defined
|
|
1096
|
+
* when using toolUse and toolResult content blocks"). Adding a dummy toolConfig
|
|
1097
|
+
* is not an option: AWS requires at least one tool, and it would expose a
|
|
1098
|
+
* capability the destination was intentionally denied.
|
|
1099
|
+
*
|
|
1100
|
+
* Each tool-call turn plus its trailing tool results (ToolMessages or
|
|
1101
|
+
* `tool_result` content blocks) is collapsed into a single `[Previous tool
|
|
1102
|
+
* interaction]` HumanMessage that preserves the tool name, arguments and result
|
|
1103
|
+
* as text (image blocks are kept as-is). Runs in a single pass: non-tool
|
|
1104
|
+
* messages pass through, `result` is allocated lazily on the first fold, and the
|
|
1105
|
+
* original array is returned unchanged when it holds no tool content (the common
|
|
1106
|
+
* fresh-tool-less-agent case).
|
|
1107
|
+
*/
|
|
1108
|
+
function foldToolBlocksForToollessAgent(messages, config) {
|
|
1109
|
+
let result = null;
|
|
1110
|
+
let foldedCount = 0;
|
|
1111
|
+
let i = 0;
|
|
1112
|
+
while (i < messages.length) {
|
|
1113
|
+
const msg = messages[i];
|
|
1114
|
+
if (!messageHasToolContent(msg)) {
|
|
1115
|
+
result?.push(msg);
|
|
1116
|
+
i++;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
/** First fold — copy the untouched prefix once, then append from here. */
|
|
1120
|
+
if (result === null) result = messages.slice(0, i);
|
|
1121
|
+
const parts = [];
|
|
1122
|
+
const textChunks = ["[Previous tool interaction]"];
|
|
1123
|
+
appendMessageContent(msg, isToolResultMessage(msg) ? "Tool" : "AI", textChunks, parts);
|
|
1124
|
+
foldedCount++;
|
|
1125
|
+
let j = i + 1;
|
|
1126
|
+
while (j < messages.length && isToolResultMessage(messages[j])) {
|
|
1127
|
+
appendMessageContent(messages[j], "Tool", textChunks, parts);
|
|
1128
|
+
foldedCount++;
|
|
1129
|
+
j++;
|
|
1130
|
+
}
|
|
1131
|
+
flushTextChunks(textChunks, parts);
|
|
1132
|
+
result.push(withMessageRole(new _langchain_core_messages.HumanMessage({ content: require_langchain.toLangChainContent(parts) }), "user"));
|
|
1133
|
+
i = j;
|
|
1134
|
+
}
|
|
1135
|
+
if (result === null) return messages;
|
|
1136
|
+
require_events.emitAgentLog(config, "warn", "format", `foldToolBlocksForToollessAgent: folded ${foldedCount} tool message(s) into text for a tool-less agent`);
|
|
1137
|
+
return result;
|
|
1138
|
+
}
|
|
1008
1139
|
/**
|
|
1009
1140
|
* Walks backwards from `currentIndex` through the message array to check
|
|
1010
1141
|
* whether an earlier AI message in the same "chain" (no HumanMessage boundary)
|
|
@@ -1032,6 +1163,7 @@ function chainHasThinkingBlock(messages, currentIndex) {
|
|
|
1032
1163
|
}
|
|
1033
1164
|
//#endregion
|
|
1034
1165
|
exports.ensureThinkingBlockInMessages = ensureThinkingBlockInMessages;
|
|
1166
|
+
exports.foldToolBlocksForToollessAgent = foldToolBlocksForToollessAgent;
|
|
1035
1167
|
exports.formatAgentMessages = formatAgentMessages;
|
|
1036
1168
|
exports.formatFromLangChain = formatFromLangChain;
|
|
1037
1169
|
exports.formatLangChainMessages = formatLangChainMessages;
|