import { z } from 'zod' import { RunnableConfig } from '@langchain/core/runnables' import { StructuredTool, ToolParams } from '@langchain/core/tools' import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager' import { executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils' import { ICommonObject } from '../../../src/Interface' class ToolInputParsingException extends Error { output?: string constructor(message: string, output?: string) { super(message) this.output = output } } export interface BaseDynamicToolInput extends ToolParams { name: string description: string code: string returnDirect?: boolean } export interface DynamicStructuredToolInput< // eslint-disable-next-line T extends z.ZodObject = z.ZodObject > extends BaseDynamicToolInput { func?: (input: z.infer, runManager?: CallbackManagerForToolRun) => Promise schema: T } export class DynamicStructuredTool< // eslint-disable-next-line T extends z.ZodObject = z.ZodObject > extends StructuredTool { name: string description: string code: string func: DynamicStructuredToolInput['func'] // @ts-ignore schema: T private variables: any[] private flowObj: any constructor(fields: DynamicStructuredToolInput) { super(fields) this.name = fields.name this.description = fields.description this.code = fields.code this.func = fields.func this.returnDirect = fields.returnDirect ?? this.returnDirect this.schema = fields.schema } async call( arg: z.output, configArg?: RunnableConfig | Callbacks, tags?: string[], flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject } ): Promise { const config = parseCallbackConfigArg(configArg) if (config.runName === undefined) { config.runName = this.name } let parsed try { parsed = await this.schema.parseAsync(arg) } catch (e) { throw new ToolInputParsingException(`Received tool input did not match expected schema`, JSON.stringify(arg)) } const callbackManager_ = await CallbackManager.configure( config.callbacks, this.callbacks, config.tags || tags, this.tags, config.metadata, this.metadata, { verbose: this.verbose } ) const runManager = await callbackManager_?.handleToolStart( this.toJSON(), typeof parsed === 'string' ? parsed : JSON.stringify(parsed), undefined, undefined, undefined, undefined, config.runName ) let result try { result = await this._call(parsed, runManager, flowConfig) } catch (e) { await runManager?.handleToolError(e) throw e } if (result && typeof result !== 'string') { result = JSON.stringify(result) } await runManager?.handleToolEnd(result) return result } // @ts-ignore protected async _call( arg: z.output, _?: CallbackManagerForToolRun, flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject } ): Promise { // Create additional sandbox variables for tool arguments const additionalSandbox: ICommonObject = {} if (typeof arg === 'object' && Object.keys(arg).length) { for (const item in arg) { additionalSandbox[`$${item}`] = arg[item] } } // Prepare flow object for sandbox const flow = this.flowObj ? { ...this.flowObj, ...flowConfig } : {} const sandbox = createCodeExecutionSandbox('', this.variables || [], flow, additionalSandbox) let response = await executeJavaScriptCode(this.code, sandbox) if (typeof response === 'object') { response = JSON.stringify(response) } return response } setVariables(variables: any[]) { this.variables = variables } setFlowObject(flow: any) { this.flowObj = flow } }