@librechat/agents 3.3.10 → 3.3.12
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/graphs/Graph.cjs +248 -27
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +228 -16
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +13 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +160 -43
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +190 -13
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/streamLimits.cjs +723 -0
- package/dist/cjs/llm/streamLimits.cjs.map +1 -0
- package/dist/cjs/main.cjs +12 -3
- package/dist/cjs/messages/core.cjs +592 -27
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/run.cjs +7 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +4 -1
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +105 -14
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +157 -37
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +4 -3
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -3
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +77 -4
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +3 -2
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +7 -1
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-search.cjs +3 -1
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +7 -1
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +7 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-search.cjs +3 -1
- package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +26 -8
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +30 -10
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs +7 -1
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +7 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs +3 -1
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +17 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +54 -3
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +2 -1
- package/dist/cjs/utils/misc.cjs +12 -0
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/cjs/utils/proxy.cjs +63 -0
- package/dist/cjs/utils/proxy.cjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +247 -26
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +228 -16
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +13 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +160 -43
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +192 -15
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/streamLimits.mjs +704 -0
- package/dist/esm/llm/streamLimits.mjs.map +1 -0
- package/dist/esm/main.mjs +8 -6
- package/dist/esm/messages/core.mjs +592 -28
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/run.mjs +7 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -1
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +105 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +157 -37
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -2
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +4 -3
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -3
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +77 -4
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +3 -2
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +7 -1
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/crw-search.mjs +3 -1
- package/dist/esm/tools/search/crw-search.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +7 -1
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +7 -1
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-search.mjs +3 -1
- package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +26 -8
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +30 -10
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs +7 -1
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +7 -1
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs +3 -1
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +17 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +54 -3
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +2 -1
- package/dist/esm/utils/misc.mjs +12 -1
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/esm/utils/proxy.mjs +62 -0
- package/dist/esm/utils/proxy.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +53 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +1 -0
- package/dist/types/llm/invoke.d.ts +26 -5
- package/dist/types/llm/openai/index.d.ts +3 -0
- package/dist/types/llm/streamLimits.d.ts +314 -0
- package/dist/types/messages/core.d.ts +11 -6
- package/dist/types/run.d.ts +1 -0
- package/dist/types/summarization/node.d.ts +27 -2
- package/dist/types/tools/BashExecutor.d.ts +2 -2
- package/dist/types/tools/CodeExecutor.d.ts +3 -3
- package/dist/types/tools/ToolNode.d.ts +11 -1
- package/dist/types/tools/search/crw-scraper.d.ts +2 -0
- package/dist/types/tools/search/firecrawl.d.ts +2 -0
- package/dist/types/tools/search/keenable-scraper.d.ts +2 -0
- package/dist/types/tools/search/rerankers.d.ts +9 -5
- package/dist/types/tools/search/serper-scraper.d.ts +2 -0
- package/dist/types/tools/search/tavily-scraper.d.ts +2 -0
- package/dist/types/tools/search/types.d.ts +28 -33
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +44 -0
- package/dist/types/types/graph.d.ts +7 -1
- package/dist/types/types/run.d.ts +46 -1
- package/dist/types/types/tools.d.ts +21 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/misc.d.ts +7 -0
- package/dist/types/utils/proxy.d.ts +31 -0
- package/package.json +2 -1
- package/src/__tests__/stream.eagerArgsDivergence.test.ts +158 -0
- package/src/__tests__/stream.eagerEventExecution.test.ts +1 -0
- package/src/__tests__/stream.streamLimits.test.ts +1982 -0
- package/src/graphs/Graph.ts +302 -27
- package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +234 -0
- package/src/index.ts +11 -0
- package/src/langfuseToolOutputTracing.ts +410 -14
- package/src/llm/bedrock/index.ts +22 -2
- package/src/llm/custom-chat-models.smoke.test.ts +747 -0
- package/src/llm/invoke.streamLimits.test.ts +142 -0
- package/src/llm/invoke.test.ts +187 -1
- package/src/llm/invoke.ts +231 -43
- package/src/llm/openai/cacheWriteTokens.test.ts +112 -0
- package/src/llm/openai/index.ts +348 -31
- package/src/llm/openai/llm.spec.ts +107 -6
- package/src/llm/streamLimits.test.ts +450 -0
- package/src/llm/streamLimits.ts +1158 -0
- package/src/messages/core.ts +1290 -42
- package/src/messages/formatAgentMessages.test.ts +2623 -0
- package/src/run.ts +4 -0
- package/src/session/AgentSession.ts +5 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +887 -0
- package/src/specs/preemptSeal.test.ts +374 -5
- package/src/stream.ts +141 -6
- package/src/summarization/__tests__/node.test.ts +269 -0
- package/src/summarization/chunkHandler.test.ts +196 -0
- package/src/summarization/node.ts +203 -6
- package/src/tools/BashExecutor.ts +4 -3
- package/src/tools/CodeExecutor.ts +5 -4
- package/src/tools/ProgrammaticToolCalling.ts +7 -5
- package/src/tools/ToolNode.ts +109 -6
- package/src/tools/ToolSearch.ts +4 -3
- package/src/tools/__tests__/BashExecutor.test.ts +2 -2
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +2 -4
- package/src/tools/__tests__/SubagentExecutor.test.ts +126 -0
- package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +389 -0
- package/src/tools/__tests__/ToolNode.streamLimits.test.ts +69 -0
- package/src/tools/search/crw-scraper.ts +6 -0
- package/src/tools/search/crw-search.ts +6 -1
- package/src/tools/search/firecrawl.ts +6 -0
- package/src/tools/search/http-agent.test.ts +133 -0
- package/src/tools/search/keenable-scraper.ts +6 -0
- package/src/tools/search/keenable-search.ts +6 -1
- package/src/tools/search/rerankers.ts +36 -10
- package/src/tools/search/search.ts +29 -11
- package/src/tools/search/serper-scraper.ts +6 -0
- package/src/tools/search/tavily-scraper.ts +6 -0
- package/src/tools/search/tavily-search.ts +2 -0
- package/src/tools/search/tool.ts +16 -0
- package/src/tools/search/types.ts +31 -33
- package/src/tools/subagent/SubagentExecutor.ts +96 -3
- package/src/types/graph.ts +7 -0
- package/src/types/run.ts +49 -1
- package/src/types/tools.ts +21 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +19 -0
- package/src/utils/proxy.test.ts +176 -0
- package/src/utils/proxy.ts +93 -0
|
@@ -25,6 +25,8 @@ import { resolveToolOutputTracingConfigForSpan } from '@/langfuseRuntimeScope';
|
|
|
25
25
|
export { LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT, resolveLangfuseConfig };
|
|
26
26
|
|
|
27
27
|
const LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';
|
|
28
|
+
const SERVER_TOOL_RESULT_PREFIX = '{"serverToolResult":';
|
|
29
|
+
const SERVER_TOOL_RESULT_REPLAY_MARKER_KEY = 'librechatResponsesReplay';
|
|
28
30
|
|
|
29
31
|
const CHAT_ROLES = new Set([
|
|
30
32
|
'assistant',
|
|
@@ -44,11 +46,73 @@ type RedactionResult = {
|
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
type RedactionContext = {
|
|
49
|
+
generatedImageData: Set<string>;
|
|
50
|
+
generatedImageIds: Set<string>;
|
|
47
51
|
toolNamesByCallId: Map<string, string>;
|
|
48
52
|
};
|
|
49
53
|
|
|
50
54
|
const TOOL_OUTPUT_FIELD_KEYS = ['content', 'artifact'];
|
|
51
55
|
|
|
56
|
+
type ResponsesReplayOutputDescriptor = {
|
|
57
|
+
nestedOutputFields?: Readonly<Record<string, readonly string[]>>;
|
|
58
|
+
outputFields: readonly string[];
|
|
59
|
+
resolveToolNameFromCallId?: boolean;
|
|
60
|
+
toolName?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const RESPONSES_REPLAY_OUTPUT_DESCRIPTORS: Readonly<
|
|
64
|
+
Record<string, ResponsesReplayOutputDescriptor>
|
|
65
|
+
> = {
|
|
66
|
+
local_shell_call_output: {
|
|
67
|
+
outputFields: ['output'],
|
|
68
|
+
toolName: 'local_shell',
|
|
69
|
+
},
|
|
70
|
+
shell_call_output: { outputFields: ['output'], toolName: 'shell' },
|
|
71
|
+
apply_patch_call_output: {
|
|
72
|
+
outputFields: ['output'],
|
|
73
|
+
toolName: 'apply_patch',
|
|
74
|
+
},
|
|
75
|
+
program_output: { outputFields: ['result'], toolName: 'program' },
|
|
76
|
+
code_interpreter_call: {
|
|
77
|
+
outputFields: ['outputs'],
|
|
78
|
+
toolName: 'code_interpreter',
|
|
79
|
+
},
|
|
80
|
+
mcp_call: { outputFields: ['output', 'error'], toolName: 'mcp' },
|
|
81
|
+
mcp_list_tools: {
|
|
82
|
+
outputFields: ['tools', 'error'],
|
|
83
|
+
toolName: 'mcp_list_tools',
|
|
84
|
+
},
|
|
85
|
+
image_generation_call: {
|
|
86
|
+
outputFields: ['result'],
|
|
87
|
+
toolName: 'image_generation',
|
|
88
|
+
},
|
|
89
|
+
file_search_call: {
|
|
90
|
+
outputFields: ['results'],
|
|
91
|
+
toolName: 'file_search',
|
|
92
|
+
},
|
|
93
|
+
web_search_call: {
|
|
94
|
+
nestedOutputFields: { action: ['sources'] },
|
|
95
|
+
outputFields: ['results'],
|
|
96
|
+
toolName: 'web_search',
|
|
97
|
+
},
|
|
98
|
+
tool_search_output: {
|
|
99
|
+
outputFields: ['tools'],
|
|
100
|
+
toolName: 'tool_search',
|
|
101
|
+
},
|
|
102
|
+
function_call_output: {
|
|
103
|
+
outputFields: ['output'],
|
|
104
|
+
resolveToolNameFromCallId: true,
|
|
105
|
+
},
|
|
106
|
+
custom_tool_call_output: {
|
|
107
|
+
outputFields: ['output'],
|
|
108
|
+
resolveToolNameFromCallId: true,
|
|
109
|
+
},
|
|
110
|
+
computer_call_output: {
|
|
111
|
+
outputFields: ['output'],
|
|
112
|
+
toolName: 'computer_use',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
52
116
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
53
117
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
54
118
|
}
|
|
@@ -119,6 +183,8 @@ function getSerializedToolCallId(
|
|
|
119
183
|
): string | undefined {
|
|
120
184
|
return (
|
|
121
185
|
getStringField(value, 'tool_call_id') ??
|
|
186
|
+
getStringField(value, 'toolCallId') ??
|
|
187
|
+
getStringField(value, 'call_id') ??
|
|
122
188
|
getNestedStringField(value, 'kwargs', 'tool_call_id') ??
|
|
123
189
|
getNestedStringField(value, 'additional_kwargs', 'tool_call_id') ??
|
|
124
190
|
getNestedStringField(value, 'data', 'tool_call_id') ??
|
|
@@ -180,6 +246,26 @@ function hasToolMessageIdentity(value: Record<string, unknown>): boolean {
|
|
|
180
246
|
);
|
|
181
247
|
}
|
|
182
248
|
|
|
249
|
+
function hasAssistantMessageIdentity(value: Record<string, unknown>): boolean {
|
|
250
|
+
const role = getStringField(value, 'role');
|
|
251
|
+
if (role?.toLowerCase() === 'assistant') {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
const type = getStringField(value, 'type') ?? getStringField(value, '_type');
|
|
255
|
+
if (type === 'ai' || type === 'assistant') {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
const id = value.id;
|
|
259
|
+
return (
|
|
260
|
+
Array.isArray(id) &&
|
|
261
|
+
id.some(
|
|
262
|
+
(part) =>
|
|
263
|
+
typeof part === 'string' &&
|
|
264
|
+
(part === 'AIMessage' || part === 'AIMessageChunk')
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
183
269
|
function redactToolContentFields(
|
|
184
270
|
value: Record<string, unknown>,
|
|
185
271
|
config: ResolvedLangfuseToolOutputTracingConfig
|
|
@@ -213,13 +299,250 @@ function redactToolContentFields(
|
|
|
213
299
|
return next;
|
|
214
300
|
}
|
|
215
301
|
|
|
216
|
-
function
|
|
302
|
+
function redactResponsesReplayOutput(
|
|
303
|
+
value: Record<string, unknown>,
|
|
304
|
+
config: ResolvedLangfuseToolOutputTracingConfig,
|
|
305
|
+
redactionContext: RedactionContext
|
|
306
|
+
): RedactionResult | undefined {
|
|
307
|
+
const type = getStringField(value, 'type');
|
|
308
|
+
if (
|
|
309
|
+
type == null ||
|
|
310
|
+
!Object.prototype.hasOwnProperty.call(
|
|
311
|
+
RESPONSES_REPLAY_OUTPUT_DESCRIPTORS,
|
|
312
|
+
type
|
|
313
|
+
)
|
|
314
|
+
) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
const descriptor =
|
|
318
|
+
RESPONSES_REPLAY_OUTPUT_DESCRIPTORS[
|
|
319
|
+
type as keyof typeof RESPONSES_REPLAY_OUTPUT_DESCRIPTORS
|
|
320
|
+
];
|
|
321
|
+
const hasDirectOutput = descriptor.outputFields.some(
|
|
322
|
+
(outputField) => outputField in value
|
|
323
|
+
);
|
|
324
|
+
const hasNestedOutput = Object.entries(
|
|
325
|
+
descriptor.nestedOutputFields ?? {}
|
|
326
|
+
).some(([objectField, outputFields]) => {
|
|
327
|
+
const nested = value[objectField];
|
|
328
|
+
return (
|
|
329
|
+
isRecord(nested) &&
|
|
330
|
+
outputFields.some((outputField) => outputField in nested)
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
if (!hasDirectOutput && !hasNestedOutput) {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
let toolName = descriptor.toolName;
|
|
337
|
+
if (descriptor.resolveToolNameFromCallId === true) {
|
|
338
|
+
toolName = getSerializedToolName(value, redactionContext);
|
|
339
|
+
} else if (type === 'mcp_call') {
|
|
340
|
+
toolName = getStringField(value, 'name') ?? descriptor.toolName;
|
|
341
|
+
}
|
|
342
|
+
if (!shouldRedactTool(toolName, config)) {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
const redacted = { ...value };
|
|
346
|
+
for (const outputField of descriptor.outputFields) {
|
|
347
|
+
if (outputField in redacted) {
|
|
348
|
+
redacted[outputField] = config.redactionText;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const [objectField, outputFields] of Object.entries(
|
|
352
|
+
descriptor.nestedOutputFields ?? {}
|
|
353
|
+
)) {
|
|
354
|
+
const nested = redacted[objectField];
|
|
355
|
+
if (!isRecord(nested)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const redactedNested = { ...nested };
|
|
359
|
+
for (const outputField of outputFields) {
|
|
360
|
+
if (outputField in redactedNested) {
|
|
361
|
+
redactedNested[outputField] = config.redactionText;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
redacted[objectField] = redactedNested;
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
value: redacted,
|
|
368
|
+
changed: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function redactStandardServerToolResult(
|
|
373
|
+
value: Record<string, unknown>,
|
|
374
|
+
config: ResolvedLangfuseToolOutputTracingConfig,
|
|
375
|
+
redactionContext: RedactionContext
|
|
376
|
+
): RedactionResult | undefined {
|
|
377
|
+
if (
|
|
378
|
+
getStringField(value, 'type') !== 'server_tool_call_result' ||
|
|
379
|
+
!('output' in value)
|
|
380
|
+
) {
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
const toolName =
|
|
384
|
+
getNestedStringField(value, 'extras', 'name') ??
|
|
385
|
+
getSerializedToolName(value, redactionContext);
|
|
386
|
+
if (!shouldRedactTool(toolName, config)) {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
value: { ...value, output: config.redactionText },
|
|
391
|
+
changed: true,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function parseSerializedServerToolResultMarker(
|
|
396
|
+
text: string
|
|
397
|
+
): { toolName?: string } | undefined {
|
|
398
|
+
if (!text.startsWith(SERVER_TOOL_RESULT_PREFIX)) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
const parsed = JSON.parse(text) as unknown;
|
|
403
|
+
if (!isRecord(parsed) || !isRecord(parsed.serverToolResult)) {
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
const result = parsed.serverToolResult;
|
|
407
|
+
if (result[SERVER_TOOL_RESULT_REPLAY_MARKER_KEY] !== true) {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
const status = getStringField(result, 'status');
|
|
411
|
+
if (
|
|
412
|
+
(status !== 'error' && status !== 'success') ||
|
|
413
|
+
!Object.prototype.hasOwnProperty.call(result, 'output')
|
|
414
|
+
) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
const toolName = getStringField(result, 'toolName');
|
|
418
|
+
return toolName != null ? { toolName } : {};
|
|
419
|
+
} catch {
|
|
420
|
+
const match =
|
|
421
|
+
/^\{"serverToolResult":\{"librechatResponsesReplay":true,(?:"toolName":("(?:\\.|[^"\\])*"),)?"status":"(?:error|success)","output":/.exec(
|
|
422
|
+
text.slice(0, 512)
|
|
423
|
+
);
|
|
424
|
+
if (match == null) {
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
const encodedToolName = match.at(1);
|
|
428
|
+
if (encodedToolName == null) {
|
|
429
|
+
return {};
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
const toolName = JSON.parse(encodedToolName) as unknown;
|
|
433
|
+
return typeof toolName === 'string' ? { toolName } : {};
|
|
434
|
+
} catch {
|
|
435
|
+
return {};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function redactMarkedServerToolResult(
|
|
441
|
+
value: Record<string, unknown>,
|
|
442
|
+
config: ResolvedLangfuseToolOutputTracingConfig,
|
|
443
|
+
allowSerializedMarker: boolean
|
|
444
|
+
): RedactionResult | undefined {
|
|
445
|
+
const type = getStringField(value, 'type');
|
|
446
|
+
if (type !== 'text' && type !== 'image') {
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
const text = type === 'text' ? getStringField(value, 'text') : undefined;
|
|
450
|
+
const extras = value.extras;
|
|
451
|
+
const marker = isRecord(extras)
|
|
452
|
+
? extras.librechatServerToolResult
|
|
453
|
+
: undefined;
|
|
454
|
+
const serializedMarker =
|
|
455
|
+
allowSerializedMarker && text != null
|
|
456
|
+
? parseSerializedServerToolResultMarker(text)
|
|
457
|
+
: undefined;
|
|
458
|
+
if (!isRecord(marker) && serializedMarker == null) {
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
const toolName = isRecord(marker)
|
|
462
|
+
? getStringField(marker, 'toolName')
|
|
463
|
+
: serializedMarker?.toolName;
|
|
464
|
+
if (!shouldRedactTool(toolName, config)) {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
if (type === 'image') {
|
|
468
|
+
const redacted = { ...value };
|
|
469
|
+
let changed = false;
|
|
470
|
+
for (const outputField of ['data', 'url', 'fileId']) {
|
|
471
|
+
if (outputField in redacted) {
|
|
472
|
+
redacted[outputField] = config.redactionText;
|
|
473
|
+
changed = true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return changed ? { value: redacted, changed: true } : undefined;
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
value: { ...value, text: config.redactionText },
|
|
480
|
+
changed: true,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function redactGeneratedImageBlock(
|
|
485
|
+
value: Record<string, unknown>,
|
|
486
|
+
config: ResolvedLangfuseToolOutputTracingConfig,
|
|
487
|
+
redactionContext: RedactionContext,
|
|
488
|
+
isAssistantContent: boolean
|
|
489
|
+
): RedactionResult | undefined {
|
|
490
|
+
if (
|
|
491
|
+
getStringField(value, 'type') !== 'image' ||
|
|
492
|
+
!shouldRedactTool('image_generation', config)
|
|
493
|
+
) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
const extras = value.extras;
|
|
497
|
+
const explicitMarker = isRecord(extras)
|
|
498
|
+
? extras.librechatServerToolResult
|
|
499
|
+
: undefined;
|
|
500
|
+
const explicitToolName = isRecord(explicitMarker)
|
|
501
|
+
? getStringField(explicitMarker, 'toolName')
|
|
502
|
+
: undefined;
|
|
503
|
+
if (
|
|
504
|
+
explicitToolName != null &&
|
|
505
|
+
normalizeToolName(explicitToolName) !== 'image_generation'
|
|
506
|
+
) {
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
const id = getStringField(value, 'id');
|
|
510
|
+
const data = getStringField(value, 'data');
|
|
511
|
+
const metadata = value.metadata;
|
|
512
|
+
const normalizedStatus = isRecord(metadata)
|
|
513
|
+
? getStringField(metadata, 'status')
|
|
514
|
+
: undefined;
|
|
515
|
+
const hasNormalizedGeneratedImageIdentity =
|
|
516
|
+
isAssistantContent &&
|
|
517
|
+
id?.startsWith('ig_') === true &&
|
|
518
|
+
isPresent(normalizedStatus);
|
|
519
|
+
const isGeneratedImage =
|
|
520
|
+
explicitToolName != null ||
|
|
521
|
+
hasNormalizedGeneratedImageIdentity ||
|
|
522
|
+
(id != null
|
|
523
|
+
? redactionContext.generatedImageIds.has(id)
|
|
524
|
+
: data != null && redactionContext.generatedImageData.has(data));
|
|
525
|
+
if (!isGeneratedImage) {
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
const redacted = { ...value };
|
|
529
|
+
let changed = false;
|
|
530
|
+
for (const outputField of ['data', 'url', 'fileId']) {
|
|
531
|
+
if (outputField in redacted) {
|
|
532
|
+
redacted[outputField] = config.redactionText;
|
|
533
|
+
changed = true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return changed ? { value: redacted, changed: true } : undefined;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function collectRedactionContext(
|
|
217
540
|
value: unknown,
|
|
218
541
|
redactionContext: RedactionContext
|
|
219
542
|
): void {
|
|
220
543
|
if (Array.isArray(value)) {
|
|
221
544
|
for (const item of value) {
|
|
222
|
-
|
|
545
|
+
collectRedactionContext(item, redactionContext);
|
|
223
546
|
}
|
|
224
547
|
return;
|
|
225
548
|
}
|
|
@@ -234,21 +557,38 @@ function collectToolCallNames(
|
|
|
234
557
|
redactionContext.toolNamesByCallId.set(toolCallId, toolName);
|
|
235
558
|
}
|
|
236
559
|
|
|
560
|
+
if (getStringField(value, 'type') === 'image_generation_call') {
|
|
561
|
+
const id = getStringField(value, 'id');
|
|
562
|
+
const result = getStringField(value, 'result');
|
|
563
|
+
if (id != null) {
|
|
564
|
+
redactionContext.generatedImageIds.add(id);
|
|
565
|
+
}
|
|
566
|
+
if (result != null) {
|
|
567
|
+
redactionContext.generatedImageData.add(result);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
237
571
|
for (const child of Object.values(value)) {
|
|
238
|
-
|
|
572
|
+
collectRedactionContext(child, redactionContext);
|
|
239
573
|
}
|
|
240
574
|
}
|
|
241
575
|
|
|
242
576
|
function redactValue(
|
|
243
577
|
value: unknown,
|
|
244
578
|
config: ResolvedLangfuseToolOutputTracingConfig,
|
|
245
|
-
redactionContext: RedactionContext
|
|
579
|
+
redactionContext: RedactionContext,
|
|
580
|
+
allowSerializedServerToolResult = false
|
|
246
581
|
): RedactionResult {
|
|
247
582
|
if (Array.isArray(value)) {
|
|
248
583
|
let changed = false;
|
|
249
584
|
const next: unknown[] = [];
|
|
250
585
|
for (const item of value) {
|
|
251
|
-
const result = redactValue(
|
|
586
|
+
const result = redactValue(
|
|
587
|
+
item,
|
|
588
|
+
config,
|
|
589
|
+
redactionContext,
|
|
590
|
+
allowSerializedServerToolResult
|
|
591
|
+
);
|
|
252
592
|
if (result.changed) {
|
|
253
593
|
changed = true;
|
|
254
594
|
}
|
|
@@ -261,6 +601,43 @@ function redactValue(
|
|
|
261
601
|
return { value, changed: false };
|
|
262
602
|
}
|
|
263
603
|
|
|
604
|
+
const allowSerializedMarker =
|
|
605
|
+
allowSerializedServerToolResult || hasAssistantMessageIdentity(value);
|
|
606
|
+
|
|
607
|
+
const replayOutput = redactResponsesReplayOutput(
|
|
608
|
+
value,
|
|
609
|
+
config,
|
|
610
|
+
redactionContext
|
|
611
|
+
);
|
|
612
|
+
if (replayOutput != null) {
|
|
613
|
+
return replayOutput;
|
|
614
|
+
}
|
|
615
|
+
const standardServerToolResult = redactStandardServerToolResult(
|
|
616
|
+
value,
|
|
617
|
+
config,
|
|
618
|
+
redactionContext
|
|
619
|
+
);
|
|
620
|
+
if (standardServerToolResult != null) {
|
|
621
|
+
return standardServerToolResult;
|
|
622
|
+
}
|
|
623
|
+
const markedServerToolResult = redactMarkedServerToolResult(
|
|
624
|
+
value,
|
|
625
|
+
config,
|
|
626
|
+
allowSerializedMarker
|
|
627
|
+
);
|
|
628
|
+
if (markedServerToolResult != null) {
|
|
629
|
+
return markedServerToolResult;
|
|
630
|
+
}
|
|
631
|
+
const generatedImageBlock = redactGeneratedImageBlock(
|
|
632
|
+
value,
|
|
633
|
+
config,
|
|
634
|
+
redactionContext,
|
|
635
|
+
allowSerializedMarker
|
|
636
|
+
);
|
|
637
|
+
if (generatedImageBlock != null) {
|
|
638
|
+
return generatedImageBlock;
|
|
639
|
+
}
|
|
640
|
+
|
|
264
641
|
const toolName = getSerializedToolName(value, redactionContext);
|
|
265
642
|
if (hasToolMessageIdentity(value) && shouldRedactTool(toolName, config)) {
|
|
266
643
|
return {
|
|
@@ -272,7 +649,12 @@ function redactValue(
|
|
|
272
649
|
let changed = false;
|
|
273
650
|
const next: Record<string, unknown> = {};
|
|
274
651
|
for (const [key, child] of Object.entries(value)) {
|
|
275
|
-
const result = redactValue(
|
|
652
|
+
const result = redactValue(
|
|
653
|
+
child,
|
|
654
|
+
config,
|
|
655
|
+
redactionContext,
|
|
656
|
+
allowSerializedMarker
|
|
657
|
+
);
|
|
276
658
|
if (result.changed) {
|
|
277
659
|
changed = true;
|
|
278
660
|
}
|
|
@@ -287,10 +669,12 @@ function redactSerializedValue(
|
|
|
287
669
|
config: ResolvedLangfuseToolOutputTracingConfig
|
|
288
670
|
): RedactionResult {
|
|
289
671
|
const redactionContext: RedactionContext = {
|
|
672
|
+
generatedImageData: new Set(),
|
|
673
|
+
generatedImageIds: new Set(),
|
|
290
674
|
toolNamesByCallId: new Map(),
|
|
291
675
|
};
|
|
292
676
|
if (typeof value !== 'string') {
|
|
293
|
-
|
|
677
|
+
collectRedactionContext(value, redactionContext);
|
|
294
678
|
return redactValue(value, config, redactionContext);
|
|
295
679
|
}
|
|
296
680
|
|
|
@@ -301,13 +685,18 @@ function redactSerializedValue(
|
|
|
301
685
|
|
|
302
686
|
try {
|
|
303
687
|
const parsed = JSON.parse(value) as unknown;
|
|
304
|
-
|
|
688
|
+
collectRedactionContext(parsed, redactionContext);
|
|
305
689
|
const result = redactValue(parsed, config, redactionContext);
|
|
306
690
|
return result.changed
|
|
307
691
|
? { value: JSON.stringify(result.value), changed: true }
|
|
308
692
|
: { value, changed: false };
|
|
309
693
|
} catch {
|
|
310
|
-
|
|
694
|
+
// OpenTelemetry truncates string attributes before span processors run.
|
|
695
|
+
// Langfuse serializes structured message values to JSON first, so a
|
|
696
|
+
// truncated attribute can still contain tool output even though it is no
|
|
697
|
+
// longer parseable. Fail closed for JSON-shaped attributes whenever tool
|
|
698
|
+
// output redaction is active instead of exporting a partial secret.
|
|
699
|
+
return { value: config.redactionText, changed: true };
|
|
311
700
|
}
|
|
312
701
|
}
|
|
313
702
|
|
|
@@ -397,6 +786,17 @@ export function redactLangfuseSpanToolOutputs(
|
|
|
397
786
|
}
|
|
398
787
|
}
|
|
399
788
|
|
|
789
|
+
export function prepareLangfuseSpanForExport(
|
|
790
|
+
span: ReadableSpan,
|
|
791
|
+
config?: ResolvedLangfuseToolOutputTracingConfig
|
|
792
|
+
): void {
|
|
793
|
+
classifyLangfuseToolNodeSpan(span);
|
|
794
|
+
if (config != null) {
|
|
795
|
+
redactLangfuseSpanToolOutputs(span, config);
|
|
796
|
+
}
|
|
797
|
+
shapeLangfuseSpan(span);
|
|
798
|
+
}
|
|
799
|
+
|
|
400
800
|
class ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {
|
|
401
801
|
private readonly processor: LangfuseSpanProcessor;
|
|
402
802
|
private readonly fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig;
|
|
@@ -430,12 +830,8 @@ class ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {
|
|
|
430
830
|
if (shouldDropLangfuseSpan(span.name)) {
|
|
431
831
|
return;
|
|
432
832
|
}
|
|
433
|
-
classifyLangfuseToolNodeSpan(span);
|
|
434
|
-
shapeLangfuseSpan(span);
|
|
435
833
|
const config = this.spanConfigs.get(span) ?? this.fallbackConfig;
|
|
436
|
-
|
|
437
|
-
redactLangfuseSpanToolOutputs(span, config);
|
|
438
|
-
}
|
|
834
|
+
prepareLangfuseSpanForExport(span, config);
|
|
439
835
|
this.processor.onEnd(span);
|
|
440
836
|
}
|
|
441
837
|
|
package/src/llm/bedrock/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
supportsBedrockToolCache,
|
|
47
47
|
type PromptCacheTtl,
|
|
48
48
|
} from '@/messages/cache';
|
|
49
|
+
import { linkStreamLimitCanonical } from '@/llm/streamLimits';
|
|
49
50
|
import { applyCachePointsToConversePayload } from './cachePoints';
|
|
50
51
|
import { insertBedrockToolCachePoint } from './toolCache';
|
|
51
52
|
|
|
@@ -572,8 +573,17 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
|
|
|
572
573
|
}
|
|
573
574
|
|
|
574
575
|
const deltaChunk = handleConverseStreamContentBlockDelta(splitDelta);
|
|
576
|
+
const enrichedChunk = this.enrichChunk(deltaChunk, seenBlockIndices);
|
|
577
|
+
if (enrichedChunk !== deltaChunk) {
|
|
578
|
+
/** The callback copy is the same emission as the enriched yield;
|
|
579
|
+
* without the link, stream-limit accounting charges both message
|
|
580
|
+
* objects and Bedrock tool arguments falsely trip near half the
|
|
581
|
+
* cap. Linked on the messages, which are what the accounting
|
|
582
|
+
* observes. */
|
|
583
|
+
linkStreamLimitCanonical(deltaChunk.message, enrichedChunk.message);
|
|
584
|
+
}
|
|
575
585
|
await enqueueChunk({
|
|
576
|
-
chunk:
|
|
586
|
+
chunk: enrichedChunk,
|
|
577
587
|
callbackChunk: deltaChunk,
|
|
578
588
|
callbackToken: deltaChunk.text,
|
|
579
589
|
smooth,
|
|
@@ -602,8 +612,18 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
|
|
|
602
612
|
toolUseBlockIndices.add(idx);
|
|
603
613
|
}
|
|
604
614
|
}
|
|
615
|
+
const enrichedStart = this.enrichChunk(
|
|
616
|
+
startChunk,
|
|
617
|
+
seenBlockIndices
|
|
618
|
+
);
|
|
619
|
+
if (enrichedStart !== startChunk) {
|
|
620
|
+
linkStreamLimitCanonical(
|
|
621
|
+
startChunk.message,
|
|
622
|
+
enrichedStart.message
|
|
623
|
+
);
|
|
624
|
+
}
|
|
605
625
|
await enqueueChunk({
|
|
606
|
-
chunk:
|
|
626
|
+
chunk: enrichedStart,
|
|
607
627
|
callbackChunk: startChunk,
|
|
608
628
|
callbackToken: startChunk.text,
|
|
609
629
|
});
|