DeepSeek Harness: 依存関係の宣言:inject
最終更新:2026-08-31
プラグインは孤島ではない——ほとんどのプラグインは他のプラグインが提供するサービスを必要とします。inject 配列は Cordis の依存関係宣言機構で、依存するサービスがコンシューマーより先にロードされることを保証し、「サービスが存在しない」ランタイムエラーを排除します。
📋 前提知識:11-first-plugin.md の完了、apply と Context を理解していること
1. 学習内容
- 依存関係を宣言する
inject配列 - 組み込みサービス一覧:tools、llm、sessions、fs、shell 等
- 依存関係のロード順序保証
- オプション依存関係
inject: ['tools', 'llm?'] - 循環依存の検出
- 依存性注入の基盤機構
2. 依存関係を宣言する inject 配列
▶ サンプル 1:
import { Context } from '@deepseek-ai/cordis'
export const name = 'my-tool'
export const inject = ['tools', 'llm']
export function apply(ctx: Context) {
// apply が呼び出される時点で ctx.tools と ctx.llm は確実に準備完了
ctx.logger.info('tools ready:', !!ctx.tools)
ctx.logger.info('llm ready:', !!ctx.llm)
}
inject 配列はプラグインが必要なすべてのサービス名を列挙します。フレームワークはこれらのサービスが apply 呼び出し前に登録されていることを保証します。
▶ サンプル 2:
export default {
name: 'my-tool',
inject: ['tools', 'llm'],
apply(ctx: Context) {
// ...
}
}
▶ サンプル 3:
export default class MyPlugin {
static name = 'my-tool'
static inject = ['tools', 'llm']
constructor(private ctx: Context) {
// ...
}
}
(4) inject を宣言しない場合の結果
// ❌ inject を宣言せず、サービスを直接使用
export function apply(ctx: Context) {
ctx.tools.register(...) // ランタイムエラー:ctx.tools が undefined の可能性
}
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 | シェルコマンド実行 |
sandbox |
dsh-plugin-sandbox | サンドボックス環境 |
search |
dsh-plugin-search | コード検索 |
trajectory |
dsh-core | ログ記録 |
(2) サービスへのアクセス方法
inject を宣言後、ctx.serviceName でサービスにアクセス:
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 上のサービス型を自動推論します:
// inject = ['tools'] → ctx.tools: ToolsService
// inject = ['llm'] → ctx.llm: LLMService
// inject = ['tools', 'llm'] → ctx.tools + ctx.llm 両方が型付き
4. 依存関係のロード順序保証
(1) トポロジカルソート
Cordis はすべてのプラグインの inject 宣言から依存グラフを構築し、トポロジカル順序でロードします:
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 より前に出現しても、手動でロード順序を制御する必要はありません:
plugins:
plugin-c: ...
plugin-a: ...
plugin-b: ...
フレームワークは依然として A → B → C の順序でロードします。
(3) 並列ロード
依存関係のないプラグインは並列にロードできます:
graph TB
A[plugin-a] --> C[plugin-c<br/>inject: a, b]
B[plugin-b] --> C
A と B は同時にロード可能;C は両方の完了後のみロードされます。
(4) ロードフェーズ
フェーズ 1:依存関係のないプラグインをロード → [core, logger]
フェーズ 2:フェーズ 1 に依存するプラグインをロード → [tools, sessions]
フェーズ 3:フェーズ 2 に依存するプラグインをロード → [my-plugin, other-plugin]
...
5. オプション依存関係
(1) 構文
依存関係名に ? を付けるとオプションになります:
export const inject = ['tools', 'llm?']
意味:tools は必須依存(欠落するとロード失敗)、llm はオプション(欠落しても正常にロード)。
(2) オプション依存関係へのアクセス
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) ランタイム検出
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 に依存する状態:
A inject: ['B']
B inject: ['A']
これはデッドロックを引き起こします:A は B を待ち、B は A を待つ——どちらもロードできません。
(2) Cordis の検出機構
フレームワークは起動時に依存グラフをチェックし、循環依存を即座に報告します:
Error: Circular dependency detected:
plugin-a → plugin-b → plugin-a
Please review your inject declarations.
(3) 循環依存の解決
解決策 1:共有依存関係の抽出
変更前:A → B → A
変更後:A → C, B → C
A と B の両方に必要なロジックを C に抽出します。
解決策 2:イベントによる疎結合化
// A は B に直接依存せず、イベントをリッスン
export const inject = []
export function apply(ctx: Context) {
ctx.on('b/ready', (bService) => {
// A は依存を宣言せずに B の機能を使用
})
}
解決策 3:オプション依存関係の使用
// A は B にオプションで依存
export const inject = ['B?']
export function apply(ctx: Context) {
if (ctx.B) {
// B を使用
}
}
(4) 3ノードの循環
A → B → C → A
Cordis は多ノードの循環も検出できます。エラーメッセージには完全なチェーンが表示されます。
7. 依存性注入の基盤機構
(1) サービスの登録と検出
// プロバイダプラグインがサービスを登録
ctx.provide('tools', toolsInstance)
// コンシューマプラグインがサービスを検出
const tools = ctx.get('tools')
(2) inject と apply のタイミング
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) 依存関係が準備できていない場合
sequenceDiagram
participant F as Framework
participant C as Consumer Plugin
F->>F: inject ['tools'] をチェック ❌ 未準備
F->>C: プラグインは保留状態に
Note over F: tools サービスの登録を待機
F->>F: tools サービスが登録された
F->>C: 再チェック ✅ → apply() を呼び出し
依存関係が欠落している場合、プラグインは破棄されず——保留状態に入り、依存関係が準備でき次第自動的にアクティブになります。
(4) 型安全な依存性注入
// フレームワーク内部の型マッピング
interface Context {
tools: ToolsService // inject に 'tools' が含まれる場合
llm: LLMService // inject に 'llm' が含まれる場合
sessions: SessionService // inject に 'sessions' が含まれる場合
// ...
}
TypeScript の条件型機構は inject 配列に基づいて ctx の型定義を自動拡張し、コンパイル時の型安全性を保証します。
❓ よくある質問
bash pnpm dsh web --patch --dump-config # またはランタイム:ctx.logger.info(Object.keys(ctx.services)) import は TypeScript の静的モジュール参照で、コンパイル時に決定されます。inject はランタイムサービス依存で、Cordis フレームワークがプラグインロード時に解決します。両者は補完関係:import は型とユーティリティ関数を、inject はランタイムサービス依存を宣言します。📖 まとめ
inject配列はプラグインのランタイム依存を宣言;フレームワークは依存関係が apply 前にロードされることを保証- 組み込みサービス:tools、llm、sessions、fs、shell、sandbox、search、trajectory
- 依存関係名に
?を付けるとオプション依存に;欠落してもプラグインは正常にロード - Cordis は循環依存を自動検出してエラーを報告;共有依存の抽出/イベントによる疎結合化/オプション依存で解決
- 依存関係が欠落している場合、プラグインは保留状態に入り、準備完了時に自動的にアクティブ化
- inject は宣言的依存——ロード順序の手動制御は禁止
📝 練習問題
1. ⭐ 基礎:inject: ['tools'] を宣言するプラグインを書き、apply 内で ctx.tools.register を使ってシンプルなツールを登録してください。起動してツールが利用可能であることを確認すること。
2. ⭐⭐ 応用:inject: ['tools', 'llm?'] を宣言するプラグインを書いてください。llm が利用可能な場合はツールが LLM を呼び出して強化;利用不可の場合はデグレード結果を返す。両方のシナリオをテストすること。
3. ⭐⭐⭐ チャレンジ:意図的に相互依存する2つのプラグイン A(inject: ['B'])と B(inject: ['A'])を作成し、Cordis の循環依存エラーを観察してください。その後、イベントによる疎結合化で書き直して循環依存を排除し、両方のプラグインが正常にロードされることを確認すること。