DeepSeek Harness: 声明依赖:inject

最后更新:2026-08-31

插件不是孤岛——大多数插件需要依赖其他插件提供的服务。inject 数组是 Cordis 的依赖声明机制,它确保依赖先于使用者加载,杜绝"服务不存在"的运行时错误。

💡 提示:inject 是声明式依赖——你只需告诉框架"我需要什么",框架负责按正确顺序加载。绝不手动控制加载顺序。

📋 前置知识:已完成 11-first-plugin.md,理解 apply 和 Context

1. 你将学到


2. inject 数组声明依赖

Inject 就绪机制

(1) ▶ 示例 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 和 ctx.llm 在 apply 调用时保证已就绪
  ctx.logger.info('tools ready:', !!ctx.tools)
  ctx.logger.info('llm ready:', !!ctx.llm)
}

inject 数组列出插件需要的所有服务名。框架确保这些服务在 apply 被调用前已经注册完成。

(2) ▶ 示例 2

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

(3) ▶ 示例 3

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

(4) 不声明 inject 的后果

TYPESCRIPT
// ❌ 不声明 inject,直接使用服务
export function apply(ctx: Context) {
  ctx.tools.register(...)  // 运行时错误:ctx.tools 可能不存在
}

不声明 inject 时,插件可能在依赖服务注册之前就被加载,导致 ctx.tools 为 undefined。


3. 内置服务列表

(1) 核心服务

DSH 通过内置插件提供以下服务:

服务名 提供者 功能
tools dsh-core 工具注册与执行
llm dsh-plugin-llm LLM 适配器
sessions dsh-core 会话管理
fs dsh-plugin-fs 文件系统操作
shell dsh-plugin-shell Shell 命令执行
sandbox dsh-plugin-sandbox 沙箱环境
search dsh-plugin-search 代码搜索
trajectory dsh-core 日志记录

(2) 服务访问方式

声明 inject 后,通过 ctx.服务名 访问:

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

export function apply(ctx: Context) {
  // ctx.tools — 工具服务
  ctx.tools.register({
    name: 'my_tool',
    // ...
  })

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

(3) 服务类型推断

TypeScript 会根据 inject 自动推断 ctx 上的服务类型:

TYPESCRIPT
// inject = ['tools'] → ctx.tools: ToolsService
// inject = ['llm']   → ctx.llm: LLMService
// inject = ['tools', 'llm'] → ctx.tools + ctx.llm 都有类型

4. 依赖加载顺序保证

(1) 拓扑排序

Cordis 根据所有插件的 inject 声明构建依赖图,然后按拓扑排序加载:

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

加载顺序:A → B → C

(2) 自动排序

你不需要手动控制加载顺序。即使在 cordis.yml 中 C 写在 A 前面:

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

框架仍然按 A → B → C 的顺序加载。

(3) 并行加载

没有依赖关系的插件可以并行加载:

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

A 和 B 可以同时加载,都完成后才加载 C。

(4) 加载阶段示意

TEXT 📖 仅展示
Phase 1: 加载无依赖插件 → [core, logger]
Phase 2: 加载依赖 Phase 1 的 → [tools, sessions]
Phase 3: 加载依赖 Phase 2 的 → [my-plugin, other-plugin]
...

5. 可选依赖

(1) 语法

在依赖名后加 ? 表示可选:

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

含义:tools 是必需依赖(缺失则加载失败),llm 是可选依赖(缺失时正常加载)。

(2) 可选依赖的访问

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

export function apply(ctx: Context) {
  // tools 一定存在
  ctx.tools.register(...)

  // llm 可能不存在
  if (ctx.llm) {
    ctx.llm.complete(...)
  } else {
    ctx.logger.warn('llm not available, skipping LLM features')
  }
}

(3) 可选依赖的使用场景

场景 必需/可选 原因
工具注册必须用 tools 必需 核心功能
LLM 能力增强 可选 没有也能工作
沙箱功能 可选 不是所有环境都有沙箱
日志服务 必需 基础设施

(4) 运行时检测

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. 循环依赖检测

(1) 什么是循环依赖

A 依赖 B,B 又依赖 A:

TEXT 📖 仅展示
A inject: ['B']
B inject: ['A']

这形成死锁:A 等 B 加载,B 等 A 加载,谁也加载不了。

(2) Cordis 的检测机制

框架在启动时检查依赖图,发现循环后立即报错:

TEXT 📖 仅展示
Error: Circular dependency detected:
  plugin-a → plugin-b → plugin-a
  
Please review your inject declarations.

(3) 解决循环依赖

方案 1:提取公共依赖

TEXT 📖 仅展示
Before:  A → B → A
After:   A → C, B → C

将 A 和 B 共同需要的逻辑提取到 C 中。

方案 2:使用事件解耦

TYPESCRIPT
// A 不直接依赖 B,而是监听事件
export const inject = []

export function apply(ctx: Context) {
  ctx.on('b/ready', (bService) => {
    // A 使用 B 的能力,但不声明依赖
  })
}

方案 3:使用可选依赖

TYPESCRIPT
// A 可选依赖 B
export const inject = ['B?']

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

(4) 三节点循环

TEXT 📖 仅展示
A → B → C → A

Cordis 同样能检测多节点循环。报错信息会显示完整链路。


7. 依赖注入的底层机制

(1) 服务注册与发现

TYPESCRIPT
// Provider 插件注册服务
ctx.provide('tools', toolsInstance)

// Consumer 插件发现服务
const tools = ctx.get('tools')

(2) inject 与 apply 的时序

100%
sequenceDiagram
    participant F as Framework
    participant P as Provider Plugin
    participant C as Consumer Plugin
    
    F->>P: 加载 Provider
    P->>F: apply() → 注册 'tools' 服务
    F->>C: 检查 inject ['tools'] ✅ 已就绪
    F->>C: 调用 apply()
    C->>F: ctx.tools 可用

(3) 依赖未就绪时

100%
sequenceDiagram
    participant F as Framework
    participant C as Consumer Plugin
    
    F->>F: 检查 inject ['tools'] ❌ 未就绪
    F->>C: 插件进入 pending 状态
    Note over F: 等待 tools 服务注册
    F->>F: tools 服务注册完成
    F->>C: 重新检查 ✅ → 调用 apply()

插件不会因依赖缺失而被丢弃,而是进入 pending 状态,等依赖就绪后自动激活。

(4) 类型安全的依赖注入

TYPESCRIPT
// 框架内部类型映射
interface Context {
  tools: ToolsService      // 当 inject 包含 'tools'
  llm: LLMService          // 当 inject 包含 'llm'
  sessions: SessionService // 当 inject 包含 'sessions'
  // ...
}

TypeScript 的条件类型机制根据 inject 数组自动扩展 ctx 的类型定义,确保编译期类型安全。


❓ 常见问题

Q inject 数组的顺序有影响吗?
A 没有。inject 只是声明"我需要这些服务",加载顺序由框架根据全局依赖图决定。
Q 忘记声明 inject 但使用了服务会怎样?
A 编译期不会报错(TypeScript 可能有 warn),运行时 ctx 上的对应属性可能为 undefined,导致 TypeError。始终声明你使用的服务。
Q 一个插件可以依赖多少个服务?
A 没有硬性上限。但依赖过多通常意味着插件职责不清晰,考虑拆分。
Q 可选依赖的服务后来注册了,pending 的插件会自动激活吗?
A 会。Cordis 监听服务注册事件,依赖就绪后自动将 pending 插件转为 active。
Q 如何查看当前已注册的所有服务?
A bash pnpm dsh web --patch --dump-config # 或在运行时 ctx.logger.info(Object.keys(ctx.services))
Q inject 和 import 有什么区别?
A import 是 TypeScript 的静态模块引用,编译期确定。inject 是运行时服务依赖,由 Cordis 框架在插件加载时解析。两者互补:import 引入类型和工具函数,inject 声明运行时服务依赖。 ---

📖 小节


📝 作业

1. ⭐ 基础题:编写一个插件,声明 inject: ['tools'],在 apply 中用 ctx.tools.register 注册一个简单工具。启动后验证工具可用。

2. ⭐⭐ 进阶题:编写一个插件,声明 inject: ['tools', 'llm?']。当 llm 可用时,工具调用 LLM 增强;不可用时,工具返回降级结果。分别测试两种场景。

3. ⭐⭐⭐ 挑战题:故意创建两个互相依赖的插件 A(inject: ['B'])和 B(inject: ['A']),观察 Cordis 的循环依赖报错信息。然后用事件解耦的方式重写,消除循环依赖,验证两个插件都能正常加载。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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