@librechat/agents 3.1.97 → 3.1.99

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 (49) hide show
  1. package/dist/cjs/graphs/Graph.cjs +6 -0
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/langfuseToolOutputTracing.cjs +16 -5
  4. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/index.cjs +10 -0
  6. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  7. package/dist/cjs/llm/bedrock/toolCache.cjs +125 -0
  8. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -0
  9. package/dist/cjs/messages/cache.cjs +17 -9
  10. package/dist/cjs/messages/cache.cjs.map +1 -1
  11. package/dist/cjs/messages/prune.cjs +45 -8
  12. package/dist/cjs/messages/prune.cjs.map +1 -1
  13. package/dist/cjs/tools/ToolNode.cjs +6 -1
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/esm/graphs/Graph.mjs +6 -0
  16. package/dist/esm/graphs/Graph.mjs.map +1 -1
  17. package/dist/esm/langfuseToolOutputTracing.mjs +16 -5
  18. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  19. package/dist/esm/llm/bedrock/index.mjs +10 -0
  20. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  21. package/dist/esm/llm/bedrock/toolCache.mjs +122 -0
  22. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -0
  23. package/dist/esm/messages/cache.mjs +17 -9
  24. package/dist/esm/messages/cache.mjs.map +1 -1
  25. package/dist/esm/messages/prune.mjs +45 -8
  26. package/dist/esm/messages/prune.mjs.map +1 -1
  27. package/dist/esm/tools/ToolNode.mjs +6 -1
  28. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  29. package/dist/types/llm/bedrock/index.d.ts +16 -0
  30. package/dist/types/llm/bedrock/toolCache.d.ts +4 -0
  31. package/dist/types/messages/cache.d.ts +2 -2
  32. package/dist/types/types/llm.d.ts +2 -2
  33. package/package.json +1 -1
  34. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +332 -0
  35. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +504 -0
  36. package/src/graphs/Graph.ts +14 -0
  37. package/src/langfuseToolOutputTracing.ts +26 -7
  38. package/src/llm/bedrock/index.ts +32 -1
  39. package/src/llm/bedrock/llm.spec.ts +154 -1
  40. package/src/llm/bedrock/toolCache.test.ts +131 -0
  41. package/src/llm/bedrock/toolCache.ts +191 -0
  42. package/src/messages/cache.test.ts +97 -38
  43. package/src/messages/cache.ts +18 -10
  44. package/src/messages/prune.ts +55 -17
  45. package/src/specs/langfuse-tool-output-tracing.test.ts +28 -0
  46. package/src/specs/prune.test.ts +193 -0
  47. package/src/tools/ToolNode.ts +7 -1
  48. package/src/tools/__tests__/ToolNode.langfuse.test.ts +6 -0
  49. package/src/types/llm.ts +2 -2
@@ -2382,3 +2382,196 @@ describe('thinking enabled — tail tool_use without a thinking block (issue #11
2382
2382
  expect(result.thinkingStartIndex).toBeGreaterThanOrEqual(0);
2383
2383
  });
2384
2384
  });
