> */ private array $trace = array(); /** * Constructor. */ public function __construct() { $this->memory = new Aivexa_Memory(); } /** * Process a chat message. * * @param string $message User message. * @param string $conversation_id Optional conversation ID. * @param bool $include_trace Include debug trace. * @return array|WP_Error */ public function chat( string $message, string $conversation_id = '', bool $include_trace = false ) { $this->trace = array(); Aivexa_Abilities::set_latest_user_message( $message ); if ( ! Aivexa_Client::is_available() ) { return new WP_Error( 'aivexa_ai_unavailable', __( 'The WordPress AI Client is not available. Aivexa requires WordPress 7.0 or later.', 'aivexa' ) ); } if ( ! Aivexa_Client::is_provider_configured() ) { return new WP_Error( 'aivexa_not_configured', __( 'No AI provider is configured. Open Settings → Connectors to connect a provider.', 'aivexa' ) ); } $conversation = null; if ( '' !== $conversation_id ) { $conversation = $this->memory->get_conversation( $conversation_id ); } if ( null === $conversation ) { $conversation_id = $this->memory->create_conversation(); $conversation = $this->memory->get_conversation( $conversation_id ); } if ( null === $conversation ) { return new WP_Error( 'aivexa_conversation_error', __( 'Unable to start a conversation.', 'aivexa' ) ); } $prior_rows = isset( $conversation['messages'] ) && is_array( $conversation['messages'] ) ? $conversation['messages'] : array(); $this->memory->append_message( $conversation_id, array( 'role' => 'user', 'content' => $message, 'timestamp' => time(), ) ); $ability_names = Aivexa_Abilities::enabled_ability_names(); $history = Aivexa_Client::messages_from_history( $prior_rows ); $builder = Aivexa_Client::base_prompt( $message ); if ( is_wp_error( $builder ) ) { $this->increment_stat( 'errors' ); return $builder; } if ( ! empty( $history ) ) { $builder = $builder->with_history( ...$history ); } if ( ! empty( $ability_names ) ) { $builder = $builder->using_abilities( ...$ability_names ); } $result = $builder->generate_text_result(); if ( is_wp_error( $result ) ) { $this->increment_stat( 'errors' ); return Aivexa_Client::friendly_error( $result ); } $resolver = null; if ( ! empty( $ability_names ) && class_exists( 'WP_AI_Client_Ability_Function_Resolver' ) ) { $resolver = new WP_AI_Client_Ability_Function_Resolver( ...$ability_names ); } $iteration = 0; $assistant_message = $result->toMessage(); $history_chain = array_merge( $history, array( new UserMessage( array( new MessagePart( $message ) ) ) ) ); while ( $resolver instanceof WP_AI_Client_Ability_Function_Resolver && $resolver->has_ability_calls( $assistant_message ) && $iteration < self::MAX_TOOL_ITERATIONS ) { $this->record_ability_trace( $assistant_message ); $this->increment_stat( 'tool_calls' ); $function_response_message = $resolver->execute_abilities( $assistant_message ); $history_chain[] = $assistant_message; $history_chain[] = $function_response_message; $follow_up = Aivexa_Client::base_prompt( null ); if ( is_wp_error( $follow_up ) ) { $this->increment_stat( 'errors' ); return $follow_up; } $follow_up = $follow_up->with_history( ...$history_chain ); if ( ! empty( $ability_names ) ) { $follow_up = $follow_up->using_abilities( ...$ability_names ); } $result = $follow_up->generate_text_result(); if ( is_wp_error( $result ) ) { $this->increment_stat( 'errors' ); return Aivexa_Client::friendly_error( $result ); } $assistant_message = $result->toMessage(); ++$iteration; } $assistant_text = Aivexa_Client::extract_text( $result ); if ( '' === $assistant_text ) { $this->increment_stat( 'errors' ); return new WP_Error( 'aivexa_empty_response', __( 'Sorry, the AI assistant is temporarily unavailable. Please try again later.', 'aivexa' ) ); } $this->memory->append_message( $conversation_id, array( 'role' => 'assistant', 'content' => $assistant_text, 'timestamp' => time(), ) ); $this->increment_stat( 'total_requests' ); $this->update_last_request_time(); $response = array( 'success' => true, 'response' => $assistant_text, 'conversation_id' => $conversation_id, ); if ( $include_trace ) { $response['trace'] = $this->trace; } return $response; } /** * Enabled tool map for admin UI. * * @return array */ public function get_tools(): array { $tools = array(); foreach ( Aivexa_Abilities::tool_instances() as $tool ) { if ( $tool->is_enabled() ) { $tools[ $tool->get_name() ] = $tool; } } return $tools; } /** * Ability declarations for admin tooling / tests. * * @return array> */ public function get_tool_definitions(): array { $out = array(); foreach ( Aivexa_Abilities::tool_instances() as $tool ) { if ( ! $tool->is_enabled() ) { continue; } $out[] = array( 'type' => 'function', 'name' => $tool->get_name(), 'description' => $tool->get_description(), 'parameters' => $tool->get_parameters_schema(), 'ability' => 'aivexa/' . str_replace( '_', '-', $tool->get_name() ), ); } return $out; } /** * Record ability calls in the admin trace. * * @param Message $message Assistant message. */ private function record_ability_trace( Message $message ): void { foreach ( Aivexa_Client::extract_function_calls( $message ) as $call ) { $this->trace[] = array( 'tool' => (string) $call->getName(), 'arguments' => Aivexa_Security::redact_sensitive( $call->getArgs() ), 'result' => __( 'Executed via WordPress Abilities API', 'aivexa' ), ); } } /** * Increment usage stat. * * @param string $key Stat key. */ private function increment_stat( string $key ): void { $stats = get_option( 'aivexa_stats', array() ); if ( ! is_array( $stats ) ) { $stats = array(); } $stats[ $key ] = isset( $stats[ $key ] ) ? (int) $stats[ $key ] + 1 : 1; update_option( 'aivexa_stats', $stats, false ); } /** * Update last request timestamp. */ private function update_last_request_time(): void { $stats = get_option( 'aivexa_stats', array() ); if ( ! is_array( $stats ) ) { $stats = array(); } $stats['last_request_time'] = time(); update_option( 'aivexa_stats', $stats, false ); } }