PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
← All changes | app/Services/Integrations/FluentBot/FluentBotAPI.php +9 -22 2.4.0 → 2.4.5 View file →
@@ -54,9 +54,8 @@
54 54 $tokenUsage = $responseBody['token_usage'] ?? [];
55 55 $totalTokens = ($tokenUsage['input_tokens'] ?? 0) + ($tokenUsage['output_tokens'] ?? 0);
56 56 do_action('fluent_support/ai_response_success', $ticketId, $prompt, $totalTokens, "FluentBot");
57 57
58 - // Return both content and chat_id if available
59 58 return [
60 59 'content' => $content,
61 60 'chat_id' => $responseBody['chat_id'] ?? null
62 61 ];
@@ -65,10 +64,9 @@
65 64 public function makeStreamRequest(int $ticketId, $prompt, array $args = [])
66 65 {
67 66 $timeout = apply_filters('fs_ai_request_timeout', 120);
68 67
69 - // Use cURL for streaming
70 - // Note: Using cURL here because WordPress HTTP API doesn't support streaming SSE responses
68 + // Raw cURL because the WP HTTP API can't stream SSE.
71 69 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init
72 70 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_setopt
73 71 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_exec
74 72 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_close
@@ -90,9 +88,8 @@
90 88 $streamTokens = 0;
91 89
92 90 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$buffer, &$conversationId, &$streamTokens) {
93 91 foreach ($this->drainSseChunk($data, $buffer) as $parsed) {
94 - // Store chat_id for later use
95 92 if ($parsed['event'] === 'chat_id' && !empty($parsed['data'])) {
96 93 $conversationId = $parsed['data'][0];
97 94 } elseif ($parsed['event'] === 'token_usage' && !empty($parsed['data'])) {
98 95 $usage = json_decode($parsed['data'][0], true);
@@ -143,9 +140,8 @@
143 140 private function drainSseChunk(string $data, string &$buffer): array
144 141 {
145 142 $buffer .= $data;
146 143
147 - // Process complete SSE events from the AI API
148 144 $events = explode("\n\n", $buffer);
149 145 $buffer = array_pop($events); // Keep incomplete event in buffer
150 146
151 147 $parsed = [];
@@ -154,9 +150,9 @@
154 150 if (!trim($event)) {
155 151 continue;
156 152 }
157 153
158 - // SSE comment/keepalive (starts ":"): forward raw so proxies keep seeing bytes, no idle-timeout.
154 + // Forward keepalive comments as-is so proxies don't hit an idle timeout.
159 155 if (strpos(ltrim($event), ':') === 0) {
160 156 echo $event . "\n\n";
161 157 flush();
162 158 continue;
@@ -176,9 +172,8 @@
176 172 $eventDataLines[] = (string)substr($line, 6);
177 173 }
178 174 }
179 175
180 - // Forward the event to the browser with proper formatting
181 176 if (!$eventType) {
182 177 continue;
183 178 }
184 179
@@ -183,14 +178,12 @@
183 178 }
184 179
185 180 echo "event: ".esc_html($eventType)."\n";
186 181
187 - // Include ID if present
188 182 if ($eventId !== '') {
189 183 echo "id: ".esc_html($eventId)."\n";
190 184 }
191 185
192 - // Handle multiple data lines properly
193 186 if (!empty($eventDataLines)) {
194 187 foreach ($eventDataLines as $dataLine) {
195 188 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw SSE payload from trusted bot endpoint; rendered output sanitized client-side
196 189 echo "data: ".$dataLine."\n";
@@ -208,16 +201,14 @@
208 201 return $parsed;
209 202 }
210 203
211 204 /**
212 - * Reconnect to an in-flight turn: replay its buffered SSE and tail it live. Same wire
213 - * format as makeStreamRequest. Upstream ends on `__done__` or an `idle` frame; a total
214 - * cap still applies — see the timeout block below.
205 + * Reconnect to an in-flight turn: replay its buffered SSE and tail it live.
206 + * Same wire format as makeStreamRequest. Upstream ends on `__done__` or an `idle` frame.
215 207 */
216 208 public function makeResumeStreamRequest()
217 209 {
218 - // Use cURL for streaming
219 - // Note: Using cURL here because WordPress HTTP API doesn't support streaming SSE responses
210 + // Raw cURL because the WP HTTP API can't stream SSE.
220 211 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init
221 212 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_setopt
222 213 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_exec
223 214 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_close
@@ -235,10 +226,9 @@
235 226
236 227 $buffer = '';
237 228
238 229 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$buffer) {
239 - // Stop as soon as the agent navigates away — nothing here needs to
240 - // outlive the client, the turn itself is persisted upstream.
230 + // Stop when the agent leaves. The turn itself is persisted upstream.
241 231 if (connection_aborted()) {
242 232 return 0;
243 233 }
244 234
@@ -246,16 +236,13 @@
246 236
247 237 return strlen($data);
248 238 });
249 239
250 - // Bounded on purpose: connection_aborted() can lag (abort travels browser ->
251 - // proxy -> fluent-bot, Apache buffers writes), so a refresh mid-turn can leave
252 - // the old tail pinning a worker. The cap makes that self-limiting — the client
253 - // treats a cut tail as a body ended without __done__ and refetches.
240 + // connection_aborted() can lag behind proxies and buffering, so cap the tail
241 + // or a refresh mid-turn can pin a worker. The client refetches a cut tail.
254 242 curl_setopt($ch, CURLOPT_TIMEOUT, apply_filters('fs_ai_resume_timeout', 120));
255 243 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
256 - // Abort sooner when genuinely stalled: the upstream sends ': keepalive'
257 - // during quiet gaps, so a healthy tail always beats this window.
244 + // Upstream sends keepalives during quiet gaps, so a stall here means it's dead.
258 245 curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 1);
259 246 curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, apply_filters('fs_ai_resume_stall_timeout', 60));
260 247 curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1);
261 248 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);