@librechat/agents 3.1.95 → 3.1.97

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 (67) hide show
  1. package/dist/cjs/graphs/Graph.cjs +54 -21
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/instrumentation.cjs +120 -9
  4. package/dist/cjs/instrumentation.cjs.map +1 -1
  5. package/dist/cjs/langfuse.cjs +30 -226
  6. package/dist/cjs/langfuse.cjs.map +1 -1
  7. package/dist/cjs/langfuseToolOutputTracing.cjs +465 -0
  8. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -0
  9. package/dist/cjs/main.cjs +1 -0
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/run.cjs +142 -69
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +29 -2
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  15. package/dist/cjs/tools/ToolNode.cjs +20 -8
  16. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  17. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +10 -6
  18. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  19. package/dist/esm/graphs/Graph.mjs +56 -23
  20. package/dist/esm/graphs/Graph.mjs.map +1 -1
  21. package/dist/esm/instrumentation.mjs +118 -9
  22. package/dist/esm/instrumentation.mjs.map +1 -1
  23. package/dist/esm/langfuse.mjs +28 -224
  24. package/dist/esm/langfuse.mjs.map +1 -1
  25. package/dist/esm/langfuseToolOutputTracing.mjs +457 -0
  26. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -0
  27. package/dist/esm/main.mjs +1 -1
  28. package/dist/esm/run.mjs +144 -71
  29. package/dist/esm/run.mjs.map +1 -1
  30. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +29 -3
  31. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  32. package/dist/esm/tools/ToolNode.mjs +20 -8
  33. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  34. package/dist/esm/tools/subagent/SubagentExecutor.mjs +10 -6
  35. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  36. package/dist/types/graphs/Graph.d.ts +5 -1
  37. package/dist/types/instrumentation.d.ts +5 -1
  38. package/dist/types/langfuse.d.ts +6 -28
  39. package/dist/types/langfuseToolOutputTracing.d.ts +20 -0
  40. package/dist/types/run.d.ts +5 -1
  41. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +1 -0
  42. package/dist/types/tools/ToolNode.d.ts +4 -1
  43. package/dist/types/tools/subagent/SubagentExecutor.d.ts +2 -0
  44. package/dist/types/types/graph.d.ts +30 -0
  45. package/dist/types/types/run.d.ts +6 -0
  46. package/dist/types/types/tools.d.ts +7 -0
  47. package/package.json +2 -1
  48. package/src/graphs/Graph.ts +90 -34
  49. package/src/instrumentation.ts +172 -11
  50. package/src/langfuse.ts +59 -324
  51. package/src/langfuseToolOutputTracing.ts +683 -0
  52. package/src/run.ts +190 -87
  53. package/src/specs/langfuse-callbacks.test.ts +178 -1
  54. package/src/specs/langfuse-config.test.ts +112 -76
  55. package/src/specs/langfuse-instrumentation.test.ts +283 -0
  56. package/src/specs/langfuse-metadata.test.ts +54 -1
  57. package/src/specs/langfuse-tool-output-tracing.test.ts +588 -0
  58. package/src/tools/BashProgrammaticToolCalling.ts +39 -5
  59. package/src/tools/ToolNode.ts +28 -7
  60. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +54 -0
  61. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +72 -4
  62. package/src/tools/__tests__/SubagentExecutor.test.ts +32 -0
  63. package/src/tools/__tests__/ToolNode.langfuse.test.ts +41 -0
  64. package/src/tools/subagent/SubagentExecutor.ts +11 -6
  65. package/src/types/graph.ts +32 -0
  66. package/src/types/run.ts +6 -0
  67. package/src/types/tools.ts +7 -0
