minovative-mind-cli 2.2.5 → 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/LICENSE.md +2 -0
- package/README.md +8 -8
- package/dist/commands/logout.js +1 -1
- package/dist/services/agent/slashCommands.js +53 -14
- package/dist/services/agent/toolLoop.js +4 -1
- package/dist/services/agent/types.d.ts +4 -0
- package/dist/services/agent-tools.js +55 -27
- package/dist/services/agent.d.ts +4 -0
- package/dist/services/agent.js +77 -60
- package/dist/services/ai.d.ts +1 -2
- package/dist/services/ai.js +21 -19
- package/dist/services/auth.d.ts +1 -1
- package/dist/services/auth.js +7 -28
- package/dist/services/chatHistoryService.d.ts +8 -0
- package/dist/services/contextAgent.js +18 -1
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/orchestration/investigationAgent.d.ts +2 -2
- package/dist/services/orchestration/investigationAgent.js +25 -6
- package/dist/services/orchestration/orchestrator.d.ts +1 -1
- package/dist/services/orchestration/orchestrator.js +37 -18
- package/dist/services/orchestration/subAgent.d.ts +1 -1
- package/dist/services/orchestration/subAgent.js +23 -13
- package/dist/services/proxyClient.d.ts +16 -14
- package/dist/services/proxyClient.js +199 -103
- package/dist/utils/analysisRunner.js +2 -2
- package/dist/utils/config.d.ts +3 -3
- package/dist/utils/config.js +3 -3
- package/dist/utils/credentialStore.d.ts +30 -0
- package/dist/utils/credentialStore.js +540 -0
- package/dist/utils/projectStorage.js +70 -0
- package/dist/utils/systemPrompts.d.ts +3 -3
- package/dist/utils/systemPrompts.js +4 -4
- package/oclif.manifest.json +1 -1
- package/package.json +2 -1
|
@@ -1,4 +1,61 @@
|
|
|
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
|
+
}
|
|
36
|
+
let globalSessionAccumulatedUsage = {
|
|
37
|
+
promptTokens: 0,
|
|
38
|
+
candidatesTokens: 0,
|
|
39
|
+
cachedTokens: 0,
|
|
40
|
+
totalTokenCount: 0,
|
|
41
|
+
creditsUsed: 0,
|
|
42
|
+
remainingBalance: undefined
|
|
43
|
+
};
|
|
44
|
+
export function getAndResetTurnUsage() {
|
|
45
|
+
const current = { ...globalSessionAccumulatedUsage };
|
|
46
|
+
globalSessionAccumulatedUsage = {
|
|
47
|
+
promptTokens: 0,
|
|
48
|
+
candidatesTokens: 0,
|
|
49
|
+
cachedTokens: 0,
|
|
50
|
+
totalTokenCount: 0,
|
|
51
|
+
creditsUsed: 0,
|
|
52
|
+
remainingBalance: undefined
|
|
53
|
+
};
|
|
54
|
+
return current;
|
|
55
|
+
}
|
|
56
|
+
export function peekTurnUsage() {
|
|
57
|
+
return { ...globalSessionAccumulatedUsage };
|
|
58
|
+
}
|
|
2
59
|
/**
|
|
3
60
|
* Client service interacting directly with the serverless Gemini proxy endpoint.
|
|
4
61
|
* Ensures authorization via Firebase token passing and parses streamed content.
|
|
@@ -22,123 +79,162 @@ export class ProxyClient {
|
|
|
22
79
|
* @throws {Error} If authentication fails (401), credits are insufficient (402), or network/proxy errors occur.
|
|
23
80
|
*/
|
|
24
81
|
async generateFunctionCallViaProxy(idToken, modelName, contents, tools, toolConfig, systemInstruction, generationConfig, streamCallbacks, abortSignal) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 = '';
|
|
46
114
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
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
|
+
}
|
|
49
123
|
}
|
|
50
124
|
catch (e) {
|
|
51
|
-
details =
|
|
125
|
+
details = 'Unknown error reading body';
|
|
52
126
|
}
|
|
127
|
+
throw new Error(`Authentication failed: ${details}. Please login again.`);
|
|
53
128
|
}
|
|
54
|
-
|
|
55
|
-
|
|
129
|
+
if (response.status === 402) {
|
|
130
|
+
throw new Error('Insufficient credits. Please visit minovativemind.dev to purchase more credits.');
|
|
56
131
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
else if (data.type === 'chunk' && data.text) {
|
|
104
|
-
thought += data.text;
|
|
105
|
-
if (streamCallbacks?.onChunk)
|
|
106
|
-
streamCallbacks.onChunk(data.text);
|
|
107
|
-
}
|
|
108
|
-
else if (data.type === 'parts' && data.parts) {
|
|
109
|
-
parts = data.parts;
|
|
110
|
-
}
|
|
111
|
-
else if (data.type === 'done') {
|
|
112
|
-
if (data.usage) {
|
|
113
|
-
usageMetadata = data.usage;
|
|
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);
|
|
114
177
|
}
|
|
115
|
-
if (data.
|
|
116
|
-
|
|
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;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (data.type === 'error') {
|
|
199
|
+
throw new Error(`Proxy generation error: ${data.message}`);
|
|
117
200
|
}
|
|
118
201
|
}
|
|
119
|
-
|
|
120
|
-
|
|
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}`);
|
|
121
207
|
}
|
|
122
208
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
128
223
|
}
|
|
129
224
|
}
|
|
225
|
+
throw streamError;
|
|
130
226
|
}
|
|
227
|
+
finally {
|
|
228
|
+
reader.releaseLock();
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
functionCall,
|
|
232
|
+
functionCalls,
|
|
233
|
+
thought: thought.trim(),
|
|
234
|
+
parts,
|
|
235
|
+
usageMetadata,
|
|
236
|
+
groundingMetadata,
|
|
237
|
+
};
|
|
131
238
|
}
|
|
132
|
-
finally {
|
|
133
|
-
reader.releaseLock();
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
functionCall,
|
|
137
|
-
functionCalls,
|
|
138
|
-
thought: thought.trim(),
|
|
139
|
-
parts,
|
|
140
|
-
usageMetadata,
|
|
141
|
-
groundingMetadata,
|
|
142
|
-
};
|
|
143
239
|
}
|
|
144
240
|
}
|
|
@@ -70,8 +70,8 @@ function truncateOutput(text, max) {
|
|
|
70
70
|
* @returns Captured stdout, stderr, and exit code.
|
|
71
71
|
*/
|
|
72
72
|
export async function runEphemeralScript(workspaceRoot, language, code, options) {
|
|
73
|
-
const timeoutMs = options?.timeoutMs ??
|
|
74
|
-
const maxOutputChars = options?.maxOutputChars ??
|
|
73
|
+
const timeoutMs = options?.timeoutMs ?? 60_000;
|
|
74
|
+
const maxOutputChars = options?.maxOutputChars ?? 100_000;
|
|
75
75
|
const ext = LANGUAGE_EXTENSIONS[language.toLowerCase()];
|
|
76
76
|
if (!ext) {
|
|
77
77
|
return {
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ export declare const GITHUB_CLIENT_ID = "Ov23linFYFfjO3JILG7r";
|
|
|
14
14
|
* Supported Gemini AI models.
|
|
15
15
|
*/
|
|
16
16
|
export declare const GEMINI_MODELS: {
|
|
17
|
-
readonly
|
|
18
|
-
readonly
|
|
19
|
-
readonly
|
|
17
|
+
readonly PRO: "gemini-3.1-pro-preview";
|
|
18
|
+
readonly FLASH: "gemini-3.6-flash";
|
|
19
|
+
readonly FLASH_LITE: "gemini-3.5-flash-lite";
|
|
20
20
|
readonly AUTO: "auto";
|
|
21
21
|
};
|
|
22
22
|
/** Default Gemini model for the coding agent. */
|
package/dist/utils/config.js
CHANGED
|
@@ -14,9 +14,9 @@ export const GITHUB_CLIENT_ID = 'Ov23linFYFfjO3JILG7r';
|
|
|
14
14
|
* Supported Gemini AI models.
|
|
15
15
|
*/
|
|
16
16
|
export const GEMINI_MODELS = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
PRO: 'gemini-3.1-pro-preview',
|
|
18
|
+
FLASH: 'gemini-3.6-flash',
|
|
19
|
+
FLASH_LITE: 'gemini-3.5-flash-lite',
|
|
20
20
|
AUTO: 'auto',
|
|
21
21
|
};
|
|
22
22
|
/** Default Gemini model for the coding agent. */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Legacy plaintext file path (for migration). */
|
|
2
|
+
export declare const LEGACY_CONFIG_FILE: string;
|
|
3
|
+
export interface StoredCredentials {
|
|
4
|
+
idToken?: string;
|
|
5
|
+
refreshToken?: string;
|
|
6
|
+
idTokenExpiry?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Persists authentication credentials to the most secure available store.
|
|
10
|
+
*
|
|
11
|
+
* Strategy (tried in order):
|
|
12
|
+
* 1. macOS Keychain (`security` CLI)
|
|
13
|
+
* 2. Linux libsecret (`secret-tool` CLI)
|
|
14
|
+
* 3. Windows DPAPI (PowerShell)
|
|
15
|
+
* 4. AES-256-GCM encrypted file with 0600 permissions
|
|
16
|
+
*/
|
|
17
|
+
export declare function saveCredentials(data: StoredCredentials): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Loads authentication credentials from the secure store.
|
|
20
|
+
* Returns an empty object if no credentials are found.
|
|
21
|
+
*
|
|
22
|
+
* On first run after upgrade, silently migrates any legacy plaintext
|
|
23
|
+
* `~/.minovative-mind-cli.json` into the secure store and deletes the old file.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadCredentials(): Promise<StoredCredentials>;
|
|
26
|
+
/**
|
|
27
|
+
* Removes all stored credentials from the secure store.
|
|
28
|
+
* Also cleans up any legacy plaintext file if it still exists.
|
|
29
|
+
*/
|
|
30
|
+
export declare function clearCredentials(): Promise<void>;
|