Files
Danny AvilaandGitHub 1ea5528806 fix: preserve image blocks in ensureThinkingBlockInMessages to prevent 174× token amplification (#71)
* fix: message formatting for tool-call sequences

- Introduced a new function, buildToolSequenceContent, to construct structured content arrays from tool-call sequences, preserving image content as native images.
- Updated ensureThinkingBlockInMessages to utilize the new function, ensuring proper handling of structured content and avoiding unnecessary serialization of binary data.
- Removed the deprecated getBufferString function call to streamline message processing.

* docs: add AGENTS.md and CLAUDE.md for project guidelines and documentation

- Created AGENTS.md to provide an overview of the @librechat/agents library, including project structure, code style guidelines, development commands, testing philosophy, and formatting rules.
- Added CLAUDE.md to reference AGENTS.md for comprehensive project instructions and guidelines.

* test: enhance ensureThinkingBlockInMessages tests and add content extraction utility

- Introduced a new helper function, getTextContent, to extract text from message content, improving readability and maintainability of tests.
- Updated existing tests in ensureThinkingBlockInMessages to utilize getTextContent for verifying message content, ensuring consistent handling of both string and structured array formats.
- Added new tests to verify the preservation of image content and prevent unnecessary serialization of large base64 data, addressing token amplification issues.

* chore: update ESLint configuration and refactor role assignment logic

- Added a new ESLint rule, "no-nested-ternary", set to warn, to improve code clarity.
- Refactored role assignment logic in buildToolSequenceContent to enhance readability by replacing nested ternary operators with if-else statements.

* refactor: streamline message content handling in tool sequences

- Introduced new functions, appendMessageContent and flushTextChunks, to improve the organization and readability of message content processing.
- Updated ensureThinkingBlockInMessages to utilize the new functions, ensuring efficient handling of text and image content while preventing unnecessary serialization of binary data.
- Enhanced role assignment logic and content accumulation for better clarity and maintainability.

* refactor: enhance message content handling for image and tool blocks

- Renamed IMAGE_TYPES to IMAGE_BLOCK_TYPES for clarity in identifying block types containing binary image data.
- Introduced isToolMessage function to streamline the identification of tool-role messages.
- Improved appendMessageContent logic to ensure proper handling of image blocks and unrecognized types, preserving context without unnecessary serialization.
- Updated ensureThinkingBlockInMessages to utilize the new isToolMessage function for better readability.

* test: enhance ensureThinkingBlockInMessages tests for content handling

- Updated getTextContent function to accept both string and structured array formats for improved type handling.
- Refined tests to ensure base64 images are preserved as structured blocks and not serialized as text, addressing token amplification issues.
- Added new tests to verify the correct serialization of tool messages and prevent double-serialization of content, ensuring clarity in message processing.

* refactor: improve tool_calls handling in message content processing

- Updated appendMessageContent to ensure tool_calls are only serialized when content does not include tool_use blocks, preventing double serialization.
- Introduced a flag to track the presence of tool_use blocks, enhancing clarity in message processing logic.
- Added fallback logic to handle cases where content arrays are empty but tool_calls are populated, ensuring robust message formatting.

* test: add case for tool_calls serialization with empty content array

- Introduced a new test to verify that tool_calls are correctly serialized when the content array is empty, ensuring robust handling of message formatting.
- Updated existing tests to clarify the serialization behavior of tool_use blocks and prevent double serialization, enhancing overall test coverage for message processing.
2026-03-12 00:11:05 -04:00

5.9 KiB

@librechat/agents

Project Overview

@librechat/agents is a TypeScript library for LLM agent orchestration — tool calling, multi-agent graphs, message formatting, streaming, and provider abstraction (Anthropic, Bedrock, VertexAI, OpenAI, Google). Published as @librechat/agents on npm. This is a major backend dependency of LibreChat (same team).

Path Purpose
src/messages/ Message formatting, caching, content processing
src/graphs/ LangGraph-based agent graphs (single + multi-agent)
src/llm/ Provider-specific LLM wrappers and utilities
src/tools/ Tool definitions and search
src/agents/ Agent definitions and handoff logic
src/types/ Shared TypeScript types
src/common/ Enums, constants
src/run.ts Main run orchestration
src/stream.ts Streaming logic

Code Style

Structure and Clarity

  • Never-nesting: early returns, flat code, minimal indentation. Break complex operations into well-named helpers.
  • Functional first: pure functions, immutable data, map/filter/reduce over imperative loops. Only reach for OOP when it clearly improves domain modeling or state encapsulation.
  • No dynamic imports unless absolutely necessary.

DRY

  • Extract repeated logic into utility functions.
  • Parameterized helpers instead of near-duplicate functions.
  • Constants for repeated values; configuration objects over duplicated init code.
  • Shared validators, centralized error handling, single source of truth for business rules.
  • Shared typing system with interfaces/types extending common base definitions.
  • Abstraction layers for external API interactions.

Iteration and Performance

  • Minimize looping — especially over shared data structures like message arrays, which are iterated frequently throughout the codebase. Every additional pass adds up at scale.
  • Consolidate sequential O(n) operations into a single pass whenever possible; never loop over the same collection twice if the work can be combined.
  • Choose data structures that reduce the need to iterate (e.g., Map/Set for lookups instead of Array.find/Array.includes).
  • Avoid unnecessary object creation; consider space-time tradeoffs.
  • Prevent memory leaks: careful with closures, dispose resources/event listeners, no circular references.

Type Safety

  • Never use any. Explicit types for all parameters, return values, and variables.
  • Limit unknown — avoid unknown, Record<string, unknown>, and as unknown as T assertions. A Record<string, unknown> almost always signals a missing explicit type definition.
  • Don't duplicate types — before defining a new type, check whether it already exists in src/types/. Reuse and extend existing types (MessageContentComplex, ExtendedMessageContent, TMessage, etc.) rather than creating redundant definitions.
  • Use union types, generics, and interfaces appropriately.
  • All TypeScript and ESLint warnings/errors must be addressed — do not leave unresolved diagnostics.

Comments and Documentation

  • Write self-documenting code; no inline comments narrating what code does.
  • JSDoc only for complex/non-obvious logic or intellisense on public APIs.
  • Single-line JSDoc for brief docs, multi-line for complex cases.
  • Avoid standalone // comments unless absolutely necessary.

Import Order

Imports are organized into three sections:

  1. Package imports — sorted shortest to longest line length.
  2. import type imports — sorted longest to shortest (package types first, then local types; length resets between sub-groups).
  3. Local/project imports — sorted longest to shortest.

Multi-line imports count total character length across all lines. Consolidate value imports from the same module. Always use standalone import type { ... } — never inline type inside value imports.

JS/TS Loop Preferences

  • Limit looping as much as possible. Prefer single-pass transformations and avoid re-iterating the same data.
  • for (let i = 0; ...) for performance-critical or index-dependent operations.
  • for...of for simple array iteration.
  • for...in only for object property enumeration.

Development Commands

Command Purpose
npm run build Build CJS + ESM + types via Rollup
npx jest <pattern> Run tests matching pattern
npx tsc --noEmit Type-check without emitting
npx eslint src/ Lint source files
  • Package manager: Yarn (.yarn/ directory, yarn.lock)
  • TypeScript with path aliases: @/* → src/*
  • Test framework: Jest with ts-jest

Testing

  • Framework: Jest, run from project root: npx jest <pattern>.
  • Test files: *.test.ts and *.spec.ts under src/.

Philosophy

  • Real logic over mocks. Exercise actual code paths with real dependencies. Mocking is a last resort.
  • Spies over mocks. Assert that real functions are called with expected arguments and frequency without replacing underlying logic.
  • MCP: use real @modelcontextprotocol/sdk exports for servers, transports, and tool definitions. Mirror real scenarios, don't stub SDK internals.
  • Only mock what you cannot control: external HTTP APIs, rate-limited services, non-deterministic system calls.
  • Heavy mocking is a code smell, not a testing strategy.

Formatting

Fix all formatting lint errors (trailing spaces, tabs, newlines, indentation) using auto-fix when available. All TypeScript/ESLint warnings and errors must be resolved.