@librechat/agents 3.6.9 → 3.6.10
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 +46 -39
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/agents/projection.cjs +2 -2
- package/dist/cjs/agents/projection.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +28 -18
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +87 -16
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +10 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +14 -0
- package/dist/cjs/messages/alternation.cjs +125 -20
- package/dist/cjs/messages/alternation.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +195 -18
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +488 -118
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/handoffCue.cjs +3 -2
- package/dist/cjs/messages/handoffCue.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -0
- package/dist/cjs/messages/injected.cjs +5 -2
- package/dist/cjs/messages/injected.cjs.map +1 -1
- package/dist/cjs/messages/provenance.cjs +411 -0
- package/dist/cjs/messages/provenance.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +18 -13
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/toolResultTypes.cjs +663 -0
- package/dist/cjs/messages/toolResultTypes.cjs.map +1 -0
- package/dist/cjs/run.cjs +7 -4
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +21 -4
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CallerCapabilities.cjs +48 -0
- package/dist/cjs/tools/CallerCapabilities.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs +34 -0
- package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +21 -4
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +25 -24
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +12 -4
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +13 -4
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +47 -11
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +2 -2
- package/dist/cjs/utils/toolContent.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +46 -39
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/agents/projection.mjs +2 -2
- package/dist/esm/agents/projection.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +30 -20
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +87 -16
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +10 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -3
- package/dist/esm/messages/alternation.mjs +125 -20
- package/dist/esm/messages/alternation.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +196 -19
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +488 -119
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/handoffCue.mjs +3 -2
- package/dist/esm/messages/handoffCue.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -0
- package/dist/esm/messages/injected.mjs +5 -2
- package/dist/esm/messages/injected.mjs.map +1 -1
- package/dist/esm/messages/provenance.mjs +401 -0
- package/dist/esm/messages/provenance.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +18 -13
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/toolResultTypes.mjs +657 -0
- package/dist/esm/messages/toolResultTypes.mjs.map +1 -0
- package/dist/esm/run.mjs +7 -4
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +21 -4
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CallerCapabilities.mjs +45 -0
- package/dist/esm/tools/CallerCapabilities.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticCallerPolicy.mjs +32 -0
- package/dist/esm/tools/ProgrammaticCallerPolicy.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +21 -4
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +25 -24
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +14 -6
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +15 -6
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +47 -12
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +2 -2
- package/dist/esm/utils/toolContent.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +8 -13
- package/dist/types/agents/projection.d.ts +3 -1
- package/dist/types/graphs/Graph.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +20 -0
- package/dist/types/messages/format.d.ts +5 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/messages/provenance.d.ts +85 -0
- package/dist/types/messages/toolResultTypes.d.ts +33 -0
- package/dist/types/tools/CallerCapabilities.d.ts +12 -0
- package/dist/types/tools/ProgrammaticCallerPolicy.d.ts +20 -0
- package/dist/types/tools/ToolNode.d.ts +4 -7
- package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +4 -0
- package/dist/types/tools/ptcTimeout.d.ts +8 -0
- package/dist/types/types/graph.d.ts +3 -1
- package/dist/types/types/tools.d.ts +13 -2
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +109 -67
- package/src/agents/projection.ts +9 -1
- package/src/graphs/Graph.ts +56 -23
- package/src/graphs/MultiAgentGraph.ts +175 -33
- package/src/llm/openai/index.ts +43 -0
- package/src/messages/alternation.ts +217 -34
- package/src/messages/core.ts +358 -38
- package/src/messages/format.ts +958 -137
- package/src/messages/handoffCue.ts +7 -4
- package/src/messages/index.ts +1 -0
- package/src/messages/injected.ts +9 -6
- package/src/messages/provenance.ts +897 -0
- package/src/messages/prune.ts +28 -10
- package/src/messages/toolResultTypes.ts +1174 -0
- package/src/run.ts +9 -4
- package/src/tools/BashProgrammaticToolCalling.ts +46 -12
- package/src/tools/CallerCapabilities.ts +78 -0
- package/src/tools/ProgrammaticCallerPolicy.ts +97 -0
- package/src/tools/ProgrammaticToolCalling.ts +41 -7
- package/src/tools/ToolNode.ts +70 -52
- package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +24 -26
- package/src/tools/local/LocalProgrammaticToolCalling.ts +28 -23
- package/src/tools/local/resolveLocalExecutionTools.ts +132 -13
- package/src/tools/ptcTimeout.ts +6 -0
- package/src/types/graph.ts +3 -0
- package/src/types/tools.ts +14 -3
- package/src/utils/toolContent.ts +2 -2
|
@@ -17,8 +17,17 @@ import {
|
|
|
17
17
|
import type { BaseMessage, AIMessageChunk } from '@langchain/core/messages';
|
|
18
18
|
import type { LangGraphRunnableConfig } from '@langchain/langgraph';
|
|
19
19
|
import type { ToolRuntime } from '@langchain/core/tools';
|
|
20
|
+
import type { ProviderMessageProvenancePart } from '@/messages/provenance';
|
|
20
21
|
import type { GraphFactoryDependencies } from '@/graphs/graphFactory';
|
|
21
22
|
import type * as t from '@/types';
|
|
23
|
+
import {
|
|
24
|
+
hasBijectiveProviderContentPartMapping,
|
|
25
|
+
inspectProviderMessageProvenance,
|
|
26
|
+
inspectProviderSourceMessageIds,
|
|
27
|
+
setInvalidProviderMessageProvenance,
|
|
28
|
+
setProviderMessageProvenance,
|
|
29
|
+
stampSyntheticProviderMessage,
|
|
30
|
+
} from '@/messages/provenance';
|
|
22
31
|
import { serializeToolContentBounded } from '@/utils/toolContent';
|
|
23
32
|
import { Constants, MULTI_AGENT_GRAPH_RUN_NAME } from '@/common';
|
|
24
33
|
import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
|
|
@@ -36,10 +45,12 @@ const HANDOFF_INSTRUCTIONS_KEY = 'handoff_instructions';
|
|
|
36
45
|
* message replayed out of the payload.
|
|
37
46
|
*/
|
|
38
47
|
function buildRoutingPrompt(content: string): HumanMessage {
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
return stampSyntheticProviderMessage(
|
|
49
|
+
new HumanMessage({
|
|
50
|
+
content,
|
|
51
|
+
additional_kwargs: { role: 'user', isMeta: true, source: 'routing' },
|
|
52
|
+
})
|
|
53
|
+
);
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
function getHandoffInstructions(
|
|
@@ -102,23 +113,137 @@ function isTransferToolName(name: unknown): boolean {
|
|
|
102
113
|
function filterTransferToolUseBlocks(
|
|
103
114
|
content: AIMessage['content'],
|
|
104
115
|
transferToolCallIds: ReadonlySet<string>
|
|
105
|
-
):
|
|
116
|
+
): {
|
|
117
|
+
content: AIMessage['content'];
|
|
118
|
+
retainedContentPartIndices?: readonly number[];
|
|
119
|
+
} {
|
|
106
120
|
if (!Array.isArray(content)) {
|
|
107
|
-
return content;
|
|
121
|
+
return { content };
|
|
108
122
|
}
|
|
109
|
-
|
|
123
|
+
const filteredContent: typeof content = [];
|
|
124
|
+
const retainedContentPartIndices: number[] = [];
|
|
125
|
+
for (let index = 0; index < content.length; index++) {
|
|
126
|
+
const block = content[index];
|
|
110
127
|
if (
|
|
111
|
-
typeof block
|
|
112
|
-
(block as { type?: string } | null)?.type
|
|
128
|
+
typeof block === 'object' &&
|
|
129
|
+
(block as { type?: string } | null)?.type === 'tool_use'
|
|
113
130
|
) {
|
|
114
|
-
|
|
131
|
+
const toolUse = block as { id?: string; name?: string };
|
|
132
|
+
if (
|
|
133
|
+
(toolUse.id != null && transferToolCallIds.has(toolUse.id)) ||
|
|
134
|
+
isTransferToolName(toolUse.name)
|
|
135
|
+
) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
filteredContent.push(block);
|
|
140
|
+
retainedContentPartIndices.push(index);
|
|
141
|
+
}
|
|
142
|
+
return { content: filteredContent, retainedContentPartIndices };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Rebuild a handoff AI message without losing authorship for retained bytes. */
|
|
146
|
+
function copyFilteredAIMessageProvenance(
|
|
147
|
+
source: AIMessage | AIMessageChunk,
|
|
148
|
+
target: AIMessage,
|
|
149
|
+
retainedContentPartIndices?: readonly number[]
|
|
150
|
+
): AIMessage {
|
|
151
|
+
const sourceMessageIdsState = inspectProviderSourceMessageIds(source);
|
|
152
|
+
const provenanceState = inspectProviderMessageProvenance(source);
|
|
153
|
+
if (
|
|
154
|
+
provenanceState.status === 'invalid' ||
|
|
155
|
+
sourceMessageIdsState.status === 'invalid'
|
|
156
|
+
) {
|
|
157
|
+
setInvalidProviderMessageProvenance(target);
|
|
158
|
+
return target;
|
|
159
|
+
}
|
|
160
|
+
const sourceMessageIds =
|
|
161
|
+
sourceMessageIdsState.status === 'valid'
|
|
162
|
+
? sourceMessageIdsState.sourceMessageIds
|
|
163
|
+
: [];
|
|
164
|
+
if (provenanceState.status === 'absent') {
|
|
165
|
+
if (sourceMessageIds.length > 0) {
|
|
166
|
+
setProviderMessageProvenance(
|
|
167
|
+
target,
|
|
168
|
+
sourceMessageIds.map((sourceMessageId) => ({
|
|
169
|
+
attribution: 'model',
|
|
170
|
+
sourceMessageId,
|
|
171
|
+
}))
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return target;
|
|
175
|
+
}
|
|
176
|
+
const provenance = provenanceState.provenance;
|
|
177
|
+
const typedSourceMessageIds = new Set<string>();
|
|
178
|
+
for (const part of provenance.parts) {
|
|
179
|
+
if (part.sourceMessageId != null) {
|
|
180
|
+
typedSourceMessageIds.add(part.sourceMessageId);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const legacyParts: ProviderMessageProvenancePart[] = [];
|
|
184
|
+
for (const sourceMessageId of sourceMessageIds) {
|
|
185
|
+
if (!typedSourceMessageIds.has(sourceMessageId)) {
|
|
186
|
+
legacyParts.push({ attribution: 'model', sourceMessageId });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const setFilteredProvenance = (
|
|
190
|
+
parts: readonly ProviderMessageProvenancePart[]
|
|
191
|
+
): void => {
|
|
192
|
+
if (legacyParts.length === 0) {
|
|
193
|
+
setProviderMessageProvenance(target, parts);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const combinedParts = parts.slice();
|
|
197
|
+
for (const part of legacyParts) {
|
|
198
|
+
combinedParts.push(part);
|
|
115
199
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
setProviderMessageProvenance(target, combinedParts);
|
|
201
|
+
};
|
|
202
|
+
if (
|
|
203
|
+
retainedContentPartIndices == null ||
|
|
204
|
+
!Array.isArray(source.content) ||
|
|
205
|
+
retainedContentPartIndices.length === source.content.length
|
|
206
|
+
) {
|
|
207
|
+
setFilteredProvenance(provenance.parts);
|
|
208
|
+
return target;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (
|
|
212
|
+
!hasBijectiveProviderContentPartMapping(
|
|
213
|
+
provenance.parts,
|
|
214
|
+
source.content.length
|
|
215
|
+
)
|
|
216
|
+
) {
|
|
217
|
+
/** An older or aggregate envelope cannot be mapped to current blocks.
|
|
218
|
+
* Preserve its conservative attribution instead of dropping tool lineage. */
|
|
219
|
+
setFilteredProvenance(provenance.parts);
|
|
220
|
+
return target;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const retainedParts: ProviderMessageProvenancePart[] = [];
|
|
224
|
+
const retainedContentPartIndexSet = new Set(retainedContentPartIndices);
|
|
225
|
+
for (const part of provenance.parts) {
|
|
226
|
+
if (part.sourceContentPartIndices == null) {
|
|
227
|
+
retainedParts.push(part);
|
|
228
|
+
continue;
|
|
119
229
|
}
|
|
120
|
-
|
|
121
|
-
|
|
230
|
+
const retainedSourceContentPartIndices: number[] = [];
|
|
231
|
+
for (const sourceContentPartIndex of part.sourceContentPartIndices) {
|
|
232
|
+
if (retainedContentPartIndexSet.has(sourceContentPartIndex)) {
|
|
233
|
+
retainedSourceContentPartIndices.push(sourceContentPartIndex);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (retainedSourceContentPartIndices.length > 0) {
|
|
237
|
+
retainedParts.push({
|
|
238
|
+
...part,
|
|
239
|
+
sourceContentPartIndices: retainedSourceContentPartIndices,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
setFilteredProvenance(
|
|
244
|
+
retainedParts.length > 0 ? retainedParts : [{ attribution: 'model' }]
|
|
245
|
+
);
|
|
246
|
+
return target;
|
|
122
247
|
}
|
|
123
248
|
|
|
124
249
|
function isValidHandoffGroupId(value: unknown): value is number {
|
|
@@ -502,15 +627,22 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
502
627
|
const handoffTools: t.GenericTool[] = [];
|
|
503
628
|
const sourceAgentName = agentContext.name ?? agentId;
|
|
504
629
|
for (const edge of edges) {
|
|
505
|
-
|
|
506
|
-
|
|
630
|
+
const edgeTools = this.createHandoffToolsForEdge(
|
|
631
|
+
edge,
|
|
632
|
+
agentId,
|
|
633
|
+
sourceAgentName
|
|
507
634
|
);
|
|
635
|
+
for (const edgeTool of edgeTools) {
|
|
636
|
+
handoffTools.push(edgeTool);
|
|
637
|
+
}
|
|
508
638
|
}
|
|
509
639
|
|
|
510
640
|
if (!agentContext.graphTools) {
|
|
511
641
|
agentContext.graphTools = [];
|
|
512
642
|
}
|
|
513
|
-
|
|
643
|
+
for (const handoffTool of handoffTools) {
|
|
644
|
+
agentContext.graphTools.push(handoffTool);
|
|
645
|
+
}
|
|
514
646
|
}
|
|
515
647
|
}
|
|
516
648
|
|
|
@@ -701,11 +833,14 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
701
833
|
* Multiple tool calls - create filtered AIMessage with ONLY this call.
|
|
702
834
|
* This ensures valid message structure for parallel handoffs.
|
|
703
835
|
*/
|
|
704
|
-
const filteredAiMsg =
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
836
|
+
const filteredAiMsg = copyFilteredAIMessageProvenance(
|
|
837
|
+
originalAiMsg,
|
|
838
|
+
new AIMessage({
|
|
839
|
+
content: originalAiMsg.content,
|
|
840
|
+
tool_calls: [thisToolCall],
|
|
841
|
+
id: originalAiMsg.id,
|
|
842
|
+
})
|
|
843
|
+
);
|
|
709
844
|
|
|
710
845
|
filteredMessages = [
|
|
711
846
|
...messages.slice(0, aiMessageIndex),
|
|
@@ -981,14 +1116,19 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
981
1116
|
* result in THIS recipient's state, so its id is never
|
|
982
1117
|
* collected, but its name still marks it.
|
|
983
1118
|
*/
|
|
984
|
-
const
|
|
985
|
-
content
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1119
|
+
const filteredContent = filterTransferToolUseBlocks(
|
|
1120
|
+
aiMsg.content,
|
|
1121
|
+
transferToolCallIds
|
|
1122
|
+
);
|
|
1123
|
+
const filteredAiMsg = copyFilteredAIMessageProvenance(
|
|
1124
|
+
aiMsg,
|
|
1125
|
+
new AIMessage({
|
|
1126
|
+
content: filteredContent.content,
|
|
1127
|
+
tool_calls: remainingToolCalls,
|
|
1128
|
+
id: aiMsg.id,
|
|
1129
|
+
}),
|
|
1130
|
+
filteredContent.retainedContentPartIndices
|
|
1131
|
+
);
|
|
992
1132
|
filteredMessages.push(filteredAiMsg);
|
|
993
1133
|
}
|
|
994
1134
|
/** If no remaining content or tool calls, skip this message entirely */
|
|
@@ -1157,8 +1297,10 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1157
1297
|
if (lastMsg != null && lastMsg.getType() === 'tool') {
|
|
1158
1298
|
messagesForAgent = [
|
|
1159
1299
|
...filteredMessages,
|
|
1160
|
-
|
|
1161
|
-
|
|
1300
|
+
stampSyntheticProviderMessage(
|
|
1301
|
+
new AIMessage(
|
|
1302
|
+
`[Processed tool result and transferring to ${agentId}]`
|
|
1303
|
+
)
|
|
1162
1304
|
),
|
|
1163
1305
|
buildRoutingPrompt(instructions),
|
|
1164
1306
|
];
|
package/src/llm/openai/index.ts
CHANGED
|
@@ -919,6 +919,48 @@ function attachResponsesReplayPosition(
|
|
|
919
919
|
chunk.message.lc_kwargs.additional_kwargs = additionalKwargs;
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
+
type ResponsesAnnotationsBoundaryEvent = {
|
|
923
|
+
type: OpenAIClient.Responses.ResponseStreamEvent['type'];
|
|
924
|
+
response?: {
|
|
925
|
+
output?: Array<{
|
|
926
|
+
type: string;
|
|
927
|
+
content?: Array<{
|
|
928
|
+
type: string;
|
|
929
|
+
annotations?: object[] | null;
|
|
930
|
+
}>;
|
|
931
|
+
}>;
|
|
932
|
+
};
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* The Responses API spec declares `annotations` required on `output_text`
|
|
937
|
+
* content parts, but some OpenAI-compatible gateways omit the field on the
|
|
938
|
+
* terminal `response.completed`/`response.incomplete` events. LangChain's
|
|
939
|
+
* converter calls `part.annotations.map(...)` unconditionally, so a missing
|
|
940
|
+
* field crashes the whole stream. Default it to `[]` before conversion.
|
|
941
|
+
*/
|
|
942
|
+
export function ensureResponsesOutputAnnotations(
|
|
943
|
+
event: ResponsesAnnotationsBoundaryEvent
|
|
944
|
+
): void {
|
|
945
|
+
if (event.type !== 'response.completed' && event.type !== 'response.incomplete') {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const output = event.response?.output;
|
|
949
|
+
if (!Array.isArray(output)) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
for (const item of output) {
|
|
953
|
+
if (item.type !== 'message' || !Array.isArray(item.content)) {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
for (const part of item.content) {
|
|
957
|
+
if (part.type === 'output_text' && !Array.isArray(part.annotations)) {
|
|
958
|
+
part.annotations = [];
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
922
964
|
function getResponsesStreamError(
|
|
923
965
|
event: OpenAIClient.Responses.ResponseStreamEvent
|
|
924
966
|
): Error | undefined {
|
|
@@ -960,6 +1002,7 @@ async function* convertLibreChatResponsesStream(
|
|
|
960
1002
|
if (streamError != null) {
|
|
961
1003
|
throw streamError;
|
|
962
1004
|
}
|
|
1005
|
+
ensureResponsesOutputAnnotations(event);
|
|
963
1006
|
const convertedChunk =
|
|
964
1007
|
convertResponsesDeltaToChatGenerationChunk(event) ??
|
|
965
1008
|
convertDroppedResponsesReplayOutput(event);
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
// src/messages/alternation.ts
|
|
2
2
|
import { HumanMessage } from '@langchain/core/messages';
|
|
3
3
|
import type { BaseMessage, MessageContent } from '@langchain/core/messages';
|
|
4
|
+
import type { ProviderMessageProvenancePart } from './provenance';
|
|
5
|
+
import type { ProviderToolCallIndex } from './toolResultTypes';
|
|
6
|
+
import {
|
|
7
|
+
appendProviderToolCallDescriptor,
|
|
8
|
+
consumeProviderToolResultPair,
|
|
9
|
+
getBoundedProviderPairingArrayProperty,
|
|
10
|
+
getProviderAIMessageToolCallDescriptor,
|
|
11
|
+
getProviderToolCallPartDescriptor,
|
|
12
|
+
getProviderToolResultPartDescriptor,
|
|
13
|
+
} from './toolResultTypes';
|
|
14
|
+
import {
|
|
15
|
+
inspectProviderMessageProvenance,
|
|
16
|
+
inspectProviderSourceMessageIds,
|
|
17
|
+
setInvalidProviderMessageProvenance,
|
|
18
|
+
setProviderMessageProvenance,
|
|
19
|
+
} from './provenance';
|
|
4
20
|
import { Providers } from '@/common';
|
|
5
21
|
|
|
6
22
|
/**
|
|
@@ -19,22 +35,57 @@ export const strictAlternationProviders: ReadonlySet<Providers> = new Set([
|
|
|
19
35
|
Providers.MISTRALAI,
|
|
20
36
|
]);
|
|
21
37
|
|
|
22
|
-
const TOOL_RESULT_TYPES = new Set(['tool_result', 'toolResult']);
|
|
23
|
-
|
|
24
38
|
/**
|
|
25
39
|
* True when every block is a tool result. Both vendored converters already
|
|
26
40
|
* merge adjacent runs of these, and folding one into a text turn would break
|
|
27
41
|
* the tool pairing they depend on — so they are left alone here.
|
|
28
42
|
*/
|
|
29
|
-
function
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
function collectProviderToolCalls(message: BaseMessage): ProviderToolCallIndex {
|
|
44
|
+
const calls: ProviderToolCallIndex = new Map();
|
|
45
|
+
const content = getBoundedProviderPairingArrayProperty(message, 'content');
|
|
46
|
+
if (content != null) {
|
|
47
|
+
for (let index = 0; index < content.length; index++) {
|
|
48
|
+
const descriptor = getProviderToolCallPartDescriptor(content[index]);
|
|
49
|
+
if (descriptor != null) {
|
|
50
|
+
appendProviderToolCallDescriptor(calls, descriptor);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
33
53
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
const toolCalls = getBoundedProviderPairingArrayProperty(
|
|
55
|
+
message,
|
|
56
|
+
'tool_calls'
|
|
37
57
|
);
|
|
58
|
+
if (toolCalls != null) {
|
|
59
|
+
for (let index = 0; index < toolCalls.length; index++) {
|
|
60
|
+
const descriptor = getProviderAIMessageToolCallDescriptor(
|
|
61
|
+
toolCalls[index]
|
|
62
|
+
);
|
|
63
|
+
if (descriptor != null) {
|
|
64
|
+
appendProviderToolCallDescriptor(calls, descriptor);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return calls;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isToolResultMessage(
|
|
72
|
+
message: BaseMessage,
|
|
73
|
+
pairedToolCalls: ProviderToolCallIndex
|
|
74
|
+
): boolean {
|
|
75
|
+
const content = getBoundedProviderPairingArrayProperty(message, 'content');
|
|
76
|
+
if (content == null || content.length === 0) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
for (let index = 0; index < content.length; index++) {
|
|
80
|
+
const descriptor = getProviderToolResultPartDescriptor(content[index]);
|
|
81
|
+
if (
|
|
82
|
+
descriptor?.allowHumanMessagePairing !== true ||
|
|
83
|
+
!consumeProviderToolResultPair(descriptor, pairedToolCalls)
|
|
84
|
+
) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
38
89
|
}
|
|
39
90
|
|
|
40
91
|
function toBlocks(content: MessageContent): Exclude<MessageContent, string> {
|
|
@@ -44,14 +95,97 @@ function toBlocks(content: MessageContent): Exclude<MessageContent, string> {
|
|
|
44
95
|
return content;
|
|
45
96
|
}
|
|
46
97
|
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
):
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
function joinStringContents(
|
|
99
|
+
messages: readonly BaseMessage[],
|
|
100
|
+
endIndex = messages.length
|
|
101
|
+
): string {
|
|
102
|
+
const contents: string[] = [];
|
|
103
|
+
for (let index = 0; index < endIndex; index++) {
|
|
104
|
+
const content = messages[index].content as string;
|
|
105
|
+
if (contents.length === 0 && content === '') {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
contents.push(content);
|
|
109
|
+
}
|
|
110
|
+
return contents.join('\n\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function joinContents(messages: readonly BaseMessage[]): MessageContent {
|
|
114
|
+
const firstArrayIndex = messages.findIndex((message) =>
|
|
115
|
+
Array.isArray(message.content)
|
|
116
|
+
);
|
|
117
|
+
if (firstArrayIndex === -1) {
|
|
118
|
+
return joinStringContents(messages);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const prefix = joinStringContents(messages, firstArrayIndex);
|
|
122
|
+
const blocks: Exclude<MessageContent, string> = [];
|
|
123
|
+
const appendBlocks = (content: MessageContent): void => {
|
|
124
|
+
const contentBlocks = toBlocks(content);
|
|
125
|
+
for (let index = 0; index < contentBlocks.length; index++) {
|
|
126
|
+
blocks.push(contentBlocks[index]);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
appendBlocks(prefix);
|
|
130
|
+
for (let index = firstArrayIndex; index < messages.length; index++) {
|
|
131
|
+
appendBlocks(messages[index].content);
|
|
53
132
|
}
|
|
54
|
-
return
|
|
133
|
+
return blocks;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getHumanMessageProvenanceParts(
|
|
137
|
+
message: BaseMessage
|
|
138
|
+
): ProviderMessageProvenancePart[] | null {
|
|
139
|
+
const provenanceState = inspectProviderMessageProvenance(message);
|
|
140
|
+
const sourceMessageIdsState = inspectProviderSourceMessageIds(message);
|
|
141
|
+
if (
|
|
142
|
+
provenanceState.status === 'invalid' ||
|
|
143
|
+
sourceMessageIdsState.status === 'invalid'
|
|
144
|
+
) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const sourceMessageIds =
|
|
148
|
+
sourceMessageIdsState.status === 'valid'
|
|
149
|
+
? sourceMessageIdsState.sourceMessageIds
|
|
150
|
+
: [];
|
|
151
|
+
if (provenanceState.status === 'valid') {
|
|
152
|
+
const parts = [...provenanceState.provenance.parts];
|
|
153
|
+
const representedSourceIds = new Set<string>();
|
|
154
|
+
for (const part of parts) {
|
|
155
|
+
if (part.sourceMessageId != null) {
|
|
156
|
+
representedSourceIds.add(part.sourceMessageId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const missingSourceIds = sourceMessageIds.filter(
|
|
160
|
+
(sourceMessageId) => !representedSourceIds.has(sourceMessageId)
|
|
161
|
+
);
|
|
162
|
+
if (
|
|
163
|
+
parts.length === 1 &&
|
|
164
|
+
parts[0].sourceMessageId == null &&
|
|
165
|
+
missingSourceIds.length === 1
|
|
166
|
+
) {
|
|
167
|
+
return missingSourceIds.map((sourceMessageId) => ({
|
|
168
|
+
...parts[0],
|
|
169
|
+
sourceMessageId,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
for (const sourceMessageId of missingSourceIds) {
|
|
173
|
+
parts.push({ attribution: 'user', sourceMessageId });
|
|
174
|
+
}
|
|
175
|
+
return parts;
|
|
176
|
+
}
|
|
177
|
+
return sourceMessageIds.length > 0
|
|
178
|
+
? sourceMessageIds.map((sourceMessageId) => ({
|
|
179
|
+
attribution: 'user' as const,
|
|
180
|
+
sourceMessageId,
|
|
181
|
+
}))
|
|
182
|
+
: [{ attribution: 'user' }];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function hasProviderVisibleContent(message: BaseMessage): boolean {
|
|
186
|
+
return typeof message.content === 'string'
|
|
187
|
+
? message.content.length > 0
|
|
188
|
+
: message.content.length > 0;
|
|
55
189
|
}
|
|
56
190
|
|
|
57
191
|
/**
|
|
@@ -66,25 +200,66 @@ function joinContent(
|
|
|
66
200
|
export function coalesceAdjacentUserTurns(
|
|
67
201
|
messages: BaseMessage[]
|
|
68
202
|
): BaseMessage[] {
|
|
69
|
-
|
|
70
|
-
let
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
203
|
+
let result: BaseMessage[] | null = null;
|
|
204
|
+
let pairedToolCalls: ProviderToolCallIndex = new Map();
|
|
205
|
+
let index = 0;
|
|
206
|
+
while (index < messages.length) {
|
|
207
|
+
const first = messages[index];
|
|
208
|
+
if (first.getType() === 'ai') {
|
|
209
|
+
pairedToolCalls = collectProviderToolCalls(first);
|
|
210
|
+
result?.push(first);
|
|
211
|
+
index++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (first.getType() !== 'human') {
|
|
215
|
+
result?.push(first);
|
|
216
|
+
index++;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (isToolResultMessage(first, pairedToolCalls)) {
|
|
220
|
+
result?.push(first);
|
|
221
|
+
index++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
79
224
|
|
|
80
|
-
|
|
81
|
-
|
|
225
|
+
let endIndex = index + 1;
|
|
226
|
+
while (
|
|
227
|
+
endIndex < messages.length &&
|
|
228
|
+
messages[endIndex].getType() === 'human' &&
|
|
229
|
+
!isToolResultMessage(messages[endIndex], pairedToolCalls)
|
|
230
|
+
) {
|
|
231
|
+
endIndex++;
|
|
232
|
+
}
|
|
233
|
+
if (endIndex === index + 1) {
|
|
234
|
+
result?.push(first);
|
|
235
|
+
pairedToolCalls = new Map();
|
|
236
|
+
index = endIndex;
|
|
82
237
|
continue;
|
|
83
238
|
}
|
|
84
|
-
|
|
239
|
+
result ??= messages.slice(0, index);
|
|
240
|
+
const run = messages.slice(index, endIndex);
|
|
241
|
+
const last = run[run.length - 1];
|
|
242
|
+
const provenanceParts: ProviderMessageProvenancePart[] = [];
|
|
243
|
+
let hasInvalidProvenance = false;
|
|
244
|
+
for (const message of run) {
|
|
245
|
+
if (!hasProviderVisibleContent(message)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const messageProvenanceParts = getHumanMessageProvenanceParts(message);
|
|
249
|
+
if (messageProvenanceParts == null) {
|
|
250
|
+
hasInvalidProvenance = true;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
for (const part of messageProvenanceParts) {
|
|
254
|
+
provenanceParts.push(part);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (provenanceParts.length === 0) {
|
|
258
|
+
provenanceParts.push({ attribution: 'user' });
|
|
259
|
+
}
|
|
85
260
|
|
|
86
|
-
|
|
87
|
-
content:
|
|
261
|
+
const mergedMessage = new HumanMessage({
|
|
262
|
+
content: joinContents(run),
|
|
88
263
|
/**
|
|
89
264
|
* The LATER turn's kwargs, deliberately. The one provider-path consumer
|
|
90
265
|
* of these flags is the prompt-cache tail anchor, and it reasons
|
|
@@ -96,9 +271,17 @@ export function coalesceAdjacentUserTurns(
|
|
|
96
271
|
* skill body must not pin the cache to the volatile body. The first
|
|
97
272
|
* turn's id is kept so origin tracking can re-attach by key.
|
|
98
273
|
*/
|
|
99
|
-
additional_kwargs:
|
|
100
|
-
...(
|
|
274
|
+
additional_kwargs: { ...last.additional_kwargs },
|
|
275
|
+
...(first.id != null && { id: first.id }),
|
|
101
276
|
});
|
|
277
|
+
if (hasInvalidProvenance) {
|
|
278
|
+
setInvalidProviderMessageProvenance(mergedMessage);
|
|
279
|
+
} else {
|
|
280
|
+
setProviderMessageProvenance(mergedMessage, provenanceParts);
|
|
281
|
+
}
|
|
282
|
+
result.push(mergedMessage);
|
|
283
|
+
pairedToolCalls = new Map();
|
|
284
|
+
index = endIndex;
|
|
102
285
|
}
|
|
103
286
|
/**
|
|
104
287
|
* Identity on the no-merge path. The pass runs twice for a primary
|
|
@@ -108,5 +291,5 @@ export function coalesceAdjacentUserTurns(
|
|
|
108
291
|
* the SAME array rather than reallocating a context-sized copy, and
|
|
109
292
|
* callers can cheaply detect "nothing changed" by identity.
|
|
110
293
|
*/
|
|
111
|
-
return
|
|
294
|
+
return result ?? messages;
|
|
112
295
|
}
|