Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@loopstack/custom-tool-example-module

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@loopstack/custom-tool-example-module - npm Package Compare versions

Comparing version
0.22.1
to
0.23.0
+4
-2
dist/tools/counter.tool.d.ts
import { BaseTool, ToolResult } from '@loopstack/common';
export declare class CounterTool extends BaseTool {
import type { LoopstackContext } from '@loopstack/common';
export type CounterToolResult = number;
export declare class CounterTool extends BaseTool<object, object, CounterToolResult> {
count: number;
call(_args?: object): Promise<ToolResult<number>>;
protected handle(_args: object | undefined, _ctx: LoopstackContext): Promise<ToolResult<CounterToolResult>>;
}
//# sourceMappingURL=counter.tool.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"counter.tool.d.ts","sourceRoot":"","sources":["../../src/tools/counter.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAQ,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/D,qBAKa,WAAY,SAAQ,QAAQ;IACvC,KAAK,EAAE,MAAM,CAAK;IAElB,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;CAIlD"}
{"version":3,"file":"counter.tool.d.ts","sourceRoot":"","sources":["../../src/tools/counter.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAQ,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEvC,qBAIa,WAAY,SAAQ,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC;IAC1E,KAAK,EAAE,MAAM,CAAK;cAEF,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;CAIlH"}

@@ -13,3 +13,3 @@ "use strict";

count = 0;
call(_args) {
async handle(_args, _ctx) {
this.count++;

@@ -22,7 +22,6 @@ return Promise.resolve({ data: this.count });

(0, common_1.Tool)({
uiConfig: {
description: 'Counter tool.',
},
name: 'counter',
description: 'Counter tool.',
})
], CounterTool);
//# sourceMappingURL=counter.tool.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"counter.tool.js","sourceRoot":"","sources":["../../src/tools/counter.tool.ts"],"names":[],"mappings":";;;;;;;;;AAAA,8CAA+D;AAOxD,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,iBAAQ;IACvC,KAAK,GAAW,CAAC,CAAC;IAElB,IAAI,CAAC,KAAc;QACjB,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF,CAAA;AAPY,kCAAW;sBAAX,WAAW;IALvB,IAAA,aAAI,EAAC;QACJ,QAAQ,EAAE;YACR,WAAW,EAAE,eAAe;SAC7B;KACF,CAAC;GACW,WAAW,CAOvB"}
{"version":3,"file":"counter.tool.js","sourceRoot":"","sources":["../../src/tools/counter.tool.ts"],"names":[],"mappings":";;;;;;;;;AAAA,8CAA+D;AASxD,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,iBAA2C;IAC1E,KAAK,GAAW,CAAC,CAAC;IAER,KAAK,CAAC,MAAM,CAAC,KAAyB,EAAE,IAAsB;QACtE,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF,CAAA;AAPY,kCAAW;sBAAX,WAAW;IAJvB,IAAA,aAAI,EAAC;QACJ,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,eAAe;KAC7B,CAAC;GACW,WAAW,CAOvB"}
import { z } from 'zod';
import { BaseTool, ToolResult } from '@loopstack/common';
import type { LoopstackContext } from '@loopstack/common';
import { MathService } from '../services/math.service';
declare const MathSumSchema: z.ZodObject<{

@@ -8,7 +10,9 @@ a: z.ZodNumber;

type MathSumArgs = z.infer<typeof MathSumSchema>;
export declare class MathSumTool extends BaseTool {
private mathService;
call(args: MathSumArgs): Promise<ToolResult<number>>;
export type MathSumToolResult = number;
export declare class MathSumTool extends BaseTool<MathSumArgs, object, MathSumToolResult> {
private readonly mathService;
constructor(mathService: MathService);
protected handle(args: MathSumArgs, _ctx: LoopstackContext): Promise<ToolResult<MathSumToolResult>>;
}
export {};
//# sourceMappingURL=math-sum.tool.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"math-sum.tool.d.ts","sourceRoot":"","sources":["../../src/tools/math-sum.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAQ,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/D,QAAA,MAAM,aAAa;;;kBAKR,CAAC;AAEZ,KAAK,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEjD,qBAMa,WAAY,SAAQ,QAAQ;IAEvC,OAAO,CAAC,WAAW,CAAc;IAEjC,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;CAIrD"}
{"version":3,"file":"math-sum.tool.d.ts","sourceRoot":"","sources":["../../src/tools/math-sum.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAQ,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,QAAA,MAAM,aAAa;;;kBAKR,CAAC;AAEZ,KAAK,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEvC,qBAKa,WAAY,SAAQ,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,iBAAiB,CAAC;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;cAIrC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;CAI1G"}

@@ -13,5 +13,4 @@ "use strict";

exports.MathSumTool = void 0;
const common_1 = require("@nestjs/common");
const zod_1 = require("zod");
const common_2 = require("@loopstack/common");
const common_1 = require("@loopstack/common");
const math_service_1 = require("../services/math.service");

@@ -24,5 +23,9 @@ const MathSumSchema = zod_1.z

.strict();
let MathSumTool = class MathSumTool extends common_2.BaseTool {
let MathSumTool = class MathSumTool extends common_1.BaseTool {
mathService;
call(args) {
constructor(mathService) {
super();
this.mathService = mathService;
}
async handle(args, _ctx) {
const sum = this.mathService.sum(args.a, args.b);

@@ -33,14 +36,10 @@ return Promise.resolve({ data: sum });

exports.MathSumTool = MathSumTool;
__decorate([
(0, common_1.Inject)(),
__metadata("design:type", math_service_1.MathService)
], MathSumTool.prototype, "mathService", void 0);
exports.MathSumTool = MathSumTool = __decorate([
(0, common_2.Tool)({
uiConfig: {
description: 'Math tool calculating the sum of two arguments by using an injected service.',
},
(0, common_1.Tool)({
name: 'math_sum',
description: 'Math tool calculating the sum of two arguments by using an injected service.',
schema: MathSumSchema,
})
}),
__metadata("design:paramtypes", [math_service_1.MathService])
], MathSumTool);
//# sourceMappingURL=math-sum.tool.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"math-sum.tool.js","sourceRoot":"","sources":["../../src/tools/math-sum.tool.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAwC;AACxC,6BAAwB;AACxB,8CAA+D;AAC/D,2DAAuD;AAEvD,MAAM,aAAa,GAAG,OAAC;KACpB,MAAM,CAAC;IACN,CAAC,EAAE,OAAC,CAAC,MAAM,EAAE;IACb,CAAC,EAAE,OAAC,CAAC,MAAM,EAAE;CACd,CAAC;KACD,MAAM,EAAE,CAAC;AAUL,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,iBAAQ;IAE/B,WAAW,CAAc;IAEjC,IAAI,CAAC,IAAiB;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,CAAC;CACF,CAAA;AARY,kCAAW;AAEd;IADP,IAAA,eAAM,GAAE;8BACY,0BAAW;gDAAC;sBAFtB,WAAW;IANvB,IAAA,aAAI,EAAC;QACJ,QAAQ,EAAE;YACR,WAAW,EAAE,8EAA8E;SAC5F;QACD,MAAM,EAAE,aAAa;KACtB,CAAC;GACW,WAAW,CAQvB"}
{"version":3,"file":"math-sum.tool.js","sourceRoot":"","sources":["../../src/tools/math-sum.tool.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,8CAA+D;AAE/D,2DAAuD;AAEvD,MAAM,aAAa,GAAG,OAAC;KACpB,MAAM,CAAC;IACN,CAAC,EAAE,OAAC,CAAC,MAAM,EAAE;IACb,CAAC,EAAE,OAAC,CAAC,MAAM,EAAE;CACd,CAAC;KACD,MAAM,EAAE,CAAC;AAWL,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,iBAAgD;IAClD;IAA7B,YAA6B,WAAwB;QACnD,KAAK,EAAE,CAAC;QADmB,gBAAW,GAAX,WAAW,CAAa;IAErD,CAAC;IAES,KAAK,CAAC,MAAM,CAAC,IAAiB,EAAE,IAAsB;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,CAAC;CACF,CAAA;AATY,kCAAW;sBAAX,WAAW;IALvB,IAAA,aAAI,EAAC;QACJ,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,8EAA8E;QAC3F,MAAM,EAAE,aAAa;KACtB,CAAC;qCAE0C,0BAAW;GAD1C,WAAW,CASvB"}

@@ -1,21 +0,6 @@

title: 'Custom Tool'
description: |
This workflow demonstrates the usage of custom tools, including both stateless and stateful tools.
It performs a simple addition operation using a custom MathSumTool and showcases the behavior of
stateless and stateful counter tools. It also tests that tool state persists across checkpoints.
ui:
form:
properties:
a:
title: 'First number (a)'
b:
title: 'Second number (b)'
widgets:
- widget: button
enabledWhen:
- waiting_for_user
options:
transition: userContinue
label: Continue
widget: button
enabledWhen:
- waiting_for_user
options:
transition: userContinue
label: Continue
import { BaseWorkflow } from '@loopstack/common';
import type { LoopstackContext } from '@loopstack/common';
import { MathSumTool } from '../tools';
import { CounterTool } from '../tools';
interface CustomToolExampleState {
total?: number;
}
export declare class CustomToolExampleWorkflow extends BaseWorkflow<{
a: number;
b: number;
}> {
private counterTool;
private mathTool;
total?: number;
calculate(args: {
a: number;
b: number;
}): Promise<void>;
userContinue(): Promise<void>;
continueCount(): Promise<{
}, CustomToolExampleState> {
private readonly counterTool;
private readonly mathTool;
constructor(counterTool: CounterTool, mathTool: MathSumTool);
calculate(state: CustomToolExampleState, ctx: LoopstackContext): Promise<CustomToolExampleState>;
userContinue(state: CustomToolExampleState): Promise<CustomToolExampleState>;
continueCount(state: CustomToolExampleState): Promise<{
total: number | undefined;

@@ -19,2 +22,3 @@ }>;

}
export {};
//# sourceMappingURL=custom-tool-example.workflow.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"custom-tool-example.workflow.d.ts","sourceRoot":"","sources":["../../src/workflows/custom-tool-example.workflow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAqE,MAAM,mBAAmB,CAAC;AAIpH,qBASa,yBAA0B,SAAQ,YAAY,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;IACrE,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAc;IAE5C,KAAK,CAAC,EAAE,MAAM,CAAC;IAGT,SAAS,CAAC,IAAI,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IA6BxC,YAAY;IAKZ,aAAa,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAc7D,OAAO,CAAC,GAAG;CAGZ"}
{"version":3,"file":"custom-tool-example.workflow.d.ts","sourceRoot":"","sources":["../../src/workflows/custom-tool-example.workflow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAyC,MAAM,mBAAmB,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,UAAU,sBAAsB;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAYa,yBAA0B,SAAQ,YAAY,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,sBAAsB,CAAC;IAEzG,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,WAAW;IAMlC,SAAS,CAAC,KAAK,EAAE,sBAAsB,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA+BhG,YAAY,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAM5E,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAc1F,OAAO,CAAC,GAAG;CAGZ"}

@@ -20,11 +20,16 @@ "use strict";

mathTool;
total;
async calculate(args) {
constructor(counterTool, mathTool) {
super();
this.counterTool = counterTool;
this.mathTool = mathTool;
}
async calculate(state, ctx) {
const args = ctx.args;
const calcResult = await this.mathTool.call({ a: args.a, b: args.b });
this.total = calcResult.data;
await this.repository.save(common_1.MessageDocument, {
const total = calcResult.data;
await this.documentStore.save(common_1.MessageDocument, {
role: 'assistant',
content: `Tool calculation result:\n${args.a} + ${args.b} = ${this.total}`,
content: `Tool calculation result:\n${args.a} + ${args.b} = ${total}`,
});
await this.repository.save(common_1.MessageDocument, {
await this.documentStore.save(common_1.MessageDocument, {
role: 'assistant',

@@ -36,18 +41,20 @@ content: `Alternatively, using workflow method:\n${args.a} + ${args.b} = ${this.sum(args.a, args.b)}`,

const c3 = await this.counterTool.call();
await this.repository.save(common_1.MessageDocument, {
await this.documentStore.save(common_1.MessageDocument, {
role: 'assistant',
content: `Counter before pause: ${c1.data}, ${c2.data}, ${c3.data}\n\nPress Next to continue...`,
});
return { ...state, total };
}
async userContinue() {
async userContinue(state) {
return state;
}
async continueCount() {
async continueCount(state) {
const c4 = await this.counterTool.call();
const c5 = await this.counterTool.call();
const c6 = await this.counterTool.call();
await this.repository.save(common_1.MessageDocument, {
await this.documentStore.save(common_1.MessageDocument, {
role: 'assistant',
content: `Counter after resume: ${c4.data}, ${c5.data}, ${c6.data}\n\nIf state persisted, this should be 4, 5, 6.`,
});
return { total: this.total };
return { total: state.total };
}

@@ -60,13 +67,5 @@ sum(a, b) {

__decorate([
(0, common_1.InjectTool)(),
__metadata("design:type", tools_2.CounterTool)
], CustomToolExampleWorkflow.prototype, "counterTool", void 0);
__decorate([
(0, common_1.InjectTool)(),
__metadata("design:type", tools_1.MathSumTool)
], CustomToolExampleWorkflow.prototype, "mathTool", void 0);
__decorate([
(0, common_1.Initial)({ to: 'waiting_for_user' }),
(0, common_1.Transition)({ to: 'waiting_for_user' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)

@@ -77,9 +76,9 @@ ], CustomToolExampleWorkflow.prototype, "calculate", null);

__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], CustomToolExampleWorkflow.prototype, "userContinue", null);
__decorate([
(0, common_1.Final)({ from: 'resumed' }),
(0, common_1.Transition)({ from: 'resumed', to: 'end' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)

@@ -89,3 +88,5 @@ ], CustomToolExampleWorkflow.prototype, "continueCount", null);

(0, common_1.Workflow)({
uiConfig: __dirname + '/custom-tool-example.ui.yaml',
title: 'Custom Tool',
description: 'This workflow demonstrates the usage of custom tools, including both stateless and stateful tools.\nIt performs a simple addition operation using a custom MathSumTool and showcases the behavior of\nstateless and stateful counter tools. It also tests that tool state persists across checkpoints.',
widget: __dirname + '/custom-tool-example.ui.yaml',
schema: zod_1.z

@@ -97,4 +98,6 @@ .object({

.strict(),
})
}),
__metadata("design:paramtypes", [tools_2.CounterTool,
tools_1.MathSumTool])
], CustomToolExampleWorkflow);
//# sourceMappingURL=custom-tool-example.workflow.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"custom-tool-example.workflow.js","sourceRoot":"","sources":["../../src/workflows/custom-tool-example.workflow.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"custom-tool-example.workflow.js","sourceRoot":"","sources":["../../src/workflows/custom-tool-example.workflow.ts"],"names":[],"mappings":"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"}

@@ -12,3 +12,3 @@ {

],
"version": "0.22.1",
"version": "0.23.0",
"license": "MIT",

@@ -34,3 +34,3 @@ "author": {

"dependencies": {
"@loopstack/common": "^0.31.0",
"@loopstack/common": "^0.32.0",
"@nestjs/common": "^11.1.19",

@@ -37,0 +37,0 @@ "zod": "^4.3.6"

+106
-58

@@ -13,7 +13,7 @@ # @loopstack/custom-tool-example-module

- How to create tools that extend `BaseTool` with a `call()` method
- How to create tools that extend `BaseTool` with a `handle()` method
- The difference between stateless and stateful tools
- How to define input schemas with Zod on the `@Tool` decorator
- How to use NestJS dependency injection in tools
- How to wire tools into workflows using `@InjectTool()`
- How to use NestJS dependency injection in tools and workflows
- How to wire tools into workflows via constructor injection
- How to use `wait: true` on transitions for manual triggers

@@ -30,2 +30,14 @@ - How to return output from a workflow

### Module Setup
Register the workflow, tools, and any supporting services as NestJS providers:
```typescript
@Module({
providers: [CustomToolExampleWorkflow, MathSumTool, CounterTool, MathService],
exports: [CustomToolExampleWorkflow, MathSumTool, CounterTool, MathService],
})
export class CustomToolModule {}
```
### Creating Custom Tools

@@ -35,3 +47,3 @@

A simple tool that maintains internal state across calls. It extends `BaseTool` and implements a `call()` method:
A simple tool that maintains internal state across calls. It extends `BaseTool` and implements `handle()`:

@@ -42,2 +54,3 @@ ```typescript

@Tool({
name: 'counter',
uiConfig: {

@@ -47,6 +60,6 @@ description: 'Counter tool.',

})
export class CounterTool extends BaseTool {
export class CounterTool extends BaseTool<object, object, number> {
count: number = 0;
call(_args?: object): Promise<ToolResult<number>> {
protected async handle(_args?: object): Promise<ToolResult<number>> {
this.count++;

@@ -62,6 +75,5 @@ return Promise.resolve({ data: this.count });

A tool that accepts typed arguments via a Zod schema and uses NestJS dependency injection for services:
A tool that accepts typed arguments via a Zod schema and uses NestJS constructor injection for services:
```typescript
import { Inject } from '@nestjs/common';
import { z } from 'zod';

@@ -81,2 +93,3 @@ import { BaseTool, Tool, ToolResult } from '@loopstack/common';

@Tool({
name: 'math_sum',
uiConfig: {

@@ -87,7 +100,8 @@ description: 'Math tool calculating the sum of two arguments by using an injected service.',

})
export class MathSumTool extends BaseTool {
@Inject()
private mathService: MathService;
export class MathSumTool extends BaseTool<MathSumArgs, object, number> {
constructor(private readonly mathService: MathService) {
super();
}
call(args: MathSumArgs): Promise<ToolResult<number>> {
protected async handle(args: MathSumArgs): Promise<ToolResult<number>> {
const sum = this.mathService.sum(args.a, args.b);

@@ -112,5 +126,9 @@ return Promise.resolve({ data: sum });

The workflow extends `BaseWorkflow<TArgs>` with a typed argument object. The schema is defined in the `@Workflow` decorator:
The workflow extends `BaseWorkflow` with typed arguments and typed state. The argument schema is defined in the `@Workflow` decorator:
```typescript
interface CustomToolExampleState {
total?: number;
}
@Workflow({

@@ -125,10 +143,15 @@ uiConfig: __dirname + '/custom-tool-example.ui.yaml',

})
export class CustomToolExampleWorkflow extends BaseWorkflow<{ a: number; b: number }> {
@InjectTool() private counterTool: CounterTool;
@InjectTool() private mathTool: MathSumTool;
total?: number;
export class CustomToolExampleWorkflow extends BaseWorkflow<{ a: number; b: number }, CustomToolExampleState> {
constructor(
private readonly counterTool: CounterTool,
private readonly mathTool: MathSumTool,
@Inject(DOCUMENT_STORE) private readonly documentStore: DocumentStore,
) {
super();
}
}
```
Tools registered in the same NestJS module are injected via the constructor. Call them with `this.tool.call(args)`. `call()` is provided by `BaseTool` and delegates to your `handle()` implementation.
### Key Concepts

@@ -138,22 +161,30 @@

Call tools via `this.tool.call(args)` inside transition methods. Store the result as an instance property:
Call tools inside transition methods and store results in workflow state:
```typescript
@Initial({ to: 'waiting_for_user' })
async calculate(args: { a: number; b: number }) {
@Transition({ to: 'waiting_for_user' })
async calculate(
ctx: WorkflowContext,
args: { a: number; b: number },
state: CustomToolExampleState,
): Promise<CustomToolExampleState> {
const calcResult = await this.mathTool.call({ a: args.a, b: args.b });
this.total = calcResult.data as number;
const total = calcResult.data as number;
await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
content: `Tool calculation result:\n${args.a} + ${args.b} = ${this.total}`,
content: `Tool calculation result:\n${args.a} + ${args.b} = ${total}`,
});
await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
content: `Alternatively, using workflow method:\n${args.a} + ${args.b} = ${this.sum(args.a, args.b)}`,
});
return { ...state, total };
}
```
The workflow then prints a second message using `this.sum(args.a, args.b)`. That private helper is **intentional demo redundancy**. It shows the same calculation can also live as workflow-local logic. In production you would normally pick one approach: call a tool (reusable, injectable, schema-validated) or use a private helper (simple, workflow-specific). Here both are shown side by side for comparison. Note that `total` stored in workflow state comes from the tool result, not from `sum()`.
#### 2. Stateful Tool Behavior

@@ -168,3 +199,3 @@

await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',

@@ -177,18 +208,20 @@ content: `Counter before pause: ${c1.data}, ${c2.data}, ${c3.data}\n\nPress Next to continue...`,

Use `wait: true` on a transition to pause the workflow until it is manually triggered (e.g., by user input):
Use `wait: true` on a transition to pause the workflow until it is manually triggered (e.g., by a UI button):
```typescript
@Transition({ from: 'waiting_for_user', to: 'resumed', wait: true })
async userContinue() {}
async userContinue(ctx: WorkflowContext, state: CustomToolExampleState): Promise<CustomToolExampleState> {
return state;
}
```
The workflow pauses at the `waiting_for_user` state until an external signal triggers the `userContinue` transition.
The workflow pauses at `waiting_for_user` until the user triggers the `userContinue` transition.
#### 4. Workflow Output
A `@Final` method can return data as the workflow output:
A terminal `@Transition` method can return data as the workflow output:
```typescript
@Final({ from: 'resumed' })
async continueCount(): Promise<{ total: number | undefined }> {
@Transition({ from: 'resumed', to: 'end' })
async continueCount(ctx: WorkflowContext, state: CustomToolExampleState): Promise<{ total: number | undefined }> {
const c4 = await this.counterTool.call();

@@ -198,3 +231,3 @@ const c5 = await this.counterTool.call();

await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',

@@ -204,3 +237,3 @@ content: `Counter after resume: ${c4.data}, ${c5.data}, ${c6.data}\n\nIf state persisted, this should be 4, 5, 6.`,

return { total: this.total };
return { total: state.total };
}

@@ -213,3 +246,3 @@ ```

Define private methods for reusable logic within the workflow:
Define private methods for reusable logic within the workflow. In this example, `sum()` mirrors what `MathSumTool` already does. It exists only to demonstrate that alternative:

@@ -222,11 +255,17 @@ ```typescript

Use helpers for workflow-specific formatting or glue logic; use tools when the logic should be shared, tested independently, or exposed to agents.
### Complete Workflow
```typescript
import { Inject } from '@nestjs/common';
import { z } from 'zod';
import { BaseWorkflow, Final, Initial, InjectTool, Transition, Workflow } from '@loopstack/common';
import { MessageDocument } from '@loopstack/common';
import { MathSumTool } from '../tools';
import { CounterTool } from '../tools';
import { BaseWorkflow, DOCUMENT_STORE, Final, Initial, MessageDocument, Transition, Workflow } from '@loopstack/common';
import type { DocumentStore, WorkflowContext } from '@loopstack/common';
import { CounterTool, MathSumTool } from '../tools';
interface CustomToolExampleState {
total?: number;
}
@Workflow({

@@ -241,19 +280,26 @@ uiConfig: __dirname + '/custom-tool-example.ui.yaml',

})
export class CustomToolExampleWorkflow extends BaseWorkflow<{ a: number; b: number }> {
@InjectTool() private counterTool: CounterTool;
@InjectTool() private mathTool: MathSumTool;
export class CustomToolExampleWorkflow extends BaseWorkflow<{ a: number; b: number }, CustomToolExampleState> {
constructor(
private readonly counterTool: CounterTool,
private readonly mathTool: MathSumTool,
@Inject(DOCUMENT_STORE) private readonly documentStore: DocumentStore,
) {
super();
}
total?: number;
@Initial({ to: 'waiting_for_user' })
async calculate(args: { a: number; b: number }) {
@Transition({ to: 'waiting_for_user' })
async calculate(
ctx: WorkflowContext,
args: { a: number; b: number },
state: CustomToolExampleState,
): Promise<CustomToolExampleState> {
const calcResult = await this.mathTool.call({ a: args.a, b: args.b });
this.total = calcResult.data as number;
const total = calcResult.data as number;
await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
content: `Tool calculation result:\n${args.a} + ${args.b} = ${this.total}`,
content: `Tool calculation result:\n${args.a} + ${args.b} = ${total}`,
});
await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',

@@ -267,13 +313,16 @@ content: `Alternatively, using workflow method:\n${args.a} + ${args.b} = ${this.sum(args.a, args.b)}`,

await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
content: `Counter before pause: ${c1.data}, ${c2.data}, ${c3.data}\n\nPress Next to continue...`,
});
return { ...state, total };
}
@Transition({ from: 'waiting_for_user', to: 'resumed', wait: true })
async userContinue() {}
async userContinue(ctx: WorkflowContext, state: CustomToolExampleState): Promise<CustomToolExampleState> {
return state;
}
@Final({ from: 'resumed' })
async continueCount(): Promise<{ total: number | undefined }> {
@Transition({ from: 'resumed', to: 'end' })
async continueCount(ctx: WorkflowContext, state: CustomToolExampleState): Promise<{ total: number | undefined }> {
const c4 = await this.counterTool.call();

@@ -283,3 +332,3 @@ const c5 = await this.counterTool.call();

await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',

@@ -289,3 +338,3 @@ content: `Counter after resume: ${c4.data}, ${c5.data}, ${c6.data}\n\nIf state persisted, this should be 4, 5, 6.`,

return { total: this.total };
return { total: state.total };
}

@@ -303,4 +352,3 @@

- `@loopstack/common` - Base classes, decorators, and tool injection
- `@loopstack/common` - Provides `MessageDocument` for chat messages
- `@loopstack/common`: Base classes, decorators, `BaseTool`, `DocumentStore`, and `MessageDocument`

@@ -307,0 +355,0 @@ ## About

import { BaseTool, Tool, ToolResult } from '@loopstack/common';
import type { LoopstackContext } from '@loopstack/common';
export type CounterToolResult = number;
@Tool({
uiConfig: {
description: 'Counter tool.',
},
name: 'counter',
description: 'Counter tool.',
})
export class CounterTool extends BaseTool {
export class CounterTool extends BaseTool<object, object, CounterToolResult> {
count: number = 0;
call(_args?: object): Promise<ToolResult<number>> {
protected async handle(_args: object | undefined, _ctx: LoopstackContext): Promise<ToolResult<CounterToolResult>> {
this.count++;

@@ -13,0 +15,0 @@ return Promise.resolve({ data: this.count });

@@ -1,4 +0,4 @@

import { Inject } from '@nestjs/common';
import { z } from 'zod';
import { BaseTool, Tool, ToolResult } from '@loopstack/common';
import type { LoopstackContext } from '@loopstack/common';
import { MathService } from '../services/math.service';

@@ -15,13 +15,15 @@

export type MathSumToolResult = number;
@Tool({
uiConfig: {
description: 'Math tool calculating the sum of two arguments by using an injected service.',
},
name: 'math_sum',
description: 'Math tool calculating the sum of two arguments by using an injected service.',
schema: MathSumSchema,
})
export class MathSumTool extends BaseTool {
@Inject()
private mathService: MathService;
export class MathSumTool extends BaseTool<MathSumArgs, object, MathSumToolResult> {
constructor(private readonly mathService: MathService) {
super();
}
call(args: MathSumArgs): Promise<ToolResult<number>> {
protected async handle(args: MathSumArgs, _ctx: LoopstackContext): Promise<ToolResult<MathSumToolResult>> {
const sum = this.mathService.sum(args.a, args.b);

@@ -28,0 +30,0 @@ return Promise.resolve({ data: sum });

import { TestingModule } from '@nestjs/testing';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { z } from 'zod';
import { getBlockArgsSchema, getBlockConfig, getBlockTools } from '@loopstack/common';
import { getBlockArgsSchema, getBlockConfig } from '@loopstack/common';
import { WorkflowProcessorService } from '@loopstack/core';

@@ -52,8 +52,5 @@ import { ToolMock, createStatelessContext, createWorkflowTest } from '@loopstack/testing';

it('should have all tools available via workflow.tools', () => {
expect(getBlockTools(workflow)).toBeDefined();
expect(Array.isArray(getBlockTools(workflow))).toBe(true);
expect(getBlockTools(workflow)).toContain('counterTool');
expect(getBlockTools(workflow)).toContain('mathTool');
expect(getBlockTools(workflow)).toHaveLength(2);
it('should have tools available via constructor injection', () => {
expect(mockMathSumTool).toBeDefined();
expect(mockCounterTool).toBeDefined();
});

@@ -109,3 +106,3 @@ });

// Tool calls
expect(mockMathSumTool.call).toHaveBeenCalledWith({ a: 10, b: 20 }, undefined);
expect(mockMathSumTool.call).toHaveBeenCalledWith({ a: 10, b: 20 });
expect(mockCounterTool.call).toHaveBeenCalledTimes(3);

@@ -117,3 +114,3 @@

expect.objectContaining({
className: 'MessageDocument',
documentName: 'message',
content: expect.objectContaining({

@@ -120,0 +117,0 @@ role: 'assistant',

@@ -1,21 +0,6 @@

title: 'Custom Tool'
description: |
This workflow demonstrates the usage of custom tools, including both stateless and stateful tools.
It performs a simple addition operation using a custom MathSumTool and showcases the behavior of
stateless and stateful counter tools. It also tests that tool state persists across checkpoints.
ui:
form:
properties:
a:
title: 'First number (a)'
b:
title: 'Second number (b)'
widgets:
- widget: button
enabledWhen:
- waiting_for_user
options:
transition: userContinue
label: Continue
widget: button
enabledWhen:
- waiting_for_user
options:
transition: userContinue
label: Continue
import { z } from 'zod';
import { BaseWorkflow, Final, Initial, InjectTool, MessageDocument, Transition, Workflow } from '@loopstack/common';
import { BaseWorkflow, MessageDocument, Transition, Workflow } from '@loopstack/common';
import type { LoopstackContext } from '@loopstack/common';
import { MathSumTool } from '../tools';
import { CounterTool } from '../tools';
interface CustomToolExampleState {
total?: number;
}
@Workflow({
uiConfig: __dirname + '/custom-tool-example.ui.yaml',
title: 'Custom Tool',
description:
'This workflow demonstrates the usage of custom tools, including both stateless and stateful tools.\nIt performs a simple addition operation using a custom MathSumTool and showcases the behavior of\nstateless and stateful counter tools. It also tests that tool state persists across checkpoints.',
widget: __dirname + '/custom-tool-example.ui.yaml',
schema: z

@@ -15,22 +23,25 @@ .object({

})
export class CustomToolExampleWorkflow extends BaseWorkflow<{ a: number; b: number }> {
@InjectTool() private counterTool: CounterTool;
@InjectTool() private mathTool: MathSumTool;
export class CustomToolExampleWorkflow extends BaseWorkflow<{ a: number; b: number }, CustomToolExampleState> {
constructor(
private readonly counterTool: CounterTool,
private readonly mathTool: MathSumTool,
) {
super();
}
total?: number;
@Initial({ to: 'waiting_for_user' })
async calculate(args: { a: number; b: number }) {
@Transition({ to: 'waiting_for_user' })
async calculate(state: CustomToolExampleState, ctx: LoopstackContext): Promise<CustomToolExampleState> {
const args = ctx.args as { a: number; b: number };
// Use a custom tool
const calcResult = await this.mathTool.call({ a: args.a, b: args.b });
this.total = calcResult.data as number;
const total = calcResult.data as number;
// Display the result
await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
content: `Tool calculation result:\n${args.a} + ${args.b} = ${this.total}`,
content: `Tool calculation result:\n${args.a} + ${args.b} = ${total}`,
});
// Alternatively, use a custom workflow method
await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',

@@ -45,15 +56,17 @@ content: `Alternatively, using workflow method:\n${args.a} + ${args.b} = ${this.sum(args.a, args.b)}`,

await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
content: `Counter before pause: ${c1.data}, ${c2.data}, ${c3.data}\n\nPress Next to continue...`,
});
return { ...state, total };
}
@Transition({ from: 'waiting_for_user', to: 'resumed', wait: true })
async userContinue() {
async userContinue(state: CustomToolExampleState): Promise<CustomToolExampleState> {
// User pressed Next — counter state should persist from checkpoint
return state;
}
@Final({ from: 'resumed' })
async continueCount(): Promise<{ total: number | undefined }> {
@Transition({ from: 'resumed', to: 'end' })
async continueCount(state: CustomToolExampleState): Promise<{ total: number | undefined }> {
// Count after resume — should continue: 4, 5, 6

@@ -64,3 +77,3 @@ const c4 = await this.counterTool.call();

await this.repository.save(MessageDocument, {
await this.documentStore.save(MessageDocument, {
role: 'assistant',

@@ -70,3 +83,3 @@ content: `Counter after resume: ${c4.data}, ${c5.data}, ${c6.data}\n\nIf state persisted, this should be 4, 5, 6.`,

return { total: this.total };
return { total: state.total };
}

@@ -73,0 +86,0 @@