Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
GeminiAPI
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 6
240
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 post
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 requestToCurl
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 postCompletions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 refreshAccessToken
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getAIAccessToken
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3namespace App\Services\FlyMsgAI;
4
5use App\Helpers\FlyMSGLogger;
6use Exception;
7use Google\Client as GoogleClient;
8use GuzzleHttp\Client;
9use GuzzleHttp\Exception\GuzzleException;
10use GuzzleHttp\Psr7\Request;
11use Illuminate\Support\Facades\Cache;
12use Illuminate\Support\Facades\Log;
13use Psr\Http\Message\ResponseInterface;
14
15class GeminiAPI
16{
17    const FOURTY_MINUTES = 2400;
18
19    private $client;
20
21    private $headers;
22
23    private $baseURL = 'https://us-central1-aiplatform.googleapis.com/v1/projects/project-romeo/locations/us-central1/publishers/google/models/';
24    // private  $baseURL = "https://aiplatform.googleapis.com/v1/projects/project-romeo/locations/global/publishers/google/models/";
25
26    /**
27     * @throws Exception
28     */
29    public function __construct($accessToken)
30    {
31        if (! $accessToken) {
32            throw new Exception('Access token is required. gcloud auth print-access-token');
33        }
34        $this->client = new Client;
35        $this->headers = [
36            'Authorization' => "Bearer $accessToken",
37            'Content-Type' => 'application/json',
38        ];
39    }
40
41    /**
42     * @throws GuzzleException
43     */
44    private function post($data = [], $model = 'gemini-2.5-flash:streamGenerateContent'): ResponseInterface
45    {
46        $request = new Request(
47            'POST',
48            $this->baseURL.$model,
49            $this->headers,
50            json_encode($data)
51        );
52
53        $curlCommand = $this->requestToCurl($request);
54
55        Log::info('Executing cURL command: ', ['command' => $curlCommand]);
56
57        return $this->client->send($request);
58    }
59
60    public function requestToCurl($request)
61    {
62        $method = $request->getMethod();
63        $url = (string) $request->getUri();
64        $headers = '';
65        foreach ($request->getHeaders() as $name => $values) {
66            foreach ($values as $value) {
67                // Each header separately
68                $headers .= sprintf(" -H '%s: %s'", $name, $value);
69            }
70        }
71        $body = (string) $request->getBody();
72        $data = $body !== '' ? " --data '$body'" : '';
73
74        return "curl -X $method$headers$data '$url'";
75    }
76
77    /**
78     * @throws GuzzleException
79     */
80    public function postCompletions(array $postParams, $model = 'gemini-2.5-flash:streamGenerateContent'): ResponseInterface
81    {
82        return $this->post($postParams, $model);
83    }
84
85    public static function refreshAccessToken()
86    {
87        Cache::forget('ai_geminiapi_access_token');
88
89        return GeminiAPI::getAIAccessToken();
90    }
91
92    public static function getAIAccessToken()
93    {
94        $access_token = Cache::get('ai_geminiapi_access_token');
95        if ($access_token) {
96            return $access_token;
97        }
98
99        try {
100            $googleCredentials = SecretsAWS::getSecretValue();
101            $privateKey = str_replace('\\n', "\n", $googleCredentials['GOOGLE_PRIVATE_KEY']);
102
103            $client = new GoogleClient;
104
105            $credentials = [
106                'type' => 'service_account',
107                'project_id' => $googleCredentials['GOOGLE_CLOUD_PROJECT'],
108                'private_key_id' => $googleCredentials['GOOGLE_PRIVATE_KEY_ID'],
109                'private_key' => $privateKey,
110                'client_email' => $googleCredentials['GOOGLE_CLIENT_EMAIL'],
111                'client_id' => $googleCredentials['GOOGLE_CLIENT_ID'],
112                'auth_uri' => 'https://accounts.google.com/o/oauth2/auth',
113                'token_uri' => 'https://oauth2.googleapis.com/token',
114                'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs',
115                'client_x509_cert_url' => $googleCredentials['GOOGLE_CLIENT_X509_CERT_URL'],
116            ];
117
118            $client->setAuthConfig($credentials);
119            $client->setApplicationName('FlyMSG');
120            $client->setScopes(['https://www.googleapis.com/auth/cloud-platform']);
121
122            if ($client->isAccessTokenExpired()) {
123                $client->fetchAccessTokenWithAssertion();
124            }
125
126            $access_token = $client->getAccessToken();
127
128            if (! $access_token || ! isset($access_token['access_token'])) {
129                Log::error('Access token is null.');
130            }
131
132            Cache::put('ai_geminiapi_access_token', $access_token['access_token'], GeminiAPI::FOURTY_MINUTES);
133
134            return $access_token['access_token'];
135        } catch (Exception $e) {
136            FlyMSGLogger::logError('FlyMSG-AI', $e);
137
138            return null;
139        }
140    }
141}