DeepSeek Harness: Declaring Dependencies: inject

Last updated: 2026-08-31

Plugins aren't islands — most plugins need services provided by other plugins. The inject array is Cordis's dependency declaration mechanism, ensuring dependencies load before their consumers and eliminating "service doesn't exist" runtime errors.

💡 Tip: inject is declarative dependency — you just tell the framework "what I need," and it handles loading in the correct order. Never manually control loading order.

📋 Prerequisites: Completed 11-first-plugin.md, understand apply and Context

1. What You'll Learn


Inject Ready

2. inject Array for Declaring Dependencies

(1) ▶ Example 1

TYPESCRIPT
import { Context } from '@deepseek-ai/cordis'

export const name = 'my-tool'
export const inject = ['tools', 'llm']

export function apply(ctx: Context) {
  // ctx.tools and ctx.llm are guaranteed to be ready when apply is called
  ctx.logger.info('tools ready:', !!ctx.tools)
  ctx.logger.info('llm ready:', !!ctx.llm)
}

The inject array lists all service names the plugin needs. The framework ensures these services are registered before apply is called.

(2) ▶ Example 2

TYPESCRIPT
export default {
  name: 'my-tool',
  inject: ['tools', 'llm'],
  apply(ctx: Context) {
    // ...
  }
}

(3) ▶ Example 3

TYPESCRIPT
export default class MyPlugin {
  static name = 'my-tool'
  static inject = ['tools', 'llm']
  
  constructor(private ctx: Context) {
    // ...
  }
}

(4) Consequences of Not Declaring inject

TYPESCRIPT
// ❌ Not declaring inject, using services directly
export function apply(ctx: Context) {
  ctx.tools.register(...)  // Runtime error: ctx.tools may not exist
}

Without declaring inject, the plugin may load before dependent services are registered, causing ctx.tools to be undefined.


3. Built-in Service List

(1) Core Services

DSH provides the following services through built-in plugins:

Service Name Provider Function
tools dsh-core Tool registration and execution
llm dsh-plugin-llm LLM adapter
sessions dsh-core Session management
fs dsh-plugin-fs File system operations
shell dsh-plugin-shell Shell command execution
sandbox dsh-plugin-sandbox Sandbox environment
search dsh-plugin-search Code search
trajectory dsh-core Log recording

(2) Service Access Method

After declaring inject, access services via ctx.serviceName:

TYPESCRIPT
export const inject = ['tools', 'llm']

export function apply(ctx: Context) {
  // ctx.tools — Tool service
  ctx.tools.register({
    name: 'my_tool',
    // ...
  })

  // ctx.llm — LLM service
  const response = await ctx.llm.complete({
    messages: [{ role: 'user', content: 'hello' }]
  })
}

(3) Service Type Inference

TypeScript automatically infers service types on ctx based on inject:

TYPESCRIPT
// inject = ['tools'] → ctx.tools: ToolsService
// inject = ['llm']   → ctx.llm: LLMService
// inject = ['tools', 'llm'] → both ctx.tools + ctx.llm are typed

4. Dependency Loading Order Guarantee

(1) Topological Sorting

Cordis builds a dependency graph from all plugins' inject declarations, then loads in topological order:

100%
graph LR
    A[plugin-a<br/>inject: []] --> B[plugin-b<br/>inject: ['a']]
    B --> C[plugin-c<br/>inject: ['a', 'b']]

Loading order: A → B → C

(2) Automatic Ordering

You don't need to manually control loading order. Even if C appears before A in cordis.yml:

YAML
plugins:
  plugin-c: ...
  plugin-a: ...
  plugin-b: ...

The framework still loads in A → B → C order.

(3) Parallel Loading

Plugins with no dependency relationships can load in parallel:

100%
graph TB
    A[plugin-a] --> C[plugin-c<br/>inject: a, b]
    B[plugin-b] --> C

A and B can load simultaneously; C only loads after both complete.

(4) Loading Phases

TEXT 📖 Display only
Phase 1: Load plugins with no dependencies → [core, logger]
Phase 2: Load plugins depending on Phase 1 → [tools, sessions]
Phase 3: Load plugins depending on Phase 2 → [my-plugin, other-plugin]
...

5. Optional Dependencies

(1) Syntax

Append ? to a dependency name to make it optional:

TYPESCRIPT
export const inject = ['tools', 'llm?']

Meaning: tools is a required dependency (missing causes load failure), llm is optional (missing still loads normally).

(2) Accessing Optional Dependencies

TYPESCRIPT
export const inject = ['tools', 'llm?']

export function apply(ctx: Context) {
  // tools always exists
  ctx.tools.register(...)

  // llm may not exist
  if (ctx.llm) {
    ctx.llm.complete(...)
  } else {
    ctx.logger.warn('llm not available, skipping LLM features')
  }
}

(3) Optional Dependency Use Cases

Scenario Required/Optional Reason
Tool registration must use tools Required Core functionality
LLM capability enhancement Optional Works without it
Sandbox functionality Optional Not all environments have sandbox
Logging service Required Infrastructure

