コンテンツへスキップ
CADCAMFUN
ドキュメント

ドキュメント

アシスタントツールプラグインを作成する

自己完結型の読み取りツールを構築し、そのマニフェストと引数を検証し、意図的に有効にして、実際に受け取るワーカー境界をテストします。

英語から自動翻訳されています。 英語の原文を見る

アシスタント拡張形式の選択

Production Assistant 拡張ローダーは、plugin.json とBunドルされた JavaScript エントリを含むディレクトリを受け入れます。モジュールを Bun ワーカーにインポートし、宣言された関数をその aiTools レコードからアシスタントに公開します。現在サポートされている機能は ai:tool です。このガイドでは、座標を返す読み取り専用のボルト円計算ツールを構築します。ジオメトリを図面に挿入するわけではありません。

/api/plugins には、manifest.json、main、ブラウザー権限、およびブートストラップ アセットを含むブラウザー プラグイン ZIP API もあります。このインストーラーでは、ここで説明されているワーカー プラグインは有効になりません。の プラグインHTTPリファレンス 形式を分離した文書。 プラグイン 両方の面について説明します。マニフェストの名前を変更するだけでそれらの間で変換を行わないでください。

アシスタント ツールは、アプリケーションのランタイム境界内で実行されるコードです。ホスト機能チェックはホスト RPC を介したアクセスを制限しますが、現在のワーカーはオペレーティング システムのサンドボックスではありません。実行がその環境に適した拡張コードを使用し、その宣言された読み取り/書き込み動作が関数の実際の動作と一致するようにします。

ディレクトリと同意レコードを作成する

拡張するサイドカーの正確なアプリケーション データ ディレクトリを使用します。ディレクトリ ID をマニフェストの逆引き DNS ID と同一にして、次の構造を作成します。

アプリケーションプラグインディレクトリ
<dataDir>/plugins/
  enabled.json
  com.example.bolt-circle/
    plugin.json
    index.js

実稼働ローダーは、enabled.json を安全な識別子の JSON 配列として読み取ります。重複/無効なエントリは無視され、JSON の形式が不正または欠落している場合は、プラグインが有効になっていないことを意味します。リストにないフォルダーは読み込まれません。ローダーは、フォルダー/マニフェスト ID の不一致や、サポートされていない機能を要求するマニフェストも拒否します。

ロードは、アシスタント プラグイン サービスの初期化時に行われます。ファイルシステム ウォッチャーをホット リロード用に構成しません。まずファイルを準備して検証してから、そのインスタンスを有効化/再起動します。失敗したプラグインは警告をログに記録しますが、他のプラグインとアプリケーションの起動は続行されます。有効なファイル エントリがツールの実行を保証すると仮定するのではなく、これらの警告を調べてください。

正確なツール契約を宣言する

この完全なマニフェストは、単一の読み取りツールに名前を付けます。コントリビューション ID は、モデル側のローカル識別子です。エントリは、モジュールによってエクスポートされた関数の名前です。ここでのbolt_circleとboltCircleのように、これらの文字列は異なる場合があります。

plugin.json
{
  "id": "com.example.bolt-circle",
  "name": "Bolt circle calculator",
  "version": "1.0.0",
  "apiVersion": "1",
  "entry": "index.js",
  "capabilities": ["ai:tool"],
  "contributes": {
    "aiTools": [{
      "id": "bolt_circle",
      "description": "Calculate equally spaced XY hole centers around the origin for a bolt circle. Returns coordinates without changing a drawing.",
      "kind": "read",
      "entry": "boltCircle",
      "parameters": {
        "type": "object",
        "properties": {
          "count": { "type": "integer", "minimum": 1, "maximum": 64 },
          "radius": { "type": "number", "exclusiveMinimum": 0 }
        },
        "required": ["count", "radius"],
        "additionalProperties": false
      }
    }]
  }
}

SDK には、3 ~ 128 文字の逆引き DNS ID、オプションのプレリリースを含むセマンティックの Major.minor.patch バージョン、API バージョン「1」、空ではない名前/エントリ、および認識される機能が必要です。ツール ID は小文字で始まり、40 文字までの小文字、数字、またはアンダースコアを使用します。説明には 10 ~ 2、000 文字が含まれています。ツールの種類は読み取りまたは書き込みです。破壊的な拡張機能はありません。

パラメーター スキーマは小さく、わかりやすいものにしてください。その役割は、モデルが有効な引数を生成できるようにし、アダプターが実行前に通常の間違いを拒否できるようにすることです。コード内の検証を置き換えるものではありません。

