npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tul-sdk

v0.1.0

Published

Claude-level tool calling for Gemini. Fewer tokens. Zero config.

Downloads

110

Readme

Tul

Claude-level tool calling for Gemini. Fewer tokens. Zero config.

Tul is a lightweight middleware library that brings Claude's superior tool-calling patterns to Google's Gemini models. It reduces token consumption, prevents common failure modes, and makes your Gemini agents significantly more reliable.

Why Tul Exists

While Gemini is powerful, its tool-calling behavior differs from Claude's in ways that impact real-world agents:

| Problem | Claude | Gemini | Tul | |---------|--------|--------|-----| | Token bloat | Efficient schemas | Verbose function definitions | Smart compression, filtering | | Malformed JSON | Rare | Occasional | Auto-repair with jsonrepair | | Tool loops | Built-in detection | None | Configurable loop detection | | Schema validation | Strict mode available | No enforcement | Strict validation with retry | | Examples | Supports tool examples | Limited | Example injection system | | Thought signatures | N/A | Gemini 3+ requirement | Automatic handling |

Tul bridges these gaps with a zero-config middleware pipeline that "just works."

Installation

npm install tul
yarn add tul
pnpm add tul

Quick Start

import { createTul } from 'tul';

// Create client with defaults (all optimizations enabled)
const tul = createTul({
  apiKey: process.env.GOOGLE_AI_API_KEY,
  model: 'gemini-2.5-flash',
});

// Define tools
tul.defineTools([
  {
    name: 'search_web',
    description: 'Search the web for information',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: 'Search query' },
        maxResults: { type: 'integer', minimum: 1, maximum: 10 },
      },
      required: ['query'],
    },
    // Claude-inspired: provide examples
    examples: [
      { query: 'latest TypeScript features', maxResults: 5 },
    ],
    // Claude-inspired: enforce strict schema validation
    strict: true,
  },
]);

// Register tool handlers
tul.onToolCall(async (name, args) => {
  if (name === 'search_web') {
    return await searchWeb(args.query, args.maxResults);
  }
});

// Run a conversation turn
const response = await tul.run('Find the latest news about AI');

console.log(response.text);
console.log(`Tokens saved: ${response.stats.tokensSaved}`);

Features

Smart Tool Filtering

Reduces token usage by only sending relevant tools per request.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  toolFiltering: true,           // default: true
  maxToolsPerRequest: 5,          // default: 5
  filterThreshold: 0.3,           // default: 0.3 (relevance score)
  alwaysIncludeTools: ['error_handler'], // never filter these
});

Schema Compression

Compresses JSON schemas to reduce token consumption.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  schemaCompression: true,        // default: true
  compressionLevel: 'moderate',   // 'light' | 'moderate' | 'aggressive'
});

Compression levels:

  • light: Remove only verbose descriptions
  • moderate: Remove descriptions, abbreviate keys (default)
  • aggressive: Maximum compression, drop optional field metadata

Example Injection (Claude-Inspired)

Injects tool usage examples into the system prompt.

tul.defineTools([
  {
    name: 'create_file',
    description: 'Create a new file',
    parameters: { /* ... */ },
    examples: [
      { path: '/src/index.ts', content: 'export default {};' },
      { path: '/config.json', content: '{}' },
    ],
  },
]);

Example injection is automatic when exampleInjection: true (default).

Strict Schema Validation (Claude-Inspired)

Validates Gemini's tool call arguments against your schemas.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  strictValidation: true,         // default: true
  onValidationError: 'retry',     // 'retry' | 'warn' | 'throw'
});

tul.defineTools([
  {
    name: 'send_email',
    description: 'Send an email',
    parameters: {
      type: 'object',
      properties: {
        to: { type: 'string', pattern: '^[^@]+@[^@]+$' },
        subject: { type: 'string', minLength: 1 },
        body: { type: 'string' },
      },
      required: ['to', 'subject', 'body'],
    },
    strict: true, // Enable for this tool
  },
]);

JSON Repair

Automatically fixes malformed JSON in Gemini's responses.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  jsonRepair: true, // default: true
});

Uses the jsonrepair library to handle common issues:

  • Trailing commas
  • Missing quotes
  • Unescaped characters
  • Truncated responses

Loop Detection

