lemmy LLM Client Library and Tools

repository·main·Indexed 23 days ago

https://github.com/badlogic/lemmy

A TypeScript API wrapper for multiple LLM providers. The ecosystem includes lemmy-chat for interactive and one-shot messaging with a TUI, claude-bridge for using non-Anthropic models with Claude Code via API interception, and claude-trace for recording and indexing Claude Code sessions. It also provides @mariozechner/lemmy-tools for file system, shell, and Model Context Protocol (MCP) integration.

Tokens
54.8K
Snippets
116
Records
316
Agent score
80%

What's inside lemmy

  1. Overview of the Lemmy Ecosystem

    main
    Lemmy is a TypeScript monorepo ecosystem designed for building AI applications. It provides a unified interface for multiple LLM providers (Anthropic Claude, OpenAI, and Google Gemini), a rich terminal UI framework with differential rendering, and development tools for monitoring AI interactions. It also supports MCP (Model Context Protocol) integration and provides a bridge to use alternative LLMs with Claude-specific tools.
  2. Core Architecture and Design Principles

    main

    The Lemmy architecture is built around several key abstractions and principles:

    • Provider Abstraction: Uses a ChatClient interface to unify different LLM providers.
    • Tool Definitions: Tools are defined using Zod schemas for type-safe validation.
    • Streaming Support: Supports streaming responses, including specialized handling for thinking/reasoning blocks.
    • Context Management: Built-in mechanisms for managing conversation state.
    • Type Safety: The project enforces strict typing throughout to avoid the use of any.
  3. Use multiple LLM providers with the same context

    main

    Because Context is provider-agnostic, you can switch between different LLM providers (Anthropic, OpenAI, Google) mid-conversation while maintaining the same conversation history and tool definitions.

    // Switch providers mid-conversation
    const openai = lemmy.openai({
    	apiKey: "sk-...",
    	model: "gpt-4o",
    });
    
    const google = lemmy.google({
    	apiKey: "...",
    	model: "gemini-1.5-pro",
    });
    
    // Same context works across all providers
    await claude.ask("Start a story", { context });
    await openai.ask("Continue the story", { context });
    await google.ask("End the story", { context });
  4. How differential rendering works

    main

    Differential rendering optimizes performance by only updating parts of the screen that have changed.

    Components return a result object: {lines: string[], changed: boolean, keepLines?: number}.

    • lines: The full set of lines the component should display.
    • changed: A boolean indicating if the content has changed.
    • keepLines: (For Containers) The number of lines from the top that remain unchanged.

    The TUI calculates the total unchanged lines, moves the cursor up by the difference, clears the remaining area using \x1b[0J, and prints only the new lines. Note: Do not add extra cursor positioning after printing, as it interferes with terminal scrolling.

  5. Package Structure and Modules

    main

    The monorepo is organized into the following key packages and applications:

    • @packages/lemmy: The core LLM wrapper library.
    • @packages/lemmy-tui: Terminal UI components.
    • @packages/lemmy-tools: A collection of built-in and custom tools.
    • @packages/lemmy-cli-args: CLI argument parsing utilities.
    • apps/: Contains example applications such as chat, bridge, and trace.
  6. Understand claude-trace request filtering

    main

    By default, claude-trace filters logs to focus on substantial conversations to reduce file size and noise:

    • Default behavior: Only logs requests to /v1/messages with more than 2 messages in the conversation.
    • With --include-all-requests: Logs all requests made to api.anthropic.com, including single-message requests and other endpoints.
  7. Manage conversation context with Context

    main

    Use the Context class to maintain conversation history and system messages across multiple turns. The Context object also automatically tracks usage costs, which can be retrieved via .getTotalCost().

    // Maintain context across multiple messages
    const context = new Context();
    context.setSystemMessage("You are a helpful coding assistant.");
    
    await claude.ask("My name is Alice", { context });
    const result = await claude.ask("What's my name?", { context });
    // "Your name is Alice"
    
    // Track costs automatically
    console.log(`Total cost: $${context.getTotalCost()}`);
  8. How claude-bridge works

    main

    Claude Code is designed to work only with Anthropic models. claude-bridge enables other models by performing the following steps:

    1. Spawn: It starts Claude Code as a subprocess using a custom Node.js loader.
    2. Patch: It patches the global fetch() function to intercept requests sent to api.anthropic.com/v1/messages.
    3. Transform: It converts Anthropic-formatted requests into a unified lemmy format, then into the specific provider's API format.
    4. Stream: It converts the provider's response back into Anthropic's Server-Sent Events (SSE) format and streams it back to Claude Code.
  9. Understand claude-bridge limitations

    main

    Because claude-bridge intercepts and transforms requests, some features are not fully supported:

    Completely Broken:

    • Token usage/cost reporting: Claude Code's displays will show incorrect data.
    • Image uploads: Drag/drop, pasting, or file paths will not work as they rely on Anthropic's servers.
    • Input caching: Prompt caching is not implemented.
    • Web search/fetch tools: These rely on Anthropic-specific logic.

    Somewhat Janky:

    • Model-specific features: Features like Claude's 'artifacts' or GPT's reasoning modes may not translate.
    • Thinking/reasoning output: Formatting may differ between providers.
    • Error messages: Provider-specific auth failures might result in cryptic errors.
    • Tool schemas: Conversion between JSON Schema and Zod is used and usually works, but can occasionally fail.
    • Streaming behavior: Subtle differences may exist despite SSE conversion.
  10. Spawning Claude via Node.js

    main

    When running Claude with interception or extracting tokens, the claude-trace app uses a specific spawning pattern. Instead of executing the path directly, it uses Node.js to execute the file, which allows it to handle symlinks that point to JavaScript files.

    Pattern: spawn("node", ["--require", loaderPath, claudePath, ...claudeArgs])

    Warning: This pattern requires claudePath to be a valid JavaScript file (or a symlink to one). If claudePath points to a bash wrapper script, the spawn command will fail with error code -1 because Node.js cannot execute bash scripts.

  11. Understand claude-trace log file naming and location

    main

    When running the claude-trace app, logs are stored in a .claude-trace/ directory. The filenames are generated using a timestamp based on the current ISO date, where colons, dots, and the 'T' separator are replaced with hyphens.

    Log files follow the pattern: log-YYYY-MM-DD-HH-MM-SS.

    Two types of log files are produced:

    1. .jsonl files: Machine-readable JSON Lines format.
    2. .html files: Human-readable HTML format.

    Note: While the console output may display a placeholder pattern like log-YYYY-MM-DD-HH-MM-SS.{jsonl,html}, the actual files will contain the specific timestamp of the session.

    // Example of how the timestamp is constructed internally
    const timestamp = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, -5);
    
    this.logFile = path.join(this.logDir, `log-${timestamp}.jsonl`);
    this.htmlFile = path.join(this.logDir, `log-${timestamp}.html`);
  12. Develop claude-bridge

    main

    To develop claude-bridge, clone the lemmy repository and set up the monorepo environment.

    Setup

    git clone https://github.com/badlogic/lemmy
    cd lemmy && npm install && npm run dev

    Running npm run dev starts compilation in watch mode for all packages and apps.

    Testing

    Use the following commands to run specific test suites:

    • npm run test:all: All tests.
    • npm run test:unit: Unit tests.
    • npm run test:core: CLI functionality.
    • npm run test:tools: Tool integration.
    • npm run test:providers: Multi-provider tests.

    Debugging in VS Code

    To debug using a JavaScript Debug Terminal, use the --patch-claude flag:

    npx tsx src/cli.ts <arguments> --patch-claude