升级组件

This commit is contained in:
xiaoxixi 2026-07-27 22:40:28 +08:00
parent 1c1b96f5d4
commit 751de27e45
4 changed files with 51 additions and 36 deletions

View File

@ -4,18 +4,18 @@ version = "1.3.1"
edition = "2024"
[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"] }
regex = "1.12"
regex = "1.13"
serde_json = "1.0"
async-trait = "0.1"
thiserror = "2.0.18"
tokio = { version = "1.52", features = ["full"] }
thiserror = "2.0.19"
tokio = { version = "1.53", features = ["full"] }
tokio-util = { version = "0.7", features = ["rt", "io"] }
dashmap = "6.1"
uuid = { version = "1.23", features = ["v4"] }
dashmap = "6.2"
uuid = { version = "1.24", features = ["v4"] }
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"
clap = { version = "4", features = ["derive"] }
dirs = "6.0.0"
@ -26,21 +26,21 @@ tracing-appender = "0.2"
time = { version = "0.3", features = ["formatting", "local-offset"] }
anyhow = "1.0"
mime_guess = "2.0"
base64 = "0.22"
sha2 = "0.10"
base64 = "0.23"
sha2 = "0.11"
tempfile = "3"
cron = "0.16"
cron = "0.17"
chrono-tz = "0.10"
ratatui = "0.30"
crossterm = { version = "0.29", features = ["event-stream"] }
termimad = "0.34"
termimad = "0.35"
textwrap = "0.16"
unicode-width = "0.2"
chrono = "0.4"
sqlx = { version = "0.8", features = ["sqlite", "macros", "chrono", "runtime-tokio"] }
jieba-rs = "0.9"
sqlx = { version = "0.9", features = ["sqlite", "macros", "chrono", "runtime-tokio"] }
jieba-rs = "0.10"
which = "8"
rmcp = { version = "1.7", default-features = false, features = [
rmcp = { version = "2.2", default-features = false, features = [
"client",
"transport-child-process",
"transport-streamable-http-client-reqwest",

View File

@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex};
use anyhow::Context;
use http::{HeaderName, HeaderValue};
use rmcp::model::{CallToolRequestParams, RawContent};
use rmcp::model::{CallToolRequestParams, ContentBlock};
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
use rmcp::transport::{StreamableHttpClientTransport, TokioChildProcess};
use rmcp::{Peer, RoleClient, ServiceExt};
@ -85,14 +85,14 @@ impl McpConnection {
fn extract_text(result: &rmcp::model::CallToolResult) -> String {
let mut parts = Vec::new();
for content in &result.content {
match &**content {
RawContent::Text(text) => {
match content {
ContentBlock::Text(text) => {
parts.push(text.text.clone());
}
RawContent::Image(image) => {
ContentBlock::Image(image) => {
parts.push(format!("[image: {}]", image.mime_type,));
}
RawContent::Resource(resource) => match &resource.resource {
ContentBlock::Resource(resource) => match &resource.resource {
rmcp::model::ResourceContents::TextResourceContents { text, .. } => {
parts.push(format!(
"[resource text: {}]",
@ -102,6 +102,7 @@ fn extract_text(result: &rmcp::model::CallToolResult) -> String {
rmcp::model::ResourceContents::BlobResourceContents { uri, .. } => {
parts.push(format!("[resource blob: {}]", uri));
}
_ => parts.push("[unsupported resource]".to_string()),
},
_ => {
parts.push("[unsupported content]".to_string());

View File

@ -103,8 +103,9 @@ impl super::Storage {
let fts_query = jieba()
.cut(query, true)
.into_iter()
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
.map(|w| format!("\"{}\"", w.replace('"', "")))
.map(|token| token.word)
.filter(|word| word.len() > 1 || word.bytes().any(|b| b > 127))
.map(|word| format!("\"{}\"", word.replace('"', "")))
.collect::<Vec<_>>()
.join(" OR ");
@ -138,8 +139,9 @@ impl super::Storage {
let terms: Vec<String> = jieba()
.cut(query, true)
.into_iter()
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
.map(|w| w.replace(['%', '_'], ""))
.map(|token| token.word)
.filter(|word| word.len() > 1 || word.bytes().any(|b| b > 127))
.map(|word| word.replace(['%', '_'], ""))
.collect();
if !terms.is_empty() {
@ -163,7 +165,8 @@ impl super::Storage {
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 {
let pattern = format!("%{}%", term);
query_builder = query_builder.bind(pattern.clone()).bind(pattern);
@ -205,8 +208,9 @@ impl super::Storage {
let terms: Vec<String> = jieba()
.cut(q, true)
.into_iter()
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
.map(|w| w.replace(['%', '_'], ""))
.map(|token| token.word)
.filter(|word| word.len() > 1 || word.bytes().any(|b| b > 127))
.map(|word| word.replace(['%', '_'], ""))
.collect();
if terms.is_empty() {
@ -234,7 +238,8 @@ impl super::Storage {
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 {
let pattern = format!("%{}%", term);
query_builder = query_builder.bind(pattern.clone()).bind(pattern);

View File

@ -29,7 +29,7 @@ const INSERT_MESSAGE_SQL: &str = r#"
fn insert_message_query<'a>(
session_id: &'a str,
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)
.bind(&msg.id)
.bind(session_id)
@ -424,13 +424,19 @@ impl Storage {
("job_runs", "delivery_error", "delivery_error TEXT"),
] {
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
.iter()
.any(|row| row.get::<String, _>("name") == column)
{
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)
.await?;
sqlx::query(&format!("PRAGMA user_version = {SCHEMA_VERSION}"))
sqlx::query(sqlx::AssertSqlSafe(format!(
"PRAGMA user_version = {SCHEMA_VERSION}"
)))
.execute(&mut *tx)
.await?;
tx.commit().await?;
@ -1082,7 +1090,8 @@ impl Storage {
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 {
count_query = count_query.bind(bt);
}
@ -1092,7 +1101,7 @@ impl Storage {
let count_row = count_query.fetch_one(self.pool()).await?;
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 {
select_query = select_query.bind(bt);
}
@ -1602,7 +1611,7 @@ mod tests {
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())
.await
.unwrap();