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