Building plugins
การสร้าง Plugin สำหรับผู้ให้บริการ
สร้าง Plugin ผู้ให้บริการเพื่อเพิ่มผู้ให้บริการโมเดล (LLM) ให้กับ OpenClaw ซึ่งประกอบด้วยแค็ตตาล็อกโมเดล การยืนยันตัวตนด้วยคีย์ API และการแก้ไขโมเดลแบบไดนามิก
ขั้นตอนโดยละเอียด
แพ็กเกจและไฟล์ manifest
ขั้นตอนที่ 1: แพ็กเกจและไฟล์ 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}}setup.providers[].envVars ช่วยให้ OpenClaw ตรวจพบข้อมูลประจำตัวได้โดยไม่ต้อง
โหลดรันไทม์ของ Plugin เพิ่ม providerAuthAliases เมื่อตัวแปรย่อยของผู้ให้บริการ
ควรใช้การยืนยันตัวตนของรหัสผู้ให้บริการอื่นซ้ำ modelSupport เป็นตัวเลือก
และช่วยให้ OpenClaw โหลด Plugin ผู้ให้บริการของคุณโดยอัตโนมัติจากรหัสโมเดลแบบย่อ
เช่น acme-large ก่อนที่จะมีฮุกของรันไทม์ ต้องมี openclaw.compat
และ openclaw.build ใน package.json เพื่อเผยแพร่บน ClawHub
(openclaw.compat.pluginApi และ openclaw.build.openclawVersion
เป็นสองฟิลด์ที่จำเป็น ส่วน minGatewayVersion จะใช้
openclaw.install.minHostVersion เป็นค่าทดแทนเมื่อไม่ได้ระบุ)
ลงทะเบียนผู้ให้บริการ
ผู้ให้บริการข้อความแบบพื้นฐานต้องมี id, label, auth และ catalog
catalog คือฮุกของรันไทม์/การกำหนดค่าที่ผู้ให้บริการเป็นเจ้าของ โดยสามารถเรียก 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 คือพื้นผิวแค็ตตาล็อกของระนาบควบคุมรุ่นใหม่กว่า
สำหรับ UI รายการ/วิธีใช้/ตัวเลือก ซึ่งครอบคลุมแถว text, voice, image_generation,
video_generation และ music_generation เก็บการเรียกปลายทางของผู้จำหน่าย
และการแมปการตอบกลับไว้ใน 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 แบบสดแจ้งเพียงว่า
แถวใดในแค็ตตาล็อกคงที่ที่ผู้ให้บริการเป็นเจ้าของพร้อมใช้งานอยู่ในขณะนี้:
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
ด้วยตนเอง:
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 ที่เฉพาะเจาะจงกับผู้ให้บริการเฉพาะเมื่อ
การตอบกลับจากต้นทางไม่ได้อยู่ในรูปแบบ { data: [{ id, object }] }
ที่เข้ากันได้กับ OpenAI
หากผู้ให้บริการต้นทางใช้โทเค็นควบคุมที่ต่างจาก 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", provider: { label: "Acme AI", docsPath: "/providers/acme-ai", auth: [ { methodId: "api-key", label: "คีย์ API ของ Acme AI", hint: "คีย์ API จากแดชบอร์ด Acme AI ของคุณ", optionKey: "acmeAiApiKey", flagName: "--acme-ai-api-key", envVar: "ACME_AI_API_KEY", promptMessage: "ป้อนคีย์ API ของ Acme AI", 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 เรียกใช้แค็ตตาล็อกแบบคงที่
เฉพาะสำหรับ Plugin ผู้ให้บริการที่รวมมาให้ โดยใช้การกำหนดค่าว่าง ตัวแปรสภาพแวดล้อมว่าง และไม่มี
พาธของเอเจนต์/พื้นที่ทำงาน
หากโฟลว์การยืนยันตัวตนของคุณต้องแก้ไข 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 ไม่ใช้เครือข่ายสำหรับการสร้างแค็ตตาล็อกออฟไลน์
เพิ่มการระบุโมเดลแบบไดนามิก
หากผู้ให้บริการของคุณยอมรับรหัสโมเดลใดก็ได้ (เช่น พร็อกซีหรือเราเตอร์)
ให้เพิ่ม resolveDynamicModel:
api.registerProvider({ // ... id, label, auth, catalog จากด้านบน 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 จะทำงานอีกครั้งหลังจากเสร็จสิ้น
เพิ่มฮุกขณะรันไทม์ (ตามความจำเป็น)
ผู้ให้บริการส่วนใหญ่ต้องการเพียง catalog + resolveDynamicModel ให้เพิ่มฮุก
ทีละส่วนตามที่ผู้ให้บริการของคุณต้องการ
ขณะนี้ตัวสร้างตัวช่วยร่วมครอบคลุมกลุ่มความเข้ากันได้ของการเล่นซ้ำ/เครื่องมือ ที่พบบ่อยที่สุดแล้ว ดังนั้นโดยปกติ Plugin ไม่จำเป็นต้องเชื่อมต่อฮุกแต่ละตัวด้วยตนเองทีละตัว:
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 ก็ต่อเมื่อโมเดลที่ระบุได้เป็นรหัส Claude จริงเท่านั้น |
amazon-bedrock |
native-anthropic-by-model |
นโยบาย Claude ตามโมเดลแบบเดียวกับ anthropic-by-model พร้อมการปรับรหัสการเรียกเครื่องมือให้ถูกต้องและการคงรหัสการใช้เครื่องมือเนทีฟของ Anthropic สำหรับทรานสปอร์ตที่ต้องเก็บรหัสเนทีฟของผู้จำหน่ายไว้ |
anthropic-vertex, clawrouter |
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 ซึ่งเป็นทางเลือกจะยังจำกัดอยู่เฉพาะฝั่ง Anthropic | minimax |
กลุ่มสตรีมที่ใช้ได้ในปัจจุบัน:
| กลุ่ม | สิ่งที่เชื่อมต่อให้ | ตัวอย่างที่รวมมาให้ |
|---|---|---|
google-thinking |
การปรับเพย์โหลดการคิดของ Gemini ให้เป็นมาตรฐานบนเส้นทางสตรีมร่วม | google, google-gemini-cli |
kilocode-thinking |
ตัวห่อการให้เหตุผลของ Kilo บนเส้นทางสตรีมพร็อกซีร่วม โดย kilo/auto และรหัสการให้เหตุผลของพร็อกซีที่ไม่รองรับจะข้ามการแทรกการคิด |
kilocode |
moonshot-thinking |
การแมปเพย์โหลดการคิดเนทีฟแบบไบนารีของ Moonshot จากการกำหนดค่า + ระดับ /think |
moonshot |
minimax-fast-mode |
การเขียนโมเดลโหมดเร็วของ MiniMax ใหม่บนเส้นทางสตรีมร่วม | minimax, minimax-portal |
openai-responses-defaults |
ตัวห่อ Responses เนทีฟร่วมของ OpenAI/Codex ได้แก่ ส่วนหัวการระบุแหล่งที่มา, /fast/serviceTier, ระดับรายละเอียดข้อความ, การค้นหาเว็บเนทีฟของ Codex, การปรับรูปแบบเพย์โหลดให้เข้ากันได้กับการให้เหตุผล และการจัดการบริบทของ Responses |
openai |
openrouter-thinking |
ตัวห่อการให้เหตุผลของ OpenRouter สำหรับเส้นทางพร็อกซี โดยจัดการการข้ามโมเดลที่ไม่รองรับ/auto จากส่วนกลาง |
openrouter |
tool-stream-default-on |
ตัวห่อ tool_stream ที่เปิดเป็นค่าเริ่มต้นสำหรับผู้ให้บริการอย่าง Z.AI ซึ่งต้องการสตรีมเครื่องมือ เว้นแต่จะปิดไว้อย่างชัดเจน |
zai |
รอยต่อ SDK ที่ขับเคลื่อนตัวสร้างกลุ่ม
ตัวสร้างแต่ละกลุ่มประกอบขึ้นจากตัวช่วยสาธารณะระดับล่างที่ส่งออกจากแพ็กเกจเดียวกัน ซึ่งคุณสามารถเลือกใช้ได้เมื่อผู้ให้บริการต้องออกนอกแบบแผนทั่วไป:
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 Messages (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 ของตนเอง เนื่องจากรายการเหล่านี้เข้ารหัสการจัดการ OAuth เบต้าของ Claude และการจำกัด context1m ในทำนองเดียวกัน Plugin xAI จะเก็บการปรับรูปแบบ Responses เนทีฟของ xAI ไว้ใน wrapStreamFn ของตนเอง (/fast aliases, ค่าเริ่มต้น 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, };},ส่วนหัวแบบกำหนดเอง
สำหรับผู้ให้บริการที่ต้องใช้ส่วนหัวคำขอแบบกำหนดเองหรือแก้ไขเนื้อหาคำขอ:
// wrapStreamFn ส่งคืน StreamFn ที่สร้างจาก 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); };},อัตลักษณ์ทรานสปอร์ตเนทีฟ
สำหรับผู้ให้บริการที่ต้องใช้ส่วนหัวหรือข้อมูลเมตาของคำขอ/เซสชันแบบเนทีฟบน ทรานสปอร์ต 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);},resolveUsageAuth มีผลลัพธ์ที่เป็นไปได้สามแบบ ให้คืนค่า
{ token, accountId?, subscriptionType?, rateLimitTier? } เมื่อ
ผู้ให้บริการมีข้อมูลประจำตัวสำหรับการใช้งาน/การเรียกเก็บเงิน (ฟิลด์ที่ไม่บังคับจะส่งต่อ
เมตาดาต้าแผนที่ไม่เป็นความลับจากโปรไฟล์ที่แก้ไขแล้วไปยัง
fetchUsageSnapshot) ให้คืนค่า
{ handled: true } เฉพาะเมื่อผู้ให้บริการจัดการการยืนยันตัวตนสำหรับการใช้งาน
อย่างเด็ดขาดแล้ว แต่ไม่มีโทเค็นการใช้งานที่ใช้ได้ และ OpenClaw ต้องข้ามการใช้
API key/OAuth สำรองแบบทั่วไป ให้คืนค่า null หรือ undefined เมื่อผู้ให้บริการ
ไม่ได้จัดการคำขอ และ OpenClaw ควรดำเนินการใช้กลไกสำรองแบบทั่วไปต่อไป
ประกาศรหัสผู้ให้บริการใน contracts.usageProviders เมื่อสัญญาในไฟล์กำกับ
ดังกล่าวและฮุก ทั้งสองรายการ มีอยู่ OpenClaw จะรวม
ผู้ให้บริการไว้ในการรวบรวมข้อมูลการใช้งานโดยอัตโนมัติ โดยไม่โหลด Plugin
ของผู้ให้บริการอื่นที่ไม่เกี่ยวข้อง ไม่จำเป็นต้องอัปเดตรายการอนุญาตของแกนหลัก
fetchUsageSnapshot คืนค่าในรูปแบบที่ใช้ร่วมกันและไม่ผูกกับผู้ให้บริการ:
plan: การสมัครสมาชิกหรือป้ายกำกับคีย์ที่ผู้ให้บริการรายงานwindows: ช่วงโควตาที่รีเซ็ตได้ในรูปเปอร์เซ็นต์ที่ใช้ไปbilling: รายการbalance,spendหรือbudgetที่ระบุชนิดแล้ว โดยunitอาจเป็น สกุลเงินตามมาตรฐาน ISO หรือหน่วยของผู้ให้บริการ เช่นcreditssummary: บริบทเฉพาะของผู้ให้บริการแบบกระชับที่ไม่เหมาะกับ ฟิลด์แบบมีโครงสร้างเหล่านั้น
รักษาความหมายของสกุลเงินให้ถูกต้องแม่นยำ เครดิตของผู้ให้บริการไม่ใช่ USD เว้นแต่
สัญญาต้นทางจะระบุไว้เช่นนั้น Plugin ที่ติดตั้งใช้งานเฉพาะ
fetchUsageSnapshot ยังคงพร้อมใช้งานสำหรับตัวเรียกแบบชัดแจ้ง/สังเคราะห์ แต่
จะไม่ถูกค้นพบโดยอัตโนมัติ เนื่องจาก OpenClaw ไม่สามารถแก้ไขข้อมูลประจำตัวสำหรับการใช้งานของ Plugin นั้นได้
ฮุกทั่วไปของผู้ให้บริการ
OpenClaw เรียกฮุกตามลำดับโดยประมาณดังนี้สำหรับ Plugin ของโมเดล/ผู้ให้บริการ
ผู้ให้บริการส่วนใหญ่ใช้เพียง 2-3 รายการ นี่ไม่ใช่สัญญา ProviderPlugin
ทั้งหมด โปรดดู ส่วนภายใน: ฮุกรันไทม์ของผู้ให้บริการ
สำหรับ
รายการฮุกที่ครบถ้วนและถูกต้องในปัจจุบัน รวมถึงหมายเหตุเกี่ยวกับกลไกสำรอง
ฟิลด์ของผู้ให้บริการที่มีไว้เพื่อความเข้ากันได้เท่านั้นและ OpenClaw ไม่เรียกใช้อีกต่อไป เช่น
ProviderPlugin.capabilities และ suppressBuiltInModel จะไม่แสดงไว้
ที่นี่
| ฮุก | ควรใช้เมื่อใด |
|---|---|
catalog |
แค็ตตาล็อกโมเดลหรือค่าเริ่มต้นของ URL ฐาน |
applyConfigDefaults |
ค่าเริ่มต้นส่วนกลางที่ผู้ให้บริการเป็นเจ้าของระหว่างการสร้างการกำหนดค่า |
normalizeModelId |
ล้างนามแฝงรหัสโมเดลแบบเก่า/ตัวอย่างก่อนค้นหา |
normalizeTransport |
ล้าง api / baseUrl ของตระกูลผู้ให้บริการก่อนประกอบโมเดลทั่วไป |
normalizeConfig |
ปรับการกำหนดค่า models.providers.<id> ให้เป็นมาตรฐาน |
applyNativeStreamingUsageCompat |
เขียนการกำหนดค่าใหม่เพื่อรองรับความเข้ากันได้ของการใช้งานแบบสตรีมมิงเนทีฟ |
resolveConfigApiKey |
แก้ไขการยืนยันตัวตนด้วยตัวทำเครื่องหมายตัวแปรสภาพแวดล้อมที่ผู้ให้บริการเป็นเจ้าของ |
resolveSyntheticAuth |
การยืนยันตัวตนสังเคราะห์สำหรับระบบภายใน/โฮสต์เองหรือที่มีการกำหนดค่าเป็นแหล่งข้อมูล |
resolveExternalAuthProfiles |
ซ้อนทับโปรไฟล์การยืนยันตัวตนภายนอกที่ผู้ให้บริการเป็นเจ้าของสำหรับข้อมูลประจำตัวที่จัดการโดย CLI/แอป |
shouldDeferSyntheticProfileAuth |
ลดลำดับความสำคัญของตัวยึดตำแหน่งโปรไฟล์สังเคราะห์ที่จัดเก็บไว้ให้อยู่หลังการยืนยันตัวตนจากสภาพแวดล้อม/การกำหนดค่า |
resolveDynamicModel |
ยอมรับรหัสโมเดลต้นทางใดก็ได้ |
prepareDynamicModel |
ดึงเมตาดาต้าแบบอะซิงโครนัสก่อนแก้ไข |
normalizeResolvedModel |
เขียนการขนส่งใหม่ก่อนส่งให้ตัวรัน |
normalizeToolSchemas |
ล้างสคีมาเครื่องมือที่ผู้ให้บริการเป็นเจ้าของก่อนลงทะเบียน |
inspectToolSchemas |
วินิจฉัยสคีมาเครื่องมือที่ผู้ให้บริการเป็นเจ้าของ |
resolveReasoningOutputMode |
สัญญาเอาต์พุตการใช้เหตุผลแบบติดแท็กเทียบกับแบบเนทีฟ |
prepareExtraParams |
พารามิเตอร์คำขอเริ่มต้น |
createStreamFn |
การขนส่ง StreamFn แบบกำหนดเองทั้งหมด |
wrapStreamFn |
ตัวห่อส่วนหัว/เนื้อหาแบบกำหนดเองบนเส้นทางสตรีมปกติ |
resolveTransportTurnState |
ส่วนหัว/เมตาดาต้าเนทีฟต่อรอบ |
resolveWebSocketSessionPolicy |
ส่วนหัว/ช่วงพักของเซสชัน WS แบบเนทีฟ |
formatApiKey |
รูปแบบโทเค็นรันไทม์แบบกำหนดเอง |
refreshOAuth |
การรีเฟรช OAuth แบบกำหนดเอง |
buildAuthDoctorHint |
คำแนะนำในการซ่อมแซมการยืนยันตัวตน |
matchesContextOverflowError |
การตรวจจับบริบทล้นที่ผู้ให้บริการเป็นเจ้าของ |
classifyFailoverReason |
การจำแนกการจำกัดอัตรา/ภาระเกินที่ผู้ให้บริการเป็นเจ้าของ |
isCacheTtlEligible |
การควบคุม TTL ของแคชพรอมป์ |
buildMissingAuthMessage |
คำแนะนำแบบกำหนดเองเมื่อขาดการยืนยันตัวตน |
augmentModelCatalog |
แถวสังเคราะห์เพื่อความเข้ากันได้ในอนาคต (เลิกใช้แล้ว โปรดใช้ registerModelCatalogProvider) |
resolveThinkingProfile |
ชุดตัวเลือก /think เฉพาะโมเดล |
isBinaryThinking |
ความเข้ากันได้ของการเปิด/ปิดการคิดแบบไบนารี (เลิกใช้แล้ว โปรดใช้ resolveThinkingProfile) |
supportsXHighThinking |
ความเข้ากันได้กับการรองรับการใช้เหตุผลระดับ xhigh (เลิกใช้แล้ว โปรดใช้ resolveThinkingProfile) |
resolveDefaultThinkingLevel |
ความเข้ากันได้กับนโยบาย /think เริ่มต้น (เลิกใช้แล้ว โปรดใช้ resolveThinkingProfile) |
isModernModelRef |
การจับคู่โมเดลสำหรับการทดสอบจริง/การทดสอบเบื้องต้น |
prepareRuntimeAuth |
การแลกเปลี่ยนโทเค็นก่อนอนุมาน |
resolveUsageAuth |
การแยกวิเคราะห์ข้อมูลประจำตัวสำหรับการใช้งานแบบกำหนดเอง |
fetchUsageSnapshot |
ปลายทางการใช้งานแบบกำหนดเอง |
createEmbeddingProvider |
อะแดปเตอร์การฝังข้อมูลที่ผู้ให้บริการเป็นเจ้าของสำหรับหน่วยความจำ/การค้นหา |
buildReplayPolicy |
นโยบายการเล่นซ้ำทรานสคริปต์/Compaction แบบกำหนดเอง |
sanitizeReplayHistory |
การเขียนประวัติการเล่นซ้ำใหม่เฉพาะผู้ให้บริการหลังการล้างทั่วไป |
validateReplayTurns |
การตรวจสอบรอบการเล่นซ้ำอย่างเข้มงวดก่อนตัวรันแบบฝัง |
onModelSelected |
คอลแบ็กหลังเลือก (เช่น การวัดและส่งข้อมูลทางไกล) |
หมายเหตุเกี่ยวกับกลไกสำรองของรันไทม์:
normalizeConfigแก้ไข Plugin เจ้าของหนึ่งรายการต่อรหัสผู้ให้บริการ (ผู้ให้บริการที่มาพร้อมระบบก่อน จากนั้นจึงเป็น Plugin รันไทม์ที่ตรงกัน) และเรียกเฉพาะฮุกนั้น โดยไม่มีการสแกนผู้ให้บริการรายอื่น ฮุกnormalizeConfigของ Google เองเป็นตัวปรับรายการกำหนดค่าgoogle/google-vertex/google-antigravityให้เป็นมาตรฐาน ไม่ใช่กลไกสำรองแยกต่างหากของแกนหลักresolveConfigApiKeyใช้ฮุกของผู้ให้บริการเมื่อมีการเปิดเผย Amazon Bedrock เก็บการแก้ไขตัวทำเครื่องหมายตัวแปรสภาพแวดล้อม AWS ไว้ใน Plugin ของผู้ให้บริการ ส่วนการยืนยันตัวตนของรันไทม์ยังคงใช้สายโซ่เริ่มต้นของ AWS SDK เมื่อกำหนดค่าด้วยauth: "aws-sdk"resolveThinkingProfile(ctx)รับprovider,modelIdที่เลือก คำแนะนำแค็ตตาล็อกreasoningที่ผสานแล้วซึ่งเป็นตัวเลือก และข้อเท็จจริงcompatของโมเดลที่ผสานแล้วซึ่งเป็นตัวเลือก ใช้compatเพื่อเลือก UI/โปรไฟล์การคิดของผู้ให้บริการเท่านั้นresolveSystemPromptContributionช่วยให้ผู้ให้บริการแทรกคำแนะนำพรอมป์ระบบที่รับรู้แคชสำหรับตระกูลโมเดลได้ ควรใช้แทนฮุกbefore_prompt_buildแบบเก่าที่ใช้กับทั้ง Plugin เมื่อพฤติกรรมนั้นเป็นของผู้ให้บริการ/ตระกูลโมเดลหนึ่งราย และควรรักษาการแบ่งแคชแบบคงที่/ไดนามิกไว้
เพิ่มความสามารถเสริม (ไม่บังคับ)
ขั้นตอนที่ 5: เพิ่มความสามารถเสริม
Plugin ของผู้ให้บริการสามารถลงทะเบียนการฝังข้อมูล เสียงพูด การถอดเสียงแบบเรียลไทม์ เสียงแบบเรียลไทม์ การทำความเข้าใจสื่อ การสร้างภาพ การสร้างวิดีโอ การดึงข้อมูลจากเว็บ และการค้นหาเว็บควบคู่กับการอนุมานข้อความ OpenClaw จัดประเภทนี้เป็น Plugin แบบ ความสามารถผสม ซึ่งเป็นรูปแบบที่แนะนำสำหรับ Plugin ของบริษัท (หนึ่ง Plugin ต่อผู้จำหน่าย) โปรดดู ส่วนภายใน: ความเป็นเจ้าของความสามารถ
ลงทะเบียนแต่ละความสามารถภายใน register(api) ควบคู่กับการเรียก
api.registerProvider(...) ที่มีอยู่ เลือกเฉพาะแท็บที่คุณต้องการ:
เสียงพูด (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(); } },});ใช้ assertOkOrThrowProviderError(...) สำหรับความล้มเหลว HTTP ของผู้ให้บริการ เพื่อให้
Plugin ใช้การอ่านเนื้อหาข้อผิดพลาดแบบจำกัดขนาด การแยกวิเคราะห์ข้อผิดพลาด JSON และ
ส่วนต่อท้ายรหัสคำขอร่วมกัน
การถอดเสียงแบบเรียลไทม์
ควรใช้ createRealtimeTranscriptionWebSocketSession(...) โดยตัวช่วยที่ใช้ร่วมกัน
จะจัดการการบันทึกพร็อกซี การหน่วงเวลาลองเชื่อมต่อใหม่ การระบายข้อมูลเมื่อปิด การจับมือ
เพื่อระบุความพร้อม การจัดคิวเสียง และการวินิจฉัยเหตุการณ์ปิด 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" }); }, }); },});ผู้ให้บริการ STT แบบแบตช์ที่ POST เสียงแบบ multipart ควรใช้
buildAudioTranscriptionFormData(...) จาก
openclaw/plugin-sdk/provider-http ตัวช่วยนี้ปรับชื่อไฟล์ที่อัปโหลดให้เป็นมาตรฐาน
รวมถึงไฟล์ AAC ที่ต้องใช้ชื่อไฟล์ในรูปแบบ M4A เพื่อให้ทำงานร่วมกับ
API การถอดเสียงที่รองรับ
เสียงแบบเรียลไทม์
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, handlesInputAudioBargeIn: true, supportsToolCalls: true, }, isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey), createBridge: (req) => ({ // ตั้งค่านี้เฉพาะเมื่อผู้ให้บริการยอมรับผลตอบกลับจากเครื่องมือหลายรายการสำหรับ // การเรียกหนึ่งครั้ง เช่น ผลตอบกลับ "กำลังดำเนินการ" ทันที แล้วตามด้วย // ผลลัพธ์สุดท้าย supportsToolResultContinuation: false, connect: async () => {}, sendAudio: () => {}, setMediaTimestamp: () => {}, handleBargeIn: () => {}, submitToolResult: () => {}, acknowledgeMark: () => {}, close: () => {}, isConnected: () => true, }),});ประกาศ capabilities เพื่อให้ talk.catalog เปิดเผยโหมด
การขนส่ง รูปแบบเสียง และแฟล็กคุณลักษณะที่ใช้ได้แก่ไคลเอนต์ Talk
บนเบราว์เซอร์และแบบเนทีฟ ใช้งาน handleBargeIn เมื่อการขนส่งตรวจพบได้ว่า
มนุษย์กำลังขัดจังหวะการเล่นเสียงของผู้ช่วย และผู้ให้บริการรองรับ
การตัดทอนหรือล้างผลตอบกลับเสียงที่กำลังใช้งานอยู่
submitToolResult อาจคืนค่า void สำหรับการส่งแบบซิงโครนัส หรือ
Promise<void> สำหรับขอบเขตการเสร็จสิ้นแบบอะซิงโครนัสที่บริดจ์ของผู้ให้บริการ
สามารถเปิดเผยได้ เซสชันรีเลย์ของ Gateway จะรอพรอมิสนี้ก่อน
ยืนยันผลลัพธ์สุดท้ายหรือล้างการทำงานที่เชื่อมโยงไว้ และให้ปฏิเสธพรอมิสเมื่อ
การส่งล้มเหลว
ตั้งค่า supportsToolResultSuppression: false เมื่อผู้ให้บริการไม่สามารถ
ปฏิบัติตาม options.suppressResponse จากนั้น OpenClaw จะหลีกเลี่ยงการระงับ
สำหรับผลลัพธ์การบังคับให้ปรึกษาและการยกเลิกภายใน และปฏิเสธคำขอผลลัพธ์
แบบระงับโดยตรงแทนที่จะเริ่มผลตอบกลับโดยไม่มีการแจ้ง
ผู้ใช้ createRealtimeVoiceBridgeSession สามารถคืนพรอมิสจาก
onToolCall ได้เช่นกัน การโยนข้อผิดพลาดแบบซิงโครนัสและการปฏิเสธพรอมิสจะถูกส่งต่อ
ไปยังคอลแบ็ก onError ของเซสชัน
ตั้งค่า handlesInputAudioBargeIn เฉพาะเมื่อ VAD ของผู้ให้บริการยืนยัน
การขัดจังหวะด้วยการเรียก onClearAudio("barge-in") ผู้ให้บริการที่ไม่ระบุ
แฟล็กนี้จะใช้การตรวจจับสำรองจากเสียงขาเข้าภายในเครื่องของ OpenClaw
การทำความเข้าใจสื่อ
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..." }),});เวกเตอร์ฝัง
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 นี่คือ
สัญญาเวกเตอร์ฝังทั่วไปสำหรับการสร้างเวกเตอร์ที่นำกลับมาใช้ใหม่ได้ รวมถึง
การค้นหาหน่วยความจำ registerMemoryEmbeddingProvider(...) เป็น
ความเข้ากันได้ที่เลิกแนะนำให้ใช้แล้วสำหรับอะแดปเตอร์เฉพาะหน่วยความจำที่มีอยู่
การสร้างภาพและวิดีโอ
ความสามารถด้านภาพและวิดีโอใช้โครงสร้างที่ รับรู้โหมด ผู้ให้บริการ
ภาพประกาศบล็อกความสามารถ generate และ edit ที่จำเป็น
ส่วนผู้ให้บริการวิดีโอประกาศ generate, imageToVideo และ
videoToVideo ฟิลด์รวมแบบแบน เช่น maxInputImages /
maxInputVideos / maxDurationSeconds ไม่เพียงพอสำหรับการประกาศ
การรองรับโหมดการแปลงหรือโหมดที่ปิดใช้งานอย่างชัดเจน การสร้างเพลง
ใช้รูปแบบ generate / edit เดียวกัน
api.registerImageGenerationProvider({ id: "acme-ai", label: "Acme Images", capabilities: { generate: { maxCount: 4, supportsSize: true }, edit: { enabled: false }, }, generateImage: async (req) => ({ images: [] }),}); api.registerVideoGenerationProvider({ id: "acme-ai", label: "Acme Video", defaultTimeoutMs: 600_000, models: ["acme-video", "acme-image-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 }, }, catalogByModel: { "acme-image-video": { modes: ["imageToVideo"], capabilities: { imageToVideo: { enabled: true, maxVideos: 1, maxInputImages: 1, resolutions: ["480P", "720P", "1080P"], supportsResolution: true, }, videoToVideo: { enabled: false }, }, }, }, generateVideo: async (req) => ({ videos: [] }),});ผู้ให้บริการทั้งสองประเภทต้องมี capabilities ส่วน edit และ
บล็อกการแปลงวิดีโอ (imageToVideo, videoToVideo) ต้องมี
แฟล็ก enabled ที่ระบุชัดเจนเสมอ
ใช้ catalogByModel เมื่อโหมดหรือความสามารถแบบคงที่ของโมเดลที่ระบุ
แตกต่างจากค่าเริ่มต้นของผู้ให้บริการ เมทาดาทานี้ช่วยให้
video_generate action=list และแค็ตตาล็อกโมเดลมีความถูกต้องโดยไม่ต้อง
เรียกใช้โค้ดของผู้ให้บริการ การค้นหาและบังคับใช้ความสามารถ ณ เวลาที่รับคำขอ
ยังคงเป็นหน้าที่ของ resolveModelCapabilities และ generateVideo และควรใช้
ค่าคงที่ของความสามารถเดียวกันซ้ำสำหรับทั้งสองเส้นทางเมื่อทำได้
การดึงข้อมูลและค้นหาเว็บ
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", hint: "Search the web through Acme's search backend.", envVars: ["ACME_SEARCH_API_KEY"], placeholder: "acme-...", signupUrl: "https://acme.example.com/search", credentialPath: "plugins.entries.acme.config.webSearch.apiKey", getCredentialValue: (searchConfig) => searchConfig?.acme?.apiKey, setCredentialValue: (searchConfigTarget, value) => { const acme = (searchConfigTarget.acme ??= {}); acme.apiKey = value; }, createTool: () => ({ description: "Search the web through Acme Search.", parameters: {}, execute: async (args) => ({ content: [] }), }),});ผู้ให้บริการทั้งสองประเภทใช้โครงสร้างการเชื่อมต่อข้อมูลประจำตัวแบบเดียวกัน:
hint, envVars, placeholder, signupUrl, credentialPath,
getCredentialValue, setCredentialValue และ createTool
ล้วนจำเป็นทั้งหมด
ทดสอบ
ขั้นตอนที่ 6: ทดสอบ
import { describe, it, expect } from "vitest";// ส่งออกออบเจ็กต์การกำหนดค่าผู้ให้บริการจาก index.ts หรือไฟล์เฉพาะ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
Plugin ผู้ให้บริการเผยแพร่ด้วยวิธีเดียวกับ Plugin โค้ดภายนอกอื่น ๆ:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginclawhub skill publish <path> เป็นคำสั่งอีกคำสั่งหนึ่งสำหรับเผยแพร่โฟลเดอร์ Skills
ไม่ใช่แพ็กเกจ Plugin ดังนั้นอย่าใช้คำสั่งนี้ที่นี่
โครงสร้างไฟล์
<bundled-plugin-root>/acme-ai/├── package.json # เมทาดาทา openclaw.providers├── openclaw.plugin.json # แมนิเฟสต์พร้อมเมทาดาทาการตรวจสอบสิทธิ์ของผู้ให้บริการ├── index.ts # definePluginEntry + registerProvider└── src/ ├── provider.test.ts # การทดสอบ └── usage.ts # ปลายทางการใช้งาน (ไม่บังคับ)ข้อมูลอ้างอิงลำดับแค็ตตาล็อก
catalog.order ควบคุมจังหวะที่แค็ตตาล็อกของคุณจะผสานเมื่อเทียบกับผู้ให้บริการ
ในตัว:
| ลำดับ | เมื่อใด | กรณีการใช้งาน |
|---|---|---|
simple |
รอบแรก | ผู้ให้บริการที่ใช้คีย์ API แบบทั่วไป |
profile |
หลัง simple | ผู้ให้บริการที่จำกัดการเข้าถึงตามโปรไฟล์การยืนยันตัวตน |
paired |
หลัง profile | สร้างรายการที่เกี่ยวข้องกันหลายรายการ |
late |
รอบสุดท้าย | แทนที่ผู้ให้บริการที่มีอยู่ (ชนะเมื่อเกิดการชนกัน) |
ขั้นตอนถัดไป
- Plugin ช่องทาง - หาก Plugin ของคุณมีช่องทางด้วย
- รันไทม์ SDK - ตัวช่วย
api.runtime(TTS, การค้นหา, เอเจนต์ย่อย) - ภาพรวม SDK - ข้อมูลอ้างอิงฉบับเต็มสำหรับการนำเข้าจากพาธย่อย
- ส่วนภายในของ Plugin - รายละเอียดฮุกและตัวอย่างที่รวมมาให้