Prevents runaway tool call loops.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  loopDetection: true,            // default: true
  maxToolCallsPerTurn: 10,        // default: 10
  maxIdenticalCalls: 2,           // default: 2
  onLoop: 'break',                // 'break' | 'warn'
});

Detected loop types:

  • identical: Same tool called with same arguments
  • oscillation: Tool A calls tool B calls tool A
  • runaway: Too many tool calls in one turn

Retry Handler

Automatically retries on transient failures.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  retryOnFailure: true,           // default: true
  maxRetries: 3,                  // default: 3
  retryDelay: 'exponential',      // 'none' | 'linear' | 'exponential'
});

Result Caching

Caches tool results to avoid redundant executions.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  resultCaching: true,            // default: true
  cacheTTL: 300000,               // default: 5 minutes
  cacheMaxSize: 100,              // default: 100 entries
});

// Per-tool TTL override
tul.defineTools([
  {
    name: 'get_time',
    description: 'Get current time',
    cacheTTL: 0, // Never cache
  },
  {
    name: 'get_weather',
    description: 'Get weather for location',
    cacheTTL: 60000, // Cache for 1 minute
  },
]);

Context Management (Claude-Inspired)

Automatically manages conversation context to stay within token limits.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-2.5-flash',
  contextManagement: true,        // default: true
  maxContextTokens: 80000,        // default: 80000
  turnsToKeepFull: 3,             // default: 3
  compactionStrategy: 'summarize', // 'summarize' | 'truncate' | 'drop'
});

Thought Signatures (Gemini 3+)

Handles thought signatures required by Gemini 3+ models.

const tul = createTul({
  apiKey: 'your-key',
  model: 'gemini-3-pro',
  thoughtSignatures: true, // default: true
});

Configuration Reference

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | required | Google AI API key | | model | string | required | Gemini model name | | toolFiltering | boolean | true | Enable smart tool filtering | | maxToolsPerRequest | number | 5 | Max tools to send per request | | filterThreshold | number | 0.3 | Minimum relevance score (0-1) | | alwaysIncludeTools | string[] | [] | Tools to never filter out | | schemaCompression | boolean | true | Enable schema compression | | compressionLevel | 'light' \| 'moderate' \| 'aggressive' | 'moderate' | Compression level | | exampleInjection | boolean | true | Enable example injection | | strictValidation | boolean | true | Enable schema validation | | onValidationError | 'retry' \| 'warn' \| 'throw' | 'retry' | Validation error action | | jsonRepair | boolean | true | Enable JSON repair | | loopDetection | boolean | true | Enable loop detection | | maxToolCallsPerTurn | number | 10 | Max tool calls per turn | | maxIdenticalCalls | number | 2 | Max identical calls before loop | | onLoop | 'break' \| 'warn' | 'break' | Loop detection action | | retryOnFailure | boolean | true | Enable automatic retry | | maxRetries | number | 3 | Maximum retry attempts | | retryDelay | 'none' \| 'linear' \| 'exponential' | 'linear' | Retry delay strategy | | resultCaching | boolean | true | Enable result caching | | cacheTTL | number | 300000 | Cache TTL in ms (5 min) | | cacheMaxSize | number | 100 | Max cache entries | | contextManagement | boolean | true | Enable context management | | maxContextTokens | number | 80000 | Max context tokens | | turnsToKeepFull | number | 3 | Recent turns to keep full | | compactionStrategy | 'summarize' \| 'truncate' \| 'drop' | 'summarize' | Context compaction strategy | | thoughtSignatures | boolean | true | Enable thought signatures | | systemPrompt | string | undefined | Custom system prompt | | verbose | boolean | false | Enable verbose logging | | logLevel | 'debug' \| 'info' \| 'warn' \| 'error' \| 'silent' | 'warn' | Log level |

API Reference

createTul(config: TulConfig): TulClient

Creates a new Tul client with the specified configuration.

TulClient

defineTools(tools: ToolDefinition[]): void

Registers tool definitions. Can be called multiple times to add more tools.

tul.defineTools([
  { name: 'tool_a', description: '...', parameters: { ... } },
  { name: 'tool_b', description: '...', parameters: { ... } },
]);

onToolCall(handler: ToolCallHandler): void

Registers a handler for tool call execution.

