DeepSeek Harness: 依存関係の宣言:inject

最終更新:2026-08-31

プラグインは孤島ではない——ほとんどのプラグインは他のプラグインが提供するサービスを必要とします。inject 配列は Cordis の依存関係宣言機構で、依存するサービスがコンシューマーより先にロードされることを保証し、「サービスが存在しない」ランタイムエラーを排除します。

💡 ヒント:inject は宣言的依存——フレームワークに「何が必要か」を伝えるだけで、ロード順序はフレームワークが処理します。手動でロード順序を制御してはいけません。

📋 前提知識11-first-plugin.md の完了、apply と Context を理解していること

1. 学習内容

Inject レディメカニズム


2. 依存関係を宣言する inject 配列

▶ サンプル 1:

TYPESCRIPT
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:

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

▶ サンプル 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 が 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 でサービスにアクセス:

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 📖 参照専用
フェーズ 1:依存関係のないプラグインをロード → [core, logger]
フェーズ 2:フェーズ 1 に依存するプラグインをロード → [tools, sessions]
フェーズ 3:フェーズ 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 📖 参照専用
変更前:A → B → A
変更後: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) 3ノードの循環

TEXT 📖 参照専用
A → B → C → A

Cordis は多ノードの循環も検出できます。エラーメッセージには完全なチェーンが表示されます。


7. 依存性注入の基盤機構

(1) サービスの登録と検出

TYPESCRIPT
// プロバイダプラグインがサービスを登録
ctx.provide('tools', toolsInstance)

// コンシューマプラグインがサービスを検出
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: プラグインは保留状態に
    Note over F: tools サービスの登録を待機
    F->>F: tools サービスが登録された
    F->>C: 再チェック ✅ → apply() を呼び出し

依存関係が欠落している場合、プラグインは破棄されず——保留状態に入り、依存関係が準備でき次第自動的にアクティブになります。

(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 は警告する可能性)、ランタイムで ctx 上の対応プロパティが undefined になり TypeError が発生します。使用するサービスは必ず宣言してください。
Q プラグインはいくつのサービスに依存できますか?
A ハードリミットはありません。しかし、依存関係が多すぎるのはプラグインの責任が不明確な兆候——分割を検討してください。
Q オプション依存関係のサービスが後で登録された場合、保留中のプラグインは自動的にアクティブになりますか?
A はい。Cordis はサービス登録イベントを監視し、依存関係が準備でき次第保留中のプラグインをアクティブに移行します。
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. ⭐⭐⭐ チャレンジ:意図的に相互依存する2つのプラグイン A(inject: ['B'])と B(inject: ['A'])を作成し、Cordis の循環依存エラーを観察してください。その後、イベントによる疎結合化で書き直して循環依存を排除し、両方のプラグインが正常にロードされることを確認すること。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%