- ホーム
- Documentation
- ランタイムツール
- カスタムツール
カスタムツール
カスタムツールは、組み込みツールと同じツール実行パイプラインに組み込まれる、モデルから呼び出し可能な関数です。
カスタムツールは、ファクトリーをエクスポートする TypeScript/JavaScript モジュールです。ファクトリーはホスト API(CustomToolAPI)を受け取り、1 つのツールまたはツールの配列を返します。
これが何であるか(そして何でないか)
Section titled “これが何であるか(そして何でないか)”- カスタムツール: ターン中にモデルから呼び出し可能(
execute+ TypeBox スキーマ)。 - Extension(拡張機能): ツールの登録やイベントのインターセプト/変更が可能なライフサイクル/イベントフレームワーク。
- Hook(フック): 外部のコマンド前後スクリプト。
- Skill(スキル): 静的なガイダンス/コンテキストパッケージ。実行可能なツールコードではない。
モデルからコードを直接呼び出す必要がある場合は、カスタムツールを使用してください。
現在のコードにおける統合パス
Section titled “現在のコードにおける統合パス”2 つのアクティブな統合スタイルがあります:
-
SDK 提供のカスタムツール (
options.customTools)CustomToolAdapterまたは拡張ラッパーを介してエージェントツールにラップされます。- SDK ブートストラップ時に常に初期アクティブツールセットに含まれます。
-
ローダー API 経由でファイルシステムから検出されるモジュール (
discoverAndLoadCustomTools/loadCustomTools)src/extensibility/custom-tools/loader.tsのライブラリ API として公開されています。- ホストコードはこれらを呼び出して、config/provider/plugin パスからツールモジュールを検出・読み込みできます。
モデルツール呼び出しフロー
LLM ツール呼び出し │ ▼ツールレジストリ(組み込み + カスタムツールアダプター) │ ▼CustomTool.execute(toolCallId, params, onUpdate, ctx, signal) │ ├─ onUpdate(...) -> ストリーミングされた部分的結果 └─ return result -> 最終ツールコンテンツ/詳細検出場所(ローダー API)
Section titled “検出場所(ローダー API)”discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames) は以下をマージします:
- ケーパビリティプロバイダー(
toolCapability)、以下を含む:- ネイティブ OMP 設定(
~/.xcsh/agent/tools、.xcsh/tools) - Claude 設定(
~/.claude/tools、.claude/tools) - Codex 設定(
~/.codex/tools、.codex/tools) - Claude マーケットプレイスプラグインキャッシュプロバイダー
- ネイティブ OMP 設定(
- インストール済みプラグインマニフェスト(プラグインローダー経由の
~/.xcsh/plugins/node_modules/*) - ローダーに渡された明示的に設定されたパス
- 解決されたパスの重複は除去されます。
- ツール名の競合は、組み込みツールおよびすでに読み込まれたカスタムツールに対して拒否されます。
.mdおよび.jsonファイルは一部のプロバイダーによってツールメタデータとして検出されますが、実行可能モジュールローダーはこれらを実行可能ツールとして拒否します。- 相対設定パスは
cwdから解決され、~は展開されます。
モジュールコントラクト
Section titled “モジュールコントラクト”カスタムツールモジュールは関数をエクスポートする必要があります(デフォルトエクスポートを推奨):
import type { CustomToolFactory } from "@f5-sales-demo/xcsh";
const factory: CustomToolFactory = (pi) => ({ name: "repo_stats", label: "Repo Stats", description: "Counts tracked TypeScript files", parameters: pi.typebox.Type.Object({ glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: "**/*.ts" })), }),
async execute(toolCallId, params, onUpdate, ctx, signal) { onUpdate?.({ content: [{ type: "text", text: "Scanning files..." }], details: { phase: "scan" }, });
const result = await pi.exec("git", ["ls-files", params.glob ?? "**/*.ts"], { signal, cwd: pi.cwd }); if (result.killed) { throw new Error("Scan was cancelled"); } if (result.code !== 0) { throw new Error(result.stderr || "git ls-files failed"); }
const files = result.stdout.split("\n").filter(Boolean); return { content: [{ type: "text", text: `Found ${files.length} files` }], details: { count: files.length, sample: files.slice(0, 10) }, }; },
onSession(event) { if (event.reason === "shutdown") { // 必要に応じてリソースをクリーンアップ } },});
export default factory;ファクトリーの戻り値の型:
CustomToolCustomTool[]Promise<CustomTool | CustomTool[]>
ファクトリーに渡される API サーフェス(CustomToolAPI)
Section titled “ファクトリーに渡される API サーフェス(CustomToolAPI)”types.ts および loader.ts より:
cwd: ホストのワーキングディレクトリexec(command, args, options?): プロセス実行ヘルパーui: UI コンテキスト(ヘッドレスモードでは no-op になる場合あり)hasUI: 非インタラクティブフローではfalselogger: 共有ファイルロガーtypebox: 注入された@sinclair/typeboxpi: 注入された@f5-sales-demo/xcshエクスポートpushPendingAction(action): 隠しresolveツール用のプレビューアクションを登録する(docs/resolve-tool-runtime.md)
ローダーは no-op UI コンテキストで開始し、実際の UI が準備できた際にホストコードが setUIContext(...) を呼び出す必要があります。
実行コントラクトと型付け
Section titled “実行コントラクトと型付け”CustomTool.execute のシグネチャ:
execute(toolCallId, params, onUpdate, ctx, signal)paramsは、Static<TParams>を介して TypeBox スキーマから静的に型付けされます。- ランタイム引数の検証は、エージェントループでの実行前に行われます。
onUpdateは UI ストリーミング用の部分的な結果を送出します。ctxにはセッション/モデルの状態とabort()ヘルパーが含まれます。signalはキャンセルを伝達します。
CustomToolAdapter はこれをエージェントツールインターフェースにブリッジし、正しい引数順序で呼び出しを転送します。
モデルへのツールの公開方法
Section titled “モデルへのツールの公開方法”- ツールは
AgentToolインスタンス(CustomToolAdapterまたは拡張ラッパー)にラップされます。 - 名前によってセッションツールレジストリに挿入されます。
- SDK ブートストラップでは、カスタムおよび拡張機能で登録されたツールは初期アクティブセットに強制的に含まれます。
- CLI の
--toolsは現在、組み込みツール名のみを検証します。カスタムツールの組み込みは、検出/登録パスおよび SDK オプションを通じて処理されます。
レンダリングフック
Section titled “レンダリングフック”オプションのレンダリングフック:
renderCall(args, theme)renderResult(result, options, theme, args?)
TUI でのランタイム動作:
- フックが存在する場合、ツール出力は
Boxコンテナ内にレンダリングされます。 renderResultは{ expanded, isPartial, spinnerFrame? }を受け取ります。- レンダラーエラーはキャッチされてログに記録され、UI はデフォルトのテキストレンダリングにフォールバックします。
セッション/状態処理
Section titled “セッション/状態処理”オプションの onSession(event, ctx) はセッションライフサイクルイベントを受け取ります。以下を含みます:
start、switch、branch、tree、shutdownauto_compaction_start、auto_compaction_endauto_retry_start、auto_retry_endttsr_triggered、todo_reminder
ブランチ/セッションコンテキストが変更された際に履歴から状態を再構築するには、ctx.sessionManager を使用してください。
失敗とキャンセルのセマンティクス
Section titled “失敗とキャンセルのセマンティクス”同期/非同期の失敗
Section titled “同期/非同期の失敗”executeでのスロー(または拒否された Promise)はツール失敗として扱われます。- エージェントランタイムは失敗を
isError: trueとエラーテキストコンテンツを含むツール結果メッセージに変換します。 - 拡張ラッパーを使用する場合、
tool_resultハンドラーはコンテンツ/詳細をさらに書き換え、エラーステータスを上書きすることもできます。
- エージェントのアボートは
AbortSignalを通じてexecuteに伝播されます。 - 協調的なキャンセルのために、
signalをサブプロセス処理(pi.exec(..., { signal }))に転送してください。 ctx.abort()により、ツールは現在のエージェント操作のアボートを要求できます。
onSession エラー
Section titled “onSession エラー”onSessionエラーはキャッチされて警告としてログに記録されます。セッションはクラッシュしません。
設計上の実際の制約
Section titled “設計上の実際の制約”- ツール名はアクティブなレジストリ内でグローバルに一意である必要があります。
- レンダラー/状態の再構築のために、
detailsには決定論的でスキーマ形式の出力を優先してください。 - UI の使用は
pi.hasUIでガードしてください。 - ツールディレクトリ内の
.md/.jsonはメタデータとして扱い、実行可能モジュールとして扱わないでください。