الانتقال إلى المحتوى الرئيسي
يرشدك هذا الدليل خلال بناء Plugin موفّر يضيف موفّر نماذج (LLM) إلى OpenClaw. في النهاية سيكون لديك موفّر يتضمن فهرس نماذج، ومصادقة بمفتاح API، وحلًّا ديناميكيًا للنماذج.
إذا لم تبنِ أي OpenClaw Plugin من قبل، فاقرأ بدء الاستخدام أولًا لمعرفة بنية الحزمة الأساسية وإعداد البيان.
تضيف Plugins الموفّرات نماذج إلى حلقة الاستدلال العادية في OpenClaw. إذا كان النموذج يجب أن يعمل عبر عفريت وكيل أصلي يملك السلاسل، أو Compaction، أو أحداث الأدوات، فاقرن الموفّر مع حزمة وكيل بدلًا من وضع تفاصيل بروتوكول العفريت في النواة.

شرح تفصيلي

1

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-"]
  },
  "setup": {
    "providers": [
      {
        "id": "acme-ai",
        "envVars": ["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
  }
}
يصرّح البيان بـ setup.providers[].envVars حتى يتمكن OpenClaw من اكتشاف بيانات الاعتماد دون تحميل وقت تشغيل Plugin. أضف providerAuthAliases عندما ينبغي لمتغير موفّر أن يعيد استخدام مصادقة معرّف موفّر آخر. modelSupport اختياري، ويتيح لـ OpenClaw تحميل Plugin الموفّر تلقائيًا من معرّفات نماذج مختصرة مثل acme-large قبل وجود خطافات وقت التشغيل. إذا نشرت الموفّر على ClawHub، فحقلا openclaw.compat وopenclaw.build مطلوبان في package.json.
2

Register the provider

يحتاج موفّر النصوص الأدنى إلى id وlabel وauth وcatalog. catalog هو خطاف وقت التشغيل/الإعدادات الذي يملكه الموفّر؛ ويمكنه استدعاء واجهات API الحية للبائع وإرجاع إدخالات models.providers.
index.ts
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,
                },
              ],
            },
          };
        },
      },
    });

    api.registerModelCatalogProvider({
      provider: "acme-ai",
      kinds: ["text"],
      liveCatalog: async (ctx) => {
        const apiKey = ctx.resolveProviderApiKey("acme-ai").apiKey;
        if (!apiKey) return null;
        return [
          {
            kind: "text",
            provider: "acme-ai",
            model: "acme-large",
            label: "Acme Large",
            source: "live",
          },
        ];
      },
    });
  },
});
registerModelCatalogProvider هو سطح فهرس مستوى التحكم الأحدث لواجهة مستخدم القائمة/المساعدة/المنتقي. استخدمه لصفوف النصوص، وتوليد الصور، وتوليد الفيديو، وتوليد الموسيقى. أبقِ استدعاءات نقاط نهاية البائع وتخطيط الاستجابات داخل Plugin؛ يملك OpenClaw شكل الصف المشترك، وتسميات المصدر، وعرض المساعدة.هذا موفّر عامل. يمكن للمستخدمين الآن تشغيل openclaw onboard --acme-ai-api-key <key> واختيار acme-ai/acme-large كنموذجهم.

اكتشاف النماذج الحية

إذا كان موفّرك يعرض API بنمط /models، فأبقِ نقطة النهاية الخاصة بالموفّر وإسقاط الصفوف داخل Plugin الخاص بك، واستخدم openclaw/plugin-sdk/provider-catalog-live-runtime لدورة حياة الجلب المشتركة. يمنحك المساعد جلب HTTP محميًا، وترويسات مصادقة الموفّر، وأخطاء HTTP مهيكلة، وتخزينًا مؤقتًا بمدة TTL، وسلوك رجوع ثابتًا دون وضع سياسة الموفّر في نواة OpenClaw.استخدم buildLiveModelProviderConfig عندما تخبرك API الحية فقط بأي صفوف الفهرس الثابتة التي يملكها الموفّر والمتاحة حاليًا:
index.ts
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import {
  buildLiveModelProviderConfig,
  type LiveModelCatalogFetchGuard,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";

const STATIC_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,
  },
] as const;

async function buildAcmeLiveProvider(params: {
  apiKey: string;
  discoveryApiKey?: string;
  fetchGuard?: LiveModelCatalogFetchGuard;
}) {
  return await buildLiveModelProviderConfig({
    providerId: "acme-ai",
    endpoint: "https://api.acme-ai.com/v1/models",
    providerConfig: {
      baseUrl: "https://api.acme-ai.com/v1",
      api: "openai-completions",
    },
    models: STATIC_MODELS,
    apiKey: params.apiKey,
    discoveryApiKey: params.discoveryApiKey,
    fetchGuard: params.fetchGuard,
    ttlMs: 60_000,
    auditContext: "acme-ai-model-discovery",
  });
}

export default definePluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  register(api) {
    api.registerProvider({
      id: "acme-ai",
      label: "Acme AI",
      catalog: {
        order: "simple",
        run: async (ctx) => {
          const auth = ctx.resolveProviderAuth("acme-ai");
          const apiKey =
            auth.apiKey ?? ctx.resolveProviderApiKey("acme-ai").apiKey;
          if (!apiKey) return null;
          return {
            provider: await buildAcmeLiveProvider({
              apiKey,
              discoveryApiKey: auth.discoveryApiKey,
            }),
          };
        },
      },
      staticCatalog: {
        order: "simple",
        run: async () => ({
          provider: {
            baseUrl: "https://api.acme-ai.com/v1",
            api: "openai-completions",
            models: [...STATIC_MODELS],
          },
        }),
      },
    });
  },
});
استخدم getCachedLiveProviderModelRows عندما تعيد API الخاصة بالموفّر بيانات وصفية أغنى ويحتاج Plugin إلى إسقاط الصفوف إلى تعريفات نماذج OpenClaw بنفسه:
index.ts
import {
  getCachedLiveProviderModelRows,
  LiveModelCatalogHttpError,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";

async function discoverAcmeModels(apiKey: string) {
  try {
    const rows = await getCachedLiveProviderModelRows({
      providerId: "acme-ai",
      endpoint: "https://api.acme-ai.com/v1/models",
      apiKey,
      ttlMs: 60_000,
      auditContext: "acme-ai-model-discovery",
    });
    return rows
      .map((row) => projectAcmeModel(row))
      .filter((model) => model !== null);
  } catch (error) {
    if (error instanceof LiveModelCatalogHttpError) {
      return STATIC_MODELS;
    }
    throw error;
  }
}
ينبغي أن يظل run محكومًا بالمصادقة وأن يعيد null عندما لا تتوفر بيانات اعتماد قابلة للاستخدام. أبقِ staticRun غير متصل أو رجوعًا ثابتًا حتى لا تعتمد أسطح الإعداد، والوثائق، والاختبارات، والمنتقي على وصول حي إلى الشبكة. استخدم TTL مناسبة لحداثة قائمة النماذج، وتجنب استطلاع نظام الملفات وقت الطلب، ومرّر readRows / readModelId خاصًا بالموفّر فقط عندما لا تكون استجابة المصدر بشكل متوافق مع OpenAI مثل { data: [{ id, object }] }.إذا كان الموفّر الأصلي يستخدم رموز تحكم مختلفة عن 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 إضافة إلى وقت تشغيل واحد مدعوم بالفهرس، فضّل المساعد الأضيق 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 فقط للصفوف غير المتصلة الآمنة للعرض قبل تهيئة المصادقة؛ ويجب ألا يتطلب بيانات اعتماد أو يجري طلبات شبكة. عرض models list --all في OpenClaw ينفّذ حاليًا الكتالوجات الثابتة فقط من أجل Plugins المزوّدين المضمّنة، مع إعدادات فارغة، وبيئة فارغة، ودون مسارات وكيل/مساحة عمل.إذا كان تدفق المصادقة لديك يحتاج أيضًا إلى تصحيح models.providers.*، والأسماء المستعارة، والنموذج الافتراضي للوكيل أثناء الإعداد، فاستخدم مساعدات الإعداد المسبق من openclaw/plugin-sdk/provider-onboard. أضيق المساعدات نطاقًا هي createDefaultModelPresetAppliers(...)، وcreateDefaultModelsPresetAppliers(...)، و createModelCatalogPresetAppliers(...).عندما تدعم نقطة النهاية الأصلية لمزوّد كتل استخدام متدفقة على ناقل openai-completions العادي، ففضّل مساعدات الكتالوج المشتركة في openclaw/plugin-sdk/provider-catalog-shared بدلًا من ترميز فحوصات معرّف المزوّد مباشرة. يكتشف supportsNativeStreamingUsageCompat(...) و applyProviderNativeStreamingUsageCompat(...) الدعم من خريطة قدرات نقطة النهاية، لذلك تظل نقاط النهاية الأصلية بأسلوب Moonshot/DashScope مشتركة حتى عندما يستخدم Plugin معرّف مزوّد مخصصًا.تغطي أمثلة الاكتشاف الحي أعلاه واجهات API للمزوّدين بأسلوب /models. أبقِ ذلك الاكتشاف داخل catalog.run، محكومًا بمصادقة قابلة للاستخدام، وأبقِ staticRun بلا شبكة لتوليد الكتالوج دون اتصال.
3

Add dynamic model resolution

إذا كان مزوّدك يقبل معرّفات نماذج عشوائية (مثل وكيل وسيط أو موجّه)، فأضف 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 مرة أخرى بعد اكتماله.
4

Add runtime hooks (as needed)

لا يحتاج معظم المزوّدين إلا إلى catalog + resolveDynamicModel. أضف الخطافات تدريجيًا بحسب ما يتطلبه مزوّدك.تغطي بُناة المساعدات المشتركة الآن أكثر عائلات إعادة التشغيل/توافق الأدوات شيوعًا، لذلك لا تحتاج Plugins عادةً إلى توصيل كل خطاف يدويًا واحدًا تلو الآخر:
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، بما في ذلك تنظيف معرّف استدعاء الأداة، وإصلاحات ترتيب المساعد أولًا، والتحقق العام من أدوار Gemini حيث يحتاج الناقل إلى ذلكmoonshot, ollama, xai, zai
anthropic-by-modelسياسة إعادة تشغيل واعية بـ Claude تُختار بواسطة modelId، بحيث لا تحصل نواقل رسائل Anthropic إلا على تنظيف كتل التفكير الخاص بـ Claude عندما يكون النموذج المحلول فعلًا معرّف Claudeamazon-bedrock, anthropic-vertex
google-geminiسياسة إعادة تشغيل Gemini الأصلية إضافة إلى تنظيف إعادة تشغيل التمهيد. تُبقي العائلة المشتركة Gemini CLI ذي مخرجات النص على الاستدلال الموسوم؛ ويتجاوز مزوّد google المباشر resolveReasoningOutputMode إلى native لأن تفكير Gemini API يصل كأجزاء تفكير أصلية.google, google-gemini-cli
passthrough-geminiتنظيف توقيع التفكير في Gemini لنماذج Gemini العاملة عبر نواقل وكيل متوافقة مع OpenAI؛ ولا يفعّل تحقق إعادة تشغيل Gemini الأصلي أو إعادة كتابة التمهيدopenrouter, kilocode, opencode, opencode-go
hybrid-anthropic-openaiسياسة هجينة للمزوّدين الذين يمزجون أسطح نماذج رسائل Anthropic والمتوافقة مع OpenAI في Plugin واحد؛ ويظل إسقاط كتل التفكير الاختياري الخاص بـ Claude محصورًا في جانب Anthropicminimax
عائلات البث المتاحة اليوم:
العائلةما الذي تربطهأمثلة مضمّنة
google-thinkingتطبيع حمولة تفكير Gemini على مسار البث المشتركgoogle, google-gemini-cli
kilocode-thinkingغلاف استدلال Kilo على مسار بث الوكيل المشترك، مع تخطي kilo/auto ومعرّفات استدلال الوكيل غير المدعومة للتفكير المُحقنkilocode
moonshot-thinkingربط حمولة التفكير الأصلي الثنائية في Moonshot من الإعداد + مستوى /thinkmoonshot
minimax-fast-modeإعادة كتابة نموذج الوضع السريع في MiniMax على مسار البث المشتركminimax, minimax-portal
openai-responses-defaultsأغلفة Responses الأصلية المشتركة لـ OpenAI/Codex: ترويسات الإسناد، و/fast/serviceTier، وإسهاب النص، وبحث الويب الأصلي في Codex، وتشكيل حمولة توافق الاستدلال، وإدارة سياق Responsesopenai
openrouter-thinkingغلاف استدلال OpenRouter لمسارات الوكيل، مع معالجة تخطيات النماذج غير المدعومة/auto مركزيًاopenrouter
tool-stream-default-onغلاف tool_stream مفعّل افتراضيًا للمزوّدين مثل Z.AI الذين يريدون بث الأدوات ما لم يُعطّل صراحةًzai
يتكوّن كل باني عائلة من مساعدات عامة أدنى مستوى مُصدّرة من الحزمة نفسها، ويمكنك استخدامها عندما يحتاج مزوّد إلى الخروج عن النمط الشائع:
  • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamily، وbuildProviderReplayFamilyHooks(...)، وبُناة إعادة التشغيل الخام (buildOpenAICompatibleReplayPolicy، وbuildAnthropicReplayPolicyForModel، وbuildGoogleGeminiReplayPolicy، وbuildHybridAnthropicOrOpenAIReplayPolicy). يصدّر أيضًا مساعدات إعادة تشغيل Gemini (sanitizeGoogleGeminiReplayHistory، وresolveTaggedReasoningOutputMode) ومساعدات نقطة النهاية/النموذج (resolveProviderEndpoint، وnormalizeProviderId، وnormalizeGooglePreviewModelId).
  • openclaw/plugin-sdk/provider-stream - ProviderStreamFamily، وbuildProviderStreamFamilyHooks(...)، وcomposeProviderStreamWrappers(...)، إضافة إلى أغلفة OpenAI/Codex المشتركة (createOpenAIAttributionHeadersWrapper، وcreateOpenAIFastModeWrapper، وcreateOpenAIServiceTierWrapper، وcreateOpenAIResponsesContextManagementWrapper، وcreateCodexNativeWebSearchWrapper)، وغلاف DeepSeek V4 المتوافق مع OpenAI (createDeepSeekV4OpenAICompatibleThinkingWrapper)، وتنظيف التمهيد المسبق لتفكير رسائل Anthropic (createAnthropicThinkingPrefillPayloadWrapper)، وتوافق استدعاء أدوات النص العادي (createPlainTextToolCallCompatWrapper)، وأغلفة الوكيل/المزوّد المشتركة (createOpenRouterWrapper، وcreateToolStreamWrapper، وcreateMinimaxFastModeWrapper).
  • openclaw/plugin-sdk/provider-stream-shared - أغلفة خفيفة للحمولة والأحداث لمسارات المزوّد الساخنة، بما في ذلك createOpenAICompatibleCompletionsThinkingOffWrapper، وcreatePayloadPatchStreamWrapper، وcreatePlainTextToolCallCompatWrapper، وnormalizeOpenAICompatibleReasoningPayload(...)، وsetQwenChatTemplateThinking(...).
  • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamily، وbuildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai")، ومساعدات مخطط المزوّد الأساسية.
بالنسبة إلى مزوّدي عائلة Gemini، أبقِ وضع مخرجات الاستدلال متوافقًا مع الناقل. ينبغي لمزوّدي Google Gemini API المباشرين استخدام مخرجات استدلال native كي يستهلك OpenClaw أجزاء التفكير الأصلية دون إضافة توجيهات مطالبة <think> / <final>. ويمكن للخلفيات النصية فقط بأسلوب Gemini CLI التي تحلل استجابة JSON/نص نهائية أن تبقي عقد google-gemini الموسوم المشترك.تبقى بعض مساعدات البث محلية للمزوّد عمدًا. يحتفظ @openclaw/anthropic-provider بـ wrapAnthropicProviderStream، وresolveAnthropicBetas، وresolveAnthropicFastMode، وresolveAnthropicServiceTier، وبُناة أغلفة Anthropic الأدنى مستوى في سطحه العام api.ts / contract-api.ts لأنها ترمز معالجة Claude OAuth beta وحوكمة context1m. وبالمثل يحتفظ Plugin xAI بتشكيل Responses الأصلي الخاص بـ xAI في wrapStreamFn الخاص به (أسماء /fast المستعارة، وtool_stream الافتراضي، وتنظيف الأدوات الصارمة غير المدعومة، وإزالة حمولة الاستدلال الخاصة بـ xAI).يدعم نمط جذر الحزمة نفسه أيضًا @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,
  };
},
يستدعي OpenClaw الخطافات بهذا الترتيب. يستخدم معظم المزوّدين 2-3 فقط: حقول المزوّد الخاصة بالتوافق فقط التي لم يعد OpenClaw يستدعيها، مثل ProviderPlugin.capabilities وsuppressBuiltInModel، غير مدرجة هنا.
#الخطافمتى يُستخدم
1catalogكتالوج النماذج أو إعدادات URL الأساسية الافتراضية
2applyConfigDefaultsالإعدادات العامة الافتراضية المملوكة للمزوّد أثناء تجسيد الإعدادات
3normalizeModelIdتنظيف أسماء نماذج قديمة/تجريبية قبل البحث
4normalizeTransportتنظيف api / baseUrl الخاص بعائلة المزوّد قبل تجميع النموذج العام
5normalizeConfigتطبيع إعدادات models.providers.<id>
6applyNativeStreamingUsageCompatإعادة كتابة توافق الاستخدام أثناء البث الأصلي لمزوّدي الإعدادات
7resolveConfigApiKeyحل المصادقة بعلامات البيئة المملوك للمزوّد
8resolveSyntheticAuthمصادقة اصطناعية محلية/ذاتية الاستضافة أو مدعومة بالإعدادات
9shouldDeferSyntheticProfileAuthخفض أولوية العناصر النائبة الاصطناعية للملفات الشخصية المخزّنة خلف مصادقة البيئة/الإعدادات
10resolveDynamicModelقبول معرّفات نماذج عشوائية من المنبع
11prepareDynamicModelجلب بيانات وصفية غير متزامن قبل الحل
12normalizeResolvedModelإعادة كتابة النقل قبل المشغّل
13normalizeToolSchemasتنظيف مخططات الأدوات المملوك للمزوّد قبل التسجيل
14inspectToolSchemasتشخيصات مخططات الأدوات المملوكة للمزوّد
15resolveReasoningOutputModeعقد مخرجات الاستدلال الموسومة مقابل الأصلية
16prepareExtraParamsمعاملات الطلب الافتراضية
17createStreamFnنقل StreamFn مخصص بالكامل
19wrapStreamFnأغلفة رؤوس/نص مخصصة على مسار البث العادي
20resolveTransportTurnStateرؤوس/بيانات وصفية أصلية لكل دورة
21resolveWebSocketSessionPolicyرؤوس جلسة WS أصلية وفترة تهدئة
22formatApiKeyشكل رمز تشغيل مخصص
23refreshOAuthتحديث OAuth مخصص
24buildAuthDoctorHintإرشادات إصلاح المصادقة
25matchesContextOverflowErrorاكتشاف تجاوز السعة المملوك للمزوّد
26classifyFailoverReasonتصنيف حدود المعدل/التحميل الزائد المملوك للمزوّد
27isCacheTtlEligibleبوابة TTL لذاكرة التخزين المؤقت للموجّه
28buildMissingAuthMessageتلميح مخصص للمصادقة الناقصة
29augmentModelCatalogصفوف اصطناعية للتوافق المستقبلي
30resolveThinkingProfileمجموعة خيارات /think الخاصة بالنموذج
31isBinaryThinkingتوافق تشغيل/إيقاف التفكير الثنائي
32supportsXHighThinkingتوافق دعم الاستدلال xhigh
33resolveDefaultThinkingLevelتوافق سياسة /think الافتراضية
34isModernModelRefمطابقة نماذج الاختبار الحي/الدخاني
35prepareRuntimeAuthتبادل الرموز قبل الاستدلال
36resolveUsageAuthتحليل مخصص لاعتماد الاستخدام
37fetchUsageSnapshotنقطة نهاية استخدام مخصصة
38createEmbeddingProviderمحوّل تضمين مملوك للمزوّد للذاكرة/البحث
39buildReplayPolicyسياسة مخصصة لإعادة تشغيل/Compaction النص
40sanitizeReplayHistoryإعادة كتابة إعادة التشغيل الخاصة بالمزوّد بعد التنظيف العام
41validateReplayTurnsتحقق صارم من دورات إعادة التشغيل قبل المشغّل المضمن
42onModelSelectedرد نداء بعد الاختيار (مثل القياسات عن بُعد)
ملاحظات الرجوع في وقت التشغيل:
  • يتحقق normalizeConfig من المزوّد المطابق أولًا، ثم من Plugins المزوّدين الآخرين القادرين على الخطافات إلى أن يغيّر أحدهم الإعدادات فعليًا. إذا لم يُعد أي خطاف مزوّد كتابة إدخال إعدادات مدعوم من عائلة Google، يظل مطبّع إعدادات Google المضمّن مطبقًا.
  • يستخدم resolveConfigApiKey خطاف المزوّد عند كشفه. يحتفظ Amazon Bedrock بحل علامات بيئة AWS في Plugin المزوّد الخاص به؛ أما مصادقة وقت التشغيل نفسها فتظل تستخدم سلسلة AWS SDK الافتراضية عند إعدادها باستخدام auth: "aws-sdk".
  • يتلقى resolveThinkingProfile(ctx) قيمة provider المحددة، وmodelId، وتلميح كتالوج reasoning المدمج الاختياري، وحقائق compat للنموذج المدمجة الاختيارية. استخدم compat فقط لاختيار واجهة/ملف التفكير الخاص بالمزوّد.
  • يسمح resolveSystemPromptContribution للمزوّد بحقن إرشادات موجه نظام واعية بذاكرة التخزين المؤقت لعائلة نموذج. فضّله على before_prompt_build عندما يكون السلوك تابعًا لمزوّد/عائلة نموذج واحد ويجب أن يحافظ على فصل ذاكرة التخزين المؤقت المستقر/الديناميكي.
