Summary
The published npm package praisonai exports createAgentLoop(), whose onToolCall callback is documented and exampled as an approval hook. The implementation calls PraisonAI's generateText() wrapper with the caller's executable tools first, receives toolResults, and only then calls onToolCall().
Because AI SDK generateText() executes tools with an execute function as part of the generation call, onToolCall can deny a tool only after the sensitive side effect has already happened. PraisonAI then returns finishReason: "tool_rejected", which is a false security signal: the rejected tool already ran.
The PoV is deterministic and local-only. It uses mock AI SDK modules, no live model call, no API key, and no network target. The tool increments an in-memory counter rather than touching the filesystem or executing commands.
Technical Details
In src/praisonai-ts/src/ai/agent-loop.ts, the public config says:
/** On tool call callback (for approval) */
onToolCall?: (toolCall: ToolCallInfo) => Promise<boolean>;
The inline approval example also asks a user for approval and returns the decision:
onToolCall: async (toolCall) => {
const approved = await askUserForApproval(toolCall);
return approved;
}
However, AgentLoop.step() calls generateText() with the executable tools before invoking onToolCall:
const result = await generateText({
model: this.config.model,
messages: this.messages as any,
tools: this.config.tools,
maxSteps: 1,
});
It then materializes toolResults:
toolResults: result.toolResults.map(tr => ({
toolCallId: tr.toolCallId,
toolName: tr.toolName,
result: tr.result,
})),
Only afterward does the approval callback run: