@loopstack/custom-tool-example-module
Advanced tools
| 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":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,8CAAoH;AACpH,oCAAuC;AACvC,oCAAuC;AAWhC,IAAM,yBAAyB,GAA/B,MAAM,yBAA0B,SAAQ,qBAAsC;IAC7D,WAAW,CAAc;IACzB,QAAQ,CAAc;IAE5C,KAAK,CAAU;IAGT,AAAN,KAAK,CAAC,SAAS,CAAC,IAA8B;QAE5C,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,IAAc,CAAC;QAGvC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,wBAAe,EAAE;YAC1C,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,6BAA6B,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE;SAC3E,CAAC,CAAC;QAGH,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,wBAAe,EAAE;YAC1C,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,0CAA0C,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE;SACtG,CAAC,CAAC;QAGH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAEzC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,wBAAe,EAAE;YAC1C,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,yBAAyB,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,+BAA+B;SACjG,CAAC,CAAC;IACL,CAAC;IAGK,AAAN,KAAK,CAAC,YAAY;IAElB,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa;QAEjB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAEzC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,wBAAe,EAAE;YAC1C,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,yBAAyB,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,iDAAiD;SACnH,CAAC,CAAC;QAEH,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IAEO,GAAG,CAAC,CAAS,EAAE,CAAS;QAC9B,OAAO,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;CACF,CAAA;AA1DY,8DAAyB;AACd;IAArB,IAAA,mBAAU,GAAE;8BAAsB,mBAAW;8DAAC;AACzB;IAArB,IAAA,mBAAU,GAAE;8BAAmB,mBAAW;2DAAC;AAKtC;IADL,IAAA,gBAAO,EAAC,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC;;;;0DA2BnC;AAGK;IADL,IAAA,mBAAU,EAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;;;6DAGnE;AAGK;IADL,IAAA,cAAK,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;;;8DAa1B;oCArDU,yBAAyB;IATrC,IAAA,iBAAQ,EAAC;QACR,QAAQ,EAAE,SAAS,GAAG,8BAA8B;QACpD,MAAM,EAAE,OAAC;aACN,MAAM,CAAC;YACN,CAAC,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;YACxB,CAAC,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;SACzB,CAAC;aACD,MAAM,EAAE;KACZ,CAAC;GACW,yBAAyB,CA0DrC"} | ||
| {"version":3,"file":"custom-tool-example.workflow.js","sourceRoot":"","sources":["../../src/workflows/custom-tool-example.workflow.ts"],"names":[],"mappings":"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"} |
+2
-2
@@ -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 @@ |
48241
10.12%644
4.04%344
16.22%+ Added
+ Added
- Removed
- Removed
Updated