للاطلاع على أوصاف مفصلة وأمثلة من الواقع، راجع الداخليات: خطافات وقت تشغيل المزوّد.
5

إضافة قدرات إضافية (اختياري)

الخطوة 5: إضافة قدرات إضافية

يمكن لـ Plugin مزوّد تسجيل التضمينات، والكلام، والنسخ الفوري، والصوت الفوري، وفهم الوسائط، وتوليد الصور، وتوليد الفيديو، وجلب الويب، وبحث الويب إلى جانب استدلال النص. يصنّف OpenClaw هذا على أنه Plugin hybrid-capability - وهو النمط الموصى به لـ Plugins الشركات (Plugin واحد لكل بائع). راجع الداخليات: ملكية القدرات.سجّل كل قدرة داخل register(api) إلى جانب استدعاء api.registerProvider(...) الحالي. اختر علامات التبويب التي تحتاجها فقط:
import {
  assertOkOrThrowProviderError,
  postJsonRequest,
} from "openclaw/plugin-sdk/provider-http";

api.registerSpeechProvider({
  id: "acme-ai",
  label: "Acme Speech",
  defaultTimeoutMs: 120_000,
  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();
    }
  },
});
استخدم assertOkOrThrowProviderError(...) لإخفاقات HTTP الخاصة بالمزوّد حتى تشترك Plugins في قراءات نص الخطأ المحددة، وتحليل أخطاء JSON، ولواحق معرّف الطلب.
6

اختبار

الخطوة 6: الاختبار

src/provider.test.ts
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

تُنشر 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 في وقت دمج الكتالوج الخاص بك نسبةً إلى المزوّدين المضمّنين:
الترتيبمتىحالة الاستخدام
simpleالمرور الأولمزوّدو مفاتيح API البسيطة
profileبعد simpleمزوّدون مقيّدون بملفات تعريف المصادقة
pairedبعد profileإنشاء إدخالات متعددة مترابطة
lateالمرور الأخيرتجاوز المزوّدين الحاليين (يفوز عند التعارض)

الخطوات التالية

ذات صلة