mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-11-01 10:20:50 +08:00
feat(mcp): 用 MCP 服务替换内置 AI Agent (#319)
在现有 HTTP 服务挂载 /mcp,让外部 AI 经共享 API Key 调用原分辨率截图、批量键鼠、ATX/WOL 和虚拟介质八个工具。所有调用共用有界 FIFO 和全局限流,复用现有采集管线与硬件控制器。 移除内置模型调用、网页 Agent 面板及旧配置,设置页提供可持续查看的 Key、统一的地址/密钥复制样式。补齐 DEB 的 libudev1 运行依赖。 验证:583 项 Rust 库测试、前端生产构建、ARMv7 设备视觉操作与介质/WOL 实测,以及默认 Release 构建和 DEB 包校验通过。 Refs #319
This commit is contained in:
@@ -120,6 +120,10 @@ enum QueuedHidEvent {
|
||||
Mouse(MouseEvent),
|
||||
Consumer(ConsumerEvent),
|
||||
Reset,
|
||||
Confirmed(
|
||||
Box<QueuedHidEvent>,
|
||||
tokio::sync::oneshot::Sender<Result<()>>,
|
||||
),
|
||||
}
|
||||
|
||||
pub struct HidController {
|
||||
@@ -313,6 +317,36 @@ impl HidController {
|
||||
self.enqueue_event(QueuedHidEvent::Consumer(event)).await
|
||||
}
|
||||
|
||||
/// MCP uses acknowledged events so timing starts after the hardware send.
|
||||
pub async fn send_keyboard_confirmed(&self, event: KeyboardEvent) -> Result<()> {
|
||||
self.send_confirmed(QueuedHidEvent::Keyboard(event)).await
|
||||
}
|
||||
|
||||
pub async fn send_mouse_confirmed(&self, event: MouseEvent) -> Result<()> {
|
||||
self.send_confirmed(QueuedHidEvent::Mouse(event)).await
|
||||
}
|
||||
|
||||
pub async fn reset_confirmed(&self) -> Result<()> {
|
||||
self.send_confirmed(QueuedHidEvent::Reset).await
|
||||
}
|
||||
|
||||
async fn send_confirmed(&self, event: QueuedHidEvent) -> Result<()> {
|
||||
if !self.backend_available.load(Ordering::Acquire) {
|
||||
return Err(AppError::ServiceUnavailable(
|
||||
"HID backend not available".into(),
|
||||
));
|
||||
}
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
self.enqueue_event(QueuedHidEvent::Confirmed(Box::new(event), tx))
|
||||
.await?;
|
||||
tokio::time::timeout(Duration::from_secs(3), rx)
|
||||
.await
|
||||
.map_err(|_| AppError::ServiceUnavailable("HID hardware send timed out".into()))?
|
||||
.map_err(|_| {
|
||||
AppError::ServiceUnavailable("HID event discarded before sending".into())
|
||||
})?
|
||||
}
|
||||
|
||||
pub async fn bluetooth_status(&self) -> Result<serde_json::Value> {
|
||||
let backend = self
|
||||
.backend
|
||||
@@ -625,6 +659,18 @@ impl HidController {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn test_controller(backend: Arc<dyn HidBackend>) -> Arc<HidController> {
|
||||
#[cfg(unix)]
|
||||
let controller = Arc::new(HidController::new(HidBackendType::None, None));
|
||||
#[cfg(not(unix))]
|
||||
let controller = Arc::new(HidController::new(HidBackendType::None));
|
||||
*controller.backend.write().await = Some(backend);
|
||||
controller.sync_runtime_state_from_backend().await;
|
||||
controller.start_event_worker().await;
|
||||
controller
|
||||
}
|
||||
|
||||
async fn apply_backend_runtime_state(
|
||||
backend_type: &Arc<RwLock<HidBackendType>>,
|
||||
runtime_state: &Arc<RwLock<HidRuntimeState>>,
|
||||
@@ -645,35 +691,34 @@ async fn process_hid_event(
|
||||
event: QueuedHidEvent,
|
||||
backend: &Arc<RwLock<Option<Arc<dyn HidBackend>>>>,
|
||||
) {
|
||||
let backend_opt = backend.read().await.clone();
|
||||
let backend = match backend_opt {
|
||||
Some(b) => b,
|
||||
None => return,
|
||||
let (event, reply) = match event {
|
||||
QueuedHidEvent::Confirmed(event, reply) => (*event, Some(reply)),
|
||||
event => (event, None),
|
||||
};
|
||||
|
||||
let backend_for_send = backend.clone();
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
futures::executor::block_on(async move {
|
||||
match event {
|
||||
QueuedHidEvent::Keyboard(ev) => backend_for_send.send_keyboard(ev).await,
|
||||
QueuedHidEvent::Mouse(ev) => backend_for_send.send_mouse(ev).await,
|
||||
QueuedHidEvent::Consumer(ev) => backend_for_send.send_consumer(ev).await,
|
||||
QueuedHidEvent::Reset => backend_for_send.reset().await,
|
||||
}
|
||||
let backend = backend.read().await.clone();
|
||||
let result = match backend {
|
||||
Some(backend) => tokio::task::spawn_blocking(move || {
|
||||
futures::executor::block_on(async move {
|
||||
match event {
|
||||
QueuedHidEvent::Keyboard(ev) => backend.send_keyboard(ev).await,
|
||||
QueuedHidEvent::Mouse(ev) => backend.send_mouse(ev).await,
|
||||
QueuedHidEvent::Consumer(ev) => backend.send_consumer(ev).await,
|
||||
QueuedHidEvent::Reset => backend.reset().await,
|
||||
QueuedHidEvent::Confirmed(..) => unreachable!("nested confirmed HID event"),
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
let result = match result {
|
||||
Ok(r) => r,
|
||||
Err(_) => return,
|
||||
.await
|
||||
.unwrap_or_else(|_| Err(AppError::ServiceUnavailable("HID worker failed".into()))),
|
||||
None => Err(AppError::ServiceUnavailable(
|
||||
"HID backend not available".into(),
|
||||
)),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("HID event processing failed: {}", e);
|
||||
}
|
||||
if let Err(error) = &result {
|
||||
warn!("HID event processing failed: {}", error);
|
||||
}
|
||||
if let Some(reply) = reply {
|
||||
let _ = reply.send(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user