自己完結型モジュールを実装する

このエントリは、マニフェストで指定されたエントリ関数を使用して aiTools をエクスポートします。ランタイム引数を再度検証し、JSON 互換の小さな結果を返します。有限座標演算と有界カウントにより、不正な入力によって無界のリストや意味のない出力が生成されるのを防ぎます。

index.js
export const aiTools = {
  boltCircle: async (args) => {
    if (!args || typeof args !== "object") {
      return { ok: false, error: "Expected count and radius" }
    }
    const { count, radius } = args
    if (!Number.isInteger(count) || count < 1 || count > 64) {
      return { ok: false, error: "count must be an integer from 1 to 64" }
    }
    if (typeof radius !== "number" || !Number.isFinite(radius) || radius <= 0) {
      return { ok: false, error: "radius must be a positive finite number" }
    }
    const holes = Array.from({ length: count }, (_, index) => {
      const angle = 2 * Math.PI * index / count
      return {
        x: Number((radius * Math.cos(angle)).toFixed(4)),
        y: Number((radius * Math.sin(angle)).toFixed(4))
      }
    })
    return { ok: true, units: "mm", holes }
  }
}

Count 4 and radius 10 return centers at approximately (10,0), (0,10), (−10,0), and (0,−10), in counter-clockwise order around the origin. The result deliberately contains no database mutation or drawing identifier. A client wanting to insert holes must use a separate supported application action with its own access and permission behavior.

ノードスタイルの相対インポートが解決されると想定するのではなく、依存関係をエントリにバンドルします。ホストはエントリ テキストを読み取り、BLOB モジュールを通じてインポートします。モジュールのトップレベル コードはインポート時に実行されるため、計算機のロードのみによる副作用を回避します。

マニフェストと両方の引数のパスを検証する

プラグインをenabled.jsonに追加する前に検証してください。この開発チェックでは、アプリケーションと同じ SDK マニフェスト パーサーと AI スキーマ アダプターを使用します。 apps/sidecar などの依存関係のあるワークスペースにスクリプトを配置し、絶対プラグイン ディレクトリを引数として渡します。

開発検証スクリプト
import { join } from "node:path"
import { Effect } from "effect"
import { parseManifest } from "@cadcamfun/plugin-sdk"
import { pluginParameters } from "@cadcamfun/ai"

const directory = process.argv[2]
if (!directory) throw new Error("Pass the absolute plugin directory")
const manifest = await Effect.runPromise(parseManifest(
  await Bun.file(join(directory, "plugin.json")).text()))
if (manifest.capabilities.some((capability) => capability !== "ai:tool")) {
  throw new Error("This production loader accepts only ai:tool")
}
const contribution = manifest.contributes?.aiTools?.[0]
if (!contribution) throw new Error("No tool contribution")
const parameters = pluginParameters(contribution.parameters)
console.log(parameters.safeParse({ count: 4, radius: 10 }).success) // true
console.log(parameters.safeParse({ count: 0, radius: 10 }).success) // false
const module = await import(join(directory, manifest.entry))
console.log(await module.aiTools[contribution.entry]({ count: 4, radius: 10 }))
console.log(await module.aiTools[contribution.entry]({ count: 0, radius: 10 }))

最後の 2 つの呼び出しでは、アダプターの拒否とは別に関数レベルの動作をテストします。 SDK はマニフェスト構造を検証しますが、実稼働ローダーは独自の機能とフォルダー ID ルールを追加します。宣言、引数アダプター、実際のモジュール関数の 3 つの境界をすべてテストします。

実際のツール識別子を有効にして検出する

検証後、アプリケーション データ ディレクトリで ID を有効にします。

enabled.json
["com.example.bolt-circle"]

目的のサイドカー サービスを再起動/再初期化し、ログで負荷警告を調べます。次にリストします GET /api/ai/actions。読み取りプラグイン ツールは、埋め込みアシスタントだけでなくそこでも公開されます。ポリシーによりカタログを絞り込むことができます。ツール識別子はプラグイン ID とローカル ツール ID から生成され、正規化され、ホストの識別子のサイズに制限されます。

このマニフェストの現在の派生 ID は次のとおりです。 plugin_com_example_bolt_circle_bolt_circle。すべてのクライアントで命名規則を再実装するのではなく、それを発見してください。長いプラグイン ID は、ツールの接尾辞のための余地を残すために切り詰められる場合があります。カタログの説明にはプラグイン名が含まれているため、ユーザーはソースを特定できます。

