Skip to main content

zerolaunch_plugin_sdk_rust/
runtime.rs

1//! 第三方 Rust 插件的 JSON-RPC 运行时。
2//!
3//! 运行时采用三任务异步架构:
4//! - `read_task`:唯一 stdin 读取者,解析 LSP 帧,路由响应到 pending_map,
5//!   转发请求到 dispatch_task。
6//! - `write_task`:唯一 stdout 写入者,将所有出站消息编码为 LSP 帧。
7//! - `dispatch_task`:处理 plugin/* 请求,调用用户 Plugin trait 实现,
8//!   将响应发到 write_task。
9//!
10//! HostProxy 通过共享的 pending_map 和 outbound_tx 发送 host/* 请求,
11//! 避免了同步 BufReader 造成的死锁问题。
12
13use dashmap::DashMap;
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
17use tokio::sync::{mpsc, oneshot};
18
19use zerolaunch_plugin_api::config::Configurable;
20use zerolaunch_plugin_api::{
21    ActionExecutor, CachedCandidateData, DataSource, KeywordInjector, KeywordOptimizer, Plugin,
22    ScoreBooster, SearchEngine,
23};
24use zerolaunch_plugin_protocol::codec::{
25    encode_frame, summarize_message, MAX_FRAME_SIZE, MAX_HEADER_SIZE,
26};
27use zerolaunch_plugin_protocol::jsonrpc::{Message, Request, Response};
28use zerolaunch_plugin_protocol::messages::*;
29use zerolaunch_plugin_protocol::methods::plugin as plugin_methods;
30use zerolaunch_plugin_protocol::{codes, JsonRpcError, PROTOCOL_VERSION};
31
32use crate::host_proxy::HostProxy;
33use crate::logging;
34
35use std::sync::OnceLock;
36
37// 全局 HostProxy,由 `run_async` 初始化。
38static HOST_PROXY: OnceLock<Arc<HostProxy>> = OnceLock::new();
39
40/// 返回当前运行时的 `HostProxy`。
41/// 在 `run()` 之前调用会 panic。
42pub fn host() -> Arc<HostProxy> {
43    HOST_PROXY
44        .get()
45        .expect("host() 必须在 run() 之后调用")
46        .clone()
47}
48
49/// 从 read task 路由到 dispatch task 的入站 JSON-RPC 请求。
50struct IncomingRequest {
51    id: u64,
52    method: String,
53    params: serde_json::Value,
54}
55
56/// SDK 组件集合:一个 Plugin 主组件 + 任意个 DataSource / ActionExecutor /
57/// SearchEngine / ScoreBooster / KeywordOptimizer / KeywordInjector 附加组件。
58///
59/// 与内置插件完全对等:每个组件都是独立的 Configurable(各自的 component_id、
60/// schema、设置),进程向宿主声明全部组件(GET_COMPONENTS 返回全部)。
61pub struct PluginApp {
62    /// 插件主组件:进程级元数据、触发词、面板查询。
63    pub(crate) plugin: Arc<dyn Plugin>,
64    pub(crate) data_sources: Vec<Arc<dyn DataSource>>,
65    pub(crate) executors: Vec<Arc<dyn ActionExecutor>>,
66    /// 搜索引擎组件(与内置引擎互斥,仅允许一个启用)。
67    pub(crate) search_engines: Vec<Arc<dyn SearchEngine>>,
68    /// 分数增强器组件(引擎打分后追加修正)。
69    pub(crate) score_boosters: Vec<Arc<dyn ScoreBooster>>,
70    /// 关键词优化器组件(扩展候选关键词)。
71    pub(crate) keyword_optimizers: Vec<Arc<dyn KeywordOptimizer>>,
72    /// 关键词注入器组件(基于候选上下文注入关键词)。
73    pub(crate) keyword_injectors: Vec<Arc<dyn KeywordInjector>>,
74    /// component_id → 组件统一索引(全部组件类别皆入)。
75    /// dispatch 按 component_id 以 O(1) 路由组件方法。
76    by_id: HashMap<String, ComponentEntry>,
77}
78
79/// 组件索引条目:统一持有各 trait 对象,按需向上转型。
80enum ComponentEntry {
81    Plugin(Arc<dyn Plugin>),
82    DataSource(Arc<dyn DataSource>),
83    Executor(Arc<dyn ActionExecutor>),
84    /// 搜索引擎组件(与内置引擎互斥,仅允许一个启用)。
85    SearchEngine(Arc<dyn SearchEngine>),
86    /// 分数增强器组件。
87    ScoreBooster(Arc<dyn ScoreBooster>),
88    /// 关键词优化器组件。
89    KeywordOptimizer(Arc<dyn KeywordOptimizer>),
90    /// 关键词注入器组件。
91    KeywordInjector(Arc<dyn KeywordInjector>),
92}
93
94impl PluginApp {
95    /// 以 Plugin 主组件构建应用(必须存在:进程级 metadata/触发词/面板查询属于它)。
96    pub fn new(plugin: impl Plugin + 'static) -> Self {
97        let plugin = Arc::new(plugin);
98        let mut by_id = HashMap::new();
99        by_id.insert(
100            plugin.component_id().to_string(),
101            ComponentEntry::Plugin(plugin.clone()),
102        );
103        Self {
104            plugin,
105            data_sources: Vec::new(),
106            executors: Vec::new(),
107            search_engines: Vec::new(),
108            score_boosters: Vec::new(),
109            keyword_optimizers: Vec::new(),
110            keyword_injectors: Vec::new(),
111            by_id,
112        }
113    }
114
115    /// 附加 DataSource 组件(候选采集;与内置数据源完全对等)。
116    /// 组件 id 重复属于插件编码错误,直接 panic 暴露。
117    pub fn with_data_source(mut self, ds: impl DataSource + 'static) -> Self {
118        let ds = Arc::new(ds);
119        let component_id = ds.component_id().to_string();
120        assert!(
121            self.by_id
122                .insert(component_id, ComponentEntry::DataSource(ds.clone()))
123                .is_none(),
124            "DataSource 组件 id 重复:{}",
125            ds.component_id()
126        );
127        self.data_sources.push(ds);
128        self
129    }
130
131    /// 附加 ActionExecutor 组件(候选执行;与内置执行器完全对等)。
132    /// 组件 id 重复属于插件编码错误,直接 panic 暴露。
133    pub fn with_executor(mut self, ex: impl ActionExecutor + 'static) -> Self {
134        let ex = Arc::new(ex);
135        let component_id = ex.component_id().to_string();
136        assert!(
137            self.by_id
138                .insert(component_id, ComponentEntry::Executor(ex.clone()))
139                .is_none(),
140            "ActionExecutor 组件 id 重复:{}",
141            ex.component_id()
142        );
143        self.executors.push(ex);
144        self
145    }
146    /// 附加 SearchEngine 组件(搜索打分;与内置搜索引擎完全对等)。
147    /// 组件 id 重复属于插件编码错误,直接 panic 暴露。
148    pub fn with_search_engine(mut self, engine: impl SearchEngine + 'static) -> Self {
149        let engine = Arc::new(engine);
150        let component_id = engine.component_id().to_string();
151        assert!(
152            self.by_id
153                .insert(component_id, ComponentEntry::SearchEngine(engine.clone()))
154                .is_none(),
155            "SearchEngine 组件 id 重复:{}",
156            engine.component_id()
157        );
158        self.search_engines.push(engine);
159        self
160    }
161
162    /// 附加 ScoreBooster 组件(分数增强;与内置分数增强器完全对等)。
163    /// 组件 id 重复属于插件编码错误,直接 panic 暴露。
164    pub fn with_score_booster(mut self, booster: impl ScoreBooster + 'static) -> Self {
165        let booster = Arc::new(booster);
166        let component_id = booster.component_id().to_string();
167        assert!(
168            self.by_id
169                .insert(component_id, ComponentEntry::ScoreBooster(booster.clone()))
170                .is_none(),
171            "ScoreBooster 组件 id 重复:{}",
172            booster.component_id()
173        );
174        self.score_boosters.push(booster);
175        self
176    }
177
178    /// 附加 KeywordOptimizer 组件(关键词扩展;与内置优化器完全对等)。
179    /// 组件 id 重复属于插件编码错误,直接 panic 暴露。
180    pub fn with_keyword_optimizer(mut self, optimizer: impl KeywordOptimizer + 'static) -> Self {
181        let optimizer = Arc::new(optimizer);
182        let component_id = optimizer.component_id().to_string();
183        assert!(
184            self.by_id
185                .insert(
186                    component_id,
187                    ComponentEntry::KeywordOptimizer(optimizer.clone()),
188                )
189                .is_none(),
190            "KeywordOptimizer 组件 id 重复:{}",
191            optimizer.component_id()
192        );
193        self.keyword_optimizers.push(optimizer);
194        self
195    }
196
197    /// 附加 KeywordInjector 组件(基于候选上下文注入关键词;与内置注入器完全对等)。
198    /// 组件 id 重复属于插件编码错误,直接 panic 暴露。
199    pub fn with_keyword_injector(mut self, injector: impl KeywordInjector + 'static) -> Self {
200        let injector = Arc::new(injector);
201        let component_id = injector.component_id().to_string();
202        assert!(
203            self.by_id
204                .insert(
205                    component_id,
206                    ComponentEntry::KeywordInjector(injector.clone()),
207                )
208                .is_none(),
209            "KeywordInjector 组件 id 重复:{}",
210            injector.component_id()
211        );
212        self.keyword_injectors.push(injector);
213        self
214    }
215
216    /// 运行 JSON-RPC stdio 循环(阻塞当前线程直到进程退出)。
217    pub fn run(self) {
218        let rt = tokio::runtime::Builder::new_multi_thread()
219            .worker_threads(2)
220            .enable_all()
221            .build()
222            .expect("failed to build tokio runtime");
223
224        rt.block_on(async move {
225            run_async(self).await;
226        });
227    }
228}
229
230/// 使用给定的 Plugin 实现运行 JSON-RPC stdio 循环。
231/// 等价于 `PluginApp::new(plugin).run()`。
232pub fn run(plugin: impl Plugin + 'static) {
233    PluginApp::new(plugin).run()
234}
235
236async fn run_async(mut app: PluginApp) {
237    // 初始化日志系统(双写:stderr → 文件 + WARN/ERROR → host/log 转发)
238    let mut log_rx = logging::init_logging();
239    let stdin = tokio::io::stdin();
240    let stdout = tokio::io::stdout();
241
242    // 通道
243    let (request_tx, mut request_rx) = mpsc::channel::<IncomingRequest>(64);
244    let (outbound_tx, mut outbound_rx) = mpsc::channel::<Vec<u8>>(64);
245    let pending: Arc<DashMap<u64, oneshot::Sender<Result<serde_json::Value, JsonRpcError>>>> =
246        Arc::new(DashMap::new());
247    // 创建 HostProxy 并写入全局,供 dispatch/read/write 三任务共享访问。
248    let host_proxy = Arc::new(HostProxy::new(pending.clone(), outbound_tx.clone()));
249    let _ = HOST_PROXY.set(host_proxy);
250    let hp_for_logs = HOST_PROXY.get().expect("HOST_PROXY 已设置").clone();
251
252    // 插件状态
253    let mut plugin_context: Option<zerolaunch_plugin_api::PluginContext> = None;
254
255    // --- 日志转发后台任务:将 WARN/ERROR 非阻塞转发到宿主 ---
256    tokio::spawn(async move {
257        while let Some(entry) = log_rx.recv().await {
258            hp_for_logs.log_no_wait(&entry.level, &entry.message);
259        }
260    });
261
262    // --- 读任务:stdin → pending_map(响应)或 request_tx(新请求)---
263    let pending_r = pending.clone();
264    let request_tx_clone = request_tx.clone();
265    let mut read_handle = tokio::spawn(async move {
266        let reader = BufReader::new(stdin);
267        let mut stdin = reader;
268        while let Ok(body) = read_frame(&mut stdin).await {
269            tracing::debug!(
270                "收到宿主原始帧 ({} bytes): {:?}",
271                body.len(),
272                String::from_utf8_lossy(&body)
273            );
274            let msg: Message = match serde_json::from_slice(&body) {
275                Ok(m) => m,
276                Err(e) => {
277                    tracing::debug!(
278                        "解析宿主消息失败: {}; raw frame: {:?}",
279                        e,
280                        String::from_utf8_lossy(&body)
281                    );
282                    continue;
283                }
284            };
285            match msg {
286                Message::Response(resp) => {
287                    if let Some((_, tx)) = pending_r.remove(&resp.id) {
288                        // 错误走 Err 分支携带结构化 JsonRpcError,不再伪装成字符串结果:
289                        // 宿主拒绝/模型错误等可被调用方区分,模型方法 from_value 不再被
290                        // "invalid type: string" 淹没真实错误。
291                        let result = if let Some(err) = resp.error {
292                            Err(err)
293                        } else {
294                            Ok(resp.result.unwrap_or(serde_json::Value::Null))
295                        };
296                        let _ = tx.send(result);
297                    } else {
298                        // 如果没有对应的 pending channel,说明响应已经超时或被取消,忽略。同时打印一下被忽略的信息
299                        tracing::warn!(
300                            "收到未知的响应 id={},可能已超时或被取消: {:?}",
301                            resp.id,
302                            resp
303                        );
304                    }
305                }
306                Message::Request(req) => {
307                    let ret = request_tx_clone
308                        .send(IncomingRequest {
309                            id: req.id,
310                            method: req.method,
311                            params: req.params,
312                        })
313                        .await;
314                    // 如果 dispatch task 已退出,说明插件可能已经崩溃或被关闭,无法处理请求。打印警告信息。
315                    if ret.is_err() {
316                        tracing::warn!(
317                            "无法将请求发送到 dispatch task,可能 dispatch task 已退出: {:?}",
318                            ret
319                        );
320                    }
321                }
322                Message::Notification(_) => {
323                    tracing::trace!("忽略通知");
324                }
325            }
326        }
327    });
328
329    // --- 分发任务:plugin/* 请求 → 用户 Plugin → 响应到 outbound_tx ---
330    let outbound_dispatch = outbound_tx.clone();
331    let mut dispatch_handle = tokio::spawn(async move {
332        while let Some(incoming) = request_rx.recv().await {
333            let req = Request::new(incoming.id, &incoming.method, incoming.params);
334            tracing::debug!(
335                "dispatch 收到请求: {:?}",
336                summarize_message(&Message::Request(req.clone()))
337            );
338            // 调用用户实现的 Plugin trait 处理,并将响应发送到 outbound_tx。
339            let result = handle_request(&mut app, &req, &mut plugin_context).await;
340            if let Ok(payload) = serde_json::to_vec(&result) {
341                if outbound_dispatch.send(payload).await.is_err() {
342                    tracing::warn!(
343                        "dispatch 发送响应失败(outbound channel 关闭),dispatch task 退出"
344                    );
345                    break;
346                }
347            } else {
348                tracing::error!("dispatch 序列化响应失败,请求被丢弃");
349            }
350        }
351        tracing::warn!("dispatch task 退出(request channel 关闭或发送失败)");
352    });
353
354    // --- 写任务:outbound_rx → stdout ---
355    let write_handle = tokio::spawn(async move {
356        let mut writer = stdout;
357        while let Some(payload) = outbound_rx.recv().await {
358            match serde_json::from_slice::<Message>(&payload) {
359                Ok(msg) => tracing::debug!("发送给宿主的消息: {:?}", summarize_message(&msg)),
360                Err(e) => tracing::debug!(
361                    "发送给宿主的原始负载 ({} bytes, 解析失败: {}): {:?}",
362                    payload.len(),
363                    e,
364                    String::from_utf8_lossy(&payload)
365                ),
366            }
367            let frame = encode_frame(&payload);
368            if writer.write_all(&frame).await.is_err() {
369                break;
370            }
371            if writer.flush().await.is_err() {
372                break;
373            }
374        }
375    });
376
377    // 等待读任务结束(传输层关闭),或 dispatch task 崩溃(插件代码 panic)。
378    tokio::select! {
379        // 正常退出:stdin 关闭(宿主 shutdown / EOF)→ 读任务结束。
380        _ = &mut read_handle => {
381            // 释放 request_tx → dispatch_task 在当前请求处理完后
382            // 通过 channel 关闭优雅退出。
383            drop(request_tx);
384            // 写任务无法通过 channel 关闭退出,因为 HOST_PROXY 全局
385            // 仍持有 outbound_tx 的 clone,因此直接 abort。
386            write_handle.abort();
387            let _ = (&mut dispatch_handle).await;
388        }
389        // 插件代码 panic:dispatch task 死亡,进程退出由宿主按 auto_restart 重启。
390        r = &mut dispatch_handle => {
391            if r.is_err() {
392                tracing::error!("dispatch task 因 panic 退出,插件进程终止(宿主将按 auto_restart 重启)");
393            }
394        }
395    }
396}
397
398/// 读取单条 LSP 风格 Content-Length 帧消息。
399/// 返回原始 JSON 字节,或在解析/大小/IO 失败时返回错误字符串。
400async fn read_frame<R: tokio::io::AsyncBufRead + Unpin>(reader: &mut R) -> Result<Vec<u8>, String> {
401    let mut content_length: Option<usize> = None;
402    let mut total_header_len = 0usize;
403    loop {
404        let mut line = String::new();
405        let n = reader
406            .read_line(&mut line)
407            .await
408            .map_err(|e| format!("read error: {}", e))?;
409        if n == 0 {
410            return Err("transport closed".into());
411        }
412        total_header_len += n;
413        if total_header_len > MAX_HEADER_SIZE {
414            return Err("header too long".into());
415        }
416        let trimmed = line.trim();
417        if trimmed.is_empty() {
418            break;
419        }
420        if let Some(value) = trimmed.strip_prefix("Content-Length:") {
421            content_length = Some(
422                value
423                    .trim()
424                    .parse::<usize>()
425                    .map_err(|e| format!("bad Content-Length: {}", e))?,
426            );
427        }
428    }
429    let len = content_length.ok_or("missing Content-Length")?;
430    if len > MAX_FRAME_SIZE {
431        return Err(format!("Content-Length too large: {}", len));
432    }
433    let mut body = vec![0u8; len];
434    reader
435        .read_exact(&mut body)
436        .await
437        .map_err(|e| format!("read body: {}", e))?;
438    Ok(body)
439}
440
441// / 处理单条 plugin/* 请求,返回响应 Message。
442async fn handle_request(
443    app: &mut PluginApp,
444    req: &Request,
445    plugin_ctx: &mut Option<zerolaunch_plugin_api::PluginContext>,
446) -> Message {
447    let id = req.id;
448    let result = dispatch(app, &req.method, &req.params, plugin_ctx).await;
449    match result {
450        Ok(value) => Message::Response(Response::ok(id, value)),
451        Err(err) => Message::Response(Response::err(id, err)),
452    }
453}
454
455async fn dispatch(
456    app: &mut PluginApp,
457    method: &str,
458    params: &serde_json::Value,
459    plugin_ctx: &mut Option<zerolaunch_plugin_api::PluginContext>,
460) -> Result<serde_json::Value, JsonRpcError> {
461    match method {
462        // 初始化请求,设置 plugin_ctx 并返回插件版本信息。
463        plugin_methods::INITIALIZE => {
464            let p: InitializeParams = serde_json::from_value(params.clone())
465                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
466            // 记录插件 id:t_key() 据此自动补 `plugin.<id>.` 前缀
467            crate::set_plugin_id(&p.plugin_id);
468            *plugin_ctx = Some(zerolaunch_plugin_api::PluginContext {
469                trace_id: "init".into(),
470                query_id: None,
471                plugin_id: Some(p.plugin_id),
472                // 远端插件无宿主查询版本门控,恒视为最新。
473                query_revision_gate: None,
474                // 远端插件会话由宿主经 RPC 下发通道,未收到时缺省视为 GUI 通道。
475                query_channel: zerolaunch_plugin_api::QueryChannel::Ui,
476                // 宿主语言在握手时下发(InitializeParams.locale),写入会话上下文。
477                locale: p.locale,
478            });
479            let result = InitializeResult {
480                plugin_version: app.plugin.metadata().version.clone(),
481                protocol_version: PROTOCOL_VERSION.to_string(),
482            };
483            Ok(serde_json::to_value(result).unwrap_or_default())
484        }
485        // 返回 metadata
486        plugin_methods::GET_METADATA => {
487            Ok(serde_json::to_value(app.plugin.metadata()).unwrap_or(serde_json::Value::Null))
488        }
489        // 返回这个插件实现的全部组件(Plugin + 附加 DataSource / ActionExecutor)
490        plugin_methods::GET_COMPONENTS => {
491            let mut components = vec![ComponentDescriptor {
492                component_id: app.plugin.component_id().to_string(),
493                component_name: app.plugin.component_name().to_string(),
494                component_description: app.plugin.metadata().description.clone(),
495                component_type: app.plugin.component_type(),
496                kind: ComponentKind::Plugin {
497                    trigger_keywords: app.plugin.metadata().trigger_keywords.clone(),
498                },
499                priority: app.plugin.metadata().priority,
500            }];
501            for ds in &app.data_sources {
502                components.push(ComponentDescriptor {
503                    component_id: ds.component_id().to_string(),
504                    component_name: ds.component_name().to_string(),
505                    component_description: ds.component_description().to_string(),
506                    component_type: ds.component_type(),
507                    kind: ComponentKind::DataSource,
508                    priority: ds.priority(),
509                });
510            }
511            for ex in &app.executors {
512                components.push(ComponentDescriptor {
513                    component_id: ex.component_id().to_string(),
514                    component_name: ex.component_name().to_string(),
515                    component_description: ex.component_description().to_string(),
516                    component_type: ex.component_type(),
517                    kind: ComponentKind::ActionExecutor {
518                        target_types: ex.supported_target_types(),
519                    },
520                    priority: ex.priority(),
521                });
522            }
523            for engine in &app.search_engines {
524                components.push(ComponentDescriptor {
525                    component_id: engine.component_id().to_string(),
526                    component_name: engine.component_name().to_string(),
527                    component_description: engine.component_description().to_string(),
528                    component_type: engine.component_type(),
529                    kind: ComponentKind::SearchEngine,
530                    priority: engine.priority(),
531                });
532            }
533            for booster in &app.score_boosters {
534                components.push(ComponentDescriptor {
535                    component_id: booster.component_id().to_string(),
536                    component_name: booster.component_name().to_string(),
537                    component_description: booster.component_description().to_string(),
538                    component_type: booster.component_type(),
539                    kind: ComponentKind::ScoreBooster,
540                    priority: booster.priority(),
541                });
542            }
543            for optimizer in &app.keyword_optimizers {
544                components.push(ComponentDescriptor {
545                    component_id: optimizer.component_id().to_string(),
546                    component_name: optimizer.component_name().to_string(),
547                    component_description: optimizer.component_description().to_string(),
548                    component_type: optimizer.component_type(),
549                    kind: ComponentKind::KeywordOptimizer,
550                    priority: optimizer.priority(),
551                });
552            }
553            for injector in &app.keyword_injectors {
554                components.push(ComponentDescriptor {
555                    component_id: injector.component_id().to_string(),
556                    component_name: injector.component_name().to_string(),
557                    component_description: injector.component_description().to_string(),
558                    component_type: injector.component_type(),
559                    kind: ComponentKind::KeywordInjector,
560                    priority: injector.priority(),
561                });
562            }
563            Ok(serde_json::to_value(components).unwrap_or_default())
564        }
565        // 返回指定组件的注册配置项
566        plugin_methods::GET_SETTINGS_SCHEMA => {
567            let p: GetSettingsSchemaParams = serde_json::from_value(params.clone())
568                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
569            let conf = find_configurable(app, &p.component_id)?;
570            Ok(serde_json::to_value(conf.setting_schema()).unwrap_or(serde_json::Value::Null))
571        }
572        // 返回指定组件当前的配置值
573        plugin_methods::GET_SETTINGS => {
574            let p: GetSettingsParams = serde_json::from_value(params.clone())
575                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
576            let conf = find_configurable(app, &p.component_id)?;
577            Ok(conf.get_settings())
578        }
579        // 返回指定组件的默认启用状态
580        plugin_methods::GET_DEFAULT_ENABLED => {
581            let p: GetDefaultEnabledParams = serde_json::from_value(params.clone())
582                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
583            let conf = find_configurable(app, &p.component_id)?;
584            Ok(serde_json::to_value(conf.default_enabled()).unwrap_or_default())
585        }
586        // 宿主下发新的配置值,指定组件据此更新自身行为
587        plugin_methods::APPLY_SETTINGS => {
588            let p: ApplySettingsParams = serde_json::from_value(params.clone())
589                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
590            let conf = find_configurable(app, &p.component_id)?;
591            conf.apply_settings(p.settings)
592                .await
593                .map_err(|e| JsonRpcError::new(codes::PLUGIN_ERROR, e.to_string()))?;
594            Ok(serde_json::Value::Null)
595        }
596        // 验证一组配置值是否合法(不会实际应用),返回验证结果或错误信息
597        plugin_methods::VALIDATE_SETTINGS => {
598            let p: ValidateSettingsParams = serde_json::from_value(params.clone())
599                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
600            let conf = find_configurable(app, &p.component_id)?;
601            let result = match conf.validate_settings(&p.settings).await {
602                Ok(()) => ValidateSettingsResult { error: None },
603                Err(e) => ValidateSettingsResult {
604                    error: Some(e.to_string()),
605                },
606            };
607            Ok(serde_json::to_value(result).unwrap_or_default())
608        }
609        // 返回裸数组:宿主 discover 流程以 `Vec<ConfigActionDef>` 反序列化
610        // (与 get_settings_schema 的裸数组约定一致),包装结构会解析失败。
611        plugin_methods::CONFIG_ACTIONS => {
612            let p: ConfigActionsParams = serde_json::from_value(params.clone())
613                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
614            let conf = find_configurable(app, &p.component_id)?;
615            Ok(serde_json::to_value(conf.config_actions()).unwrap_or_default())
616        }
617        plugin_methods::EXECUTE_CONFIG_ACTION => {
618            let p: ExecuteConfigActionParams = serde_json::from_value(params.clone())
619                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
620            let conf = find_configurable(app, &p.component_id)?;
621            conf.execute_config_action(&p.action, &p.params)
622                .await
623                .map_err(|e| JsonRpcError::new(codes::PLUGIN_ERROR, e.to_string()))
624        }
625        plugin_methods::QUERY => {
626            let p: QueryParams = serde_json::from_value(params.clone())
627                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
628            let response = app
629                .plugin
630                .query(&p.ctx, &p.query)
631                .await
632                .map_err(|e| JsonRpcError::new(codes::PLUGIN_ERROR, e.to_string()))?;
633            Ok(serde_json::to_value(response).unwrap_or_default())
634        }
635        plugin_methods::EXECUTE_ACTION => {
636            let p: ExecuteActionParams = serde_json::from_value(params.clone())
637                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
638            app.plugin
639                .execute_action(&p.ctx, &p.action_id, p.payload)
640                .await
641                .map_err(|e| JsonRpcError::new(codes::PLUGIN_ERROR, e.to_string()))?;
642            Ok(serde_json::Value::Null)
643        }
644        // 插件初始化钩子:宿主在注册完成后调用(内置插件在启动期统一 init)。
645        // 远端进程无宿主 PluginHandle(跨进程不可序列化),以 None 传入,
646        // 平台能力经 host() 的 host/* RPC 访问——与内置插件语义对等。
647        plugin_methods::INIT => {
648            let p: InitParams = serde_json::from_value(params.clone())
649                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
650            app.plugin
651                .init(&p.ctx, None)
652                .await
653                .map_err(|e| JsonRpcError::new(codes::PLUGIN_ERROR, e.to_string()))?;
654            Ok(serde_json::Value::Null)
655        }
656        // 插件交互策略(查询触发方式/防抖/按键绑定):宿主在会话推送时读取。
657        plugin_methods::INTERACTION_POLICY => {
658            let _p: InteractionPolicyParams = serde_json::from_value(params.clone())
659                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
660            Ok(serde_json::to_value(app.plugin.interaction_policy()).unwrap_or_default())
661        }
662        // DataSource 组件:采集候选项(与内置数据源对等)
663        plugin_methods::FETCH_CANDIDATES => {
664            let p: FetchCandidatesParams = serde_json::from_value(params.clone())
665                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
666            let ds = find_data_source(app, &p.component_id)?;
667            let cache = ds.fetch_candidates().await;
668            Ok(serde_json::to_value(FetchCandidatesResult {
669                candidates: cache.get_candidates().clone(),
670            })
671            .unwrap_or_default())
672        }
673        // ActionExecutor 组件:支持的目标类型列表
674        plugin_methods::SUPPORTED_TARGET_TYPES => {
675            let p: SupportedTargetTypesParams = serde_json::from_value(params.clone())
676                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
677            let ex = find_executor(app, &p.component_id)?;
678            Ok(serde_json::to_value(ex.supported_target_types()).unwrap_or_default())
679        }
680        // ActionExecutor 组件:支持的动作列表(与内置 supported_actions() 语义一致,
681        // 不区分 target_type——宿主按 (id, label) 去重合并)
682        plugin_methods::SUPPORTED_ACTIONS => {
683            let p: SupportedActionsParams = serde_json::from_value(params.clone())
684                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
685            let ex = find_executor(app, &p.component_id)?;
686            Ok(serde_json::to_value(ex.supported_actions()).unwrap_or_default())
687        }
688        // ActionExecutor 组件:执行动作(完整 ExecutionContext 原样下发)
689        plugin_methods::EXECUTOR_EXECUTE => {
690            let p: ExecutorExecuteParams = serde_json::from_value(params.clone())
691                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
692            let ex = find_executor(app, &p.component_id)?;
693            let result = match ex.execute(&p.execution_ctx, &p.action_id).await {
694                Ok(()) => ExecutorExecuteResult { error: None },
695                Err(e) => ExecutorExecuteResult {
696                    error: Some(e.to_string()),
697                },
698            };
699            Ok(serde_json::to_value(result).unwrap_or_default())
700        }
701        // SearchEngine 组件:对缓存候选计算分数(候选快照保真还原,
702        // 插件进程内按原 id / 原顺序可回查)
703        plugin_methods::CALCULATE_SCORES => {
704            let p: CalculateScoresParams = serde_json::from_value(params.clone())
705                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
706            let engine = find_search_engine(app, &p.component_id)?;
707            let cache = CachedCandidateData::from_data(p.candidates);
708            let scored = engine.calculate_scores(&cache, &p.query).await;
709            Ok(serde_json::to_value(scored).unwrap_or_default())
710        }
711        // ScoreBooster 组件:增强分数(返回增强后的完整列表,索引与传入一致)
712        plugin_methods::BOOSTER_BOOST => {
713            let p: BoosterBoostParams = serde_json::from_value(params.clone())
714                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
715            let booster = find_score_booster(app, &p.component_id)?;
716            let cache = CachedCandidateData::from_data(p.candidates);
717            let mut scored = p.scored;
718            booster.boost(&mut scored, &cache, &p.query).await;
719            Ok(serde_json::to_value(scored).unwrap_or_default())
720        }
721        // ScoreBooster 组件:记录用户确认(选中候选 → 学习用户习惯)
722        plugin_methods::BOOSTER_RECORD => {
723            let p: BoosterRecordParams = serde_json::from_value(params.clone())
724                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
725            let booster = find_score_booster(app, &p.component_id)?;
726            let cache = CachedCandidateData::from_data(p.candidates);
727            booster.record(p.candidate_id, &cache, &p.query).await;
728            Ok(serde_json::Value::Null)
729        }
730        // KeywordOptimizer 组件:动态属性(uses_context / priority 为设置可变字段)
731        plugin_methods::KEYWORD_OPTIMIZER_INFO => {
732            let p: KeywordOptimizerInfoParams = serde_json::from_value(params.clone())
733                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
734            let optimizer = find_keyword_optimizer(app, &p.component_id)?;
735            Ok(serde_json::to_value(KeywordOptimizerInfo {
736                uses_context: optimizer.uses_context(),
737                priority: optimizer.get_priority(),
738            })
739            .unwrap_or_default())
740        }
741        // KeywordOptimizer 组件:优化单个关键词
742        plugin_methods::KEYWORD_OPTIMIZE => {
743            let p: KeywordOptimizeParams = serde_json::from_value(params.clone())
744                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
745            let optimizer = find_keyword_optimizer(app, &p.component_id)?;
746            Ok(serde_json::to_value(optimizer.optimize(&p.keyword).await).unwrap_or_default())
747        }
748        // KeywordInjector 组件:对单个候选注入关键词
749        plugin_methods::KEYWORD_INJECT => {
750            let p: KeywordInjectParams = serde_json::from_value(params.clone())
751                .map_err(|e| JsonRpcError::new(codes::INVALID_PARAMS, e.to_string()))?;
752            let injector = find_keyword_injector(app, &p.component_id)?;
753            Ok(
754                serde_json::to_value(injector.inject_keywords(&p.candidate).await)
755                    .unwrap_or_default(),
756            )
757        }
758        _ => Err(JsonRpcError::new(
759            codes::METHOD_NOT_FOUND,
760            format!("method not found: {}", method),
761        )),
762    }
763}
764
765/// 按 component_id 查找 Configurable 组件(Plugin / DataSource / ActionExecutor 皆可)。
766/// 组件未注册时返回 METHOD_NOT_FOUND。
767fn find_configurable<'a>(
768    app: &'a PluginApp,
769    component_id: &str,
770) -> Result<&'a dyn Configurable, JsonRpcError> {
771    let entry = app.by_id.get(component_id).ok_or_else(|| {
772        JsonRpcError::new(
773            codes::METHOD_NOT_FOUND,
774            format!("component not found: {component_id}"),
775        )
776    })?;
777    Ok(match entry {
778        ComponentEntry::Plugin(p) => p.as_ref() as &dyn Configurable,
779        ComponentEntry::DataSource(ds) => ds.as_ref() as &dyn Configurable,
780        ComponentEntry::Executor(ex) => ex.as_ref() as &dyn Configurable,
781        ComponentEntry::SearchEngine(engine) => engine.as_ref() as &dyn Configurable,
782        ComponentEntry::ScoreBooster(booster) => booster.as_ref() as &dyn Configurable,
783        ComponentEntry::KeywordOptimizer(optimizer) => optimizer.as_ref() as &dyn Configurable,
784        ComponentEntry::KeywordInjector(injector) => injector.as_ref() as &dyn Configurable,
785    })
786}
787
788/// 按 component_id 查找 DataSource 组件。
789fn find_data_source<'a>(
790    app: &'a PluginApp,
791    component_id: &str,
792) -> Result<&'a dyn DataSource, JsonRpcError> {
793    match app.by_id.get(component_id) {
794        Some(ComponentEntry::DataSource(ds)) => Ok(ds.as_ref()),
795        Some(_) => Err(JsonRpcError::new(
796            codes::METHOD_NOT_FOUND,
797            format!("component is not a data source: {component_id}"),
798        )),
799        None => Err(JsonRpcError::new(
800            codes::METHOD_NOT_FOUND,
801            format!("data source not found: {component_id}"),
802        )),
803    }
804}
805
806/// 按 component_id 查找 ActionExecutor 组件。
807fn find_executor<'a>(
808    app: &'a PluginApp,
809    component_id: &str,
810) -> Result<&'a dyn ActionExecutor, JsonRpcError> {
811    match app.by_id.get(component_id) {
812        Some(ComponentEntry::Executor(ex)) => Ok(ex.as_ref()),
813        Some(_) => Err(JsonRpcError::new(
814            codes::METHOD_NOT_FOUND,
815            format!("component is not an executor: {component_id}"),
816        )),
817        None => Err(JsonRpcError::new(
818            codes::METHOD_NOT_FOUND,
819            format!("executor not found: {component_id}"),
820        )),
821    }
822}
823/// 按 component_id 查找 SearchEngine 组件。
824fn find_search_engine<'a>(
825    app: &'a PluginApp,
826    component_id: &str,
827) -> Result<&'a dyn SearchEngine, JsonRpcError> {
828    match app.by_id.get(component_id) {
829        Some(ComponentEntry::SearchEngine(engine)) => Ok(engine.as_ref()),
830        Some(_) => Err(JsonRpcError::new(
831            codes::METHOD_NOT_FOUND,
832            format!("component is not a search engine: {component_id}"),
833        )),
834        None => Err(JsonRpcError::new(
835            codes::METHOD_NOT_FOUND,
836            format!("search engine not found: {component_id}"),
837        )),
838    }
839}
840
841/// 按 component_id 查找 ScoreBooster 组件。
842fn find_score_booster<'a>(
843    app: &'a PluginApp,
844    component_id: &str,
845) -> Result<&'a dyn ScoreBooster, JsonRpcError> {
846    match app.by_id.get(component_id) {
847        Some(ComponentEntry::ScoreBooster(booster)) => Ok(booster.as_ref()),
848        Some(_) => Err(JsonRpcError::new(
849            codes::METHOD_NOT_FOUND,
850            format!("component is not a score booster: {component_id}"),
851        )),
852        None => Err(JsonRpcError::new(
853            codes::METHOD_NOT_FOUND,
854            format!("score booster not found: {component_id}"),
855        )),
856    }
857}
858
859/// 按 component_id 查找 KeywordOptimizer 组件。
860fn find_keyword_optimizer<'a>(
861    app: &'a PluginApp,
862    component_id: &str,
863) -> Result<&'a dyn KeywordOptimizer, JsonRpcError> {
864    match app.by_id.get(component_id) {
865        Some(ComponentEntry::KeywordOptimizer(optimizer)) => Ok(optimizer.as_ref()),
866        Some(_) => Err(JsonRpcError::new(
867            codes::METHOD_NOT_FOUND,
868            format!("component is not a keyword optimizer: {component_id}"),
869        )),
870        None => Err(JsonRpcError::new(
871            codes::METHOD_NOT_FOUND,
872            format!("keyword optimizer not found: {component_id}"),
873        )),
874    }
875}
876
877/// 按 component_id 查找 KeywordInjector 组件。
878fn find_keyword_injector<'a>(
879    app: &'a PluginApp,
880    component_id: &str,
881) -> Result<&'a dyn KeywordInjector, JsonRpcError> {
882    match app.by_id.get(component_id) {
883        Some(ComponentEntry::KeywordInjector(injector)) => Ok(injector.as_ref()),
884        Some(_) => Err(JsonRpcError::new(
885            codes::METHOD_NOT_FOUND,
886            format!("component is not a keyword injector: {component_id}"),
887        )),
888        None => Err(JsonRpcError::new(
889            codes::METHOD_NOT_FOUND,
890            format!("keyword injector not found: {component_id}"),
891        )),
892    }
893}