minovative-mind-cli 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/proxyClient.d.ts +0 -14
- package/dist/services/proxyClient.js +175 -111
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
import type { Content, Tool, ToolConfig, FunctionCall } from '@google/generative-ai';
|
|
2
|
-
/**
|
|
3
|
-
* ============================================================================
|
|
4
|
-
* PROXY CLIENT SERVICE
|
|
5
|
-
* ============================================================================
|
|
6
|
-
* Facilitates stream-based communication with the secure, Firebase-authenticated
|
|
7
|
-
* serverless Gemini content generation proxy.
|
|
8
|
-
*
|
|
9
|
-
* Core Capabilities:
|
|
10
|
-
* - Server-Sent Events (SSE) parsing for thoughts, text, and function calls.
|
|
11
|
-
* - Secure Authentication handling (Firebase ID Tokens).
|
|
12
|
-
* - Real-time stream-callback piping for instant responses.
|
|
13
|
-
* - Accurate token usage, credit consumption, and grounding metadata parsing.
|
|
14
|
-
* ============================================================================
|
|
15
|
-
*/
|
|
16
2
|
/**
|
|
17
3
|
* Metadata containing real-time proxy token and credit usage diagnostics.
|
|
18
4
|
*/
|
|
@@ -1,4 +1,38 @@
|
|
|
1
1
|
import { debugLog } from '../utils/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* ============================================================================
|
|
4
|
+
* PROXY CLIENT SERVICE
|
|
5
|
+
* ============================================================================
|
|
6
|
+
* Facilitates stream-based communication with the secure, Firebase-authenticated
|
|
7
|
+
* serverless Gemini content generation proxy.
|
|
8
|
+
*
|
|
9
|
+
* Core Capabilities:
|
|
10
|
+
* - Server-Sent Events (SSE) parsing for thoughts, text, and function calls.
|
|
11
|
+
* - Secure Authentication handling (Firebase ID Tokens).
|
|
12
|
+
* - Real-time stream-callback piping for instant responses.
|
|
13
|
+
* - Accurate token usage, credit consumption, and grounding metadata parsing.
|
|
14
|
+
* ============================================================================
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Helper to pause execution for a specified duration, respecting abort signals.
|
|
18
|
+
*/
|
|
19
|
+
async function delay(ms, abortSignal) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let timeout;
|
|
22
|
+
const abortHandler = () => {
|
|
23
|
+
clearTimeout(timeout);
|
|
24
|
+
reject(new Error('Operation aborted'));
|
|
25
|
+
};
|
|
26
|
+
if (abortSignal?.aborted) {
|
|
27
|
+
return abortHandler();
|
|
28
|
+
}
|
|
29
|
+
abortSignal?.addEventListener('abort', abortHandler);
|
|
30
|
+
timeout = setTimeout(() => {
|
|
31
|
+
abortSignal?.removeEventListener('abort', abortHandler);
|
|
32
|
+
resolve();
|
|
33
|
+
}, ms);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
2
36
|
let globalSessionAccumulatedUsage = {
|
|
3
37
|
promptTokens: 0,
|
|
4
38
|
candidatesTokens: 0,
|
|
@@ -45,132 +79,162 @@ export class ProxyClient {
|
|
|
45
79
|
* @throws {Error} If authentication fails (401), credits are insufficient (402), or network/proxy errors occur.
|
|
46
80
|
*/
|
|
47
81
|
async generateFunctionCallViaProxy(idToken, modelName, contents, tools, toolConfig, systemInstruction, generationConfig, streamCallbacks, abortSignal) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
const MAX_RETRIES = 5;
|
|
83
|
+
const BASE_DELAY_MS = 2000;
|
|
84
|
+
const MAX_DELAY_MS = 30000;
|
|
85
|
+
let attempt = 0;
|
|
86
|
+
retryLoop: while (true) {
|
|
87
|
+
const response = await fetch(this.PROXY_URL, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
'X-Firebase-Auth': `Bearer ${idToken}`,
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
model: modelName,
|
|
95
|
+
contents,
|
|
96
|
+
tools,
|
|
97
|
+
toolConfig,
|
|
98
|
+
systemInstruction,
|
|
99
|
+
generationConfig,
|
|
100
|
+
}),
|
|
101
|
+
signal: abortSignal,
|
|
102
|
+
});
|
|
103
|
+
debugLog(`Proxy Request to ${modelName} complete. Status: ${response.status} ${response.statusText}`);
|
|
104
|
+
if ((response.status === 429 || response.status === 503) && attempt < MAX_RETRIES) {
|
|
105
|
+
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
106
|
+
const delayTime = Math.round(exponentialDelay * (0.5 + Math.random() * 0.5));
|
|
107
|
+
console.warn(`Rate limit or service unavailable hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
108
|
+
await delay(delayTime, abortSignal);
|
|
109
|
+
attempt++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (response.status === 401) {
|
|
113
|
+
let details = '';
|
|
69
114
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
115
|
+
const text = await response.text();
|
|
116
|
+
try {
|
|
117
|
+
const errorData = JSON.parse(text);
|
|
118
|
+
details = errorData.details || errorData.error || text;
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
details = text;
|
|
122
|
+
}
|
|
72
123
|
}
|
|
73
124
|
catch (e) {
|
|
74
|
-
details =
|
|
125
|
+
details = 'Unknown error reading body';
|
|
75
126
|
}
|
|
127
|
+
throw new Error(`Authentication failed: ${details}. Please login again.`);
|
|
76
128
|
}
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
if (response.status === 402) {
|
|
130
|
+
throw new Error('Insufficient credits. Please visit minovativemind.dev to purchase more credits.');
|
|
79
131
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
globalSessionAccumulatedUsage.remainingBalance = data.usage.remainingBalance;
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
const errorData = await response.json().catch(() => ({}));
|
|
134
|
+
throw new Error(`Proxy error ${response.status}: ${errorData.error || response.statusText}`);
|
|
135
|
+
}
|
|
136
|
+
if (!response.body) {
|
|
137
|
+
throw new Error('No response body received from proxy');
|
|
138
|
+
}
|
|
139
|
+
// Node 18+ fetch body is a ReadableStream which is async iterable but type might mismatch.
|
|
140
|
+
// Let's read chunks manually
|
|
141
|
+
const reader = response.body.getReader();
|
|
142
|
+
const decoder = new TextDecoder();
|
|
143
|
+
let buffer = '';
|
|
144
|
+
let functionCall = null;
|
|
145
|
+
const functionCalls = [];
|
|
146
|
+
let thought = '';
|
|
147
|
+
let parts = undefined;
|
|
148
|
+
let usageMetadata = undefined;
|
|
149
|
+
let groundingMetadata = undefined;
|
|
150
|
+
try {
|
|
151
|
+
while (true) {
|
|
152
|
+
const { done, value } = await reader.read();
|
|
153
|
+
if (done)
|
|
154
|
+
break;
|
|
155
|
+
buffer += decoder.decode(value, { stream: true });
|
|
156
|
+
const lines = buffer.split('\n\n');
|
|
157
|
+
buffer = lines.pop() || '';
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
if (!line.startsWith('data: '))
|
|
160
|
+
continue;
|
|
161
|
+
const dataStr = line.slice(6);
|
|
162
|
+
try {
|
|
163
|
+
const data = JSON.parse(dataStr);
|
|
164
|
+
if (data.type === 'functionCall' && data.functionCall) {
|
|
165
|
+
functionCall = data.functionCall;
|
|
166
|
+
functionCalls.push(data.functionCall);
|
|
167
|
+
}
|
|
168
|
+
else if (data.type === 'thought' && data.thought) {
|
|
169
|
+
thought += data.thought;
|
|
170
|
+
if (streamCallbacks?.onChunk)
|
|
171
|
+
streamCallbacks.onChunk(data.thought);
|
|
172
|
+
}
|
|
173
|
+
else if (data.type === 'chunk' && data.text) {
|
|
174
|
+
thought += data.text;
|
|
175
|
+
if (streamCallbacks?.onChunk)
|
|
176
|
+
streamCallbacks.onChunk(data.text);
|
|
177
|
+
}
|
|
178
|
+
else if (data.type === 'parts' && data.parts) {
|
|
179
|
+
parts = data.parts;
|
|
180
|
+
}
|
|
181
|
+
else if (data.type === 'done') {
|
|
182
|
+
if (data.usage) {
|
|
183
|
+
usageMetadata = data.usage;
|
|
184
|
+
globalSessionAccumulatedUsage.promptTokens += data.usage.promptTokens || 0;
|
|
185
|
+
globalSessionAccumulatedUsage.candidatesTokens += data.usage.candidatesTokens || 0;
|
|
186
|
+
globalSessionAccumulatedUsage.cachedTokens += data.usage.cachedTokens || 0;
|
|
187
|
+
globalSessionAccumulatedUsage.creditsUsed += data.usage.creditsUsed || 0;
|
|
188
|
+
globalSessionAccumulatedUsage.totalTokenCount +=
|
|
189
|
+
(data.usage.promptTokens || 0) + (data.usage.cachedTokens || 0) + (data.usage.candidatesTokens || 0);
|
|
190
|
+
if (data.usage.remainingBalance !== undefined) {
|
|
191
|
+
globalSessionAccumulatedUsage.remainingBalance = data.usage.remainingBalance;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (data.groundingMetadata) {
|
|
195
|
+
groundingMetadata = data.groundingMetadata;
|
|
145
196
|
}
|
|
146
197
|
}
|
|
147
|
-
if (data.
|
|
148
|
-
|
|
198
|
+
else if (data.type === 'error') {
|
|
199
|
+
throw new Error(`Proxy generation error: ${data.message}`);
|
|
149
200
|
}
|
|
150
201
|
}
|
|
151
|
-
|
|
152
|
-
|
|
202
|
+
catch (parseError) {
|
|
203
|
+
if (parseError.message && parseError.message.startsWith('Proxy generation error:')) {
|
|
204
|
+
throw parseError;
|
|
205
|
+
}
|
|
206
|
+
debugLog(`Failed to parse SSE data: ${dataStr} - Error: ${parseError.message || parseError}`);
|
|
153
207
|
}
|
|
154
208
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (streamError) {
|
|
212
|
+
if (streamError.message?.includes('429') ||
|
|
213
|
+
streamError.message?.includes('503') ||
|
|
214
|
+
streamError.message?.includes('RESOURCE_EXHAUSTED') ||
|
|
215
|
+
streamError.message?.includes('Too Many Requests')) {
|
|
216
|
+
if (attempt < MAX_RETRIES) {
|
|
217
|
+
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
218
|
+
const delayTime = Math.round(exponentialDelay * (0.5 + Math.random() * 0.5));
|
|
219
|
+
console.warn(`Rate limit or service unavailable hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
220
|
+
await delay(delayTime, abortSignal);
|
|
221
|
+
attempt++;
|
|
222
|
+
continue retryLoop;
|
|
160
223
|
}
|
|
161
224
|
}
|
|
225
|
+
throw streamError;
|
|
162
226
|
}
|
|
227
|
+
finally {
|
|
228
|
+
reader.releaseLock();
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
functionCall,
|
|
232
|
+
functionCalls,
|
|
233
|
+
thought: thought.trim(),
|
|
234
|
+
parts,
|
|
235
|
+
usageMetadata,
|
|
236
|
+
groundingMetadata,
|
|
237
|
+
};
|
|
163
238
|
}
|
|
164
|
-
finally {
|
|
165
|
-
reader.releaseLock();
|
|
166
|
-
}
|
|
167
|
-
return {
|
|
168
|
-
functionCall,
|
|
169
|
-
functionCalls,
|
|
170
|
-
thought: thought.trim(),
|
|
171
|
-
parts,
|
|
172
|
-
usageMetadata,
|
|
173
|
-
groundingMetadata,
|
|
174
|
-
};
|
|
175
239
|
}
|
|
176
240
|
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED