1use 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
37static HOST_PROXY: OnceLock<Arc<HostProxy>> = OnceLock::new();
39
40pub fn host() -> Arc<HostProxy> {
43 HOST_PROXY
44 .get()
45 .expect("host() 必须在 run() 之后调用")
46 .clone()
47}
48
49struct IncomingRequest {
51 id: u64,
52 method: String,
53 params: serde_json::Value,
54}
55
56pub struct PluginApp {
62 pub(crate) plugin: Arc<dyn Plugin>,
64 pub(crate) data_sources: Vec<Arc<dyn DataSource>>,
65 pub(crate) executors: Vec<Arc<dyn ActionExecutor>>,
66 pub(crate) search_engines: Vec<Arc<dyn SearchEngine>>,
68 pub(crate) score_boosters: Vec<Arc<dyn ScoreBooster>>,
70 pub(crate) keyword_optimizers: Vec<Arc<dyn KeywordOptimizer>>,
72 pub(crate) keyword_injectors: Vec<Arc<dyn KeywordInjector>>,
74 by_id: HashMap<String, ComponentEntry>,
77}
78
79enum ComponentEntry {
81 Plugin(Arc<dyn Plugin>),
82 DataSource(Arc<dyn DataSource>),
83 Executor(Arc<dyn ActionExecutor>),
84 SearchEngine(Arc<dyn SearchEngine>),
86 ScoreBooster(Arc<dyn ScoreBooster>),
88 KeywordOptimizer(Arc<dyn KeywordOptimizer>),
90 KeywordInjector(Arc<dyn KeywordInjector>),
92}
93
94impl PluginApp {
95 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 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 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 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 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 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 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 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
230pub fn run(plugin: impl Plugin + 'static) {
233 PluginApp::new(plugin).run()
234}
235
236async fn run_async(mut app: PluginApp) {
237 let mut log_rx = logging::init_logging();
239 let stdin = tokio::io::stdin();
240 let stdout = tokio::io::stdout();
241
242 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 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 let mut plugin_context: Option<zerolaunch_plugin_api::PluginContext> = None;
254
255 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 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 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 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 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 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 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 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 tokio::select! {
379 _ = &mut read_handle => {
381 drop(request_tx);
384 write_handle.abort();
387 let _ = (&mut dispatch_handle).await;
388 }
389 r = &mut dispatch_handle => {
391 if r.is_err() {
392 tracing::error!("dispatch task 因 panic 退出,插件进程终止(宿主将按 auto_restart 重启)");
393 }
394 }
395 }
396}
397
398async 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
441async 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_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 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 query_revision_gate: None,
474 query_channel: zerolaunch_plugin_api::QueryChannel::Ui,
476 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 plugin_methods::GET_METADATA => {
487 Ok(serde_json::to_value(app.plugin.metadata()).unwrap_or(serde_json::Value::Null))
488 }
489 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
765fn 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
788fn 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
806fn 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}
823fn 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
841fn 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
859fn 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
877fn 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}