@pheem49/mint 1.8.2 → 1.10.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.lock +55 -3
- package/Cargo.toml +2 -1
- package/Release_Note.md +297 -107
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/actions.rs +233 -0
- package/crates/mint-cli/src/agent.rs +382 -127
- package/crates/mint-cli/src/gmail.rs +22 -2
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/interactive.rs +2888 -0
- package/crates/mint-cli/src/main.rs +391 -2506
- package/crates/mint-cli/src/markdown.rs +294 -0
- package/crates/mint-cli/src/onboard.rs +152 -109
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-cli/src/updater.rs +48 -6
- package/crates/mint-core/Cargo.toml +1 -0
- package/crates/mint-core/src/agent_loop.rs +1 -0
- package/crates/mint-core/src/api_server.rs +431 -65
- package/crates/mint-core/src/browser.rs +630 -102
- package/crates/mint-core/src/channels.rs +105 -6
- package/crates/mint-core/src/chat.rs +174 -11
- package/crates/mint-core/src/code_tools.rs +47 -31
- package/crates/mint-core/src/config.rs +302 -17
- package/crates/mint-core/src/image_gen.rs +299 -38
- package/crates/mint-core/src/lib.rs +10 -7
- package/crates/mint-core/src/mcp.rs +119 -6
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +237 -96
- package/crates/mint-core/src/pictures.rs +67 -5
- 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 +128 -25
- package/crates/mint-core/src/video_gen.rs +235 -0
- package/package.json +8 -2
- 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/shared-modules/luajit/luajit.json +2 -2
- package/skills-lock.json +41 -0
- package/src/renderer/index-web.html +1 -1
- package/src/renderer/index.html +1 -1
- package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
- package/src/renderer/shared/constants/slashCommands.ts +27 -0
- package/src/renderer/shared/platform.ts +3 -0
- package/src/renderer/shared/types.ts +4 -0
- package/src/renderer/shared/utils/approval.tsx +86 -7
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/shared/utils/markdown.tsx +217 -88
- package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
- package/src/renderer/src/components/ChatPanel.tsx +486 -69
- package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
- package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
- package/src/renderer/src/components/MintDashboard.tsx +195 -7
- package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
- package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src/components/Settings/GeneralTab.tsx +795 -413
- package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
- package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
- package/src/renderer/src/components/SettingsWindow.tsx +106 -3
- package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/image-studio.css +106 -0
- package/src/renderer/src/css/settings.css +158 -0
- package/src/renderer/src/css/styles.css +368 -1
- package/src/renderer/src/css/veo-studio.css +626 -0
- package/src/renderer/src/index.css +1 -0
- package/src/renderer/src/tauri.ts +144 -17
- package/src/renderer/src-web/components/ChatPanel.tsx +385 -93
- package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
- package/src/renderer/src-web/components/MintDashboard.tsx +156 -5
- package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +795 -411
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
- package/src/renderer/src-web/components/SettingsWindow.tsx +110 -3
- package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
- package/src/renderer/src-web/css/image-studio.css +106 -0
- package/src/renderer/src-web/css/settings.css +158 -0
- package/src/renderer/src-web/css/styles.css +369 -1
- package/src/renderer/src-web/css/veo-studio.css +650 -0
- package/src/renderer/src-web/index.css +1 -0
- package/src/renderer/src-web/tauri.ts +135 -16
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +155 -25
- package/src-tauri/src/webhooks.rs +45 -31
- package/src-tauri/src/workflows.rs +1 -0
- package/src-tauri/tauri.conf.json +1 -1
- package/temp_gridgeist/AGENTS.md +8 -0
- package/temp_gridgeist/CHANGELOG.md +130 -0
- package/temp_gridgeist/LICENSE +21 -0
- package/temp_gridgeist/README.md +323 -0
- package/temp_gridgeist/README.th.md +325 -0
- package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
- package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
- package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
- package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
- package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
|
@@ -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,36 +247,176 @@ 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
|
+
/// Sets the active provider and model, saves the configuration to disk, and logs a system event interaction.
|
|
262
|
+
pub fn set_active_model(
|
|
263
|
+
&mut self,
|
|
264
|
+
provider: &str,
|
|
265
|
+
model: Option<&str>,
|
|
266
|
+
) -> Result<String, ConfigError> {
|
|
267
|
+
let old_provider = self.ai_provider.clone();
|
|
268
|
+
let old_active_model = self.active_model().to_string();
|
|
269
|
+
|
|
270
|
+
self.ai_provider = provider.to_string();
|
|
271
|
+
if let Some(m) = model {
|
|
272
|
+
let m_trimmed = m.trim();
|
|
273
|
+
if !m_trimmed.is_empty() {
|
|
274
|
+
match provider {
|
|
275
|
+
"anthropic" => self.anthropic_model = m_trimmed.to_string(),
|
|
276
|
+
"openai" => self.openai_model = m_trimmed.to_string(),
|
|
277
|
+
"openrouter" => self.openrouter_model = m_trimmed.to_string(),
|
|
278
|
+
"deepseek" => self.deepseek_model = m_trimmed.to_string(),
|
|
279
|
+
"huggingface" => self.hf_model = m_trimmed.to_string(),
|
|
280
|
+
"local_openai" => self.local_model_name = m_trimmed.to_string(),
|
|
281
|
+
"ollama" => self.ollama_model = m_trimmed.to_string(),
|
|
282
|
+
"gemini" => self.gemini_model = m_trimmed.to_string(),
|
|
283
|
+
_ => {}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
save_config(self)?;
|
|
288
|
+
|
|
289
|
+
let active_model = self.active_model().to_string();
|
|
290
|
+
let display_name = format!("{} • {}", self.ai_provider, active_model);
|
|
291
|
+
|
|
292
|
+
let changed = old_provider != self.ai_provider || old_active_model != active_model;
|
|
293
|
+
if changed {
|
|
294
|
+
if let Ok(memory) = crate::MemoryStore::open_default() {
|
|
295
|
+
let _ = memory.add_interaction_for_chat_with_fallback(
|
|
296
|
+
crate::CHAT_CLI_ID,
|
|
297
|
+
&display_name,
|
|
298
|
+
"",
|
|
299
|
+
"system",
|
|
300
|
+
"provider_change",
|
|
301
|
+
None,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
Ok(display_name)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// Returns the `CustomProvider` whose id matches `provider_key`.
|
|
310
|
+
///
|
|
311
|
+
/// `provider_key` must be in `"custom:<id>"` format (the leading `"custom:"`
|
|
312
|
+
/// prefix is stripped before the lookup).
|
|
313
|
+
pub fn resolve_custom_provider(&self, provider_key: &str) -> Option<&CustomProvider> {
|
|
314
|
+
let id = provider_key.strip_prefix("custom:").unwrap_or(provider_key);
|
|
315
|
+
self.custom_providers.iter().find(|cp| cp.id == id)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
pub fn available_providers(&self) -> Vec<String> {
|
|
213
319
|
let mut providers = Vec::new();
|
|
214
320
|
if has_value(&self.anthropic_api_key) {
|
|
215
|
-
providers.push("anthropic");
|
|
321
|
+
providers.push("anthropic".to_owned());
|
|
216
322
|
}
|
|
217
323
|
if has_value(&self.openai_api_key) {
|
|
218
|
-
providers.push("openai");
|
|
324
|
+
providers.push("openai".to_owned());
|
|
219
325
|
}
|
|
220
326
|
if has_value(&self.openrouter_api_key) {
|
|
221
|
-
providers.push("openrouter");
|
|
327
|
+
providers.push("openrouter".to_owned());
|
|
222
328
|
}
|
|
223
329
|
if has_value(&self.deepseek_api_key) {
|
|
224
|
-
providers.push("deepseek");
|
|
330
|
+
providers.push("deepseek".to_owned());
|
|
225
331
|
}
|
|
226
332
|
if has_value(&self.api_key) {
|
|
227
|
-
providers.push("gemini");
|
|
333
|
+
providers.push("gemini".to_owned());
|
|
228
334
|
}
|
|
229
335
|
if has_value(&self.hf_api_key) {
|
|
230
|
-
providers.push("huggingface");
|
|
336
|
+
providers.push("huggingface".to_owned());
|
|
231
337
|
}
|
|
232
338
|
if has_value(&self.local_api_base_url) {
|
|
233
|
-
providers.push("local_openai");
|
|
339
|
+
providers.push("local_openai".to_owned());
|
|
340
|
+
}
|
|
341
|
+
providers.push("ollama".to_owned());
|
|
342
|
+
// Include all user-defined custom providers that have a base URL configured.
|
|
343
|
+
for cp in &self.custom_providers {
|
|
344
|
+
if !cp.base_url.trim().is_empty() {
|
|
345
|
+
providers.push(format!("custom:{}", cp.id));
|
|
346
|
+
}
|
|
234
347
|
}
|
|
235
|
-
providers.push("ollama");
|
|
236
348
|
providers
|
|
237
349
|
}
|
|
350
|
+
|
|
351
|
+
pub fn active_workspace_path(&self) -> Option<PathBuf> {
|
|
352
|
+
self.extra
|
|
353
|
+
.get("activeWorkspacePath")
|
|
354
|
+
.or_else(|| self.extra.get("defaultWorkspacePath"))
|
|
355
|
+
.and_then(Value::as_str)
|
|
356
|
+
.map(str::trim)
|
|
357
|
+
.filter(|s| !s.is_empty())
|
|
358
|
+
.map(PathBuf::from)
|
|
359
|
+
.or_else(|| std::env::current_dir().ok())
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
pub fn line_webhook_host(&self) -> String {
|
|
363
|
+
self.extra
|
|
364
|
+
.get("lineWebhookHost")
|
|
365
|
+
.and_then(Value::as_str)
|
|
366
|
+
.map(str::trim)
|
|
367
|
+
.filter(|s| !s.is_empty())
|
|
368
|
+
.unwrap_or("127.0.0.1")
|
|
369
|
+
.to_string()
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
pub fn line_webhook_port(&self) -> u16 {
|
|
373
|
+
self.extra
|
|
374
|
+
.get("lineWebhookPort")
|
|
375
|
+
.and_then(|v| {
|
|
376
|
+
v.as_u64()
|
|
377
|
+
.or_else(|| v.as_str().and_then(|s| s.parse().ok()))
|
|
378
|
+
})
|
|
379
|
+
.map(|p| p as u16)
|
|
380
|
+
.unwrap_or(3000)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
pub fn whatsapp_webhook_host(&self) -> String {
|
|
384
|
+
self.extra
|
|
385
|
+
.get("whatsappWebhookHost")
|
|
386
|
+
.and_then(Value::as_str)
|
|
387
|
+
.map(str::trim)
|
|
388
|
+
.filter(|s| !s.is_empty())
|
|
389
|
+
.unwrap_or("127.0.0.1")
|
|
390
|
+
.to_string()
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
pub fn whatsapp_webhook_port(&self) -> u16 {
|
|
394
|
+
self.extra
|
|
395
|
+
.get("whatsappWebhookPort")
|
|
396
|
+
.and_then(|v| {
|
|
397
|
+
v.as_u64()
|
|
398
|
+
.or_else(|| v.as_str().and_then(|s| s.parse().ok()))
|
|
399
|
+
})
|
|
400
|
+
.map(|p| p as u16)
|
|
401
|
+
.unwrap_or(3001)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
pub fn bridge_ack_enabled(&self) -> bool {
|
|
405
|
+
self.extra
|
|
406
|
+
.get("enableBridgeAckNotification")
|
|
407
|
+
.and_then(Value::as_bool)
|
|
408
|
+
.unwrap_or(true)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
pub fn bridge_ack_message(&self) -> String {
|
|
412
|
+
self.extra
|
|
413
|
+
.get("bridgeAckMessage")
|
|
414
|
+
.and_then(Value::as_str)
|
|
415
|
+
.map(str::trim)
|
|
416
|
+
.filter(|s| !s.is_empty())
|
|
417
|
+
.unwrap_or("[Mint Agent] Remote command received, processing...")
|
|
418
|
+
.to_string()
|
|
419
|
+
}
|
|
238
420
|
}
|
|
239
421
|
|
|
240
422
|
pub fn config_path() -> Result<PathBuf, ConfigError> {
|
|
@@ -438,7 +620,10 @@ mod tests {
|
|
|
438
620
|
|
|
439
621
|
#[test]
|
|
440
622
|
fn default_config_always_exposes_ollama() {
|
|
441
|
-
assert_eq!(
|
|
623
|
+
assert_eq!(
|
|
624
|
+
MintConfig::default().available_providers(),
|
|
625
|
+
vec!["ollama".to_owned()]
|
|
626
|
+
);
|
|
442
627
|
}
|
|
443
628
|
|
|
444
629
|
#[test]
|
|
@@ -454,12 +639,12 @@ mod tests {
|
|
|
454
639
|
assert_eq!(
|
|
455
640
|
config.available_providers(),
|
|
456
641
|
vec![
|
|
457
|
-
"openai",
|
|
458
|
-
"openrouter",
|
|
459
|
-
"deepseek",
|
|
460
|
-
"gemini",
|
|
461
|
-
"local_openai",
|
|
462
|
-
"ollama"
|
|
642
|
+
"openai".to_owned(),
|
|
643
|
+
"openrouter".to_owned(),
|
|
644
|
+
"deepseek".to_owned(),
|
|
645
|
+
"gemini".to_owned(),
|
|
646
|
+
"local_openai".to_owned(),
|
|
647
|
+
"ollama".to_owned(),
|
|
463
648
|
]
|
|
464
649
|
);
|
|
465
650
|
}
|
|
@@ -509,8 +694,108 @@ mod tests {
|
|
|
509
694
|
save_config_to(&path, &initial_config).unwrap();
|
|
510
695
|
|
|
511
696
|
let loaded = load_config_from(&path).unwrap();
|
|
512
|
-
assert!(loaded.theme.
|
|
697
|
+
assert!(!loaded.theme.is_empty());
|
|
513
698
|
|
|
514
699
|
let _ = std::fs::remove_file(&path);
|
|
515
700
|
}
|
|
701
|
+
|
|
702
|
+
#[test]
|
|
703
|
+
fn custom_provider_round_trips() {
|
|
704
|
+
let config = MintConfig {
|
|
705
|
+
custom_providers: vec![CustomProvider {
|
|
706
|
+
id: "myprovider".into(),
|
|
707
|
+
display_name: "My Provider".into(),
|
|
708
|
+
base_url: "http://localhost:20128/v1".into(),
|
|
709
|
+
api_key: "sk-abc".into(),
|
|
710
|
+
models: vec![CustomProviderModel {
|
|
711
|
+
model_id: "llama-3.1-8b".into(),
|
|
712
|
+
display_name: "Llama 3.1 8B".into(),
|
|
713
|
+
}],
|
|
714
|
+
headers: vec![CustomProviderHeader {
|
|
715
|
+
name: "X-Custom".into(),
|
|
716
|
+
value: "test".into(),
|
|
717
|
+
}],
|
|
718
|
+
}],
|
|
719
|
+
..MintConfig::default()
|
|
720
|
+
};
|
|
721
|
+
let json = serde_json::to_string(&config).unwrap();
|
|
722
|
+
let restored: MintConfig = serde_json::from_str(&json).unwrap();
|
|
723
|
+
assert_eq!(restored, config);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
#[test]
|
|
727
|
+
fn custom_provider_appears_in_available_providers() {
|
|
728
|
+
let config = MintConfig {
|
|
729
|
+
custom_providers: vec![CustomProvider {
|
|
730
|
+
id: "myprovider".into(),
|
|
731
|
+
base_url: "http://localhost:20128/v1".into(),
|
|
732
|
+
..CustomProvider::default()
|
|
733
|
+
}],
|
|
734
|
+
..MintConfig::default()
|
|
735
|
+
};
|
|
736
|
+
assert!(
|
|
737
|
+
config
|
|
738
|
+
.available_providers()
|
|
739
|
+
.contains(&"custom:myprovider".to_owned())
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
#[test]
|
|
744
|
+
fn custom_provider_without_base_url_excluded() {
|
|
745
|
+
let config = MintConfig {
|
|
746
|
+
custom_providers: vec![CustomProvider {
|
|
747
|
+
id: "empty".into(),
|
|
748
|
+
base_url: "".into(),
|
|
749
|
+
..CustomProvider::default()
|
|
750
|
+
}],
|
|
751
|
+
..MintConfig::default()
|
|
752
|
+
};
|
|
753
|
+
assert!(
|
|
754
|
+
!config
|
|
755
|
+
.available_providers()
|
|
756
|
+
.contains(&"custom:empty".to_owned())
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
#[test]
|
|
761
|
+
fn active_model_resolves_first_custom_model() {
|
|
762
|
+
let config = MintConfig {
|
|
763
|
+
ai_provider: "custom:myprovider".into(),
|
|
764
|
+
custom_providers: vec![CustomProvider {
|
|
765
|
+
id: "myprovider".into(),
|
|
766
|
+
base_url: "http://localhost:20128/v1".into(),
|
|
767
|
+
models: vec![CustomProviderModel {
|
|
768
|
+
model_id: "llama-3.1-8b".into(),
|
|
769
|
+
display_name: String::new(),
|
|
770
|
+
}],
|
|
771
|
+
..CustomProvider::default()
|
|
772
|
+
}],
|
|
773
|
+
..MintConfig::default()
|
|
774
|
+
};
|
|
775
|
+
assert_eq!(config.active_model(), "llama-3.1-8b");
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
#[test]
|
|
779
|
+
fn resolve_custom_provider_returns_none_for_unknown() {
|
|
780
|
+
let config = MintConfig::default();
|
|
781
|
+
assert!(
|
|
782
|
+
config
|
|
783
|
+
.resolve_custom_provider("custom:nonexistent")
|
|
784
|
+
.is_none()
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
#[test]
|
|
789
|
+
fn resolve_custom_provider_strips_prefix() {
|
|
790
|
+
let config = MintConfig {
|
|
791
|
+
custom_providers: vec![CustomProvider {
|
|
792
|
+
id: "myp".into(),
|
|
793
|
+
..CustomProvider::default()
|
|
794
|
+
}],
|
|
795
|
+
..MintConfig::default()
|
|
796
|
+
};
|
|
797
|
+
assert!(config.resolve_custom_provider("custom:myp").is_some());
|
|
798
|
+
// Also works without prefix
|
|
799
|
+
assert!(config.resolve_custom_provider("myp").is_some());
|
|
800
|
+
}
|
|
516
801
|
}
|