Building plugins
ساخت Pluginهای ارائهدهنده
این راهنما مراحل ساخت یک Plugin ارائهدهنده را توضیح میدهد که یک ارائهدهنده مدل (LLM) را به OpenClaw اضافه میکند. در پایان، ارائهدهندهای با کاتالوگ مدل، احراز هویت با کلید API، و تفکیک پویای مدل خواهید داشت.
مرور گامبهگام
بسته و manifest
گام ۱: بسته و 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-"]},"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}}manifest مقدار setup.providers[].envVars را اعلام میکند تا OpenClaw بتواند
credentials را بدون بارگذاری runtime Plugin شما تشخیص دهد. زمانی providerAuthAliases
را اضافه کنید که یک گونه ارائهدهنده باید از احراز هویت id ارائهدهنده دیگری دوباره استفاده کند. modelSupport
اختیاری است و به OpenClaw امکان میدهد Plugin ارائهدهنده شما را از شناسههای کوتاهشده
مدل مانند acme-large پیش از وجود hookهای runtime بهصورت خودکار بارگذاری کند. اگر
ارائهدهنده را در ClawHub منتشر میکنید، آن فیلدهای openclaw.compat و openclaw.build
در package.json الزامی هستند.
ثبت ارائهدهنده
یک ارائهدهنده متن حداقلی به id، label، auth، و catalog نیاز دارد.
catalog hook متعلق به ارائهدهنده برای runtime/config است؛ میتواند APIهای زنده
فروشنده را فراخوانی کند و ورودیهای models.providers را برگرداند.
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 سطح کاتالوگ control-plane جدیدتر
برای رابط کاربری list/help/picker است. از آن برای ردیفهای متن، تولید تصویر،
تولید ویدئو، و تولید موسیقی استفاده کنید. فراخوانیهای endpoint فروشنده و
نگاشت پاسخ را در Plugin نگه دارید؛ OpenClaw مالک شکل مشترک ردیف، برچسبهای
source، و رندر help است.
این یک ارائهدهنده کارآمد است. کاربران اکنون میتوانند
openclaw onboard --acme-ai-api-key <key> را اجرا کنند و
acme-ai/acme-large را بهعنوان مدل خود انتخاب کنند.
کشف زنده مدل
اگر ارائهدهنده شما یک API به سبک /models ارائه میکند، endpoint مخصوص ارائهدهنده
و projection ردیف را در Plugin خود نگه دارید و از
openclaw/plugin-sdk/provider-catalog-live-runtime برای چرخه عمر fetch مشترک
استفاده کنید. این helper به شما fetchهای HTTP محافظتشده، headerهای provider-auth،
خطاهای HTTP ساختاریافته، caching با TTL، و رفتار fallback ایستا میدهد، بدون آنکه
policy ارائهدهنده را در core OpenClaw قرار دهد.
زمانی از buildLiveModelProviderConfig استفاده کنید که API زنده فقط به شما میگوید کدام
ردیفهای کاتالوگ ایستای متعلق به ارائهدهنده در حال حاضر در دسترس هستند:
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 ارائهدهنده metadata
غنیتری برمیگرداند و Plugin باید خودش ردیفها را به تعریفهای مدل OpenClaw
project کند:
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 باید auth-gated بماند و وقتی credential قابل استفادهای
در دسترس نیست null برگرداند. یک staticRun آفلاین یا fallback ایستا نگه دارید تا سطوح setup، docs،
tests، و picker به دسترسی شبکه زنده وابسته نباشند. از TTL
مناسب برای تازگی فهرست مدل استفاده کنید، از polling فایلسیستم در زمان درخواست پرهیز کنید،
و فقط زمانی readRows / readModelId مخصوص ارائهدهنده را پاس دهید که
پاسخ upstream شکل سازگار با OpenAI یعنی { data: [{ id, object }] }
نداشته باشد.
اگر ارائهدهنده upstream از tokenهای کنترلی متفاوتی نسبت به OpenClaw استفاده میکند، بهجای جایگزین کردن مسیر stream، یک transform کوچک دوطرفه متن اضافه کنید:
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 پیش از transport، prompt نهایی سیستم و محتوای پیام متنی را بازنویسی میکند.
output پیش از آنکه OpenClaw markerهای کنترلی خودش را parse کند یا تحویل channel را انجام دهد،
deltaهای متن assistant و متن نهایی را بازنویسی میکند.
برای ارائهدهندگان bundled که فقط یک ارائهدهنده متن با احراز هویت API-key
بههمراه یک runtime مبتنی بر کاتالوگ واحد ثبت میکنند، helper محدودتر
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 بتواند احراز هویت واقعی
ارائهدهنده را resolve کند استفاده میشود. ممکن است discovery اختصاصی ارائهدهنده را انجام دهد. از
buildStaticProvider فقط برای ردیفهای آفلاینی استفاده کنید که نمایش آنها پیش از پیکربندی احراز هویت
امن است؛ این مسیر نباید به credentials نیاز داشته باشد یا درخواست شبکهای انجام دهد.
نمایش فعلی models list --all در OpenClaw کاتالوگهای static را
فقط برای provider plugins همراهشده، با config خالی، env خالی، و بدون
مسیرهای agent/workspace اجرا میکند.
اگر جریان احراز هویت شما همچنین لازم دارد models.providers.*، aliasها، و
مدل پیشفرض agent را هنگام onboarding وصله کند، از preset helperهای
openclaw/plugin-sdk/provider-onboard استفاده کنید. محدودترین helperها عبارتاند از
createDefaultModelPresetAppliers(...)،
createDefaultModelsPresetAppliers(...)، و
createModelCatalogPresetAppliers(...).
وقتی endpoint بومی یک ارائهدهنده از usage blockهای streamed روی
انتقال عادی openai-completions پشتیبانی میکند، بهجای hardcode کردن
بررسیهای provider-id، helperهای کاتالوگ مشترک در
openclaw/plugin-sdk/provider-catalog-shared را ترجیح دهید. supportsNativeStreamingUsageCompat(...) و
applyProviderNativeStreamingUsageCompat(...) پشتیبانی را از
capability map مربوط به endpoint تشخیص میدهند، بنابراین endpointهای بومی به سبک Moonshot/DashScope همچنان
opt in میشوند حتی وقتی یک Plugin از provider id سفارشی استفاده میکند.
مثالهای discovery زنده در بالا APIهای ارائهدهنده به سبک /models را پوشش میدهند. این
discovery را داخل catalog.run نگه دارید، آن را به احراز هویت قابلاستفاده محدود کنید، و
staticRun را برای تولید کاتالوگ آفلاین بدون شبکه نگه دارید.
افزودن resolve پویای مدل
اگر ارائهدهنده شما IDهای دلخواه مدل را میپذیرد (مانند proxy یا router)،
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, }),});اگر resolve کردن به فراخوانی شبکهای نیاز دارد، از prepareDynamicModel برای warm-up ناهمگام
استفاده کنید - resolveDynamicModel پس از کامل شدن آن دوباره اجرا میشود.
افزودن hookهای runtime (در صورت نیاز)
بیشتر ارائهدهندهها فقط به catalog + resolveDynamicModel نیاز دارند. hookها را
بهتدریج و بر اساس نیاز ارائهدهنده خود اضافه کنید.
helper builderهای مشترک اکنون رایجترین خانوادههای replay/tool-compat را پوشش میدهند، بنابراین Pluginها معمولا نیازی ندارند هر hook را یکییکی دستی سیمکشی کنند:
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,});خانوادههای replay موجود امروز:
| خانواده | آنچه سیمکشی میکند | مثالهای همراهشده |
|---|---|---|
openai-compatible |
سیاست replay مشترک به سبک OpenAI برای انتقالهای سازگار با OpenAI، شامل پاکسازی tool-call-id، اصلاح ترتیب assistant-first، و اعتبارسنجی generic Gemini-turn در جایی که انتقال به آن نیاز دارد | moonshot, ollama, xai, zai |
anthropic-by-model |
سیاست replay آگاه از Claude که با modelId انتخاب میشود، تا انتقالهای Anthropic-message فقط زمانی cleanup اختصاصی thinking-block مربوط به Claude را دریافت کنند که مدل resolveشده واقعا یک Claude id باشد |
amazon-bedrock, anthropic-vertex |
google-gemini |
سیاست replay بومی Gemini بههمراه پاکسازی bootstrap replay. خانواده مشترک، خروجی متنی Gemini CLI را روی reasoning برچسبخورده نگه میدارد؛ ارائهدهنده مستقیم google مقدار resolveReasoningOutputMode را به native override میکند، چون thinking در Gemini API بهصورت thought partهای بومی میآید. |
google, google-gemini-cli |
passthrough-gemini |
پاکسازی thought-signature مربوط به Gemini برای مدلهای Gemini که از طریق انتقالهای proxy سازگار با OpenAI اجرا میشوند؛ اعتبارسنجی replay بومی Gemini یا بازنویسیهای bootstrap را فعال نمیکند | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
سیاست ترکیبی برای ارائهدهندههایی که سطوح مدل Anthropic-message و سازگار با OpenAI را در یک Plugin ترکیب میکنند؛ حذف اختیاری thinking-block فقط برای Claude محدود به سمت Anthropic میماند | minimax |
خانوادههای stream موجود امروز:
| خانواده | آنچه سیمکشی میکند | مثالهای همراهشده |
|---|---|---|
google-thinking |
نرمالسازی payload مربوط به thinking در Gemini روی مسیر stream مشترک | google, google-gemini-cli |
kilocode-thinking |
wrapper reasoning مربوط به Kilo روی مسیر stream proxy مشترک، با kilo/auto و idهای reasoning پشتیبانینشده proxy که از thinking تزریقشده عبور میکنند |
kilocode |
moonshot-thinking |
نگاشت payload باینری native-thinking در Moonshot از config + سطح /think |
moonshot |
minimax-fast-mode |
بازنویسی مدل fast-mode در MiniMax روی مسیر stream مشترک | minimax, minimax-portal |
openai-responses-defaults |
wrapperهای مشترک Responses بومی OpenAI/Codex: headerهای attribution، /fast/serviceTier، verbosity متن، جستوجوی وب بومی Codex، شکلدهی payload سازگار با reasoning، و مدیریت context در Responses |
openai |
openrouter-thinking |
wrapper reasoning مربوط به OpenRouter برای routeهای proxy، با عبور از unsupported-model/auto بهصورت متمرکز |
openrouter |
tool-stream-default-on |
wrapper پیشفرضروشن tool_stream برای ارائهدهندههایی مانند Z.AI که tool streaming را مگر در صورت غیرفعالسازی صریح میخواهند |
zai |
درزهای SDK که family builderها را پشتیبانی میکنند
هر family builder از helperهای عمومی سطح پایینتر تشکیل شده که از همان package صادر میشوند، و وقتی یک ارائهدهنده لازم دارد از الگوی رایج خارج شود میتوانید از آنها استفاده کنید:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...)، و replay builderهای خام (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). همچنین helperهای replay مربوط به Gemini (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) و helperهای endpoint/model (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId) را صادر میکند.openclaw/plugin-sdk/provider-stream-ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...)، بهعلاوه wrapperهای مشترک OpenAI/Codex (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper)، wrapper سازگار با OpenAI مربوط به DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper)، cleanup پیشپرکردن thinking در Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper)، سازگاری tool-call متن ساده (createPlainTextToolCallCompatWrapper)، و wrapperهای مشترک proxy/provider (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-stream-shared- wrapperهای سبک payload و event برای مسیرهای داغ ارائهدهنده، شاملcreateOpenAICompatibleCompletionsThinkingOffWrapper,createPayloadPatchStreamWrapper,createPlainTextToolCallCompatWrapper,normalizeOpenAICompatibleReasoningPayload(...)، وsetQwenChatTemplateThinking(...).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai")، و helperهای زیرین schema ارائهدهنده.
برای ارائهدهندههای خانواده Gemini، حالت خروجی reasoning را با
transport همراستا نگه دارید. ارائهدهندههای مستقیم Google Gemini API باید از خروجی reasoning
native استفاده کنند تا OpenClaw بدون افزودن directiveهای prompt
<think> / <final>، thought partهای بومی را مصرف کند. backendهای به سبک Gemini CLI فقطمتنی
که پاسخ نهایی JSON/text را parse میکنند، میتوانند قرارداد برچسبخورده مشترک
google-gemini را نگه دارند.
برخی helperهای stream عمدا provider-local میمانند. @openclaw/anthropic-provider، wrapAnthropicProviderStream، resolveAnthropicBetas، resolveAnthropicFastMode، resolveAnthropicServiceTier، و builderهای wrapper سطح پایینتر Anthropic را در درز عمومی api.ts / contract-api.ts خودش نگه میدارد، چون handling بتای OAuth مربوط به Claude و gating مربوط به context1m را کدگذاری میکنند. Plugin مربوط به xAI نیز بهطور مشابه شکلدهی Responses بومی xAI را در wrapStreamFn خودش نگه میدارد (aliasهای /fast، مقدار پیشفرض tool_stream، cleanup مربوط به strict-tool پشتیبانینشده، حذف payload reasoning اختصاصی xAI).
همین الگوی package-root همچنین پشتوانه @openclaw/openai-provider (builderهای provider، helperهای default-model، builderهای realtime provider) و @openclaw/openrouter-provider (builder ارائهدهنده بهعلاوه helperهای onboarding/config) است.
تبادل token
برای ارائهدهندههایی که پیش از هر فراخوانی inference به token exchange نیاز دارند:
prepareRuntimeAuth: async (ctx) => { const exchanged = await exchangeToken(ctx.apiKey); return { apiKey: exchanged.token, baseUrl: exchanged.baseUrl, expiresAt: exchanged.expiresAt, };},headerهای سفارشی
برای ارائهدهندههایی که به headerهای سفارشی request یا تغییرات body نیاز دارند:
// wrapStreamFn returns a StreamFn derived from ctx.streamFnwrapStreamFn: (ctx) => { if (!ctx.streamFn) return undefined; const inner = ctx.streamFn; return async (params) => { params.headers = { ...params.headers, "X-Acme-Version": "2", }; return inner(params); };},هویت transport بومی
برای ارائهدهندههایی که روی transportهای generic HTTP یا WebSocket به headerهای request/session یا metadata بومی نیاز دارند:
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,}),Usage and billing
برای ارائهدهندگانی که دادههای مصرف/صورتحساب را ارائه میکنند:
resolveUsageAuth: async (ctx) => { const auth = await ctx.resolveOAuthToken(); return auth ? { token: auth.token } : null;},fetchUsageSnapshot: async (ctx) => { return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);},resolveUsageAuth سه نتیجه دارد. وقتی ارائهدهنده اعتبارنامه مصرف/صورتحساب دارد،
{ token, accountId? } را برگردانید. فقط زمانی
{ handled: true } را برگردانید که ارائهدهنده احراز هویت مصرف را بهطور قطعی
مدیریت کرده اما توکن مصرف قابل استفاده ندارد، و OpenClaw باید fallback عمومی
کلید API/OAuth را رد کند. وقتی ارائهدهنده درخواست را مدیریت نکرده
و OpenClaw باید با fallback عمومی ادامه دهد، null یا undefined را برگردانید.
All available provider hooks
OpenClaw hookها را به این ترتیب فراخوانی میکند. بیشتر ارائهدهندگان فقط از ۲ تا ۳ مورد استفاده میکنند:
فیلدهای ارائهدهنده که فقط برای سازگاری هستند و OpenClaw دیگر آنها را فراخوانی نمیکند، مانند
ProviderPlugin.capabilities و suppressBuiltInModel، اینجا فهرست نشدهاند.
| # | Hook | زمان استفاده |
|---|---|---|
| 1 | catalog |
کاتالوگ مدل یا پیشفرضهای URL پایه |
| 2 | applyConfigDefaults |
پیشفرضهای سراسری متعلق به ارائهدهنده هنگام مادیسازی پیکربندی |
| 3 | normalizeModelId |
پاکسازی نام مستعار شناسه مدل legacy/preview پیش از lookup |
| 4 | normalizeTransport |
پاکسازی api / baseUrl خانواده ارائهدهنده پیش از مونتاژ عمومی مدل |
| 5 | normalizeConfig |
نرمالسازی پیکربندی models.providers.<id> |
| 6 | applyNativeStreamingUsageCompat |
بازنویسیهای سازگاری مصرف streaming بومی برای ارائهدهندگان پیکربندی |
| 7 | resolveConfigApiKey |
حل احراز هویت نشانگر env متعلق به ارائهدهنده |
| 8 | resolveSyntheticAuth |
احراز هویت synthetic محلی/خودمیزبان یا متکی به پیکربندی |
| 9 | shouldDeferSyntheticProfileAuth |
پایین آوردن جاینگهدارهای پروفایل ذخیرهشده synthetic پشت احراز هویت env/config |
| 10 | resolveDynamicModel |
پذیرش شناسههای دلخواه مدل upstream |
| 11 | prepareDynamicModel |
دریافت ناهمگام metadata پیش از حل |
| 12 | normalizeResolvedModel |
بازنویسیهای transport پیش از runner |
| 13 | normalizeToolSchemas |
پاکسازی schema ابزار متعلق به ارائهدهنده پیش از ثبت |
| 14 | inspectToolSchemas |
عیبیابی schema ابزار متعلق به ارائهدهنده |
| 15 | resolveReasoningOutputMode |
قرارداد خروجی reasoning برچسبدار در برابر بومی |
| 16 | prepareExtraParams |
پارامترهای پیشفرض درخواست |
| 17 | createStreamFn |
transport کاملاً سفارشی StreamFn |
| 19 | wrapStreamFn |
پوششهای سفارشی header/body روی مسیر معمول stream |
| 20 | resolveTransportTurnState |
headerها/metadata بومی برای هر turn |
| 21 | resolveWebSocketSessionPolicy |
headerهای نشست WS بومی/دوره آرامسازی |
| 22 | formatApiKey |
شکل توکن runtime سفارشی |
| 23 | refreshOAuth |
refresh سفارشی OAuth |
| 24 | buildAuthDoctorHint |
راهنمای ترمیم احراز هویت |
| 25 | matchesContextOverflowError |
تشخیص overflow متعلق به ارائهدهنده |
| 26 | classifyFailoverReason |
طبقهبندی rate-limit/overload متعلق به ارائهدهنده |
| 27 | isCacheTtlEligible |
دروازهگذاری TTL کش prompt |
| 28 | buildMissingAuthMessage |
راهنمای سفارشی نبود احراز هویت |
| 29 | augmentModelCatalog |
ردیفهای synthetic برای سازگاری رو به جلو |
| 30 | resolveThinkingProfile |
مجموعه گزینه /think ویژه مدل |
| 31 | isBinaryThinking |
سازگاری روشن/خاموش thinking دودویی |
| 32 | supportsXHighThinking |
سازگاری پشتیبانی reasoning با xhigh |
| 33 | resolveDefaultThinkingLevel |
سازگاری سیاست پیشفرض /think |
| 34 | isModernModelRef |
تطبیق مدل live/smoke |
| 35 | prepareRuntimeAuth |
تبادل توکن پیش از inference |
| 36 | resolveUsageAuth |
parsing سفارشی اعتبارنامه مصرف |
| 37 | fetchUsageSnapshot |
endpoint سفارشی مصرف |
| 38 | createEmbeddingProvider |
adapter embedding متعلق به ارائهدهنده برای حافظه/جستوجو |
| 39 | buildReplayPolicy |
سیاست سفارشی replay/compaction رونوشت |
| 40 | sanitizeReplayHistory |
بازنویسیهای replay ویژه ارائهدهنده پس از پاکسازی عمومی |
| 41 | validateReplayTurns |
اعتبارسنجی سختگیرانه turnهای replay پیش از runner تعبیهشده |
| 42 | onModelSelected |
callback پس از انتخاب (مثلاً telemetry) |
نکات fallback runtime:
normalizeConfigابتدا ارائهدهنده منطبق را بررسی میکند، سپس سایر Pluginهای ارائهدهنده دارای hook را تا زمانی که یکی واقعاً پیکربندی را تغییر دهد. اگر هیچ hook ارائهدهندهای یک ورودی پیکربندی پشتیبانیشده خانواده Google را بازنویسی نکند، نرمالساز پیکربندی Google که همراه بسته است همچنان اعمال میشود.resolveConfigApiKeyوقتی hook ارائهدهنده ارائه شده باشد از آن استفاده میکند. Amazon Bedrock حل نشانگر env مربوط به AWS را در Plugin ارائهدهنده خودش نگه میدارد؛ خود احراز هویت runtime همچنان وقتی باauth: "aws-sdk"پیکربندی شده باشد از زنجیره پیشفرض AWS SDK استفاده میکند.resolveThinkingProfile(ctx)،providerانتخابشده،modelId، راهنمای کاتالوگreasoningادغامشده اختیاری، و facts ادغامشده اختیاریcompatمدل را دریافت میکند. فقط برای انتخاب UI/profile thinking ارائهدهنده ازcompatاستفاده کنید.resolveSystemPromptContributionبه ارائهدهنده اجازه میدهد راهنمای system-prompt آگاه از کش را برای یک خانواده مدل تزریق کند. وقتی رفتار به یک ارائهدهنده/خانواده مدل تعلق دارد و باید جداسازی کش پایدار/پویا را حفظ کند، آن را بهbefore_prompt_buildترجیح دهید.
برای توضیحات دقیق و مثالهای دنیای واقعی، Internals: Provider Runtime Hooks را ببینید.
Add extra capabilities (optional)
گام ۵: افزودن قابلیتهای بیشتر
یک Plugin ارائهدهنده میتواند embeddingها، گفتار، رونویسی بلادرنگ، صدای بلادرنگ، درک رسانه، تولید تصویر، تولید ویدئو، واکشی وب و جستوجوی وب را در کنار inference متنی ثبت کند. OpenClaw این را بهعنوان Plugin با hybrid-capability طبقهبندی میکند؛ الگوی پیشنهادی برای Pluginهای شرکتی (یک Plugin برای هر فروشنده). ببینید: Internals: Capability Ownership.
هر قابلیت را داخل register(api) در کنار فراخوانی موجود
api.registerProvider(...) ثبت کنید. فقط tabهایی را انتخاب کنید که نیاز دارید:
Speech (TTS)
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(); } },});برای شکستهای HTTP ارائهدهنده از assertOkOrThrowProviderError(...) استفاده کنید تا
Pluginها خواندن محدودشده بدنه خطا، parsing خطای JSON، و
پسوندهای request-id مشترک داشته باشند.
Realtime transcription
createRealtimeTranscriptionWebSocketSession(...) را ترجیح دهید؛ helper مشترک
capture پروکسی، backoff اتصال مجدد، flushing هنگام close، handshakeهای ready،
صفگذاری صوت، و عیبیابی رخداد close را مدیریت میکند. Plugin شما
فقط رخدادهای upstream را map میکند.
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" }); }, }); },});ارائهدهندگان STT دستهای که صوت multipart را POST میکنند باید از
buildAudioTranscriptionFormData(...) از
openclaw/plugin-sdk/provider-http استفاده کنند. این helper نام فایلهای upload را
نرمال میکند، از جمله uploadهای AAC که برای APIهای رونویسی سازگار
به نام فایل به سبک M4A نیاز دارند.
Realtime voice
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, }),});Declare capabilities تا talk.catalog بتواند حالتهای معتبر،
ترابریها، قالبهای صوتی، و پرچمهای ویژگی را برای کلاینتهای Talk
مرورگر و بومی ارائه کند. وقتی یک ترابری میتواند تشخیص دهد که یک
انسان در حال قطع کردن پخش دستیار است و ارائهدهنده از کوتاهکردن یا
پاککردن پاسخ صوتی فعال پشتیبانی میکند، handleBargeIn را پیادهسازی کنید.
Media understanding
api.registerMediaUnderstandingProvider({ id: "acme-ai", capabilities: ["image", "audio"], describeImage: async (req) => ({ text: "A photo of..." }), transcribeAudio: async (req) => ({ text: "Transcript..." }),});ارائهدهندگان رسانه محلی یا خودمیزبان که عمداً به اعتبارنامه نیاز
ندارند، میتوانند resolveAuth را ارائه کنند و kind: "none" برگردانند.
OpenClaw همچنان دروازه احراز هویت معمول را برای ارائهدهندگانی که
صراحتاً اعلام آمادگی نمیکنند حفظ میکند. ارائهدهندگان موجود میتوانند
همچنان req.apiKey را بخوانند؛ ارائهدهندگان جدید بهتر است از req.auth
استفاده کنند.
api.registerMediaUnderstandingProvider({ id: "local-audio", capabilities: ["audio"], resolveAuth: () => ({ kind: "none", source: "local-audio plugin no-auth", }), transcribeAudio: async (req) => ({ text: "Transcript..." }),});Embeddings
api.registerEmbeddingProvider({ id: "acme-ai", defaultModel: "acme-embed", transport: "remote", authProviderId: "acme-ai", create: async ({ model }) => ({ provider: { id: "acme-ai", model, dimensions: 1536, embed: async (input) => { const text = typeof input === "string" ? input : input.text; return fetchAcmeEmbedding(text); }, embedBatch: async (inputs) => Promise.all( inputs.map((input) => fetchAcmeEmbedding(typeof input === "string" ? input : input.text), ), ), }, }),});همان شناسه را در contracts.embeddingProviders اعلام کنید. این قرارداد
عمومی embedding برای تولید بردار قابل استفاده مجدد، از جمله جستوجوی
حافظه است. registerMemoryEmbeddingProvider(...) سازگاری منسوخشده
برای آداپتورهای موجودِ ویژه حافظه است.
Image and video generation
قابلیتهای ویدئو از شکلی آگاه به حالت استفاده میکنند: generate،
imageToVideo، و videoToVideo. فیلدهای تجمیعی تخت مانند
maxInputImages / 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", defaultTimeoutMs: 600_000, 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 fetch and search
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: [] }),});Test
مرحله ۶: آزمایش
import { describe, it, expect } from "vitest";// Export your provider config object from index.ts or a dedicated fileimport { 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
Pluginهای ارائهدهنده به همان روش هر Plugin کد خارجی دیگری منتشر میشوند:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginاینجا از نام مستعار انتشار قدیمیِ فقط مخصوص skill استفاده نکنید؛ بستههای 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 |
آخرین گذر | بازنویسی ارائهدهندگان موجود (در برخوردها برنده میشود) |
گامهای بعدی
- Pluginهای کانال - اگر Plugin شما یک کانال هم ارائه میکند
- زمان اجرای SDK - کمککنندههای
api.runtime(TTS، جستوجو، زیرعامل) - نمای کلی SDK - مرجع کامل import زیربخشها
- جزئیات داخلی Plugin - جزئیات hook و نمونههای همراه