tul.onToolCall(async (name, args) => {
  switch (name) {
    case 'search': return await search(args.query);
    case 'read_file': return await fs.readFile(args.path, 'utf8');
    default: throw new Error(`Unknown tool: ${name}`);
  }
});

run(message: string): Promise<TulResponse>

Sends a message and handles the full tool-calling loop.

const response = await tul.run('What is the weather in Tokyo?');

addMessage(content: Content): void

Manually adds a message to the conversation history.

clearHistory(): void

Clears the conversation history.

getStats(): CumulativeStats

Returns cumulative statistics across all requests.

const stats = tul.getStats();
console.log(`Total tokens saved: ${stats.tokensSaved} (${stats.percentSaved}%)`);

TulResponse

interface TulResponse {
  text: string;                  // Final text response
  toolCalls: ToolCallResult[];   // Tools called during request
  stats: RequestStats;           // Per-request statistics
  raw: unknown;                  // Raw Gemini API response
  truncatedByLoop?: boolean;     // Whether loop detection triggered
  warnings?: string[];           // Any warnings generated
}

RequestStats

interface RequestStats {
  inputTokens: number;
  outputTokens: number;
  tokensSaved: number;
  toolsFiltered: number;
  toolsSent: number;
  examplesInjected: number;
  exampleTokens: number;
  compressionSaved: number;
  cacheHit: boolean;
  cacheHits: number;
  retries: number;
  jsonRepaired: boolean;
  loopDetected: boolean;
  validationFailed: boolean;
  validationRecovered: boolean;
  contextCompactionSaved: number;
  toolCallsMade: number;
}

Events

Listen for tool execution events:

tul.on('tool:call', ({ name, args }) => {
  console.log(`Calling ${name} with`, args);
});

tul.on('tool:result', ({ name, result, cached }) => {
  console.log(`${name} returned:`, result, cached ? '(cached)' : '');
});

tul.on('tool:loop', ({ loopType }) => {
  console.warn(`Loop detected: ${loopType}`);
});

tul.on('tool:validation:fail', ({ name, errors }) => {
  console.warn(`Validation failed for ${name}:`, errors);
});

Token Savings Benchmarks

Note: Benchmarks coming soon. We're actively measuring token savings across various agent workloads.

Expected savings based on feature analysis:

| Feature | Estimated Savings | |---------|------------------| | Tool Filtering (10 -> 5 tools) | ~40-60% on tool schema tokens | | Schema Compression (moderate) | ~20-30% on schema tokens | | Result Caching | Varies by hit rate | | Context Compaction | ~30-50% on context tokens |

Overall, Tul typically reduces total token consumption by 20-40% compared to naive Gemini tool calling.

Comparison with Alternatives

| Feature | Tul | Raw Gemini | LangChain | |---------|-----|------------|-----------| | Zero config | Yes | N/A | No | | Token optimization | Yes | No | Limited | | Loop detection | Yes | No | No | | JSON repair | Yes | No | No | | Strict validation | Yes | No | Limited | | Bundle size | ~50KB | N/A | ~500KB+ |

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a pull request.

Development Setup

# Clone the repository
git clone https://github.com/your-org/tul.git
cd tul

# Install dependencies
npm install

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Lint
npm run lint

# Type check
npm run typecheck

# Build
npm run build

Project Structure

src/
  types/           # TypeScript type definitions
  utils/           # Shared utility functions
    logger.ts      # Configurable logging
    helpers.ts     # General helpers
    schema-utils.ts # JSON Schema utilities
  middleware/      # Middleware implementations
    pipeline.ts    # Middleware orchestration
    tool-filter.ts # Smart tool filtering
    schema-compressor.ts
    example-injector.ts
    strict-validator.ts
    json-repairer.ts
    loop-detector.ts
    retry-handler.ts
    result-cache.ts
    context-manager.ts
    thought-signatures.ts
  analytics/       # Token tracking and reporting
    token-tracker.ts
    reporter.ts
  tool-runner.ts   # Tool execution engine
  client.ts        # Main TulClient
  index.ts         # Public exports

Code Style

  • TypeScript strict mode
  • ESLint + Prettier
  • No external dependencies except jsonrepair and zod

License

MIT License - see LICENSE for details.


Built with care to make Gemini tool calling reliable.