1use async_trait::async_trait;
8use parking_lot::RwLock;
9use std::sync::Arc;
10use std::time::Duration;
11use zerolaunch_plugin_api::config::{
12 ComponentCore, ComponentType, ConfigActionDef, ConfigError, Configurable, SettingDefinition,
13};
14use zerolaunch_plugin_api::{
15 keyword_trigger_match, ActionExecutor, CachedCandidateData, CandidateId, DataSource,
16 ExecutionContext, ExecutionError, KeywordInjector, KeywordInputSource, KeywordOptimizer,
17 PanelInteraction, Plugin, PluginContext, PluginError, PluginHandle, PluginMetadata, Query,
18 QueryResponse, ResultAction, ScoreBooster, ScoredCandidate, SearchCandidate, SearchEngine,
19 TargetType,
20};
21
22use crate::client::JsonRpcClient;
23use zerolaunch_plugin_protocol::messages::*;
24use zerolaunch_plugin_protocol::methods::plugin as plugin_methods;
25use zerolaunch_plugin_protocol::{codes, ProtocolError};
26
27const MATCH_QUERY_TIMEOUT: Duration = Duration::from_millis(100);
32
33const SHUTDOWN_HOOK_TIMEOUT: Duration = Duration::from_secs(3);
38
39#[derive(Debug)]
45pub enum RemoteComponentKind {
46 DataSource,
47 ActionExecutor {
48 target_types: Vec<TargetType>,
49 result_actions: Vec<ResultAction>,
50 },
51 SearchEngine,
53 Plugin {
54 metadata: Arc<PluginMetadata>,
57 interaction_policy: RwLock<PanelInteraction>,
61 },
62 ScoreBooster,
64 KeywordOptimizer {
68 info: RwLock<KeywordOptimizerInfo>,
69 },
70 KeywordInjector,
72}
73
74pub struct RemoteComponent {
75 pub core: ComponentCore,
77
78 pub client: Arc<JsonRpcClient>,
80
81 cached_settings: RwLock<serde_json::Value>,
83 cached_schema: RwLock<Vec<SettingDefinition>>,
84 cached_actions: RwLock<Vec<ConfigActionDef>>,
87 cached_default_enabled: RwLock<bool>,
89
90 pub kind: RemoteComponentKind,
92}
93
94fn to_config_error(e: ProtocolError) -> ConfigError {
96 ConfigError::ApplyFailed(e.to_string())
97}
98impl std::fmt::Debug for RemoteComponent {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 f.debug_struct("RemoteComponent")
101 .field("core", &self.core)
102 .field("cached_settings", &self.cached_settings.read())
103 .field("cached_schema", &self.cached_schema.read())
104 .field("cached_actions", &self.cached_actions.read())
105 .field("kind", &self.kind)
106 .finish()
107 }
108}
109
110impl RemoteComponent {
111 #[allow(clippy::too_many_arguments)]
112 pub fn new(
113 component_id: String,
114 component_name: String,
115 component_description: String,
116 component_type: ComponentType,
117 priority: u32,
118 client: Arc<JsonRpcClient>,
119 schema: Vec<SettingDefinition>,
120 settings: serde_json::Value,
121 actions: Vec<ConfigActionDef>,
122 default_enabled: bool,
123 kind: RemoteComponentKind,
124 ) -> Self {
125 Self {
126 core: ComponentCore::new(
127 component_id,
128 component_name,
129 component_description,
130 component_type,
131 priority,
132 ),
133 client,
134 cached_settings: RwLock::new(settings),
135 cached_schema: RwLock::new(schema),
136 cached_actions: RwLock::new(actions),
137 cached_default_enabled: RwLock::new(default_enabled),
138 kind,
139 }
140 }
141
142 pub fn as_data_source(self: Arc<Self>) -> Option<Arc<dyn DataSource>> {
144 matches!(self.kind, RemoteComponentKind::DataSource).then(|| self as Arc<dyn DataSource>)
145 }
146
147 pub fn as_action_executor(self: Arc<Self>) -> Option<Arc<dyn ActionExecutor>> {
149 matches!(self.kind, RemoteComponentKind::ActionExecutor { .. })
150 .then(|| self as Arc<dyn ActionExecutor>)
151 }
152
153 pub fn as_plugin(self: Arc<Self>) -> Option<Arc<dyn Plugin>> {
155 matches!(self.kind, RemoteComponentKind::Plugin { .. }).then(|| self as Arc<dyn Plugin>)
156 }
157 pub fn as_search_engine(self: Arc<Self>) -> Option<Arc<dyn SearchEngine>> {
159 matches!(self.kind, RemoteComponentKind::SearchEngine)
160 .then(|| self as Arc<dyn SearchEngine>)
161 }
162
163 pub fn as_score_booster(self: Arc<Self>) -> Option<Arc<dyn ScoreBooster>> {
165 matches!(self.kind, RemoteComponentKind::ScoreBooster)
166 .then(|| self as Arc<dyn ScoreBooster>)
167 }
168
169 pub fn as_keyword_optimizer(self: Arc<Self>) -> Option<Arc<dyn KeywordOptimizer>> {
171 matches!(self.kind, RemoteComponentKind::KeywordOptimizer { .. })
172 .then(|| self as Arc<dyn KeywordOptimizer>)
173 }
174
175 pub fn as_keyword_injector(self: Arc<Self>) -> Option<Arc<dyn KeywordInjector>> {
177 matches!(self.kind, RemoteComponentKind::KeywordInjector)
178 .then(|| self as Arc<dyn KeywordInjector>)
179 }
180
181 pub fn is_data_source(&self) -> bool {
182 matches!(self.kind, RemoteComponentKind::DataSource)
183 }
184
185 pub fn is_action_executor(&self) -> bool {
186 matches!(self.kind, RemoteComponentKind::ActionExecutor { .. })
187 }
188
189 pub fn is_plugin(&self) -> bool {
190 matches!(self.kind, RemoteComponentKind::Plugin { .. })
191 }
192 pub fn is_search_engine(&self) -> bool {
194 matches!(self.kind, RemoteComponentKind::SearchEngine)
195 }
196
197 pub fn is_score_booster(&self) -> bool {
199 matches!(self.kind, RemoteComponentKind::ScoreBooster)
200 }
201
202 pub fn is_keyword_optimizer(&self) -> bool {
204 matches!(self.kind, RemoteComponentKind::KeywordOptimizer { .. })
205 }
206
207 pub fn is_keyword_injector(&self) -> bool {
209 matches!(self.kind, RemoteComponentKind::KeywordInjector)
210 }
211}
212#[async_trait]
213impl Configurable for RemoteComponent {
214 fn core(&self) -> &ComponentCore {
215 &self.core
216 }
217
218 fn setting_schema(&self) -> Vec<SettingDefinition> {
219 self.cached_schema.read().clone()
220 }
221
222 fn get_settings(&self) -> serde_json::Value {
223 self.cached_settings.read().clone()
224 }
225
226 fn config_actions(&self) -> Vec<ConfigActionDef> {
227 self.cached_actions.read().clone()
228 }
229
230 fn default_enabled(&self) -> bool {
232 *self.cached_default_enabled.read()
233 }
234
235 async fn apply_settings(&self, settings: serde_json::Value) -> Result<(), ConfigError> {
236 let client = self.client.clone();
237 let component_id = self.core.component_id().to_string();
238 let settings_clone = settings.clone();
239 client
240 .call::<_, serde_json::Value>(
241 plugin_methods::APPLY_SETTINGS,
242 ApplySettingsParams {
243 component_id,
244 settings: settings_clone,
245 },
246 Duration::from_secs(5),
247 )
248 .await
249 .map_err(to_config_error)?;
250 *self.cached_settings.write() = settings;
251 self.refresh_interaction_policy().await;
255 if self.is_keyword_optimizer() {
256 self.refresh_keyword_optimizer_info().await;
257 }
258 Ok(())
259 }
260
261 async fn validate_settings(&self, settings: &serde_json::Value) -> Result<(), ConfigError> {
262 let contribution = self.settings_contribution()?;
265 contribution
266 .validate_values(settings)
267 .map_err(ConfigError::ValidationFailed)?;
268
269 let client = self.client.clone();
271 let component_id = self.core.component_id().to_string();
272 let settings_clone = settings.clone();
273 let result: ValidateSettingsResult = client
274 .call(
275 plugin_methods::VALIDATE_SETTINGS,
276 ValidateSettingsParams {
277 component_id,
278 settings: settings_clone,
279 },
280 Duration::from_secs(5),
281 )
282 .await
283 .map_err(to_config_error)?;
284 if let Some(error) = result.error {
285 return Err(ConfigError::ValidationFailed(error));
286 }
287 Ok(())
288 }
289
290 async fn execute_config_action(
291 &self,
292 action: &str,
293 params: &serde_json::Value,
294 ) -> Result<serde_json::Value, String> {
295 let client = self.client.clone();
296 let component_id = self.core.component_id().to_string();
297 let action = action.to_string();
298 let params = params.clone();
299 let result: Result<serde_json::Value, ProtocolError> = client
300 .call::<_, serde_json::Value>(
301 plugin_methods::EXECUTE_CONFIG_ACTION,
302 ExecuteConfigActionParams {
303 component_id,
304 action,
305 params,
306 },
307 Duration::from_secs(10),
308 )
309 .await;
310 result.map_err(|e| e.to_string())
311 }
312}
313
314#[async_trait]
315impl DataSource for RemoteComponent {
316 async fn fetch_candidates(&self) -> CachedCandidateData {
317 assert!(
318 matches!(self.kind, RemoteComponentKind::DataSource),
319 "RemoteComponent {} is not a DataSource but fetch_candidates() was called",
320 self.core.component_id()
321 );
322
323 let result: Result<FetchCandidatesResult, _> = self
324 .client
325 .call(
326 plugin_methods::FETCH_CANDIDATES,
327 FetchCandidatesParams {
328 component_id: self.core.component_id().to_string(),
329 },
330 Duration::from_secs(30),
331 )
332 .await;
333
334 match result {
335 Ok(data) => {
336 let mut cache = CachedCandidateData::new();
337 for candidate in data.candidates {
338 cache.add_candidate(candidate);
339 }
340 cache
341 }
342 Err(e) => {
343 tracing::warn!(
344 "DataSource {} fetch_candidates failed: {}",
345 self.core.component_id(),
346 e
347 );
348 CachedCandidateData::new()
349 }
350 }
351 }
352}
353
354#[async_trait]
355impl ActionExecutor for RemoteComponent {
356 fn supported_target_types(&self) -> Vec<TargetType> {
357 match &self.kind {
358 RemoteComponentKind::ActionExecutor { target_types, .. } => target_types.clone(),
359 _ => panic!(
360 "RemoteComponent {} is not an ActionExecutor but supported_target_types() was called",
361 self.core.component_id()
362 ),
363 }
364 }
365
366 fn supported_actions(&self) -> Vec<ResultAction> {
367 match &self.kind {
368 RemoteComponentKind::ActionExecutor { result_actions, .. } => result_actions.clone(),
369 _ => panic!(
370 "RemoteComponent {} is not an ActionExecutor but supported_actions() was called",
371 self.core.component_id()
372 ),
373 }
374 }
375
376 async fn execute(&self, ctx: &ExecutionContext, action_id: &str) -> Result<(), ExecutionError> {
377 assert!(
378 matches!(self.kind, RemoteComponentKind::ActionExecutor { .. }),
379 "RemoteComponent {} is not an ActionExecutor but execute() was called",
380 self.core.component_id()
381 );
382
383 let result: Result<ExecutorExecuteResult, _> = self
386 .client
387 .call(
388 plugin_methods::EXECUTOR_EXECUTE,
389 ExecutorExecuteParams {
390 component_id: self.core.component_id().to_string(),
391 execution_ctx: ctx.clone(),
392 action_id: action_id.to_string(),
393 },
394 Duration::from_secs(30),
395 )
396 .await;
397
398 match result {
399 Ok(r) => {
400 if let Some(error) = r.error {
401 Err(ExecutionError::Failed(error))
402 } else {
403 Ok(())
404 }
405 }
406 Err(e) => Err(ExecutionError::Failed(e.to_string())),
407 }
408 }
409}
410
411#[async_trait]
412impl Plugin for RemoteComponent {
413 async fn init(
414 &self,
415 ctx: &PluginContext,
416 _handle: Option<Arc<PluginHandle>>,
417 ) -> Result<(), PluginError> {
418 assert!(
419 matches!(self.kind, RemoteComponentKind::Plugin { .. }),
420 "RemoteComponent {} is not a Plugin but init() was called",
421 self.core.component_id()
422 );
423 let metadata = match &self.kind {
428 RemoteComponentKind::Plugin { metadata, .. } => metadata,
429 _ => unreachable!("kind 已在上面断言为 Plugin"),
430 };
431 let result: Result<serde_json::Value, _> = self
432 .client
433 .call(
434 plugin_methods::INIT,
435 InitParams {
436 plugin_id: metadata.id.clone(),
437 ctx: ctx.clone(),
438 },
439 Duration::from_secs(10),
440 )
441 .await;
442 match result {
443 Ok(_) => Ok(()),
444 Err(zerolaunch_plugin_protocol::ProtocolError::Rpc { code, .. })
445 if code == codes::METHOD_NOT_FOUND =>
446 {
447 Ok(())
448 }
449 Err(e) => Err(PluginError::InitFailed(e.to_string())),
450 }
451 }
452
453 fn interaction_policy(&self) -> PanelInteraction {
456 match &self.kind {
457 RemoteComponentKind::Plugin {
458 interaction_policy, ..
459 } => interaction_policy.read().clone(),
460 _ => panic!(
461 "RemoteComponent {} is not a Plugin but interaction_policy() was called",
462 self.core.component_id()
463 ),
464 }
465 }
466
467 async fn query(
468 &self,
469 ctx: &PluginContext,
470 query: &Query,
471 ) -> Result<QueryResponse, PluginError> {
472 let metadata = match &self.kind {
473 RemoteComponentKind::Plugin { metadata, .. } => metadata,
474 _ => panic!(
475 "RemoteComponent {} is not a Plugin but query() was called",
476 self.core.component_id()
477 ),
478 };
479
480 self.refresh_interaction_policy().await;
482
483 self.client
484 .call::<_, QueryResponse>(
485 plugin_methods::QUERY,
486 QueryParams {
487 plugin_id: metadata.id.clone(),
488 ctx: ctx.clone(),
489 query: query.clone(),
490 },
491 Duration::from_secs(30),
492 )
493 .await
494 .map_err(|e| PluginError::QueryFailed(e.to_string()))
495 }
496
497 async fn match_query(&self, raw_query: &str, declared_trigger_keywords: &[String]) -> bool {
503 if !matches!(self.kind, RemoteComponentKind::Plugin { .. }) {
504 panic!(
505 "RemoteComponent {} is not a Plugin but match_query() was called",
506 self.core.component_id()
507 );
508 }
509 let result: Result<bool, _> = self
510 .client
511 .call(
512 plugin_methods::MATCH_QUERY,
513 MatchQueryParams {
514 raw_query: raw_query.to_string(),
515 trigger_keywords: declared_trigger_keywords.to_vec(),
516 },
517 MATCH_QUERY_TIMEOUT,
518 )
519 .await;
520 match result {
521 Ok(verdict) => verdict,
522 Err(ProtocolError::Rpc { code, .. }) if code == codes::METHOD_NOT_FOUND => {
523 keyword_trigger_match(declared_trigger_keywords, raw_query).is_some()
524 }
525 Err(e) => {
526 tracing::warn!(
527 component_id = self.core.component_id(),
528 error = %e,
529 "插件查询匹配失败,按不命中处理"
530 );
531 false
532 }
533 }
534 }
535
536 async fn execute_action(
537 &self,
538 ctx: &PluginContext,
539 action_id: &str,
540 payload: serde_json::Value,
541 ) -> Result<(), PluginError> {
542 let metadata = match &self.kind {
543 RemoteComponentKind::Plugin { metadata, .. } => metadata,
544 _ => panic!(
545 "RemoteComponent {} is not a Plugin but execute_action() was called",
546 self.core.component_id()
547 ),
548 };
549
550 self.client
551 .call::<_, serde_json::Value>(
552 plugin_methods::EXECUTE_ACTION,
553 ExecuteActionParams {
554 plugin_id: metadata.id.clone(),
555 ctx: ctx.clone(),
556 action_id: action_id.to_string(),
557 payload,
558 },
559 Duration::from_secs(30),
560 )
561 .await
562 .map_err(|e| PluginError::ActionFailed(e.to_string()))?;
563 Ok(())
564 }
565
566 async fn on_shutdown(&self, ctx: &PluginContext) -> Result<(), PluginError> {
571 if !matches!(self.kind, RemoteComponentKind::Plugin { .. }) {
572 panic!(
573 "RemoteComponent {} is not a Plugin but on_shutdown() was called",
574 self.core.component_id()
575 );
576 }
577 match self
578 .client
579 .call::<_, serde_json::Value>(
580 plugin_methods::SHUTDOWN,
581 ShutdownParams { ctx: ctx.clone() },
582 SHUTDOWN_HOOK_TIMEOUT,
583 )
584 .await
585 {
586 Ok(_) => Ok(()),
587 Err(ProtocolError::Rpc { code, .. }) if code == codes::METHOD_NOT_FOUND => Ok(()),
588 Err(e) => Err(PluginError::ShutdownFailed(e.to_string())),
589 }
590 }
591}
592
593impl RemoteComponent {
594 async fn refresh_interaction_policy(&self) {
598 let RemoteComponentKind::Plugin {
599 interaction_policy, ..
600 } = &self.kind
601 else {
602 return;
603 };
604 let result: Result<PanelInteraction, _> = self
605 .client
606 .call(
607 plugin_methods::INTERACTION_POLICY,
608 InteractionPolicyParams {
609 component_id: self.core.component_id().to_string(),
610 },
611 Duration::from_secs(5),
612 )
613 .await;
614 if let Ok(policy) = result {
615 *interaction_policy.write() = policy;
616 }
617 }
618 async fn refresh_keyword_optimizer_info(&self) {
621 let RemoteComponentKind::KeywordOptimizer { info } = &self.kind else {
622 return;
623 };
624 let result: Result<KeywordOptimizerInfo, _> = self
625 .client
626 .call(
627 plugin_methods::KEYWORD_OPTIMIZER_INFO,
628 KeywordOptimizerInfoParams {
629 component_id: self.core.component_id().to_string(),
630 },
631 Duration::from_secs(5),
632 )
633 .await;
634 match result {
635 Ok(meta) => *info.write() = meta,
636 Err(e) => {
637 tracing::warn!(
639 "KeywordOptimizer {} 属性刷新失败: {}",
640 self.core.component_id(),
641 e
642 );
643 }
644 }
645 }
646}
647#[async_trait]
648impl SearchEngine for RemoteComponent {
649 async fn calculate_scores(
650 &self,
651 candidates: &CachedCandidateData,
652 query: &str,
653 ) -> Vec<ScoredCandidate> {
654 assert!(
655 matches!(self.kind, RemoteComponentKind::SearchEngine),
656 "RemoteComponent {} is not a SearchEngine but calculate_scores() was called",
657 self.core.component_id()
658 );
659 let result: Result<Vec<ScoredCandidate>, _> = self
660 .client
661 .call(
662 plugin_methods::CALCULATE_SCORES,
663 CalculateScoresParams {
664 component_id: self.core.component_id().to_string(),
665 candidates: candidates.to_data(),
666 query: query.to_string(),
667 },
668 Duration::from_secs(5),
669 )
670 .await;
671 match result {
672 Ok(scored) => scored,
673 Err(e) => {
674 tracing::error!(
676 "SearchEngine {} calculate_scores failed: {}",
677 self.core.component_id(),
678 e
679 );
680 Vec::new()
681 }
682 }
683 }
684}
685
686#[async_trait]
687impl ScoreBooster for RemoteComponent {
688 async fn record(&self, candidate_id: CandidateId, data: &CachedCandidateData, query: &str) {
689 assert!(
690 matches!(self.kind, RemoteComponentKind::ScoreBooster),
691 "RemoteComponent {} is not a ScoreBooster but record() was called",
692 self.core.component_id()
693 );
694 let result: Result<serde_json::Value, _> = self
695 .client
696 .call(
697 plugin_methods::BOOSTER_RECORD,
698 BoosterRecordParams {
699 component_id: self.core.component_id().to_string(),
700 candidate_id,
701 candidates: data.to_data(),
702 query: query.to_string(),
703 },
704 Duration::from_secs(2),
705 )
706 .await;
707 if let Err(e) = result {
708 tracing::warn!(
709 "ScoreBooster {} record failed: {}",
710 self.core.component_id(),
711 e
712 );
713 }
714 }
715
716 async fn boost(
717 &self,
718 candidates: &mut Vec<ScoredCandidate>,
719 data: &CachedCandidateData,
720 query: &str,
721 ) {
722 assert!(
723 matches!(self.kind, RemoteComponentKind::ScoreBooster),
724 "RemoteComponent {} is not a ScoreBooster but boost() was called",
725 self.core.component_id()
726 );
727 let result: Result<Vec<ScoredCandidate>, _> = self
728 .client
729 .call(
730 plugin_methods::BOOSTER_BOOST,
731 BoosterBoostParams {
732 component_id: self.core.component_id().to_string(),
733 candidates: data.to_data(),
734 scored: candidates.clone(),
735 query: query.to_string(),
736 },
737 Duration::from_secs(5),
738 )
739 .await;
740 match result {
741 Ok(scored) => *candidates = scored,
742 Err(e) => {
743 tracing::warn!(
745 "ScoreBooster {} boost failed: {}",
746 self.core.component_id(),
747 e
748 );
749 }
750 }
751 }
752}
753
754#[async_trait]
755impl KeywordOptimizer for RemoteComponent {
756 async fn optimize(&self, keyword: &str) -> Vec<String> {
757 assert!(
758 matches!(self.kind, RemoteComponentKind::KeywordOptimizer { .. }),
759 "RemoteComponent {} is not a KeywordOptimizer but optimize() was called",
760 self.core.component_id()
761 );
762 let result: Result<Vec<String>, _> = self
763 .client
764 .call(
765 plugin_methods::KEYWORD_OPTIMIZE,
766 KeywordOptimizeParams {
767 component_id: self.core.component_id().to_string(),
768 keyword: keyword.to_string(),
769 input_source: self.input_source(),
770 },
771 Duration::from_secs(10),
772 )
773 .await;
774 match result {
775 Ok(keywords) => keywords,
776 Err(e) => {
777 tracing::warn!(
779 "KeywordOptimizer {} optimize failed: {}",
780 self.core.component_id(),
781 e
782 );
783 Vec::new()
784 }
785 }
786 }
787
788 fn input_source(&self) -> KeywordInputSource {
789 match &self.kind {
790 RemoteComponentKind::KeywordOptimizer { info } => info.read().input_source.clone(),
791 _ => panic!(
792 "RemoteComponent {} is not a KeywordOptimizer but input_source() was called",
793 self.core.component_id()
794 ),
795 }
796 }
797
798 fn get_priority(&self) -> u32 {
799 match &self.kind {
800 RemoteComponentKind::KeywordOptimizer { info } => info.read().priority,
801 _ => panic!(
802 "RemoteComponent {} is not a KeywordOptimizer but get_priority() was called",
803 self.core.component_id()
804 ),
805 }
806 }
807}
808
809#[async_trait]
810impl KeywordInjector for RemoteComponent {
811 async fn inject_keywords(&self, candidate: &SearchCandidate) -> Vec<String> {
812 assert!(
813 matches!(self.kind, RemoteComponentKind::KeywordInjector),
814 "RemoteComponent {} is not a KeywordInjector but inject_keywords() was called",
815 self.core.component_id()
816 );
817 let result: Result<Vec<String>, _> = self
818 .client
819 .call(
820 plugin_methods::KEYWORD_INJECT,
821 KeywordInjectParams {
822 component_id: self.core.component_id().to_string(),
823 candidate: candidate.clone(),
824 },
825 Duration::from_secs(10),
826 )
827 .await;
828 match result {
829 Ok(keywords) => keywords,
830 Err(e) => {
831 tracing::warn!(
833 "KeywordInjector {} inject_keywords failed: {}",
834 self.core.component_id(),
835 e
836 );
837 Vec::new()
838 }
839 }
840 }
841}