@librechat/agents 3.3.9 → 3.3.11

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.
Files changed (61) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +4 -0
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +21 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/langfuseToolOutputTracing.cjs +228 -16
  6. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  7. package/dist/cjs/llm/init.cjs +1 -1
  8. package/dist/cjs/llm/invoke.cjs +14 -7
  9. package/dist/cjs/llm/invoke.cjs.map +1 -1
  10. package/dist/cjs/llm/openai/index.cjs +188 -11
  11. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  12. package/dist/cjs/main.cjs +4 -3
  13. package/dist/cjs/messages/core.cjs +592 -27
  14. package/dist/cjs/messages/core.cjs.map +1 -1
  15. package/dist/cjs/run.cjs +11 -1
  16. package/dist/cjs/run.cjs.map +1 -1
  17. package/dist/cjs/stream.cjs +2 -2
  18. package/dist/cjs/tools/ToolNode.cjs +1 -1
  19. package/dist/cjs/tools/search/tool.cjs +1 -1
  20. package/dist/cjs/utils/index.cjs +1 -1
  21. package/dist/esm/agents/AgentContext.mjs +4 -0
  22. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  23. package/dist/esm/graphs/Graph.mjs +21 -2
  24. package/dist/esm/graphs/Graph.mjs.map +1 -1
  25. package/dist/esm/langfuseToolOutputTracing.mjs +228 -16
  26. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  27. package/dist/esm/llm/init.mjs +1 -1
  28. package/dist/esm/llm/invoke.mjs +14 -7
  29. package/dist/esm/llm/invoke.mjs.map +1 -1
  30. package/dist/esm/llm/openai/index.mjs +190 -13
  31. package/dist/esm/llm/openai/index.mjs.map +1 -1
  32. package/dist/esm/main.mjs +5 -5
  33. package/dist/esm/messages/core.mjs +592 -28
  34. package/dist/esm/messages/core.mjs.map +1 -1
  35. package/dist/esm/run.mjs +11 -1
  36. package/dist/esm/run.mjs.map +1 -1
  37. package/dist/esm/stream.mjs +2 -2
  38. package/dist/esm/tools/ToolNode.mjs +1 -1
  39. package/dist/esm/tools/search/tool.mjs +1 -1
  40. package/dist/esm/utils/index.mjs +1 -1
  41. package/dist/types/agents/AgentContext.d.ts +2 -0
  42. package/dist/types/graphs/Graph.d.ts +5 -0
  43. package/dist/types/langfuseToolOutputTracing.d.ts +1 -0
  44. package/dist/types/llm/invoke.d.ts +1 -1
  45. package/dist/types/messages/core.d.ts +11 -6
  46. package/dist/types/run.d.ts +7 -0
  47. package/package.json +1 -1
  48. package/src/agents/AgentContext.ts +5 -0
  49. package/src/graphs/Graph.ts +39 -0
  50. package/src/langfuseToolOutputTracing.ts +410 -14
  51. package/src/llm/custom-chat-models.smoke.test.ts +747 -0
  52. package/src/llm/invoke.test.ts +98 -0
  53. package/src/llm/invoke.ts +34 -23
  54. package/src/llm/openai/index.ts +334 -25
  55. package/src/llm/openai/llm.spec.ts +107 -6
  56. package/src/messages/core.ts +1290 -42
  57. package/src/messages/formatAgentMessages.test.ts +2623 -0
  58. package/src/run.ts +15 -0
  59. package/src/specs/discovered-tools.test.ts +217 -0
  60. package/src/specs/langfuse-tool-output-tracing.test.ts +887 -0
  61. package/src/specs/preemptSeal.test.ts +374 -5
package/src/run.ts CHANGED
@@ -538,6 +538,21 @@ export class Run<_T extends t.BaseGraphState> {
538
538
  return this.Graph.getRunMessages();
539
539
  }
540
540
 
