PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / trunk
AI Engine – The Chatbot, AI Framework & MCP for WordPress vtrunk
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / services / message-builder.php
ai-engine / classes / services Last commit date
image.php 1 day ago message-builder.php 3 weeks ago model-environment.php 7 months ago response-id-manager.php 1 day ago session.php 11 months ago usage-stats.php 1 month ago
message-builder.php
384 lines
1 <?php
2
3 /**
4 * Service for building and transforming messages for different AI APIs.
5 *
6 * Simplifies the complex message building logic by breaking it down
7 * into smaller, focused methods.
8 */
9 class Meow_MWAI_Services_MessageBuilder {
10 private Meow_MWAI_Core $core;
11
12 public function __construct( Meow_MWAI_Core $core ) {
13 $this->core = $core;
14 }
15
16 /**
17 * Build messages array for Responses API format
18 */
19 public function build_responses_api_messages( Meow_MWAI_Query_Base $query ): array {
20 $messages = [];
21
22 // Handle different query types
23 if ( $query instanceof Meow_MWAI_Query_Feedback ) {
24 $messages = $this->build_feedback_messages( $query );
25 }
26 else {
27 $messages = $this->convert_messages_to_responses_format( $query->messages );
28 }
29
30 // Add user message with attachments if needed
31 if ( !( $query instanceof Meow_MWAI_Query_Feedback ) ) {
32 $messages = $this->add_user_message_with_attachments( $messages, $query );
33 }
34
35 return $messages;
36 }
37
38 /**
39 * Build messages for feedback queries
40 */
41 private function build_feedback_messages( Meow_MWAI_Query_Feedback $query ): array {
42 $messages = [];
43
44 // Convert existing messages
45 $messages = $this->convert_messages_to_responses_format( $query->messages );
46
47 // Process feedback blocks
48 if ( !empty( $query->blocks ) ) {
49 $messages = $this->add_feedback_results( $messages, $query->blocks );
50 }
51
52 return $messages;
53 }
54
55 /**
56 * Convert role-based messages to Responses API format
57 */
58 private function convert_messages_to_responses_format( array $messages ): array {
59 $converted = [];
60
61 foreach ( $messages as $message ) {
62 if ( !isset( $message['role'] ) ) {
63 // Already in Responses API format
64 $converted[] = $message;
65 continue;
66 }
67
68 // Handle assistant messages with tool calls
69 if ( $message['role'] === 'assistant' && isset( $message['tool_calls'] ) ) {
70 $converted = array_merge(
71 $converted,
72 $this->convert_assistant_with_tools( $message )
73 );
74 }
75 else {
76 // Regular messages stay as-is
77 $converted[] = $message;
78 }
79 }
80
81 return $converted;
82 }
83
84 /**
85 * Convert assistant message with tool calls to separate messages
86 */
87 private function convert_assistant_with_tools( array $message ): array {
88 $messages = [];
89
90 // Add assistant text if present
91 if ( !empty( $message['content'] ) ) {
92 $messages[] = [
93 'role' => 'assistant',
94 'content' => $message['content']
95 ];
96 }
97
98 // Convert each tool call to function_call message
99 if ( isset( $message['tool_calls'] ) ) {
100 foreach ( $message['tool_calls'] as $toolCall ) {
101 $functionCall = Meow_MWAI_Data_FunctionCall::from_tool_call( $toolCall, $message );
102 $messages[] = [
103 'type' => 'function_call',
104 'call_id' => $functionCall->id,
105 'name' => $functionCall->name,
106 'arguments' => $functionCall->get_arguments_json()
107 ];
108 }
109 }
110
111 return $messages;
112 }
113
114 /**
115 * Add feedback results to messages
116 */
117 private function add_feedback_results( array $messages, array $blocks ): array {
118 $functionResults = [];
119 $processedCallIds = [];
120
121 foreach ( $blocks as $block ) {
122 if ( !isset( $block['feedbacks'] ) ) {
123 continue;
124 }
125
126 foreach ( $block['feedbacks'] as $feedback ) {
127 $toolId = $feedback['request']['toolId'] ?? null;
128
129 // Skip duplicates
130 if ( !$toolId || in_array( $toolId, $processedCallIds ) ) {
131 continue;
132 }
133
134 // Create function result object
135 $result = Meow_MWAI_Data_FunctionResult::success(
136 $toolId,
137 $feedback['reply']['value'] ?? null
138 );
139
140 $functionResults[] = $result->to_responses_api_format();
141 $processedCallIds[] = $toolId;
142 }
143 }
144
145 // Add function results at the end
146 return array_merge( $messages, $functionResults );
147 }
148
149 /**
150 * Add user message with attachments
151 */
152 private function add_user_message_with_attachments( array $messages, Meow_MWAI_Query_Base $query ): array {
153 // Get all attachments using the unified method
154 $attachments = method_exists( $query, 'getAttachments' ) ? $query->getAttachments() : [];
155
156 if ( empty( $attachments ) ) {
157 // Simple text message
158 $messages[] = [
159 'role' => 'user',
160 'content' => [
161 [
162 'type' => 'input_text',
163 'text' => $query->get_message()
164 ]
165 ]
166 ];
167 }
168 else {
169 // Message with file/image attachment(s). The text part is skipped when the
170 // message is empty (an image can be sent without text).
171 $content = [];
172 $message = $query->get_message();
173 if ( $message !== null && $message !== '' ) {
174 $content[] = [
175 'type' => 'input_text',
176 'text' => $message
177 ];
178 }
179
180 // Process all attachments
181 foreach ( $attachments as $file ) {
182 // Check file type to determine how to handle it
183 // Images can be loaded via URL or base64, but PDFs use OpenAI file_id references
184 $mimeType = $file->get_mimeType() ?? '';
185 $isImage = strpos( $mimeType, 'image/' ) === 0;
186
187 if ( $isImage ) {
188 $fileUrl = $query->image_remote_upload === 'url'
189 ? $file->get_url()
190 : $file->get_inline_base64_url();
191
192 $content[] = [
193 'type' => 'input_image',
194 'image_url' => $fileUrl
195 ];
196 }
197 else {
198 // For non-images (PDFs, documents), use file_id reference
199 $fileId = $file->get_refId();
200
201 if ( !$fileId ) {
202 // File should have been uploaded by the engine before message building
203 // If we get here, something went wrong - log and skip this file
204 error_log( '[AI Engine] MessageBuilder: File without file_id encountered (upload should happen in engine)' );
205 continue;
206 }
207
208 // File was uploaded to OpenAI, use file_id reference
209 // Responses API format: { type: 'input_file', file_id: 'file-xxx' }
210 $content[] = [
211 'type' => 'input_file',
212 'file_id' => $fileId
213 ];
214 }
215 }
216
217 // If every attachment was skipped and there was no message, fall back to an
218 // empty text part rather than sending an invalid empty content array.
219 if ( empty( $content ) ) {
220 $content[] = [ 'type' => 'input_text', 'text' => '' ];
221 }
222
223 $messages[] = [
224 'role' => 'user',
225 'content' => $content
226 ];
227 }
228
229 return $messages;
230 }
231
232 /**
233 * Build feedback-only messages for Responses API with previous_response_id
234 */
235 public function build_feedback_only_messages( Meow_MWAI_Query_Feedback $query ): array {
236 $messages = [];
237
238 if ( empty( $query->blocks ) ) {
239 return $messages;
240 }
241
242 // For Responses API with previous_response_id, we should ONLY send function_call_output messages.
243 // The API already knows about the function_call messages from the previous response.
244 // According to OpenAI documentation, we should NOT echo back the function_call messages.
245
246 foreach ( $query->blocks as $block ) {
247 if ( !isset( $block['feedbacks'] ) || empty( $block['feedbacks'] ) ) {
248 continue;
249 }
250
251 // Get the rawMessage from the first feedback (they should all have the same rawMessage)
252 $rawMessage = $block['feedbacks'][0]['request']['rawMessage'] ?? null;
253
254 if ( !$rawMessage || !isset( $rawMessage['tool_calls'] ) ) {
255 continue;
256 }
257
258 // Process ALL tool calls from the rawMessage in order
259 // But ONLY add the function_call_output messages (not the function_call messages)
260 foreach ( $rawMessage['tool_calls'] as $toolCall ) {
261 $callId = $toolCall['id'];
262
263 // Find and add the corresponding function result
264 // We do NOT add the function_call message when using previous_response_id
265 $foundResult = false;
266 foreach ( $block['feedbacks'] as $feedback ) {
267 if ( ( $feedback['request']['toolId'] ?? null ) === $callId ) {
268 $result = Meow_MWAI_Data_FunctionResult::success( $callId, $feedback['reply']['value'] ?? '' );
269 $messages[] = $result->to_responses_api_format();
270 $foundResult = true;
271 break;
272 }
273 }
274
275 if ( !$foundResult ) {
276 // This should not happen, but if we can't find the result, add an error result
277 $result = Meow_MWAI_Data_FunctionResult::failure( $callId, 'Function result not found' );
278 $messages[] = $result->to_responses_api_format();
279 }
280 }
281 }
282
283 return $messages;
284 }
285
286 /**
287 * Build messages for Chat Completions API
288 */
289 public function build_chat_completions_messages( Meow_MWAI_Query_Base $query ): array {
290 $messages = [];
291
292 // Add system message if present
293 if ( !empty( $query->instructions ) ) {
294 $messages[] = [
295 'role' => 'system',
296 'content' => $query->instructions
297 ];
298 }
299
300 // Add conversation messages
301 if ( !empty( $query->messages ) ) {
302 $messages = array_merge( $messages, $query->messages );
303 }
304
305 // Handle feedback queries - add function results
306 if ( $query instanceof Meow_MWAI_Query_Feedback && !empty( $query->blocks ) ) {
307 foreach ( $query->blocks as $block ) {
308 if ( !isset( $block['feedbacks'] ) ) {
309 continue;
310 }
311
312 foreach ( $block['feedbacks'] as $feedback ) {
313 $messages[] = [
314 'role' => 'tool',
315 'tool_call_id' => $feedback['request']['toolId'],
316 'content' => json_encode( $feedback['reply']['value'] ?? '' )
317 ];
318 }
319 }
320 }
321
322 // Add user message (if not a feedback query)
323 if ( !( $query instanceof Meow_MWAI_Query_Feedback ) ) {
324 $userMessage = $this->build_user_message( $query );
325 if ( $userMessage ) {
326 $messages[] = $userMessage;
327 }
328 }
329
330 return $messages;
331 }
332
333 /**
334 * Build user message for Chat Completions API
335 */
336 private function build_user_message( Meow_MWAI_Query_Base $query ): ?array {
337 $message = $query->get_message();
338 if ( empty( $message ) ) {
339 return null;
340 }
341
342 // Get all attachments using the unified method
343 $attachments = method_exists( $query, 'getAttachments' ) ? $query->getAttachments() : [];
344
345 // Handle image attachments (for Chat Completions API)
346 if ( !empty( $attachments ) ) {
347 $content = [
348 [
349 'type' => 'text',
350 'text' => $message
351 ]
352 ];
353
354 // Add first image attachment (Chat Completions format)
355 foreach ( $attachments as $file ) {
356 if ( $file->get_type() === 'image' ) {
357 $imageUrl = $query->image_remote_upload === 'url'
358 ? $file->get_url()
359 : $file->get_inline_base64_url();
360
361 $content[] = [
362 'type' => 'image_url',
363 'image_url' => [
364 'url' => $imageUrl
365 ]
366 ];
367 break; // Chat Completions typically handles one image
368 }
369 }
370
371 return [
372 'role' => 'user',
373 'content' => $content
374 ];
375 }
376
377 // Simple text message
378 return [
379 'role' => 'user',
380 'content' => $message
381 ];
382 }
383 }
384