@pheem49/mint 1.8.2 → 1.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/Cargo.toml +1 -1
- package/Release_Note.md +48 -3
- package/crates/mint-cli/src/agent.rs +95 -84
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/main.rs +806 -403
- package/crates/mint-cli/src/onboard.rs +108 -108
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-core/src/api_server.rs +57 -54
- package/crates/mint-core/src/browser.rs +1 -1
- package/crates/mint-core/src/chat.rs +123 -8
- package/crates/mint-core/src/code_tools.rs +18 -20
- package/crates/mint-core/src/config.rs +184 -17
- package/crates/mint-core/src/image_gen.rs +5 -5
- package/crates/mint-core/src/lib.rs +5 -5
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +104 -85
- package/crates/mint-core/src/pictures.rs +3 -4
- package/crates/mint-core/src/semantic.rs +1 -1
- package/crates/mint-core/src/shell.rs +1 -1
- package/crates/mint-core/src/skills.rs +46 -19
- package/package.json +1 -1
- package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
- package/shared-modules/linux-audio/fluidsynth2.json +8 -2
- package/shared-modules/linux-audio/lilv.json +26 -19
- package/shared-modules/linux-audio/lv2.json +0 -1
- package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/src/components/ChatPanel.tsx +25 -5
- package/src/renderer/src/components/MintDashboard.tsx +45 -2
- package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
- package/src/renderer/src/components/SettingsWindow.tsx +68 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/styles.css +16 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +25 -5
- package/src/renderer/src-web/components/MintDashboard.tsx +19 -1
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
- package/src/renderer/src-web/components/SettingsWindow.tsx +75 -1
- package/src/renderer/src-web/css/styles.css +16 -0
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +41 -17
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -81,6 +81,8 @@ pub struct MintConfig {
|
|
|
81
81
|
pub disabled_tools: Vec<String>,
|
|
82
82
|
pub agents: Vec<AgentConfig>,
|
|
83
83
|
pub enable_agent_collaboration: bool,
|
|
84
|
+
/// User-defined OpenAI-compatible providers.
|
|
85
|
+
pub custom_providers: Vec<CustomProvider>,
|
|
84
86
|
#[serde(flatten)]
|
|
85
87
|
pub extra: BTreeMap<String, Value>,
|
|
86
88
|
}
|
|
@@ -97,6 +99,45 @@ pub struct AgentConfig {
|
|
|
97
99
|
pub enabled: bool,
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
/// A single model entry inside a [`CustomProvider`].
|
|
103
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
|
104
|
+
#[serde(rename_all = "camelCase")]
|
|
105
|
+
pub struct CustomProviderModel {
|
|
106
|
+
/// The model identifier sent to the API, e.g. `"llama-3.1-8b"`.
|
|
107
|
+
pub model_id: String,
|
|
108
|
+
/// Human-readable label shown in the UI, e.g. `"Llama 3.1 8B"`.
|
|
109
|
+
pub display_name: String,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// An extra HTTP header to include with every request to a [`CustomProvider`].
|
|
113
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
|
114
|
+
#[serde(rename_all = "camelCase")]
|
|
115
|
+
pub struct CustomProviderHeader {
|
|
116
|
+
pub name: String,
|
|
117
|
+
pub value: String,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// A fully user-defined, OpenAI-compatible AI provider.
|
|
121
|
+
///
|
|
122
|
+
/// When this provider is active the `ai_provider` config field is set to
|
|
123
|
+
/// `"custom:<id>"` where `<id>` is [`CustomProvider::id`].
|
|
124
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
|
125
|
+
#[serde(rename_all = "camelCase")]
|
|
126
|
+
pub struct CustomProvider {
|
|
127
|
+
/// Slug identifier (lowercase letters, numbers, hyphens, underscores).
|
|
128
|
+
pub id: String,
|
|
129
|
+
/// Human-readable name shown in the UI.
|
|
130
|
+
pub display_name: String,
|
|
131
|
+
/// Base URL of the OpenAI-compatible endpoint, e.g. `"http://localhost:20128/v1"`.
|
|
132
|
+
pub base_url: String,
|
|
133
|
+
/// API key sent as `Authorization: Bearer <key>`. Empty = no auth header.
|
|
134
|
+
pub api_key: String,
|
|
135
|
+
/// Models offered by this provider.
|
|
136
|
+
pub models: Vec<CustomProviderModel>,
|
|
137
|
+
/// Optional extra HTTP headers sent with every request.
|
|
138
|
+
pub headers: Vec<CustomProviderHeader>,
|
|
139
|
+
}
|
|
140
|
+
|
|
100
141
|
impl Default for MintConfig {
|
|
101
142
|
fn default() -> Self {
|
|
102
143
|
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
|
|
@@ -158,6 +199,7 @@ impl Default for MintConfig {
|
|
|
158
199
|
disabled_tools: Vec::new(),
|
|
159
200
|
agents: default_agents(),
|
|
160
201
|
enable_agent_collaboration: false,
|
|
202
|
+
custom_providers: Vec::new(),
|
|
161
203
|
extra: runtime_extra_defaults(),
|
|
162
204
|
}
|
|
163
205
|
}
|
|
@@ -205,34 +247,56 @@ impl MintConfig {
|
|
|
205
247
|
"huggingface" => &self.hf_model,
|
|
206
248
|
"local_openai" => &self.local_model_name,
|
|
207
249
|
"ollama" => &self.ollama_model,
|
|
250
|
+
p if p.starts_with("custom:") => {
|
|
251
|
+
// Return the first model id of the matching custom provider, if any.
|
|
252
|
+
self.resolve_custom_provider(p)
|
|
253
|
+
.and_then(|cp| cp.models.first())
|
|
254
|
+
.map(|m| m.model_id.as_str())
|
|
255
|
+
.unwrap_or("")
|
|
256
|
+
}
|
|
208
257
|
_ => &self.gemini_model,
|
|
209
258
|
}
|
|
210
259
|
}
|
|
211
260
|
|
|
212
|
-
|
|
261
|
+
/// Returns the `CustomProvider` whose id matches `provider_key`.
|
|
262
|
+
///
|
|
263
|
+
/// `provider_key` must be in `"custom:<id>"` format (the leading `"custom:"`
|
|
264
|
+
/// prefix is stripped before the lookup).
|
|
265
|
+
pub fn resolve_custom_provider(&self, provider_key: &str) -> Option<&CustomProvider> {
|
|
266
|
+
let id = provider_key.strip_prefix("custom:").unwrap_or(provider_key);
|
|
267
|
+
self.custom_providers.iter().find(|cp| cp.id == id)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
pub fn available_providers(&self) -> Vec<String> {
|
|
213
271
|
let mut providers = Vec::new();
|
|
214
272
|
if has_value(&self.anthropic_api_key) {
|
|
215
|
-
providers.push("anthropic");
|
|
273
|
+
providers.push("anthropic".to_owned());
|
|
216
274
|
}
|
|
217
275
|
if has_value(&self.openai_api_key) {
|
|
218
|
-
providers.push("openai");
|
|
276
|
+
providers.push("openai".to_owned());
|
|
219
277
|
}
|
|
220
278
|
if has_value(&self.openrouter_api_key) {
|
|
221
|
-
providers.push("openrouter");
|
|
279
|
+
providers.push("openrouter".to_owned());
|
|
222
280
|
}
|
|
223
281
|
if has_value(&self.deepseek_api_key) {
|
|
224
|
-
providers.push("deepseek");
|
|
282
|
+
providers.push("deepseek".to_owned());
|
|
225
283
|
}
|
|
226
284
|
if has_value(&self.api_key) {
|
|
227
|
-
providers.push("gemini");
|
|
285
|
+
providers.push("gemini".to_owned());
|
|
228
286
|
}
|
|
229
287
|
if has_value(&self.hf_api_key) {
|
|
230
|
-
providers.push("huggingface");
|
|
288
|
+
providers.push("huggingface".to_owned());
|
|
231
289
|
}
|
|
232
290
|
if has_value(&self.local_api_base_url) {
|
|
233
|
-
providers.push("local_openai");
|
|
291
|
+
providers.push("local_openai".to_owned());
|
|
292
|
+
}
|
|
293
|
+
providers.push("ollama".to_owned());
|
|
294
|
+
// Include all user-defined custom providers that have a base URL configured.
|
|
295
|
+
for cp in &self.custom_providers {
|
|
296
|
+
if !cp.base_url.trim().is_empty() {
|
|
297
|
+
providers.push(format!("custom:{}", cp.id));
|
|
298
|
+
}
|
|
234
299
|
}
|
|
235
|
-
providers.push("ollama");
|
|
236
300
|
providers
|
|
237
301
|
}
|
|
238
302
|
}
|
|
@@ -438,7 +502,10 @@ mod tests {
|
|
|
438
502
|
|
|
439
503
|
#[test]
|
|
440
504
|
fn default_config_always_exposes_ollama() {
|
|
441
|
-
assert_eq!(
|
|
505
|
+
assert_eq!(
|
|
506
|
+
MintConfig::default().available_providers(),
|
|
507
|
+
vec!["ollama".to_owned()]
|
|
508
|
+
);
|
|
442
509
|
}
|
|
443
510
|
|
|
444
511
|
#[test]
|
|
@@ -454,12 +521,12 @@ mod tests {
|
|
|
454
521
|
assert_eq!(
|
|
455
522
|
config.available_providers(),
|
|
456
523
|
vec![
|
|
457
|
-
"openai",
|
|
458
|
-
"openrouter",
|
|
459
|
-
"deepseek",
|
|
460
|
-
"gemini",
|
|
461
|
-
"local_openai",
|
|
462
|
-
"ollama"
|
|
524
|
+
"openai".to_owned(),
|
|
525
|
+
"openrouter".to_owned(),
|
|
526
|
+
"deepseek".to_owned(),
|
|
527
|
+
"gemini".to_owned(),
|
|
528
|
+
"local_openai".to_owned(),
|
|
529
|
+
"ollama".to_owned(),
|
|
463
530
|
]
|
|
464
531
|
);
|
|
465
532
|
}
|
|
@@ -509,8 +576,108 @@ mod tests {
|
|
|
509
576
|
save_config_to(&path, &initial_config).unwrap();
|
|
510
577
|
|
|
511
578
|
let loaded = load_config_from(&path).unwrap();
|
|
512
|
-
assert!(loaded.theme.
|
|
579
|
+
assert!(!loaded.theme.is_empty());
|
|
513
580
|
|
|
514
581
|
let _ = std::fs::remove_file(&path);
|
|
515
582
|
}
|
|
583
|
+
|
|
584
|
+
#[test]
|
|
585
|
+
fn custom_provider_round_trips() {
|
|
586
|
+
let config = MintConfig {
|
|
587
|
+
custom_providers: vec![CustomProvider {
|
|
588
|
+
id: "myprovider".into(),
|
|
589
|
+
display_name: "My Provider".into(),
|
|
590
|
+
base_url: "http://localhost:20128/v1".into(),
|
|
591
|
+
api_key: "sk-abc".into(),
|
|
592
|
+
models: vec![CustomProviderModel {
|
|
593
|
+
model_id: "llama-3.1-8b".into(),
|
|
594
|
+
display_name: "Llama 3.1 8B".into(),
|
|
595
|
+
}],
|
|
596
|
+
headers: vec![CustomProviderHeader {
|
|
597
|
+
name: "X-Custom".into(),
|
|
598
|
+
value: "test".into(),
|
|
599
|
+
}],
|
|
600
|
+
}],
|
|
601
|
+
..MintConfig::default()
|
|
602
|
+
};
|
|
603
|
+
let json = serde_json::to_string(&config).unwrap();
|
|
604
|
+
let restored: MintConfig = serde_json::from_str(&json).unwrap();
|
|
605
|
+
assert_eq!(restored, config);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
#[test]
|
|
609
|
+
fn custom_provider_appears_in_available_providers() {
|
|
610
|
+
let config = MintConfig {
|
|
611
|
+
custom_providers: vec![CustomProvider {
|
|
612
|
+
id: "myprovider".into(),
|
|
613
|
+
base_url: "http://localhost:20128/v1".into(),
|
|
614
|
+
..CustomProvider::default()
|
|
615
|
+
}],
|
|
616
|
+
..MintConfig::default()
|
|
617
|
+
};
|
|
618
|
+
assert!(
|
|
619
|
+
config
|
|
620
|
+
.available_providers()
|
|
621
|
+
.contains(&"custom:myprovider".to_owned())
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#[test]
|
|
626
|
+
fn custom_provider_without_base_url_excluded() {
|
|
627
|
+
let config = MintConfig {
|
|
628
|
+
custom_providers: vec![CustomProvider {
|
|
629
|
+
id: "empty".into(),
|
|
630
|
+
base_url: "".into(),
|
|
631
|
+
..CustomProvider::default()
|
|
632
|
+
}],
|
|
633
|
+
..MintConfig::default()
|
|
634
|
+
};
|
|
635
|
+
assert!(
|
|
636
|
+
!config
|
|
637
|
+
.available_providers()
|
|
638
|
+
.contains(&"custom:empty".to_owned())
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
#[test]
|
|
643
|
+
fn active_model_resolves_first_custom_model() {
|
|
644
|
+
let config = MintConfig {
|
|
645
|
+
ai_provider: "custom:myprovider".into(),
|
|
646
|
+
custom_providers: vec![CustomProvider {
|
|
647
|
+
id: "myprovider".into(),
|
|
648
|
+
base_url: "http://localhost:20128/v1".into(),
|
|
649
|
+
models: vec![CustomProviderModel {
|
|
650
|
+
model_id: "llama-3.1-8b".into(),
|
|
651
|
+
display_name: String::new(),
|
|
652
|
+
}],
|
|
653
|
+
..CustomProvider::default()
|
|
654
|
+
}],
|
|
655
|
+
..MintConfig::default()
|
|
656
|
+
};
|
|
657
|
+
assert_eq!(config.active_model(), "llama-3.1-8b");
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
#[test]
|
|
661
|
+
fn resolve_custom_provider_returns_none_for_unknown() {
|
|
662
|
+
let config = MintConfig::default();
|
|
663
|
+
assert!(
|
|
664
|
+
config
|
|
665
|
+
.resolve_custom_provider("custom:nonexistent")
|
|
666
|
+
.is_none()
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
#[test]
|
|
671
|
+
fn resolve_custom_provider_strips_prefix() {
|
|
672
|
+
let config = MintConfig {
|
|
673
|
+
custom_providers: vec![CustomProvider {
|
|
674
|
+
id: "myp".into(),
|
|
675
|
+
..CustomProvider::default()
|
|
676
|
+
}],
|
|
677
|
+
..MintConfig::default()
|
|
678
|
+
};
|
|
679
|
+
assert!(config.resolve_custom_provider("custom:myp").is_some());
|
|
680
|
+
// Also works without prefix
|
|
681
|
+
assert!(config.resolve_custom_provider("myp").is_some());
|
|
682
|
+
}
|
|
516
683
|
}
|
|
@@ -659,11 +659,11 @@ async fn call_replicate(
|
|
|
659
659
|
poll_replicate(client, &api_key, &prediction_url).await?
|
|
660
660
|
};
|
|
661
661
|
|
|
662
|
-
if let Some(err) = prediction.get("error")
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
662
|
+
if let Some(err) = prediction.get("error")
|
|
663
|
+
&& !err.is_null()
|
|
664
|
+
{
|
|
665
|
+
let msg = err.as_str().unwrap_or("unknown Replicate error");
|
|
666
|
+
return Err(ImageGenError::ModelError(msg.to_owned()));
|
|
667
667
|
}
|
|
668
668
|
|
|
669
669
|
// Output is an array of image URLs
|
|
@@ -41,8 +41,8 @@ pub use code_tools::{
|
|
|
41
41
|
propose_code_edits, read_code_file, repository_summary, search_code,
|
|
42
42
|
};
|
|
43
43
|
pub use config::{
|
|
44
|
-
|
|
45
|
-
set_config_value,
|
|
44
|
+
AgentConfig, ConfigError, CustomProvider, CustomProviderHeader, CustomProviderModel,
|
|
45
|
+
MintConfig, config_path, initialize_config, load_config, save_config, set_config_value,
|
|
46
46
|
};
|
|
47
47
|
pub use files::{FileOperationError, PathKind, PathMatch, create_folder, find_paths};
|
|
48
48
|
pub use image_gen::{
|
|
@@ -92,8 +92,9 @@ pub use channels::start_channels;
|
|
|
92
92
|
pub static HTTP_CLIENT: std::sync::LazyLock<reqwest::Client> =
|
|
93
93
|
std::sync::LazyLock::new(reqwest::Client::new);
|
|
94
94
|
|
|
95
|
-
pub static ACTIVE_AGENTS: std::sync::LazyLock<
|
|
96
|
-
std::sync::
|
|
95
|
+
pub static ACTIVE_AGENTS: std::sync::LazyLock<
|
|
96
|
+
std::sync::Mutex<std::collections::HashMap<String, tokio::task::AbortHandle>>,
|
|
97
|
+
> = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
|
|
97
98
|
|
|
98
99
|
pub fn cancel_agent(chat_id: &str) -> bool {
|
|
99
100
|
let mut agents = ACTIVE_AGENTS.lock().unwrap();
|
|
@@ -104,4 +105,3 @@ pub fn cancel_agent(chat_id: &str) -> bool {
|
|
|
104
105
|
false
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
|
-
|
|
@@ -58,6 +58,8 @@ pub struct LearnedSkill {
|
|
|
58
58
|
pub source_path: String,
|
|
59
59
|
pub content: String,
|
|
60
60
|
pub created_at: String,
|
|
61
|
+
#[serde(default)]
|
|
62
|
+
pub description: Option<String>,
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
@@ -624,12 +626,15 @@ fn ensure_column(
|
|
|
624
626
|
}
|
|
625
627
|
|
|
626
628
|
fn learned_skill_row(row: &rusqlite::Row<'_>) -> Result<LearnedSkill, rusqlite::Error> {
|
|
629
|
+
let content: String = row.get(3)?;
|
|
630
|
+
let description = crate::skills::parse_skill_description(&content);
|
|
627
631
|
Ok(LearnedSkill {
|
|
628
632
|
id: row.get(0)?,
|
|
629
633
|
name: row.get(1)?,
|
|
630
634
|
source_path: row.get(2)?,
|
|
631
|
-
content
|
|
635
|
+
content,
|
|
632
636
|
created_at: row.get(4)?,
|
|
637
|
+
description,
|
|
633
638
|
})
|
|
634
639
|
}
|
|
635
640
|
|