Suma Gemini Plugin (v1.0.0)
Provides Gemini AI integration for ticket urgency analysis, text rewriting, and email reply parsing. Used externally by the osTicket system and internally by the management dashboard.
Plugin Structure
suma-gemini/
├── suma-gemini.php ← Bootstrap, activation, constants
├── src/
│ ├── Gemini_Client.php ← Core API wrapper (retries, fallback)
│ ├── Models.php ← Available model definitions
│ ├── Rate_Limiter.php ← Transient-based request limiting
│ └── REST/
│ ├── Rewrite.php ← POST /suma/gemini/rewrite
│ ├── Ticket_Urgency.php ← POST /suma/gemini/ticket-urgency
│ └── Parse_Email.php ← POST /suma/gemini/parse-email-reply
├── resources/
│ ├── js/admin.js ← Admin UI scripts
│ └── css/admin.css ← Tailwind CSS styles
├── composer.json ← google-gemini-php/client ^2.0
├── package.json ← Laravel Mix + Tailwind
└── webpack.mix.js ← Asset compilation config
Gemini Client
The Gemini_Client class wraps the google-gemini-php/client library with retry logic, model fallback, and caching.
Initialization
namespace Suma\Gemini;
class Gemini_Client {
private string $api_key;
private string $model;
private int $max_retries = 3;
public function __construct() {
$this->api_key = get_option('suma-gemini_api_key');
$this->model = get_option('suma-gemini_model', 'gemini-2.5-flash');
}
}
Retry Logic with Exponential Backoff
public function generate(string $prompt, array $options = []): string {
$model = $options['model'] ?? $this->model;
$attempts = 0;
while ($attempts < $this->max_retries) {
try {
$response = $this->client->generativeModel($model)
->generateContent($prompt);
return $response->text();
} catch (Exception $exception) {
$attempts++;
if ($attempts >= $this->max_retries) {
// Try fallback model chain
return $this->try_fallback_models($prompt, $options);
}
// Exponential backoff: 1s, 2s, 4s
sleep(pow(2, $attempts - 1));
}
}
}
Model Fallback Chain
When the primary model fails after all retries, the client attempts alternative models:
Primary: gemini-2.5-flash
↓ (fail)
Fallback 1: gemini-2.0-flash
↓ (fail)
Fallback 2: gemini-1.5-flash
↓ (fail)
Error returned to caller
Available Models
The Models class defines available Gemini models:
| Model ID | Series | Best For |
|---|---|---|
gemini-2.5-flash | 2.5 | Default — fast, capable |
gemini-2.5-pro | 2.5 | Complex analysis |
gemini-2.0-flash | 2.0 | Fast fallback |
gemini-2.0-flash-lite | 2.0 | Lightweight tasks |
gemini-1.5-flash | 1.5 | Legacy fallback |
gemini-1.5-pro | 1.5 | Legacy complex |
Model Selection
- Default model: Configurable via settings (
suma-gemini_model) - Urgency model: Separate setting (
suma-gemini_urgency_model) for ticket analysis - Rewrite model: Uses default model
- Email parsing: Uses default model
Security
API Key Authentication
External requests (e.g., from osTicket) authenticate via header:
$api_key = $request->get_header('suma-gemini-security-key');
$stored_key = get_option('suma-gemini_api_key');
// Timing-safe comparison prevents timing attacks
if (!hash_equals($stored_key, $api_key)) {
return new WP_Error('unauthorized', 'Invalid API key', ['status' => 401]);
}
Rate Limiting
Transient-based rate limiting prevents abuse:
class Rate_Limiter {
private int $max_requests;
private int $window_seconds = 60;
public function check(string $client_ip): bool {
$key = 'suma_gemini_rate_' . md5($client_ip);
$count = (int) get_transient($key);
if ($count >= $this->max_requests) {
return false; // Rate limited
}
set_transient($key, $count + 1, $this->window_seconds);
return true;
}
}
Default limit: 60 requests per 60 seconds (configurable).
IP Detection
Proxy-aware IP detection for accurate rate limiting:
private function get_client_ip(): string {
// Check Cloudflare header first
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
return sanitize_text_field($_SERVER['HTTP_CF_CONNECTING_IP']);
}
// Then X-Forwarded-For
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return sanitize_text_field(trim($ips[0]));
}
return $_SERVER['REMOTE_ADDR'];
}
Ticket Urgency Analysis
Endpoint: POST /wp-json/suma/gemini/ticket-urgency
The most critical endpoint — analyzes support tickets and returns urgency data.
Scoring Algorithm
The AI prompt instructs Gemini to score tickets on a 0–100 scale with modifiers:
| Factor | Impact |
|---|---|
| Production down | +30–40 points |
| Revenue impact | +20–30 points |
| Data loss risk | +25–35 points |
| Security breach | +30–40 points |
| Multiple users affected | +10–20 points |
| VIP client | +10 points |
| Workaround available | -15 points |
| Enhancement request | -20 points |
Urgency Levels
| Level | Range | Response Expected |
|---|---|---|
| CRITICAL | 75–100 | Immediate (within 1 hour) |
| HIGH | 50–74 | Same business day |
| MEDIUM | 25–49 | Within 2 business days |
| LOW | 0–24 | Scheduled maintenance window |
Response Structure
{
"success": true,
"urgency": {
"score": 72,
"level": "HIGH",
"summary": "E-commerce checkout failing for all customers",
"reasoning": "Production payment processing is broken, directly impacting revenue for a retail client.",
"timeline": "Should be addressed within 4 hours during business hours",
"suggested_title": "Payment Gateway Failure - Checkout Broken"
}
}
Caching
Responses are cached using WordPress transients to avoid re-analyzing identical tickets:
$cache_key = 'suma_gemini_urgency_' . md5($subject . $body);
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
// ... process and cache result for 1 hour
set_transient($cache_key, $result, HOUR_IN_SECONDS);
Text Rewriting
Endpoint: POST /wp-json/suma/gemini/rewrite
Rewrites text content with optional instructions.
Use Cases
- Improving ticket response clarity
- Generating documentation summaries
- Reformatting technical notes for clients
Prompt Template
Rewrite the following text. {instructions}
Text:
{text}
Provide only the rewritten text, no explanations.
Email Reply Parsing
Endpoint: POST /wp-json/suma/gemini/parse-email-reply
Extracts the latest reply from a full email thread (strips quoted text, signatures, etc.).
Why AI Parsing?
Email clients format replies inconsistently:
- Gmail uses
>prefix or<blockquote> - Outlook uses
From: ... Sent: ...separators - Mobile clients have no consistent markers
- Signatures vary wildly
AI parsing handles all these cases reliably.
Settings (WordPress Options)
All settings are stored as WordPress options:
| Option | Default | Description |
|---|---|---|
suma-gemini_api_key | — | Google Gemini API key |
suma-gemini_security_key | — | External API authentication key |
suma-gemini_model | gemini-2.5-flash | Default generation model |
suma-gemini_urgency_model | gemini-2.5-flash | Model for urgency scoring |
suma-gemini_rate_limit | 60 | Requests per window |
suma-gemini_rate_window | 60 | Window in seconds |
suma-gemini_cache_ttl | 3600 | Cache duration (seconds) |
suma-gemini_max_retries | 3 | Max API retry attempts |
Build System
Dependencies
{
"require": {
"google-gemini-php/client": "^2.0"
}
}
Asset Compilation
# Development with hot reload
npm run watch
# Production build (minified)
npm run production
webpack.mix.js:
const mix = require('laravel-mix');
mix.js('resources/js/admin.js', 'dist/js')
.postCss('resources/css/admin.css', 'dist/css', [
require('tailwindcss'),
]);
Integration with osTicket
The ticket system calls the urgency endpoint when new tickets arrive:
1. New ticket created in osTicket
2. osTicket PHP code sends POST to /wp-json/suma/gemini/ticket-urgency
3. Includes subject, body, client_name, is_vip flag
4. Response parsed for urgency score and metadata
5. Score stored in osTicket ticket record
6. Urgency level displayed in agent interface
The API key (suma-gemini-security-key) must match between both systems.