2385
+
2386
+ describe('thinking enabled — non-Anthropic reasoning_content blocks (issue #191)', () => {
2387
+ it('locates a trailing reasoning_content block even when reasoningType defaults to THINKING (DeepSeek/Qwen)', () => {
2388
+ // DeepSeek-R1 and DashScope/Qwen-thinking route through the non-Bedrock
2389
+ // branch, so the caller passes reasoningType: THINKING — but their blocks
2390
+ // are tagged `reasoning_content` and are not normalized upstream. With a
2391
+ // system prompt at index 0 and an all-AI/tool tail, the consume loop never
2392
+ // pops a human to clear thinkingEndIndex (the issue #116 escape hatch), so
2393
+ // searching only for `thinking` missed the present block and threw a fatal
2394
+ // that permanently bricked the thread. The pruner must find the block by
2395
+ // its actual shape instead.
2396
+ const tokenCounter = createTestTokenCounter();
2397
+ const messages: BaseMessage[] = [
2398
+ new SystemMessage('you are a helpful assistant'),
2399
+ new AIMessage({
2400
+ content: [
2401
+ {
2402
+ type: ContentTypes.REASONING_CONTENT,
2403
+ reasoningText: {
2404
+ text: 'I will fetch the doc',
2405
+ signature: 'sig-new',
2406
+ },
2407
+ },
2408
+ {
2409
+ type: 'tool_use',
2410
+ id: 'tc_get_doc',
2411
+ name: 'get_doc_content',
2412
+ input: { docId: 'abc' },
2413
+ },
2414
+ ],
2415
+ tool_calls: [
2416
+ {
2417
+ id: 'tc_get_doc',
2418
+ name: 'get_doc_content',
2419
+ args: { docId: 'abc' },
2420
+ type: 'tool_call',
2421
+ },
2422
+ ],
2423
+ }),
2424
+ new ToolMessage({
2425
+ content: 'c'.repeat(6000),
2426
+ tool_call_id: 'tc_get_doc',
2427
+ name: 'get_doc_content',
2428
+ }),
2429
+ ];
2430
+
2431
+ const indexTokenCountMap: Record<string, number | undefined> = {};
2432
+ for (let i = 0; i < messages.length; i++) {
2433
+ indexTokenCountMap[i] = tokenCounter(messages[i]);
2434
+ }
2435
+
2436
+ let result: ReturnType<typeof realGetMessagesWithinTokenLimit> | undefined;
2437
+ expect(() => {
2438
+ result = realGetMessagesWithinTokenLimit({
2439
+ messages,
2440
+ maxContextTokens: 200,
2441
+ indexTokenCountMap,
2442
+ thinkingEnabled: true,
2443
+ tokenCounter,
2444
+ reasoningType: ContentTypes.THINKING,
2445
+ });
2446
+ }).not.toThrow();
2447
+
2448
+ // thinkingStartIndex is only set when the reasoning block is actually
2449
+ // located — isolating the find fix (B) from the graceful-degradation
2450
+ // safety net (C), which would swallow the throw without finding anything.
2451
+ expect(result!.thinkingStartIndex).toBeGreaterThanOrEqual(0);
2452
+ });
2453
+
2454
+ it('does not throw when a carried-over thinking sequence has no locatable block', () => {
2455
+ // Models a stale runThinkingStartIndex carry-over pointing at an assistant
2456
+ // message that has no reasoning block. The pruner cannot find a block, but
2457
+ // a trailing AI/tool sequence keeps thinkingEndIndex set, so it used to
2458
+ // reach the fatal "no thinking block found" throw. Defense in depth: a
2459
+ // misconfiguration upstream of the pruner must not be able to brick the
2460
+ // thread — degrade to the partially-pruned context instead.
2461
+ const tokenCounter = createTestTokenCounter();
2462
+ const messages: BaseMessage[] = [
2463
+ new HumanMessage('h'.repeat(100)),
2464
+ new AIMessage({
2465
+ content: [{ type: 'text', text: 'a reply with no reasoning block' }],
2466
+ }),
2467
+ new HumanMessage('please read the doc'),
2468
+ new AIMessage({
2469
+ content: [
2470
+ {
2471
+ type: 'tool_use',
2472
+ id: 'tc_get_doc',
2473
+ name: 'get_doc_content',
2474
+ input: { docId: 'abc' },
2475
+ },
2476
+ ],
2477
+ tool_calls: [
2478
+ {
2479
+ id: 'tc_get_doc',
2480
+ name: 'get_doc_content',
2481
+ args: { docId: 'abc' },
2482
+ type: 'tool_call',
2483
+ },
2484
+ ],
2485
+ }),
2486
+ new ToolMessage({
2487
+ content: 'x'.repeat(150),
2488
+ tool_call_id: 'tc_get_doc',
2489
+ name: 'get_doc_content',
2490
+ }),
2491
+ ];
2492
+
2493
+ const indexTokenCountMap: Record<string, number | undefined> = {};
2494
+ for (let i = 0; i < messages.length; i++) {
2495
+ indexTokenCountMap[i] = tokenCounter(messages[i]);
2496
+ }
2497
+
2498
+ let result: ReturnType<typeof realGetMessagesWithinTokenLimit> | undefined;
2499
+ expect(() => {
2500
+ result = realGetMessagesWithinTokenLimit({
2501
+ messages,
2502
+ maxContextTokens: 200,
2503
+ indexTokenCountMap,
2504
+ thinkingEnabled: true,
2505
+ tokenCounter,
2506
+ thinkingStartIndex: 1,
2507
+ reasoningType: ContentTypes.THINKING,
2508
+ });
2509
+ }).not.toThrow();
2510
+
2511
+ expect(result!.context.length).toBeGreaterThan(0);
2512
+ expect(result!.messagesToRefine.length).toBeGreaterThan(0);
2513
+ // The stale carried-over index must NOT be propagated: createPruneMessages
2514
+ // persists it as runThinkingStartIndex, and a stale value would suppress
2515
+ // the trailing scan on later turns and miss a real reasoning block.
2516
+ expect(result!.thinkingStartIndex).toBeUndefined();
2517
+ });
2518
+
2519
+ it('does not match an Anthropic thinking block for a Bedrock (reasoning_content) run', () => {
2520
+ // The cross-type fallback is one-directional: REASONING_CONTENT (Bedrock)
2521
+ // must not match a `thinking` block, since the Bedrock input converter
2522
+ // rejects `thinking` blocks and reattaching one would break the request.
2523
+ const tokenCounter = createTestTokenCounter();
2524
+ const messages: BaseMessage[] = [
2525
+ new SystemMessage('you are a helpful assistant'),
2526
+ new AIMessage({
2527
+ content: [
2528
+ {
2529
+ type: ContentTypes.THINKING,
2530
+ thinking: 'inherited Anthropic-style reasoning',
2531
+ signature: 'sig-anthropic',
2532
+ },
2533
+ {
2534
+ type: 'tool_use',
2535
+ id: 'tc_get_doc',
2536
+ name: 'get_doc_content',
2537
+ input: { docId: 'abc' },
2538
+ },
2539
+ ],
2540
+ tool_calls: [
2541
+ {
2542
+ id: 'tc_get_doc',
2543
+ name: 'get_doc_content',
2544
+ args: { docId: 'abc' },
2545
+ type: 'tool_call',
2546
+ },
2547
+ ],
2548
+ }),
2549
+ new ToolMessage({
2550
+ content: 'c'.repeat(6000),
2551
+ tool_call_id: 'tc_get_doc',
2552
+ name: 'get_doc_content',
2553
+ }),
2554
+ ];
2555
+
2556
+ const indexTokenCountMap: Record<string, number | undefined> = {};
2557
+ for (let i = 0; i < messages.length; i++) {
2558
+ indexTokenCountMap[i] = tokenCounter(messages[i]);
2559
+ }
2560
+
2561
+ let result: ReturnType<typeof realGetMessagesWithinTokenLimit> | undefined;
2562
+ expect(() => {
2563
+ result = realGetMessagesWithinTokenLimit({
2564
+ messages,
2565
+ maxContextTokens: 200,
2566
+ indexTokenCountMap,
2567
+ thinkingEnabled: true,
2568
+ tokenCounter,
2569
+ reasoningType: ContentTypes.REASONING_CONTENT,
2570
+ });
2571
+ }).not.toThrow();
2572
+
2573
+ // The thinking block is intentionally not located for a Bedrock run, so no
2574
+ // index is reported and nothing gets reattached.
2575
+ expect(result!.thinkingStartIndex).toBeUndefined();
2576
+ });
2577
+ });
@@ -103,6 +103,8 @@ type RunToolBatchContext = {
103
103
  additionalContextsSink?: string[];
104
104
  };
