Extension Dependencies

Declare and manage dependencies between extensions.

Extensions can declare dependencies on other extensions, ensuring correct loading order and availability.

Declaring Dependencies

Use the dependencies() method:

impl Extension for MyExtension {
    fn dependencies(&self) -> Vec<&'static str> {
        vec!["users", "oauth"]
    }

    // ...
}

The runtime validates that all declared dependencies exist before proceeding.

Loading Order

Extensions load in priority order, but dependencies are validated regardless of priority:

  1. Extensions sorted by priority() (lower first)
  2. For each extension, verify all dependencies are registered
  3. If dependency missing, fail with LoaderError::MissingDependency

Priority vs Dependencies

Priority controls execution order within the same tier:

fn priority(&self) -> u32 {
    50  // Lower = loads first
}

Dependencies ensure availability:

fn dependencies(&self) -> Vec<&'static str> {
    vec!["users"]  // Must exist, regardless of priority
}

Best practice: Set priority lower than dependencies:

// users extension: priority 10
// oauth extension: priority 20, depends on users
// my extension: priority 50, depends on users and oauth

Cycle Detection

The runtime detects circular dependencies:

// This will fail:
// a depends on b
// b depends on c
// c depends on a

Error:

LoaderError::CircularDependency {
    chain: "a -> b -> c -> a"
}

Accessing Dependencies

Access other extensions via ExtensionContext:

fn router(&self, ctx: &dyn ExtensionContext) -> Option<ExtensionRouter> {
    // Check if dependency exists
    if !ctx.has_extension("users") {
        return None;
    }

    // Get extension
    let users_ext = ctx.get_extension("users")?;

    // Use extension capabilities...
}

Typed Dependencies

For compile-time dependency checking, use the typed extension system:

use systemprompt::extension::prelude::*;

// Define extension type
pub struct MyExtension;

impl ExtensionType for MyExtension {
    const ID: &'static str = "my-extension";
    const NAME: &'static str = "My Extension";
    const VERSION: &'static str = env!("CARGO_PKG_VERSION");
}

// Declare typed dependencies
impl Dependencies for MyExtension {
    type Deps = (UsersExtension, OAuthExtension);
}

The ExtensionBuilder validates these at compile time:

let registry = ExtensionBuilder::new()
    .extension(UsersExtension::default())
    .extension(OAuthExtension::default())
    .extension(MyExtension::default())  // Compiles only if deps registered first
    .build()?;

DependencyList Trait

pub trait DependencyList: 'static {
    fn validate(registry: &TypedExtensionRegistry) -> Result<(), MissingDependency>;
    fn dependency_ids() -> Vec<&'static str>;
}

Implemented for tuples:

impl<A: ExtensionType, B: ExtensionType> DependencyList for (A, B) {
    fn dependency_ids() -> Vec<&'static str> {
        vec![A::ID, B::ID]
    }
}

No Dependencies

For extensions without dependencies:

impl Dependencies for MyExtension {
    type Deps = ();
}

// Or use the marker trait
impl NoDependencies for MyExtension {}

Common Patterns

Core Dependencies

Most extensions depend on infrastructure:

fn dependencies(&self) -> Vec<&'static str> {
    vec!["database", "users"]
}

Feature Dependencies

Feature extensions depend on domain:

fn dependencies(&self) -> Vec<&'static str> {
    vec!["users", "content", "analytics"]
}

Optional Dependencies

Check at runtime for optional features:

fn router(&self, ctx: &dyn ExtensionContext) -> Option<ExtensionRouter> {
    let has_analytics = ctx.has_extension("analytics");

    // Conditionally add analytics routes
    if has_analytics {
        router = router.nest("/analytics", analytics_routes());
    }
}