minovative-mind-cli 2.8.4 → 2.9.0
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/README.md +2 -1
- package/dist/services/agent/slashCommands.js +29 -6
- package/dist/services/agent/syntaxAgent.d.ts +3 -1
- package/dist/services/agent/syntaxAgent.js +15 -5
- package/dist/services/agent/toolLoop.js +1 -1
- package/dist/services/agent.d.ts +1 -1
- package/dist/services/agent.js +93 -24
- package/dist/services/ai.d.ts +3 -3
- package/dist/services/ai.js +50 -13
- package/dist/services/auth.d.ts +7 -0
- package/dist/services/auth.js +41 -0
- package/dist/services/contextAgent.d.ts +2 -2
- package/dist/services/contextAgent.js +61 -10
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +12 -2
- package/dist/services/orchestration/orchestrator.d.ts +1 -1
- package/dist/services/orchestration/orchestrator.js +1 -1
- package/dist/services/orchestration/subAgent.js +1 -1
- package/dist/services/proxyClient.js +44 -2
- package/dist/utils/config.d.ts +3 -1
- package/dist/utils/config.js +3 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,8 @@ Hot-swap during a session using `/models`:
|
|
|
88
88
|
|
|
89
89
|
| Model | Best for |
|
|
90
90
|
| ------------------------- | --------------------------------------------------- |
|
|
91
|
-
| **Auto** (default) | Automatically selects (3.
|
|
91
|
+
| **Auto** (default) | Automatically selects (3.7 Flash or 3.5 Flash Lite) |
|
|
92
|
+
| **Gemini 3.7 Flash** | Next-gen performance, reasoning & fast execution |
|
|
92
93
|
| **Gemini 3.6 Flash** | Everyday coding — fast and accurate |
|
|
93
94
|
| **Gemini 3.1 Pro** | Complex architectural changes |
|
|
94
95
|
| **Gemini 3.5 Flash Lite** | Best for speed and cost efficiency |
|
|
@@ -13,6 +13,7 @@ import { renderTerminalMarkdown } from '../../utils/terminal.js';
|
|
|
13
13
|
import { readPaste } from '../../utils/paste.js';
|
|
14
14
|
import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled } from '../agent-tools.js';
|
|
15
15
|
import { ProxyClient, getAndResetTurnUsage } from '../proxyClient.js';
|
|
16
|
+
import { checkByokSubscription } from '../auth.js';
|
|
16
17
|
import { GEMINI_MODELS, isByokEnabled } from '../../utils/config.js';
|
|
17
18
|
import { loadCredentials, updateCredentialField } from '../../utils/credentialStore.js';
|
|
18
19
|
import { getGlobalActiveModel, setGlobalActiveModel, ProxyChatSession } from '../ai.js';
|
|
@@ -25,7 +26,7 @@ import { getGlobalActiveModel, setGlobalActiveModel, ProxyChatSession } from '..
|
|
|
25
26
|
* - `/paste` : Enter multi-line paste mode using EOF tracking (`Ctrl+D` submission).
|
|
26
27
|
* - `/plan` : Toggle AI step-by-step implementation planning mode.
|
|
27
28
|
* - `/clear` : Clear conversation history, wipe terminal screen, and reset CLI header.
|
|
28
|
-
* - `/models` : Hot-swap the active generative AI model (Gemini 3.1 Pro, Gemini 3.6 Flash, Gemini 3.5 Flash-Lite, Auto routing).
|
|
29
|
+
* - `/models` : Hot-swap the active generative AI model (Gemini 3.1 Pro, Gemini 3.7 Flash, Gemini 3.6 Flash, Gemini 3.5 Flash-Lite, Auto routing).
|
|
29
30
|
* - `/debug` : Toggle internal agent telemetry and diagnostic logging.
|
|
30
31
|
* - `/auto-approve`: Toggle automatic confirmation skipping for terminal shell execution commands.
|
|
31
32
|
* - `/sub-agents` : Toggle MMAAK Engine for parallel investigation sub-agent orchestration.
|
|
@@ -120,10 +121,15 @@ export async function handleSlashCommand(command, context) {
|
|
|
120
121
|
// label: 'Claude 5 Sonnet',
|
|
121
122
|
// hint: 'Best for raw speed and cost efficiency',
|
|
122
123
|
// },
|
|
124
|
+
{
|
|
125
|
+
value: 'gemini-3.7-flash',
|
|
126
|
+
label: 'Gemini 3.7 Flash',
|
|
127
|
+
hint: 'Next-gen reasoning, speed & balanced performance',
|
|
128
|
+
},
|
|
123
129
|
{
|
|
124
130
|
value: 'gemini-3.6-flash',
|
|
125
131
|
label: 'Gemini 3.6 Flash',
|
|
126
|
-
hint: '
|
|
132
|
+
hint: 'Everyday coding — fast and accurate',
|
|
127
133
|
},
|
|
128
134
|
{
|
|
129
135
|
value: 'gemini-3.5-flash-lite',
|
|
@@ -133,9 +139,9 @@ export async function handleSlashCommand(command, context) {
|
|
|
133
139
|
{
|
|
134
140
|
value: 'auto',
|
|
135
141
|
label: 'Auto (Flash-Lite / Flash)',
|
|
136
|
-
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.
|
|
142
|
+
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.7 Flash based on prompt complexity',
|
|
137
143
|
},
|
|
138
|
-
].filter(o => !(byokEnabled && o.value.includes('claude')));
|
|
144
|
+
].filter((o) => !(byokEnabled && o.value.includes('claude')));
|
|
139
145
|
const selectedModel = await p['select']({
|
|
140
146
|
message: `Select AI Model (Current: ${pc.cyan(currentModel)})`,
|
|
141
147
|
initialValue: currentModel,
|
|
@@ -1046,12 +1052,29 @@ ${diffOut}
|
|
|
1046
1052
|
if (!byokEnabled && !creds.geminiApiKey) {
|
|
1047
1053
|
p.log.error('You must set an API key before enabling BYOK mode.');
|
|
1048
1054
|
}
|
|
1055
|
+
else if (!byokEnabled) {
|
|
1056
|
+
const subCheck = await checkByokSubscription();
|
|
1057
|
+
if (!subCheck.active) {
|
|
1058
|
+
p.log.error(subCheck.message ||
|
|
1059
|
+
'A $3.99/month BYOK Subscription is required. Visit https://www.minovativemind.dev/pricing');
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
await updateCredentialField('useByok', true);
|
|
1063
|
+
p.log.success(`BYOK mode is now ${pc.green('Enabled')}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1049
1066
|
else {
|
|
1050
|
-
await updateCredentialField('useByok',
|
|
1051
|
-
p.log.success(`BYOK mode is now ${
|
|
1067
|
+
await updateCredentialField('useByok', false);
|
|
1068
|
+
p.log.success(`BYOK mode is now ${pc.yellow('Disabled')}`);
|
|
1052
1069
|
}
|
|
1053
1070
|
}
|
|
1054
1071
|
else if (action === 'set') {
|
|
1072
|
+
const subCheck = await checkByokSubscription();
|
|
1073
|
+
if (!subCheck.active) {
|
|
1074
|
+
p.log.error(subCheck.message ||
|
|
1075
|
+
'A $3.99/month BYOK Subscription is required to use your own API key. Please visit https://www.minovativemind.dev/pricing to subscribe. The $3.99 is to cover account maintance for you.');
|
|
1076
|
+
return { shouldContinue: true };
|
|
1077
|
+
}
|
|
1055
1078
|
const key = await p['password']({
|
|
1056
1079
|
message: 'Enter your Google AI Studio API Key:',
|
|
1057
1080
|
validate: (v) => (!v ? 'API key is required' : undefined),
|
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
* @param error Optional error message from local validation to guide the AI.
|
|
9
9
|
* @returns The fixed content, or undefined if it was completely valid or couldn't be fixed.
|
|
10
10
|
*/
|
|
11
|
-
export declare function validateAndFixSyntax(content: string, filePath: string, error?: string): Promise<string | undefined>;
|
|
11
|
+
export declare function validateAndFixSyntax(content: string, filePath: string, error?: string, abortSignal?: AbortSignal): Promise<string | undefined>;
|
|
12
12
|
export interface AIFuzzyMatchOptions {
|
|
13
13
|
/** Optional custom Gemini model override. Defaults to GEMINI_MODELS.FLASH. */
|
|
14
14
|
model?: string;
|
|
15
15
|
/** Whether to validate and fix syntax on the updated content. Defaults to true. */
|
|
16
16
|
validateSyntax?: boolean;
|
|
17
|
+
/** Optional AbortSignal to cancel execution immediately. */
|
|
18
|
+
abortSignal?: AbortSignal;
|
|
17
19
|
}
|
|
18
20
|
export interface AIFuzzyMatchResult {
|
|
19
21
|
/** Indicates whether the fuzzy search matching and edit application succeeded. */
|
|
@@ -68,7 +68,9 @@ function cleanOutput(raw) {
|
|
|
68
68
|
* @param error Optional error message from local validation to guide the AI.
|
|
69
69
|
* @returns The fixed content, or undefined if it was completely valid or couldn't be fixed.
|
|
70
70
|
*/
|
|
71
|
-
export async function validateAndFixSyntax(content, filePath, error) {
|
|
71
|
+
export async function validateAndFixSyntax(content, filePath, error, abortSignal) {
|
|
72
|
+
if (abortSignal?.aborted)
|
|
73
|
+
return undefined;
|
|
72
74
|
// 1. First run fast local validation to check if content is already valid or gather error details
|
|
73
75
|
const localResult = localValidate(filePath, content);
|
|
74
76
|
if (localResult.isValid && !error) {
|
|
@@ -108,7 +110,7 @@ ${win.snippet}
|
|
|
108
110
|
|
|
109
111
|
Please fix the syntax error in the snippet above and return ONLY the raw repaired code snippet:`;
|
|
110
112
|
try {
|
|
111
|
-
const result = await snippetChat.sendMessage(snippetPrompt);
|
|
113
|
+
const result = await snippetChat.sendMessage(snippetPrompt, undefined, abortSignal);
|
|
112
114
|
const repairedSnippet = cleanOutput(result.response.text());
|
|
113
115
|
if (repairedSnippet && repairedSnippet !== win.snippet) {
|
|
114
116
|
const candidateContent = content.slice(0, win.startPos) + repairedSnippet + content.slice(win.endPos);
|
|
@@ -123,6 +125,8 @@ Please fix the syntax error in the snippet above and return ONLY the raw repaire
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
}
|
|
128
|
+
if (abortSignal?.aborted)
|
|
129
|
+
return undefined;
|
|
126
130
|
// 3. Full file repair strategy (for smaller files or when snippet repair was insufficient)
|
|
127
131
|
const fullSystemInstruction = `
|
|
128
132
|
<identity>
|
|
@@ -150,7 +154,7 @@ ${effectiveError}
|
|
|
150
154
|
Content:
|
|
151
155
|
${content}`;
|
|
152
156
|
try {
|
|
153
|
-
const result = await fullChat.sendMessage(fullPrompt);
|
|
157
|
+
const result = await fullChat.sendMessage(fullPrompt, undefined, abortSignal);
|
|
154
158
|
const output = cleanOutput(result.response.text());
|
|
155
159
|
if (output === 'VALID' || output === '' || output === content) {
|
|
156
160
|
return undefined;
|
|
@@ -185,6 +189,12 @@ export async function aiFuzzyMatch(fileContent, searchContent, replaceContent, f
|
|
|
185
189
|
error: 'File content and search content must not be empty.',
|
|
186
190
|
};
|
|
187
191
|
}
|
|
192
|
+
if (options.abortSignal?.aborted) {
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
error: 'Operation aborted',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
188
198
|
const model = options.model || GEMINI_MODELS.FLASH;
|
|
189
199
|
const shouldValidateSyntax = options.validateSyntax !== false;
|
|
190
200
|
const systemInstruction = `
|
|
@@ -217,7 +227,7 @@ ${replaceContent}
|
|
|
217
227
|
File Content:
|
|
218
228
|
${fileContent}`;
|
|
219
229
|
try {
|
|
220
|
-
const response = await chat.sendMessage(prompt);
|
|
230
|
+
const response = await chat.sendMessage(prompt, undefined, options.abortSignal);
|
|
221
231
|
const rawOutput = response.response.text();
|
|
222
232
|
const output = cleanOutput(rawOutput);
|
|
223
233
|
if (output === 'UNMATCHED' || !output || output === fileContent) {
|
|
@@ -231,7 +241,7 @@ ${fileContent}`;
|
|
|
231
241
|
const valResult = localValidate(filePath, finalContent);
|
|
232
242
|
if (!valResult.isValid) {
|
|
233
243
|
// Attempt automatic syntax repair on AI output if local validation detected syntax error
|
|
234
|
-
const repaired = await validateAndFixSyntax(finalContent, filePath, valResult.error);
|
|
244
|
+
const repaired = await validateAndFixSyntax(finalContent, filePath, valResult.error, options.abortSignal);
|
|
235
245
|
if (repaired) {
|
|
236
246
|
finalContent = repaired;
|
|
237
247
|
}
|
|
@@ -264,7 +264,7 @@ export async function processResponse(chat, result, workspaceRoot, inputHandler,
|
|
|
264
264
|
additionalText = `[USER INTERRUPTION] The user sent the following message during your execution:\n"${queuedMsg}"\n\nPlease incorporate this feedback into your ongoing work. Address the user's message, but DO NOT lose track of your original overall plan or focus. After addressing this interruption, continue with your broader objective.`;
|
|
265
265
|
p.log.info(pc.cyan(`Sending queued message to AI...`));
|
|
266
266
|
// Dynamically upgrade agent permissions/intent to EXECUTE mode if the interrupted instruction requires system modifications
|
|
267
|
-
const newIntent = await routeIntent(queuedMsg);
|
|
267
|
+
const newIntent = await routeIntent(queuedMsg, '', abortSignal);
|
|
268
268
|
if (agentState.targetAgent === 'CHAT') {
|
|
269
269
|
if (newIntent.targetAgent === 'EXECUTE' || newIntent.needsContext) {
|
|
270
270
|
agentState.targetAgent = 'EXECUTE';
|
package/dist/services/agent.d.ts
CHANGED
|
@@ -125,4 +125,4 @@ export declare const HISTORY_SUMMARIZATION_THRESHOLD = 50;
|
|
|
125
125
|
* @param chat - The active ProxyChatSession instance.
|
|
126
126
|
* @returns A promise resolving to true if history was summarized and updated; false otherwise.
|
|
127
127
|
*/
|
|
128
|
-
export declare function summarizeHistoryIfNeeded(chat: any): Promise<boolean>;
|
|
128
|
+
export declare function summarizeHistoryIfNeeded(chat: any, abortSignal?: AbortSignal): Promise<boolean>;
|
package/dist/services/agent.js
CHANGED
|
@@ -315,15 +315,19 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
315
315
|
inputHandler.start(spinner);
|
|
316
316
|
changeLogger.startChangeSet(userInput);
|
|
317
317
|
let finalInput = userInput;
|
|
318
|
+
if (ac.signal.aborted)
|
|
319
|
+
return;
|
|
318
320
|
// Inter-turn cooling-off delay to allow API token buckets to settle before running intent routing
|
|
319
321
|
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.INTER_TURN_MS));
|
|
322
|
+
if (ac.signal.aborted)
|
|
323
|
+
return;
|
|
320
324
|
// Stage 0: Summarize chat history if length threshold is met
|
|
321
325
|
if (!cachedContextResult) {
|
|
322
326
|
const isDebug = isDebugOn();
|
|
323
327
|
if (!inputHandler.isCurrentlyPrompting() && !isDebug) {
|
|
324
328
|
spinner.start('Checking conversation history...');
|
|
325
329
|
}
|
|
326
|
-
const historySummarized = await summarizeHistoryIfNeeded(chat);
|
|
330
|
+
const historySummarized = await summarizeHistoryIfNeeded(chat, ac.signal);
|
|
327
331
|
if (historySummarized) {
|
|
328
332
|
debugLog('Chat history was summarized and compressed prior to context investigation.');
|
|
329
333
|
}
|
|
@@ -331,6 +335,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
331
335
|
spinner.stop();
|
|
332
336
|
}
|
|
333
337
|
}
|
|
338
|
+
if (ac.signal.aborted)
|
|
339
|
+
return;
|
|
334
340
|
// Stage 1: Gather Workspace Context and route intentions
|
|
335
341
|
spinner.start('🔍 Investigating workspace...');
|
|
336
342
|
let gatherRes = { targetAgent: 'EXECUTE', chainedMessages: [], contextResult: cachedContextResult || null };
|
|
@@ -340,19 +346,34 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
340
346
|
collector.startTimer('contextGather');
|
|
341
347
|
const chatHistory = chat.getRecentHistory(3);
|
|
342
348
|
const toolLogs = [];
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
349
|
+
try {
|
|
350
|
+
gatherRes = await gatherContext(workspaceRoot, userInput, chatHistory, inputHandler, ac.signal, (msg) => {
|
|
351
|
+
if (!inputHandler.isCurrentlyPrompting()) {
|
|
352
|
+
spinner.message(`🔍 Investigating workspace... ${pc.dim(msg)}`);
|
|
353
|
+
}
|
|
354
|
+
}, (toolMsg, label) => {
|
|
355
|
+
const formattedLog = `${pc.dim(`[${label}]`)} ${toolMsg}`;
|
|
356
|
+
toolLogs.push(formattedLog);
|
|
357
|
+
if (!inputHandler.isCurrentlyPrompting()) {
|
|
358
|
+
spinner.message(`🔍 Investigating workspace... ${formattedLog}`);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
catch (gatherErr) {
|
|
363
|
+
spinner.stop();
|
|
364
|
+
process.stdout.write('\x1b[2K\r');
|
|
365
|
+
if (ac.signal.aborted || gatherErr?.name === 'AbortError' || gatherErr?.message?.includes('abort')) {
|
|
366
|
+
return;
|
|
352
367
|
}
|
|
353
|
-
|
|
368
|
+
throw gatherErr;
|
|
369
|
+
}
|
|
354
370
|
if (collector)
|
|
355
371
|
collector.stopTimer('contextGather');
|
|
372
|
+
if (ac.signal.aborted) {
|
|
373
|
+
spinner.stop();
|
|
374
|
+
process.stdout.write('\x1b[2K\r');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
356
377
|
// Print all collected logs at once to prevent flickering, while spinner is stopped
|
|
357
378
|
if (toolLogs.length > 0) {
|
|
358
379
|
spinner.stop();
|
|
@@ -361,6 +382,11 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
361
382
|
}
|
|
362
383
|
// Post-investigation cooling-off delay to allow API Tokens-Per-Minute (TPM) sliding window to settle
|
|
363
384
|
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.POST_INVESTIGATION_MS));
|
|
385
|
+
if (ac.signal.aborted) {
|
|
386
|
+
spinner.stop();
|
|
387
|
+
process.stdout.write('\x1b[2K\r');
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
364
390
|
}
|
|
365
391
|
let latestUsage = undefined;
|
|
366
392
|
// Collect any inputs that were queued while the Context Agent was investigating
|
|
@@ -372,7 +398,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
372
398
|
let effectiveTargetAgent = gatherRes.targetAgent;
|
|
373
399
|
if (gatherRes.chainedMessages.length > 0) {
|
|
374
400
|
const chainedContent = gatherRes.chainedMessages.join('\n');
|
|
375
|
-
const newIntent = await routeIntent(chainedContent);
|
|
401
|
+
const newIntent = await routeIntent(chainedContent, '', ac.signal);
|
|
376
402
|
if (gatherRes.contextResult !== null) {
|
|
377
403
|
// If the model previously needed search context, determine if followups change the mode
|
|
378
404
|
effectiveTargetAgent = newIntent.targetAgent;
|
|
@@ -392,6 +418,11 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
392
418
|
}
|
|
393
419
|
finalInput += `\n\n[USER FOLLOW-UP INSTRUCTIONS SENT DURING INVESTIGATION]:\n${chainedContent}\n\nPlease incorporate these instructions into your work. Address them appropriately, but ensure you do not lose track of the original request's primary objective.`;
|
|
394
420
|
}
|
|
421
|
+
if (ac.signal.aborted) {
|
|
422
|
+
spinner.stop();
|
|
423
|
+
process.stdout.write('\x1b[2K\r');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
395
426
|
// Hot-swap the underlying LLM system instruction context depending on intent (conversational vs execution)
|
|
396
427
|
debugLog(`Intent Router output: original targetAgent = ${gatherRes.targetAgent}, effective = ${effectiveTargetAgent}`);
|
|
397
428
|
let config;
|
|
@@ -430,7 +461,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
430
461
|
else {
|
|
431
462
|
spinner.stop(); // MUST clear the investigation spinner first to prevent leaking the setInterval
|
|
432
463
|
spinner.start(pc.blue('🧠 Evaluating execution complexity...'));
|
|
433
|
-
const complexity = await evaluateExecutionComplexity(finalInput, gatherRes.contextResult?.summary, gatherRes.contextResult?.relevantFiles?.size || 0);
|
|
464
|
+
const complexity = await evaluateExecutionComplexity(finalInput, gatherRes.contextResult?.summary, gatherRes.contextResult?.relevantFiles?.size || 0, '', ac.signal);
|
|
434
465
|
spinner.stop();
|
|
435
466
|
process.stdout.write('\x1b[2K\r');
|
|
436
467
|
selectedModel = complexity === 'EASY' ? GEMINI_MODELS.FLASH_LITE : GEMINI_MODELS.FLASH;
|
|
@@ -441,6 +472,11 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
441
472
|
else {
|
|
442
473
|
chat.setModel(getGlobalActiveModel());
|
|
443
474
|
}
|
|
475
|
+
if (ac.signal.aborted) {
|
|
476
|
+
spinner.stop();
|
|
477
|
+
process.stdout.write('\x1b[2K\r');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
444
480
|
if (gatherRes.contextResult) {
|
|
445
481
|
if (!inputHandler.isCurrentlyPrompting()) {
|
|
446
482
|
spinner.stop(pc.green('🔍 Investigation complete.'));
|
|
@@ -710,7 +746,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
710
746
|
if (!chatSessionState.title) {
|
|
711
747
|
chatSessionState.title = 'Generating title...';
|
|
712
748
|
try {
|
|
713
|
-
const title = await generateChatTitle(userInput);
|
|
749
|
+
const title = await generateChatTitle(userInput, ac.signal);
|
|
714
750
|
chatSessionState.title = title;
|
|
715
751
|
await chatHistoryService.saveSession({
|
|
716
752
|
id: chatSessionState.id,
|
|
@@ -799,10 +835,15 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
799
835
|
// label: 'Claude 5 Sonnet',
|
|
800
836
|
// hint: 'Best for raw speed and cost efficiency',
|
|
801
837
|
// },
|
|
838
|
+
{
|
|
839
|
+
value: 'gemini-3.7-flash',
|
|
840
|
+
label: 'Gemini 3.7 Flash',
|
|
841
|
+
hint: 'Next-gen reasoning, speed & balanced performance',
|
|
842
|
+
},
|
|
802
843
|
{
|
|
803
844
|
value: 'gemini-3.6-flash',
|
|
804
845
|
label: 'Gemini 3.6 Flash',
|
|
805
|
-
hint: '
|
|
846
|
+
hint: 'Everyday coding — fast and accurate',
|
|
806
847
|
},
|
|
807
848
|
{
|
|
808
849
|
value: 'gemini-3.5-flash-lite',
|
|
@@ -812,7 +853,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
812
853
|
{
|
|
813
854
|
value: 'auto',
|
|
814
855
|
label: 'Auto (Flash-Lite / Flash)',
|
|
815
|
-
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.
|
|
856
|
+
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.7 Flash based on prompt complexity',
|
|
816
857
|
},
|
|
817
858
|
].filter((o) => !(byokEnabled && o.value.includes('claude')));
|
|
818
859
|
const selectedModel = await p['select']({
|
|
@@ -842,6 +883,9 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
842
883
|
return { planModeReturn, contextResult: gatherRes.contextResult };
|
|
843
884
|
}
|
|
844
885
|
catch (err) {
|
|
886
|
+
if (ac.signal.aborted || err?.name === 'AbortError' || err?.message?.includes('abort')) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
845
889
|
const message = err instanceof Error ? err.message : String(err);
|
|
846
890
|
p.log.error(`${pc.red('Error:')} ${message}`);
|
|
847
891
|
}
|
|
@@ -955,7 +999,7 @@ export const HISTORY_SUMMARIZATION_THRESHOLD = 50;
|
|
|
955
999
|
* @param chat - The active ProxyChatSession instance.
|
|
956
1000
|
* @returns A promise resolving to true if history was summarized and updated; false otherwise.
|
|
957
1001
|
*/
|
|
958
|
-
export async function summarizeHistoryIfNeeded(chat) {
|
|
1002
|
+
export async function summarizeHistoryIfNeeded(chat, abortSignal) {
|
|
959
1003
|
const history = chat.getRawHistory();
|
|
960
1004
|
if (!history || history.length < HISTORY_SUMMARIZATION_THRESHOLD) {
|
|
961
1005
|
return false;
|
|
@@ -966,7 +1010,7 @@ export async function summarizeHistoryIfNeeded(chat) {
|
|
|
966
1010
|
const olderHistory = history.slice(0, history.length - recentCount);
|
|
967
1011
|
const recentHistory = history.slice(history.length - recentCount);
|
|
968
1012
|
debugLog(`Summarizing ${olderHistory.length} older chat history entries...`);
|
|
969
|
-
const summaryText = await summarizeChatHistory(olderHistory);
|
|
1013
|
+
const summaryText = await summarizeChatHistory(olderHistory, abortSignal);
|
|
970
1014
|
if (summaryText && summaryText.trim().length > 0) {
|
|
971
1015
|
const summaryContent = [
|
|
972
1016
|
{
|
|
@@ -984,6 +1028,9 @@ export async function summarizeHistoryIfNeeded(chat) {
|
|
|
984
1028
|
}
|
|
985
1029
|
}
|
|
986
1030
|
catch (error) {
|
|
1031
|
+
if (abortSignal?.aborted || error?.name === 'AbortError' || error?.message?.includes('abort')) {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
987
1034
|
debugLog(`Failed to summarize chat history: ${error?.message || error}`);
|
|
988
1035
|
}
|
|
989
1036
|
return false;
|
|
@@ -998,15 +1045,15 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
|
|
|
998
1045
|
let intentVerified = false;
|
|
999
1046
|
let isFixingCodeError = false;
|
|
1000
1047
|
while (correctionAttempts <= MAX_CORRECTIONS) {
|
|
1001
|
-
if (finalText === '[Generation stopped by user]')
|
|
1048
|
+
if (signal.aborted || finalText === '[Generation stopped by user]')
|
|
1002
1049
|
break;
|
|
1003
1050
|
const historyLengthBefore = chat.getRawHistory().length;
|
|
1004
1051
|
const currentText = await processResponse(chat, result, workspaceRoot, inputHandler, agentState, signal);
|
|
1005
1052
|
const historyLengthAfter = chat.getRawHistory().length;
|
|
1006
1053
|
const usedTools = historyLengthAfter > historyLengthBefore;
|
|
1007
1054
|
debugLog(`processResponse returned text (length ${currentText.length}): "${currentText.substring(0, 10)}..."`);
|
|
1008
|
-
if (currentText === '[Generation stopped by user]') {
|
|
1009
|
-
finalText =
|
|
1055
|
+
if (currentText === '[Generation stopped by user]' || signal.aborted) {
|
|
1056
|
+
finalText = '[Generation stopped by user]';
|
|
1010
1057
|
break;
|
|
1011
1058
|
}
|
|
1012
1059
|
if (currentText.startsWith('[The AI repeatedly returned empty responses')) {
|
|
@@ -1032,7 +1079,18 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
|
|
|
1032
1079
|
debugLog('AI failed to execute tools or modify files during the correction attempt. Retrying...');
|
|
1033
1080
|
const forcePrompt = `AUTOMATED SYSTEM CHECK: You did not execute any tools or modify files. You MUST use your tools (such as run_command or modify_file) to investigate and apply a fix for the previously mentioned errors. Do not just explain the issue.`;
|
|
1034
1081
|
spinner.start('Thinking (Correction)...');
|
|
1035
|
-
|
|
1082
|
+
try {
|
|
1083
|
+
result = await chat.sendMessage(forcePrompt, undefined, signal);
|
|
1084
|
+
}
|
|
1085
|
+
catch (e) {
|
|
1086
|
+
spinner.stop();
|
|
1087
|
+
process.stdout.write('\x1b[2K\r');
|
|
1088
|
+
if (e.name === 'AbortError' || e.message?.includes('abort') || signal.aborted) {
|
|
1089
|
+
p.log.warn(pc.yellow('Generation stopped by user during correction.'));
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
throw e;
|
|
1093
|
+
}
|
|
1036
1094
|
spinner.stop();
|
|
1037
1095
|
process.stdout.write('\x1b[2K\r');
|
|
1038
1096
|
correctionAttempts++;
|
|
@@ -1057,7 +1115,7 @@ If you are not done, please continue working using your other tools.`;
|
|
|
1057
1115
|
catch (e) {
|
|
1058
1116
|
spinner.stop();
|
|
1059
1117
|
process.stdout.write('\x1b[2K\r');
|
|
1060
|
-
if (e.name === 'AbortError' || e.message?.includes('abort')) {
|
|
1118
|
+
if (e.name === 'AbortError' || e.message?.includes('abort') || signal.aborted) {
|
|
1061
1119
|
p.log.warn(pc.yellow('Generation stopped by user during verification.'));
|
|
1062
1120
|
break;
|
|
1063
1121
|
}
|
|
@@ -1130,7 +1188,18 @@ If you are not done, please continue working using your other tools.`;
|
|
|
1130
1188
|
// Compile compilation and syntax diagnostic warnings into an auto-correction prompt
|
|
1131
1189
|
const correctionPrompt = `AUTOMATED SYSTEM CHECK: Your previous changes resulted in the following issues:\n\n${combinedIssuesForAI}\n\nPlease analyze these issues and use your file modification tools to fix them.`;
|
|
1132
1190
|
spinner.start('Thinking (Correction)...');
|
|
1133
|
-
|
|
1191
|
+
try {
|
|
1192
|
+
result = await chat.sendMessage(correctionPrompt, undefined, signal);
|
|
1193
|
+
}
|
|
1194
|
+
catch (e) {
|
|
1195
|
+
spinner.stop();
|
|
1196
|
+
process.stdout.write('\x1b[2K\r');
|
|
1197
|
+
if (e.name === 'AbortError' || e.message?.includes('abort') || signal.aborted) {
|
|
1198
|
+
p.log.warn(pc.yellow('Generation stopped by user during correction.'));
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
throw e;
|
|
1202
|
+
}
|
|
1134
1203
|
spinner.stop();
|
|
1135
1204
|
process.stdout.write('\x1b[2K\r');
|
|
1136
1205
|
}
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export declare function getPlanModeConfig(): {
|
|
|
80
80
|
* Compresses a large string of text using gemini-3.5-flash-lite.
|
|
81
81
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
82
82
|
*/
|
|
83
|
-
export declare function compressTextUsingFlashLite(text: string, instruction?: string, inlineData?: any, force?: boolean): Promise<string>;
|
|
83
|
+
export declare function compressTextUsingFlashLite(text: string, instruction?: string, inlineData?: any, force?: boolean, abortSignal?: AbortSignal): Promise<string>;
|
|
84
84
|
/**
|
|
85
85
|
* Returns the tool declarations for the read-only Context Agent.
|
|
86
86
|
* Extracted as a reusable function so investigation sub-agents can import
|
|
@@ -96,8 +96,8 @@ export declare function createHistorySummarizerSession(): any;
|
|
|
96
96
|
/**
|
|
97
97
|
* Summarizes an array of Content history entries using Gemini Flash Lite.
|
|
98
98
|
*/
|
|
99
|
-
export declare function summarizeChatHistory(history: Content[]): Promise<string>;
|
|
99
|
+
export declare function summarizeChatHistory(history: Content[], abortSignal?: AbortSignal): Promise<string>;
|
|
100
100
|
/**
|
|
101
101
|
* Generates a concise title for a chat session based on the user's first message.
|
|
102
102
|
*/
|
|
103
|
-
export declare function generateChatTitle(firstMessage: string): Promise<string>;
|
|
103
|
+
export declare function generateChatTitle(firstMessage: string, abortSignal?: AbortSignal): Promise<string>;
|
package/dist/services/ai.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { SchemaType
|
|
1
|
+
import { SchemaType } from '@google/generative-ai';
|
|
2
2
|
import { GEMINI_MODELS, DEFAULT_MODEL, MAX_OUTPUT_TOKENS, isByokEnabled } from '../utils/config.js';
|
|
3
3
|
import { getToolDeclarations } from './agent-tools.js';
|
|
4
4
|
import { getMetricCollector } from './metrics.js';
|
|
5
|
-
import { getAuthorizedIdToken } from './auth.js';
|
|
5
|
+
import { getAuthorizedIdToken, checkByokSubscription } from './auth.js';
|
|
6
6
|
import { debugLog } from '../utils/logger.js';
|
|
7
7
|
import { readCache, writeCache } from '../utils/projectStorage.js';
|
|
8
8
|
import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, PLAN_MODE_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
|
|
@@ -186,6 +186,14 @@ export class ProxyChatSession {
|
|
|
186
186
|
if (!idToken) {
|
|
187
187
|
throw new Error('You are not signed in. Please run `minovative-mind-cli login` first.');
|
|
188
188
|
}
|
|
189
|
+
const byokEnabled = await isByokEnabled();
|
|
190
|
+
if (byokEnabled) {
|
|
191
|
+
const subCheck = await checkByokSubscription();
|
|
192
|
+
if (!subCheck.active) {
|
|
193
|
+
throw new Error(subCheck.message ||
|
|
194
|
+
'A $3.99/month BYOK Subscription is required to use your own API key. Please visit https://www.minovativemind.dev/pricing to subscribe. The $3.99 is to cover account maintance for you.');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
189
197
|
// Convert message to Part, truncating text to prevent memory blowout
|
|
190
198
|
let newParts;
|
|
191
199
|
if (typeof message === 'string') {
|
|
@@ -228,7 +236,6 @@ export class ProxyChatSession {
|
|
|
228
236
|
// Prune old history before sending to keep payload bounded
|
|
229
237
|
await this.pruneHistory();
|
|
230
238
|
const effectiveGenerationConfig = { ...this.generationConfig };
|
|
231
|
-
const byokEnabled = await isByokEnabled();
|
|
232
239
|
let result;
|
|
233
240
|
if (byokEnabled) {
|
|
234
241
|
const creds = await loadCredentials();
|
|
@@ -341,9 +348,11 @@ export function getPlanModeConfig() {
|
|
|
341
348
|
* Compresses a large string of text using gemini-3.5-flash-lite.
|
|
342
349
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
343
350
|
*/
|
|
344
|
-
export async function compressTextUsingFlashLite(text, instruction = "<directives>\nSummarize the following text concisely. Preserve the most critical technical details, function names, and architecture logic. Make sure it's understandable without the fluff.\n</directives>", inlineData, force = false) {
|
|
351
|
+
export async function compressTextUsingFlashLite(text, instruction = "<directives>\nSummarize the following text concisely. Preserve the most critical technical details, function names, and architecture logic. Make sure it's understandable without the fluff.\n</directives>", inlineData, force = false, abortSignal) {
|
|
345
352
|
if (!text || (!force && text.length < 1000 && !inlineData))
|
|
346
353
|
return text; // Don't compress tiny texts unless forced
|
|
354
|
+
if (abortSignal?.aborted)
|
|
355
|
+
return text;
|
|
347
356
|
try {
|
|
348
357
|
const idToken = await getAuthorizedIdToken();
|
|
349
358
|
if (!idToken)
|
|
@@ -357,13 +366,19 @@ export async function compressTextUsingFlashLite(text, instruction = "<directive
|
|
|
357
366
|
const byokEnabled = await isByokEnabled();
|
|
358
367
|
let result;
|
|
359
368
|
if (byokEnabled) {
|
|
369
|
+
const subCheck = await checkByokSubscription();
|
|
370
|
+
if (!subCheck.active) {
|
|
371
|
+
throw new Error(subCheck.message ||
|
|
372
|
+
'A $3.99/month BYOK Subscription is required. Visit https://www.minovativemind.dev/pricing');
|
|
373
|
+
}
|
|
360
374
|
const creds = await loadCredentials();
|
|
361
375
|
result = await proxyClient.generateViaBYOK(creds.geminiApiKey, model, contents, [], // no tools
|
|
362
|
-
undefined, instruction, { temperature: 0.2 });
|
|
376
|
+
undefined, instruction, { temperature: 0.2 }, undefined, abortSignal);
|
|
363
377
|
}
|
|
364
378
|
else {
|
|
365
379
|
result = await proxyClient.generateFunctionCallViaProxy(idToken, model, contents, [], // no tools
|
|
366
|
-
undefined, instruction, { temperature: 0.2 }
|
|
380
|
+
undefined, instruction, { temperature: 0.2 }, // low temp for factual summary
|
|
381
|
+
undefined, abortSignal);
|
|
367
382
|
}
|
|
368
383
|
let textPart = '';
|
|
369
384
|
if (result.parts) {
|
|
@@ -379,6 +394,9 @@ export async function compressTextUsingFlashLite(text, instruction = "<directive
|
|
|
379
394
|
return summary ? summary : text;
|
|
380
395
|
}
|
|
381
396
|
catch (error) {
|
|
397
|
+
if (abortSignal?.aborted || error?.name === 'AbortError' || error?.message?.includes('abort')) {
|
|
398
|
+
return text;
|
|
399
|
+
}
|
|
382
400
|
debugLog(`Failed to compress text using flash-lite: ${error}`);
|
|
383
401
|
if (error?.status === 401 ||
|
|
384
402
|
error?.status === 403 ||
|
|
@@ -483,7 +501,7 @@ export function getContextToolDeclarations() {
|
|
|
483
501
|
},
|
|
484
502
|
{
|
|
485
503
|
name: 'run_analysis_script',
|
|
486
|
-
description:
|
|
504
|
+
description: 'Write and execute a disposable analysis script to structurally map code in the workspace. Use this to get exact line ranges for functions, classes, and variables by leveraging the language\'s native AST parser (e.g., TypeScript compiler API, Python ast module, go/parser). You can also use this to probe the development environment — detecting available runtimes (e.g., node --version, python3 --version), checking if ports are in use, identifying project type (monorepo, package manager), or diagnosing system-level issues (disk space, memory) that may affect execution. For complex investigation tasks, you can write lightweight ML scripts (e.g., TF-IDF cosine similarity to rank file relevance, Z-score outlier detection for anomalous log lines, K-Means clustering, or Naive Bayes classification). Default to "node" for generic math/analysis as a safe baseline, but act like a native inhabitant of the host environment — if Python, Go, Rust, or specialized libraries are available in the project context, leverage the host\'s native runtimes and standard libraries for maximum efficiency. The script is executed from a temporary directory and automatically cleaned up after execution. Output should be structured JSON to stdout. Use the results to make precise read_file calls with exact startLine/endLine instead of guessing. CRITICAL: Do not use this tool on binary, document, or non-code files (e.g. PDF, image, audio, docx).',
|
|
487
505
|
parameters: {
|
|
488
506
|
type: SchemaType.OBJECT,
|
|
489
507
|
properties: {
|
|
@@ -587,22 +605,35 @@ export function createHistorySummarizerSession() {
|
|
|
587
605
|
let model = getGlobalActiveModel();
|
|
588
606
|
if (model === 'auto' || model.includes('claude'))
|
|
589
607
|
model = GEMINI_MODELS.FLASH_LITE;
|
|
590
|
-
return new ProxyChatSession(model, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, [], {
|
|
608
|
+
return new ProxyChatSession(model, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, [], {
|
|
609
|
+
temperature: 0.2,
|
|
610
|
+
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
611
|
+
});
|
|
591
612
|
}
|
|
592
613
|
/**
|
|
593
614
|
* Summarizes an array of Content history entries using Gemini Flash Lite.
|
|
594
615
|
*/
|
|
595
|
-
export async function summarizeChatHistory(history) {
|
|
616
|
+
export async function summarizeChatHistory(history, abortSignal) {
|
|
596
617
|
if (!history || history.length === 0) {
|
|
597
618
|
return '';
|
|
598
619
|
}
|
|
620
|
+
if (abortSignal?.aborted) {
|
|
621
|
+
const err = new Error('Operation aborted');
|
|
622
|
+
err.name = 'AbortError';
|
|
623
|
+
throw err;
|
|
624
|
+
}
|
|
599
625
|
try {
|
|
600
626
|
const session = createHistorySummarizerSession();
|
|
601
627
|
session.loadRawHistory(JSON.parse(JSON.stringify(history)));
|
|
602
|
-
const result = await session.sendMessage('Summarize the preceding conversation history following your compression rules.');
|
|
628
|
+
const result = await session.sendMessage('Summarize the preceding conversation history following your compression rules.', undefined, abortSignal);
|
|
603
629
|
return result.response.text() || '';
|
|
604
630
|
}
|
|
605
631
|
catch (error) {
|
|
632
|
+
if (abortSignal?.aborted || error?.name === 'AbortError' || error?.message?.includes('abort')) {
|
|
633
|
+
const err = new Error('Operation aborted');
|
|
634
|
+
err.name = 'AbortError';
|
|
635
|
+
throw err;
|
|
636
|
+
}
|
|
606
637
|
debugLog(`Failed to summarize chat history: ${error?.message || error}`);
|
|
607
638
|
return '';
|
|
608
639
|
}
|
|
@@ -610,10 +641,12 @@ export async function summarizeChatHistory(history) {
|
|
|
610
641
|
/**
|
|
611
642
|
* Generates a concise title for a chat session based on the user's first message.
|
|
612
643
|
*/
|
|
613
|
-
export async function generateChatTitle(firstMessage) {
|
|
644
|
+
export async function generateChatTitle(firstMessage, abortSignal) {
|
|
614
645
|
const maxLength = 60;
|
|
615
646
|
if (!firstMessage || firstMessage.trim().length === 0)
|
|
616
647
|
return 'New Chat';
|
|
648
|
+
if (abortSignal?.aborted)
|
|
649
|
+
return firstMessage.substring(0, maxLength);
|
|
617
650
|
try {
|
|
618
651
|
const idToken = await getAuthorizedIdToken();
|
|
619
652
|
if (!idToken)
|
|
@@ -626,13 +659,17 @@ export async function generateChatTitle(firstMessage) {
|
|
|
626
659
|
const byokEnabled = await isByokEnabled();
|
|
627
660
|
let result;
|
|
628
661
|
if (byokEnabled) {
|
|
662
|
+
const subCheck = await checkByokSubscription();
|
|
663
|
+
if (!subCheck.active) {
|
|
664
|
+
return firstMessage.substring(0, maxLength);
|
|
665
|
+
}
|
|
629
666
|
const creds = await loadCredentials();
|
|
630
667
|
result = await proxyClient.generateViaBYOK(creds.geminiApiKey, model, contents, [], // no tools
|
|
631
|
-
undefined, instruction, { temperature: 0.2 });
|
|
668
|
+
undefined, instruction, { temperature: 0.2 }, undefined, abortSignal);
|
|
632
669
|
}
|
|
633
670
|
else {
|
|
634
671
|
result = await proxyClient.generateFunctionCallViaProxy(idToken, model, contents, [], // no tools
|
|
635
|
-
undefined, instruction, { temperature: 0.2 });
|
|
672
|
+
undefined, instruction, { temperature: 0.2 }, undefined, abortSignal);
|
|
636
673
|
}
|
|
637
674
|
let title = '';
|
|
638
675
|
if (result.parts) {
|
package/dist/services/auth.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export declare function login(): Promise<boolean>;
|
|
2
2
|
export declare function logout(): Promise<void>;
|
|
3
3
|
export declare function getAuthorizedIdToken(): Promise<string | undefined>;
|
|
4
|
+
/**
|
|
5
|
+
* Checks if the signed-in CLI user has an active $3.99/month BYOK subscription.
|
|
6
|
+
*/
|
|
7
|
+
export declare function checkByokSubscription(): Promise<{
|
|
8
|
+
active: boolean;
|
|
9
|
+
message?: string;
|
|
10
|
+
}>;
|
package/dist/services/auth.js
CHANGED
|
@@ -160,3 +160,44 @@ export async function getAuthorizedIdToken() {
|
|
|
160
160
|
}
|
|
161
161
|
return undefined;
|
|
162
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Checks if the signed-in CLI user has an active $3.99/month BYOK subscription.
|
|
165
|
+
*/
|
|
166
|
+
export async function checkByokSubscription() {
|
|
167
|
+
const idToken = await getAuthorizedIdToken();
|
|
168
|
+
if (!idToken) {
|
|
169
|
+
return {
|
|
170
|
+
active: false,
|
|
171
|
+
message: "Authentication Required: You must be logged into minovative-mind-cli to use BYOK mode. Run 'minovative-mind-cli login' first, then subscribe at https://www.minovativemind.dev/pricing ($3.99/month).",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const res = await fetch('https://verifysubscription-6obg3e4zwa-uc.a.run.app', {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: {
|
|
178
|
+
'Content-Type': 'application/json',
|
|
179
|
+
'X-Firebase-Auth': `Bearer ${idToken}`,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
return {
|
|
184
|
+
active: false,
|
|
185
|
+
message: 'A $3.99/month BYOK Subscription is required to use your own API key. Please visit https://www.minovativemind.dev/pricing to subscribe. The $3.99 is to cover account maintance for you.',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const data = (await res.json());
|
|
189
|
+
if (!data.hasActiveSubscription) {
|
|
190
|
+
return {
|
|
191
|
+
active: false,
|
|
192
|
+
message: 'A $3.99/month BYOK Subscription is required to use your own API key. Please visit https://www.minovativemind.dev/pricing to subscribe. The $3.99 is to cover account maintance for you.',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { active: true };
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return {
|
|
199
|
+
active: false,
|
|
200
|
+
message: 'Failed to verify BYOK subscription status. Please check your network connection or visit https://www.minovativemind.dev/pricing to subscribe ($3.99/month).',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -14,8 +14,8 @@ export interface IntentRoute {
|
|
|
14
14
|
needsContext: boolean;
|
|
15
15
|
targetAgent: 'CHAT' | 'EXECUTE';
|
|
16
16
|
}
|
|
17
|
-
export declare function routeIntent(userRequest: string, chatHistory?: string): Promise<IntentRoute>;
|
|
18
|
-
export declare function evaluateExecutionComplexity(userRequest: string, investigationSummary: string | undefined, numRelevantFiles: number, chatHistory?: string): Promise<'EASY' | 'HARD'>;
|
|
17
|
+
export declare function routeIntent(userRequest: string, chatHistory?: string, abortSignal?: AbortSignal): Promise<IntentRoute>;
|
|
18
|
+
export declare function evaluateExecutionComplexity(userRequest: string, investigationSummary: string | undefined, numRelevantFiles: number, chatHistory?: string, abortSignal?: AbortSignal): Promise<'EASY' | 'HARD'>;
|
|
19
19
|
export declare function gatherContext(workspaceRoot: string, userRequest: string, chatHistory: string | undefined, inputHandler: {
|
|
20
20
|
getAndClear: () => string;
|
|
21
21
|
waitForPrompt: () => Promise<void>;
|
|
@@ -139,14 +139,19 @@ async function detectProjectType(workspaceRoot) {
|
|
|
139
139
|
}
|
|
140
140
|
return types.join(' / ');
|
|
141
141
|
}
|
|
142
|
-
export async function routeIntent(userRequest, chatHistory = '') {
|
|
142
|
+
export async function routeIntent(userRequest, chatHistory = '', abortSignal) {
|
|
143
|
+
if (abortSignal?.aborted) {
|
|
144
|
+
const err = new Error('Operation aborted');
|
|
145
|
+
err.name = 'AbortError';
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
143
148
|
try {
|
|
144
149
|
const session = createIntentRouterSession();
|
|
145
150
|
let prompt = `User Request: "${userRequest}"`;
|
|
146
151
|
if (chatHistory) {
|
|
147
152
|
prompt = `Previous Conversation Context:\n${chatHistory}\n\n${prompt}`;
|
|
148
153
|
}
|
|
149
|
-
const result = await session.sendMessage(prompt);
|
|
154
|
+
const result = await session.sendMessage(prompt, undefined, abortSignal);
|
|
150
155
|
const text = result.response.text()?.trim() || '{}';
|
|
151
156
|
const parsed = JSON.parse(text);
|
|
152
157
|
debugLog(`Intent Router Parsed: ${JSON.stringify(parsed)}`);
|
|
@@ -156,12 +161,22 @@ export async function routeIntent(userRequest, chatHistory = '') {
|
|
|
156
161
|
};
|
|
157
162
|
}
|
|
158
163
|
catch (e) {
|
|
164
|
+
if (abortSignal?.aborted || e?.name === 'AbortError' || e?.message?.includes('abort')) {
|
|
165
|
+
const err = new Error('Operation aborted');
|
|
166
|
+
err.name = 'AbortError';
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
159
169
|
debugLog(`Intent Router failed to parse JSON, falling back to EXECUTE. Error: ${String(e)}`);
|
|
160
170
|
// Fallback to searching if the router fails
|
|
161
171
|
return { needsContext: true, targetAgent: 'EXECUTE' };
|
|
162
172
|
}
|
|
163
173
|
}
|
|
164
|
-
export async function evaluateExecutionComplexity(userRequest, investigationSummary, numRelevantFiles, chatHistory = '') {
|
|
174
|
+
export async function evaluateExecutionComplexity(userRequest, investigationSummary, numRelevantFiles, chatHistory = '', abortSignal) {
|
|
175
|
+
if (abortSignal?.aborted) {
|
|
176
|
+
const err = new Error('Operation aborted');
|
|
177
|
+
err.name = 'AbortError';
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
165
180
|
try {
|
|
166
181
|
const session = createExecutionComplexitySession();
|
|
167
182
|
let prompt = `User Request: "${userRequest}"
|
|
@@ -170,24 +185,39 @@ Number of Relevant Files: ${numRelevantFiles}`;
|
|
|
170
185
|
if (chatHistory) {
|
|
171
186
|
prompt = `Previous Conversation Context:\n${chatHistory}\n\n${prompt}`;
|
|
172
187
|
}
|
|
173
|
-
const result = await session.sendMessage(prompt);
|
|
188
|
+
const result = await session.sendMessage(prompt, undefined, abortSignal);
|
|
174
189
|
const text = result.response.text()?.trim() || '{}';
|
|
175
190
|
const parsed = JSON.parse(text);
|
|
176
191
|
debugLog(`Execution Complexity Parsed: ${JSON.stringify(parsed)}`);
|
|
177
192
|
return parsed.complexity === 'EASY' ? 'EASY' : 'HARD';
|
|
178
193
|
}
|
|
179
194
|
catch (e) {
|
|
195
|
+
if (abortSignal?.aborted || e?.name === 'AbortError' || e?.message?.includes('abort')) {
|
|
196
|
+
const err = new Error('Operation aborted');
|
|
197
|
+
err.name = 'AbortError';
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
180
200
|
debugLog(`Execution Complexity Router failed, falling back to HARD. Error: ${String(e)}`);
|
|
181
201
|
return 'HARD';
|
|
182
202
|
}
|
|
183
203
|
}
|
|
184
204
|
export async function gatherContext(workspaceRoot, userRequest, chatHistory = '', inputHandler, abortSignal, onProgress, onToolCall) {
|
|
205
|
+
if (abortSignal.aborted) {
|
|
206
|
+
const err = new Error('Operation aborted');
|
|
207
|
+
err.name = 'AbortError';
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
185
210
|
// Always skip slash commands for zero latency
|
|
186
211
|
if (userRequest.startsWith('/')) {
|
|
187
212
|
return { contextResult: null, targetAgent: 'EXECUTE', chainedMessages: [] };
|
|
188
213
|
}
|
|
189
214
|
// Use the AI Intent Router to decide if we need to search
|
|
190
|
-
const { needsContext, targetAgent } = await routeIntent(userRequest, chatHistory);
|
|
215
|
+
const { needsContext, targetAgent } = await routeIntent(userRequest, chatHistory, abortSignal);
|
|
216
|
+
if (abortSignal.aborted) {
|
|
217
|
+
const err = new Error('Operation aborted');
|
|
218
|
+
err.name = 'AbortError';
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
191
221
|
debugLog(`GatherContext Route: needsContext=${needsContext}, targetAgent=${targetAgent}`);
|
|
192
222
|
if (!needsContext) {
|
|
193
223
|
return { contextResult: null, targetAgent, chainedMessages: [] };
|
|
@@ -197,17 +227,22 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
197
227
|
let projectTree = '';
|
|
198
228
|
let primaryProjectType = 'Unknown';
|
|
199
229
|
for (const { alias, root } of allRoots) {
|
|
230
|
+
if (abortSignal.aborted) {
|
|
231
|
+
const err = new Error('Operation aborted');
|
|
232
|
+
err.name = 'AbortError';
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
200
235
|
const label = alias ? `@${alias} (${root})` : `Primary Workspace (${root})`;
|
|
201
236
|
const treeResult = await executeTool(root, 'list_directory', { dirPath: '.', maxDepth: 10 });
|
|
202
237
|
let tree = treeResult.output;
|
|
203
238
|
if (tree.length > 30000) {
|
|
204
|
-
tree = tree.substring(0, 30000) + '
|
|
239
|
+
tree = tree.substring(0, 30000) + '\n... (Project tree truncated due to size)';
|
|
205
240
|
}
|
|
206
241
|
const type = await detectProjectType(root);
|
|
207
242
|
if (!alias) {
|
|
208
243
|
primaryProjectType = type;
|
|
209
244
|
}
|
|
210
|
-
projectTree += `=== ${label}
|
|
245
|
+
projectTree += `=== ${label} ===\nProject Type: ${type}\n${tree}\n\n`;
|
|
211
246
|
}
|
|
212
247
|
const projectType = primaryProjectType;
|
|
213
248
|
const { lookupInvestigation, saveInvestigation } = await import('./orchestration/investigationCache.js');
|
|
@@ -217,6 +252,11 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
217
252
|
onProgress(`⚡ Memory Bank HIT — loaded ${cacheHit.entry.relevantFiles.length} files from cache`);
|
|
218
253
|
const cachedFiles = new Map();
|
|
219
254
|
for (const filePath of cacheHit.entry.relevantFiles) {
|
|
255
|
+
if (abortSignal.aborted) {
|
|
256
|
+
const err = new Error('Operation aborted');
|
|
257
|
+
err.name = 'AbortError';
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
220
260
|
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
221
261
|
if (!readResult.error) {
|
|
222
262
|
cachedFiles.set(filePath, { text: readResult.output, inlineData: readResult.inlineData });
|
|
@@ -227,11 +267,15 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
227
267
|
const { resolveAndValidateMultiWorkspacePath } = await import('../utils/pathSecurity.js');
|
|
228
268
|
const autoDiscovered = new Set();
|
|
229
269
|
for (const filePath of cacheHit.entry.relevantFiles) {
|
|
270
|
+
if (abortSignal.aborted)
|
|
271
|
+
break;
|
|
230
272
|
try {
|
|
231
273
|
const resolved = resolveAndValidateMultiWorkspacePath(workspaceRoot, filePath);
|
|
232
274
|
const graph = await buildDependencyGraph(resolved.workspaceRoot);
|
|
233
275
|
const reverseDeps = graph.getImportedBy(resolved.relativePath);
|
|
234
276
|
for (const dep of reverseDeps) {
|
|
277
|
+
if (abortSignal.aborted)
|
|
278
|
+
break;
|
|
235
279
|
if (cachedFiles.has(dep) || autoDiscovered.has(dep))
|
|
236
280
|
continue;
|
|
237
281
|
if (cachedFiles.size + autoDiscovered.size >= MAX_TOTAL_FILES)
|
|
@@ -265,7 +309,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
265
309
|
if (isSubAgentsEnabled()) {
|
|
266
310
|
// Determine complexity and domain breakdown
|
|
267
311
|
const approxFiles = projectTree.split('\n').length;
|
|
268
|
-
const complexity = await evaluateInvestigationComplexity(userRequest, projectType, approxFiles, chatHistory);
|
|
312
|
+
const complexity = await evaluateInvestigationComplexity(userRequest, projectType, approxFiles, chatHistory, abortSignal);
|
|
269
313
|
if (complexity.strategy === 'PARALLEL' && complexity.agentAssignments.length > 0) {
|
|
270
314
|
const orchestrator = new InvestigationOrchestrator();
|
|
271
315
|
const parallelResult = await orchestrator.runParallelInvestigation(userRequest, complexity.agentAssignments, workspaceRoot, projectTree, projectType, chatHistory, abortSignal, onProgress, onToolCall);
|
|
@@ -292,6 +336,11 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
292
336
|
currentMessage += `\n\nStart investigating to find relevant files.`;
|
|
293
337
|
const MAX_TURNS = Infinity;
|
|
294
338
|
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
339
|
+
if (abortSignal.aborted) {
|
|
340
|
+
const err = new Error('Operation aborted');
|
|
341
|
+
err.name = 'AbortError';
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
295
344
|
await inputHandler.waitForPrompt();
|
|
296
345
|
const queuedMsg = inputHandler.getAndClear();
|
|
297
346
|
let additionalText = undefined;
|
|
@@ -308,8 +357,10 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
308
357
|
result = await session.sendMessage(currentMessage, additionalText, abortSignal);
|
|
309
358
|
}
|
|
310
359
|
catch (e) {
|
|
311
|
-
if (e.name === 'AbortError' || e.message?.includes('abort')) {
|
|
312
|
-
|
|
360
|
+
if (abortSignal.aborted || e.name === 'AbortError' || e.message?.includes('abort')) {
|
|
361
|
+
const err = new Error('Operation aborted');
|
|
362
|
+
err.name = 'AbortError';
|
|
363
|
+
throw err;
|
|
313
364
|
}
|
|
314
365
|
throw e;
|
|
315
366
|
}
|
|
@@ -42,4 +42,4 @@ export interface InvestigationComplexityResult {
|
|
|
42
42
|
* @param chatHistory - Recent conversation history for context.
|
|
43
43
|
* @returns The complexity classification with domain decomposition.
|
|
44
44
|
*/
|
|
45
|
-
export declare function evaluateInvestigationComplexity(userRequest: string, projectType: string, approximateFileCount: number, chatHistory?: string): Promise<InvestigationComplexityResult>;
|
|
45
|
+
export declare function evaluateInvestigationComplexity(userRequest: string, projectType: string, approximateFileCount: number, chatHistory?: string, abortSignal?: AbortSignal): Promise<InvestigationComplexityResult>;
|
|
@@ -27,7 +27,12 @@ import { debugLog } from '../utils/logger.js';
|
|
|
27
27
|
* @param chatHistory - Recent conversation history for context.
|
|
28
28
|
* @returns The complexity classification with domain decomposition.
|
|
29
29
|
*/
|
|
30
|
-
export async function evaluateInvestigationComplexity(userRequest, projectType, approximateFileCount, chatHistory = '') {
|
|
30
|
+
export async function evaluateInvestigationComplexity(userRequest, projectType, approximateFileCount, chatHistory = '', abortSignal) {
|
|
31
|
+
if (abortSignal?.aborted) {
|
|
32
|
+
const err = new Error('Operation aborted');
|
|
33
|
+
err.name = 'AbortError';
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
31
36
|
try {
|
|
32
37
|
const session = createInvestigationComplexitySession();
|
|
33
38
|
let prompt = `User Request: "${userRequest}"
|
|
@@ -36,7 +41,7 @@ Approximate File Count: ${approximateFileCount}`;
|
|
|
36
41
|
if (chatHistory) {
|
|
37
42
|
prompt = `Previous Conversation Context:\n${chatHistory}\n\n${prompt}`;
|
|
38
43
|
}
|
|
39
|
-
const result = await session.sendMessage(prompt);
|
|
44
|
+
const result = await session.sendMessage(prompt, undefined, abortSignal);
|
|
40
45
|
const text = result.response.text()?.trim() || '{}';
|
|
41
46
|
const parsed = JSON.parse(text);
|
|
42
47
|
debugLog(`Investigation Complexity Parsed: ${JSON.stringify(parsed)}`);
|
|
@@ -80,6 +85,11 @@ Approximate File Count: ${approximateFileCount}`;
|
|
|
80
85
|
};
|
|
81
86
|
}
|
|
82
87
|
catch (e) {
|
|
88
|
+
if (abortSignal?.aborted || e?.name === 'AbortError' || e?.message?.includes('abort')) {
|
|
89
|
+
const err = new Error('Operation aborted');
|
|
90
|
+
err.name = 'AbortError';
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
83
93
|
debugLog(`Investigation Complexity Router failed, falling back to SINGLE. Error: ${String(e)}`);
|
|
84
94
|
return {
|
|
85
95
|
strategy: 'SINGLE',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview Main Orchestrator for Sub-Agent Dispatch and Coordination.
|
|
3
3
|
*
|
|
4
4
|
* The orchestrator acts as the "PM Kernel", responsible for:
|
|
5
|
-
* 1. Task Decomposition (using gemini-3.
|
|
5
|
+
* 1. Task Decomposition (using gemini-3.7-flash)
|
|
6
6
|
* 2. Graph Validation (Cycle detection via Kahn's algorithm)
|
|
7
7
|
* 3. Lock Ordering (Conflict resolution across parallel waves)
|
|
8
8
|
* 4. Parallel Dispatch (Executing waves sequentially, agents in parallel)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview Main Orchestrator for Sub-Agent Dispatch and Coordination.
|
|
3
3
|
*
|
|
4
4
|
* The orchestrator acts as the "PM Kernel", responsible for:
|
|
5
|
-
* 1. Task Decomposition (using gemini-3.
|
|
5
|
+
* 1. Task Decomposition (using gemini-3.7-flash)
|
|
6
6
|
* 2. Graph Validation (Cycle detection via Kahn's algorithm)
|
|
7
7
|
* 3. Lock Ordering (Conflict resolution across parallel waves)
|
|
8
8
|
* 4. Parallel Dispatch (Executing waves sequentially, agents in parallel)
|
|
@@ -43,7 +43,7 @@ export class SubAgentRunner {
|
|
|
43
43
|
let model = getGlobalActiveModel();
|
|
44
44
|
if (model === GEMINI_MODELS.AUTO)
|
|
45
45
|
model = GEMINI_MODELS.FLASH;
|
|
46
|
-
// Sub-agents default to flash-3.
|
|
46
|
+
// Sub-agents default to flash-3.7 for better reasoning capabilities
|
|
47
47
|
this.chat = new ProxyChatSession(model, this.buildSystemInstruction(), [{ functionDeclarations: getScopedToolDeclarations() }], {
|
|
48
48
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
49
49
|
temperature: 0.3, // Lower temperature for more focused execution
|
|
@@ -22,12 +22,14 @@ async function delay(ms, abortSignal) {
|
|
|
22
22
|
let timeout;
|
|
23
23
|
const abortHandler = () => {
|
|
24
24
|
clearTimeout(timeout);
|
|
25
|
-
|
|
25
|
+
const err = new Error('Operation aborted');
|
|
26
|
+
err.name = 'AbortError';
|
|
27
|
+
reject(err);
|
|
26
28
|
};
|
|
27
29
|
if (abortSignal?.aborted) {
|
|
28
30
|
return abortHandler();
|
|
29
31
|
}
|
|
30
|
-
abortSignal?.addEventListener('abort', abortHandler);
|
|
32
|
+
abortSignal?.addEventListener('abort', abortHandler, { once: true });
|
|
31
33
|
timeout = setTimeout(() => {
|
|
32
34
|
abortSignal?.removeEventListener('abort', abortHandler);
|
|
33
35
|
resolve();
|
|
@@ -97,6 +99,11 @@ export class ProxyClient {
|
|
|
97
99
|
const MAX_DELAY_MS = 30000;
|
|
98
100
|
let attempt = 0;
|
|
99
101
|
retryLoop: while (true) {
|
|
102
|
+
if (abortSignal?.aborted) {
|
|
103
|
+
const err = new Error('Operation aborted');
|
|
104
|
+
err.name = 'AbortError';
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
100
107
|
const response = await fetch(this.PROXY_URL, {
|
|
101
108
|
method: 'POST',
|
|
102
109
|
headers: {
|
|
@@ -115,11 +122,21 @@ export class ProxyClient {
|
|
|
115
122
|
});
|
|
116
123
|
debugLog(`Proxy Request to ${modelName} complete. Status: ${response.status} ${response.statusText}`);
|
|
117
124
|
if ((response.status === 429 || response.status === 503 || response.status === 502 || response.status === 500 || response.status === 504) && attempt < MAX_RETRIES) {
|
|
125
|
+
if (abortSignal?.aborted) {
|
|
126
|
+
const err = new Error('Operation aborted');
|
|
127
|
+
err.name = 'AbortError';
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
118
130
|
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
119
131
|
const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
|
|
120
132
|
process.stdout.write('\n');
|
|
121
133
|
console.warn(`Server error or rate limit hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
122
134
|
await delay(delayTime, abortSignal);
|
|
135
|
+
if (abortSignal?.aborted) {
|
|
136
|
+
const err = new Error('Operation aborted');
|
|
137
|
+
err.name = 'AbortError';
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
123
140
|
attempt++;
|
|
124
141
|
continue;
|
|
125
142
|
}
|
|
@@ -231,6 +248,11 @@ export class ProxyClient {
|
|
|
231
248
|
}
|
|
232
249
|
}
|
|
233
250
|
catch (streamError) {
|
|
251
|
+
if (abortSignal?.aborted || streamError.name === 'AbortError' || streamError.message?.includes('abort')) {
|
|
252
|
+
const err = new Error('Operation aborted');
|
|
253
|
+
err.name = 'AbortError';
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
234
256
|
if (streamError.message?.includes('429') ||
|
|
235
257
|
streamError.message?.includes('502') ||
|
|
236
258
|
streamError.message?.includes('503') ||
|
|
@@ -245,6 +267,11 @@ export class ProxyClient {
|
|
|
245
267
|
process.stdout.write('\n');
|
|
246
268
|
console.warn(`Server error or rate limit hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
247
269
|
await delay(delayTime, abortSignal);
|
|
270
|
+
if (abortSignal?.aborted) {
|
|
271
|
+
const err = new Error('Operation aborted');
|
|
272
|
+
err.name = 'AbortError';
|
|
273
|
+
throw err;
|
|
274
|
+
}
|
|
248
275
|
attempt++;
|
|
249
276
|
continue retryLoop;
|
|
250
277
|
}
|
|
@@ -287,6 +314,11 @@ export class ProxyClient {
|
|
|
287
314
|
const MAX_DELAY_MS = 30000;
|
|
288
315
|
let attempt = 0;
|
|
289
316
|
while (true) {
|
|
317
|
+
if (abortSignal?.aborted) {
|
|
318
|
+
const err = new Error('Operation aborted');
|
|
319
|
+
err.name = 'AbortError';
|
|
320
|
+
throw err;
|
|
321
|
+
}
|
|
290
322
|
const response = await fetch(url, {
|
|
291
323
|
method: 'POST',
|
|
292
324
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -300,11 +332,21 @@ export class ProxyClient {
|
|
|
300
332
|
response.status === 500 ||
|
|
301
333
|
response.status === 504) &&
|
|
302
334
|
attempt < MAX_RETRIES) {
|
|
335
|
+
if (abortSignal?.aborted) {
|
|
336
|
+
const err = new Error('Operation aborted');
|
|
337
|
+
err.name = 'AbortError';
|
|
338
|
+
throw err;
|
|
339
|
+
}
|
|
303
340
|
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
304
341
|
const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
|
|
305
342
|
process.stdout.write('\n');
|
|
306
343
|
console.warn(`Server error or rate limit hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
307
344
|
await delay(delayTime, abortSignal);
|
|
345
|
+
if (abortSignal?.aborted) {
|
|
346
|
+
const err = new Error('Operation aborted');
|
|
347
|
+
err.name = 'AbortError';
|
|
348
|
+
throw err;
|
|
349
|
+
}
|
|
308
350
|
attempt++;
|
|
309
351
|
continue;
|
|
310
352
|
}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -17,7 +17,9 @@ export declare const GEMINI_MODELS: {
|
|
|
17
17
|
readonly CLAUDE_OPUS: "claude-opus-5";
|
|
18
18
|
readonly PRO: "gemini-3.1-pro-preview";
|
|
19
19
|
readonly CLAUDE_SONNET: "claude-sonnet-5";
|
|
20
|
-
readonly
|
|
20
|
+
readonly FLASH_3_7: "gemini-3.7-flash";
|
|
21
|
+
readonly FLASH: "gemini-3.7-flash";
|
|
22
|
+
readonly FLASH_3_6: "gemini-3.6-flash";
|
|
21
23
|
readonly FLASH_LITE: "gemini-3.5-flash-lite";
|
|
22
24
|
readonly AUTO: "auto";
|
|
23
25
|
};
|
package/dist/utils/config.js
CHANGED
|
@@ -17,7 +17,9 @@ export const GEMINI_MODELS = {
|
|
|
17
17
|
CLAUDE_OPUS: 'claude-opus-5',
|
|
18
18
|
PRO: 'gemini-3.1-pro-preview',
|
|
19
19
|
CLAUDE_SONNET: 'claude-sonnet-5',
|
|
20
|
-
|
|
20
|
+
FLASH_3_7: 'gemini-3.7-flash',
|
|
21
|
+
FLASH: 'gemini-3.7-flash',
|
|
22
|
+
FLASH_3_6: 'gemini-3.6-flash',
|
|
21
23
|
FLASH_LITE: 'gemini-3.5-flash-lite',
|
|
22
24
|
AUTO: 'auto',
|
|
23
25
|
};
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED