Plugins

Xây dựng Plugin nhà cung cấp

Hướng dẫn này trình bày cách xây dựng một provider plugin thêm một nhà cung cấp mô hình (LLM) vào OpenClaw. Khi hoàn tất, bạn sẽ có một provider với danh mục mô hình, xác thực bằng khóa API và phân giải mô hình động.

Hướng dẫn từng bước

  • Package and manifest

    Bước 1: Package và manifest

    {
    "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
    }
    }
    

    Manifest khai báo providerAuthEnvVars để OpenClaw có thể phát hiện thông tin xác thực mà không cần tải runtime của plugin. Thêm providerAuthAliases khi một biến thể provider cần dùng lại xác thực của một id provider khác. modelSupport là tùy chọn và cho phép OpenClaw tự động tải provider plugin của bạn từ các id mô hình viết tắt như acme-large trước khi có runtime hook. Nếu bạn phát hành provider trên ClawHub, các trường openclaw.compatopenclaw.build đó là bắt buộc trong package.json.

  • Register the provider

    Một provider tối thiểu cần có id, label, authcatalog:

    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,
                    },
                  ],
                },
              };
            },
          },
        });
      },
    });
    

    Đây là một provider hoạt động được. Giờ đây người dùng có thể openclaw onboard --acme-ai-api-key <key> và chọn acme-ai/acme-large làm mô hình của họ.

    Nếu provider upstream dùng token điều khiển khác với OpenClaw, hãy thêm một phép biến đổi văn bản hai chiều nhỏ thay vì thay thế đường dẫn stream:

    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 viết lại prompt hệ thống cuối cùng và nội dung tin nhắn văn bản trước khi truyền tải. output viết lại delta văn bản của trợ lý và văn bản cuối cùng trước khi OpenClaw phân tích các marker điều khiển riêng của nó hoặc phân phối qua kênh.

    Với các provider đi kèm chỉ đăng ký một provider văn bản có xác thực bằng khóa API cùng một runtime duy nhất dựa trên catalog, hãy ưu tiên helper hẹp hơn 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 là đường dẫn catalog trực tiếp được dùng khi OpenClaw có thể phân giải xác thực provider thật. Nó có thể thực hiện discovery riêng cho provider. Chỉ dùng buildStaticProvider cho các hàng offline an toàn để hiển thị trước khi xác thực được cấu hình; nó không được yêu cầu thông tin xác thực hoặc thực hiện yêu cầu mạng. Màn hình models list --all của OpenClaw hiện chỉ thực thi catalog tĩnh cho các provider plugin đi kèm, với config rỗng, env rỗng và không có đường dẫn tác tử/workspace.

    Nếu luồng xác thực của bạn cũng cần vá models.providers.*, alias và mô hình mặc định của tác tử trong quá trình onboarding, hãy dùng các helper preset từ openclaw/plugin-sdk/provider-onboard. Các helper hẹp nhất là createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...)createModelCatalogPresetAppliers(...).

    Khi endpoint native của provider hỗ trợ block usage dạng stream trên transport openai-completions thông thường, hãy ưu tiên các helper catalog dùng chung trong openclaw/plugin-sdk/provider-catalog-shared thay vì hardcode kiểm tra id provider. supportsNativeStreamingUsageCompat(...)applyProviderNativeStreamingUsageCompat(...) phát hiện hỗ trợ từ bản đồ capability của endpoint, nên các endpoint kiểu Moonshot/DashScope native vẫn tự bật ngay cả khi một plugin đang dùng id provider tùy chỉnh.

  • Add dynamic model resolution

    Nếu provider của bạn chấp nhận ID mô hình tùy ý (như proxy hoặc router), hãy thêm 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,
      }),
    });
    

    Nếu việc phân giải cần gọi mạng, hãy dùng prepareDynamicModel để khởi động async trước - resolveDynamicModel sẽ chạy lại sau khi hoàn tất.

  • Add runtime hooks (as needed)

    Hầu hết provider chỉ cần catalog + resolveDynamicModel. Thêm hook dần dần khi provider của bạn cần chúng.

    Các helper builder dùng chung hiện đã bao phủ những nhóm replay/tool-compat phổ biến nhất, nên plugin thường không cần tự nối từng hook một:

    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,
    });
    

    Các nhóm replay hiện có:

    Nhóm Nội dung được nối vào Ví dụ đi kèm
    openai-compatible Chính sách replay dùng chung kiểu OpenAI cho transport tương thích OpenAI, bao gồm vệ sinh tool-call-id, sửa thứ tự assistant-first và kiểm tra Gemini-turn chung khi transport cần moonshot, ollama, xai, zai
    anthropic-by-model Chính sách replay nhận biết Claude được chọn theo modelId, để các transport tin nhắn Anthropic chỉ nhận dọn dẹp thinking-block riêng cho Claude khi mô hình đã phân giải thật sự là một id Claude amazon-bedrock, anthropic-vertex
    google-gemini Chính sách replay Gemini native cùng vệ sinh bootstrap replay và chế độ reasoning-output có gắn thẻ google, google-gemini-cli
    passthrough-gemini Vệ sinh thought-signature Gemini cho các mô hình Gemini chạy qua transport proxy tương thích OpenAI; không bật kiểm tra replay Gemini native hoặc viết lại bootstrap openrouter, kilocode, opencode, opencode-go
    hybrid-anthropic-openai Chính sách hybrid cho provider kết hợp bề mặt mô hình tin nhắn Anthropic và tương thích OpenAI trong một plugin; việc loại bỏ thinking-block chỉ dành cho Claude tùy chọn vẫn được giới hạn ở phía Anthropic minimax

    Các nhóm luồng hiện có hôm nay:

    Nhóm Thành phần được nối vào Ví dụ đi kèm
    google-thinking Chuẩn hóa payload suy nghĩ Gemini trên đường dẫn luồng dùng chung google, google-gemini-cli
    kilocode-thinking Wrapper suy luận Kilo trên đường dẫn luồng proxy dùng chung, với kilo/auto và các id suy luận proxy không được hỗ trợ bỏ qua suy nghĩ được chèn kilocode
    moonshot-thinking Ánh xạ payload native-thinking nhị phân của Moonshot từ cấu hình + cấp độ /think moonshot
    minimax-fast-mode Viết lại mô hình chế độ nhanh MiniMax trên đường dẫn luồng dùng chung minimax, minimax-portal
    openai-responses-defaults Các wrapper Responses OpenAI/Codex native dùng chung: header quy kết, /fast/serviceTier, độ chi tiết văn bản, tìm kiếm web Codex native, định dạng payload tương thích suy luận, và quản lý ngữ cảnh Responses openai, openai-codex
    openrouter-thinking Wrapper suy luận OpenRouter cho các tuyến proxy, với việc bỏ qua mô hình không được hỗ trợ/auto được xử lý tập trung openrouter
    tool-stream-default-on Wrapper tool_stream bật mặc định cho các nhà cung cấp như Z.AI muốn phát trực tuyến công cụ trừ khi bị tắt rõ ràng zai
    Các seam SDK vận hành các bộ dựng nhóm

    Mỗi bộ dựng nhóm được cấu thành từ các helper công khai cấp thấp hơn được xuất từ cùng gói, bạn có thể dùng khi một nhà cung cấp cần đi lệch khỏi mẫu chung:

    • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamily, buildProviderReplayFamilyHooks(...), và các bộ dựng replay thô (buildOpenAICompatibleReplayPolicy, buildAnthropicReplayPolicyForModel, buildGoogleGeminiReplayPolicy, buildHybridAnthropicOrOpenAIReplayPolicy). Cũng xuất các helper replay Gemini (sanitizeGoogleGeminiReplayHistory, resolveTaggedReasoningOutputMode) và helper endpoint/mô hình (resolveProviderEndpoint, normalizeProviderId, normalizeGooglePreviewModelId, normalizeNativeXaiModelId).
    • openclaw/plugin-sdk/provider-stream - ProviderStreamFamily, buildProviderStreamFamilyHooks(...), composeProviderStreamWrappers(...), cùng các wrapper OpenAI/Codex dùng chung (createOpenAIAttributionHeadersWrapper, createOpenAIFastModeWrapper, createOpenAIServiceTierWrapper, createOpenAIResponsesContextManagementWrapper, createCodexNativeWebSearchWrapper), wrapper tương thích OpenAI DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), dọn dẹp điền sẵn suy nghĩ Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper), và các wrapper proxy/nhà cung cấp dùng chung (createOpenRouterWrapper, createToolStreamWrapper, createMinimaxFastModeWrapper).
    • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamily, buildProviderToolCompatFamilyHooks("gemini"), các helper schema Gemini nền tảng (normalizeGeminiToolSchemas, inspectGeminiToolSchemas), và helper tương thích xAI (resolveXaiModelCompatPatch(), applyXaiModelCompat(model)). Plugin xAI đi kèm dùng normalizeResolvedModel + contributeResolvedModelCompat với các helper này để giữ quy tắc xAI thuộc sở hữu của nhà cung cấp.

    Một số helper luồng cố ý giữ cục bộ theo nhà cung cấp. @openclaw/anthropic-provider giữ wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier, và các bộ dựng wrapper Anthropic cấp thấp hơn trong seam công khai api.ts / contract-api.ts của riêng nó vì chúng mã hóa xử lý beta Claude OAuth và cổng context1m. Plugin xAI cũng giữ định dạng Responses xAI native trong wrapStreamFn của riêng nó (bí danh /fast, tool_stream mặc định, dọn dẹp strict-tool không được hỗ trợ, loại bỏ payload suy luận đặc thù xAI).

    Mẫu gốc gói tương tự cũng hỗ trợ @openclaw/openai-provider (bộ dựng nhà cung cấp, helper mô hình mặc định, bộ dựng nhà cung cấp realtime) và @openclaw/openrouter-provider (bộ dựng nhà cung cấp cùng helper onboarding/cấu hình).

    Trao đổi token

    Với các nhà cung cấp cần trao đổi token trước mỗi lệnh gọi suy luận:

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

    Header tùy chỉnh

    Với các nhà cung cấp cần header yêu cầu tùy chỉnh hoặc chỉnh sửa body:

    // 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);
      };
    },
    

    Danh tính truyền tải native

    Với các nhà cung cấp cần header yêu cầu/phiên native hoặc metadata trên các truyền tải HTTP hoặc WebSocket chung:

    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,
    }),
    

    Mức sử dụng và thanh toán

    Với các nhà cung cấp hiển thị dữ liệu mức sử dụng/thanh toán:

    resolveUsageAuth: async (ctx) => {
      const auth = await ctx.resolveOAuthToken();
      return auth ? { token: auth.token } : null;
    },
    fetchUsageSnapshot: async (ctx) => {
      return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);
    },
    
    Tất cả hook nhà cung cấp có sẵn

    OpenClaw gọi các hook theo thứ tự này. Hầu hết nhà cung cấp chỉ dùng 2-3: Các trường nhà cung cấp chỉ để tương thích mà OpenClaw không còn gọi nữa, chẳng hạn như ProviderPlugin.capabilitiessuppressBuiltInModel, không được liệt kê ở đây.

    # Hook Khi nào dùng
    1 catalog Catalog mô hình hoặc mặc định URL cơ sở
    2 applyConfigDefaults Mặc định toàn cục do nhà cung cấp sở hữu trong quá trình cụ thể hóa cấu hình
    3 normalizeModelId Dọn dẹp bí danh model-id legacy/preview trước khi tra cứu
    4 normalizeTransport Dọn dẹp api / baseUrl theo nhóm nhà cung cấp trước khi lắp ráp mô hình chung
    5 normalizeConfig Chuẩn hóa cấu hình models.providers.<id>
    6 applyNativeStreamingUsageCompat Viết lại tương thích streaming-usage native cho nhà cung cấp cấu hình
    7 resolveConfigApiKey Phân giải xác thực env-marker do nhà cung cấp sở hữu
    8 resolveSyntheticAuth Xác thực tổng hợp cục bộ/tự lưu trữ hoặc dựa trên cấu hình
    9 shouldDeferSyntheticProfileAuth Hạ thấp placeholder hồ sơ đã lưu tổng hợp phía sau xác thực env/cấu hình
    10 resolveDynamicModel Chấp nhận các ID mô hình upstream tùy ý
    11 prepareDynamicModel Tải metadata bất đồng bộ trước khi phân giải
    12 normalizeResolvedModel Viết lại truyền tải trước runner
    13 contributeResolvedModelCompat Cờ tương thích cho mô hình nhà cung cấp phía sau một truyền tải tương thích khác
    14 normalizeToolSchemas Dọn dẹp tool-schema do nhà cung cấp sở hữu trước khi đăng ký
    15 inspectToolSchemas Chẩn đoán tool-schema do nhà cung cấp sở hữu
    16 resolveReasoningOutputMode Hợp đồng đầu ra suy luận dạng gắn thẻ so với native
    17 prepareExtraParams Tham số yêu cầu mặc định
    18 createStreamFn Truyền tải StreamFn hoàn toàn tùy chỉnh
    19 wrapStreamFn Wrapper header/body tùy chỉnh trên đường dẫn luồng thông thường
    20 resolveTransportTurnState Header/metadata native theo từng lượt
    21 resolveWebSocketSessionPolicy Header/cool-down phiên WS native
    22 formatApiKey Dạng token runtime tùy chỉnh
    23 refreshOAuth Làm mới OAuth tùy chỉnh
    24 buildAuthDoctorHint Hướng dẫn sửa xác thực
    25 matchesContextOverflowError Phát hiện tràn do nhà cung cấp sở hữu
    26 classifyFailoverReason Phân loại giới hạn tốc độ/quá tải do nhà cung cấp sở hữu
    27 isCacheTtlEligible Cổng TTL bộ nhớ đệm prompt
    28 buildMissingAuthMessage Gợi ý thiếu xác thực tùy chỉnh
    29 augmentModelCatalog Hàng forward-compat tổng hợp
    30 resolveThinkingProfile Bộ tùy chọn /think theo mô hình
    31 isBinaryThinking Tương thích bật/tắt suy nghĩ nhị phân
    32 supportsXHighThinking Tương thích hỗ trợ suy luận xhigh
    33 resolveDefaultThinkingLevel Tương thích chính sách /think mặc định
    34 isModernModelRef Khớp mô hình live/smoke
    35 prepareRuntimeAuth Trao đổi token trước suy luận
    36 resolveUsageAuth Phân tích thông tin xác thực mức sử dụng tùy chỉnh
    37 fetchUsageSnapshot Endpoint mức sử dụng tùy chỉnh
    38 createEmbeddingProvider Adapter embedding do nhà cung cấp sở hữu cho bộ nhớ/tìm kiếm
    39 buildReplayPolicy Chính sách replay/Compaction bản ghi tùy chỉnh
    40 sanitizeReplayHistory Viết lại replay đặc thù nhà cung cấp sau khi dọn dẹp chung
    41 validateReplayTurns Xác thực lượt replay nghiêm ngặt trước runner nhúng
    42 onModelSelected Callback sau khi chọn (ví dụ: telemetry)

    Ghi chú fallback runtime:

    • normalizeConfig kiểm tra nhà cung cấp khớp trước, rồi đến các Plugin nhà cung cấp khác có hook cho đến khi một hook thực sự thay đổi cấu hình. Nếu không hook nhà cung cấp nào viết lại mục cấu hình nhóm Google được hỗ trợ, bộ chuẩn hóa cấu hình Google đi kèm vẫn được áp dụng.
    • resolveConfigApiKey dùng hook nhà cung cấp khi được hiển thị. Đường dẫn amazon-bedrock đi kèm cũng có bộ phân giải env-marker AWS tích hợp ở đây, dù bản thân xác thực runtime Bedrock vẫn dùng chuỗi mặc định AWS SDK.
    • resolveSystemPromptContribution cho phép một nhà cung cấp chèn hướng dẫn system-prompt nhận biết bộ nhớ đệm cho một nhóm mô hình. Ưu tiên dùng nó thay vì before_prompt_build khi hành vi thuộc về một nhóm nhà cung cấp/mô hình và cần giữ nguyên phân tách bộ nhớ đệm ổn định/động.

    Để xem mô tả chi tiết và ví dụ thực tế, hãy xem Nội bộ: Hook Runtime Nhà cung cấp.

  • Thêm năng lực bổ sung (tùy chọn)

    Bước 5: Thêm năng lực bổ sung

    Một Plugin nhà cung cấp có thể đăng ký speech, phiên âm realtime, giọng nói realtime, hiểu media, tạo ảnh, tạo video, web fetch và tìm kiếm web bên cạnh suy luận văn bản. OpenClaw phân loại đây là một Plugin hybrid-capability - mẫu được khuyến nghị cho Plugin công ty (một Plugin cho mỗi nhà cung cấp). Xem Nội bộ: Quyền sở hữu năng lực.

    Đăng ký từng năng lực bên trong register(api) cùng với lệnh gọi api.registerProvider(...) hiện có của bạn. Chỉ chọn các tab bạn cần:

    Giọng nói (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();
        }
      },
    });
    

    Dùng assertOkOrThrowProviderError(...) cho các lỗi HTTP của nhà cung cấp để các plugin chia sẻ việc đọc thân lỗi được giới hạn, phân tích lỗi JSON và hậu tố request-id.

    Phiên âm thời gian thực

    Ưu tiên createRealtimeTranscriptionWebSocketSession(...) - helper dùng chung xử lý ghi nhận proxy, backoff khi kết nối lại, flush khi đóng, handshake sẵn sàng, xếp hàng âm thanh và chẩn đoán sự kiện đóng. Plugin của bạn chỉ ánh xạ các sự kiện từ thượng nguồn.

    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" });
          },
        });
      },
    });
    

    Các nhà cung cấp STT theo lô POST âm thanh multipart nên dùng buildAudioTranscriptionFormData(...) từ openclaw/plugin-sdk/provider-http. Helper chuẩn hóa tên tệp tải lên, bao gồm các bản tải lên AAC cần tên tệp kiểu M4A cho những API phiên âm tương thích.

    Giọng nói thời gian thực

    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,
      }),
    });
    

    Khai báo capabilities để talk.catalog có thể hiển thị các chế độ, transport, định dạng âm thanh và cờ tính năng hợp lệ cho các client Talk trên trình duyệt và native. Triển khai handleBargeIn khi transport có thể phát hiện con người đang ngắt phần phát lại của trợ lý và nhà cung cấp hỗ trợ cắt ngắn hoặc xóa phản hồi âm thanh đang hoạt động.

    Hiểu nội dung đa phương tiện

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

    Tạo hình ảnh và video

    Khả năng video dùng một dạng nhận biết chế độ: generate, imageToVideovideoToVideo. Các trường tổng hợp phẳng như maxInputImages / maxInputVideos / maxDurationSeconds không đủ để công bố rõ ràng khả năng hỗ trợ chế độ biến đổi hoặc các chế độ bị tắt. Tạo nhạc cũng theo cùng mẫu với các khối generate / edit rõ ràng.

    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: [] }),
    });
    

    Tìm nạp và tìm kiếm 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: [] }),
    });
    
  • Kiểm thử

    Bước 6: Kiểm thử

    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();
      });
    });
    
  • Xuất bản lên ClawHub

    Các plugin nhà cung cấp được xuất bản giống như mọi plugin mã bên ngoài khác:

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

    Đừng dùng bí danh xuất bản cũ chỉ dành cho Skills ở đây; các gói plugin nên dùng clawhub package publish.

    Cấu trúc tệp

    <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)
    

    Tham chiếu thứ tự danh mục

    catalog.order kiểm soát thời điểm danh mục của bạn được hợp nhất so với các nhà cung cấp tích hợp sẵn:

    Thứ tự Khi nào Trường hợp sử dụng
    simple Lượt đầu tiên Nhà cung cấp dùng khóa API đơn giản
    profile Sau simple Nhà cung cấp được kiểm soát bằng hồ sơ xác thực
    paired Sau profile Tổng hợp nhiều mục liên quan
    late Lượt cuối Ghi đè nhà cung cấp hiện có (thắng khi xung đột)

    Bước tiếp theo

    • Plugin kênh - nếu plugin của bạn cũng cung cấp một kênh
    • Runtime SDK - helper api.runtime (TTS, tìm kiếm, tác tử con)
    • Tổng quan SDK - tham chiếu đầy đủ về import đường dẫn con
    • Nội bộ Plugin - chi tiết hook và ví dụ được đóng gói

    Liên quan