@pheem49/mint 1.5.5 → 1.6.1
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/.codex +0 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/release.yml +79 -0
- package/Cargo.lock +5792 -0
- package/Cargo.toml +32 -0
- package/README.md +387 -353
- package/assets/icon.png +0 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +23 -0
- package/crates/mint-cli/src/agent.rs +851 -0
- package/crates/mint-cli/src/gmail.rs +216 -0
- package/crates/mint-cli/src/image.rs +142 -0
- package/crates/mint-cli/src/main.rs +2837 -0
- package/crates/mint-cli/src/mcp.rs +63 -0
- package/crates/mint-cli/src/onboard.rs +1149 -0
- package/crates/mint-cli/src/setup.rs +390 -0
- package/crates/mint-cli/src/skills.rs +8 -0
- package/crates/mint-cli/src/updater.rs +279 -0
- package/crates/mint-core/Cargo.toml +22 -0
- package/crates/mint-core/src/agent_loop.rs +94 -0
- package/crates/mint-core/src/api_server.rs +991 -0
- package/crates/mint-core/src/channels.rs +248 -0
- package/crates/mint-core/src/chat.rs +895 -0
- package/crates/mint-core/src/code_tools.rs +729 -0
- package/crates/mint-core/src/config.rs +368 -0
- package/crates/mint-core/src/files.rs +159 -0
- package/crates/mint-core/src/knowledge.rs +541 -0
- package/crates/mint-core/src/lib.rs +84 -0
- package/crates/mint-core/src/mcp.rs +273 -0
- package/crates/mint-core/src/memory.rs +673 -0
- package/crates/mint-core/src/orchestration.rs +2157 -0
- package/crates/mint-core/src/pictures.rs +314 -0
- package/crates/mint-core/src/plugins.rs +727 -0
- package/crates/mint-core/src/safety.rs +416 -0
- package/crates/mint-core/src/semantic.rs +254 -0
- package/crates/mint-core/src/shell.rs +317 -0
- package/crates/mint-core/src/skills.rs +71 -0
- package/crates/mint-core/src/symbols.rs +157 -0
- package/crates/mint-core/src/tasks.rs +308 -0
- package/crates/mint-core/src/tts.rs +92 -0
- package/crates/mint-core/src/weather.rs +93 -0
- package/crates/mint-core/src/web_search.rs +200 -0
- package/crates/mint-core/src/workflows.rs +81 -0
- package/crates/mint-core/tests/mcp_stdio.rs +45 -0
- package/crates/mint-core/tests/memory_persistence.rs +172 -0
- package/crates/mint-core/tests/pictures_storage.rs +14 -0
- package/crates/mint-core/tests/task_lifecycle.rs +87 -0
- package/package.json +35 -99
- package/src/bin/index.js +16 -0
- package/src/renderer/index-web.html +17 -0
- package/src/renderer/index.html +17 -0
- package/src/renderer/public/Live2DCubismCore.js +9 -0
- package/src/renderer/public/assets/icon.png +0 -0
- package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
- package/src/renderer/src/App.tsx +33 -0
- package/src/renderer/src/calculator.ts +47 -0
- package/src/renderer/src/components/ChatPanel.tsx +1598 -0
- package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
- package/src/renderer/src/components/Live2DStage.tsx +374 -0
- package/src/renderer/src/components/MintDashboard.tsx +950 -0
- package/src/renderer/src/components/ModelPanel.tsx +154 -0
- package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
- package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
- package/src/renderer/src/components/ScreenPicker.tsx +579 -0
- package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
- package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
- package/src/renderer/src/components/WidgetWindow.tsx +36 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
- package/src/{UI → renderer/src/css}/settings.css +69 -16
- package/src/renderer/src/css/spotlight.css +113 -0
- package/src/renderer/src/css/styles.css +3722 -0
- package/src/renderer/src/css/widget.css +185 -0
- package/src/renderer/src/env.d.ts +116 -0
- package/src/renderer/src/index.css +379 -0
- package/src/renderer/src/main.tsx +13 -0
- package/src/renderer/src/tauri.ts +996 -0
- package/src/renderer/src-web/App.tsx +25 -0
- package/src/renderer/src-web/calculator.ts +47 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
- package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
- package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
- package/src/renderer/src-web/css/settings.css +1100 -0
- package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
- package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
- package/src/{UI → renderer/src-web/css}/widget.css +2 -2
- package/src/renderer/src-web/env.d.ts +107 -0
- package/src/renderer/src-web/index.css +379 -0
- package/src/renderer/src-web/main.tsx +13 -0
- package/src/renderer/src-web/tauri.ts +983 -0
- package/tsconfig.json +30 -0
- package/vite.config.ts +33 -0
- package/vite.config.web.ts +51 -0
- package/GUIDE_TH.md +0 -125
- package/assets/Agent_Mint.png +0 -0
- package/assets/CLI_Screen.png +0 -0
- package/assets/Settings.png +0 -0
- package/benchmark_ai.js +0 -71
- package/install.ps1 +0 -64
- package/install.sh +0 -54
- package/main.js +0 -139
- package/mint-cli-logic.js +0 -3
- package/mint-cli.js +0 -410
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
- package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
- package/preload-picker.js +0 -11
- package/preload-settings.js +0 -11
- package/preload.js +0 -41
- package/scripts/install_linux_desktop_entry.js +0 -48
- package/src/AI_Brain/Gemini_API.js +0 -813
- package/src/AI_Brain/agent_orchestrator.js +0 -73
- package/src/AI_Brain/autonomous_brain.js +0 -179
- package/src/AI_Brain/behavior_memory.js +0 -135
- package/src/AI_Brain/headless_agent.js +0 -143
- package/src/AI_Brain/knowledge_base.js +0 -349
- package/src/AI_Brain/memory_store.js +0 -662
- package/src/AI_Brain/proactive_engine.js +0 -172
- package/src/AI_Brain/provider_adapter.js +0 -365
- package/src/Automation_Layer/browser_automation.js +0 -149
- package/src/Automation_Layer/file_operations.js +0 -286
- package/src/Automation_Layer/open_app.js +0 -85
- package/src/Automation_Layer/open_website.js +0 -38
- package/src/CLI/approval_handler.js +0 -47
- package/src/CLI/chat_router.js +0 -247
- package/src/CLI/chat_ui.js +0 -1159
- package/src/CLI/cli_colors.js +0 -115
- package/src/CLI/cli_formatters.js +0 -94
- package/src/CLI/code_agent.js +0 -1667
- package/src/CLI/code_session_memory.js +0 -62
- package/src/CLI/gmail_auth.js +0 -210
- package/src/CLI/image_input.js +0 -90
- package/src/CLI/intent_detectors.js +0 -181
- package/src/CLI/interactive_chat.js +0 -658
- package/src/CLI/list_features.js +0 -64
- package/src/CLI/onboarding.js +0 -416
- package/src/CLI/repo_summarizer.js +0 -282
- package/src/CLI/semantic_code_search.js +0 -312
- package/src/CLI/skill_manager.js +0 -41
- package/src/CLI/slash_command_handler.js +0 -418
- package/src/CLI/symbol_indexer.js +0 -231
- package/src/CLI/updater.js +0 -230
- package/src/CLI/workspace_manager.js +0 -90
- package/src/Channels/brave_search_bridge.js +0 -35
- package/src/Channels/discord_bridge.js +0 -66
- package/src/Channels/google_search_bridge.js +0 -38
- package/src/Channels/line_bridge.js +0 -60
- package/src/Channels/slack_bridge.js +0 -48
- package/src/Channels/telegram_bridge.js +0 -41
- package/src/Channels/whatsapp_bridge.js +0 -57
- package/src/Command_Parser/parser.js +0 -45
- package/src/Plugins/dev_tools.js +0 -41
- package/src/Plugins/discord.js +0 -20
- package/src/Plugins/docker.js +0 -47
- package/src/Plugins/gmail.js +0 -251
- package/src/Plugins/google_calendar.js +0 -252
- package/src/Plugins/mcp_manager.js +0 -95
- package/src/Plugins/notion.js +0 -256
- package/src/Plugins/obsidian.js +0 -54
- package/src/Plugins/plugin_manager.js +0 -81
- package/src/Plugins/spotify.js +0 -173
- package/src/Plugins/system_metrics.js +0 -31
- package/src/Plugins/system_monitor.js +0 -72
- package/src/System/action_executor.js +0 -178
- package/src/System/bridge_manager.js +0 -76
- package/src/System/chat_history_manager.js +0 -83
- package/src/System/config_manager.js +0 -194
- package/src/System/custom_workflows.js +0 -163
- package/src/System/daemon_manager.js +0 -67
- package/src/System/google_tts_urls.js +0 -51
- package/src/System/granular_automation.js +0 -157
- package/src/System/ipc_handlers.js +0 -332
- package/src/System/notifications.js +0 -23
- package/src/System/optional_require.js +0 -23
- package/src/System/picture_store.js +0 -109
- package/src/System/proactive_loop.js +0 -153
- package/src/System/safety_manager.js +0 -273
- package/src/System/sandbox_runner.js +0 -182
- package/src/System/screen_capture.js +0 -175
- package/src/System/smart_context.js +0 -227
- package/src/System/system_automation.js +0 -162
- package/src/System/system_events.js +0 -79
- package/src/System/system_info.js +0 -125
- package/src/System/task_manager.js +0 -222
- package/src/System/tool_registry.js +0 -293
- package/src/System/window_manager.js +0 -220
- package/src/UI/floating.css +0 -80
- package/src/UI/floating.html +0 -17
- package/src/UI/floating.js +0 -67
- package/src/UI/live2d_manager.js +0 -600
- package/src/UI/preload-floating.js +0 -7
- package/src/UI/preload-spotlight.js +0 -11
- package/src/UI/preload-widget.js +0 -5
- package/src/UI/proactive-glow.html +0 -42
- package/src/UI/renderer.js +0 -2127
- package/src/UI/screenPicker.html +0 -214
- package/src/UI/screenPicker.js +0 -262
- package/src/UI/settings.html +0 -577
- package/src/UI/settings.js +0 -770
- package/src/UI/spotlight.html +0 -23
- package/src/UI/spotlight.js +0 -185
- package/src/UI/widget.html +0 -29
- package/src/UI/widget.js +0 -10
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
collections::hash_map::DefaultHasher,
|
|
3
|
+
fs,
|
|
4
|
+
hash::{Hash, Hasher},
|
|
5
|
+
path::{Path, PathBuf},
|
|
6
|
+
process::Command,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
use quick_xml::{Reader, events::Event};
|
|
10
|
+
use rusqlite::{Connection, params, types::Type};
|
|
11
|
+
use serde::Serialize;
|
|
12
|
+
use thiserror::Error;
|
|
13
|
+
|
|
14
|
+
use crate::{Capability, MintConfig, SafetyError, assert_path_capability, memory_path};
|
|
15
|
+
|
|
16
|
+
const MAX_FILE_BYTES: u64 = 10 * 1024 * 1024;
|
|
17
|
+
const CHUNK_CHARACTERS: usize = 1000;
|
|
18
|
+
const CHUNK_OVERLAP: usize = 200;
|
|
19
|
+
|
|
20
|
+
#[derive(Debug, Error)]
|
|
21
|
+
pub enum KnowledgeError {
|
|
22
|
+
#[error(transparent)]
|
|
23
|
+
Safety(#[from] SafetyError),
|
|
24
|
+
#[error("unable to locate Mint knowledge database: {0}")]
|
|
25
|
+
DatabasePath(String),
|
|
26
|
+
#[error("database error: {0}")]
|
|
27
|
+
Database(#[from] rusqlite::Error),
|
|
28
|
+
#[error("unable to read knowledge file {path}: {source}")]
|
|
29
|
+
Read {
|
|
30
|
+
path: PathBuf,
|
|
31
|
+
source: std::io::Error,
|
|
32
|
+
},
|
|
33
|
+
#[error("knowledge file is too large (> 10 MiB): {0}")]
|
|
34
|
+
TooLarge(PathBuf),
|
|
35
|
+
#[error("native text indexing does not support this file type yet: {0}")]
|
|
36
|
+
UnsupportedFileType(PathBuf),
|
|
37
|
+
#[error("unable to extract text from {path}: {message}")]
|
|
38
|
+
Extract { path: PathBuf, message: String },
|
|
39
|
+
#[error("knowledge file does not contain readable text: {0}")]
|
|
40
|
+
Empty(PathBuf),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[derive(Debug, Clone)]
|
|
44
|
+
pub struct KnowledgeStore {
|
|
45
|
+
path: PathBuf,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[derive(Debug, Clone, Serialize)]
|
|
49
|
+
#[serde(rename_all = "camelCase")]
|
|
50
|
+
pub struct KnowledgeSource {
|
|
51
|
+
pub id: i64,
|
|
52
|
+
pub path: String,
|
|
53
|
+
pub name: String,
|
|
54
|
+
pub last_indexed: String,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#[derive(Debug, Clone, Serialize)]
|
|
58
|
+
#[serde(rename_all = "camelCase")]
|
|
59
|
+
pub struct KnowledgeHit {
|
|
60
|
+
pub source: String,
|
|
61
|
+
pub text: String,
|
|
62
|
+
pub score: f32,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl KnowledgeStore {
|
|
66
|
+
pub fn open_default() -> Result<Self, KnowledgeError> {
|
|
67
|
+
Ok(Self::open(memory_path().map_err(|error| {
|
|
68
|
+
KnowledgeError::DatabasePath(error.to_string())
|
|
69
|
+
})?))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn open(path: impl Into<PathBuf>) -> Self {
|
|
73
|
+
Self { path: path.into() }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn index_file(&self, path: &Path, config: &MintConfig) -> Result<usize, KnowledgeError> {
|
|
77
|
+
let path = assert_path_capability(path, Capability::Read, config)?;
|
|
78
|
+
let metadata = fs::metadata(&path).map_err(|source| KnowledgeError::Read {
|
|
79
|
+
path: path.clone(),
|
|
80
|
+
source,
|
|
81
|
+
})?;
|
|
82
|
+
if metadata.len() > MAX_FILE_BYTES {
|
|
83
|
+
return Err(KnowledgeError::TooLarge(path));
|
|
84
|
+
}
|
|
85
|
+
if !supported(&path) {
|
|
86
|
+
return Err(KnowledgeError::UnsupportedFileType(path));
|
|
87
|
+
}
|
|
88
|
+
let content = extract_text(&path)?;
|
|
89
|
+
if content.trim().is_empty() {
|
|
90
|
+
return Err(KnowledgeError::Empty(path));
|
|
91
|
+
}
|
|
92
|
+
let chunks = chunks(&content);
|
|
93
|
+
let connection = self.connection()?;
|
|
94
|
+
let path_text = path.to_string_lossy().to_string();
|
|
95
|
+
let name = path
|
|
96
|
+
.file_name()
|
|
97
|
+
.map(|name| name.to_string_lossy().to_string())
|
|
98
|
+
.unwrap_or_else(|| path_text.clone());
|
|
99
|
+
let hash = content_hash(&content);
|
|
100
|
+
connection.execute(
|
|
101
|
+
"INSERT INTO sources (path, name, hash, last_indexed)
|
|
102
|
+
VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP)
|
|
103
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
104
|
+
name = excluded.name,
|
|
105
|
+
hash = excluded.hash,
|
|
106
|
+
last_indexed = CURRENT_TIMESTAMP",
|
|
107
|
+
params![path_text, name, hash],
|
|
108
|
+
)?;
|
|
109
|
+
let source_id: i64 = connection.query_row(
|
|
110
|
+
"SELECT id FROM sources WHERE path = ?1",
|
|
111
|
+
params![path_text],
|
|
112
|
+
|row| row.get(0),
|
|
113
|
+
)?;
|
|
114
|
+
connection.execute(
|
|
115
|
+
"DELETE FROM chunks WHERE source_id = ?1",
|
|
116
|
+
params![source_id],
|
|
117
|
+
)?;
|
|
118
|
+
for chunk in &chunks {
|
|
119
|
+
connection.execute(
|
|
120
|
+
"INSERT INTO chunks (source_id, text, embedding) VALUES (?1, ?2, ?3)",
|
|
121
|
+
params![source_id, chunk, encode_embedding(&embedding(chunk))],
|
|
122
|
+
)?;
|
|
123
|
+
}
|
|
124
|
+
Ok(chunks.len())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
pub fn list_sources(&self) -> Result<Vec<KnowledgeSource>, KnowledgeError> {
|
|
128
|
+
let connection = self.connection()?;
|
|
129
|
+
let mut statement = connection.prepare(
|
|
130
|
+
"SELECT id, path, name, last_indexed FROM sources ORDER BY last_indexed DESC, id DESC",
|
|
131
|
+
)?;
|
|
132
|
+
statement
|
|
133
|
+
.query_map([], |row| {
|
|
134
|
+
Ok(KnowledgeSource {
|
|
135
|
+
id: row.get(0)?,
|
|
136
|
+
path: row.get(1)?,
|
|
137
|
+
name: row.get(2)?,
|
|
138
|
+
last_indexed: row.get(3)?,
|
|
139
|
+
})
|
|
140
|
+
})?
|
|
141
|
+
.collect::<Result<Vec<_>, _>>()
|
|
142
|
+
.map_err(Into::into)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
pub fn search(&self, query: &str, limit: usize) -> Result<Vec<KnowledgeHit>, KnowledgeError> {
|
|
146
|
+
let connection = self.connection()?;
|
|
147
|
+
let query_embedding = embedding(query);
|
|
148
|
+
let mut statement = connection.prepare(
|
|
149
|
+
"SELECT sources.name, chunks.text, chunks.embedding
|
|
150
|
+
FROM chunks JOIN sources ON sources.id = chunks.source_id",
|
|
151
|
+
)?;
|
|
152
|
+
let mut hits = statement
|
|
153
|
+
.query_map([], |row| {
|
|
154
|
+
let text: String = row.get(1)?;
|
|
155
|
+
let vector = row
|
|
156
|
+
.get::<_, Option<Vec<u8>>>(2)?
|
|
157
|
+
.map(|raw| {
|
|
158
|
+
decode_embedding(&raw).map_err(|error| {
|
|
159
|
+
rusqlite::Error::FromSqlConversionFailure(2, Type::Blob, error.into())
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
.transpose()?
|
|
163
|
+
.unwrap_or_else(|| embedding(&text));
|
|
164
|
+
Ok(KnowledgeHit {
|
|
165
|
+
source: row.get(0)?,
|
|
166
|
+
text,
|
|
167
|
+
score: cosine_similarity(&query_embedding, &vector),
|
|
168
|
+
})
|
|
169
|
+
})?
|
|
170
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
171
|
+
hits.sort_by(|left, right| right.score.total_cmp(&left.score));
|
|
172
|
+
hits.truncate(limit);
|
|
173
|
+
Ok(hits)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fn connection(&self) -> Result<Connection, KnowledgeError> {
|
|
177
|
+
if let Some(directory) = self.path.parent() {
|
|
178
|
+
fs::create_dir_all(directory).map_err(|source| KnowledgeError::Read {
|
|
179
|
+
path: directory.to_path_buf(),
|
|
180
|
+
source,
|
|
181
|
+
})?;
|
|
182
|
+
}
|
|
183
|
+
let connection = Connection::open(&self.path)?;
|
|
184
|
+
|
|
185
|
+
static INITIALIZED_DATABASES: std::sync::LazyLock<
|
|
186
|
+
std::sync::Mutex<std::collections::HashSet<PathBuf>>,
|
|
187
|
+
> = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new()));
|
|
188
|
+
|
|
189
|
+
let needs_init = {
|
|
190
|
+
let mut set = INITIALIZED_DATABASES.lock().unwrap();
|
|
191
|
+
set.insert(self.path.clone())
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if needs_init {
|
|
195
|
+
connection.execute_batch(
|
|
196
|
+
"PRAGMA journal_mode = WAL;
|
|
197
|
+
PRAGMA synchronous = NORMAL;
|
|
198
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
199
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
200
|
+
path TEXT UNIQUE,
|
|
201
|
+
name TEXT,
|
|
202
|
+
hash TEXT,
|
|
203
|
+
last_indexed DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
204
|
+
);
|
|
205
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
206
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
207
|
+
source_id INTEGER,
|
|
208
|
+
text TEXT,
|
|
209
|
+
embedding BLOB,
|
|
210
|
+
FOREIGN KEY(source_id) REFERENCES sources(id) ON DELETE CASCADE
|
|
211
|
+
);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_source ON chunks(source_id);",
|
|
213
|
+
)?;
|
|
214
|
+
}
|
|
215
|
+
Ok(connection)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
pub fn extract_document_text(path: &Path, config: &MintConfig) -> Result<String, KnowledgeError> {
|
|
220
|
+
let path = assert_path_capability(path, Capability::Read, config)?;
|
|
221
|
+
let metadata = fs::metadata(&path).map_err(|source| KnowledgeError::Read {
|
|
222
|
+
path: path.clone(),
|
|
223
|
+
source,
|
|
224
|
+
})?;
|
|
225
|
+
if metadata.len() > MAX_FILE_BYTES {
|
|
226
|
+
return Err(KnowledgeError::TooLarge(path));
|
|
227
|
+
}
|
|
228
|
+
if !supported(&path) {
|
|
229
|
+
return Err(KnowledgeError::UnsupportedFileType(path));
|
|
230
|
+
}
|
|
231
|
+
let content = extract_text(&path)?;
|
|
232
|
+
if content.trim().is_empty() {
|
|
233
|
+
return Err(KnowledgeError::Empty(path));
|
|
234
|
+
}
|
|
235
|
+
Ok(content)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fn supported(path: &Path) -> bool {
|
|
239
|
+
path.extension()
|
|
240
|
+
.and_then(|extension| extension.to_str())
|
|
241
|
+
.is_some_and(|extension| {
|
|
242
|
+
matches!(
|
|
243
|
+
extension.to_ascii_lowercase().as_str(),
|
|
244
|
+
"txt"
|
|
245
|
+
| "md"
|
|
246
|
+
| "pdf"
|
|
247
|
+
| "docx"
|
|
248
|
+
| "xlsx"
|
|
249
|
+
| "json"
|
|
250
|
+
| "toml"
|
|
251
|
+
| "yaml"
|
|
252
|
+
| "yml"
|
|
253
|
+
| "rs"
|
|
254
|
+
| "ts"
|
|
255
|
+
| "tsx"
|
|
256
|
+
| "js"
|
|
257
|
+
| "jsx"
|
|
258
|
+
| "py"
|
|
259
|
+
| "html"
|
|
260
|
+
| "css"
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn extract_text(path: &Path) -> Result<String, KnowledgeError> {
|
|
266
|
+
match extension(path).as_str() {
|
|
267
|
+
"pdf" => command_text(path, "pdftotext", &["-layout"], Some("-")),
|
|
268
|
+
"docx" => {
|
|
269
|
+
let xml = command_text(path, "unzip", &["-p"], Some("word/document.xml"))?;
|
|
270
|
+
xml_text(&xml, &["w:t", "w:tab", "w:br", "w:p"])
|
|
271
|
+
}
|
|
272
|
+
"xlsx" => extract_xlsx(path),
|
|
273
|
+
_ => fs::read_to_string(path).map_err(|source| KnowledgeError::Read {
|
|
274
|
+
path: path.to_path_buf(),
|
|
275
|
+
source,
|
|
276
|
+
}),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
fn extract_xlsx(path: &Path) -> Result<String, KnowledgeError> {
|
|
281
|
+
let files = command_text(path, "unzip", &["-Z1"], None)?;
|
|
282
|
+
let mut text = String::new();
|
|
283
|
+
for entry in files.lines().filter(|entry| {
|
|
284
|
+
*entry == "xl/sharedStrings.xml"
|
|
285
|
+
|| (entry.starts_with("xl/worksheets/") && entry.ends_with(".xml"))
|
|
286
|
+
}) {
|
|
287
|
+
let xml = command_text(path, "unzip", &["-p"], Some(entry))?;
|
|
288
|
+
let extracted = xml_text(&xml, &["t", "v", "row"])?;
|
|
289
|
+
if !extracted.trim().is_empty() {
|
|
290
|
+
text.push_str(&format!("\nSheet XML: {entry}\n{extracted}\n"));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
Ok(text)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fn command_text(
|
|
297
|
+
path: &Path,
|
|
298
|
+
program: &'static str,
|
|
299
|
+
prefix: &[&str],
|
|
300
|
+
suffix: Option<&str>,
|
|
301
|
+
) -> Result<String, KnowledgeError> {
|
|
302
|
+
let mut command = Command::new(program);
|
|
303
|
+
command.args(prefix).arg(path);
|
|
304
|
+
if let Some(suffix) = suffix {
|
|
305
|
+
command.arg(suffix);
|
|
306
|
+
}
|
|
307
|
+
let output = command.output().map_err(|error| KnowledgeError::Extract {
|
|
308
|
+
path: path.to_path_buf(),
|
|
309
|
+
message: format!("unable to run {program}: {error}"),
|
|
310
|
+
})?;
|
|
311
|
+
if !output.status.success() {
|
|
312
|
+
return Err(KnowledgeError::Extract {
|
|
313
|
+
path: path.to_path_buf(),
|
|
314
|
+
message: format!(
|
|
315
|
+
"{program} exited with {}: {}",
|
|
316
|
+
output.status,
|
|
317
|
+
String::from_utf8_lossy(&output.stderr).trim()
|
|
318
|
+
),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
fn xml_text(xml: &str, text_elements: &[&str]) -> Result<String, KnowledgeError> {
|
|
325
|
+
let mut reader = Reader::from_str(xml);
|
|
326
|
+
let mut active = false;
|
|
327
|
+
let mut output = String::new();
|
|
328
|
+
loop {
|
|
329
|
+
match reader.read_event() {
|
|
330
|
+
Ok(Event::Start(element)) => {
|
|
331
|
+
let name = String::from_utf8_lossy(element.name().as_ref()).into_owned();
|
|
332
|
+
active = text_elements.contains(&name.as_str());
|
|
333
|
+
if matches!(name.as_str(), "w:p" | "row") {
|
|
334
|
+
output.push('\n');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
Ok(Event::Empty(element)) => {
|
|
338
|
+
let name = String::from_utf8_lossy(element.name().as_ref()).into_owned();
|
|
339
|
+
if matches!(name.as_str(), "w:tab") {
|
|
340
|
+
output.push('\t');
|
|
341
|
+
} else if matches!(name.as_str(), "w:br") {
|
|
342
|
+
output.push('\n');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
Ok(Event::Text(text)) if active => {
|
|
346
|
+
output.push_str(&text.decode().map_err(|error| KnowledgeError::Extract {
|
|
347
|
+
path: PathBuf::from("<xml>"),
|
|
348
|
+
message: error.to_string(),
|
|
349
|
+
})?);
|
|
350
|
+
output.push(' ');
|
|
351
|
+
}
|
|
352
|
+
Ok(Event::End(_)) => active = false,
|
|
353
|
+
Ok(Event::Eof) => break,
|
|
354
|
+
Err(error) => {
|
|
355
|
+
return Err(KnowledgeError::Extract {
|
|
356
|
+
path: PathBuf::from("<xml>"),
|
|
357
|
+
message: error.to_string(),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
_ => {}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
Ok(output)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
fn extension(path: &Path) -> String {
|
|
367
|
+
path.extension()
|
|
368
|
+
.and_then(|extension| extension.to_str())
|
|
369
|
+
.unwrap_or_default()
|
|
370
|
+
.to_ascii_lowercase()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
fn chunks(text: &str) -> Vec<String> {
|
|
374
|
+
let characters = text.chars().collect::<Vec<_>>();
|
|
375
|
+
let mut chunks = Vec::new();
|
|
376
|
+
let mut start = 0;
|
|
377
|
+
while start < characters.len() {
|
|
378
|
+
let end = (start + CHUNK_CHARACTERS).min(characters.len());
|
|
379
|
+
chunks.push(characters[start..end].iter().collect());
|
|
380
|
+
if end == characters.len() {
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
start = end.saturating_sub(CHUNK_OVERLAP);
|
|
384
|
+
}
|
|
385
|
+
chunks
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
fn content_hash(content: &str) -> String {
|
|
389
|
+
let mut hasher = DefaultHasher::new();
|
|
390
|
+
content.hash(&mut hasher);
|
|
391
|
+
format!("{:016x}", hasher.finish())
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const EMBEDDING_DIMENSIONS: usize = 256;
|
|
395
|
+
|
|
396
|
+
fn embedding(text: &str) -> Vec<f32> {
|
|
397
|
+
let mut vector = vec![0.0; EMBEDDING_DIMENSIONS];
|
|
398
|
+
for token in text
|
|
399
|
+
.split(|character: char| !character.is_alphanumeric())
|
|
400
|
+
.filter(|token| !token.is_empty())
|
|
401
|
+
{
|
|
402
|
+
let mut hasher = DefaultHasher::new();
|
|
403
|
+
let has_upper = token.chars().any(|c| c.is_uppercase());
|
|
404
|
+
if has_upper {
|
|
405
|
+
use std::cell::RefCell;
|
|
406
|
+
thread_local! {
|
|
407
|
+
static LOWERCASE_BUF: RefCell<String> = RefCell::new(String::with_capacity(64));
|
|
408
|
+
}
|
|
409
|
+
LOWERCASE_BUF.with(|buf| {
|
|
410
|
+
let mut buf = buf.borrow_mut();
|
|
411
|
+
buf.clear();
|
|
412
|
+
for c in token.chars() {
|
|
413
|
+
for lc in c.to_lowercase() {
|
|
414
|
+
buf.push(lc);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
use std::hash::Hash;
|
|
418
|
+
buf.hash(&mut hasher);
|
|
419
|
+
});
|
|
420
|
+
} else {
|
|
421
|
+
token.hash(&mut hasher);
|
|
422
|
+
}
|
|
423
|
+
let hash = hasher.finish();
|
|
424
|
+
let index = hash as usize % EMBEDDING_DIMENSIONS;
|
|
425
|
+
vector[index] += if hash & 1 == 0 { 1.0 } else { -1.0 };
|
|
426
|
+
}
|
|
427
|
+
let norm = vector.iter().map(|value| value * value).sum::<f32>().sqrt();
|
|
428
|
+
if norm > 0.0 {
|
|
429
|
+
vector.iter_mut().for_each(|value| *value /= norm);
|
|
430
|
+
}
|
|
431
|
+
vector
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
fn encode_embedding(vector: &[f32]) -> Vec<u8> {
|
|
435
|
+
vector
|
|
436
|
+
.iter()
|
|
437
|
+
.flat_map(|value| value.to_le_bytes())
|
|
438
|
+
.collect()
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
fn decode_embedding(raw: &[u8]) -> Result<Vec<f32>, &'static str> {
|
|
442
|
+
if !raw.len().is_multiple_of(4) {
|
|
443
|
+
return Err("embedding blob length is invalid");
|
|
444
|
+
}
|
|
445
|
+
Ok(raw
|
|
446
|
+
.chunks_exact(4)
|
|
447
|
+
.map(|bytes| f32::from_le_bytes(bytes.try_into().unwrap()))
|
|
448
|
+
.collect())
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
fn cosine_similarity(left: &[f32], right: &[f32]) -> f32 {
|
|
452
|
+
left.iter()
|
|
453
|
+
.zip(right)
|
|
454
|
+
.map(|(left, right)| left * right)
|
|
455
|
+
.sum()
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#[cfg(test)]
|
|
459
|
+
mod tests {
|
|
460
|
+
use super::*;
|
|
461
|
+
|
|
462
|
+
#[test]
|
|
463
|
+
fn indexes_and_searches_text_files() {
|
|
464
|
+
let root = std::env::temp_dir().join(format!("mint-knowledge-{}", std::process::id()));
|
|
465
|
+
fs::create_dir_all(&root).unwrap();
|
|
466
|
+
let file = root.join("notes.md");
|
|
467
|
+
fs::write(&file, "Mint native knowledge search").unwrap();
|
|
468
|
+
let store = KnowledgeStore::open(root.join("knowledge.sqlite"));
|
|
469
|
+
let config = MintConfig {
|
|
470
|
+
allowed_read_paths: vec![root.clone()],
|
|
471
|
+
blocked_paths: vec![],
|
|
472
|
+
..MintConfig::default()
|
|
473
|
+
};
|
|
474
|
+
assert_eq!(store.index_file(&file, &config).unwrap(), 1);
|
|
475
|
+
assert_eq!(
|
|
476
|
+
store.search("native knowledge", 5).unwrap()[0].source,
|
|
477
|
+
"notes.md"
|
|
478
|
+
);
|
|
479
|
+
let _ = fs::remove_dir_all(root);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
#[test]
|
|
483
|
+
fn extracts_docx_xml_text() {
|
|
484
|
+
assert_eq!(
|
|
485
|
+
xml_text(
|
|
486
|
+
"<w:p><w:r><w:t>Hello</w:t></w:r><w:r><w:t>Mint</w:t></w:r></w:p>",
|
|
487
|
+
&["w:t", "w:p"]
|
|
488
|
+
)
|
|
489
|
+
.unwrap()
|
|
490
|
+
.trim(),
|
|
491
|
+
"Hello Mint"
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#[test]
|
|
496
|
+
fn embedding_search_ranks_related_chunk_first() {
|
|
497
|
+
let root = std::env::temp_dir().join("mint-knowledge-embedding");
|
|
498
|
+
let _ = fs::remove_dir_all(&root);
|
|
499
|
+
fs::create_dir_all(&root).unwrap();
|
|
500
|
+
let store = KnowledgeStore::open(root.join("knowledge.sqlite"));
|
|
501
|
+
let config = MintConfig {
|
|
502
|
+
allowed_read_paths: vec![root.clone()],
|
|
503
|
+
blocked_paths: vec![],
|
|
504
|
+
..MintConfig::default()
|
|
505
|
+
};
|
|
506
|
+
let rust = root.join("rust.md");
|
|
507
|
+
let cooking = root.join("cooking.md");
|
|
508
|
+
fs::write(&rust, "Rust backend ownership borrowing cargo").unwrap();
|
|
509
|
+
fs::write(&cooking, "Pasta tomato basil kitchen recipe").unwrap();
|
|
510
|
+
store.index_file(&rust, &config).unwrap();
|
|
511
|
+
store.index_file(&cooking, &config).unwrap();
|
|
512
|
+
assert_eq!(
|
|
513
|
+
store.search("cargo rust backend", 1).unwrap()[0].source,
|
|
514
|
+
"rust.md"
|
|
515
|
+
);
|
|
516
|
+
let _ = fs::remove_dir_all(root);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#[test]
|
|
520
|
+
fn searches_legacy_chunks_without_stored_embeddings() {
|
|
521
|
+
let root = std::env::temp_dir().join("mint-knowledge-legacy-embedding");
|
|
522
|
+
let _ = fs::remove_dir_all(&root);
|
|
523
|
+
fs::create_dir_all(&root).unwrap();
|
|
524
|
+
let store = KnowledgeStore::open(root.join("knowledge.sqlite"));
|
|
525
|
+
let connection = store.connection().unwrap();
|
|
526
|
+
connection
|
|
527
|
+
.execute(
|
|
528
|
+
"INSERT INTO sources (path, name, hash) VALUES ('legacy', 'legacy.md', 'hash')",
|
|
529
|
+
[],
|
|
530
|
+
)
|
|
531
|
+
.unwrap();
|
|
532
|
+
connection
|
|
533
|
+
.execute(
|
|
534
|
+
"INSERT INTO chunks (source_id, text, embedding) VALUES (1, 'legacy rust backend', NULL)",
|
|
535
|
+
[],
|
|
536
|
+
)
|
|
537
|
+
.unwrap();
|
|
538
|
+
assert_eq!(store.search("rust", 1).unwrap()[0].source, "legacy.md");
|
|
539
|
+
let _ = fs::remove_dir_all(root);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#![recursion_limit = "256"]
|
|
2
|
+
|
|
3
|
+
pub mod agent_loop;
|
|
4
|
+
pub mod chat;
|
|
5
|
+
pub mod code_tools;
|
|
6
|
+
pub mod config;
|
|
7
|
+
pub mod files;
|
|
8
|
+
pub mod knowledge;
|
|
9
|
+
pub mod mcp;
|
|
10
|
+
pub mod memory;
|
|
11
|
+
pub mod orchestration;
|
|
12
|
+
pub mod pictures;
|
|
13
|
+
pub mod plugins;
|
|
14
|
+
pub mod safety;
|
|
15
|
+
pub mod semantic;
|
|
16
|
+
pub mod shell;
|
|
17
|
+
pub mod skills;
|
|
18
|
+
pub mod symbols;
|
|
19
|
+
pub mod tasks;
|
|
20
|
+
pub mod tts;
|
|
21
|
+
pub mod weather;
|
|
22
|
+
pub mod web_search;
|
|
23
|
+
pub mod workflows;
|
|
24
|
+
|
|
25
|
+
pub use agent_loop::{AgentActionFuture, AgentLoopError, parse_agent_json, run_agent_loop};
|
|
26
|
+
pub use chat::{
|
|
27
|
+
ChatError, ChatRequest, ChatResponse, send_chat, send_chat_with_fallback, stream_chat,
|
|
28
|
+
stream_chat_with_fallback,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
pub use code_tools::{
|
|
32
|
+
AppliedCodeEdit, CodeEdit, CodeEditPreview, CodeEditProposal, CodeFile, CodeInspectionError,
|
|
33
|
+
CodePatchHunk, CodePlan, CodeSearchHit, RepositorySummary, apply_code_edits, build_code_patch,
|
|
34
|
+
fetch_github_repo_summary, inspect_code_plan, list_code_files, parse_github_url,
|
|
35
|
+
propose_code_edits, read_code_file, repository_summary, search_code,
|
|
36
|
+
};
|
|
37
|
+
pub use config::{
|
|
38
|
+
ConfigError, MintConfig, config_path, initialize_config, load_config, save_config,
|
|
39
|
+
set_config_value,
|
|
40
|
+
};
|
|
41
|
+
pub use files::{FileOperationError, PathKind, PathMatch, create_folder, find_paths};
|
|
42
|
+
pub use knowledge::{
|
|
43
|
+
KnowledgeError, KnowledgeHit, KnowledgeSource, KnowledgeStore, extract_document_text,
|
|
44
|
+
};
|
|
45
|
+
pub use mcp::{
|
|
46
|
+
McpError, McpServer, add_mcp_server, call_configured_mcp_tool, call_mcp_tool,
|
|
47
|
+
clear_mcp_servers, configured_mcp_servers, list_mcp_servers, remove_mcp_server,
|
|
48
|
+
};
|
|
49
|
+
pub use memory::{
|
|
50
|
+
CHAT_CLI_ID, ChatSession, DEFAULT_CONVERSATION_ID, InteractionMemory, LearnedSkill,
|
|
51
|
+
MemoryError, MemoryStore, WorkspaceSession, memory_path,
|
|
52
|
+
};
|
|
53
|
+
pub use orchestration::{
|
|
54
|
+
AgentApproval, AgentProgress, AgentResult, ApprovalOutcome, OrchestrationError,
|
|
55
|
+
orchestrate_agent_loop, orchestrate_chat, orchestrate_chat_stream,
|
|
56
|
+
orchestrate_chat_stream_with_fallback, orchestrate_chat_with_fallback,
|
|
57
|
+
};
|
|
58
|
+
pub use pictures::{
|
|
59
|
+
PictureEntry, PictureError, list_saved_pictures, parse_data_uri, save_chat_images,
|
|
60
|
+
save_sent_image,
|
|
61
|
+
};
|
|
62
|
+
pub use plugins::{NativePlugin, PluginError, execute_native_plugin, native_plugins};
|
|
63
|
+
pub use safety::{
|
|
64
|
+
Capability, SafetyError, SafetyTier, ShellClassification, ShellCommandMode,
|
|
65
|
+
assert_path_capability, classify_shell_command, shell_mode_allowed,
|
|
66
|
+
};
|
|
67
|
+
pub use semantic::{
|
|
68
|
+
SemanticChunk, SemanticError, SemanticHit, SemanticIndex, index_semantic_code,
|
|
69
|
+
search_semantic_code,
|
|
70
|
+
};
|
|
71
|
+
pub use shell::{ShellError, ShellOutput, run_shell_command};
|
|
72
|
+
pub use skills::{SkillError, learn_skill, learned_skills_context};
|
|
73
|
+
pub use symbols::{CodeSymbol, SymbolError, SymbolIndex, build_symbol_index};
|
|
74
|
+
pub use tasks::{Task, TaskError, TaskStore, tasks_path};
|
|
75
|
+
pub use tts::{TtsUrl, google_tts_urls};
|
|
76
|
+
pub use weather::{WeatherError, WeatherReport, weather};
|
|
77
|
+
pub use workflows::{WorkflowError, load_workflows, workflows_path};
|
|
78
|
+
pub mod api_server;
|
|
79
|
+
pub use api_server::start_api_server;
|
|
80
|
+
pub mod channels;
|
|
81
|
+
pub use channels::start_channels;
|
|
82
|
+
|
|
83
|
+
pub static HTTP_CLIENT: std::sync::LazyLock<reqwest::Client> =
|
|
84
|
+
std::sync::LazyLock::new(reqwest::Client::new);
|