@@ -41,6 +41,7 @@ import {
41
41
  import { safeDispatchCustomEvent } from '@/utils/events';
42
42
  import { executeHooks } from '@/hooks';
43
43
  import { toLangChainContent } from '@/messages/langchain';
44
+ import { withLangfuseToolOutputTracingConfig } from '@/langfuseToolOutputTracing';
44
45
  import { Constants, GraphEvents, CODE_EXECUTION_TOOLS } from '@/common';
45
46
  import {
46
47
  buildReferenceKey,
@@ -394,6 +395,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
394
395
  private loadRuntimeTools?: t.ToolRefGenerator;
395
396
  handleToolErrors = true;
396
397
  trace = false;
398
+ private runLangfuse?: t.LangfuseConfig;
399
+ private agentLangfuse?: t.LangfuseConfig;
397
400
  toolCallStepIds?: Map<string, string>;
398
401
  errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
399
402
  private toolUsageCount: Map<string, number>;
@@ -473,6 +476,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
473
476
  toolMap,
474
477
  name,
475
478
  tags,
479
+ trace,
480
+ runLangfuse,
481
+ agentLangfuse,
476
482
  errorHandler,
477
483
  toolCallStepIds,
478
484
  handleToolErrors,
@@ -495,6 +501,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
495
501
  fileCheckpointer,
496
502
  }: t.ToolNodeConstructorParams) {
497
503
  super({ name, tags, func: (input, config) => this.run(input, config) });
504
+ this.trace = trace ?? this.trace;
505
+ this.runLangfuse = runLangfuse;
506
+ this.agentLangfuse = agentLangfuse;
498
507
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
499
508
  this.toolCallStepIds = toolCallStepIds;
500
509
  this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
@@ -545,6 +554,19 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
545
554
  }
546
555
  }
547
556
 
557
+ override async invoke(
558
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
559
+ input: any,
560
+ options?: Partial<RunnableConfig>
561
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
562
+ ): Promise<any> {
563
+ return withLangfuseToolOutputTracingConfig(
564
+ this.runLangfuse,
565
+ () => super.invoke(input, options),
566
+ this.agentLangfuse
567
+ );
568
+ }
569
+
548
570
  /**
549
571
  * Returns the run-scoped tool output registry, or `undefined` when
550
572
  * the feature is disabled.
@@ -2140,13 +2162,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2140
2162
 
2141
2163
  /**
2142
2164
  * `interrupt()` reads the current `RunnableConfig` from
2143
- * AsyncLocalStorage, but our `RunnableCallable` sets
2144
- * `trace = false` for ToolNode (intentional avoids LangSmith
2145
- * tracing per tool call). Without the trace path, the upstream
2146
- * `runWithConfig` frame is never established, so we re-anchor
2147
- * here using the node's own `config` Pregel hands us a
2148
- * config that already carries every checkpoint/scratchpad key
2149
- * `interrupt()` needs to suspend and resume.
2165
+ * AsyncLocalStorage. ToolNode usually runs with tracing disabled
2166
+ * (unless Langfuse explicitly enables it), so the upstream
2167
+ * `runWithConfig` frame may not exist. Re-anchor here using the
2168
+ * node's own `config` Pregel hands us a config that already
2169
+ * carries every checkpoint/scratchpad key `interrupt()` needs to
2170
+ * suspend and resume.
2150
2171
  */
