New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@loopstack/custom-tool-example-module

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@loopstack/custom-tool-example-module

A complete example demonstrating how to implement and use a custom tool in a workflow

npmnpm
Version
0.21.0
Version published
Weekly downloads
261
1086.36%
Maintainers
1
Weekly downloads
 
Created
Source

@loopstack/custom-tool-example-module

A module for the Loopstack AI automation framework.

This module provides a complete example demonstrating how to implement and use custom tools in a Loopstack workflow.

Overview

Custom tools are the building blocks of Loopstack automations. This module serves as a hands-on reference for developers learning how to extend Loopstack with their own functionality.

By exploring this example, you'll understand:

  • How to create tools that perform specific tasks within workflows
  • The difference between stateless and stateful tools
  • How to use dependency injection to keep tools modular and testable
  • How to wire tools into workflows using YAML configuration
  • How to define workflow input, state, and output
  • How to structure and export a reusable module

This is a great starting point before building your own custom tools.

Installation

See SETUP.md for installation and setup instructions.

How It Works

Creating Custom Tools

1. Stateful Tool (Counter)

A simple tool that maintains internal state across calls using the @Tool decorator and ToolInterface:

@Tool({
  config: {
    description: 'Counter tool.',
  },
})
export class CounterTool implements ToolInterface {
  count: number = 0;

  async execute(): Promise<ToolResult> {
    this.count++;
    return Promise.resolve({
      data: this.count,
    });
  }
}

2. Tool with Input Schema and Dependency Injection

A tool that accepts typed arguments via @Input and uses NestJS dependency injection for services:

@Tool({
  config: {
    description: 'Math tool calculating the sum of two arguments by using an injected service.',
  },
})
export class MathSumTool implements ToolInterface {
  @Inject()
  private mathService: MathService;

  @Input({
    schema: z
      .object({
        a: z.number(),
        b: z.number(),
      })
      .strict(),
  })
  args: MathSumArgs;

  async execute(args: MathSumArgs): Promise<ToolResult<number>> {
    const sum = this.mathService.sum(args.a, args.b);
    return Promise.resolve({
      data: sum,
    });
  }
}

The injected MathService is a standard NestJS injectable:

@Injectable()
export class MathService {
  public sum(a: number, b: number) {
    return a + b;
  }
}

Workflow Class

The workflow class declares input, state, output, tools, and helpers:

@Workflow({
  uiConfig: __dirname + '/custom-tool-example.ui.yaml',
})
export class CustomToolExampleWorkflow {
  @InjectTool() private counterTool: CounterTool;
  @InjectTool() private createChatMessage: CreateChatMessage;
  @InjectTool() private mathTool: MathSumTool;

  @Input({
    schema: z
      .object({
        a: z.number().default(1),
        b: z.number().default(2),
      })
      .strict(),
  })
  args: { a: number; b: number };

  @State({
    schema: z
      .object({
        total: z.number().optional(),
        count1: z.number().optional(),
        count2: z.number().optional(),
        count3: z.number().optional(),
      })
      .strict(),
  })
  state: { total?: number; count1?: number; count2?: number; count3?: number };

  @Output()
  result() {
    return { total: this.state.total };
  }

  @DefineHelper()
  sum(a: number, b: number) {
    return a + b;
  }
}

Workflow YAML

Using Custom Tools

Call custom tools and save their results to state using assign:

- id: calculation
  tool: mathTool
  args:
    a: ${{ args.a }}
    b: ${{ args.b }}
  assign:
    total: ${{ result.data }}

Accessing State and Arguments

Reference workflow arguments with args.<name> and state with state.<name>:

- tool: createChatMessage
  args:
    role: 'assistant'
    content: |
      Tool calculation result:
      {{ args.a }} + {{ args.b }} = {{ state.total }}

Using Helper Functions

Call workflow helpers in templates:

- tool: createChatMessage
  args:
    role: 'assistant'
    content: |
      Alternatively, using workflow getter function:
      {{ args.a }} + {{ args.b }} = {{ sum args.a args.b }}

Stateful Tool Behavior

The counter tool increments on each call, demonstrating stateful tools:

- id: count1
  tool: counterTool
  assign:
    count1: ${{ result.data }}
- id: count2
  tool: counterTool
  assign:
    count2: ${{ result.data }}
- id: count3
  tool: counterTool
  assign:
    count3: ${{ result.data }}
- tool: createChatMessage
  args:
    role: 'assistant'
    content: |
      Counter tool should count:

      {{ state.count1 }}, {{ state.count2 }}, {{ state.count3 }}

Workflow Output

The @Output() decorator defines the data returned when the workflow completes:

@Output()
result() {
  return { total: this.state.total };
}

Dependencies

This workflow uses the following Loopstack modules:

  • @loopstack/core - Core framework functionality
  • @loopstack/create-chat-message-tool - Provides CreateChatMessage tool

About

Author: Jakob Klippel

License: Apache-2.0

Additional Resources

Keywords

custom

FAQs

Package last updated on 09 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts