@pheem49/mint 1.8.1 → 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.lock +3 -3
- package/Cargo.toml +1 -1
- package/README.md +0 -17
- package/Release_Note.md +137 -58
- package/bin/mint +0 -0
- package/crates/mint-cli/src/agent.rs +150 -45
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/main.rs +929 -412
- package/crates/mint-cli/src/onboard.rs +108 -108
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-core/src/agent_loop.rs +1 -0
- package/crates/mint-core/src/api_server.rs +108 -52
- package/crates/mint-core/src/browser.rs +56 -1
- package/crates/mint-core/src/channels.rs +1 -0
- package/crates/mint-core/src/chat.rs +126 -8
- package/crates/mint-core/src/code_tools.rs +18 -20
- package/crates/mint-core/src/config.rs +245 -17
- package/crates/mint-core/src/image_gen.rs +5 -5
- package/crates/mint-core/src/lib.rs +16 -2
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +260 -72
- 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/crates/mint-core/src/tts.rs +3 -1
- package/flatpak/com.pheem49.mint.desktop +9 -0
- package/flatpak/com.pheem49.mint.metainfo.xml +35 -0
- package/flatpak/com.pheem49.mint.yaml +52 -0
- package/package.json +1 -1
- package/shared-modules/.github/workflows/ci.yml +120 -0
- package/shared-modules/.github/workflows/update.yaml +28 -0
- package/shared-modules/CODEOWNERS +23 -0
- package/shared-modules/README.md +79 -0
- package/shared-modules/SDL/README.md +3 -0
- package/shared-modules/SDL/SDL-1.2.15.json +51 -0
- package/shared-modules/SDL/SDL_Pango-0.1.2-API-adds.patch +118 -0
- package/shared-modules/SDL/SDL_image-1.2.12.json +25 -0
- package/shared-modules/SDL/SDL_mixer-1.2.12.json +27 -0
- package/shared-modules/SDL/SDL_net-1.2.8.json +19 -0
- package/shared-modules/SDL/SDL_pango-0.1.2.json +26 -0
- package/shared-modules/SDL/SDL_ttf-2.0.11.json +28 -0
- package/shared-modules/SDL/libsdl-1.2.15-strict-prototypes.patch +316 -0
- package/shared-modules/SDL/sdl-check-for-SDL_VIDEO_X11_BACKINGSTORE.patch +24 -0
- package/shared-modules/SDL/sdl-libx11-build.patch +59 -0
- package/shared-modules/SDL/sdl-sysloadso-buffer-length.patch +27 -0
- package/shared-modules/SDL/sdl12-compat.json +22 -0
- package/shared-modules/SDL2/SDL2_image.json +27 -0
- package/shared-modules/SDL2/SDL2_ttf.json +29 -0
- package/shared-modules/cld2/CMakeLists.txt +157 -0
- package/shared-modules/cld2/cld2.json +24 -0
- package/shared-modules/clutter/clutter.json +72 -0
- package/shared-modules/dbus-glib/dbus-glib.json +29 -0
- package/shared-modules/glew/glew.json +36 -0
- package/shared-modules/glu/glu-9.json +12 -0
- package/shared-modules/gtk2/arc-gtk2-theme-Replace-Inkscape-dependency-with-rsvg-convert.patch +180 -0
- package/shared-modules/gtk2/gtk2-common-themes.json +360 -0
- package/shared-modules/gtk2/gtk2-fix-crash-in-show-uri.patch +30 -0
- package/shared-modules/gtk2/gtk2-gcc14.patch +281 -0
- package/shared-modules/gtk2/gtk2-gcc15.patch +250 -0
- package/shared-modules/gtk2/gtk2-murrine-c99.patch +34 -0
- package/shared-modules/gtk2/gtk2-use-adwaita-theme.patch +80 -0
- package/shared-modules/gtk2/gtk2.json +120 -0
- package/shared-modules/gtk2/ibus-gcc15.patch +13 -0
- package/shared-modules/gtk2/murrine-engine-fix-crash.patch +12 -0
- package/shared-modules/gtk2/readme.md +10 -0
- package/shared-modules/intltool/intltool-0.51.json +20 -0
- package/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch +59 -0
- package/shared-modules/lame/0001-Build-xmm_quantize_sub.c-with-msse.patch +34 -0
- package/shared-modules/lame/0002-Include-GTK-1-autoconf-directives-in-build-system.patch +222 -0
- package/shared-modules/lame/0003-Patch-out-remaining-ansi2knr.patch +70 -0
- package/shared-modules/lame/0004-initscr-is-not-used-anywhere-in-lame-sourcetree.patch +41 -0
- package/shared-modules/lame/0005-Update-autoconf.patch +81 -0
- package/shared-modules/lame/lame-3.99.5.json +32 -0
- package/shared-modules/libappindicator/libappindicator-gtk2-12.10.json +140 -0
- package/shared-modules/libappindicator/libappindicator-gtk3-12.10.json +140 -0
- package/shared-modules/libappindicator/libappindicator-gtk3-introspection-12.10.json +140 -0
- package/shared-modules/libappindicator/patches/0001-libappindicator-ftbfs.patch +20 -0
- package/shared-modules/libappindicator/patches/0002-libappindicator-no-python.patch +888 -0
- package/shared-modules/libappindicator/patches/0003-libappindicator-fix-crash-from-incorrect-signal-emission.patch +74 -0
- package/shared-modules/libappindicator/patches/0004-libappindicator-fix-crash-iterating-icon-themes.patch +11 -0
- package/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch +51 -0
- package/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch +44 -0
- package/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json +121 -0
- package/shared-modules/libcanberra/0001-gtk-Don-t-assume-all-GdkDisplays-are-GdkX11Displays-.patch +123 -0
- package/shared-modules/libcanberra/libcanberra.json +44 -0
- package/shared-modules/libmad/libmad.json +19 -0
- package/shared-modules/libsecret/libsecret.json +23 -0
- package/shared-modules/libsoup/libsoup-2.4.json +14 -0
- package/shared-modules/libusb/libusb.json +27 -0
- package/shared-modules/linux-audio/dssi.json +31 -0
- package/shared-modules/linux-audio/fftw3f-static.json +44 -0
- package/shared-modules/linux-audio/fftw3f.json +42 -0
- package/shared-modules/linux-audio/fluidsynth2-static.json +36 -0
- package/shared-modules/linux-audio/fluidsynth2.json +25 -0
- package/shared-modules/linux-audio/ladspa.json +26 -0
- package/shared-modules/linux-audio/lash.json +35 -0
- package/shared-modules/linux-audio/libinstpatch.json +16 -0
- package/shared-modules/linux-audio/liblo-static.json +22 -0
- package/shared-modules/linux-audio/liblo.json +17 -0
- package/shared-modules/linux-audio/lilv-static.json +94 -0
- package/shared-modules/linux-audio/lilv.json +89 -0
- package/shared-modules/linux-audio/lrdf.json +57 -0
- package/shared-modules/linux-audio/lv2.json +20 -0
- package/shared-modules/linux-audio/patches/lash/0001-configure-remove-aclocal-m4.patch +29 -0
- package/shared-modules/linux-audio/patches/lash/0002-Fix-build-with-recent-gcc.patch +73 -0
- package/shared-modules/linux-audio/patches/lash/0003-Fix-linking.patch +136 -0
- package/shared-modules/linux-audio/patches/raptor-libxml.patch +31 -0
- package/shared-modules/linux-audio/patches/stk-install.sh +54 -0
- package/shared-modules/linux-audio/stk.json +28 -0
- package/shared-modules/lua5.1/0004-Fix-stack-overflow-in-vararg-functions.patch +21 -0
- package/shared-modules/lua5.1/lua-5.1.5-so.patch +43 -0
- package/shared-modules/lua5.1/lua-5.1.5.json +42 -0
- package/shared-modules/lua5.3/lua-5.3.5.json +41 -0
- package/shared-modules/lua5.3/lua.pc.in +8 -0
- package/shared-modules/lua5.3/luaconf.patch +47 -0
- package/shared-modules/lua5.3/makefile-add-liblua.so-rule.patch +47 -0
- package/shared-modules/lua5.4/lua-5.4.json +41 -0
- package/shared-modules/lua5.4/lua.pc.in +8 -0
- package/shared-modules/lua5.4/luaconf.patch +34 -0
- package/shared-modules/lua5.4/makefile-add-liblua.so-rule.patch +42 -0
- package/shared-modules/luajit/luajit.json +30 -0
- package/shared-modules/lzo/lzo.json +27 -0
- package/shared-modules/mac/mac.json +18 -0
- package/shared-modules/physfs/physfs.json +26 -0
- package/shared-modules/pygame/audiofile-gcc6.patch +21 -0
- package/shared-modules/pygame/cython-fixes.patch +34 -0
- package/shared-modules/pygame/fluidsynth-no-rawmidi.patch +69 -0
- package/shared-modules/pygame/glib.patch +25 -0
- package/shared-modules/pygame/pygame1-python2.json +157 -0
- package/shared-modules/pygtk/pygtk-c99.patch +15 -0
- package/shared-modules/pygtk/pygtk.json +117 -0
- package/shared-modules/pygtk/pygtk.patch +39 -0
- package/shared-modules/python2.7/python-2.7.json +48 -0
- package/shared-modules/smpeg/smpeg-0.4.5.json +50 -0
- package/shared-modules/smpeg/smpeg-am-prog-as.patch +12 -0
- package/shared-modules/smpeg/smpeg-export-mpegaudio-class.patch +17 -0
- package/shared-modules/smpeg/smpeg-gcc6.patch +40 -0
- package/shared-modules/smpeg/smpeg-no-gtk.patch +37 -0
- package/shared-modules/squashfs-tools/squashfs-tools.json +25 -0
- package/shared-modules/update-checker.sh +50 -0
- package/shared-modules/vorbisgain/0001-temp_files.patch +75 -0
- package/shared-modules/vorbisgain/0002-errno.patch +33 -0
- package/shared-modules/vorbisgain/0003-manpage.patch +248 -0
- package/shared-modules/vorbisgain/0004-vorbisgain_mtime.patch +183 -0
- package/shared-modules/vorbisgain/0005-double_fclose.patch +21 -0
- package/shared-modules/vorbisgain/0006-manpage_hyphens.patch +56 -0
- package/shared-modules/vorbisgain/0007-recursively_spelling.patch +14 -0
- package/shared-modules/vorbisgain/0008-manpage_recursion_mistake.patch +23 -0
- package/shared-modules/vorbisgain/0009-hardening.patch +13 -0
- package/shared-modules/vorbisgain/0010-fclose.patch +13 -0
- package/shared-modules/vorbisgain/README.md +23 -0
- package/shared-modules/vorbisgain/vorbisgain_0.37-2.json +29 -0
- package/src/renderer/shared/agentProgress.ts +145 -0
- package/src/renderer/shared/calculator.ts +52 -0
- package/src/renderer/shared/components/AgentActivityDrawer.tsx +72 -0
- package/src/renderer/shared/components/AgentActivityTable.tsx +85 -0
- package/src/renderer/shared/components/ApprovalCard.tsx +93 -0
- package/src/renderer/shared/components/ChatCodeBlock.tsx +75 -0
- package/src/renderer/shared/components/ThinkingBlock.tsx +101 -0
- package/src/renderer/shared/constants/models.ts +91 -0
- package/src/renderer/shared/platform.ts +82 -0
- package/src/renderer/shared/types.ts +140 -0
- package/src/renderer/shared/utils/agentActivity.ts +187 -0
- package/src/renderer/shared/utils/approval.ts +1 -0
- package/src/renderer/shared/utils/approval.tsx +64 -0
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/shared/utils/markdown.tsx +347 -0
- package/src/renderer/shared/utils/providers.ts +36 -0
- package/src/renderer/shared/utils/speech.ts +216 -0
- package/src/renderer/shared/utils/ui.ts +147 -0
- package/src/renderer/src/agentProgress.ts +11 -39
- package/src/renderer/src/calculator.ts +3 -46
- package/src/renderer/src/components/ChatPanel.tsx +219 -1308
- package/src/renderer/src/components/MintDashboard.tsx +67 -170
- package/src/renderer/src/components/PicturesLibrary.tsx +32 -8
- package/src/renderer/src/components/Settings/AgentsTab.tsx +381 -0
- package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -22
- package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
- package/src/renderer/src/components/SettingsWindow.tsx +120 -3
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/styles.css +100 -0
- package/src/renderer/src/tauri.ts +128 -143
- package/src/renderer/src-web/agentProgress.ts +11 -39
- package/src/renderer/src-web/calculator.ts +3 -46
- package/src/renderer/src-web/components/ChatPanel.tsx +219 -1306
- package/src/renderer/src-web/components/MintDashboard.tsx +42 -170
- package/src/renderer/src-web/components/PicturesLibrary.tsx +23 -29
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +381 -0
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -22
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
- package/src/renderer/src-web/components/SettingsWindow.tsx +127 -3
- package/src/renderer/src-web/css/styles.css +100 -0
- package/src/renderer/src-web/tauri.ts +180 -102
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +168 -40
- package/src-tauri/src/webhooks.rs +1 -0
- package/src-tauri/src/workflows.rs +1 -0
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -213,16 +213,15 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
213
213
|
new_title: String,
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
if let Ok(req) = serde_json::from_str::<RenameRequest>(body)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
216
|
+
if let Ok(req) = serde_json::from_str::<RenameRequest>(body)
|
|
217
|
+
&& let Ok(memory) = MemoryStore::open_default()
|
|
218
|
+
{
|
|
219
|
+
let updated = memory
|
|
220
|
+
.rename_chat_session(&req.chat_id, &req.new_title)
|
|
221
|
+
.unwrap_or(0);
|
|
222
|
+
let response = serde_json::json!({ "status": "ok", "updated": updated });
|
|
223
|
+
send_json_response(socket, "200 OK", &response.to_string()).await;
|
|
224
|
+
return;
|
|
226
225
|
}
|
|
227
226
|
send_json_response(
|
|
228
227
|
socket,
|
|
@@ -252,12 +251,12 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
252
251
|
key: String,
|
|
253
252
|
value: String,
|
|
254
253
|
}
|
|
255
|
-
if let Ok(req) = serde_json::from_str::<ProfileRequest>(body)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
if let Ok(req) = serde_json::from_str::<ProfileRequest>(body)
|
|
255
|
+
&& let Ok(memory) = MemoryStore::open_default()
|
|
256
|
+
{
|
|
257
|
+
let _ = memory.set_profile(&req.key, &req.value);
|
|
258
|
+
send_json_response(socket, "200 OK", "{\"status\":\"ok\"}").await;
|
|
259
|
+
return;
|
|
261
260
|
}
|
|
262
261
|
send_json_response(
|
|
263
262
|
socket,
|
|
@@ -341,25 +340,18 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
341
340
|
activity: Vec<AgentProgress>,
|
|
342
341
|
}
|
|
343
342
|
|
|
344
|
-
if let Ok(payload) = serde_json::from_str::<SaveAgentActivityRequest>(body)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
.await;
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
Err(_) => {}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
343
|
+
if let Ok(payload) = serde_json::from_str::<SaveAgentActivityRequest>(body)
|
|
344
|
+
&& let Ok(memory) = MemoryStore::open_default()
|
|
345
|
+
&& let Ok(activity_json) = serde_json::to_string(&payload.activity)
|
|
346
|
+
&& memory
|
|
347
|
+
.set_interaction_agent_activity_json(
|
|
348
|
+
payload.interaction_id,
|
|
349
|
+
&activity_json,
|
|
350
|
+
)
|
|
351
|
+
.is_ok()
|
|
352
|
+
{
|
|
353
|
+
send_json_response(socket, "200 OK", "{\"status\":\"ok\"}").await;
|
|
354
|
+
return;
|
|
363
355
|
}
|
|
364
356
|
send_json_response(
|
|
365
357
|
socket,
|
|
@@ -406,11 +398,11 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
406
398
|
}
|
|
407
399
|
}
|
|
408
400
|
("POST", "/api/config") => {
|
|
409
|
-
if let Ok(new_config) = serde_json::from_str::<MintConfig>(body)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
401
|
+
if let Ok(new_config) = serde_json::from_str::<MintConfig>(body)
|
|
402
|
+
&& save_config(&new_config).is_ok()
|
|
403
|
+
{
|
|
404
|
+
send_json_response(socket, "200 OK", "{\"status\":\"ok\"}").await;
|
|
405
|
+
return;
|
|
414
406
|
}
|
|
415
407
|
send_json_response(
|
|
416
408
|
socket,
|
|
@@ -476,6 +468,7 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
476
468
|
image_data_uri: Option<String>,
|
|
477
469
|
audio_data_uri: Option<String>,
|
|
478
470
|
document_attachment: Option<crate::chat::DocumentAttachment>,
|
|
471
|
+
agent_id: Option<String>,
|
|
479
472
|
}
|
|
480
473
|
|
|
481
474
|
if let Ok(req) = serde_json::from_str::<ApiChatRequest>(body) {
|
|
@@ -488,6 +481,7 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
488
481
|
audio_data_uri: req.audio_data_uri,
|
|
489
482
|
document_attachment: req.document_attachment,
|
|
490
483
|
workspace_path: None,
|
|
484
|
+
agent_id: req.agent_id,
|
|
491
485
|
};
|
|
492
486
|
let mut chat_req =
|
|
493
487
|
chat_req.with_document_context(&config).unwrap_or(chat_req);
|
|
@@ -550,6 +544,24 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
550
544
|
)
|
|
551
545
|
.await;
|
|
552
546
|
}
|
|
547
|
+
("POST", "/api/cancel-chat") => {
|
|
548
|
+
#[derive(Deserialize)]
|
|
549
|
+
#[serde(rename_all = "camelCase")]
|
|
550
|
+
struct CancelRequest {
|
|
551
|
+
chat_id: String,
|
|
552
|
+
}
|
|
553
|
+
if let Ok(req) = serde_json::from_str::<CancelRequest>(body) {
|
|
554
|
+
crate::cancel_agent(&req.chat_id);
|
|
555
|
+
send_json_response(socket, "200 OK", "{\"status\":\"ok\"}").await;
|
|
556
|
+
} else {
|
|
557
|
+
send_json_response(
|
|
558
|
+
socket,
|
|
559
|
+
"400 Bad Request",
|
|
560
|
+
"{\"status\":\"invalid cancel body\"}",
|
|
561
|
+
)
|
|
562
|
+
.await;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
553
565
|
("POST", "/api/chat-stream") => {
|
|
554
566
|
#[derive(Deserialize)]
|
|
555
567
|
#[serde(rename_all = "camelCase")]
|
|
@@ -560,6 +572,7 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
560
572
|
image_data_uri: Option<String>,
|
|
561
573
|
audio_data_uri: Option<String>,
|
|
562
574
|
document_attachment: Option<crate::chat::DocumentAttachment>,
|
|
575
|
+
agent_id: Option<String>,
|
|
563
576
|
}
|
|
564
577
|
|
|
565
578
|
if let Ok(req) = serde_json::from_str::<ApiChatRequest>(body) {
|
|
@@ -572,6 +585,7 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
572
585
|
audio_data_uri: req.audio_data_uri,
|
|
573
586
|
document_attachment: req.document_attachment,
|
|
574
587
|
workspace_path: None,
|
|
588
|
+
agent_id: req.agent_id,
|
|
575
589
|
};
|
|
576
590
|
let mut chat_req =
|
|
577
591
|
chat_req.with_document_context(&config).unwrap_or(chat_req);
|
|
@@ -631,7 +645,8 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
631
645
|
let config_clone = config.clone();
|
|
632
646
|
let chat_req_clone = chat_req.clone();
|
|
633
647
|
let tx_done = tx.clone();
|
|
634
|
-
|
|
648
|
+
let chat_id_str = chat_req.chat_id.clone().unwrap_or_default();
|
|
649
|
+
let join_handle = tokio::spawn(async move {
|
|
635
650
|
let result = orchestrate_chat_stream_with_fallback(
|
|
636
651
|
&config_clone,
|
|
637
652
|
&chat_req_clone,
|
|
@@ -670,11 +685,29 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
670
685
|
"text": format!("Error orchestrating chat: {e}")
|
|
671
686
|
}
|
|
672
687
|
});
|
|
673
|
-
let _ = tx_done
|
|
674
|
-
.send(format!("{}\n", err_json.to_string()));
|
|
688
|
+
let _ = tx_done.send(format!("{}\n", err_json));
|
|
675
689
|
}
|
|
676
690
|
}
|
|
677
691
|
});
|
|
692
|
+
|
|
693
|
+
let abort_handle = join_handle.abort_handle();
|
|
694
|
+
if !chat_id_str.is_empty() {
|
|
695
|
+
crate::ACTIVE_AGENTS
|
|
696
|
+
.lock()
|
|
697
|
+
.unwrap()
|
|
698
|
+
.insert(chat_id_str.clone(), abort_handle);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
let chat_id_str_cleanup = chat_id_str.clone();
|
|
702
|
+
tokio::spawn(async move {
|
|
703
|
+
let _ = join_handle.await;
|
|
704
|
+
if !chat_id_str_cleanup.is_empty() {
|
|
705
|
+
crate::ACTIVE_AGENTS
|
|
706
|
+
.lock()
|
|
707
|
+
.unwrap()
|
|
708
|
+
.remove(&chat_id_str_cleanup);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
678
711
|
} else {
|
|
679
712
|
let root = std::env::current_dir().unwrap_or_default();
|
|
680
713
|
let fast_mode = config
|
|
@@ -686,16 +719,19 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
686
719
|
let tx_done = tx.clone();
|
|
687
720
|
let config_clone = config.clone();
|
|
688
721
|
let chat_id = chat_req.chat_id.clone();
|
|
722
|
+
let chat_id_str = chat_id.clone().unwrap_or_default();
|
|
689
723
|
let message = chat_req.message.clone();
|
|
690
724
|
let image_data_uri = chat_req.image_data_uri.clone();
|
|
725
|
+
let agent_id = chat_req.agent_id.clone();
|
|
691
726
|
|
|
692
|
-
tokio::spawn(async move {
|
|
727
|
+
let join_handle = tokio::spawn(async move {
|
|
693
728
|
let result = orchestrate_agent_loop(
|
|
694
729
|
&config_clone,
|
|
695
730
|
&message,
|
|
696
731
|
&root,
|
|
697
732
|
image_data_uri,
|
|
698
733
|
chat_id.as_deref(),
|
|
734
|
+
agent_id.as_deref(),
|
|
699
735
|
fast_mode,
|
|
700
736
|
|_| Ok(ApprovalOutcome::Denied),
|
|
701
737
|
progress_cb,
|
|
@@ -729,11 +765,29 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
729
765
|
"text": format!("Error orchestrating agent: {e}")
|
|
730
766
|
}
|
|
731
767
|
});
|
|
732
|
-
let _ = tx_done
|
|
733
|
-
.send(format!("{}\n", err_json.to_string()));
|
|
768
|
+
let _ = tx_done.send(format!("{}\n", err_json));
|
|
734
769
|
}
|
|
735
770
|
}
|
|
736
771
|
});
|
|
772
|
+
|
|
773
|
+
let abort_handle = join_handle.abort_handle();
|
|
774
|
+
if !chat_id_str.is_empty() {
|
|
775
|
+
crate::ACTIVE_AGENTS
|
|
776
|
+
.lock()
|
|
777
|
+
.unwrap()
|
|
778
|
+
.insert(chat_id_str.clone(), abort_handle);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
let chat_id_str_cleanup = chat_id_str.clone();
|
|
782
|
+
tokio::spawn(async move {
|
|
783
|
+
let _ = join_handle.await;
|
|
784
|
+
if !chat_id_str_cleanup.is_empty() {
|
|
785
|
+
crate::ACTIVE_AGENTS
|
|
786
|
+
.lock()
|
|
787
|
+
.unwrap()
|
|
788
|
+
.remove(&chat_id_str_cleanup);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
737
791
|
}
|
|
738
792
|
}
|
|
739
793
|
|
|
@@ -894,6 +948,7 @@ async fn run_web_agent_loop(
|
|
|
894
948
|
&root,
|
|
895
949
|
request.image_data_uri.clone(),
|
|
896
950
|
request.chat_id.as_deref(),
|
|
951
|
+
request.agent_id.as_deref(),
|
|
897
952
|
fast_mode,
|
|
898
953
|
|_| Ok(ApprovalOutcome::Denied),
|
|
899
954
|
|_| {},
|
|
@@ -1082,12 +1137,13 @@ fn percent_decode(raw: &str) -> String {
|
|
|
1082
1137
|
let mut output = Vec::with_capacity(bytes.len());
|
|
1083
1138
|
let mut index = 0;
|
|
1084
1139
|
while index < bytes.len() {
|
|
1085
|
-
if bytes[index] == b'%'
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1140
|
+
if bytes[index] == b'%'
|
|
1141
|
+
&& index + 2 < bytes.len()
|
|
1142
|
+
&& let Ok(hex) = u8::from_str_radix(&raw[index + 1..index + 3], 16)
|
|
1143
|
+
{
|
|
1144
|
+
output.push(hex);
|
|
1145
|
+
index += 3;
|
|
1146
|
+
continue;
|
|
1091
1147
|
}
|
|
1092
1148
|
output.push(if bytes[index] == b'+' {
|
|
1093
1149
|
b' '
|
|
@@ -243,7 +243,7 @@ pub async fn spawn_automation_browser(config: &MintConfig) -> Result<(), String>
|
|
|
243
243
|
.unwrap_or("chromium");
|
|
244
244
|
|
|
245
245
|
let profile_dir = dirs::config_dir()
|
|
246
|
-
.unwrap_or_else(
|
|
246
|
+
.unwrap_or_else(std::env::temp_dir)
|
|
247
247
|
.join("mint")
|
|
248
248
|
.join("automation-profile");
|
|
249
249
|
let profile_arg = format!("--user-data-dir={}", profile_dir.to_string_lossy());
|
|
@@ -340,6 +340,61 @@ async fn cdp_call(config: &MintConfig, method: &str, params: Value) -> Result<Va
|
|
|
340
340
|
let (mut socket, _) = connect_async(socket_url)
|
|
341
341
|
.await
|
|
342
342
|
.map_err(|error| format!("unable to connect to Chrome DevTools websocket: {error}"))?;
|
|
343
|
+
let is_aura_script = method == "Runtime.evaluate"
|
|
344
|
+
&& params["expression"]
|
|
345
|
+
.as_str()
|
|
346
|
+
.map(|s| s.contains("mint-browser-aura"))
|
|
347
|
+
.unwrap_or(false);
|
|
348
|
+
|
|
349
|
+
if !is_aura_script {
|
|
350
|
+
let aura_script = r#"
|
|
351
|
+
(() => {
|
|
352
|
+
if (document.getElementById('mint-browser-aura')) return;
|
|
353
|
+
const aura = document.createElement('div');
|
|
354
|
+
aura.id = 'mint-browser-aura';
|
|
355
|
+
|
|
356
|
+
const style = document.createElement('style');
|
|
357
|
+
style.id = 'mint-browser-aura-style';
|
|
358
|
+
style.textContent = `
|
|
359
|
+
@keyframes mint-pulse {
|
|
360
|
+
0% { box-shadow: inset 0 0 15px rgba(16, 185, 129, 0.4); border-color: rgba(16, 185, 129, 0.6); }
|
|
361
|
+
100% { box-shadow: inset 0 0 30px rgba(16, 185, 129, 0.8); border-color: rgba(16, 185, 129, 1); }
|
|
362
|
+
}
|
|
363
|
+
`;
|
|
364
|
+
document.head.appendChild(style);
|
|
365
|
+
|
|
366
|
+
aura.style.cssText = `
|
|
367
|
+
position: fixed;
|
|
368
|
+
top: 0;
|
|
369
|
+
left: 0;
|
|
370
|
+
width: 100vw;
|
|
371
|
+
height: 100vh;
|
|
372
|
+
border: 5px solid rgba(16, 185, 129, 0.6);
|
|
373
|
+
pointer-events: none;
|
|
374
|
+
z-index: 2147483647;
|
|
375
|
+
box-sizing: border-box;
|
|
376
|
+
animation: mint-pulse 1.5s infinite alternate;
|
|
377
|
+
`;
|
|
378
|
+
document.body.appendChild(aura);
|
|
379
|
+
})()
|
|
380
|
+
"#;
|
|
381
|
+
|
|
382
|
+
let _ = socket
|
|
383
|
+
.send(Message::Text(
|
|
384
|
+
json!({
|
|
385
|
+
"id": 999,
|
|
386
|
+
"method": "Runtime.evaluate",
|
|
387
|
+
"params": {
|
|
388
|
+
"expression": aura_script,
|
|
389
|
+
"returnByValue": true
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
.to_string()
|
|
393
|
+
.into(),
|
|
394
|
+
))
|
|
395
|
+
.await;
|
|
396
|
+
}
|
|
397
|
+
|
|
343
398
|
socket
|
|
344
399
|
.send(Message::Text(
|
|
345
400
|
json!({ "id": 1, "method": method, "params": params })
|
|
@@ -19,13 +19,13 @@ pub async fn send_chat_with_fallback(
|
|
|
19
19
|
Err(_) => {}
|
|
20
20
|
}
|
|
21
21
|
for provider in config.available_providers() {
|
|
22
|
-
if provider == config.ai_provider.as_str() {
|
|
22
|
+
if provider.as_str() == config.ai_provider.as_str() {
|
|
23
23
|
continue;
|
|
24
24
|
}
|
|
25
|
-
let alt = config_for_provider(config, provider);
|
|
25
|
+
let alt = config_for_provider(config, &provider);
|
|
26
26
|
if let Ok(mut r) = send_chat(&alt, request).await {
|
|
27
27
|
r.fallback_provider = Some(config.ai_provider.clone());
|
|
28
|
-
return Ok((r, Some(provider
|
|
28
|
+
return Ok((r, Some(provider)));
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
// all fallbacks failed — retry primary to surface original error
|
|
@@ -50,13 +50,13 @@ where
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
for provider in config.available_providers() {
|
|
53
|
-
if provider == config.ai_provider.as_str() {
|
|
53
|
+
if provider.as_str() == config.ai_provider.as_str() {
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
let alt = config_for_provider(config, provider);
|
|
56
|
+
let alt = config_for_provider(config, &provider);
|
|
57
57
|
if let Ok(mut r) = stream_chat(&alt, request, &mut on_chunk).await {
|
|
58
58
|
r.fallback_provider = Some(config.ai_provider.clone());
|
|
59
|
-
return Ok((r, Some(provider
|
|
59
|
+
return Ok((r, Some(provider)));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
stream_chat(config, request, &mut on_chunk)
|
|
@@ -99,6 +99,8 @@ pub struct ChatRequest {
|
|
|
99
99
|
pub document_attachment: Option<DocumentAttachment>,
|
|
100
100
|
#[serde(default)]
|
|
101
101
|
pub workspace_path: Option<String>,
|
|
102
|
+
#[serde(default)]
|
|
103
|
+
pub agent_id: Option<String>,
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
#[derive(Debug, Clone, Deserialize)]
|
|
@@ -228,6 +230,7 @@ pub async fn send_chat(
|
|
|
228
230
|
"ollama" => call_ollama(&client, config, request).await?,
|
|
229
231
|
"anthropic" => call_anthropic(&client, config, request).await?,
|
|
230
232
|
"huggingface" => call_huggingface(&client, config, request).await?,
|
|
233
|
+
p if p.starts_with("custom:") => call_custom_provider(&client, config, request).await?,
|
|
231
234
|
other => return Err(ChatError::UnsupportedProvider(other.into())),
|
|
232
235
|
};
|
|
233
236
|
Ok(ChatResponse {
|
|
@@ -257,6 +260,9 @@ where
|
|
|
257
260
|
"ollama" => stream_ollama(&client, config, request, &mut on_chunk).await?,
|
|
258
261
|
"anthropic" => stream_anthropic(&client, config, request, &mut on_chunk).await?,
|
|
259
262
|
"huggingface" => stream_huggingface(&client, config, request, &mut on_chunk).await?,
|
|
263
|
+
p if p.starts_with("custom:") => {
|
|
264
|
+
stream_custom_provider(&client, config, request, &mut on_chunk).await?
|
|
265
|
+
}
|
|
260
266
|
other => return Err(ChatError::UnsupportedProvider(other.into())),
|
|
261
267
|
};
|
|
262
268
|
Ok(ChatResponse {
|
|
@@ -706,6 +712,109 @@ fn required_key(provider: &str, key: &str) -> Result<(), ChatError> {
|
|
|
706
712
|
}
|
|
707
713
|
}
|
|
708
714
|
|
|
715
|
+
/// Resolve which model to use for the current custom provider.
|
|
716
|
+
///
|
|
717
|
+
/// Checks `config.extra["customModelSelections"]` first (set by the UI when the
|
|
718
|
+
/// user picks a specific model from the multi-model dropdown), then falls back to
|
|
719
|
+
/// the first model defined in the `CustomProvider`.
|
|
720
|
+
fn resolve_custom_model(config: &crate::MintConfig) -> String {
|
|
721
|
+
let provider_id = config
|
|
722
|
+
.ai_provider
|
|
723
|
+
.strip_prefix("custom:")
|
|
724
|
+
.unwrap_or(&config.ai_provider);
|
|
725
|
+
|
|
726
|
+
// Check if a per-provider model selection was persisted in extra config.
|
|
727
|
+
if let Some(selections) = config
|
|
728
|
+
.extra
|
|
729
|
+
.get("customModelSelections")
|
|
730
|
+
.and_then(|v| v.as_object())
|
|
731
|
+
&& let Some(model) = selections.get(provider_id).and_then(|v| v.as_str())
|
|
732
|
+
&& !model.is_empty()
|
|
733
|
+
{
|
|
734
|
+
return model.to_owned();
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Fall back to the first model in the provider definition.
|
|
738
|
+
config
|
|
739
|
+
.resolve_custom_provider(&config.ai_provider)
|
|
740
|
+
.and_then(|cp| cp.models.first())
|
|
741
|
+
.map(|m| m.model_id.clone())
|
|
742
|
+
.unwrap_or_default()
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async fn call_custom_provider(
|
|
746
|
+
client: &Client,
|
|
747
|
+
config: &crate::MintConfig,
|
|
748
|
+
request: &ChatRequest,
|
|
749
|
+
) -> Result<(String, String), ChatError> {
|
|
750
|
+
let provider = config
|
|
751
|
+
.resolve_custom_provider(&config.ai_provider)
|
|
752
|
+
.ok_or_else(|| ChatError::UnsupportedProvider(config.ai_provider.clone()))?;
|
|
753
|
+
|
|
754
|
+
let base_url = provider.base_url.trim_end_matches('/');
|
|
755
|
+
let model = resolve_custom_model(config);
|
|
756
|
+
let payload = openai_chat_payload(&model, request, false);
|
|
757
|
+
|
|
758
|
+
let mut builder = client
|
|
759
|
+
.post(format!("{base_url}/chat/completions"))
|
|
760
|
+
.json(&payload);
|
|
761
|
+
|
|
762
|
+
if !provider.api_key.is_empty() {
|
|
763
|
+
builder = builder.bearer_auth(&provider.api_key);
|
|
764
|
+
}
|
|
765
|
+
for h in &provider.headers {
|
|
766
|
+
if !h.name.is_empty() {
|
|
767
|
+
builder = builder.header(h.name.as_str(), h.value.as_str());
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
let response: serde_json::Value = builder.send().await?.error_for_status()?.json().await?;
|
|
772
|
+
|
|
773
|
+
Ok((
|
|
774
|
+
model,
|
|
775
|
+
response["choices"][0]["message"]["content"]
|
|
776
|
+
.as_str()
|
|
777
|
+
.ok_or(ChatError::MissingResponseText)?
|
|
778
|
+
.into(),
|
|
779
|
+
))
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
async fn stream_custom_provider<F>(
|
|
783
|
+
client: &Client,
|
|
784
|
+
config: &crate::MintConfig,
|
|
785
|
+
request: &ChatRequest,
|
|
786
|
+
on_chunk: &mut F,
|
|
787
|
+
) -> Result<(String, String), ChatError>
|
|
788
|
+
where
|
|
789
|
+
F: FnMut(String),
|
|
790
|
+
{
|
|
791
|
+
let provider = config
|
|
792
|
+
.resolve_custom_provider(&config.ai_provider)
|
|
793
|
+
.ok_or_else(|| ChatError::UnsupportedProvider(config.ai_provider.clone()))?;
|
|
794
|
+
|
|
795
|
+
let base_url = provider.base_url.trim_end_matches('/');
|
|
796
|
+
let model = resolve_custom_model(config);
|
|
797
|
+
let payload = openai_chat_payload(&model, request, true);
|
|
798
|
+
|
|
799
|
+
let mut builder = client
|
|
800
|
+
.post(format!("{base_url}/chat/completions"))
|
|
801
|
+
.json(&payload);
|
|
802
|
+
|
|
803
|
+
if !provider.api_key.is_empty() {
|
|
804
|
+
builder = builder.bearer_auth(&provider.api_key);
|
|
805
|
+
}
|
|
806
|
+
for h in &provider.headers {
|
|
807
|
+
if !h.name.is_empty() {
|
|
808
|
+
builder = builder.header(h.name.as_str(), h.value.as_str());
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
let response = builder.send().await?.error_for_status()?;
|
|
813
|
+
collect_stream(response, StreamFormat::OpenAi, on_chunk)
|
|
814
|
+
.await
|
|
815
|
+
.map(|text| (model, text))
|
|
816
|
+
}
|
|
817
|
+
|
|
709
818
|
fn provider_key(configured: &str, environment_variable: &str) -> String {
|
|
710
819
|
if configured.trim().is_empty() {
|
|
711
820
|
std::env::var(environment_variable).unwrap_or_default()
|
|
@@ -718,16 +827,24 @@ fn require_supported_attachments(provider: &str, request: &ChatRequest) -> Resul
|
|
|
718
827
|
let has_image = request
|
|
719
828
|
.image_data_uri
|
|
720
829
|
.as_ref()
|
|
721
|
-
.
|
|
830
|
+
.is_some_and(|s| !s.trim().is_empty());
|
|
722
831
|
let has_audio = request
|
|
723
832
|
.audio_data_uri
|
|
724
833
|
.as_ref()
|
|
725
|
-
.
|
|
834
|
+
.is_some_and(|s| !s.trim().is_empty());
|
|
726
835
|
|
|
727
836
|
match provider {
|
|
728
837
|
"gemini" => Ok(()),
|
|
729
838
|
"ollama" if has_audio => Err(ChatError::UnsupportedAttachments(provider.into())),
|
|
730
839
|
"ollama" => Ok(()),
|
|
840
|
+
// Custom providers are text-only for now; reject any multimodal content.
|
|
841
|
+
p if p.starts_with("custom:") => {
|
|
842
|
+
if has_image || has_audio {
|
|
843
|
+
Err(ChatError::UnsupportedAttachments(provider.into()))
|
|
844
|
+
} else {
|
|
845
|
+
Ok(())
|
|
846
|
+
}
|
|
847
|
+
}
|
|
731
848
|
_ if has_image || has_audio => Err(ChatError::UnsupportedAttachments(provider.into())),
|
|
732
849
|
_ => Ok(()),
|
|
733
850
|
}
|
|
@@ -965,6 +1082,7 @@ mod tests {
|
|
|
965
1082
|
audio_data_uri: None,
|
|
966
1083
|
document_attachment: None,
|
|
967
1084
|
workspace_path: None,
|
|
1085
|
+
agent_id: None,
|
|
968
1086
|
})
|
|
969
1087
|
.unwrap();
|
|
970
1088
|
assert_eq!(parts.len(), 3);
|
|
@@ -536,18 +536,17 @@ pub async fn fetch_github_repo_summary(owner: &str, repo: &str) -> Result<String
|
|
|
536
536
|
.await
|
|
537
537
|
.map_err(|e| e.to_string())?;
|
|
538
538
|
let mut file_tree = String::from("Unavailable");
|
|
539
|
-
if contents_resp.status().is_success()
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
file_tree = files.join("\n");
|
|
549
|
-
}
|
|
539
|
+
if contents_resp.status().is_success()
|
|
540
|
+
&& let Ok(contents_info) = contents_resp.json::<serde_json::Value>().await
|
|
541
|
+
&& let Some(arr) = contents_info.as_array()
|
|
542
|
+
{
|
|
543
|
+
let mut files = Vec::new();
|
|
544
|
+
for item in arr {
|
|
545
|
+
let name = item["name"].as_str().unwrap_or("");
|
|
546
|
+
let r#type = item["type"].as_str().unwrap_or("");
|
|
547
|
+
files.push(format!("- {} ({})", name, r#type));
|
|
550
548
|
}
|
|
549
|
+
file_tree = files.join("\n");
|
|
551
550
|
}
|
|
552
551
|
|
|
553
552
|
// 3. Fetch README.md
|
|
@@ -558,15 +557,14 @@ pub async fn fetch_github_repo_summary(owner: &str, repo: &str) -> Result<String
|
|
|
558
557
|
.await
|
|
559
558
|
.map_err(|e| e.to_string())?;
|
|
560
559
|
let mut readme_text = String::from("No README available.");
|
|
561
|
-
if readme_resp.status().is_success()
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
560
|
+
if readme_resp.status().is_success()
|
|
561
|
+
&& let Ok(readme_info) = readme_resp.json::<serde_json::Value>().await
|
|
562
|
+
&& let Some(content_b64) = readme_info["content"].as_str()
|
|
563
|
+
{
|
|
564
|
+
let cleaned_b64 = content_b64.replace(['\n', '\r'], "");
|
|
565
|
+
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
|
566
|
+
if let Ok(decoded_bytes) = STANDARD.decode(cleaned_b64) {
|
|
567
|
+
readme_text = String::from_utf8_lossy(&decoded_bytes).to_string();
|
|
570
568
|
}
|
|
571
569
|
}
|
|
572
570
|
|