docs: document small provider plugins

This commit is contained in:
Peter Steinberger
2026-06-04 07:20:32 -04:00
parent caf930e65e
commit 2ad6314d72
20 changed files with 132 additions and 0 deletions

View File

@@ -1,3 +1,7 @@
/**
* Public Arcee API barrel. It exposes model catalogs, provider config builders,
* and setup helpers without importing the plugin entry.
*/
export { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./models.js";
export { buildArceeProvider, buildArceeOpenRouterProvider } from "./provider-catalog.js";
export {

View File

@@ -1,3 +1,7 @@
/**
* Arcee AI provider plugin entry. It supports direct Arcee auth and OpenRouter
* routing while normalizing OpenRouter model ids and base URLs.
*/
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import {
@@ -101,6 +105,7 @@ function normalizeArceeResolvedModel<T extends { baseUrl?: string; id: string }>
};
}
/** Provider entry for Arcee direct and OpenRouter-backed models. */
export default definePluginEntry({
id: PROVIDER_ID,
name: "Arcee AI Provider",

View File

@@ -1,7 +1,13 @@
/**
* Arcee model catalog metadata. These definitions back both direct Arcee and
* OpenRouter-routed provider catalogs.
*/
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
/** Default direct Arcee API base URL. */
export const ARCEE_BASE_URL = "https://api.arcee.ai/api/v1";
/** Static Arcee model catalog used for provider registration. */
export const ARCEE_MODEL_CATALOG: ModelDefinitionConfig[] = [
{
id: "trinity-mini",
@@ -51,6 +57,7 @@ export const ARCEE_MODEL_CATALOG: ModelDefinitionConfig[] = [
},
];
/** Build one OpenAI-compatible Arcee model definition. */
export function buildArceeModelDefinition(
model: (typeof ARCEE_MODEL_CATALOG)[number],
): ModelDefinitionConfig {

View File

@@ -1,3 +1,7 @@
/**
* Arcee setup preset appliers. They seed model catalog defaults for direct
* Arcee API usage and the OpenRouter-backed path.
*/
import {
createModelCatalogPresetAppliers,
type OpenClawConfig,
@@ -9,7 +13,9 @@ import {
OPENROUTER_BASE_URL,
} from "./provider-catalog.js";
/** Default Arcee model ref for direct API setup. */
export const ARCEE_DEFAULT_MODEL_REF = "arcee/trinity-large-thinking";
/** Default Arcee model ref for OpenRouter setup. */
export const ARCEE_OPENROUTER_DEFAULT_MODEL_REF = "arcee/trinity-large-thinking";
const arceePresetAppliers = createModelCatalogPresetAppliers({
@@ -34,10 +40,12 @@ const arceeOpenRouterPresetAppliers = createModelCatalogPresetAppliers({
}),
});
/** Apply direct Arcee provider defaults to config. */
export function applyArceeConfig(cfg: OpenClawConfig): OpenClawConfig {
return arceePresetAppliers.applyConfig(cfg);
}
/** Apply OpenRouter-backed Arcee provider defaults to config. */
export function applyArceeOpenRouterConfig(cfg: OpenClawConfig): OpenClawConfig {
return arceeOpenRouterPresetAppliers.applyConfig(cfg);
}

View File

@@ -1,6 +1,11 @@
/**
* Arcee provider catalog builders. Direct Arcee uses native ids; OpenRouter
* catalogs normalize ids to the `arcee/*` namespace.
*/
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./models.js";
/** Canonical OpenRouter API base URL for Arcee-routed models. */
export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
const OPENROUTER_LEGACY_BASE_URL = "https://openrouter.ai/v1";
@@ -8,6 +13,7 @@ function normalizeBaseUrl(baseUrl: string | undefined): string {
return (baseUrl ?? "").trim().replace(/\/+$/, "");
}
/** Normalize OpenRouter base URLs accepted for Arcee model routing. */
export function normalizeArceeOpenRouterBaseUrl(baseUrl: string | undefined): string | undefined {
const normalized = normalizeBaseUrl(baseUrl);
if (!normalized) {
@@ -19,6 +25,7 @@ export function normalizeArceeOpenRouterBaseUrl(baseUrl: string | undefined): st
return undefined;
}
/** Convert a bare Arcee model id to the OpenRouter `arcee/*` id. */
export function toArceeOpenRouterModelId(modelId: string): string {
const normalized = modelId.trim();
if (!normalized || normalized.startsWith("arcee/")) {
@@ -27,16 +34,19 @@ export function toArceeOpenRouterModelId(modelId: string): string {
return `arcee/${normalized}`;
}
/** Build direct Arcee catalog models. */
export function buildArceeCatalogModels(): NonNullable<ModelProviderConfig["models"]> {
return ARCEE_MODEL_CATALOG.map(buildArceeModelDefinition);
}
/** Build OpenRouter-routed Arcee catalog models. */
export function buildArceeOpenRouterCatalogModels(): NonNullable<ModelProviderConfig["models"]> {
return buildArceeCatalogModels().map((model) =>
Object.assign({}, model, { id: toArceeOpenRouterModelId(model.id) }),
);
}
/** Build the direct Arcee provider config. */
export function buildArceeProvider(): ModelProviderConfig {
return {
baseUrl: ARCEE_BASE_URL,
@@ -45,6 +55,7 @@ export function buildArceeProvider(): ModelProviderConfig {
};
}
/** Build the OpenRouter-backed Arcee provider config. */
export function buildArceeOpenRouterProvider(): ModelProviderConfig {
return {
baseUrl: OPENROUTER_BASE_URL,

View File

@@ -1,6 +1,11 @@
/**
* Azure Speech plugin entry. It registers the Azure text-to-speech provider for
* message voice output and voice-note generation.
*/
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { buildAzureSpeechProvider } from "./speech-provider.js";
/** Plugin entry for Azure Speech TTS. */
export default definePluginEntry({
id: "azure-speech",
name: "Azure Speech",

View File

@@ -1,3 +1,7 @@
/**
* Azure Speech provider descriptor. It reads config/env defaults, parses speech
* directives, lists voices, and calls the Azure TTS runtime helper.
*/
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
import type {
SpeechDirectiveTokenParseContext,
@@ -189,6 +193,7 @@ function resolveGeneratedAudioMaxBytes(req: {
return DEFAULT_GENERATED_AUDIO_MAX_BYTES;
}
/** Build the Azure Speech provider descriptor for the speech-core runtime. */
export function buildAzureSpeechProvider(): SpeechProviderPlugin {
return {
id: "azure-speech",

View File

@@ -1,3 +1,7 @@
/**
* Azure Speech REST helpers. They normalize endpoints, build SSML, list voices,
* and synthesize speech with response-size and SSRF guards.
*/
import { assertOkOrThrowProviderError } from "openclaw/plugin-sdk/provider-http";
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech-core";
@@ -7,10 +11,15 @@ import {
ssrfPolicyFromHttpBaseUrlAllowedHostname,
} from "openclaw/plugin-sdk/ssrf-runtime";
/** Default Azure Speech neural voice. */
export const DEFAULT_AZURE_SPEECH_VOICE = "en-US-JennyNeural";
/** Default Azure Speech language. */
export const DEFAULT_AZURE_SPEECH_LANG = "en-US";
/** Default full-audio output format. */
export const DEFAULT_AZURE_SPEECH_AUDIO_FORMAT = "audio-24khz-48kbitrate-mono-mp3";
/** Default voice-note output format. */
export const DEFAULT_AZURE_SPEECH_VOICE_NOTE_FORMAT = "ogg-24khz-16bit-mono-opus";
/** Default telephony output format. */
export const DEFAULT_AZURE_SPEECH_TELEPHONY_FORMAT = "raw-8khz-8bit-mono-mulaw";
const DEFAULT_AZURE_SPEECH_MAX_BYTES = 16 * 1024 * 1024;
@@ -28,6 +37,7 @@ type AzureSpeechVoiceEntry = {
};
};
/** Resolve and normalize the Azure Speech base URL from endpoint or region. */
export function normalizeAzureSpeechBaseUrl(params: {
baseUrl?: string;
endpoint?: string;
@@ -62,6 +72,7 @@ function escapeXmlAttr(value: string): string {
return escapeXmlText(value).replace(/"/g, "&quot;").replace(/'/g, "&apos;");
}
/** Build escaped SSML for one Azure Speech synthesis request. */
export function buildAzureSpeechSsml(params: {
text: string;
voice: string;
@@ -76,6 +87,7 @@ export function buildAzureSpeechSsml(params: {
);
}
/** Infer the generated audio file extension from Azure output format. */
export function inferAzureSpeechFileExtension(outputFormat: string): string {
const normalized = outputFormat.toLowerCase();
if (normalized.includes("mp3")) {
@@ -99,6 +111,7 @@ export function inferAzureSpeechFileExtension(outputFormat: string): string {
return ".audio";
}
/** Return whether an Azure output format is voice-note compatible. */
export function isAzureSpeechVoiceCompatible(outputFormat: string): boolean {
const normalized = outputFormat.toLowerCase();
return normalized.startsWith("ogg-") && normalized.includes("opus");
@@ -123,6 +136,7 @@ function isDeprecatedVoice(entry: AzureSpeechVoiceEntry): boolean {
return status === "deprecated" || status === "retired" || status === "disabled";
}
/** List non-deprecated voices from the Azure Speech voices API. */
export async function listAzureSpeechVoices(params: {
apiKey: string;
baseUrl?: string;
@@ -167,6 +181,7 @@ export async function listAzureSpeechVoices(params: {
}
}
/** Synthesize text to audio bytes using Azure Speech TTS. */
export async function azureSpeechTTS(params: {
text: string;
apiKey: string;

View File

@@ -1,3 +1,7 @@
/**
* Bonjour gateway-discovery plugin entry. It advertises the local gateway over
* mDNS and lazily loads the ciao-based advertiser.
*/
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
function formatBonjourInstanceName(displayName: string) {
@@ -11,6 +15,7 @@ function formatBonjourInstanceName(displayName: string) {
return `${trimmed} (OpenClaw)`;
}
/** Plugin entry for Bonjour/mDNS gateway discovery. */
export default definePluginEntry({
id: "bonjour",
name: "Bonjour Gateway Discovery",

View File

@@ -1,3 +1,7 @@
/**
* Bonjour advertiser runtime. It publishes gateway/canvas/SSH service records,
* watches ciao state, and repairs stuck or conflicting advertisements.
*/
import type { ChildProcess } from "node:child_process";
import fs from "node:fs";
import { createRequire } from "node:module";
@@ -12,10 +16,12 @@ const childProcessModule = nodeRequire("node:child_process") as {
exec: typeof import("node:child_process").exec;
};
/** Running Bonjour advertiser handle. */
export type GatewayBonjourAdvertiser = {
stop: () => Promise<void>;
};
/** Input data used to publish OpenClaw gateway Bonjour records. */
export type GatewayBonjourAdvertiseOpts = {
instanceName?: string;
gatewayPort: number;
@@ -359,6 +365,7 @@ function installCiaoUnhandledRejectionListener(handler: UnhandledRejectionHandle
};
}
/** Start Bonjour advertisements for the local gateway services. */
export async function startGatewayBonjourAdvertiser(
opts: GatewayBonjourAdvertiseOpts,
deps: BonjourAdvertiserDeps = {},

View File

@@ -1,3 +1,7 @@
/**
* Ciao process-error classifier. It recognizes known noisy ciao failures so
* the Bonjour plugin can suppress or repair expected mDNS lifecycle issues.
*/
import { collectErrorGraphCandidates } from "openclaw/plugin-sdk/error-runtime";
import { formatBonjourError } from "./errors.js";
@@ -13,6 +17,7 @@ const CIAO_SELF_PROBE_MESSAGE_RE =
// Node surfaces this as a SystemError mentioning the libuv syscall by name.
const CIAO_INTERFACE_ENUMERATION_FAILURE_RE = /\bUV_INTERFACE_ADDRESSES\b/u;
/** Known ciao process-level errors that OpenClaw handles specially. */
export type CiaoProcessErrorClassification =
| { kind: "cancellation"; formatted: string }
| { kind: "interface-assertion"; formatted: string }
@@ -20,6 +25,7 @@ export type CiaoProcessErrorClassification =
| { kind: "self-probe"; formatted: string }
| { kind: "interface-enumeration-failure"; formatted: string };
/** Classify a ciao error/rejection chain into a known category. */
export function classifyCiaoProcessError(reason: unknown): CiaoProcessErrorClassification | null {
for (const candidate of collectErrorGraphCandidates(reason, (current) => [
current.cause,
@@ -50,8 +56,10 @@ export function classifyCiaoProcessError(reason: unknown): CiaoProcessErrorClass
return null;
}
/** Backward-compatible alias for unhandled-rejection classification. */
export const classifyCiaoUnhandledRejection = classifyCiaoProcessError;
/** Return whether a ciao unhandled rejection is known and ignorable. */
export function ignoreCiaoUnhandledRejection(reason: unknown): boolean {
return classifyCiaoProcessError(reason) !== null;
}

View File

@@ -1,3 +1,8 @@
/**
* Bonjour error formatting helper. It normalizes Error and non-Error values
* into concise messages for gateway discovery logs.
*/
/** Format an unknown Bonjour/ciao error value for logs. */
export function formatBonjourError(err: unknown): string {
if (err instanceof Error) {
const trimmedMessage = err.message.trim();

View File

@@ -1,6 +1,11 @@
/**
* Brave Search plugin entry. It registers the Brave web-search provider and
* keeps runtime HTTP execution lazy.
*/
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createBraveWebSearchProvider } from "./src/brave-web-search-provider.js";
/** Plugin entry for Brave Search. */
export default definePluginEntry({
id: "brave",
name: "Brave Plugin",

View File

@@ -1,3 +1,7 @@
/**
* Brave Search HTTP runtime. It resolves credentials, enforces endpoint safety,
* applies caching, and maps Brave web/LLM-context API responses.
*/
import {
assertOkOrThrowProviderError,
readProviderJsonResponse,
@@ -334,6 +338,7 @@ async function runBraveWebSearch(params: {
});
}
/** Execute one Brave Search request using web or LLM-context mode. */
export async function executeBraveSearch(
args: Record<string, unknown>,
searchConfig?: SearchConfigRecord,

View File

@@ -1,3 +1,7 @@
/**
* Brave Search request normalization and result mapping. It validates Brave
* country/language params and converts LLM-context responses into web results.
*/
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
@@ -9,6 +13,7 @@ type BraveConfig = {
};
type BraveLlmContextResult = { url: string; title: string; snippets: string[] };
/** Brave LLM Context API response subset used by OpenClaw. */
export type BraveLlmContextResponse = {
grounding: { generic?: BraveLlmContextResult[] };
sources?: { url?: string; hostname?: string; date?: string }[];
@@ -136,6 +141,7 @@ function normalizeBraveSearchLang(value: string | undefined): string | undefined
return canonical;
}
/** Normalize Brave country filter values. */
export function normalizeBraveCountry(value: string | undefined): string | undefined {
if (!value) {
return undefined;
@@ -164,15 +170,18 @@ function normalizeBraveUiLang(value: string | undefined): string | undefined {
return `${normalizeLowercaseStringOrEmpty(language)}-${region.toUpperCase()}`;
}
/** Resolve Brave-specific web-search config from scoped search config. */
export function resolveBraveConfig(searchConfig?: Record<string, unknown>): BraveConfig {
const brave = searchConfig?.brave;
return brave && typeof brave === "object" && !Array.isArray(brave) ? (brave as BraveConfig) : {};
}
/** Resolve whether Brave should use web search or LLM Context API mode. */
export function resolveBraveMode(brave?: BraveConfig): "web" | "llm-context" {
return brave?.mode === "llm-context" ? "llm-context" : "web";
}
/** Normalize Brave search and UI language params, detecting swapped fields. */
export function normalizeBraveLanguageParams(params: { search_lang?: string; ui_lang?: string }): {
search_lang?: string;
ui_lang?: string;
@@ -212,6 +221,7 @@ function resolveSiteName(url: string | undefined): string | undefined {
}
}
/** Map Brave LLM Context API grounding results into web-search result rows. */
export function mapBraveLlmContextResults(
data: BraveLlmContextResponse,
): { url: string; title: string; snippets: string[]; siteName?: string }[] {

View File

@@ -1,3 +1,7 @@
/**
* Brave web-search provider factory. It builds the agent tool definition and
* lazy-loads HTTP execution only when a search is run.
*/
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
import type {
SearchConfigRecord,
@@ -89,6 +93,7 @@ function createBraveToolDefinition(
};
}
/** Create the runtime Brave Search provider descriptor. */
export function createBraveWebSearchProvider(): WebSearchProviderPlugin {
return {
...buildBraveWebSearchProviderBase(),

View File

@@ -1,3 +1,7 @@
/**
* Brave Search test API barrel. Tests import normalized helpers through this
* path instead of deep runtime modules.
*/
import {
mapBraveLlmContextResults,
normalizeBraveCountry,
@@ -5,6 +9,7 @@ import {
resolveBraveMode,
} from "./src/brave-web-search-provider.shared.js";
/** Test-only Brave normalization helpers. */
export const testing = {
normalizeBraveCountry,
normalizeBraveLanguageParams,

View File

@@ -1,6 +1,11 @@
/**
* Brave Search contract provider. It exposes provider metadata without creating
* the runtime search tool.
*/
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-config-contract";
import { buildBraveWebSearchProviderBase } from "./web-search-shared.js";
/** Create the Brave provider descriptor for contract checks. */
export function createBraveWebSearchProvider(): WebSearchProviderPlugin {
return {
...buildBraveWebSearchProviderBase(),

View File

@@ -1 +1,5 @@
/**
* Public Brave web-search provider barrel. Runtime consumers import this
* lightweight path for the provider factory.
*/
export { createBraveWebSearchProvider } from "./src/brave-web-search-provider.js";

View File

@@ -1,11 +1,17 @@
/**
* Shared Brave Search provider metadata and credential lookup. Contract tests
* and runtime provider creation both use this lightweight descriptor.
*/
import {
createWebSearchProviderContractFields,
type WebSearchProviderPlugin,
} from "openclaw/plugin-sdk/provider-web-search-config-contract";
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
/** Canonical config path for the Brave Search API key. */
export const BRAVE_CREDENTIAL_PATH = "plugins.entries.brave.config.webSearch.apiKey";
/** Resolve legacy top-level Brave credentials from old web-search config. */
export function resolveLegacyTopLevelBraveCredential(
config: unknown,
): { path: string; value: unknown } | undefined {
@@ -32,6 +38,7 @@ function resolveBraveWebSearchPluginConfig(config: unknown): Record<string, unkn
return isRecord(pluginConfig?.webSearch) ? pluginConfig.webSearch : undefined;
}
/** Resolve Brave credentials from current plugin config or legacy fallback. */
export function resolveConfiguredBraveCredential(config: unknown): unknown {
return (
resolveBraveWebSearchPluginConfig(config)?.apiKey ??
@@ -39,6 +46,7 @@ export function resolveConfiguredBraveCredential(config: unknown): unknown {
);
}
/** Build the common Brave provider metadata without the runtime tool executor. */
export function buildBraveWebSearchProviderBase(): Omit<WebSearchProviderPlugin, "createTool"> {
return {
id: "brave",