書き込みツールは、通常の許可モードに関係なく、埋め込みアシスタントで人間による承認が必要とマークされます。外部ブリッジは読み取りプラグイン ツールのみを公開し、読み取り/書き込みトークンがあっても書き込み拡張機能の呼び出しを拒否します。種類を正確に保ちます。アプリケーションは、JSON スキーマから任意のモジュールの副作用を推測できません。

限定された外部アクセスを通じて読み取りツールを呼び出す

実際のプロジェクトではアプリケーションが発行したブリッジトークンを使用します。そのベアラーは GET /api/ai/actions と POST /api/ai/actions/invoke のみを開きます。呼び出し元がプラグインをインストールしたり、資格情報を編集したり、無関係なプロジェクトを列挙したりすることは許可されません。トークンのプロジェクト/モードはアクション ハンドラーによってチェックされます。アプリケーション全体のトークンは、スタンドアロンの計算機クライアントには必要ありません。

bridge-call.json、実際のプロジェクト ID を含む
{
  "projectId": "replace-with-your-project-id",
  "tool": "plugin_com_example_bolt_circle_bolt_circle",
  "input": { "count": 4, "radius": 10 }
}
実行中のプラグイン ツールを呼び出します
curl --fail-with-body \
  -H "Authorization: Bearer $CADCAMFUN_BRIDGE_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @bridge-call.json \
  "$CADCAMFUN_URL/api/ai/actions/invoke"

プラグインの入力は、宣言された引数オブジェクトです。組み込みのグループ化ツールとは異なり、この読み取りプラグインは入力内にアクション識別子を必要としません。ブリッジは、戻り値をツール/アクションのメタデータでラップします。アクションが実行されます。関数レベルの ok=false は、有用な座標リストを証明する HTTP トランスポートの成功ではなく、読み取り可能な結果のままです。

ブリッジ ポリシーは、tool.use と Bridge.use をチェックします。トークンが欠落している、期限切れである、取り消されている、または種類が間違っている場合、認証は失敗します。別のプロジェクトにトークンをバインドすることは、この呼び出しの範囲を広げる方法ではありません。

スキーマ変換と機能制限を理解する

アダプターは、表現可能な場合、コントリビューションの JSON スキーマを zod に変換します。変換が失敗した場合、スキーマが 20、000 のシリアル化文字を超えた場合、またはオブジェクトを記述していない場合は、任意のオブジェクト スキーマにフォールバックします。これが、関数例が count と radius 自体をチェックする理由です。サポートされているスキーマのネガティブ フィクスチャがローカルに渡されたとしても、すべてのスキーマ機能が表現可能であることは証明されません。

SDK は、drawing:read、drawing:write、toolpath:read、storage、ui:panel、ai:tool を認識します。図面の書き込みは図面の読み取りを意味します。 Production Assistant ローダーは現在、ai:tool のみを受け入れます。このマニフェストに drawing:write を追加すると、マニフェストがスキップされ、図面エディターのコンテキストが付与されなくなります。

一般的なワーカー ブートストラップには、プレースホルダー描画/ツールパス/UI ハンドラーが含まれています。これらのハンドラーは実稼働ジオメトリ統合ではありません。同様に、ポストプロセッサまたはオペレーションの貢献を宣言しても、アシスタントローダーがそれらを製造レジストリにワイヤリングすることはありません。フィーチャーが実際の CAD/CAM データを変更する必要がある場合は、サポートされているアプリケーション アクションを選択します。

実装されたワーカーのライフサイクルを中心に設計する

SDK では、マニフェスト、アクティブ化、オプションのクリーンアップ、および非アクティブ化フックを備えたモジュールについて説明します。現在の運用 AI パスはエントリをインポートし、エクスポートされた aiTools 関数を呼び出します。そのワーカーのアクティブ化/非アクティブ化 RPC ハンドラーはライフサイクル メッセージを確認しますが、エクスポートされたモジュール フックは呼び出しません。したがって、計算機は、アクティベーション コールバックに依存せずに、ここに示す最小限のモジュールを使用できます。

ホストは、有効なプラグインごとに 1 つのワーカーを開始し、応答を RPC ID と関連付け、保留中の呼び出しを追跡します。関数例外によりその呼び出しは失敗します。実際のワーカーに焦点を当てたテストでは、その後の呼び出しも引き続き成功する可能性があることが示されています。予想される無効な入力に対して構造化された拒否を返し、予期しない計算エラーに対してスローされた失敗を予約します。

