# ai-builder/2.7.8/includes/class-agent-execution-service.php

AI Builder – Generate pages, blocks, images &amp; translate with AI, version 2.7.8. 455 lines.

- Page: https://pluginprobe.com/plugins/ai-builder/2.7.8/code/includes/class-agent-execution-service.php
- Raw: https://pluginprobe.com/plugins/ai-builder/2.7.8/raw/includes/class-agent-execution-service.php
- Modified: 2025-12-03T17:16:46+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ai-builder/2.7.8/code/includes/class-agent-execution-service.php#L10-L20`.

```php
<?php
/**
 * Agent Execution Service
 * 
 * Executes whitelisted WordPress REST API routes internally
 * without making external HTTP requests.
 *
 * @package AI_Builder
 */

if (!defined('ABSPATH')) {
    exit;
}

class AIBUI_Agent_Execution_Service
{
    /**
     * Security service instance
     *
     * @var AIBUI_Agent_Security_Service
     */
    private $security;

    /**
     * Discovery service instance
     *
     * @var AIBUI_Agent_Discovery_Service
     */
    private $discovery;

    /**
     * Execution log for debugging
     *
     * @var array
     */
    private $execution_log = array();

    /**
     * Maximum executions per request (prevent infinite loops)
     */
    const MAX_EXECUTIONS = 20;

    /**
     * Current execution count
     *
     * @var int
     */
    private $execution_count = 0;

    /**
     * Constructor
     *
     * @param AIBUI_Agent_Security_Service $security
     * @param AIBUI_Agent_Discovery_Service $discovery
     */
    public function __construct(
        AIBUI_Agent_Security_Service $security,
        AIBUI_Agent_Discovery_Service $discovery
    ) {
        $this->security = $security;
        $this->discovery = $discovery;
    }

    /**
     * Execute a tool call from the AI
     *
     * @param string $tool_name Name of the tool (generated from route)
     * @param array $params Parameters for the tool
     * @return array Result with 'success', 'data' or 'error'
     */
    public function execute_tool($tool_name, array $params = array())
    {
        // Check execution limit
        if ($this->execution_count >= self::MAX_EXECUTIONS) {
            return $this->error_response(
                'EXECUTION_LIMIT_REACHED',
                'Maximum number of tool executions reached in this request'
            );
        }
        $this->execution_count++;

        // Validate the tool exists and is whitelisted
        $route_meta = $this->security->validate_tool($tool_name);
        
        if (!$route_meta) {
            return $this->error_response(
                'TOOL_NOT_ALLOWED',
                "Tool '{$tool_name}' is not available or not whitelisted"
            );
        }

        $route = $route_meta['route'];
        $method = $route_meta['method'];

        // Log execution attempt
        $this->log_execution('attempt', $tool_name, array(
            'route' => $route,
            'method' => $method,
            'params' => $params,
        ));

        // Execute the route
        return $this->execute_route($route, $method, $params);
    }

    /**
     * Execute a REST API route internally
     *
     * @param string $route Route path
     * @param string $method HTTP method
     * @param array $params Parameters
     * @return array Result
     */
    public function execute_route($route, $method, array $params = array())
    {
        // Security check 1: Is route whitelisted?
        if (!$this->security->is_route_whitelisted($route, $method)) {
            // Try to match parameterized route
            $matched_route = $this->match_parameterized_route($route, $method);
            if (!$matched_route || !$this->security->is_route_whitelisted($matched_route, $method)) {
                return $this->error_response(
                    'ROUTE_NOT_WHITELISTED',
                    "Route '{$method} {$route}' is not whitelisted for AI agent access"
                );
            }
        }

        // Security check 2: Is route forbidden for this HTTP method?
        if ($this->discovery->is_forbidden_route($route, $method)) {
            return $this->error_response(
                'ROUTE_FORBIDDEN',
                "Route '{$route}' is forbidden for security reasons"
            );
        }

        // Build the request
        $request = $this->build_request($route, $method, $params);

        // Execute via REST API
        try {
            $response = rest_do_request($request);
            return $this->process_response($response);
        } catch (Exception $e) {
            $this->log_execution('error', $route, array(
                'method' => $method,
                'error' => $e->getMessage(),
            ));

            return $this->error_response(
                'EXECUTION_FAILED',
                'Failed to execute request: ' . $e->getMessage()
            );
        }
    }

    /**
     * Build a WP_REST_Request object
     *
     * @param string $route Route path
     * @param string $method HTTP method
     * @param array $params Parameters
     * @return WP_REST_Request
     */
    private function build_request($route, $method, array $params)
    {
        $request = new WP_REST_Request($method, $route);

        // Separate URL params from body params
        $url_params = array();
        $body_params = array();

        // Extract URL parameters from route pattern
        $url_param_names = $this->extract_url_param_names($route);

        foreach ($params as $key => $value) {
            if (in_array($key, $url_param_names)) {
                $url_params[$key] = $value;
            } else {
                $body_params[$key] = $value;
            }
        }

        // Set URL parameters
        foreach ($url_params as $key => $value) {
            $request->set_url_params(array($key => $value));
        }

        // Set query or body parameters based on method
        if (in_array($method, array('GET', 'HEAD', 'DELETE'))) {
            $request->set_query_params($body_params);
        } else {
            $request->set_body_params($body_params);
        }

        // Set content type for POST/PUT/PATCH
        if (in_array($method, array('POST', 'PUT', 'PATCH'))) {
            $request->set_header('Content-Type', 'application/json');
        }

        return $request;
    }

    /**
     * Extract URL parameter names from route pattern
     *
     * @param string $route
     * @return array
     */
    private function extract_url_param_names($route)
    {
        $names = array();
        if (preg_match_all('/\(\?P<([^>]+)>[^)]+\)/', $route, $matches)) {
            $names = $matches[1];
        }
        return $names;
    }

    /**
     * Match a concrete path to a parameterized route pattern
     *
     * @param string $path Concrete path (e.g., /wp/v2/posts/123)
     * @param string $method HTTP method
     * @return string|null Matched route pattern or null
     */
    private function match_parameterized_route($path, $method)
    {
        $routes = $this->security->get_enabled_routes();

        foreach ($routes as $route) {
            if ($route['method'] !== strtoupper($method)) {
                continue;
            }

            // Convert route pattern to regex for matching
            $pattern = '#^' . $route['route'] . '$#';
            if (preg_match($pattern, $path)) {
                return $route['route'];
            }
        }

        return null;
    }

    /**
     * Process the REST API response
     *
     * @param WP_REST_Response $response
     * @return array
     */
    private function process_response($response)
    {
        $data = $response->get_data();
        $status = $response->get_status();
        $headers = $response->get_headers();

        // Check for errors
        if ($status >= 400) {
            $error_message = 'Request failed';
            $error_code = 'REQUEST_FAILED';

            if (is_wp_error($data)) {
                $error_message = $data->get_error_message();
                $error_code = $data->get_error_code();
            } elseif (isset($data['message'])) {
                $error_message = $data['message'];
                $error_code = isset($data['code']) ? $data['code'] : 'API_ERROR';
            }

            $this->log_execution('api_error', 'response', array(
                'status' => $status,
                'error' => $error_message,
            ));

            return array(
                'success' => false,
                'error' => array(
                    'code' => $error_code,
                    'message' => $error_message,
                    'status' => $status,
                ),
            );
        }

        // Log success
        $this->log_execution('success', 'response', array(
            'status' => $status,
            'data_type' => gettype($data),
        ));

        // Truncate large responses for AI context
        $processed_data = $this->truncate_response_data($data);

        return array(
            'success' => true,
            'data' => $processed_data,
            'meta' => array(
                'status' => $status,
                'total_results' => $this->count_results($data),
            ),
        );
    }

    /**
     * Truncate large response data to prevent token overflow
     *
     * @param mixed $data
     * @param int $max_items Maximum items in arrays
     * @return mixed
     */
    private function truncate_response_data($data, $max_items = 10)
    {
        if (is_array($data)) {
            // Check if it's a list of items
            if (isset($data[0]) && is_array($data[0])) {
                $total = count($data);
                $data = array_slice($data, 0, $max_items);
                
                // Add truncation notice
                if ($total > $max_items) {
                    $data[] = array(
                        '_truncated' => true,
                        '_message' => sprintf('Showing %d of %d total results', $max_items, $total),
                    );
                }
            }

            // Recursively process nested arrays
            foreach ($data as $key => $value) {
                if (is_array($value)) {
                    // Remove potentially large/unnecessary fields
                    $fields_to_remove = array(
                        'content', 'rendered', 'raw', 'guid', '_links',
                        'yoast_head', 'yoast_head_json',
                    );
                    
                    foreach ($fields_to_remove as $field) {
                        if (isset($value[$field]) && is_string($value[$field]) && strlen($value[$field]) > 500) {
                            $value[$field] = substr($value[$field], 0, 500) . '... [truncated]';
                        }
                    }
                    
                    $data[$key] = $value;
                }
            }
        }

        return $data;
    }

    /**
     * Count results in response data
     *
     * @param mixed $data
     * @return int|null
     */
    private function count_results($data)
    {
        if (is_array($data) && isset($data[0])) {
            return count($data);
        }
        return null;
    }

    /**
     * Create error response
     *
     * @param string $code
     * @param string $message
     * @return array
     */
    private function error_response($code, $message)
    {
        return array(
            'success' => false,
            'error' => array(
                'code' => $code,
                'message' => $message,
            ),
        );
    }

    /**
     * Log execution for debugging
     *
     * @param string $type
     * @param string $context
     * @param array $data
     */
    private function log_execution($type, $context, array $data = array())
    {
        $this->execution_log[] = array(
            'type' => $type,
            'context' => $context,
            'data' => $data,
            'timestamp' => microtime(true),
        );
    }

    /**
     * Get execution log
     *
     * @return array
     */
    public function get_execution_log()
    {
        return $this->execution_log;
    }

    /**
     * Reset execution counter (for new request)
     */
    public function reset_execution_count()
    {
        $this->execution_count = 0;
        $this->execution_log = array();
    }

    /**
     * Get current execution count
     *
     * @return int
     */
    public function get_execution_count()
    {
        return $this->execution_count;
    }

    /**
     * Execute multiple tool calls in sequence
     *
     * @param array $tool_calls Array of ['tool_name' => ..., 'params' => ...]
     * @return array Results for each tool call
     */
    public function execute_tool_calls(array $tool_calls)
    {
        $results = array();

        foreach ($tool_calls as $index => $call) {
            if (!isset($call['tool_name'])) {
                $results[$index] = $this->error_response(
                    'INVALID_TOOL_CALL',
                    'Tool call missing tool_name'
                );
                continue;
            }

            $params = isset($call['params']) ? $call['params'] : array();
            $results[$index] = $this->execute_tool($call['tool_name'], $params);
        }

        return $results;
    }
}


```
