Dependency Injection
The OpenClaw CLI uses a straightforward dependency injection pattern. Through the CliDeps interface, channel send functions and other infrastructure capabilities are injected into commands.
CliDeps Interface
Source: src/cli/deps.ts
typescript
// src/cli/deps.ts (simplified)
interface CliDeps {
sendMessageWhatsApp: SendFunction;
sendMessageTelegram: SendFunction;
sendMessageDiscord: SendFunction;
sendMessageSlack: SendFunction;
sendMessageSignal: SendFunction;
sendMessageIMessage: SendFunction;
}CliDeps is a simple interface containing send functions for all channels.
Creating Dependencies
typescript
// src/cli/deps.ts (simplified)
function createDefaultDeps(): CliDeps {
return {
sendMessageWhatsApp: createWhatsAppSender(),
sendMessageTelegram: createTelegramSender(),
sendMessageDiscord: createDiscordSender(),
sendMessageSlack: createSlackSender(),
sendMessageSignal: createSignalSender(),
sendMessageIMessage: createIMessageSender(),
};
}The createDefaultDeps() factory function creates concrete implementations for all channel senders.
Bridging to the Infrastructure Layer
Source: src/cli/deps.ts
typescript
// Bridging CLI deps to infra layer
function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {
return {
sendWhatsApp: deps.sendMessageWhatsApp,
sendTelegram: deps.sendMessageTelegram,
sendDiscord: deps.sendMessageDiscord,
sendSlack: deps.sendMessageSlack,
sendSignal: deps.sendMessageSignal,
sendIMessage: deps.sendMessageIMessage,
};
}The bridging function converts the CLI layer's CliDeps into the infrastructure layer's OutboundSendDeps. Field names may differ, but the functionality is identical.
Why Dependency Injection?
- Testability -- Tests can inject mock send functions
- Decoupling -- CLI commands do not directly depend on specific channel SDKs
- Flexibility -- Implementations can be replaced without modifying command code
- Consistency -- All commands access channel capabilities through a unified interface
Test Example
typescript
// In tests, inject mock dependencies
const mockDeps: CliDeps = {
sendMessageTelegram: vi.fn().mockResolvedValue({ messageId: 1 }),
sendMessageDiscord: vi.fn().mockResolvedValue({ messageId: 2 }),
// ...
};
// Command uses injected deps
await sendCommand(mockDeps, { to: "123", message: "test" });
// Verify mock was called
expect(mockDeps.sendMessageTelegram).toHaveBeenCalledWith(/* ... */);Summary
- CliDeps interface contains send functions for all channels
- createDefaultDeps() creates concrete implementations
- createOutboundSendDeps() bridges to the infrastructure layer
- Dependency injection provides testability, decoupling, and flexibility
Next: Media Processing