2151
2172
  const resumeValue = AsyncLocalStorageProviderSingleton.runWithConfig(
2152
2173
  config,
@@ -34,6 +34,8 @@ type FetchMock = jest.MockedFunction<
34
34
 
35
35
  type CodeApiRequestBody = {
36
36
  timeout?: number;
37
+ continuation_token?: string;
38
+ tool_results?: t.PTCToolResult[];
37
39
  };
38
40
 
39
41
  type TimeoutSchemaForTest = {
@@ -387,6 +389,58 @@ describe('CodeAPI auth header injection', () => {
387
389
  );
388
390
  });
389
391
 
392
+ it('normalizes JSON-looking bash programmatic tool results before continuation', async () => {
393
+ fetchMock
394
+ .mockResolvedValueOnce(
395
+ jsonResponse({
396
+ status: 'tool_call_required',
397
+ continuation_token: 'continue_123',
398
+ tool_calls: [{ id: 'call_001', name: 'lookup_user', input: {} }],
399
+ })
400
+ )
401
+ .mockResolvedValueOnce(completedResponse('done'));
402
+ const tool = createBashProgrammaticToolCallingTool();
403
+ const customToolMap = new Map([
404
+ [
405
+ 'lookup_user',
406
+ {
407
+ name: 'lookup_user',
408
+ invoke: jest.fn(async () =>
409
+ JSON.stringify({
410
+ result: {
411
+ data: [{ id: 'user_123', name: 'Ada' }],
412
+ },
413
+ })
414
+ ),
415
+ },
416
+ ],
417
+ ]) as unknown as t.ToolMap;
418
+
419
+ await tool.invoke(
420
+ { code: 'lookup_user "{}"' },
421
+ {
422
+ toolCall: {
423
+ name: 'bash_programmatic_code_execution',
424
+ args: {},
425
+ toolMap: customToolMap,
426
+ toolDefs,
427
+ },
428
+ }
429
+ );
430
+
431
+ expect(requestBodyAt(1).tool_results).toEqual([
432
+ {
433
+ call_id: 'call_001',
434
+ result: {
435
+ result: {
436
+ data: [{ id: 'user_123', name: 'Ada' }],
437
+ },
438
+ },
439
+ is_error: false,
440
+ },
441
+ ]);
442
+ });
443
+
390
444
  it('reminds that failed bash programmatic executions do not register new files', async () => {
391
445
  fetchMock.mockResolvedValueOnce(
392
446
  jsonResponse({
@@ -16,7 +16,10 @@ import {
16
16
  normalizeToPythonIdentifier,
17
17
  unwrapToolResponse,
18
18
  } from '../ProgrammaticToolCalling';
19
- import { createBashProgrammaticToolCallingSchema } from '../BashProgrammaticToolCalling';
19
+ import {
20
+ createBashProgrammaticToolCallingSchema,
21
+ normalizeBashToolResultsForReplay,
22
+ } from '../BashProgrammaticToolCalling';
20
23
  import {
21
24
  createProgrammaticToolRegistry,
22
25
  createGetTeamMembersTool,
@@ -40,9 +43,11 @@ describe('ProgrammaticToolCalling', () => {
40
43
  const schema = createBashProgrammaticToolCallingSchema();
41
44
  const description = schema.properties.code.description;
42
45
 
43
- expect(description).toContain('jq: use fromjson? // .');
44
- expect(description).toContain('again on JSON-string fields');
45
- expect(description).toContain('arrays may contain strings');
46
+ expect(description).toContain(
47
+ 'Tool stdout is normalized to one compact JSON value'
48
+ );
49
+ expect(description).toContain('use fromjson? // .');
50
+ expect(description).toContain('only for JSON-string fields');
46
51
  expect(description).toContain('raw=$(tool');
47
52
  expect(description).toContain('direct tool > file may be empty');
48
53
  expect(description).toContain('/mnt/data/sf.json');
@@ -53,6 +58,69 @@ describe('ProgrammaticToolCalling', () => {
53
58
  });
54
59
  });
55
60
 
61
+ describe('normalizeBashToolResultsForReplay', () => {
62
+ it('decodes JSON-looking string results before bash replay', () => {
63
+ const results = normalizeBashToolResultsForReplay([
64
+ {
65
+ call_id: 'call_001',
66
+ result: JSON.stringify({
67
+ result: {
68
+ data: [{ station_id: 'USW00094725', winter_days: 91 }],
69
+ },
70
+ }),
71
+ is_error: false,
72
+ },
73
+ ]);
74
+
75
+ expect(results[0].result).toEqual({
76
+ result: {
77
+ data: [{ station_id: 'USW00094725', winter_days: 91 }],
78
+ },
79
+ });
80
+ });
81
+
82
+ it('decodes JSON-looking array string results before bash replay', () => {
83
+ const results = normalizeBashToolResultsForReplay([
84
+ {
85
+ call_id: 'call_001',
86
+ result: '[{"name":"tempAvg"},{"name":"snowDepth"}]',
87
+ is_error: false,
88
+ },
89
+ ]);
90
+
91
+ expect(results[0].result).toEqual([
92
+ { name: 'tempAvg' },
93
+ { name: 'snowDepth' },
94
+ ]);
95
+ });
96
+
97
+ it('preserves plain strings, invalid JSON strings, and error results', () => {
98
+ const errorJson = '{"message":"tool failed"}';
99
+ const results = normalizeBashToolResultsForReplay([
100
+ {
101
+ call_id: 'call_001',
102
+ result: 'plain text',
103
+ is_error: false,
104
+ },
105
+ {
106
+ call_id: 'call_002',
107
+ result: '{not json}',
108
+ is_error: false,
109
+ },
110
+ {
111
+ call_id: 'call_003',
112
+ result: errorJson,
113
+ is_error: true,
114
+ error_message: 'tool failed',
115
+ },
116
+ ]);
117
+
118
+ expect(results[0].result).toBe('plain text');
119
+ expect(results[1].result).toBe('{not json}');
120
+ expect(results[2].result).toBe(errorJson);
121
+ });
122
+ });
123
+
56
124
  describe('executeTools', () => {
57
125
  let toolMap: t.ToolMap;
58
126
 
@@ -424,6 +424,38 @@ describe('SubagentExecutor', () => {
424
424
  expect(clearHeavyState).toHaveBeenCalled();
425
425
  });
426
426
 
427
+ it('passes parent Langfuse config to the child graph', async () => {
428
+ const langfuse = {
429
+ enabled: true,
430
+ publicKey: 'pk-run',
431
+ secretKey: 'sk-run',
432
+ baseUrl: 'https://langfuse.test',
433
+ toolOutputTracing: { enabled: false },
434
+ };
435
+ let observedLangfuse: typeof langfuse | undefined;
436
+ const executor = createExecutor({
437
+ langfuse,
438
+ createChildGraph: (input): StandardGraph => {
439
+ observedLangfuse = input.langfuse as typeof langfuse;
440
+ return {
441
+ createWorkflow: (): { invoke: jest.Mock } => ({
442
+ invoke: jest.fn().mockResolvedValue({
443
+ messages: [new AIMessage('child done')],
444
+ }),
445
+ }),
446
+ clearHeavyState: jest.fn(),
447
+ } as unknown as StandardGraph;
448
+ },
449
+ });
450
+
451
+ await executor.execute({
452
+ description: 'Research this topic',
453
+ subagentType: 'researcher',
454
+ });
455
+
456
+ expect(observedLangfuse).toBe(langfuse);
457
+ });
458
+
427
459
  it('returns error message when child graph throws', async () => {
428
460
  const executor = createExecutor({
429
461
  createChildGraph: makeThrowingGraphFactory(
@@ -0,0 +1,41 @@
1
+ const mockWithLangfuseToolOutputTracingConfig = jest.fn(
2
+ (_runLangfuse: unknown, action: () => unknown, _agentLangfuse: unknown) =>
3
+ action()
4
+ );
5
+
6
+ jest.mock('@/langfuseToolOutputTracing', () => ({
7
+ ...jest.requireActual('@/langfuseToolOutputTracing'),
8
+ withLangfuseToolOutputTracingConfig: mockWithLangfuseToolOutputTracingConfig,
9
+ }));
10
+
11
+ import { ToolNode } from '../ToolNode';
12
+
13
+ describe('ToolNode Langfuse redaction context', () => {
14
+ beforeEach(() => {
15
+ mockWithLangfuseToolOutputTracingConfig.mockClear();
16
+ });
17
+
18
+ it('scopes ToolNode invocation with run and agent Langfuse config', async () => {
19
+ const runLangfuse = {
20
+ toolOutputTracing: { enabled: true },
21
+ };
22
+ const agentLangfuse = {
23
+ toolOutputTracing: { enabled: false },
24
+ };
25
+ const node = new ToolNode({
26
+ tools: [],
27
+ runLangfuse,
28
+ agentLangfuse,
29
+ });
30
+
31
+ await expect(node.invoke([])).rejects.toThrow(
32
+ 'ToolNode only accepts AIMessages'
33
+ );
34
+
35
+ expect(mockWithLangfuseToolOutputTracingConfig).toHaveBeenCalledWith(
36
+ runLangfuse,
37
+ expect.any(Function),
38
+ agentLangfuse
39
+ );
40
+ });
41
+ });
@@ -213,6 +213,7 @@ export type SubagentExecutorOptions = {
213
213
  hookRegistry?: HookRegistry;
214
214
  parentRunId: string;
215
215
  parentAgentId?: string;
216
+ langfuse?: StandardGraphInput['langfuse'];
216
217
  tokenCounter?: TokenCounter;
217
218
  /** Remaining nesting budget. 0 or negative blocks execution. */
218
219
  maxDepth?: number;
@@ -243,6 +244,7 @@ export class SubagentExecutor {
243
244
  private readonly hookRegistry?: HookRegistry;
244
245
  private readonly parentRunId: string;
245
246
  private readonly parentAgentId?: string;
247
+ private readonly langfuse?: StandardGraphInput['langfuse'];
246
248
  private readonly tokenCounter?: TokenCounter;
247
249
  private readonly maxDepth: number;
248
250
  private readonly createChildGraph: ChildGraphFactory;
@@ -256,6 +258,7 @@ export class SubagentExecutor {
256
258
  this.hookRegistry = options.hookRegistry;
257
259
  this.parentRunId = options.parentRunId;
258
260
  this.parentAgentId = options.parentAgentId;
261
+ this.langfuse = options.langfuse;
259
262
  this.tokenCounter = options.tokenCounter;
260
263
  this.maxDepth = options.maxDepth ?? 1;
261
264
  this.createChildGraph = options.createChildGraph;
@@ -352,18 +355,20 @@ export class SubagentExecutor {
352
355
  runId: childRunId,
353
356
  signal: this.parentSignal,
354
357
  agents: [childInputs],
358
+ langfuse: this.langfuse,
355
359
  tokenCounter: this.tokenCounter,
356
360
  });
357
361
 
358
- const forwarding = forwardingEnabled
359
- ? this.createForwarderCallback({
362
+ let forwarding: ForwarderCallback | undefined;
363
+ if (forwardingEnabled) {
364
+ forwarding = this.createForwarderCallback({
360
365
  parentRegistry: parentRegistry!,
361
366
  subagentType,
362
367
  subagentAgentId: childAgentId,
363
368
  childRunId,
364
369
  parentToolCallId,
365
- })
366
- : undefined;
370
+ });
371
+ }
367
372
  const forwarder = forwarding?.handler;
368
373
 
369
374
  if (forwarder) {
@@ -444,7 +449,7 @@ export class SubagentExecutor {
444
449
  );
445
450
  } catch (error) {
446
451
  const errorMessage = truncateErrorMessage(error);
447
- if (forwarder) {
452
+ if (forwarding) {
448
453
  await forwarding.drain();
449
454
  await this.emitSubagentUpdate(parentRegistry!, {
450
455
  childRunId,
@@ -491,7 +496,7 @@ export class SubagentExecutor {
491
496
  });
492
497
  }
493
498
 
494
- if (forwarder) {
499
+ if (forwarding) {
495
500
  await forwarding.drain();
496
501
  await this.emitSubagentUpdate(parentRegistry!, {
497
502
  childRunId,
@@ -295,6 +295,7 @@ export type StandardGraphInput = {
295
295
  runId?: string;
296
296
  signal?: AbortSignal;
297
297
  agents: AgentInputs[];
298
+ langfuse?: LangfuseConfig;
298
299
  tokenCounter?: TokenCounter;
299
300
  indexTokenCountMap?: Record<string, number>;
300
301
  calibrationRatio?: number;
@@ -408,11 +409,42 @@ export interface SubagentUpdateEvent {
408
409
  timestamp: string;
409
410
  }
410
411
 
412
+ export type LangfuseToolOutputTracingConfig = {
413
+ /**
414
+ * Whether tool outputs should be exported to Langfuse. Defaults to
415
+ * `true`. Set to `false` to keep tool spans and redact their output.
416
+ */
417
+ enabled?: boolean;
418
+ /**
419
+ * Optional allowlist of tool names whose outputs should be redacted even
420
+ * when `enabled` is true.
421
+ */
422
+ redactedToolNames?: string[];
423
+ /**
424
+ * Match strategy for `redactedToolNames`. Defaults to `exact`; use
425
+ * `partial` to redact tools whose names contain a configured value.
426
+ */
427
+ redactedToolNameMatchMode?: 'exact' | 'partial';
428
+ /** Replacement text used for redacted tool outputs. */
429
+ redactionText?: string;
430
+ };
431
+
432
+ export type LangfuseToolNodeTracingConfig = {
433
+ /**
434
+ * Overrides ToolNode callback tracing. ToolNode spans are exported by the
435
+ * env-backed Langfuse callback, so this only enables tracing when that
436
+ * callback is configured.
437
+ */
438
+ enabled?: boolean;
439
+ };
440
+
411
441
  export interface LangfuseConfig {
412
442
  enabled?: boolean;
413
443
  publicKey?: string;
414
444
  secretKey?: string;
415
445
  baseUrl?: string;
446
+ toolNodeTracing?: LangfuseToolNodeTracingConfig;
447
+ toolOutputTracing?: LangfuseToolOutputTracingConfig;
416
448
  }
417
449
 
418
450
  export interface AgentInputs {
package/src/types/run.ts CHANGED
@@ -118,6 +118,12 @@ export type StandardGraphConfig = Omit<
118
118
  export type RunConfig = {
119
119
  runId: string;
120
120
  graphConfig: LegacyGraphConfig | StandardGraphConfig | MultiAgentGraphConfig;
121
+ /**
122
+ * Run-scoped Langfuse configuration. Per-agent `AgentInputs.langfuse`
123
+ * takes precedence for agent-specific spans; this object supplies defaults
124
+ * for run-wide tracing controls such as tool-output redaction.
125
+ */
126
+ langfuse?: g.LangfuseConfig;
121
127
  customHandlers?: Record<string, g.EventHandler>;
122
128
  /**
123
129
  * Pre-constructed hook registry for this run. Hooks fire at lifecycle
@@ -6,6 +6,7 @@ import type { HookRegistry } from '@/hooks';
6
6
  import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
7
7
  import type { MessageContentComplex, ToolErrorData } from './stream';
8
8
  import type { HumanInTheLoopConfig } from './hitl';
9
+ import type { LangfuseConfig } from './graph';
9
10
 
10
11
  /** Replacement type for `import type { ToolCall } from '@langchain/core/messages/tool'` in order to have stringified args typed */
11
12
  export type CustomToolCall = {
@@ -69,6 +70,12 @@ export type EagerEventToolCallChunkState = {
69
70
  export type ToolNodeOptions = {
70
71
  name?: string;
71
72
  tags?: string[];
73
+ /** Enables LangChain/LangGraph tracing for this ToolNode. Defaults to false. */
74
+ trace?: boolean;
75
+ /** Run-level Langfuse config used to scope ToolNode trace redaction. */
76
+ runLangfuse?: LangfuseConfig;
77
+ /** Agent-level Langfuse config used to scope ToolNode trace redaction. */
78
+ agentLangfuse?: LangfuseConfig;
72
79
  handleToolErrors?: boolean;
73
80
  loadRuntimeTools?: ToolRefGenerator;
74
81
  toolCallStepIds?: Map<string, string>;