呼び出しタイムアウトによりワーカーが終了し、プラグインが直ちにフォールトされます。ワーカーのクラッシュや投稿の失敗によっても、実行中のインスタンスが削除され、障害が発生したとマークされます。クラッシュ履歴には 10 分間のウィンドウがありますが、現在のハンドラーは最初のクラッシュでエラーが発生します。再起動の試行が 3 回成功するまで待機しません。ソースを修正し、自動再起動を想定するのではなく、意図的にリロード/再有効化します。

ランタイム保証を比例的に保つ

本番ローダーは、通常のライフサイクル/コマンド RPC に 10 秒のホスト バジェットを提供します。 AI ツールの呼び出しには、別途 30 秒の期限があります。ホストは、プラグインを 1 分あたり 2、000 回の呼び出し、および保留中の呼び出しは 64 回に制限します。ワーカーの作成時に、256 MB のリソース制限設定を Bun に渡します。これらはランタイム制御であり、別個のプロセスやオペレーティング システムの封じ込めポリシーではありません。

ブートストラップは空の環境から開始され、選択されたプロセス/ネットワーク グローバルの削除を試みます。削除はベストエフォート型であり、一部の Bun グローバルは引き続きアクセス可能です。したがって、機能チェックはホストが提供する API にとっては意味がありますが、任意の JavaScript を完全に分離することはできません。

マニフェスト スキーマには、オプションの署名フィールドと開発フィールドが存在します。現在のワーカーのインストール パスは、暗号化検証なしで署名テキストを記録します。このフィールドが存在するため、発行者に検証済みのラベルを付けないでください。自己完結型のソース レビューとインストール ポリシーは、スキーマの受け入れとは別個の決定として残ります。

10 分以内に 3 回クラッシュしたプラグインは自動的に無効になり、アシスタント ツールの呼び出しは 30 秒後に停止されます。どちらの値も定数です apps/sidecar/src/plugins/host.ts.

ツールを更新、削除、診断する

更新の場合は、ターゲット サービスを再起動する前に、バンドルされたモジュール/マニフェストを編集して検証します。同じ拡張子の場合は ID を安定させ、何がロードされたかを識別するためにバージョンを上げます。今後の初期化時にロードを停止するには、enabled.json から ID を削除し、再起動/再初期化します。ファイルを変更するだけでは、すでに実行中のワーカーは破棄されません。

ツールが見つからない場合は、有効なアレイ、フォルダー/マニフェスト ID の正確な同一性、サポートされる機能リスト、ログ内のマニフェスト デコード エラーを確認してください。 NoSuchTool は通常、エントリがエクスポートされた aiTools 関数に名前を付けていないことを意味します。引数の拒否は、スキーマまたは関数のガードを指します。タイムアウトは、作業のブロックまたは未解決の約束を示します。ブラウザーの ZIP 有効化/構成エンドポイントは、このワーカー同意ファイルを管理しません。

集中的な報道が行われています apps/sidecar/test/plugins-ai-tools.test.ts 実際の労働者にとっても、 ai-plugin-tools.test.ts 本番環境の読み込み、引数の検証、読み取り/書き込みの公開、ブリッジの動作など。実装ソースは次のとおりです。 packages/plugin-sdk/src/manifest.ts, packages/ai/src/tools/plugin-tool.ts, apps/sidecar/src/plugins/host.ts、 そして services/ai-plugins.ts。これらの境界を使用して、成功したテストが実際に何を証明したかを判断します。

契約ルールの概要

アイテム制約違反した場合はどうなりますか
道具 id^[a-z][a-z0-9_]{0,39}$マニフェストのデコードは失敗します。プラグインがロードされていません。
description10–2、000 文字マニフェストのデコードが失敗します。
kindread または write;破壊的な種類は存在しないマニフェストのデコードが失敗します。
parametersJSON スキーマ オブジェクト、シリアル化時に最大 20,000 文字サイズが大きすぎるスキーマまたは変換できないスキーマは、任意のオブジェクトを受け入れるようにフォールバックします。
能力のみ ai:tool アシスタントホストでプラグインは警告とともにスキップされます。
フォルダ名マニフェストと同等 idプラグインはスキップされ、両方の値に名前を付ける警告が表示されます。
通常通話予算デフォルトでは呼び出しごとに 5 秒ワーカーは、応答しないプラグインと同様に停止されます。
アシスタントツールの通話予算30 s呼び出しは失敗の結果を返します。
メモリ256 MB ヒープ作業員が殺されてしまう。
レートプラグインごとに 1 分あたり 2、000 件の呼び出し、64 件の保留中の呼び出し料金を超える通話は拒否されます。満杯のキューは失敗します plugin RPC queue is full.
クラッシュとタイムアウト10 分以上カウントされ、制限は 3 ですクラッシュまたはタイムアウトが発生するたびにワーカーが終了し、プラグインに障害が発生したとマークされます。

