升级组件
This commit is contained in:
parent
1c1b96f5d4
commit
751de27e45
28
Cargo.toml
28
Cargo.toml
@ -4,18 +4,18 @@ version = "1.3.1"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = { version = "0.13.3", default-features = false, features = ["json", "rustls", "multipart"] }
|
reqwest = { version = "0.13.4", default-features = false, features = ["json", "rustls", "multipart"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
regex = "1.12"
|
regex = "1.13"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.19"
|
||||||
tokio = { version = "1.52", features = ["full"] }
|
tokio = { version = "1.53", features = ["full"] }
|
||||||
tokio-util = { version = "0.7", features = ["rt", "io"] }
|
tokio-util = { version = "0.7", features = ["rt", "io"] }
|
||||||
dashmap = "6.1"
|
dashmap = "6.2"
|
||||||
uuid = { version = "1.23", features = ["v4"] }
|
uuid = { version = "1.24", features = ["v4"] }
|
||||||
axum = { version = "0.8", features = ["ws", "multipart"] }
|
axum = { version = "0.8", features = ["ws", "multipart"] }
|
||||||
tokio-tungstenite = { version = "0.29.0", features = ["rustls-tls-webpki-roots", "rustls"] }
|
tokio-tungstenite = { version = "0.30.0", features = ["rustls-tls-webpki-roots", "rustls"] }
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
@ -26,21 +26,21 @@ tracing-appender = "0.2"
|
|||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
base64 = "0.22"
|
base64 = "0.23"
|
||||||
sha2 = "0.10"
|
sha2 = "0.11"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
cron = "0.16"
|
cron = "0.17"
|
||||||
chrono-tz = "0.10"
|
chrono-tz = "0.10"
|
||||||
ratatui = "0.30"
|
ratatui = "0.30"
|
||||||
crossterm = { version = "0.29", features = ["event-stream"] }
|
crossterm = { version = "0.29", features = ["event-stream"] }
|
||||||
termimad = "0.34"
|
termimad = "0.35"
|
||||||
textwrap = "0.16"
|
textwrap = "0.16"
|
||||||
unicode-width = "0.2"
|
unicode-width = "0.2"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
sqlx = { version = "0.8", features = ["sqlite", "macros", "chrono", "runtime-tokio"] }
|
sqlx = { version = "0.9", features = ["sqlite", "macros", "chrono", "runtime-tokio"] }
|
||||||
jieba-rs = "0.9"
|
jieba-rs = "0.10"
|
||||||
which = "8"
|
which = "8"
|
||||||
rmcp = { version = "1.7", default-features = false, features = [
|
rmcp = { version = "2.2", default-features = false, features = [
|
||||||
"client",
|
"client",
|
||||||
"transport-child-process",
|
"transport-child-process",
|
||||||
"transport-streamable-http-client-reqwest",
|
"transport-streamable-http-client-reqwest",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use http::{HeaderName, HeaderValue};
|
use http::{HeaderName, HeaderValue};
|
||||||
use rmcp::model::{CallToolRequestParams, RawContent};
|
use rmcp::model::{CallToolRequestParams, ContentBlock};
|
||||||
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
|
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
|
||||||
use rmcp::transport::{StreamableHttpClientTransport, TokioChildProcess};
|
use rmcp::transport::{StreamableHttpClientTransport, TokioChildProcess};
|
||||||
use rmcp::{Peer, RoleClient, ServiceExt};
|
use rmcp::{Peer, RoleClient, ServiceExt};
|
||||||
@ -85,14 +85,14 @@ impl McpConnection {
|
|||||||
fn extract_text(result: &rmcp::model::CallToolResult) -> String {
|
fn extract_text(result: &rmcp::model::CallToolResult) -> String {
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
for content in &result.content {
|
for content in &result.content {
|
||||||
match &**content {
|
match content {
|
||||||
RawContent::Text(text) => {
|
ContentBlock::Text(text) => {
|
||||||
parts.push(text.text.clone());
|
parts.push(text.text.clone());
|
||||||
}
|
}
|
||||||
RawContent::Image(image) => {
|
ContentBlock::Image(image) => {
|
||||||
parts.push(format!("[image: {}]", image.mime_type,));
|
parts.push(format!("[image: {}]", image.mime_type,));
|
||||||
}
|
}
|
||||||
RawContent::Resource(resource) => match &resource.resource {
|
ContentBlock::Resource(resource) => match &resource.resource {
|
||||||
rmcp::model::ResourceContents::TextResourceContents { text, .. } => {
|
rmcp::model::ResourceContents::TextResourceContents { text, .. } => {
|
||||||
parts.push(format!(
|
parts.push(format!(
|
||||||
"[resource text: {}]",
|
"[resource text: {}]",
|
||||||
@ -102,6 +102,7 @@ fn extract_text(result: &rmcp::model::CallToolResult) -> String {
|
|||||||
rmcp::model::ResourceContents::BlobResourceContents { uri, .. } => {
|
rmcp::model::ResourceContents::BlobResourceContents { uri, .. } => {
|
||||||
parts.push(format!("[resource blob: {}]", uri));
|
parts.push(format!("[resource blob: {}]", uri));
|
||||||
}
|
}
|
||||||
|
_ => parts.push("[unsupported resource]".to_string()),
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
parts.push("[unsupported content]".to_string());
|
parts.push("[unsupported content]".to_string());
|
||||||
|
|||||||
@ -103,8 +103,9 @@ impl super::Storage {
|
|||||||
let fts_query = jieba()
|
let fts_query = jieba()
|
||||||
.cut(query, true)
|
.cut(query, true)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
|
.map(|token| token.word)
|
||||||
.map(|w| format!("\"{}\"", w.replace('"', "")))
|
.filter(|word| word.len() > 1 || word.bytes().any(|b| b > 127))
|
||||||
|
.map(|word| format!("\"{}\"", word.replace('"', "")))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" OR ");
|
.join(" OR ");
|
||||||
|
|
||||||
@ -138,8 +139,9 @@ impl super::Storage {
|
|||||||
let terms: Vec<String> = jieba()
|
let terms: Vec<String> = jieba()
|
||||||
.cut(query, true)
|
.cut(query, true)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
|
.map(|token| token.word)
|
||||||
.map(|w| w.replace(['%', '_'], ""))
|
.filter(|word| word.len() > 1 || word.bytes().any(|b| b > 127))
|
||||||
|
.map(|word| word.replace(['%', '_'], ""))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !terms.is_empty() {
|
if !terms.is_empty() {
|
||||||
@ -163,7 +165,8 @@ impl super::Storage {
|
|||||||
like_clauses
|
like_clauses
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut query_builder = sqlx::query(&sql);
|
// The only interpolated fragment is a generated sequence of bind placeholders.
|
||||||
|
let mut query_builder = sqlx::query(sqlx::AssertSqlSafe(sql));
|
||||||
for term in &terms {
|
for term in &terms {
|
||||||
let pattern = format!("%{}%", term);
|
let pattern = format!("%{}%", term);
|
||||||
query_builder = query_builder.bind(pattern.clone()).bind(pattern);
|
query_builder = query_builder.bind(pattern.clone()).bind(pattern);
|
||||||
@ -205,8 +208,9 @@ impl super::Storage {
|
|||||||
let terms: Vec<String> = jieba()
|
let terms: Vec<String> = jieba()
|
||||||
.cut(q, true)
|
.cut(q, true)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
|
.map(|token| token.word)
|
||||||
.map(|w| w.replace(['%', '_'], ""))
|
.filter(|word| word.len() > 1 || word.bytes().any(|b| b > 127))
|
||||||
|
.map(|word| word.replace(['%', '_'], ""))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if terms.is_empty() {
|
if terms.is_empty() {
|
||||||
@ -234,7 +238,8 @@ impl super::Storage {
|
|||||||
like_clauses
|
like_clauses
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut query_builder = sqlx::query(&sql);
|
// The only interpolated fragment is a generated sequence of bind placeholders.
|
||||||
|
let mut query_builder = sqlx::query(sqlx::AssertSqlSafe(sql));
|
||||||
for term in &terms {
|
for term in &terms {
|
||||||
let pattern = format!("%{}%", term);
|
let pattern = format!("%{}%", term);
|
||||||
query_builder = query_builder.bind(pattern.clone()).bind(pattern);
|
query_builder = query_builder.bind(pattern.clone()).bind(pattern);
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const INSERT_MESSAGE_SQL: &str = r#"
|
|||||||
fn insert_message_query<'a>(
|
fn insert_message_query<'a>(
|
||||||
session_id: &'a str,
|
session_id: &'a str,
|
||||||
msg: &'a crate::storage::message::MessageMeta,
|
msg: &'a crate::storage::message::MessageMeta,
|
||||||
) -> sqlx::query::Query<'a, Sqlite, sqlx::sqlite::SqliteArguments<'a>> {
|
) -> sqlx::query::Query<'a, Sqlite, sqlx::sqlite::SqliteArguments> {
|
||||||
sqlx::query(INSERT_MESSAGE_SQL)
|
sqlx::query(INSERT_MESSAGE_SQL)
|
||||||
.bind(&msg.id)
|
.bind(&msg.id)
|
||||||
.bind(session_id)
|
.bind(session_id)
|
||||||
@ -424,13 +424,19 @@ impl Storage {
|
|||||||
("job_runs", "delivery_error", "delivery_error TEXT"),
|
("job_runs", "delivery_error", "delivery_error TEXT"),
|
||||||
] {
|
] {
|
||||||
let pragma = format!("PRAGMA table_info({table})");
|
let pragma = format!("PRAGMA table_info({table})");
|
||||||
let columns = sqlx::query(&pragma).fetch_all(&mut *tx).await?;
|
// `table` comes exclusively from the fixed migration list above.
|
||||||
|
let columns = sqlx::query(sqlx::AssertSqlSafe(pragma))
|
||||||
|
.fetch_all(&mut *tx)
|
||||||
|
.await?;
|
||||||
if !columns
|
if !columns
|
||||||
.iter()
|
.iter()
|
||||||
.any(|row| row.get::<String, _>("name") == column)
|
.any(|row| row.get::<String, _>("name") == column)
|
||||||
{
|
{
|
||||||
let alter = format!("ALTER TABLE {table} ADD COLUMN {definition}");
|
let alter = format!("ALTER TABLE {table} ADD COLUMN {definition}");
|
||||||
sqlx::query(&alter).execute(&mut *tx).await?;
|
// All identifiers and definitions come from the fixed migration list above.
|
||||||
|
sqlx::query(sqlx::AssertSqlSafe(alter))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,7 +467,9 @@ impl Storage {
|
|||||||
)
|
)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await?;
|
.await?;
|
||||||
sqlx::query(&format!("PRAGMA user_version = {SCHEMA_VERSION}"))
|
sqlx::query(sqlx::AssertSqlSafe(format!(
|
||||||
|
"PRAGMA user_version = {SCHEMA_VERSION}"
|
||||||
|
)))
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await?;
|
.await?;
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
@ -1082,7 +1090,8 @@ impl Storage {
|
|||||||
where_extra
|
where_extra
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut count_query = sqlx::query(&count_sql).bind(session_id);
|
// `where_extra` contains only the fixed timestamp predicates constructed above.
|
||||||
|
let mut count_query = sqlx::query(sqlx::AssertSqlSafe(count_sql)).bind(session_id);
|
||||||
if let Some(bt) = before_time {
|
if let Some(bt) = before_time {
|
||||||
count_query = count_query.bind(bt);
|
count_query = count_query.bind(bt);
|
||||||
}
|
}
|
||||||
@ -1092,7 +1101,7 @@ impl Storage {
|
|||||||
let count_row = count_query.fetch_one(self.pool()).await?;
|
let count_row = count_query.fetch_one(self.pool()).await?;
|
||||||
let total: i64 = count_row.get("total");
|
let total: i64 = count_row.get("total");
|
||||||
|
|
||||||
let mut select_query = sqlx::query(&select_sql).bind(session_id);
|
let mut select_query = sqlx::query(sqlx::AssertSqlSafe(select_sql)).bind(session_id);
|
||||||
if let Some(bt) = before_time {
|
if let Some(bt) = before_time {
|
||||||
select_query = select_query.bind(bt);
|
select_query = select_query.bind(bt);
|
||||||
}
|
}
|
||||||
@ -1602,7 +1611,7 @@ mod tests {
|
|||||||
vec!["locked_at", "lock_owner", "lease_until"],
|
vec!["locked_at", "lock_owner", "lease_until"],
|
||||||
),
|
),
|
||||||
] {
|
] {
|
||||||
let columns = sqlx::query(&format!("PRAGMA table_info({table})"))
|
let columns = sqlx::query(sqlx::AssertSqlSafe(format!("PRAGMA table_info({table})")))
|
||||||
.fetch_all(storage.pool())
|
.fetch_all(storage.pool())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user