Plugins

プロバイダー Plugin の構築

このガイドでは、モデルプロバイダー(LLM)を OpenClaw に追加するプロバイダー Plugin の構築手順を説明します。最後まで進めると、モデルカタログ、API キー認証、動的モデル解決を備えたプロバイダーができあがります。

チュートリアル

  • Package and manifest

    ステップ 1: パッケージとマニフェスト

    {
    "name": "@myorg/openclaw-acme-ai",
    "version": "1.0.0",
    "type": "module",
    "openclaw": {
      "extensions": ["./index.ts"],
      "providers": ["acme-ai"],
      "compat": {
        "pluginApi": ">=2026.3.24-beta.2",
        "minGatewayVersion": "2026.3.24-beta.2"
      },
      "build": {
        "openclawVersion": "2026.3.24-beta.2",
        "pluginSdkVersion": "2026.3.24-beta.2"
      }
    }
    }
    
    {
    "id": "acme-ai",
    "name": "Acme AI",
    "description": "Acme AI model provider",
    "providers": ["acme-ai"],
    "modelSupport": {
      "modelPrefixes": ["acme-"]
    },
    "providerAuthEnvVars": {
      "acme-ai": ["ACME_AI_API_KEY"]
    },
    "providerAuthAliases": {
      "acme-ai-coding": "acme-ai"
    },
    "providerAuthChoices": [
      {
        "provider": "acme-ai",
        "method": "api-key",
        "choiceId": "acme-ai-api-key",
        "choiceLabel": "Acme AI API key",
        "groupId": "acme-ai",
        "groupLabel": "Acme AI",
        "cliFlag": "--acme-ai-api-key",
        "cliOption": "--acme-ai-api-key <key>",
        "cliDescription": "Acme AI API key"
      }
    ],
    "configSchema": {
      "type": "object",
      "additionalProperties": false
    }
    }
    

    マニフェストで providerAuthEnvVars を宣言すると、OpenClaw は Plugin ランタイムを読み込まなくても認証情報を検出できます。プロバイダーのバリアントが別のプロバイダー id の認証を再利用するべき場合は、providerAuthAliases を追加します。modelSupport は任意で、ランタイムフックが存在する前でも、OpenClaw が acme-large のような短縮モデル id からプロバイダー Plugin を自動読み込みできるようにします。プロバイダーを ClawHub で公開する場合、package.json ではこれらの openclaw.compatopenclaw.build フィールドが必須です。

  • Register the provider

    最小構成のプロバイダーには、idlabelauthcatalog が必要です。

    import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
    import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
    
    export default definePluginEntry({
      id: "acme-ai",
      name: "Acme AI",
      description: "Acme AI model provider",
      register(api) {
        api.registerProvider({
          id: "acme-ai",
          label: "Acme AI",
          docsPath: "/providers/acme-ai",
          envVars: ["ACME_AI_API_KEY"],
    
          auth: [
            createProviderApiKeyAuthMethod({
              providerId: "acme-ai",
              methodId: "api-key",
              label: "Acme AI API key",
              hint: "API key from your Acme AI dashboard",
              optionKey: "acmeAiApiKey",
              flagName: "--acme-ai-api-key",
              envVar: "ACME_AI_API_KEY",
              promptMessage: "Enter your Acme AI API key",
              defaultModel: "acme-ai/acme-large",
            }),
          ],
    
          catalog: {
            order: "simple",
            run: async (ctx) => {
              const apiKey =
                ctx.resolveProviderApiKey("acme-ai").apiKey;
              if (!apiKey) return null;
              return {
                provider: {
                  baseUrl: "https://api.acme-ai.com/v1",
                  apiKey,
                  api: "openai-completions",
                  models: [
                    {
                      id: "acme-large",
                      name: "Acme Large",
                      reasoning: true,
                      input: ["text", "image"],
                      cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
                      contextWindow: 200000,
                      maxTokens: 32768,
                    },
                    {
                      id: "acme-small",
                      name: "Acme Small",
                      reasoning: false,
                      input: ["text"],
                      cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
                      contextWindow: 128000,
                      maxTokens: 8192,
                    },
                  ],
                },
              };
            },
          },
        });
      },
    });
    

    これで動作するプロバイダーになります。ユーザーは openclaw onboard --acme-ai-api-key <key> を実行し、自分のモデルとして acme-ai/acme-large を選択できるようになります。

    アップストリームのプロバイダーが OpenClaw と異なる制御トークンを使用する場合は、ストリームパスを置き換えるのではなく、小さな双方向テキスト変換を追加します。

    api.registerTextTransforms({
      input: [
        { from: /red basket/g, to: "blue basket" },
        { from: /paper ticket/g, to: "digital ticket" },
        { from: /left shelf/g, to: "right shelf" },
      ],
      output: [
        { from: /blue basket/g, to: "red basket" },
        { from: /digital ticket/g, to: "paper ticket" },
        { from: /right shelf/g, to: "left shelf" },
      ],
    });
    

    input は、転送前に最終的なシステムプロンプトとテキストメッセージ内容を書き換えます。output は、OpenClaw が独自の制御マーカーやチャンネル配信を解析する前に、アシスタントのテキストデルタと最終テキストを書き換えます。

    API キー認証と単一のカタログベースのランタイムを備えたテキストプロバイダーを 1 つだけ登録するバンドルプロバイダーでは、より範囲の狭い defineSingleProviderPluginEntry(...) ヘルパーを優先してください。

    import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
    
    export default defineSingleProviderPluginEntry({
      id: "acme-ai",
      name: "Acme AI",
      description: "Acme AI model provider",
      provider: {
        label: "Acme AI",
        docsPath: "/providers/acme-ai",
        auth: [
          {
            methodId: "api-key",
            label: "Acme AI API key",
            hint: "API key from your Acme AI dashboard",
            optionKey: "acmeAiApiKey",
            flagName: "--acme-ai-api-key",
            envVar: "ACME_AI_API_KEY",
            promptMessage: "Enter your Acme AI API key",
            defaultModel: "acme-ai/acme-large",
          },
        ],
        catalog: {
          buildProvider: () => ({
            api: "openai-completions",
            baseUrl: "https://api.acme-ai.com/v1",
            models: [{ id: "acme-large", name: "Acme Large" }],
          }),
          buildStaticProvider: () => ({
            api: "openai-completions",
            baseUrl: "https://api.acme-ai.com/v1",
            models: [{ id: "acme-large", name: "Acme Large" }],
          }),
        },
      },
    });
    

    buildProvider は、OpenClaw が実際のプロバイダー認証を解決できる場合に使われるライブカタログパスです。プロバイダー固有の検出を実行してもかまいません。buildStaticProvider は、認証が設定される前に表示しても安全なオフライン行にのみ使用してください。これは認証情報を要求したり、ネットワークリクエストを行ったりしてはいけません。OpenClaw の models list --all 表示は現在、空の設定、空の env、エージェント/ワークスペースパスなしで、バンドルされたプロバイダー Plugin に対してのみ静的カタログを実行します。

    認証フローで models.providers.*、エイリアス、オンボーディング中のエージェントのデフォルトモデルもパッチする必要がある場合は、openclaw/plugin-sdk/provider-onboard のプリセットヘルパーを使用してください。最も範囲の狭いヘルパーは createDefaultModelPresetAppliers(...)createDefaultModelsPresetAppliers(...)、および createModelCatalogPresetAppliers(...) です。

    プロバイダーのネイティブエンドポイントが通常の openai-completions 転送でストリーミング使用量ブロックをサポートしている場合は、プロバイダー id のチェックをハードコードするのではなく、openclaw/plugin-sdk/provider-catalog-shared の共有カタログヘルパーを優先してください。supportsNativeStreamingUsageCompat(...)applyProviderNativeStreamingUsageCompat(...) は、エンドポイントの機能マップからサポートを検出するため、Plugin がカスタムプロバイダー id を使っている場合でも、ネイティブの Moonshot/DashScope スタイルのエンドポイントは引き続きオプトインできます。

  • Add dynamic model resolution

    プロバイダーが任意のモデル ID(プロキシやルーターなど)を受け付ける場合は、 resolveDynamicModel を追加します。

    api.registerProvider({
      // ... id, label, auth, catalog from above
    
      resolveDynamicModel: (ctx) => ({
        id: ctx.modelId,
        name: ctx.modelId,
        provider: "acme-ai",
        api: "openai-completions",
        baseUrl: "https://api.acme-ai.com/v1",
        reasoning: false,
        input: ["text"],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
        contextWindow: 128000,
        maxTokens: 8192,
      }),
    });
    

    解決にネットワーク呼び出しが必要な場合は、非同期ウォームアップに prepareDynamicModel を使用してください。完了後に resolveDynamicModel が再度実行されます。

  • Add runtime hooks (as needed)

    ほとんどのプロバイダーに必要なのは catalog + resolveDynamicModel だけです。プロバイダーで必要になった時点で、フックを段階的に追加してください。

    共有ヘルパービルダーは、最も一般的なリプレイ/ツール互換ファミリーをカバーするようになっているため、通常 Plugin が各フックを 1 つずつ手作業で配線する必要はありません。

    import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
    import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream";
    import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools";
    
    const GOOGLE_FAMILY_HOOKS = {
      ...buildProviderReplayFamilyHooks({ family: "google-gemini" }),
      ...buildProviderStreamFamilyHooks("google-thinking"),
      ...buildProviderToolCompatFamilyHooks("gemini"),
    };
    
    api.registerProvider({
      id: "acme-gemini-compatible",
      // ...
      ...GOOGLE_FAMILY_HOOKS,
    });
    

    現在利用可能なリプレイファミリー:

    ファミリー 配線される内容 バンドル例
    openai-compatible OpenAI 互換転送向けの共有 OpenAI スタイルのリプレイポリシー。ツール呼び出し id のサニタイズ、アシスタント優先の順序修正、転送で必要な場合の汎用 Gemini ターン検証を含みます moonshot, ollama, xai, zai
    anthropic-by-model modelId によって選択される Claude 対応のリプレイポリシー。これにより、Anthropic メッセージ転送は、解決済みモデルが実際に Claude id である場合にのみ Claude 固有の思考ブロッククリーンアップを受け取ります amazon-bedrock, anthropic-vertex
    google-gemini ネイティブ Gemini リプレイポリシーに加え、ブートストラップリプレイのサニタイズとタグ付き推論出力モード google, google-gemini-cli
    passthrough-gemini OpenAI 互換プロキシ転送を通して実行される Gemini モデル向けの Gemini thought-signature サニタイズ。ネイティブ Gemini リプレイ検証やブートストラップ書き換えは有効にしません openrouter, kilocode, opencode, opencode-go
    hybrid-anthropic-openai 1 つの Plugin 内で Anthropic メッセージと OpenAI 互換モデルサーフェスを混在させるプロバイダー向けのハイブリッドポリシー。任意の Claude 限定思考ブロック削除は Anthropic 側にスコープされます minimax

    Available stream families today:

    ファミリー 接続する内容 同梱例
    google-thinking 共有ストリームパスでの Gemini thinking ペイロード正規化 google, google-gemini-cli
    kilocode-thinking 共有プロキシストリームパスでの Kilo reasoning ラッパー。kilo/auto と未対応のプロキシ reasoning ID では挿入された thinking をスキップ kilocode
    moonshot-thinking 設定 + /think レベルからの Moonshot バイナリネイティブ thinking ペイロードマッピング moonshot
    minimax-fast-mode 共有ストリームパスでの MiniMax fast-mode モデル書き換え minimax, minimax-portal
    openai-responses-defaults 共有ネイティブ OpenAI/Codex Responses ラッパー: attribution ヘッダー、/fast/serviceTier、テキスト詳細度、ネイティブ Codex Web 検索、reasoning 互換ペイロード整形、Responses コンテキスト管理 openai, openai-codex
    openrouter-thinking プロキシルート用 OpenRouter reasoning ラッパー。未対応モデル/auto のスキップは中央で処理 openrouter
    tool-stream-default-on 明示的に無効化されない限りツールストリーミングを使いたい Z.AI などのプロバイダー向けのデフォルトオン tool_stream ラッパー zai
    ファミリービルダーを支える SDK シーム

    各ファミリービルダーは、同じパッケージからエクスポートされる下位レベルの公開ヘルパーで構成されており、プロバイダーが一般的なパターンから外れる必要がある場合に利用できます。

    • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamilybuildProviderReplayFamilyHooks(...)、および生の replay ビルダー(buildOpenAICompatibleReplayPolicybuildAnthropicReplayPolicyForModelbuildGoogleGeminiReplayPolicybuildHybridAnthropicOrOpenAIReplayPolicy)。Gemini replay ヘルパー(sanitizeGoogleGeminiReplayHistoryresolveTaggedReasoningOutputMode)とエンドポイント/モデルヘルパー(resolveProviderEndpointnormalizeProviderIdnormalizeGooglePreviewModelIdnormalizeNativeXaiModelId)もエクスポートします。
    • openclaw/plugin-sdk/provider-stream - ProviderStreamFamilybuildProviderStreamFamilyHooks(...)composeProviderStreamWrappers(...)、さらに共有 OpenAI/Codex ラッパー(createOpenAIAttributionHeadersWrappercreateOpenAIFastModeWrappercreateOpenAIServiceTierWrappercreateOpenAIResponsesContextManagementWrappercreateCodexNativeWebSearchWrapper)、DeepSeek V4 OpenAI 互換ラッパー(createDeepSeekV4OpenAICompatibleThinkingWrapper)、Anthropic Messages thinking prefill クリーンアップ(createAnthropicThinkingPrefillPayloadWrapper)、共有プロキシ/プロバイダーラッパー(createOpenRouterWrappercreateToolStreamWrappercreateMinimaxFastModeWrapper)。
    • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamilybuildProviderToolCompatFamilyHooks("gemini")、基盤となる Gemini スキーマヘルパー(normalizeGeminiToolSchemasinspectGeminiToolSchemas)、xAI 互換ヘルパー(resolveXaiModelCompatPatch()applyXaiModelCompat(model))。同梱の xAI Plugin は、これらとともに normalizeResolvedModel + contributeResolvedModelCompat を使い、xAI ルールをプロバイダー所有に保ちます。

    一部のストリームヘルパーは意図的にプロバイダー内に留めています。@openclaw/anthropic-provider は、Claude OAuth ベータ処理と context1m ゲートをエンコードしているため、wrapAnthropicProviderStreamresolveAnthropicBetasresolveAnthropicFastModeresolveAnthropicServiceTier、および下位レベルの Anthropic ラッパービルダーを独自の公開 api.ts / contract-api.ts シームに保持します。xAI Plugin も同様に、ネイティブ xAI Responses 整形を独自の wrapStreamFn/fast エイリアス、デフォルト tool_stream、未対応の strict-tool クリーンアップ、xAI 固有の reasoning ペイロード削除)に保持します。

    同じパッケージルートパターンは、@openclaw/openai-provider(プロバイダービルダー、デフォルトモデルヘルパー、リアルタイムプロバイダービルダー)と @openclaw/openrouter-provider(プロバイダービルダーに加えてオンボーディング/設定ヘルパー)も支えています。

    トークン交換

    各推論呼び出しの前にトークン交換が必要なプロバイダーの場合:

    prepareRuntimeAuth: async (ctx) => {
      const exchanged = await exchangeToken(ctx.apiKey);
      return {
        apiKey: exchanged.token,
        baseUrl: exchanged.baseUrl,
        expiresAt: exchanged.expiresAt,
      };
    },
    

    カスタムヘッダー

    カスタムリクエストヘッダーまたは本文の変更が必要なプロバイダーの場合:

    // wrapStreamFn returns a StreamFn derived from ctx.streamFn
    wrapStreamFn: (ctx) => {
      if (!ctx.streamFn) return undefined;
      const inner = ctx.streamFn;
      return async (params) => {
        params.headers = {
          ...params.headers,
          "X-Acme-Version": "2",
        };
        return inner(params);
      };
    },
    

    ネイティブトランスポート ID

    汎用 HTTP または WebSocket トランスポートで、ネイティブのリクエスト/セッションヘッダーやメタデータが必要なプロバイダーの場合:

    resolveTransportTurnState: (ctx) => ({
      headers: {
        "x-request-id": ctx.turnId,
      },
      metadata: {
        session_id: ctx.sessionId ?? "",
        turn_id: ctx.turnId,
      },
    }),
    resolveWebSocketSessionPolicy: (ctx) => ({
      headers: {
        "x-session-id": ctx.sessionId ?? "",
      },
      degradeCooldownMs: 60_000,
    }),
    

    使用量と課金

    使用量/課金データを公開するプロバイダーの場合:

    resolveUsageAuth: async (ctx) => {
      const auth = await ctx.resolveOAuthToken();
      return auth ? { token: auth.token } : null;
    },
    fetchUsageSnapshot: async (ctx) => {
      return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);
    },
    
    利用可能なすべてのプロバイダーフック

    OpenClaw はこの順序でフックを呼び出します。ほとんどのプロバイダーが使うのは 2〜3 個だけです。 OpenClaw が現在呼び出さない互換性専用のプロバイダーフィールド、たとえば ProviderPlugin.capabilitiessuppressBuiltInModel はここに記載していません。

    # フック 使用する場面
    1 catalog モデルカタログまたはベース URL のデフォルト
    2 applyConfigDefaults 設定具体化時のプロバイダー所有グローバルデフォルト
    3 normalizeModelId ルックアップ前のレガシー/プレビューモデル ID エイリアスのクリーンアップ
    4 normalizeTransport 汎用モデル組み立て前のプロバイダーファミリー api / baseUrl クリーンアップ
    5 normalizeConfig models.providers.<id> 設定の正規化
    6 applyNativeStreamingUsageCompat 設定プロバイダー向けネイティブ streaming-usage 互換の書き換え
    7 resolveConfigApiKey プロバイダー所有の env-marker 認証解決
    8 resolveSyntheticAuth ローカル/セルフホストまたは設定ベースの合成認証
    9 shouldDeferSyntheticProfileAuth env/config 認証の背後にある合成保存プロファイルプレースホルダーを下げる
    10 resolveDynamicModel 任意の upstream モデル ID を受け入れる
    11 prepareDynamicModel 解決前の非同期メタデータ取得
    12 normalizeResolvedModel runner 前のトランスポート書き換え
    13 contributeResolvedModelCompat 別の互換トランスポート背後にあるベンダーモデル向けの互換フラグ
    14 normalizeToolSchemas 登録前のプロバイダー所有ツールスキーマクリーンアップ
    15 inspectToolSchemas プロバイダー所有のツールスキーマ診断
    16 resolveReasoningOutputMode タグ付き reasoning-output とネイティブ reasoning-output のコントラクト
    17 prepareExtraParams デフォルトリクエストパラメーター
    18 createStreamFn 完全にカスタムの StreamFn トランスポート
    19 wrapStreamFn 通常のストリームパス上のカスタムヘッダー/本文ラッパー
    20 resolveTransportTurnState ネイティブのターンごとのヘッダー/メタデータ
    21 resolveWebSocketSessionPolicy ネイティブ WS セッションヘッダー/クールダウン
    22 formatApiKey カスタムランタイムトークン形状
    23 refreshOAuth カスタム OAuth 更新
    24 buildAuthDoctorHint 認証修復ガイダンス
    25 matchesContextOverflowError プロバイダー所有のオーバーフロー検出
    26 classifyFailoverReason プロバイダー所有のレート制限/過負荷分類
    27 isCacheTtlEligible プロンプトキャッシュ TTL ゲート
    28 buildMissingAuthMessage カスタムの認証不足ヒント
    29 augmentModelCatalog 合成 forward-compat 行
    30 resolveThinkingProfile モデル固有の /think オプションセット
    31 isBinaryThinking バイナリ thinking のオン/オフ互換性
    32 supportsXHighThinking xhigh reasoning サポート互換性
    33 resolveDefaultThinkingLevel デフォルト /think ポリシー互換性
    34 isModernModelRef ライブ/スモークモデルマッチング
    35 prepareRuntimeAuth 推論前のトークン交換
    36 resolveUsageAuth カスタム使用量認証情報の解析
    37 fetchUsageSnapshot カスタム使用量エンドポイント
    38 createEmbeddingProvider memory/search 向けのプロバイダー所有埋め込みアダプター
    39 buildReplayPolicy カスタム transcript replay/compaction ポリシー
    40 sanitizeReplayHistory 汎用クリーンアップ後のプロバイダー固有 replay 書き換え
    41 validateReplayTurns 埋め込み runner 前の厳密な replay-turn 検証
    42 onModelSelected 選択後コールバック(例: テレメトリ)

    ランタイムフォールバックの注記:

    • normalizeConfig は、まず一致したプロバイダーを確認し、その後、実際に設定を変更するものが見つかるまで他のフック対応プロバイダー Plugin を確認します。サポート対象の Google ファミリー設定エントリをどのプロバイダーフックも書き換えない場合でも、同梱の Google 設定ノーマライザーが適用されます。
    • resolveConfigApiKey は、公開されている場合はプロバイダーフックを使用します。同梱の amazon-bedrock パスには、Bedrock ランタイム認証自体がまだ AWS SDK デフォルトチェーンを使用している場合でも、ここに組み込みの AWS env-marker リゾルバーがあります。
    • resolveSystemPromptContribution により、プロバイダーはモデルファミリー向けにキャッシュを考慮したシステムプロンプトガイダンスを注入できます。その動作が 1 つのプロバイダー/モデルファミリーに属し、安定/動的キャッシュ分割を保持すべき場合は、before_prompt_build よりもこれを優先してください。

    詳細な説明と実例については、内部: プロバイダーランタイムフックを参照してください。

  • 追加機能を追加する(任意)

    ステップ 5: 追加機能を追加する

    プロバイダー Plugin は、テキスト推論と並行して、音声、リアルタイム文字起こし、リアルタイム音声、メディア理解、画像生成、動画生成、Web 取得、Web 検索を登録できます。OpenClaw はこれを hybrid-capability Plugin として分類します。これは企業 Plugin(ベンダーごとに 1 つの Plugin)に推奨されるパターンです。内部: 機能の所有権を参照してください。

    既存の api.registerProvider(...) 呼び出しと並べて、register(api) 内で各機能を登録します。必要なタブだけを選択してください:

    音声 (TTS)

    import {
      assertOkOrThrowProviderError,
      postJsonRequest,
    } from "openclaw/plugin-sdk/provider-http";
    
    api.registerSpeechProvider({
      id: "acme-ai",
      label: "Acme Speech",
      isConfigured: ({ config }) => Boolean(config.messages?.tts),
      synthesize: async (req) => {
        const { response, release } = await postJsonRequest({
          url: "https://api.example.com/v1/speech",
          headers: new Headers({ "Content-Type": "application/json" }),
          body: { text: req.text },
          timeoutMs: req.timeoutMs,
          fetchFn: fetch,
          auditContext: "acme speech",
        });
        try {
          await assertOkOrThrowProviderError(response, "Acme Speech API error");
          return {
            audioBuffer: Buffer.from(await response.arrayBuffer()),
            outputFormat: "mp3",
            fileExtension: ".mp3",
            voiceCompatible: false,
          };
        } finally {
          await release();
        }
      },
    });
    

    Provider の HTTP 障害には assertOkOrThrowProviderError(...) を使用し、 plugins が制限付きのエラー本文読み取り、JSON エラー解析、 リクエスト ID サフィックスを共有できるようにします。

    リアルタイム文字起こし

    createRealtimeTranscriptionWebSocketSession(...) を優先してください。共有 ヘルパーはプロキシキャプチャ、再接続バックオフ、クローズ時のフラッシュ、ready ハンドシェイク、音声キューイング、クローズイベント診断を処理します。Plugin は上流イベントをマッピングするだけです。

    api.registerRealtimeTranscriptionProvider({
      id: "acme-ai",
      label: "Acme Realtime Transcription",
      isConfigured: () => true,
      createSession: (req) => {
        const apiKey = String(req.providerConfig.apiKey ?? "");
        return createRealtimeTranscriptionWebSocketSession({
          providerId: "acme-ai",
          callbacks: req,
          url: "wss://api.example.com/v1/realtime-transcription",
          headers: { Authorization: `Bearer ${apiKey}` },
          onMessage: (event, transport) => {
            if (event.type === "session.created") {
              transport.sendJson({ type: "session.update" });
              transport.markReady();
              return;
            }
            if (event.type === "transcript.final") {
              req.onTranscript?.(event.text);
            }
          },
          sendAudio: (audio, transport) => {
            transport.sendJson({
              type: "audio.append",
              audio: audio.toString("base64"),
            });
          },
          onClose: (transport) => {
            transport.sendJson({ type: "audio.end" });
          },
        });
      },
    });
    

    マルチパート音声を POST するバッチ STT provider は、 openclaw/plugin-sdk/provider-httpbuildAudioTranscriptionFormData(...) を使用してください。このヘルパーは、互換性のある文字起こし API のために M4A 形式のファイル名が必要な AAC アップロードを含め、 アップロードファイル名を正規化します。

    リアルタイム音声

    api.registerRealtimeVoiceProvider({
      id: "acme-ai",
      label: "Acme Realtime Voice",
      capabilities: {
        transports: ["gateway-relay"],
        inputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
        outputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
        supportsBargeIn: true,
        supportsToolCalls: true,
      },
      isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey),
      createBridge: (req) => ({
        // Set this only if the provider accepts multiple tool responses for
        // one call, for example an immediate "working" response followed by
        // the final result.
        supportsToolResultContinuation: false,
        connect: async () => {},
        sendAudio: () => {},
        setMediaTimestamp: () => {},
        handleBargeIn: () => {},
        submitToolResult: () => {},
        acknowledgeMark: () => {},
        close: () => {},
        isConnected: () => true,
      }),
    });
    

    talk.catalog が有効なモード、トランスポート、音声形式、機能フラグをブラウザーおよびネイティブの Talk クライアントに公開できるように、capabilities を宣言してください。トランスポートが、人間がアシスタントの再生を中断していることを検出でき、 provider がアクティブな音声応答の切り詰めまたはクリアをサポートしている場合は、handleBargeIn を実装してください。

    メディア理解

    api.registerMediaUnderstandingProvider({
      id: "acme-ai",
      capabilities: ["image", "audio"],
      describeImage: async (req) => ({ text: "A photo of..." }),
      transcribeAudio: async (req) => ({ text: "Transcript..." }),
    });
    

    画像と動画の生成

    動画機能は モード対応 の形状を使用します: generateimageToVideovideoToVideomaxInputImages / maxInputVideos / maxDurationSeconds のようなフラットな集約フィールドだけでは、 変換モードのサポートや無効化されたモードを明確に示すには不十分です。 音楽生成も、明示的な generate / edit ブロックで同じパターンに従います。

    api.registerImageGenerationProvider({
      id: "acme-ai",
      label: "Acme Images",
      generate: async (req) => ({ /* image result */ }),
    });
    
    api.registerVideoGenerationProvider({
      id: "acme-ai",
      label: "Acme Video",
      capabilities: {
        generate: { maxVideos: 1, maxDurationSeconds: 10, supportsResolution: true },
        imageToVideo: {
          enabled: true,
          maxVideos: 1,
          maxInputImages: 1,
          maxInputImagesByModel: { "acme/reference-to-video": 9 },
          maxDurationSeconds: 5,
        },
        videoToVideo: { enabled: false },
      },
      generateVideo: async (req) => ({ videos: [] }),
    });
    

    Web 取得と検索

    api.registerWebFetchProvider({
      id: "acme-ai-fetch",
      label: "Acme Fetch",
      hint: "Fetch pages through Acme's rendering backend.",
      envVars: ["ACME_FETCH_API_KEY"],
      placeholder: "acme-...",
      signupUrl: "https://acme.example.com/fetch",
      credentialPath: "plugins.entries.acme.config.webFetch.apiKey",
      getCredentialValue: (fetchConfig) => fetchConfig?.acme?.apiKey,
      setCredentialValue: (fetchConfigTarget, value) => {
        const acme = (fetchConfigTarget.acme ??= {});
        acme.apiKey = value;
      },
      createTool: () => ({
        description: "Fetch a page through Acme Fetch.",
        parameters: {},
        execute: async (args) => ({ content: [] }),
      }),
    });
    
    api.registerWebSearchProvider({
      id: "acme-ai-search",
      label: "Acme Search",
      search: async (req) => ({ content: [] }),
    });
    
  • テスト

    ステップ 6: テスト

    import { describe, it, expect } from "vitest";
    // Export your provider config object from index.ts or a dedicated file
    import { acmeProvider } from "./provider.js";
    
    describe("acme-ai provider", () => {
      it("resolves dynamic models", () => {
        const model = acmeProvider.resolveDynamicModel!({
          modelId: "acme-beta-v3",
        } as any);
        expect(model.id).toBe("acme-beta-v3");
        expect(model.provider).toBe("acme-ai");
      });
    
      it("returns catalog when key is available", async () => {
        const result = await acmeProvider.catalog!.run({
          resolveProviderApiKey: () => ({ apiKey: "test-key" }),
        } as any);
        expect(result?.provider?.models).toHaveLength(2);
      });
    
      it("returns null catalog when no key", async () => {
        const result = await acmeProvider.catalog!.run({
          resolveProviderApiKey: () => ({ apiKey: undefined }),
        } as any);
        expect(result).toBeNull();
      });
    });
    
  • ClawHub に公開する

    Provider plugins は、他の外部コード Plugin と同じ方法で公開します。

    clawhub package publish your-org/your-plugin --dry-run
    clawhub package publish your-org/your-plugin
    

    ここでは従来の Skills 専用の公開エイリアスを使用しないでください。Plugin パッケージは clawhub package publish を使用する必要があります。

    ファイル構造

    <bundled-plugin-root>/acme-ai/
    ├── package.json              # openclaw.providers metadata
    ├── openclaw.plugin.json      # Manifest with provider auth metadata
    ├── index.ts                  # definePluginEntry + registerProvider
    └── src/
        ├── provider.test.ts      # Tests
        └── usage.ts              # Usage endpoint (optional)
    

    カタログ順序のリファレンス

    catalog.order は、組み込み provider に対してカタログをいつマージするかを制御します。

    順序 タイミング ユースケース
    simple 最初のパス 単純な API キー provider
    profile simple の後 認証プロファイルで制御される provider
    paired profile の後 複数の関連エントリを合成する
    late 最後のパス 既存の provider を上書きする(衝突時に勝つ)

    次のステップ

    • Channel Plugins - Plugin がチャネルも提供する場合
    • SDK Runtime - api.runtime ヘルパー(TTS、検索、サブエージェント)
    • SDK Overview - 完全なサブパスインポートリファレンス
    • Plugin Internals - フックの詳細とバンドル済みの例

    関連