Skip to main content

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 IDSeriesBest For
gemini-2.5-flash2.5Default — fast, capable
gemini-2.5-pro2.5Complex analysis
gemini-2.0-flash2.0Fast fallback
gemini-2.0-flash-lite2.0Lightweight tasks
gemini-1.5-flash1.5Legacy fallback
gemini-1.5-pro1.5Legacy 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:

FactorImpact
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

LevelRangeResponse Expected
CRITICAL75–100Immediate (within 1 hour)
HIGH50–74Same business day
MEDIUM25–49Within 2 business days
LOW0–24Scheduled 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:

OptionDefaultDescription
suma-gemini_api_keyGoogle Gemini API key
suma-gemini_security_keyExternal API authentication key
suma-gemini_modelgemini-2.5-flashDefault generation model
suma-gemini_urgency_modelgemini-2.5-flashModel for urgency scoring
suma-gemini_rate_limit60Requests per window
suma-gemini_rate_window60Window in seconds
suma-gemini_cache_ttl3600Cache duration (seconds)
suma-gemini_max_retries3Max 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.