541
+ /**
542
+ * Returns a defensive snapshot of tools discovered by the current run.
543
+ * Pass an agent id for that context, or omit it for the ordered union across
544
+ * contexts. Interrupted state is available immediately for host persistence;
545
+ * completed runs retain their final snapshot through graph cleanup.
546
+ */
547
+ getDiscoveredTools(agentId?: string): string[] {
548
+ if (!this.Graph) {
549
+ throw new Error(
550
+ 'Graph not initialized. Make sure to use Run.create() to instantiate the Run.'
551
+ );
552
+ }
553
+ return this.Graph.getDiscoveredTools(agentId);
554
+ }
555
+
541
556
  /**
542
557
  * Returns the current calibration ratio (EMA of provider-vs-estimate token ratios).
543
558
  * Hosts should persist this value and pass it back as `RunConfig.calibrationRatio`
@@ -0,0 +1,217 @@
1
+ import { z } from 'zod';
2
+ import { tool } from '@langchain/core/tools';
3
+ import { MemorySaver } from '@langchain/langgraph';
4
+ import { HumanMessage } from '@langchain/core/messages';
5
+ import { ChatGenerationChunk } from '@langchain/core/outputs';
6
+ import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
7
+ import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
8
+ import type { BaseMessage } from '@langchain/core/messages';
9
+ import type * as t from '@/types';
10
+ import { FakeChatModel } from '@/llm/fake';
11
+ import { StandardGraph } from '@/graphs';
12
+ import { askUserQuestion } from '@/hitl';
13
+ import { Providers } from '@/common';
14
+ import { Run } from '@/run';
15
+
16
+ const DISCOVERED_TOOL = 'save_issue_mcp_linear';
17
+
18
+ const searchTool = tool(
19
+ async ({ query }) => [
20
+ JSON.stringify({
21
+ found: 1,
22
+ tools: [{ name: DISCOVERED_TOOL }],
23
+ query,
24
+ }),
25
+ { tool_references: [{ tool_name: DISCOVERED_TOOL }] },
26
+ ],
27
+ {
28
+ name: 'tool_search',
29
+ description: 'Find a deferred tool.',
30
+ schema: z.object({ query: z.string() }),
31
+ responseFormat: 'content_and_artifact',
32
+ }
33
+ );
34
+
35
+ const askTool = tool(
36
+ async (input) => {
37
+ const { answer } = askUserQuestion(input);
38
+ return answer;
39
+ },
40
+ {
41
+ name: 'ask_user_question',
42
+ description: 'Pause until the user answers a question.',
43
+ schema: z.object({ question: z.string() }),
44
+ }
45
+ );
46
+
47
+ class DiscoveryThenAskModel extends FakeChatModel {
48
+ private turn = 0;
49
+
50
+ constructor() {
51
+ super({ responses: ['', ''] });
52
+ }
53
+
54
+ async *_streamResponseChunks(
55
+ _messages: BaseMessage[],
56
+ _options: this['ParsedCallOptions'],
57
+ _runManager?: CallbackManagerForLLMRun
58
+ ): AsyncGenerator<ChatGenerationChunk> {
59
+ const calls: ToolCall[][] = [
60
+ [
61
+ {
62
+ name: 'tool_search',
63
+ args: { query: 'save_issue' },
64
+ id: 'search-call',
65
+ type: 'tool_call',
66
+ },
67
+ ],
68
+ [
69
+ {
70
+ name: 'ask_user_question',
71
+ args: { question: 'Proceed?' },
72
+ id: 'ask-call',
73
+ type: 'tool_call',
74
+ },
75
+ ],
76
+ ];
77
+ const toolCallChunks = calls[this.turn++].map(
78
+ (call, index): ToolCallChunk => ({
79
+ name: call.name,
80
+ args: JSON.stringify(call.args),
81
+ id: call.id,
82
+ index,
83
+ type: 'tool_call_chunk',
84
+ })
85
+ );
86
+ yield this._createResponseChunk('', toolCallChunks);
87
+ }
88
+ }
89
+
90
+ describe('Run discovered tools', () => {
91
+ it('exposes discoveries made before an ask-user pause', async () => {
92
+ const run = await Run.create<t.IState>({
93
+ runId: 'discovery-pause-run',
94
+ graphConfig: {
95
+ type: 'standard',
96
+ llmConfig: {
97
+ provider: Providers.OPENAI,
98
+ model: 'gpt-4o-mini',
99
+ streaming: true,
100
+ streamUsage: false,
101
+ },
102
+ instructions: 'Search first, then ask the user.',
103
+ tools: [searchTool, askTool],
104
+ compileOptions: { checkpointer: new MemorySaver() },
105
+ },
106
+ returnContent: true,
107
+ customHandlers: {},
108
+ });
109
+ run.Graph!.overrideModel = new DiscoveryThenAskModel();
110
+
111
+ await run.processStream(
112
+ { messages: [new HumanMessage('Create a Linear issue')] },
113
+ {
114
+ configurable: { thread_id: 'discovery-pause-thread' },
115
+ version: 'v2',
116
+ }
117
+ );
118
+
119
+ expect(run.getInterrupt()?.payload.type).toBe('ask_user_question');
120
+ // The interrupted inner subgraph has not returned its messages to the outer
121
+ // reducer, which is why reconstructing discoveries from run history fails.
122
+ expect(run.getRunMessages()).toEqual([]);
123
+ expect(run.getDiscoveredTools()).toEqual([DISCOVERED_TOOL]);
124
+
125
+ const snapshot = run.getDiscoveredTools();
126
+ snapshot.push('caller-mutation');
127
+ expect(run.getDiscoveredTools()).toEqual([DISCOVERED_TOOL]);
128
+ });
129
+
130
+ it('retains the last snapshot when normal cleanup resets agent contexts', async () => {
131
+ const run = await Run.create<t.IState>({
132
+ runId: 'discovery-cleanup-run',
133
+ graphConfig: {
134
+ type: 'standard',
135
+ llmConfig: {
136
+ provider: Providers.OPENAI,
137
+ model: 'gpt-4o-mini',
138
+ },
139
+ instructions: 'Test discovery cleanup.',
140
+ },
141
+ });
142
+ const graph = run.Graph as StandardGraph;
143
+ graph.agentContexts
144
+ .get(graph.defaultAgentId)
145
+ ?.markToolsAsDiscovered([DISCOVERED_TOOL]);
146
+
147
+ graph.clearHeavyState();
148
+
149
+ expect(run.getDiscoveredTools()).toEqual([DISCOVERED_TOOL]);
150
+ });
151
+
152
+ it('supports per-agent snapshots while returning a deduplicated union by default', async () => {
153
+ const run = await Run.create<t.IState>({
154
+ runId: 'multi-agent-discovery-run',
155
+ graphConfig: {
156
+ type: 'multi-agent',
157
+ agents: [
158
+ {
159
+ agentId: 'researcher',
160
+ provider: Providers.ANTHROPIC,
161
+ clientOptions: {
162
+ modelName: 'claude-haiku-4-5',
163
+ apiKey: 'test-key',
164
+ },
165
+ instructions: 'Research.',
166
+ },
167
+ {
168
+ agentId: 'writer',
169
+ provider: Providers.ANTHROPIC,
170
+ clientOptions: {
171
+ modelName: 'claude-haiku-4-5',
172
+ apiKey: 'test-key',
173
+ },
174
+ instructions: 'Write.',
175
+ },
176
+ ],
177
+ edges: [{ from: 'researcher', to: 'writer', edgeType: 'direct' }],
178
+ },
179
+ });
180
+ const graph = run.Graph as StandardGraph;
181
+ graph.agentContexts
182
+ .get('researcher')
183
+ ?.markToolsAsDiscovered(['shared_tool', 'research_tool']);
184
+ graph.agentContexts
185
+ .get('writer')
186
+ ?.markToolsAsDiscovered(['shared_tool', 'writing_tool']);
187
+
188
+ expect(run.getDiscoveredTools('researcher')).toEqual([
189
+ 'shared_tool',
190
+ 'research_tool',
191
+ ]);
192
+ expect(run.getDiscoveredTools('writer')).toEqual([
193
+ 'shared_tool',
194
+ 'writing_tool',
195
+ ]);
196
+ expect(run.getDiscoveredTools()).toEqual([
197
+ 'shared_tool',
198
+ 'research_tool',
199
+ 'writing_tool',
200
+ ]);
201
+
202
+ graph.clearHeavyState();
203
+ expect(run.getDiscoveredTools('researcher')).toEqual([
204
+ 'shared_tool',
205
+ 'research_tool',
206
+ ]);
207
+ expect(run.getDiscoveredTools('writer')).toEqual([
208
+ 'shared_tool',
209
+ 'writing_tool',
210
+ ]);
211
+ expect(run.getDiscoveredTools()).toEqual([
212
+ 'shared_tool',
213
+ 'research_tool',
214
+ 'writing_tool',
215
+ ]);
216
+ });
217
+ });