Daydreams Logo

Services & Extensions

Understanding dependency injection, service providers, and modular extensions.

Daydreams uses a combination of Dependency Injection (DI), Service Providers, and an Extension system to manage dependencies, initialize components, and organize functionality in a modular way.

Dependency Injection (container.ts)

At the heart of the framework's modularity is a simple Dependency Injection container, created using createContainer(). The container is responsible for instantiating and providing access to various services and components throughout the agent's lifecycle.

Purpose:

  • Decouples components by removing the need for them to know how to create their dependencies.
  • Manages the lifecycle of services (e.g., ensuring only one instance of a database client exists).
  • Makes components like loggers, clients, or configuration easily accessible.

Core Methods:

  • container.register(token, factory): Registers a factory function. A new instance is created every time resolve is called for the token.
  • container.singleton(token, factory): Registers a factory function, but the instance is created only once on the first resolve call. Subsequent calls return the same instance.
  • container.instance(token, value): Registers a pre-existing object instance directly.
  • container.resolve<Type>(token): Retrieves the instance associated with the token. Throws an error if the token is not registered.
  • container.alias(aliasToken, originalToken): Creates an alternative name (aliasToken) to resolve an existing originalToken.
import { createContainer } from "@daydreamsai/core";
 
const container = createContainer();
 
// Register a singleton database client
container.singleton("dbClient", () => new DatabaseClient(process.env.DB_URI));
 
// Register a pre-created config object
const config = { apiKey: "123" };
container.instance("appConfig", config);
 
// Register a transient logger (new instance each time)
container.register(
  "requestLogger",
  () => new Logger({ level: LogLevel.DEBUG })
);
 
// Resolve dependencies
const db = container.resolve<DatabaseClient>("dbClient");
const cfg = container.resolve<typeof config>("appConfig");
const logger1 = container.resolve<Logger>("requestLogger");
const logger2 = container.resolve<Logger>("requestLogger"); // Different instance from logger1

The main Agent instance, Logger, TaskRunner, and other core components are typically registered within the container when createDreams is called.

Service Providers (serviceProvider.ts)

While you could register everything directly with the container, Daydreams uses a Service Provider pattern to organize the registration and initialization (booting) of related services, especially within extensions.

Purpose:

  • Groups related service registrations.
  • Provides a dedicated boot phase for initialization logic that might depend on other services already being registered (e.g., connecting a client after its configuration is registered).

Defining a Service Provider:

Use the service helper function:

import { service, type Container } from "@daydreamsai/core";
import { MyApiClient } from "./my-api-client";
 
const myApiService = service({
  // Optional: Register dependencies into the container.
  // Runs before the boot phase.
  register(container: Container) {
    container.singleton("apiConfig", () => ({ baseUrl: process.env.API_URL }));
    container.singleton(
      "apiClient",
      (c) => new MyApiClient(c.resolve("apiConfig"))
    );
  },
 
  // Optional: Perform initialization logic after registration.
  // Runs during agent.start() after all services are registered.
  async boot(container: Container) {
    const apiClient = container.resolve<MyApiClient>("apiClient");
    await apiClient.connect(); // Example: Connect the client
    console.log("My API Client Connected!");
  },
});

Lifecycle:

  1. Registration: When a service provider is added to the agent (usually via an extension), its register method is called immediately by the ServiceManager (created internally in createDreams).
  2. Booting: When agent.start() is called, the ServiceManager iterates through all registered service providers and calls their boot methods after all register methods have completed.

Extensions (extension() helper)

Extensions are the primary mechanism for packaging and distributing reusable Daydreams functionality. They bundle together contexts, actions, inputs, outputs, and service providers.

Purpose:

  • Encapsulate features (e.g., Discord integration, ChromaDB support).
  • Simplify agent configuration by adding features with a single entry.
  • Promote code reuse.

Defining an Extension:

Use the extension helper function:

import {
  extension,
  context,
  action,
  input,
  output,
  service,
} from "@daydreamsai/core";
import { z } from "zod";
import { MyApiClient } from "./my-api-client"; // From previous example
 
// Assume myApiService is defined as above
 
const myFeatureContext = context({
  /* ... context definition ... */
});
const myFeatureAction = action({
  /* ... action definition ... */
});
 
export const myExtension = extension({
  // Required: A unique name for the extension
  name: "my-feature",
 
  // Optional: Service providers required by this extension
  services: [myApiService],
 
  // Optional: Context definitions provided by this extension
  contexts: {
    myFeature: myFeatureContext,
  },
 
  // Optional: Action definitions
  actions: [myFeatureAction],
 
  // Optional: Input definitions
  inputs: {
    "my-feature:event": input({
      /* ... input definition ... */
    }),
  },
 
  // Optional: Output definitions
  outputs: {
    "my-feature:notify": output({
      /* ... output definition ... */
    }),
  },
 
  // Optional: Events defined by this extension (used for typing ctx.emit)
  events: {
    myEvent: z.object({ id: z.string() }),
  },
 
  // Optional: Logic to run once when the extension is added during agent.start()
  async install(agent) {
    console.log("Installing My Feature Extension!");
    // Perform any one-time setup
  },
});

Usage and Lifecycle:

  1. Configuration: Pass extensions to createDreams in the extensions array:

    import { createDreams } from "@daydreamsai/core";
    import { myExtension } from "./my-extension";
    import { discord } from "@daydreamsai/discord"; // Example built-in
     
    const agent = createDreams({
      model: /* ... */,
      extensions: [
        myExtension,
        discord, // Add other extensions
      ]
    });
  2. Merging: When createDreams initializes, it merges all components (contexts, actions, inputs, outputs, events) defined within the extensions into the agent's main registry.

  3. Service Registration: It registers all services defined in the extensions with the ServiceManager.

  4. Installation & Booting: When agent.start() is called:

    • The install method of each extension is executed (if defined).
    • The ServiceManager boots all registered services (calling their boot methods).

Extensions provide a powerful way to structure agent capabilities, making it easy to combine built-in features (like CLI, Discord, Chroma) with custom logic.

On this page