(4) Runtime Detection

TYPESCRIPT
export const inject = ['tools', 'search?']

export function apply(ctx: Context) {
  ctx.tools.register({
    name: 'smart_search',
    async execute(params) {
      if (ctx.search) {
        return ctx.search.query(params.query)
      }
      return 'search service not available'
    }
  })
}

6. Circular Dependency Detection

(1) What Is a Circular Dependency

A depends on B, and B depends on A:

TEXT 📖 Display only
A inject: ['B']
B inject: ['A']

This creates a deadlock: A waits for B, B waits for A — neither can load.

(2) Cordis's Detection Mechanism

The framework checks the dependency graph at startup and immediately reports circular dependencies:

TEXT 📖 Display only
Error: Circular dependency detected:
  plugin-a → plugin-b → plugin-a
  
Please review your inject declarations.

(3) Resolving Circular Dependencies

Solution 1: Extract Shared Dependencies

TEXT 📖 Display only
Before:  A → B → A
After:   A → C, B → C

Extract the logic both A and B need into C.

Solution 2: Decouple with Events

TYPESCRIPT
// A doesn't directly depend on B, but listens for events
export const inject = []

export function apply(ctx: Context) {
  ctx.on('b/ready', (bService) => {
    // A uses B's capabilities without declaring dependency
  })
}

Solution 3: Use Optional Dependencies

TYPESCRIPT
// A optionally depends on B
export const inject = ['B?']

export function apply(ctx: Context) {
  if (ctx.B) {
    // Use B
  }
}

(4) Three-Node Cycles

TEXT 📖 Display only
A → B → C → A

Cordis can also detect multi-node cycles. The error message shows the complete chain.


7. Dependency Injection Underlying Mechanism

(1) Service Registration and Discovery

TYPESCRIPT
// Provider plugin registers service
ctx.provide('tools', toolsInstance)

// Consumer plugin discovers service
const tools = ctx.get('tools')

(2) inject and apply Timing

100%
sequenceDiagram
    participant F as Framework
    participant P as Provider Plugin
    participant C as Consumer Plugin
    
    F->>P: Load Provider
    P->>F: apply() → Register 'tools' service
    F->>C: Check inject ['tools'] ✅ Ready
    F->>C: Call apply()
    C->>F: ctx.tools available

(3) When Dependencies Aren't Ready

100%
sequenceDiagram
    participant F as Framework
    participant C as Consumer Plugin
    
    F->>F: Check inject ['tools'] ❌ Not ready
    F->>C: Plugin enters pending state
    Note over F: Waiting for tools service registration
    F->>F: tools service registered
    F->>C: Re-check ✅ → Call apply()

Plugins aren't discarded when dependencies are missing — they enter a pending state and automatically activate when dependencies become ready.

(4) Type-Safe Dependency Injection

TYPESCRIPT
// Framework internal type mapping
interface Context {
  tools: ToolsService      // When inject includes 'tools'
  llm: LLMService          // When inject includes 'llm'
  sessions: SessionService // When inject includes 'sessions'
  // ...
}

TypeScript's conditional type mechanism automatically extends ctx's type definition based on the inject array, ensuring compile-time type safety.


❓ FAQ

Q Does the order of the inject array matter?
A No. inject just declares "I need these services"; the framework determines loading order based on the global dependency graph.
Q What happens if I forget to declare inject but use a service?
A No compile-time error (TypeScript may warn), but at runtime the corresponding property on ctx may be undefined, causing TypeError. Always declare the services you use.
Q How many services can a plugin depend on?
A No hard limit. But too many dependencies usually means the plugin's responsibilities aren't clear — consider splitting it.
Q If an optional dependency's service is registered later, will the pending plugin automatically activate?
A Yes. Cordis monitors service registration events and automatically transitions pending plugins to active when dependencies are ready.
Q How can I see all currently registered services?
A bash pnpm dsh web --patch --dump-config # Or at runtime: ctx.logger.info(Object.keys(ctx.services))
Q What's the difference between inject and import?
A import is TypeScript's static module reference, determined at compile time. inject is runtime service dependency, resolved by the Cordis framework during plugin loading. They complement each other: import brings in types and utility functions, inject declares runtime service dependencies.

📖 Summary


📝 Exercises

1. ⭐ Basic: Write a plugin declaring inject: ['tools'], register a simple tool using ctx.tools.register in apply. Start and verify the tool is available.

2. ⭐⭐ Intermediate: Write a plugin declaring inject: ['tools', 'llm?']. When llm is available, the tool calls LLM for enhancement; when unavailable, it returns degraded results. Test both scenarios.

3. ⭐⭐⭐ Challenge: Deliberately create two mutually dependent plugins A (inject: ['B']) and B (inject: ['A']), observe Cordis's circular dependency error. Then rewrite using event decoupling to eliminate the circular dependency, verifying both plugins load normally.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