105
105
 
106
+ const TOOL_NODE_RUN_NAME = 'tool_batch';
107
+
106
108
  /**
107
109
  * Per-batch context for `dispatchToolEvents` / `executeViaEvent`.
108
110
  * Mirrors {@link RunToolBatchContext} for the event-driven path,
@@ -500,7 +502,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
500
502
  toolExecution,
501
503
  fileCheckpointer,
502
504
  }: t.ToolNodeConstructorParams) {
503
- super({ name, tags, func: (input, config) => this.run(input, config) });
505
+ super({
506
+ name: name ?? TOOL_NODE_RUN_NAME,
507
+ tags,
508
+ func: (input, config) => this.run(input, config),
509
+ });
504
510
  this.trace = trace ?? this.trace;
505
511
  this.runLangfuse = runLangfuse;
506
512
  this.agentLangfuse = agentLangfuse;
@@ -15,6 +15,12 @@ describe('ToolNode Langfuse redaction context', () => {
15
15
  mockWithLangfuseToolOutputTracingConfig.mockClear();
16
16
  });
17
17
 
18
+ it('uses a stable default run name for tracing', () => {
19
+ const node = new ToolNode({ tools: [] });
20
+
21
+ expect(node.name).toBe('tool_batch');
22
+ });
23
+
18
24
  it('scopes ToolNode invocation with run and agent Langfuse config', async () => {
19
25
  const runLangfuse = {
20
26
  toolOutputTracing: { enabled: true },
package/src/types/llm.ts CHANGED
@@ -87,7 +87,7 @@ export type BedrockAnthropicInput = ChatBedrockConverseInput & {
87
87
  AnthropicReasoning;
88
88
  promptCache?: boolean;
89
89
  };
90
- export type BedrockConverseClientOptions = ChatBedrockConverseInput;
90
+ export type BedrockConverseClientOptions = BedrockAnthropicInput;
91
91
  export type BedrockAnthropicClientOptions = BedrockAnthropicInput;
92
92
  export type GoogleClientOptions = GoogleGenerativeAIChatInput & {
93
93
  customHeaders?: RequestOptions['customHeaders'];
@@ -128,7 +128,7 @@ export type ProviderOptionsMap = {
128
128
  [Providers.MISTRALAI]: MistralAIClientOptions;
129
129
  [Providers.MISTRAL]: MistralAIClientOptions;
130
130
  [Providers.OPENROUTER]: ChatOpenRouterCallOptions;
131
- [Providers.BEDROCK]: BedrockConverseClientOptions;
131
+ [Providers.BEDROCK]: BedrockAnthropicClientOptions;
132
132
  [Providers.XAI]: XAIClientOptions;
133
133
  [Providers.MOONSHOT]: OpenAIClientOptions;
134
134
  };