モデルの説明を書きます。ツールが何を計算するのか、その単位、いつ使用するのかを説明します。たとえば、「ボルト円の穴の中心をミリメートル単位で返します。ユーザーが直径上に等間隔の穴を要求する場合に使用します。」曖昧な説明により、ツールが間違ったタイミングで呼び出されたり、まったく呼び出されなかったりすることがあります。

リリース前チェックリスト

  1. エントリを単一の自己完結型モジュールにバンドルします。ワーカーは実行時にワークスペースのインポートを解決できません。
  2. 走る parseManifest 決勝で plugin.json 報告されたすべての問題を修正します。
  3. エクスポートされた各ツール関数を有効な引数、境界引数、および無効な引数を使用して直接呼び出し、無効な入力によってエスケープ例外ではなく、読み取り可能なエラー結果が生成されることを確認します。
  4. 結果を小さくし、JSON と互換性のあるものに保ちます。アシスタントがそれらを読み取り、大きな出力がコンテキストに詰め込まれます。
  5. 選ぶ read ツールが何かを変えない限り。あ write このツールは、通話の前にユーザーにプロンプトを表示します。
  6. フォルダを下に置きます <data dir>/plugins/<id>/にIDを追加します enabled.json、サイドカーを再起動し、アシスタントのツール リストから派生ツール ID を確認します。
  7. バンプ version 変更を送信して保存するたびに、 id 安定しているため、派生ツール ID は変更されません。

トラブルシューティング マトリックス

症状考えられる原因修理
ツールが表示されないIDがありません enabled.json、または JSON の形式が不正です配列を修正してください。 JSON が不正な形式または欠落していると、何も有効になりません。
ログにはフォルダーの名前が X と表示されますが、マニフェストには Y と表示されますフォルダーとマニフェスト id 異なる正確に一致するように名前を変更します。
ログには、drawing:read を要求していると表示されます...このホストでサポートされていない機能宣言のみ ai:tool.
アシスタントが予期しない引数の形式を渡すスキーマは任意のオブジェクトに戻りますオブジェクト スキーマを使用する properties 20、000 文字の下で、関数内でも検証します。
折り返し電話 ok: false 約 30 秒後ツールの時間予算を超えました呼び出しまたは事前計算ごとの作業を減らします。無制限のループを避けます。
プラグインがエラー後に応答を停止するワーカーのクラッシュまたはタイムアウトによりワーカーが終了し、プラグインがマークされます faulted;クラッシュも 10 分間の枠でカウントされ、制限は 3 ですクラッシュを修正し、サイドカーを再起動して再度ロードします。

インストール前にマニフェストを lint する

check-manifest.ts
import { decodeManifest, requestedCapabilities } from "@cadcamfun/plugin-sdk"
import { Effect } from "effect"

const raw = JSON.parse(await Bun.file(process.argv[2] ?? "plugin.json").text())
const manifest = await Effect.runPromise(decodeManifest(raw))   // throws a SchemaError naming the bad field
console.log(manifest.id, manifest.version, requestedCapabilities(manifest))
for (const tool of manifest.contributes?.aiTools ?? [])
  console.log(`plugin_${manifest.id}_${tool.id}`, tool.kind)
よくある間違い修理
"version": "1.0"3 つの数字を使用します。 1.0.0
"apiVersion": 1それは文字列です: "1"
プラグインID gear または My.Plugin逆引きDNS、小文字: com.example.gear
ツールID Lookup-Thread小文字、数字とアンダースコア、最初の文字、≤ 40 文字
ツールの説明は 10 文字未満です何をするのか、いつ使用するのかを説明する
両方を宣言する drawing:read そして drawing:write許可されますが、 drawing:write 単独では既に read を意味します
で始まるストレージキー / または含む ..次のような相対キーを使用します。 presets/m2

3Dで

プラグインテーブル ドラッグして周回します · スクロールしてズーム · 右ドラッグしてパンします
移行からの PluginRegistryEntry および PluginConfiguration: 構成はレジストリ エントリを参照します。