import { PersonaProtocolBase } from './base'; import { Session, ProtocolStatus, PersonaProtocolBaseConfig, PersonaTransaction, FunctionCall, PersonaPacket } from '../types'; import { ToolInstance } from '../tools'; import _ from 'lodash'; type FinishTransactionRequest = { success: boolean; output: unknown; error: string | null; }; class PersonaTransactionsManager { private config: PersonaTransactionProtocolConfig; constructor(config: PersonaTransactionProtocolConfig) { this.config = config; } async complete(transaction: PersonaTransaction, request: FinishTransactionRequest): Promise { await this.persist(transaction, { ...request, success: true }); this.config.logger?.debug('Transaction completed:', transaction); } async fail(transaction: PersonaTransaction, request: FinishTransactionRequest): Promise { await this.persist(transaction, { ...request, success: false }); this.config.logger?.debug('Transaction failed:', { ...transaction, ...request }); } async persist(transaction: PersonaTransaction, request: FinishTransactionRequest): Promise { await fetch(`${this.config.apiUrl}/transactions/${transaction.id}`, { body: JSON.stringify(request), method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'x-persona-apikey': this.config.apiKey, }, }); } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export type PersonaToolCallback = (...args: any[]) => any; export type PersonaTools = { [key: string]: PersonaToolCallback; }; class PersonaPersistableTransaction { private transaction: PersonaTransaction; private manager: PersonaTransactionsManager; constructor(transaction: PersonaTransaction, manager: PersonaTransactionsManager) { this.transaction = transaction; this.manager = manager; } public getFunctionCall(): FunctionCall | null { return this.transaction.functionCall; } async invoke(tools: PersonaTools, toolInstance?: ToolInstance): Promise { const functionCall = this.transaction.functionCall; if (!functionCall) { await this.fail('No function call found'); return; } const functionName = functionCall.name; const functionArgs = functionCall.args; const tool = tools[functionName]; if (!tool) { await this.fail(`Tool ${functionName} not found`); return; } try { const properties = _.get(toolInstance, 'schema.config.parameters.properties', {}); const paramNames = properties && Object.keys(properties).length > 0 ? Object.keys(properties) : tool .toString() .replace(/\n/g, ' ') .match(/^[^(]*\(([^)]*)\)/)?.[1] .split(',') .map((p: string) => p.trim()) .filter(Boolean) || []; // Map arguments in order const orderedArgs = paramNames.map((name: string) => functionArgs[name]); const result = await tool(...orderedArgs); await this.complete(result); } catch (error) { await this.fail(`Error executing tool ${functionName}: ${error}`); } } async complete(output: unknown): Promise { await this.manager.complete(this.transaction, { success: true, output, error: null }); } async fail(error: string): Promise { await this.manager.fail(this.transaction, { success: false, output: null, error }); } } type PersonaTransactionCallback = (transaction: PersonaPersistableTransaction) => void; type PersonaTransactionProtocolConfig = PersonaProtocolBaseConfig & { apiUrl: string; tools: PersonaTools | ToolInstance[]; onTransaction?: PersonaTransactionCallback; }; class PersonaTransactionProtocol extends PersonaProtocolBase { status: ProtocolStatus; autostart: boolean; session: Session; config: PersonaTransactionProtocolConfig; notify: boolean = true; private _tools: PersonaTools; private _toolSchema: { [key: string]: ToolInstance } = {}; constructor(config: PersonaTransactionProtocolConfig) { super(); this.config = config; this.status = 'disconnected'; this.autostart = true; if (Array.isArray(config.tools)) { this._tools = {}; for (const tool of config.tools as ToolInstance[]) { if (tool.schema && tool.implementation) { this._tools[tool.schema.name] = tool.implementation; this._toolSchema[tool.schema.name] = tool; } } } else { this._tools = config.tools as PersonaTools; } } public getName(): string { return 'transaction'; } public getPriority(): number { return 0; } public getToolInstance(name: string): ToolInstance | undefined { return this._toolSchema[name]; } public async connect(session: Session): Promise { this.setStatus('connected'); return session; } public async disconnect(): Promise { this.setStatus('disconnected'); this.session = null; } public async syncSession(session: Session): Promise { this.session = session; } // eslint-disable-next-line @typescript-eslint/no-unused-vars public async sendPacket(_packet: PersonaPacket): Promise {} public onTransaction(transaction: PersonaTransaction): void { const manager = new PersonaTransactionsManager(this.config); const persistable = new PersonaPersistableTransaction(transaction, manager); if (this.config.onTransaction) { this.config.onTransaction(persistable); } else { const toolInstance = transaction.functionCall ? this.getToolInstance(transaction.functionCall.name) : undefined; persistable.invoke(this._tools, toolInstance); } } public getTools(): PersonaTools { return this._tools; } } export { PersonaTransactionProtocol }; export type { PersonaTransactionProtocolConfig, FinishTransactionRequest, PersonaPersistableTransaction, PersonaTransactionCallback };