1pub mod catalog;
37pub mod hooks;
38pub use hooks::{HookContext, HookEvent, HookOutcome, HookRunner, HookSpec, NoopHookRunner};
39
40use async_trait::async_trait;
41use serde::{Deserialize, Serialize};
42use std::future::Future;
43use std::path::{Path, PathBuf};
44use std::pin::Pin;
45use std::sync::Arc;
46
47use crate::error::SdkError;
48
49pub type PortId = String;
59
60pub type PortValue = serde_json::Value;
65
66#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73#[serde(rename_all = "kebab-case")]
74pub enum AuthMethod {
75 #[default]
77 Bearer,
78 #[serde(rename = "x-api-key")]
80 XApiKey,
81 #[serde(rename = "api-key")]
83 ApiKey,
84 None,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
94pub struct OAuthToken {
95 pub access_token: String,
97 pub refresh_token: Option<String>,
99 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
101 pub token_type: Option<String>,
103 pub scope: Option<String>,
105}
106
107impl OAuthToken {
108 pub fn bearer(access_token: impl Into<String>) -> Self {
110 Self {
111 access_token: access_token.into(),
112 refresh_token: None,
113 expires_at: None,
114 token_type: Some("Bearer".to_string()),
115 scope: None,
116 }
117 }
118}
119
120pub trait StateStore: Send + Sync + 'static {
141 fn append(
143 &self,
144 entry: PortValue,
145 ) -> Pin<Box<dyn Future<Output = Result<PortId, SdkError>> + Send + '_>>;
146
147 fn load(
149 &self,
150 id: &PortId,
151 ) -> Pin<Box<dyn Future<Output = Result<Option<PortValue>, SdkError>> + Send + '_>>;
152
153 fn list(
155 &self,
156 prefix: &str,
157 ) -> Pin<Box<dyn Future<Output = Result<Vec<PortId>, SdkError>> + Send + '_>>;
158
159 fn delete(
161 &self,
162 id: &PortId,
163 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
164
165 #[allow(clippy::type_complexity)]
168 fn load_all(
169 &self,
170 _prefix: &str,
171 ) -> Pin<Box<dyn Future<Output = Result<Vec<(PortId, PortValue)>, SdkError>> + Send + '_>> {
172 Box::pin(async { Ok(Vec::new()) })
173 }
174}
175
176#[derive(Debug, Default, Clone, Copy)]
178pub struct NoopStateStore;
179
180impl StateStore for NoopStateStore {
181 fn append(
182 &self,
183 _entry: PortValue,
184 ) -> Pin<Box<dyn Future<Output = Result<PortId, SdkError>> + Send + '_>> {
185 Box::pin(async { Err(SdkError::PortNotConfigured { port: "StateStore" }) })
186 }
187 fn load(
188 &self,
189 _id: &PortId,
190 ) -> Pin<Box<dyn Future<Output = Result<Option<PortValue>, SdkError>> + Send + '_>> {
191 Box::pin(async { Ok(None) })
192 }
193 fn list(
194 &self,
195 _prefix: &str,
196 ) -> Pin<Box<dyn Future<Output = Result<Vec<PortId>, SdkError>> + Send + '_>> {
197 Box::pin(async { Ok(Vec::new()) })
198 }
199 fn delete(
200 &self,
201 _id: &PortId,
202 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
203 Box::pin(async { Ok(()) })
204 }
205}
206
207pub trait ConfigStore: Send + Sync + 'static {
222 fn get(&self, key: &str) -> Result<Option<PortValue>, SdkError>;
224
225 fn set(&self, key: &str, value: PortValue) -> Result<(), SdkError>;
227
228 fn list(&self) -> Result<Vec<(String, PortValue)>, SdkError>;
230
231 fn source(&self, _key: &str) -> Option<String> {
233 None
234 }
235}
236
237#[derive(Debug, Default, Clone, Copy)]
239pub struct NoopConfigStore;
240
241impl ConfigStore for NoopConfigStore {
242 fn get(&self, _key: &str) -> Result<Option<PortValue>, SdkError> {
243 Ok(None)
244 }
245 fn set(&self, _key: &str, _value: PortValue) -> Result<(), SdkError> {
246 Ok(())
247 }
248 fn list(&self) -> Result<Vec<(String, PortValue)>, SdkError> {
249 Ok(Vec::new())
250 }
251}
252
253pub trait AuthProvider: Send + Sync + 'static {
262 fn get_api_key(
264 &self,
265 provider: &str,
266 ) -> Pin<Box<dyn Future<Output = Result<Option<String>, SdkError>> + Send + '_>>;
267
268 fn get_api_key_sync(&self, _provider: &str) -> Result<Option<String>, SdkError> {
280 Ok(None)
281 }
282
283 fn set_api_key(
285 &self,
286 provider: &str,
287 key: &str,
288 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
289
290 fn delete_api_key(
292 &self,
293 provider: &str,
294 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
295
296 fn get_oauth(
298 &self,
299 provider: &str,
300 ) -> Pin<Box<dyn Future<Output = Result<Option<OAuthToken>, SdkError>> + Send + '_>>;
301
302 fn set_oauth(
304 &self,
305 provider: &str,
306 token: OAuthToken,
307 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
308
309 fn list_providers(
311 &self,
312 ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, SdkError>> + Send + '_>>;
313}
314
315#[derive(Debug, Default, Clone, Copy)]
317pub struct NoopAuthProvider;
318
319impl AuthProvider for NoopAuthProvider {
320 fn get_api_key(
321 &self,
322 _provider: &str,
323 ) -> Pin<Box<dyn Future<Output = Result<Option<String>, SdkError>> + Send + '_>> {
324 Box::pin(async { Ok(None) })
325 }
326 fn set_api_key(
327 &self,
328 _provider: &str,
329 _key: &str,
330 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
331 Box::pin(async {
332 Err(SdkError::PortNotConfigured {
333 port: "AuthProvider",
334 })
335 })
336 }
337 fn delete_api_key(
338 &self,
339 _provider: &str,
340 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
341 Box::pin(async { Ok(()) })
342 }
343 fn get_oauth(
344 &self,
345 _provider: &str,
346 ) -> Pin<Box<dyn Future<Output = Result<Option<OAuthToken>, SdkError>> + Send + '_>> {
347 Box::pin(async { Ok(None) })
348 }
349 fn set_oauth(
350 &self,
351 _provider: &str,
352 _token: OAuthToken,
353 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
354 Box::pin(async {
355 Err(SdkError::PortNotConfigured {
356 port: "AuthProvider",
357 })
358 })
359 }
360 fn list_providers(
361 &self,
362 ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, SdkError>> + Send + '_>> {
363 Box::pin(async { Ok(Vec::new()) })
364 }
365}
366
367pub type EventTopic = String;
373
374pub type EventPayload = serde_json::Value;
376
377pub trait EventBus: Send + Sync + 'static {
388 fn publish(
390 &self,
391 topic: &EventTopic,
392 payload: EventPayload,
393 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
394
395 fn subscribe(
397 &self,
398 topic: &EventTopic,
399 ) -> Pin<Box<dyn Future<Output = Result<SubscriptionHandle, SdkError>> + Send + '_>>;
400}
401
402pub struct SubscriptionHandle {
404 _unsubscribe: Option<Box<dyn FnOnce() + Send + Sync>>,
406 receiver: Option<tokio::sync::mpsc::Receiver<(EventTopic, EventPayload)>>,
408}
409
410impl SubscriptionHandle {
411 pub async fn recv(&mut self) -> Option<(EventTopic, EventPayload)> {
413 match &mut self.receiver {
414 Some(rx) => rx.recv().await,
415 None => None,
416 }
417 }
418}
419
420impl std::fmt::Debug for SubscriptionHandle {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 f.debug_struct("SubscriptionHandle")
423 .field("active", &self.receiver.is_some())
424 .finish()
425 }
426}
427
428impl SubscriptionHandle {
429 pub fn from_receiver(rx: tokio::sync::mpsc::Receiver<(EventTopic, EventPayload)>) -> Self {
433 Self {
434 _unsubscribe: None,
435 receiver: Some(rx),
436 }
437 }
438}
439
440pub struct InMemoryEventBus {
442 tx: tokio::sync::broadcast::Sender<(EventTopic, EventPayload)>,
443}
444
445impl InMemoryEventBus {
446 pub fn new(capacity: usize) -> Arc<Self> {
448 let (tx, _) = tokio::sync::broadcast::channel(capacity);
449 Arc::new(Self { tx })
450 }
451}
452
453impl EventBus for InMemoryEventBus {
454 fn publish(
455 &self,
456 topic: &EventTopic,
457 payload: EventPayload,
458 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
459 let _ = self.tx.send((topic.clone(), payload));
461 Box::pin(async { Ok(()) })
462 }
463 fn subscribe(
464 &self,
465 _topic: &EventTopic,
466 ) -> Pin<Box<dyn Future<Output = Result<SubscriptionHandle, SdkError>> + Send + '_>> {
467 let mut rx = self.tx.subscribe();
468 let (tx, rx2) = tokio::sync::mpsc::channel(64);
469 drop(tokio::spawn(async move {
470 while let Ok(event) = rx.recv().await {
471 if tx.send(event).await.is_err() {
472 break;
473 }
474 }
475 }));
476 Box::pin(async {
477 Ok(SubscriptionHandle {
478 _unsubscribe: None,
479 receiver: Some(rx2),
480 })
481 })
482 }
483}
484
485#[derive(Debug, Default, Clone, Copy)]
487pub struct NoopEventBus;
488
489impl EventBus for NoopEventBus {
490 fn publish(
491 &self,
492 _topic: &EventTopic,
493 _payload: EventPayload,
494 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
495 Box::pin(async { Ok(()) })
496 }
497 fn subscribe(
498 &self,
499 _topic: &EventTopic,
500 ) -> Pin<Box<dyn Future<Output = Result<SubscriptionHandle, SdkError>> + Send + '_>> {
501 Box::pin(async {
502 Ok(SubscriptionHandle {
503 _unsubscribe: None,
504 receiver: None,
505 })
506 })
507 }
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct SkillMeta {
517 pub name: String,
519 pub description: String,
521 pub path: PathBuf,
523 pub version: Option<String>,
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct Skill {
530 pub meta: SkillMeta,
532 pub body: String,
534}
535
536pub trait SkillLoader: Send + Sync + 'static {
538 fn list(&self) -> Pin<Box<dyn Future<Output = Result<Vec<SkillMeta>, SdkError>> + Send + '_>>;
540
541 fn load(
543 &self,
544 name: &str,
545 ) -> Pin<Box<dyn Future<Output = Result<Option<Skill>, SdkError>> + Send + '_>>;
546}
547
548#[derive(Debug, Default, Clone, Copy)]
550pub struct NoopSkillLoader;
551
552impl SkillLoader for NoopSkillLoader {
553 fn list(&self) -> Pin<Box<dyn Future<Output = Result<Vec<SkillMeta>, SdkError>> + Send + '_>> {
554 Box::pin(async { Ok(Vec::new()) })
555 }
556 fn load(
557 &self,
558 _name: &str,
559 ) -> Pin<Box<dyn Future<Output = Result<Option<Skill>, SdkError>> + Send + '_>> {
560 Box::pin(async { Ok(None) })
561 }
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct Persona {
571 pub name: String,
573 pub system_prompt: String,
575 pub preferred_model: Option<String>,
577 pub allowed_tools: Option<Vec<String>>,
579}
580
581pub trait PersonaProvider: Send + Sync + 'static {
583 fn list(&self) -> Pin<Box<dyn Future<Output = Result<Vec<Persona>, SdkError>> + Send + '_>>;
585 fn get(
587 &self,
588 name: &str,
589 ) -> Pin<Box<dyn Future<Output = Result<Option<Persona>, SdkError>> + Send + '_>>;
590}
591
592#[derive(Debug, Default, Clone, Copy)]
594pub struct NoopPersonaProvider;
595
596impl PersonaProvider for NoopPersonaProvider {
597 fn list(&self) -> Pin<Box<dyn Future<Output = Result<Vec<Persona>, SdkError>> + Send + '_>> {
598 Box::pin(async { Ok(Vec::new()) })
599 }
600 fn get(
601 &self,
602 _name: &str,
603 ) -> Pin<Box<dyn Future<Output = Result<Option<Persona>, SdkError>> + Send + '_>> {
604 Box::pin(async { Ok(None) })
605 }
606}
607
608#[derive(Debug, Clone, Serialize, Deserialize)]
614pub struct ToolCallRequest {
615 pub tool: String,
617 pub action: String,
619 pub cwd: PathBuf,
621 pub subject: String,
623}
624
625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
627pub enum AccessDecision {
628 Allow,
630 AllowWithAudit,
632 Deny {
634 reason: String,
636 },
637 RequireApproval {
639 reason: String,
641 },
642}
643
644pub trait AccessGate: Send + Sync + 'static {
646 fn check(
648 &self,
649 request: &ToolCallRequest,
650 ) -> Pin<Box<dyn Future<Output = Result<AccessDecision, SdkError>> + Send + '_>>;
651}
652
653#[derive(Debug, Default, Clone, Copy)]
655pub struct AllowAllAccessGate;
656
657impl AccessGate for AllowAllAccessGate {
658 fn check(
659 &self,
660 _request: &ToolCallRequest,
661 ) -> Pin<Box<dyn Future<Output = Result<AccessDecision, SdkError>> + Send + '_>> {
662 Box::pin(async { Ok(AccessDecision::Allow) })
663 }
664}
665
666pub trait CapabilityResolver: Send + Sync + 'static {
672 fn visible_tools(
674 &self,
675 subject: &str,
676 ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, SdkError>> + Send + '_>>;
677}
678
679#[derive(Debug, Default, Clone, Copy)]
681pub struct EmptyCapabilityResolver;
682
683impl CapabilityResolver for EmptyCapabilityResolver {
684 fn visible_tools(
685 &self,
686 _subject: &str,
687 ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, SdkError>> + Send + '_>> {
688 Box::pin(async { Ok(Vec::new()) })
689 }
690}
691
692#[derive(Debug, Clone, Serialize, Deserialize)]
698pub struct MemoryEntry {
699 pub id: String,
701 pub subject: String,
703 pub kind: String,
705 pub embedding: Option<Vec<f32>>,
707 pub content: PortValue,
709 pub created_at: chrono::DateTime<chrono::Utc>,
711}
712
713pub trait MemoryStore: Send + Sync + 'static {
715 fn put(
717 &self,
718 entry: MemoryEntry,
719 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
720 fn search(
722 &self,
723 _query: &[f32],
724 _k: usize,
725 ) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryEntry>, SdkError>> + Send + '_>> {
726 Box::pin(async { Ok(Vec::new()) })
727 }
728 fn list(
730 &self,
731 subject: &str,
732 ) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryEntry>, SdkError>> + Send + '_>>;
733 fn delete(&self, _id: &str) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
739 Box::pin(async {
740 Err(SdkError::PortNotConfigured {
741 port: "MemoryStore",
742 })
743 })
744 }
745}
746
747#[derive(Debug, Default, Clone, Copy)]
749pub struct NoopMemoryStore;
750
751impl MemoryStore for NoopMemoryStore {
752 fn put(
753 &self,
754 _entry: MemoryEntry,
755 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
756 Box::pin(async {
757 Err(SdkError::PortNotConfigured {
758 port: "MemoryStore",
759 })
760 })
761 }
762 fn list(
763 &self,
764 _subject: &str,
765 ) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryEntry>, SdkError>> + Send + '_>> {
766 Box::pin(async { Ok(Vec::new()) })
767 }
768}
769
770#[derive(Debug, Clone, Serialize, Deserialize)]
776pub struct CronJob {
777 pub id: String,
779 pub schedule: String,
781 pub action: String,
783 pub payload: Option<PortValue>,
785}
786
787pub trait CronScheduler: Send + Sync + 'static {
789 fn register(
791 &self,
792 job: CronJob,
793 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
794 fn unregister(
796 &self,
797 id: &str,
798 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>>;
799 fn list(&self) -> Pin<Box<dyn Future<Output = Result<Vec<CronJob>, SdkError>> + Send + '_>>;
801}
802
803#[derive(Debug, Default, Clone, Copy)]
805pub struct NoopCronScheduler;
806
807impl CronScheduler for NoopCronScheduler {
808 fn register(
809 &self,
810 _job: CronJob,
811 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
812 Box::pin(async {
813 Err(SdkError::PortNotConfigured {
814 port: "CronScheduler",
815 })
816 })
817 }
818 fn unregister(
819 &self,
820 _id: &str,
821 ) -> Pin<Box<dyn Future<Output = Result<(), SdkError>> + Send + '_>> {
822 Box::pin(async { Ok(()) })
823 }
824 fn list(&self) -> Pin<Box<dyn Future<Output = Result<Vec<CronJob>, SdkError>> + Send + '_>> {
825 Box::pin(async { Ok(Vec::new()) })
826 }
827}
828
829#[derive(Debug, Clone, Default, Serialize, Deserialize)]
835pub struct ResourceUsage {
836 pub cpu_percent: f32,
838 pub memory_bytes: u64,
840 pub disk_bytes: u64,
842 pub active_agents: usize,
844 pub tokens_consumed: u64,
846}
847
848pub trait ResourceMonitor: Send + Sync + 'static {
850 fn snapshot(
852 &self,
853 ) -> Pin<Box<dyn Future<Output = Result<ResourceUsage, SdkError>> + Send + '_>>;
854 fn is_over_budget(&self) -> Pin<Box<dyn Future<Output = Result<bool, SdkError>> + Send + '_>> {
856 Box::pin(async { Ok(false) })
857 }
858}
859
860#[derive(Debug, Default, Clone, Copy)]
862pub struct NoopResourceMonitor;
863
864impl ResourceMonitor for NoopResourceMonitor {
865 fn snapshot(
866 &self,
867 ) -> Pin<Box<dyn Future<Output = Result<ResourceUsage, SdkError>> + Send + '_>> {
868 Box::pin(async { Ok(ResourceUsage::default()) })
869 }
870}
871
872#[derive(Debug, Clone, Serialize, Deserialize)]
878pub struct ResolvedUrl {
879 pub url: String,
881 pub content: String,
883 pub content_type: String,
885 pub size: Option<usize>,
887 pub source_path: Option<String>,
889 pub notes: Vec<String>,
891 pub immutable: bool,
893}
894
895#[derive(Debug, Clone, Default)]
897pub struct ResolveContext {
898 pub cwd: Option<PathBuf>,
900 pub session_id: Option<String>,
902}
903
904pub trait InternalUrlRouter: Send + Sync + 'static {
906 fn resolve<'a>(
908 &'a self,
909 uri: &'a str,
910 ctx: &'a ResolveContext,
911 ) -> Pin<Box<dyn Future<Output = Result<ResolvedUrl, SdkError>> + Send + 'a>>;
912
913 fn schemes(&self) -> &[&str] {
915 &[]
916 }
917
918 fn registered_schemes(&self) -> Vec<String> {
920 Vec::new()
921 }
922}
923
924#[derive(Debug, Default, Clone, Copy)]
926pub struct NoopInternalUrlRouter;
927
928impl InternalUrlRouter for NoopInternalUrlRouter {
929 fn resolve<'a>(
930 &'a self,
931 _uri: &'a str,
932 _ctx: &'a ResolveContext,
933 ) -> Pin<Box<dyn Future<Output = Result<ResolvedUrl, SdkError>> + Send + 'a>> {
934 Box::pin(async {
935 Err(SdkError::PortNotConfigured {
936 port: "InternalUrlRouter",
937 })
938 })
939 }
940}
941
942#[async_trait]
945pub trait ProtocolHandler: Send + Sync {
946 fn scheme(&self) -> &str;
948 fn immutable(&self) -> bool {
950 false
951 }
952 async fn resolve(
954 &self,
955 url: &str,
956 selector: Option<&str>,
957 ctx: &ResolveContext,
958 ) -> Result<ResolvedUrl, SdkError>;
959}
960
961#[derive(Debug, Clone, Serialize, Deserialize)]
963pub struct UrlCompletion {
964 pub value: String,
966 pub label: Option<String>,
968 pub description: Option<String>,
970}
971
972#[derive(Debug, Clone, Default)]
974pub struct LineMap {
975 pub total_lines: u32,
977 pub displayable: Option<Vec<(u32, u32)>>,
979}
980
981#[derive(Debug, Clone)]
987pub struct Rule {
988 pub name: String,
990 pub content: String,
992 pub description: Option<String>,
994 pub condition: Vec<regex::Regex>,
996 pub scope: Vec<ScopeToken>,
998 pub interrupt_mode: InterruptMode,
1000 pub globs: Vec<String>,
1002 pub always_apply: bool,
1004 pub source: RuleSource,
1006}
1007
1008#[derive(Debug, Clone)]
1010pub enum ScopeToken {
1011 Text,
1013 Thinking,
1015 Tool {
1017 name: String,
1019 globs: Vec<String>,
1021 },
1022}
1023
1024#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1026pub enum InterruptMode {
1027 Never,
1029 ProseOnly,
1031 ToolOnly,
1033 Always,
1035}
1036
1037#[derive(Debug, Clone)]
1039pub enum RuleSource {
1040 BuiltinDefaults,
1042 Project,
1044 User,
1046}
1047
1048pub trait RuleRegistry: Send + Sync + 'static {
1050 fn rules<'a>(&'a self) -> Pin<Box<dyn Future<Output = Vec<Rule>> + Send + 'a>>;
1052 fn mark_injected(&self, _name: &str, _turn: u64) {}
1054 fn injected_records(&self) -> Vec<(String, u64)> {
1056 Vec::new()
1057 }
1058 fn restore(&self, _records: Vec<(String, u64)>) {}
1060}
1061
1062#[derive(Default)]
1064pub struct NoopRuleRegistry;
1065
1066impl RuleRegistry for NoopRuleRegistry {
1067 fn rules<'a>(&'a self) -> Pin<Box<dyn Future<Output = Vec<Rule>> + Send + 'a>> {
1068 Box::pin(async { Vec::new() })
1069 }
1070}
1071
1072pub trait EmbeddingProvider: Send + Sync + 'static {
1078 fn embed<'a>(
1080 &'a self,
1081 text: &'a str,
1082 ) -> Pin<Box<dyn Future<Output = Result<Vec<f32>, SdkError>> + Send + 'a>>;
1083}
1084
1085pub struct NoopEmbeddingProvider;
1087
1088impl EmbeddingProvider for NoopEmbeddingProvider {
1089 fn embed<'a>(
1090 &'a self,
1091 _text: &'a str,
1092 ) -> Pin<Box<dyn Future<Output = Result<Vec<f32>, SdkError>> + Send + 'a>> {
1093 Box::pin(async {
1094 Err(SdkError::PortNotConfigured {
1095 port: "EmbeddingProvider",
1096 })
1097 })
1098 }
1099}
1100
1101#[derive(Clone)]
1114pub struct PortRegistry {
1115 pub state: Arc<dyn StateStore>,
1117 pub config: Arc<dyn ConfigStore>,
1119 pub auth: Arc<dyn AuthProvider>,
1121 pub event_bus: Arc<dyn EventBus>,
1123 pub skills: Arc<dyn SkillLoader>,
1125 pub personas: Arc<dyn PersonaProvider>,
1127 pub access: Arc<dyn AccessGate>,
1129 pub capabilities: Arc<dyn CapabilityResolver>,
1131 pub memory: Arc<dyn MemoryStore>,
1133 pub cron: Arc<dyn CronScheduler>,
1135 pub resources: Arc<dyn ResourceMonitor>,
1137 pub catalog: Arc<dyn catalog::ModelCatalog>,
1140 pub url_router: Arc<dyn InternalUrlRouter>,
1143 pub rules: Arc<dyn RuleRegistry>,
1146 pub embeddings: Arc<dyn EmbeddingProvider>,
1149 pub hooks: Arc<dyn HookRunner>,
1152}
1153
1154impl std::fmt::Debug for PortRegistry {
1155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1156 f.debug_struct("PortRegistry")
1157 .field("state", &"<dyn StateStore>")
1158 .field("config", &"<dyn ConfigStore>")
1159 .field("auth", &"<dyn AuthProvider>")
1160 .field("event_bus", &"<dyn EventBus>")
1161 .field("skills", &"<dyn SkillLoader>")
1162 .field("personas", &"<dyn PersonaProvider>")
1163 .field("access", &"<dyn AccessGate>")
1164 .field("capabilities", &"<dyn CapabilityResolver>")
1165 .field("memory", &"<dyn MemoryStore>")
1166 .field("cron", &"<dyn CronScheduler>")
1167 .field("resources", &"<dyn ResourceMonitor>")
1168 .field("catalog", &"<dyn ModelCatalog>")
1169 .field("url_router", &"<dyn InternalUrlRouter>")
1170 .field("rules", &"<dyn RuleRegistry>")
1171 .field("embeddings", &"<dyn EmbeddingProvider>")
1172 .field("hooks", &"<dyn HookRunner>")
1173 .finish()
1174 }
1175}
1176
1177impl Default for PortRegistry {
1178 fn default() -> Self {
1179 Self::noop()
1180 }
1181}
1182
1183impl PortRegistry {
1184 pub fn noop() -> Self {
1187 Self {
1188 state: Arc::new(NoopStateStore),
1189 config: Arc::new(NoopConfigStore),
1190 auth: Arc::new(NoopAuthProvider),
1191 event_bus: Arc::new(NoopEventBus),
1192 skills: Arc::new(NoopSkillLoader),
1193 personas: Arc::new(NoopPersonaProvider),
1194 access: Arc::new(AllowAllAccessGate),
1195 capabilities: Arc::new(EmptyCapabilityResolver),
1196 memory: Arc::new(NoopMemoryStore),
1197 cron: Arc::new(NoopCronScheduler),
1198 resources: Arc::new(NoopResourceMonitor),
1199 catalog: catalog::NoopModelCatalog::new(),
1200 url_router: Arc::new(NoopInternalUrlRouter),
1201 rules: Arc::new(NoopRuleRegistry),
1202 embeddings: Arc::new(NoopEmbeddingProvider),
1203 hooks: Arc::new(NoopHookRunner),
1204 }
1205 }
1206
1207 pub async fn from_directory(_dir: &Path) -> Self {
1215 Self::noop()
1219 }
1220}
1221
1222#[cfg(test)]
1227mod tests {
1228 use super::*;
1229 use serde_json::json;
1230
1231 #[tokio::test]
1232 async fn noop_state_store_load_returns_none() {
1233 let s = NoopStateStore;
1234 assert!(s.load(&"x".into()).await.unwrap().is_none());
1235 assert!(s.list("").await.unwrap().is_empty());
1236 }
1237
1238 #[tokio::test]
1239 async fn noop_state_store_append_errors() {
1240 let s = NoopStateStore;
1241 let err = s.append(json!({})).await.unwrap_err();
1242 assert!(matches!(
1243 err,
1244 SdkError::PortNotConfigured { port: "StateStore" }
1245 ));
1246 }
1247
1248 #[test]
1249 fn noop_config_get_returns_none() {
1250 let c = NoopConfigStore;
1251 assert!(c.get("any").unwrap().is_none());
1252 assert!(c.list().unwrap().is_empty());
1253 }
1254
1255 #[tokio::test]
1256 async fn noop_auth_get_api_key_returns_none() {
1257 let a = NoopAuthProvider;
1258 assert!(a.get_api_key("anthropic").await.unwrap().is_none());
1259 assert!(a.list_providers().await.unwrap().is_empty());
1260 }
1261
1262 #[tokio::test]
1263 async fn in_memory_event_bus_round_trip() {
1264 let bus = InMemoryEventBus::new(8);
1265 bus.publish(&"test".to_string(), json!({"hello": "world"}))
1266 .await
1267 .unwrap();
1268 let mut sub = bus.subscribe(&"test".to_string()).await.unwrap();
1269 bus.publish(&"test".to_string(), json!({"k": 1}))
1271 .await
1272 .unwrap();
1273 let (topic, payload) = sub.recv().await.unwrap();
1274 assert_eq!(topic, "test");
1275 assert_eq!(payload, json!({"k": 1}));
1276 }
1277
1278 #[tokio::test]
1279 async fn noop_event_bus_publish_succeeds_but_subscribes_return_none() {
1280 let bus = NoopEventBus;
1281 bus.publish(&"x".to_string(), json!({})).await.unwrap();
1282 let mut sub = bus.subscribe(&"x".to_string()).await.unwrap();
1283 assert!(sub.recv().await.is_none());
1284 }
1285
1286 #[test]
1287 fn default_registry_is_noop() {
1288 let reg = PortRegistry::default();
1289 assert!(Arc::strong_count(®.state) >= 1);
1291 }
1292
1293 #[test]
1294 fn oauth_token_bearer_constructor() {
1295 let t = OAuthToken::bearer("abc");
1296 assert_eq!(t.access_token, "abc");
1297 assert_eq!(t.token_type.as_deref(), Some("Bearer"));
1298 }
1299}
1300
1301pub mod fs;
1312pub mod inmem;