@pheem49/mint 1.5.1 → 1.5.3
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/GUIDE_TH.md +7 -7
- package/README.md +140 -66
- package/assets/Agent_Mint.png +0 -0
- package/assets/Settings.png +0 -0
- package/main.js +12 -0
- package/mint-cli.js +148 -921
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +31 -1
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +6 -1
- package/package.json +20 -21
- package/preload.js +2 -0
- package/scripts/install_linux_desktop_entry.js +48 -0
- package/src/AI_Brain/Gemini_API.js +194 -491
- package/src/AI_Brain/autonomous_brain.js +46 -19
- package/src/AI_Brain/headless_agent.js +21 -2
- package/src/AI_Brain/proactive_engine.js +12 -2
- package/src/AI_Brain/provider_adapter.js +358 -0
- package/src/Automation_Layer/browser_automation.js +26 -24
- package/src/CLI/approval_handler.js +47 -0
- package/src/CLI/chat_router.js +7 -0
- package/src/CLI/chat_ui.js +586 -80
- package/src/CLI/cli_colors.js +115 -0
- package/src/CLI/cli_formatters.js +94 -0
- package/src/CLI/code_agent.js +825 -283
- package/src/CLI/intent_detectors.js +181 -0
- package/src/CLI/interactive_chat.js +641 -0
- package/src/CLI/list_features.js +3 -0
- package/src/CLI/repo_summarizer.js +282 -0
- package/src/CLI/semantic_code_search.js +312 -0
- package/src/CLI/skill_manager.js +41 -0
- package/src/CLI/slash_command_handler.js +418 -0
- package/src/CLI/symbol_indexer.js +231 -0
- package/src/CLI/updater.js +21 -1
- package/src/Channels/discord_bridge.js +11 -13
- package/src/Channels/line_bridge.js +10 -10
- package/src/Channels/slack_bridge.js +7 -12
- package/src/Channels/telegram_bridge.js +6 -14
- package/src/Channels/whatsapp_bridge.js +11 -9
- package/src/System/chat_history_manager.js +20 -12
- package/src/System/config_manager.js +4 -1
- package/src/System/ipc_handlers.js +10 -0
- package/src/System/optional_require.js +23 -0
- package/src/System/picture_store.js +109 -0
- package/src/System/task_manager.js +127 -0
- package/src/System/tool_registry.js +13 -0
- package/src/System/window_manager.js +16 -8
- package/src/UI/live2d_manager.js +246 -14
- package/src/UI/renderer.js +620 -45
- package/src/UI/settings.css +738 -439
- package/src/UI/settings.html +487 -432
- package/src/UI/settings.js +44 -10
- package/src/UI/styles.css +1403 -106
- package/privacy.txt +0 -1
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
const { GoogleGenAI } = require('@google/genai');
|
|
2
1
|
const { readConfig } = require('../System/config_manager');
|
|
3
2
|
const { performWebAutomation } = require('../Automation_Layer/browser_automation');
|
|
4
3
|
const { createFolder, deleteFile } = require('../Automation_Layer/file_operations');
|
|
5
4
|
const { searchKnowledge } = require('./knowledge_base');
|
|
6
5
|
const safetyManager = require('../System/safety_manager');
|
|
6
|
+
const providerAdapter = require('./provider_adapter');
|
|
7
|
+
const taskManager = require('../System/task_manager');
|
|
7
8
|
const fs = require('fs');
|
|
8
9
|
const path = require('path');
|
|
9
10
|
|
|
10
11
|
const os = require('os');
|
|
11
12
|
const { sendNotification } = require('../System/notifications');
|
|
12
13
|
|
|
13
|
-
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
14
|
-
|
|
15
14
|
function expandHome(filePath) {
|
|
16
15
|
if (filePath.startsWith('~/')) {
|
|
17
16
|
return path.join(os.homedir(), filePath.slice(2));
|
|
@@ -47,24 +46,23 @@ TOOL DETAILS:
|
|
|
47
46
|
- "done": Target is the final summary of what was accomplished.
|
|
48
47
|
`;
|
|
49
48
|
|
|
50
|
-
async function executeAutonomousTask(taskDescription, notifyCallback) {
|
|
49
|
+
async function executeAutonomousTask(taskDescription, notifyCallback, options = {}) {
|
|
51
50
|
const config = readConfig();
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
config
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
history: []
|
|
51
|
+
const providerOrder = providerAdapter.getProviderAttemptOrder(config, {
|
|
52
|
+
supported: ['gemini', 'anthropic', 'openai', 'local_openai'],
|
|
53
|
+
priority: ['anthropic', 'openai', 'gemini', 'local_openai']
|
|
54
|
+
});
|
|
55
|
+
const client = new providerAdapter.AgentProviderClient({
|
|
56
|
+
provider: providerOrder[0],
|
|
57
|
+
providerOrder,
|
|
58
|
+
config,
|
|
59
|
+
systemInstruction: AUTONOMOUS_SYSTEM_PROMPT,
|
|
60
|
+
responseMimeType: 'application/json',
|
|
61
|
+
maxTokens: 4096
|
|
64
62
|
});
|
|
65
63
|
|
|
66
64
|
let currentObservation = `Task: ${taskDescription}\nWhat is your first step?`;
|
|
67
|
-
let maxSteps = 10;
|
|
65
|
+
let maxSteps = Number.isFinite(options.maxSteps) ? options.maxSteps : 10;
|
|
68
66
|
let step = 0;
|
|
69
67
|
let result = null;
|
|
70
68
|
|
|
@@ -73,12 +71,20 @@ async function executeAutonomousTask(taskDescription, notifyCallback) {
|
|
|
73
71
|
if (notifyCallback) notifyCallback(`Step ${step}: Thinking...`);
|
|
74
72
|
|
|
75
73
|
try {
|
|
76
|
-
const
|
|
77
|
-
const text = response.text;
|
|
74
|
+
const text = await client.sendMessage(currentObservation);
|
|
78
75
|
const actionObj = JSON.parse(text);
|
|
79
76
|
|
|
80
77
|
console.log(`[Brain] Thought: ${actionObj.thought}`);
|
|
81
78
|
console.log(`[Brain] Action: ${actionObj.action} -> ${actionObj.target}`);
|
|
79
|
+
if (options.taskId) {
|
|
80
|
+
taskManager.addCheckpoint(options.taskId, {
|
|
81
|
+
phase: 'autonomous_step',
|
|
82
|
+
step,
|
|
83
|
+
thought: actionObj.thought,
|
|
84
|
+
action: actionObj.action,
|
|
85
|
+
target: actionObj.target
|
|
86
|
+
});
|
|
87
|
+
}
|
|
82
88
|
|
|
83
89
|
if (actionObj.action === 'done') {
|
|
84
90
|
result = actionObj.target;
|
|
@@ -111,6 +117,13 @@ async function executeAutonomousTask(taskDescription, notifyCallback) {
|
|
|
111
117
|
approved: true
|
|
112
118
|
});
|
|
113
119
|
fs.writeFileSync(filePath, actionObj.data || '');
|
|
120
|
+
if (options.taskId) {
|
|
121
|
+
taskManager.addArtifact(options.taskId, {
|
|
122
|
+
type: 'file',
|
|
123
|
+
path: filePath,
|
|
124
|
+
description: `Written by autonomous task step ${step}`
|
|
125
|
+
});
|
|
126
|
+
}
|
|
114
127
|
observation = `File written successfully to ${actionObj.target}`;
|
|
115
128
|
} catch (e) {
|
|
116
129
|
observation = `Failed to write file: ${e.message}`;
|
|
@@ -139,10 +152,24 @@ async function executeAutonomousTask(taskDescription, notifyCallback) {
|
|
|
139
152
|
}
|
|
140
153
|
|
|
141
154
|
currentObservation = `Observation: ${observation}`;
|
|
155
|
+
if (options.taskId) {
|
|
156
|
+
taskManager.addCheckpoint(options.taskId, {
|
|
157
|
+
phase: 'observation',
|
|
158
|
+
step,
|
|
159
|
+
message: observation
|
|
160
|
+
});
|
|
161
|
+
}
|
|
142
162
|
|
|
143
163
|
} catch (err) {
|
|
144
164
|
console.error('[AutonomousBrain] Error during loop:', err);
|
|
145
165
|
currentObservation = `Error occurred: ${err.message}. Please try a different approach or conclude if task is impossible.`;
|
|
166
|
+
if (options.taskId) {
|
|
167
|
+
taskManager.addCheckpoint(options.taskId, {
|
|
168
|
+
phase: 'error',
|
|
169
|
+
step,
|
|
170
|
+
message: err.message
|
|
171
|
+
});
|
|
172
|
+
}
|
|
146
173
|
}
|
|
147
174
|
}
|
|
148
175
|
|
|
@@ -23,6 +23,10 @@ let isProcessingTask = false;
|
|
|
23
23
|
async function startAgent() {
|
|
24
24
|
console.log(`\n${colors.mint}${colors.bright}[Mint-Agent] Background agent started.${colors.reset}`);
|
|
25
25
|
console.log(`${colors.gray}[Mint-Agent] Monitoring system events and task queue...${colors.reset}\n`);
|
|
26
|
+
const resumed = taskManager.resumeRunningTasks();
|
|
27
|
+
if (resumed.length > 0) {
|
|
28
|
+
console.log(`${colors.gray}[Mint-Agent] Re-queued ${resumed.length} interrupted task(s).${colors.reset}`);
|
|
29
|
+
}
|
|
26
30
|
|
|
27
31
|
// Initialize System Monitoring
|
|
28
32
|
systemEvents.startMonitoring();
|
|
@@ -70,25 +74,40 @@ async function checkTaskQueue() {
|
|
|
70
74
|
console.log(`\n${colors.mint}[Agent] Picking up task: ${task.description}${colors.reset}`);
|
|
71
75
|
|
|
72
76
|
taskManager.updateTask(task.id, { status: 'running' });
|
|
77
|
+
taskManager.addCheckpoint(task.id, {
|
|
78
|
+
phase: 'started',
|
|
79
|
+
message: task.description
|
|
80
|
+
});
|
|
73
81
|
sendNotification("🚀 เริ่มทำงานให้แล้วนะคะ", `กำลังดำเนินการ: ${task.description}`);
|
|
74
82
|
|
|
75
83
|
try {
|
|
76
84
|
const result = await executeAutonomousTask(task.description, (progress) => {
|
|
77
85
|
console.log(`${colors.gray}[Progress] ${progress}${colors.reset}`);
|
|
86
|
+
taskManager.addCheckpoint(task.id, {
|
|
87
|
+
phase: 'progress',
|
|
88
|
+
message: progress
|
|
89
|
+
});
|
|
78
90
|
// Send periodic progress notifications if important
|
|
79
91
|
if (progress.includes('เสนอให้รันคำสั่ง')) {
|
|
80
92
|
sendNotification("💡 มิ้นท์มีข้อแนะนำค่ะ", progress);
|
|
81
93
|
}
|
|
82
|
-
});
|
|
94
|
+
}, { taskId: task.id });
|
|
83
95
|
|
|
96
|
+
taskManager.addArtifact(task.id, {
|
|
97
|
+
type: 'final_result',
|
|
98
|
+
content: result
|
|
99
|
+
});
|
|
84
100
|
taskManager.updateTask(task.id, { status: 'completed', result });
|
|
85
101
|
sendNotification("✅ งานเสร็จเรียบร้อยแล้วค่ะ!", result);
|
|
86
102
|
console.log(`\n${colors.mint}[Agent] Task completed successfully.${colors.reset}`);
|
|
87
103
|
|
|
88
104
|
} catch (err) {
|
|
89
105
|
console.error('[Agent] Task execution failed:', err);
|
|
90
|
-
taskManager.
|
|
106
|
+
const next = taskManager.failTaskWithRetry(task.id, err.message);
|
|
91
107
|
sendNotification("❌ เกิดข้อผิดพลาดในการทำงาน", err.message);
|
|
108
|
+
if (next && next.status === 'pending') {
|
|
109
|
+
console.log(`${colors.gray}[Agent] Task scheduled for retry (${next.retryCount}/${next.maxRetries}).${colors.reset}`);
|
|
110
|
+
}
|
|
92
111
|
} finally {
|
|
93
112
|
isProcessingTask = false;
|
|
94
113
|
}
|
|
@@ -5,9 +5,14 @@ const { readConfig } = require('../System/config_manager');
|
|
|
5
5
|
// Proactive Engine — Smart Suggestion Engine (Multi-Choice)
|
|
6
6
|
// ============================================================
|
|
7
7
|
|
|
8
|
-
const ai = new GoogleGenAI({});
|
|
9
8
|
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
10
9
|
let lastLoggedModel = '';
|
|
10
|
+
let _ai = null;
|
|
11
|
+
|
|
12
|
+
function getAi(apiKey) {
|
|
13
|
+
if (!_ai) _ai = new GoogleGenAI({ apiKey });
|
|
14
|
+
return _ai;
|
|
15
|
+
}
|
|
11
16
|
|
|
12
17
|
const PROACTIVE_SYSTEM_PROMPT = `You are a Smart Suggestion Engine built into a Desktop AI Agent called "Mint".
|
|
13
18
|
Your job: observe the user's screen + behavior, then offer MULTIPLE relevant quick-action options — NOT just one question.
|
|
@@ -89,11 +94,15 @@ function getMinSuggestionIntervalMs() {
|
|
|
89
94
|
*/
|
|
90
95
|
async function analyzeAndSuggest(base64Image, behaviorSummary) {
|
|
91
96
|
try {
|
|
92
|
-
const
|
|
97
|
+
const cfg = readConfig();
|
|
98
|
+
const apiKey = cfg.apiKey || process.env.GEMINI_API_KEY;
|
|
99
|
+
if (!apiKey) return null; // silently skip if no API key configured
|
|
100
|
+
const model = (cfg.geminiModel || '').trim() || DEFAULT_GEMINI_MODEL;
|
|
93
101
|
if (model && model !== lastLoggedModel) {
|
|
94
102
|
console.log(`[Gemini] Proactive Engine model: ${model}`);
|
|
95
103
|
lastLoggedModel = model;
|
|
96
104
|
}
|
|
105
|
+
const ai = getAi(apiKey);
|
|
97
106
|
|
|
98
107
|
const now = Date.now();
|
|
99
108
|
const minInterval = getMinSuggestionIntervalMs();
|
|
@@ -127,6 +136,7 @@ Rules: Only suggest if you see a clear opportunity. Return 2–4 relevant chips.
|
|
|
127
136
|
contents: [{ role: 'user', parts: userMessage }]
|
|
128
137
|
});
|
|
129
138
|
|
|
139
|
+
|
|
130
140
|
let parsed;
|
|
131
141
|
try {
|
|
132
142
|
parsed = JSON.parse(response.text);
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { GoogleGenAI } = require('@google/genai');
|
|
3
|
+
const { getAvailableProviders } = require('../System/config_manager');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
6
|
+
const ALL_PROVIDERS = ['anthropic', 'openai', 'gemini', 'local_openai', 'ollama', 'huggingface'];
|
|
7
|
+
|
|
8
|
+
function splitDataUri(dataUri = '') {
|
|
9
|
+
const match = String(dataUri).match(/^data:([^;]+);base64,([\s\S]+)$/);
|
|
10
|
+
if (!match) return null;
|
|
11
|
+
return {
|
|
12
|
+
mimeType: match[1],
|
|
13
|
+
data: match[2]
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function contentToText(content) {
|
|
18
|
+
if (content && typeof content === 'object' && !Array.isArray(content)) {
|
|
19
|
+
return String(content.text || '');
|
|
20
|
+
}
|
|
21
|
+
return String(content || '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function contentToGeminiParts(content) {
|
|
25
|
+
const text = contentToText(content);
|
|
26
|
+
const parts = text ? [{ text }] : [];
|
|
27
|
+
if (content && typeof content === 'object') {
|
|
28
|
+
const images = Array.isArray(content.imageDataUris)
|
|
29
|
+
? content.imageDataUris
|
|
30
|
+
: (content.imageDataUri ? [content.imageDataUri] : []);
|
|
31
|
+
for (const item of images) {
|
|
32
|
+
const image = splitDataUri(item);
|
|
33
|
+
if (image) parts.push({ inlineData: { mimeType: image.mimeType, data: image.data } });
|
|
34
|
+
}
|
|
35
|
+
if (content.audioDataUri) {
|
|
36
|
+
const audio = splitDataUri(content.audioDataUri);
|
|
37
|
+
if (audio) parts.push({ inlineData: { mimeType: audio.mimeType, data: audio.data } });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return parts.length > 0 ? parts : [{ text: '' }];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function contentToOpenAIContent(content) {
|
|
44
|
+
const text = contentToText(content) || 'Analyze this input.';
|
|
45
|
+
if (content && typeof content === 'object') {
|
|
46
|
+
const images = Array.isArray(content.imageDataUris)
|
|
47
|
+
? content.imageDataUris
|
|
48
|
+
: (content.imageDataUri ? [content.imageDataUri] : []);
|
|
49
|
+
if (images.length > 0) {
|
|
50
|
+
return [
|
|
51
|
+
{ type: 'text', text },
|
|
52
|
+
...images.map(item => ({ type: 'image_url', image_url: { url: item } }))
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return text;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function contentToAnthropicContent(content) {
|
|
60
|
+
const text = contentToText(content) || 'Analyze this input.';
|
|
61
|
+
if (content && typeof content === 'object') {
|
|
62
|
+
const images = Array.isArray(content.imageDataUris)
|
|
63
|
+
? content.imageDataUris
|
|
64
|
+
: (content.imageDataUri ? [content.imageDataUri] : []);
|
|
65
|
+
if (images.length > 0) {
|
|
66
|
+
const blocks = [];
|
|
67
|
+
for (const item of images) {
|
|
68
|
+
const image = splitDataUri(item);
|
|
69
|
+
if (image) {
|
|
70
|
+
blocks.push({ type: 'image', source: { type: 'base64', media_type: image.mimeType, data: image.data } });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
blocks.push({ type: 'text', text });
|
|
74
|
+
return blocks;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return text;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function contentToOllamaMessage(content) {
|
|
81
|
+
const text = contentToText(content) || 'Analyze this input.';
|
|
82
|
+
const message = { role: 'user', content: text };
|
|
83
|
+
if (content && typeof content === 'object') {
|
|
84
|
+
const images = Array.isArray(content.imageDataUris)
|
|
85
|
+
? content.imageDataUris
|
|
86
|
+
: (content.imageDataUri ? [content.imageDataUri] : []);
|
|
87
|
+
const imagePayloads = images
|
|
88
|
+
.map(item => splitDataUri(item))
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.map(image => image.data);
|
|
91
|
+
if (imagePayloads.length > 0) message.images = imagePayloads;
|
|
92
|
+
}
|
|
93
|
+
return message;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getProviderAttemptOrder(config = {}, options = {}) {
|
|
97
|
+
const supported = options.supported || ALL_PROVIDERS;
|
|
98
|
+
const available = (options.availableProviders || getAvailableProviders(config))
|
|
99
|
+
.filter(provider => supported.includes(provider));
|
|
100
|
+
const requested = options.requested || config.aiProvider || 'gemini';
|
|
101
|
+
const priority = (options.priority || ALL_PROVIDERS).filter(provider => supported.includes(provider));
|
|
102
|
+
const ordered = [];
|
|
103
|
+
|
|
104
|
+
if (supported.includes(requested) && available.includes(requested)) {
|
|
105
|
+
ordered.push(requested);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const provider of priority) {
|
|
109
|
+
if (available.includes(provider) && !ordered.includes(provider)) {
|
|
110
|
+
ordered.push(provider);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return ordered.length > 0 ? ordered : ['gemini'];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getProviderModel(provider, config = {}) {
|
|
118
|
+
switch (provider) {
|
|
119
|
+
case 'anthropic':
|
|
120
|
+
return config.anthropicModel || 'claude-3-5-sonnet-latest';
|
|
121
|
+
case 'openai':
|
|
122
|
+
return config.openaiModel || 'gpt-4o';
|
|
123
|
+
case 'local_openai':
|
|
124
|
+
return config.localModelName || 'local-model';
|
|
125
|
+
case 'ollama':
|
|
126
|
+
return config.ollamaModel || 'llama3:latest';
|
|
127
|
+
case 'huggingface':
|
|
128
|
+
return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct';
|
|
129
|
+
case 'gemini':
|
|
130
|
+
default:
|
|
131
|
+
return config.geminiModel || DEFAULT_GEMINI_MODEL;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class AgentProviderClient {
|
|
136
|
+
constructor(options = {}) {
|
|
137
|
+
this.provider = options.provider || 'gemini';
|
|
138
|
+
this.providerOrder = options.providerOrder && options.providerOrder.length
|
|
139
|
+
? options.providerOrder
|
|
140
|
+
: [this.provider];
|
|
141
|
+
this.config = options.config || {};
|
|
142
|
+
this.history = options.history || [];
|
|
143
|
+
this.systemInstruction = options.systemInstruction || '';
|
|
144
|
+
this.responseMimeType = options.responseMimeType || 'application/json';
|
|
145
|
+
this.maxTokens = options.maxTokens || 8192;
|
|
146
|
+
this.lastSuccessfulProvider = null;
|
|
147
|
+
this.usageTotals = {};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
recordUsage(provider, model, usage = {}) {
|
|
151
|
+
const key = `${provider}:${model || ''}`;
|
|
152
|
+
if (!this.usageTotals[key]) {
|
|
153
|
+
this.usageTotals[key] = {
|
|
154
|
+
provider,
|
|
155
|
+
model,
|
|
156
|
+
requests: 0,
|
|
157
|
+
inputTokens: 0,
|
|
158
|
+
cacheReads: 0,
|
|
159
|
+
outputTokens: 0,
|
|
160
|
+
reasoningTokens: 0,
|
|
161
|
+
totalTokens: 0
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const row = this.usageTotals[key];
|
|
166
|
+
row.requests += 1;
|
|
167
|
+
row.inputTokens += Number(usage.inputTokens) || 0;
|
|
168
|
+
row.cacheReads += Number(usage.cacheReads) || 0;
|
|
169
|
+
row.outputTokens += Number(usage.outputTokens) || 0;
|
|
170
|
+
row.reasoningTokens += Number(usage.reasoningTokens) || 0;
|
|
171
|
+
row.totalTokens += Number(usage.totalTokens) || 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getUsageSummary() {
|
|
175
|
+
return Object.values(this.usageTotals);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async sendMessage(observation) {
|
|
179
|
+
this.history.push({ role: 'user', content: observation });
|
|
180
|
+
|
|
181
|
+
const failures = [];
|
|
182
|
+
for (const provider of this.providerOrder) {
|
|
183
|
+
this.provider = provider;
|
|
184
|
+
try {
|
|
185
|
+
let responseText = '';
|
|
186
|
+
if (provider === 'anthropic') responseText = await this.callAnthropic();
|
|
187
|
+
else if (provider === 'openai' || provider === 'local_openai') responseText = await this.callOpenAI();
|
|
188
|
+
else if (provider === 'ollama') responseText = await this.callOllama();
|
|
189
|
+
else if (provider === 'huggingface') responseText = await this.callHuggingFace();
|
|
190
|
+
else responseText = await this.callGemini();
|
|
191
|
+
|
|
192
|
+
this.history.push({ role: 'assistant', content: responseText });
|
|
193
|
+
this.lastSuccessfulProvider = provider;
|
|
194
|
+
return responseText;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const message = error.message || error.code || 'unknown error';
|
|
197
|
+
failures.push(`${provider}: ${message}`);
|
|
198
|
+
if (process.env.MINT_DEBUG === '1') {
|
|
199
|
+
console.error(`[ProviderAdapter] Provider '${provider}' failed: ${message}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw new Error(`All providers failed. ${failures.join(' | ')}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async callAnthropic() {
|
|
208
|
+
const apiKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
209
|
+
const model = getProviderModel('anthropic', this.config);
|
|
210
|
+
const messages = this.history.map(m => ({
|
|
211
|
+
role: m.role,
|
|
212
|
+
content: contentToAnthropicContent(m.content)
|
|
213
|
+
}));
|
|
214
|
+
|
|
215
|
+
const response = await axios.post('https://api.anthropic.com/v1/messages', {
|
|
216
|
+
model,
|
|
217
|
+
max_tokens: this.maxTokens,
|
|
218
|
+
system: this.systemInstruction,
|
|
219
|
+
messages
|
|
220
|
+
}, {
|
|
221
|
+
headers: {
|
|
222
|
+
'x-api-key': apiKey,
|
|
223
|
+
'anthropic-version': '2023-06-01',
|
|
224
|
+
'content-type': 'application/json'
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
const usage = response.data.usage || {};
|
|
228
|
+
this.recordUsage('anthropic', model, {
|
|
229
|
+
inputTokens: usage.input_tokens,
|
|
230
|
+
cacheReads: usage.cache_read_input_tokens,
|
|
231
|
+
outputTokens: usage.output_tokens,
|
|
232
|
+
totalTokens: (Number(usage.input_tokens) || 0) + (Number(usage.output_tokens) || 0)
|
|
233
|
+
});
|
|
234
|
+
return response.data.content[0].text;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async callOpenAI() {
|
|
238
|
+
const isLocal = this.provider === 'local_openai';
|
|
239
|
+
const apiKey = isLocal ? 'not-needed' : (this.config.openaiApiKey || process.env.OPENAI_API_KEY);
|
|
240
|
+
const baseUrl = isLocal ? (this.config.localApiBaseUrl || 'http://localhost:1234/v1') : 'https://api.openai.com/v1';
|
|
241
|
+
const model = getProviderModel(this.provider, this.config);
|
|
242
|
+
const messages = [
|
|
243
|
+
{ role: 'system', content: this.systemInstruction },
|
|
244
|
+
...this.history.map(m => ({
|
|
245
|
+
role: m.role,
|
|
246
|
+
content: contentToOpenAIContent(m.content)
|
|
247
|
+
}))
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
const response = await axios.post(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
|
|
251
|
+
model,
|
|
252
|
+
messages,
|
|
253
|
+
response_format: isLocal ? undefined : { type: 'json_object' }
|
|
254
|
+
}, {
|
|
255
|
+
headers: {
|
|
256
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
257
|
+
'Content-Type': 'application/json'
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const usage = response.data.usage || {};
|
|
261
|
+
this.recordUsage(this.provider, model, {
|
|
262
|
+
inputTokens: usage.prompt_tokens,
|
|
263
|
+
cacheReads: usage.prompt_tokens_details && usage.prompt_tokens_details.cached_tokens,
|
|
264
|
+
outputTokens: usage.completion_tokens,
|
|
265
|
+
reasoningTokens: usage.completion_tokens_details && usage.completion_tokens_details.reasoning_tokens,
|
|
266
|
+
totalTokens: usage.total_tokens
|
|
267
|
+
});
|
|
268
|
+
return response.data.choices[0].message.content;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async callGemini() {
|
|
272
|
+
const apiKey = this.config.apiKey || process.env.GEMINI_API_KEY;
|
|
273
|
+
const model = getProviderModel('gemini', this.config);
|
|
274
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
275
|
+
const recentHistory = this.history.slice(-16);
|
|
276
|
+
const priorHistory = recentHistory.slice(0, -1);
|
|
277
|
+
const lastEntry = recentHistory[recentHistory.length - 1] || { content: '' };
|
|
278
|
+
const history = priorHistory.map(m => ({
|
|
279
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
280
|
+
parts: contentToGeminiParts(m.content)
|
|
281
|
+
}));
|
|
282
|
+
const chat = ai.chats.create({
|
|
283
|
+
model,
|
|
284
|
+
config: {
|
|
285
|
+
systemInstruction: this.systemInstruction,
|
|
286
|
+
responseMimeType: this.responseMimeType
|
|
287
|
+
},
|
|
288
|
+
history
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const response = await chat.sendMessage({ message: contentToGeminiParts(lastEntry.content) });
|
|
292
|
+
const usage = response.usageMetadata || {};
|
|
293
|
+
this.recordUsage('gemini', model, {
|
|
294
|
+
inputTokens: usage.promptTokenCount,
|
|
295
|
+
cacheReads: usage.cachedContentTokenCount,
|
|
296
|
+
outputTokens: usage.candidatesTokenCount,
|
|
297
|
+
reasoningTokens: usage.thoughtsTokenCount,
|
|
298
|
+
totalTokens: usage.totalTokenCount
|
|
299
|
+
});
|
|
300
|
+
return typeof response.text === 'function' ? response.text() : response.text;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async callOllama() {
|
|
304
|
+
const model = getProviderModel('ollama', this.config);
|
|
305
|
+
const baseUrl = (this.config.ollamaHost || 'http://localhost:11434').replace(/\/$/, '');
|
|
306
|
+
const messages = [
|
|
307
|
+
{ role: 'system', content: this.systemInstruction },
|
|
308
|
+
...this.history.map(m => m.role === 'assistant'
|
|
309
|
+
? { role: 'assistant', content: contentToText(m.content) }
|
|
310
|
+
: contentToOllamaMessage(m.content))
|
|
311
|
+
];
|
|
312
|
+
const response = await axios.post(`${baseUrl}/api/chat`, {
|
|
313
|
+
model,
|
|
314
|
+
messages,
|
|
315
|
+
format: this.responseMimeType === 'application/json' ? 'json' : undefined,
|
|
316
|
+
stream: false
|
|
317
|
+
});
|
|
318
|
+
return response.data.message.content;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async callHuggingFace() {
|
|
322
|
+
const apiKey = this.config.hfApiKey || process.env.HF_API_KEY;
|
|
323
|
+
const model = getProviderModel('huggingface', this.config);
|
|
324
|
+
const messages = [
|
|
325
|
+
{ role: 'system', content: this.systemInstruction },
|
|
326
|
+
...this.history.map(m => ({
|
|
327
|
+
role: m.role,
|
|
328
|
+
content: contentToOpenAIContent(m.content)
|
|
329
|
+
}))
|
|
330
|
+
];
|
|
331
|
+
const response = await axios.post(`https://api-inference.huggingface.co/models/${model}/v1/chat/completions`, {
|
|
332
|
+
model,
|
|
333
|
+
messages,
|
|
334
|
+
max_tokens: this.maxTokens
|
|
335
|
+
}, {
|
|
336
|
+
headers: {
|
|
337
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
338
|
+
'Content-Type': 'application/json'
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
return response.data.choices[0].message.content;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = {
|
|
346
|
+
DEFAULT_GEMINI_MODEL,
|
|
347
|
+
ALL_PROVIDERS,
|
|
348
|
+
AgentProviderClient,
|
|
349
|
+
getProviderAttemptOrder,
|
|
350
|
+
getProviderModel,
|
|
351
|
+
_helpers: {
|
|
352
|
+
splitDataUri,
|
|
353
|
+
contentToGeminiParts,
|
|
354
|
+
contentToOpenAIContent,
|
|
355
|
+
contentToAnthropicContent,
|
|
356
|
+
contentToOllamaMessage
|
|
357
|
+
}
|
|
358
|
+
};
|