refactor: unify plugin sdk primitives

This commit is contained in:
Peter Steinberger
2026-03-18 23:58:49 +00:00
parent bea90b72e6
commit 07d9f725b6
58 changed files with 1007 additions and 588 deletions

View File

@@ -925,6 +925,12 @@ authoring plugins:
- `openclaw/plugin-sdk/plugin-entry` for plugin registration primitives.
- `openclaw/plugin-sdk/core` for the generic shared plugin-facing contract.
- Stable channel primitives such as `openclaw/plugin-sdk/channel-setup`,
`openclaw/plugin-sdk/channel-pairing`,
`openclaw/plugin-sdk/channel-reply-pipeline`,
`openclaw/plugin-sdk/secret-input`, and
`openclaw/plugin-sdk/webhook-ingress` for shared setup/auth/reply/webhook
wiring.
- Domain subpaths such as `openclaw/plugin-sdk/channel-config-helpers`,
`openclaw/plugin-sdk/channel-config-schema`,
`openclaw/plugin-sdk/channel-policy`,
@@ -961,6 +967,9 @@ authoring plugins:
Compatibility note:
- Avoid the root `openclaw/plugin-sdk` barrel for new code.
- Prefer the narrow stable primitives first. The newer setup/pairing/reply/
secret-input/webhook subpaths are the intended contract for new bundled and
external plugin work.
- Bundled extension-specific helper barrels are not stable by default. If a
helper is only needed by a bundled extension, keep it behind the extension's
local `api.js` or `runtime-api.js` seam instead of promoting it into

View File

@@ -95,8 +95,10 @@ subpaths rather than the monolithic root:
```typescript
// Correct: focused subpaths
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-runtime";
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import { createOptionalChannelSetupSurface } from "openclaw/plugin-sdk/channel-setup";
import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
// Wrong: monolithic root (lint will reject this)
@@ -105,17 +107,24 @@ import { ... } from "openclaw/plugin-sdk";
Common subpaths:
| Subpath | Purpose |
| ---------------------------------- | ------------------------------------ |
| `plugin-sdk/core` | Plugin entry definitions, base types |
| `plugin-sdk/channel-runtime` | Channel runtime helpers |
| `plugin-sdk/channel-config-schema` | Config schema builders |
| `plugin-sdk/channel-policy` | Group/DM policy helpers |
| `plugin-sdk/setup` | Setup wizard adapters |
| `plugin-sdk/runtime-store` | Persistent plugin storage |
| `plugin-sdk/allow-from` | Allowlist resolution |
| `plugin-sdk/reply-payload` | Message reply types |
| `plugin-sdk/testing` | Test utilities |
| Subpath | Purpose |
| ----------------------------------- | ------------------------------------ |
| `plugin-sdk/core` | Plugin entry definitions, base types |
| `plugin-sdk/channel-setup` | Optional setup adapters/wizards |
| `plugin-sdk/channel-pairing` | DM pairing primitives |
| `plugin-sdk/channel-reply-pipeline` | Prefix + typing reply wiring |
| `plugin-sdk/channel-config-schema` | Config schema builders |
| `plugin-sdk/channel-policy` | Group/DM policy helpers |
| `plugin-sdk/secret-input` | Secret input parsing/helpers |
| `plugin-sdk/webhook-ingress` | Webhook request/target helpers |
| `plugin-sdk/runtime-store` | Persistent plugin storage |
| `plugin-sdk/allow-from` | Allowlist resolution |
| `plugin-sdk/reply-payload` | Message reply types |
| `plugin-sdk/provider-onboard` | Provider onboarding config patches |
| `plugin-sdk/testing` | Test utilities |
Use the narrowest primitive that matches the job. Reach for `channel-runtime`
or other larger helper barrels only when a dedicated subpath does not exist yet.
## Step 4: Use local barrels for internal imports

View File

@@ -1,12 +1,6 @@
import {
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "openclaw/plugin-sdk/secret-input-runtime";
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input-schema";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
};
} from "openclaw/plugin-sdk/secret-input";

View File

@@ -6,7 +6,7 @@ import {
} from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
@@ -17,24 +17,20 @@ export { CHUTES_DEFAULT_MODEL_REF };
* Registers all catalog models and sets provider aliases (chutes-fast, etc.).
*/
export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
for (const m of CHUTES_MODEL_CATALOG) {
models[`chutes/${m.id}`] = {
...models[`chutes/${m.id}`],
};
}
models["chutes-fast"] = { alias: "chutes/zai-org/GLM-4.7-FP8" };
models["chutes-vision"] = { alias: "chutes/chutesai/Mistral-Small-3.2-24B-Instruct-2506" };
models["chutes-pro"] = { alias: "chutes/deepseek-ai/DeepSeek-V3.2-TEE" };
const chutesModels = CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition);
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
return applyProviderConfigWithModelCatalogPreset(cfg, {
providerId: "chutes",
api: "openai-completions",
baseUrl: CHUTES_BASE_URL,
catalogModels: chutesModels,
catalogModels: CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition),
aliases: [
...CHUTES_MODEL_CATALOG.map((model) => `chutes/${model.id}`),
{ modelRef: "chutes-fast", alias: "chutes/zai-org/GLM-4.7-FP8" },
{
modelRef: "chutes-vision",
alias: "chutes/chutesai/Mistral-Small-3.2-24B-Instruct-2506",
},
{ modelRef: "chutes-pro", alias: "chutes/deepseek-ai/DeepSeek-V3.2-TEE" },
],
});
}

View File

@@ -1,16 +1,15 @@
import { ChannelType, type RequestClient } from "@buape/carbon";
import { resolveAckReaction, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
import { EmbeddedBlockChunker } from "openclaw/plugin-sdk/agent-runtime";
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
import { shouldAckReaction as shouldAckReactionGate } from "openclaw/plugin-sdk/channel-runtime";
import { logTypingFailure, logAckFailure } from "openclaw/plugin-sdk/channel-runtime";
import { createReplyPrefixOptions } from "openclaw/plugin-sdk/channel-runtime";
import { recordInboundSession } from "openclaw/plugin-sdk/channel-runtime";
import {
createStatusReactionController,
DEFAULT_TIMING,
type StatusReactionAdapter,
} from "openclaw/plugin-sdk/channel-runtime";
import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime";
import { resolveDiscordPreviewStreamMode } from "openclaw/plugin-sdk/config-runtime";
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
@@ -420,11 +419,24 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
? deliverTarget.slice("channel:".length)
: messageChannelId;
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg,
agentId: route.agentId,
channel: "discord",
accountId: route.accountId,
typing: {
start: () => sendTyping({ client, channelId: typingChannelId }),
onStartError: (err) => {
logTypingFailure({
log: logVerbose,
channel: "discord",
target: typingChannelId,
error: err,
});
},
// Long tool-heavy runs are expected on Discord; keep heartbeats alive.
maxDurationMs: DISCORD_TYPING_MAX_DURATION_MS,
},
});
const tableMode = resolveMarkdownTableMode({
cfg,
@@ -438,20 +450,6 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
});
const chunkMode = resolveChunkMode(cfg, "discord", accountId);
const typingCallbacks = createTypingCallbacks({
start: () => sendTyping({ client, channelId: typingChannelId }),
onStartError: (err) => {
logTypingFailure({
log: logVerbose,
channel: "discord",
target: typingChannelId,
error: err,
});
},
// Long tool-heavy runs are expected on Discord; keep heartbeats alive.
maxDurationMs: DISCORD_TYPING_MAX_DURATION_MS,
});
// --- Discord draft stream (edit-based preview streaming) ---
const discordStreamMode = resolveDiscordPreviewStreamMode(discordConfig);
const draftMaxChars = Math.min(textLimit, 2000);
@@ -597,9 +595,8 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } =
createReplyDispatcherWithTyping({
...prefixOptions,
...replyPipeline,
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
typingCallbacks,
deliver: async (payload: ReplyPayload, info) => {
if (isProcessAborted(abortSignal)) {
return;
@@ -715,7 +712,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
if (isProcessAborted(abortSignal)) {
return;
}
await typingCallbacks.onReplyStart();
await replyPipeline.typingCallbacks?.onReplyStart();
await statusReactions.setThinking();
},
});

View File

@@ -10,10 +10,9 @@ import {
buildAgentMediaPayload,
buildPendingHistoryContextFromMap,
clearHistoryEntriesIfEnabled,
createScopedPairingAccess,
createChannelPairingController,
DEFAULT_GROUP_HISTORY_LIMIT,
type HistoryEntry,
issuePairingChallenge,
normalizeAgentId,
recordPendingHistoryEntryIfEnabled,
resolveAgentOutboundIdentity,
@@ -445,7 +444,7 @@ export async function handleFeishuMessage(params: {
try {
const core = getFeishuRuntime();
const pairing = createScopedPairingAccess({
const pairing = createChannelPairingController({
core,
channel: "feishu",
accountId: account.accountId,
@@ -471,12 +470,10 @@ export async function handleFeishuMessage(params: {
if (isDirect && dmPolicy !== "open" && !dmAllowed) {
if (dmPolicy === "pairing") {
await issuePairingChallenge({
channel: "feishu",
await pairing.issueChallenge({
senderId: ctx.senderOpenId,
senderIdLine: `Your Feishu user id: ${ctx.senderOpenId}`,
meta: { name: ctx.senderName },
upsertPairingRequest: pairing.upsertPairingRequest,
onCreated: () => {
log(`feishu[${account.accountId}]: pairing request sender=${ctx.senderOpenId}`);
},

View File

@@ -1,13 +1,6 @@
import {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../runtime-api.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
};
} from "openclaw/plugin-sdk/secret-input";

View File

@@ -1,8 +1,7 @@
import {
GROUP_POLICY_BLOCKED_LABEL,
createScopedPairingAccess,
createChannelPairingController,
evaluateGroupRouteAccessForPolicy,
issuePairingChallenge,
isDangerousNameMatchingEnabled,
resolveAllowlistProviderRuntimeGroupPolicy,
resolveDefaultGroupPolicy,
@@ -166,7 +165,7 @@ export async function applyGoogleChatInboundAccessPolicy(params: {
} = params;
const allowNameMatching = isDangerousNameMatchingEnabled(account.config);
const spaceId = space.name ?? "";
const pairing = createScopedPairingAccess({
const pairing = createChannelPairingController({
core,
channel: "googlechat",
accountId: account.accountId,
@@ -311,12 +310,10 @@ export async function applyGoogleChatInboundAccessPolicy(params: {
if (access.decision !== "allow") {
if (access.decision === "pairing") {
await issuePairingChallenge({
channel: "googlechat",
await pairing.issueChallenge({
senderId,
senderIdLine: `Your Google Chat user id: ${senderId}`,
meta: { name: senderName || undefined, email: senderEmail },
upsertPairingRequest: pairing.upsertPairingRequest,
onCreated: () => {
logVerbose(`googlechat pairing request sender=${senderId}`);
},

View File

@@ -5,8 +5,8 @@ import {
} from "openclaw/plugin-sdk/reply-payload";
import type { OpenClawConfig } from "../runtime-api.js";
import {
createChannelReplyPipeline,
createWebhookInFlightLimiter,
createReplyPrefixOptions,
registerWebhookTargetWithPluginRoute,
resolveInboundRouteEnvelopeBuilderWithRuntime,
resolveWebhookPath,
@@ -307,7 +307,7 @@ async function processMessageWithPipeline(params: {
}
}
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg: config,
agentId: route.agentId,
channel: "googlechat",
@@ -318,7 +318,7 @@ async function processMessageWithPipeline(params: {
ctx: ctxPayload,
cfg: config,
dispatcherOptions: {
...prefixOptions,
...replyPipeline,
deliver: async (payload) => {
await deliverGoogleChatReply({
payload,

View File

@@ -4,32 +4,27 @@ import {
HUGGINGFACE_MODEL_CATALOG,
} from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
export const HUGGINGFACE_DEFAULT_MODEL_REF = "huggingface/deepseek-ai/DeepSeek-R1";
export function applyHuggingfaceProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[HUGGINGFACE_DEFAULT_MODEL_REF] = {
...models[HUGGINGFACE_DEFAULT_MODEL_REF],
alias: models[HUGGINGFACE_DEFAULT_MODEL_REF]?.alias ?? "Hugging Face",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
function applyHuggingfacePreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
return applyProviderConfigWithModelCatalogPreset(cfg, {
providerId: "huggingface",
api: "openai-completions",
baseUrl: HUGGINGFACE_BASE_URL,
catalogModels: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition),
aliases: [{ modelRef: HUGGINGFACE_DEFAULT_MODEL_REF, alias: "Hugging Face" }],
primaryModelRef,
});
}
export function applyHuggingfaceConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyHuggingfaceProviderConfig(cfg),
HUGGINGFACE_DEFAULT_MODEL_REF,
);
export function applyHuggingfaceProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyHuggingfacePreset(cfg);
}
export function applyHuggingfaceConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyHuggingfacePreset(cfg, HUGGINGFACE_DEFAULT_MODEL_REF);
}

View File

@@ -9,10 +9,9 @@ import {
} from "./policy.js";
import {
GROUP_POLICY_BLOCKED_LABEL,
createScopedPairingAccess,
createChannelPairingController,
deliverFormattedTextWithAttachments,
dispatchInboundReplyWithBase,
issuePairingChallenge,
logInboundDrop,
isDangerousNameMatchingEnabled,
readStoreAllowFromForDmPolicy,
@@ -90,7 +89,7 @@ export async function handleIrcInbound(params: {
}): Promise<void> {
const { message, account, config, runtime, connectedNick, statusSink } = params;
const core = getIrcRuntime();
const pairing = createScopedPairingAccess({
const pairing = createChannelPairingController({
core,
channel: CHANNEL_ID,
accountId: account.accountId,
@@ -208,12 +207,10 @@ export async function handleIrcInbound(params: {
}).allowed;
if (!dmAllowed) {
if (dmPolicy === "pairing") {
await issuePairingChallenge({
channel: CHANNEL_ID,
await pairing.issueChallenge({
senderId: senderDisplay.toLowerCase(),
senderIdLine: `Your IRC id: ${senderDisplay}`,
meta: { name: message.senderNick || undefined },
upsertPairingRequest: pairing.upsertPairingRequest,
sendPairingReply: async (text) => {
await deliverIrcReply({
payload: { text },

View File

@@ -1,6 +1,5 @@
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModelPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
@@ -12,28 +11,30 @@ import {
export const KIMI_MODEL_REF = `kimi/${KIMI_CODING_DEFAULT_MODEL_ID}`;
export const KIMI_CODING_MODEL_REF = KIMI_MODEL_REF;
export function applyKimiCodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[KIMI_MODEL_REF] = {
...models[KIMI_MODEL_REF],
alias: models[KIMI_MODEL_REF]?.alias ?? "Kimi",
};
function resolveKimiCodingDefaultModel() {
return buildKimiCodingProvider().models[0];
}
const defaultModel = buildKimiCodingProvider().models[0];
function applyKimiCodingPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
const defaultModel = resolveKimiCodingDefaultModel();
if (!defaultModel) {
return cfg;
}
return applyProviderConfigWithDefaultModel(cfg, {
agentModels: models,
return applyProviderConfigWithDefaultModelPreset(cfg, {
providerId: "kimi",
api: "anthropic-messages",
baseUrl: KIMI_CODING_BASE_URL,
defaultModel,
defaultModelId: KIMI_CODING_DEFAULT_MODEL_ID,
aliases: [{ modelRef: KIMI_MODEL_REF, alias: "Kimi" }],
primaryModelRef,
});
}
export function applyKimiCodeConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyKimiCodeProviderConfig(cfg), KIMI_MODEL_REF);
export function applyKimiCodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyKimiCodingPreset(cfg);
}
export function applyKimiCodeConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyKimiCodingPreset(cfg, KIMI_MODEL_REF);
}

View File

@@ -1,13 +1,6 @@
import {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../runtime-api.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
};
} from "openclaw/plugin-sdk/secret-input";

View File

@@ -1,13 +1,6 @@
import {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "./runtime-api.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
};
} from "openclaw/plugin-sdk/secret-input";

View File

@@ -1,6 +1,5 @@
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModelPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
@@ -11,23 +10,22 @@ import {
export const MISTRAL_DEFAULT_MODEL_REF = `mistral/${MISTRAL_DEFAULT_MODEL_ID}`;
export function applyMistralProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[MISTRAL_DEFAULT_MODEL_REF] = {
...models[MISTRAL_DEFAULT_MODEL_REF],
alias: models[MISTRAL_DEFAULT_MODEL_REF]?.alias ?? "Mistral",
};
return applyProviderConfigWithDefaultModel(cfg, {
agentModels: models,
function applyMistralPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
return applyProviderConfigWithDefaultModelPreset(cfg, {
providerId: "mistral",
api: "openai-completions",
baseUrl: MISTRAL_BASE_URL,
defaultModel: buildMistralModelDefinition(),
defaultModelId: MISTRAL_DEFAULT_MODEL_ID,
aliases: [{ modelRef: MISTRAL_DEFAULT_MODEL_REF, alias: "Mistral" }],
primaryModelRef,
});
}
export function applyMistralConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyMistralProviderConfig(cfg), MISTRAL_DEFAULT_MODEL_REF);
export function applyMistralProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyMistralPreset(cfg);
}
export function applyMistralConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyMistralPreset(cfg, MISTRAL_DEFAULT_MODEL_REF);
}

View File

@@ -1,6 +1,5 @@
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
@@ -15,26 +14,19 @@ export { MODELSTUDIO_CN_BASE_URL, MODELSTUDIO_DEFAULT_MODEL_REF, MODELSTUDIO_GLO
function applyModelStudioProviderConfigWithBaseUrl(
cfg: OpenClawConfig,
baseUrl: string,
primaryModelRef?: string,
): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
const provider = buildModelStudioProvider();
for (const model of provider.models ?? []) {
const modelRef = `modelstudio/${model.id}`;
if (!models[modelRef]) {
models[modelRef] = {};
}
}
models[MODELSTUDIO_DEFAULT_MODEL_REF] = {
...models[MODELSTUDIO_DEFAULT_MODEL_REF],
alias: models[MODELSTUDIO_DEFAULT_MODEL_REF]?.alias ?? "Qwen",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
return applyProviderConfigWithModelCatalogPreset(cfg, {
providerId: "modelstudio",
api: provider.api ?? "openai-completions",
baseUrl,
catalogModels: provider.models ?? [],
aliases: [
...(provider.models ?? []).map((model) => `modelstudio/${model.id}`),
{ modelRef: MODELSTUDIO_DEFAULT_MODEL_REF, alias: "Qwen" },
],
primaryModelRef,
});
}
@@ -47,15 +39,17 @@ export function applyModelStudioProviderConfigCn(cfg: OpenClawConfig): OpenClawC
}
export function applyModelStudioConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyModelStudioProviderConfig(cfg),
return applyModelStudioProviderConfigWithBaseUrl(
cfg,
MODELSTUDIO_GLOBAL_BASE_URL,
MODELSTUDIO_DEFAULT_MODEL_REF,
);
}
export function applyModelStudioConfigCn(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyModelStudioProviderConfigCn(cfg),
return applyModelStudioProviderConfigWithBaseUrl(
cfg,
MODELSTUDIO_CN_BASE_URL,
MODELSTUDIO_DEFAULT_MODEL_REF,
);
}

View File

@@ -1,6 +1,5 @@
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModelPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
@@ -23,38 +22,32 @@ export function applyMoonshotProviderConfigCn(cfg: OpenClawConfig): OpenClawConf
function applyMoonshotProviderConfigWithBaseUrl(
cfg: OpenClawConfig,
baseUrl: string,
primaryModelRef?: string,
): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[MOONSHOT_DEFAULT_MODEL_REF] = {
...models[MOONSHOT_DEFAULT_MODEL_REF],
alias: models[MOONSHOT_DEFAULT_MODEL_REF]?.alias ?? "Kimi",
};
const defaultModel = buildMoonshotProvider().models[0];
if (!defaultModel) {
return cfg;
}
return applyProviderConfigWithDefaultModel(cfg, {
agentModels: models,
return applyProviderConfigWithDefaultModelPreset(cfg, {
providerId: "moonshot",
api: "openai-completions",
baseUrl,
defaultModel,
defaultModelId: MOONSHOT_DEFAULT_MODEL_ID,
aliases: [{ modelRef: MOONSHOT_DEFAULT_MODEL_REF, alias: "Kimi" }],
primaryModelRef,
});
}
export function applyMoonshotConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyMoonshotProviderConfig(cfg),
MOONSHOT_DEFAULT_MODEL_REF,
);
return applyMoonshotProviderConfigWithBaseUrl(cfg, MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_REF);
}
export function applyMoonshotConfigCn(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyMoonshotProviderConfigCn(cfg),
return applyMoonshotProviderConfigWithBaseUrl(
cfg,
MOONSHOT_CN_BASE_URL,
MOONSHOT_DEFAULT_MODEL_REF,
);
}

View File

@@ -1,9 +1,8 @@
import {
GROUP_POLICY_BLOCKED_LABEL,
createScopedPairingAccess,
createChannelPairingController,
deliverFormattedTextWithAttachments,
dispatchInboundReplyWithBase,
issuePairingChallenge,
logInboundDrop,
readStoreAllowFromForDmPolicy,
resolveDmGroupAccessWithCommandGate,
@@ -58,7 +57,7 @@ export async function handleNextcloudTalkInbound(params: {
}): Promise<void> {
const { message, account, config, runtime, statusSink } = params;
const core = getNextcloudTalkRuntime();
const pairing = createScopedPairingAccess({
const pairing = createChannelPairingController({
core,
channel: CHANNEL_ID,
accountId: account.accountId,
@@ -172,12 +171,10 @@ export async function handleNextcloudTalkInbound(params: {
} else {
if (access.decision !== "allow") {
if (access.decision === "pairing") {
await issuePairingChallenge({
channel: CHANNEL_ID,
await pairing.issueChallenge({
senderId,
senderIdLine: `Your Nextcloud user id: ${senderId}`,
meta: { name: senderName || undefined },
upsertPairingRequest: pairing.upsertPairingRequest,
sendPairingReply: async (text) => {
await sendMessageNextcloudTalk(roomToken, text, { accountId: account.accountId });
statusSink?.({ lastOutboundAt: Date.now() });

View File

@@ -1,13 +1,6 @@
import {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../runtime-api.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
};
} from "openclaw/plugin-sdk/secret-input";

View File

@@ -1,6 +1,7 @@
import { OPENCODE_GO_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
withAgentModelAliases,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
@@ -13,21 +14,19 @@ const OPENCODE_GO_ALIAS_DEFAULTS: Record<string, string> = {
};
export function applyOpencodeGoProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
for (const [modelRef, alias] of Object.entries(OPENCODE_GO_ALIAS_DEFAULTS)) {
models[modelRef] = {
...models[modelRef],
alias: models[modelRef]?.alias ?? alias,
};
}
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
models,
models: withAgentModelAliases(
cfg.agents?.defaults?.models,
Object.entries(OPENCODE_GO_ALIAS_DEFAULTS).map(([modelRef, alias]) => ({
modelRef,
alias,
})),
),
},
},
};

View File

@@ -1,25 +1,22 @@
import { OPENCODE_ZEN_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
withAgentModelAliases,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
export { OPENCODE_ZEN_DEFAULT_MODEL_REF };
export function applyOpencodeZenProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[OPENCODE_ZEN_DEFAULT_MODEL_REF] = {
...models[OPENCODE_ZEN_DEFAULT_MODEL_REF],
alias: models[OPENCODE_ZEN_DEFAULT_MODEL_REF]?.alias ?? "Opus",
};
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
models,
models: withAgentModelAliases(cfg.agents?.defaults?.models, [
{ modelRef: OPENCODE_ZEN_DEFAULT_MODEL_REF, alias: "Opus" },
]),
},
},
};

View File

@@ -1,6 +1,5 @@
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithDefaultModels,
applyProviderConfigWithDefaultModelsPreset,
type ModelApi,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
@@ -12,12 +11,11 @@ import {
export const QIANFAN_DEFAULT_MODEL_REF = `qianfan/${QIANFAN_DEFAULT_MODEL_ID}`;
export function applyQianfanProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[QIANFAN_DEFAULT_MODEL_REF] = {
...models[QIANFAN_DEFAULT_MODEL_REF],
alias: models[QIANFAN_DEFAULT_MODEL_REF]?.alias ?? "QIANFAN",
};
function resolveQianfanPreset(cfg: OpenClawConfig): {
api: ModelApi;
baseUrl: string;
defaultModels: NonNullable<ReturnType<typeof buildQianfanProvider>["models"]>;
} {
const defaultProvider = buildQianfanProvider();
const existingProvider = cfg.models?.providers?.qianfan as
| {
@@ -27,22 +25,35 @@ export function applyQianfanProviderConfig(cfg: OpenClawConfig): OpenClawConfig
| undefined;
const existingBaseUrl =
typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl.trim() : "";
const resolvedBaseUrl = existingBaseUrl || QIANFAN_BASE_URL;
const resolvedApi =
const api =
typeof existingProvider?.api === "string"
? (existingProvider.api as ModelApi)
: "openai-completions";
return applyProviderConfigWithDefaultModels(cfg, {
agentModels: models,
providerId: "qianfan",
api: resolvedApi,
baseUrl: resolvedBaseUrl,
return {
api,
baseUrl: existingBaseUrl || QIANFAN_BASE_URL,
defaultModels: defaultProvider.models ?? [],
};
}
function applyQianfanPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
const preset = resolveQianfanPreset(cfg);
return applyProviderConfigWithDefaultModelsPreset(cfg, {
providerId: "qianfan",
api: preset.api,
baseUrl: preset.baseUrl,
defaultModels: preset.defaultModels,
defaultModelId: QIANFAN_DEFAULT_MODEL_ID,
aliases: [{ modelRef: QIANFAN_DEFAULT_MODEL_REF, alias: "QIANFAN" }],
primaryModelRef,
});
}
export function applyQianfanConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyQianfanProviderConfig(cfg), QIANFAN_DEFAULT_MODEL_REF);
export function applyQianfanProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyQianfanPreset(cfg);
}
export function applyQianfanConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyQianfanPreset(cfg, QIANFAN_DEFAULT_MODEL_REF);
}

View File

@@ -1,8 +1,7 @@
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
import { removeAckReactionAfterReply } from "openclaw/plugin-sdk/channel-runtime";
import { logAckFailure, logTypingFailure } from "openclaw/plugin-sdk/channel-runtime";
import { createReplyPrefixOptions } from "openclaw/plugin-sdk/channel-runtime";
import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-runtime";
import { resolveStorePath, updateLastRoute } from "openclaw/plugin-sdk/config-runtime";
import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/infra-runtime";
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
@@ -147,63 +146,62 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
const typingReaction = ctx.typingReaction;
const typingCallbacks = createTypingCallbacks({
start: async () => {
didSetStatus = true;
await ctx.setSlackThreadStatus({
channelId: message.channel,
threadTs: statusThreadTs,
status: "is typing...",
});
if (typingReaction && message.ts) {
await reactSlackMessage(message.channel, message.ts, typingReaction, {
token: ctx.botToken,
client: ctx.app.client,
}).catch(() => {});
}
},
stop: async () => {
if (!didSetStatus) {
return;
}
didSetStatus = false;
await ctx.setSlackThreadStatus({
channelId: message.channel,
threadTs: statusThreadTs,
status: "",
});
if (typingReaction && message.ts) {
await removeSlackReaction(message.channel, message.ts, typingReaction, {
token: ctx.botToken,
client: ctx.app.client,
}).catch(() => {});
}
},
onStartError: (err) => {
logTypingFailure({
log: (message) => runtime.error?.(danger(message)),
channel: "slack",
action: "start",
target: typingTarget,
error: err,
});
},
onStopError: (err) => {
logTypingFailure({
log: (message) => runtime.error?.(danger(message)),
channel: "slack",
action: "stop",
target: typingTarget,
error: err,
});
},
});
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg,
agentId: route.agentId,
channel: "slack",
accountId: route.accountId,
typing: {
start: async () => {
didSetStatus = true;
await ctx.setSlackThreadStatus({
channelId: message.channel,
threadTs: statusThreadTs,
status: "is typing...",
});
if (typingReaction && message.ts) {
await reactSlackMessage(message.channel, message.ts, typingReaction, {
token: ctx.botToken,
client: ctx.app.client,
}).catch(() => {});
}
},
stop: async () => {
if (!didSetStatus) {
return;
}
didSetStatus = false;
await ctx.setSlackThreadStatus({
channelId: message.channel,
threadTs: statusThreadTs,
status: "",
});
if (typingReaction && message.ts) {
await removeSlackReaction(message.channel, message.ts, typingReaction, {
token: ctx.botToken,
client: ctx.app.client,
}).catch(() => {});
}
},
onStartError: (err) => {
logTypingFailure({
log: (message) => runtime.error?.(danger(message)),
channel: "slack",
action: "start",
target: typingTarget,
error: err,
});
},
onStopError: (err) => {
logTypingFailure({
log: (message) => runtime.error?.(danger(message)),
channel: "slack",
action: "stop",
target: typingTarget,
error: err,
});
},
},
});
const slackStreaming = resolveSlackStreamingConfig({
@@ -299,9 +297,8 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
};
const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
...prefixOptions,
...replyPipeline,
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
typingCallbacks,
deliver: async (payload) => {
if (useStreaming) {
await deliverWithStreaming(payload);
@@ -367,7 +364,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
},
onError: (err, info) => {
runtime.error?.(danger(`slack ${info.kind} reply failed: ${String(err)}`));
typingCallbacks.onIdle?.();
replyPipeline.typingCallbacks?.onIdle?.();
},
});

View File

@@ -5,32 +5,27 @@ import {
SYNTHETIC_MODEL_CATALOG,
} from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
export { SYNTHETIC_DEFAULT_MODEL_REF };
export function applySyntheticProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[SYNTHETIC_DEFAULT_MODEL_REF] = {
...models[SYNTHETIC_DEFAULT_MODEL_REF],
alias: models[SYNTHETIC_DEFAULT_MODEL_REF]?.alias ?? "MiniMax M2.5",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
function applySyntheticPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
return applyProviderConfigWithModelCatalogPreset(cfg, {
providerId: "synthetic",
api: "anthropic-messages",
baseUrl: SYNTHETIC_BASE_URL,
catalogModels: SYNTHETIC_MODEL_CATALOG.map(buildSyntheticModelDefinition),
aliases: [{ modelRef: SYNTHETIC_DEFAULT_MODEL_REF, alias: "MiniMax M2.5" }],
primaryModelRef,
});
}
export function applySyntheticConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applySyntheticProviderConfig(cfg),
SYNTHETIC_DEFAULT_MODEL_REF,
);
export function applySyntheticProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applySyntheticPreset(cfg);
}
export function applySyntheticConfig(cfg: OpenClawConfig): OpenClawConfig {
return applySyntheticPreset(cfg, SYNTHETIC_DEFAULT_MODEL_REF);
}

View File

@@ -6,10 +6,9 @@ import {
modelSupportsVision,
} from "openclaw/plugin-sdk/agent-runtime";
import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
import { removeAckReactionAfterReply } from "openclaw/plugin-sdk/channel-runtime";
import { logAckFailure, logTypingFailure } from "openclaw/plugin-sdk/channel-runtime";
import { createReplyPrefixOptions } from "openclaw/plugin-sdk/channel-runtime";
import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-runtime";
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
import {
loadSessionStore,
@@ -381,12 +380,6 @@ export const dispatchTelegramMessage = async ({
? true
: undefined;
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
cfg,
agentId: route.agentId,
channel: "telegram",
accountId: route.accountId,
});
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
// Handle uncached stickers: get a dedicated vision description before dispatch
@@ -524,15 +517,21 @@ export const dispatchTelegramMessage = async ({
void statusReactionController.setThinking();
}
const typingCallbacks = createTypingCallbacks({
start: sendTyping,
onStartError: (err) => {
logTypingFailure({
log: logVerbose,
channel: "telegram",
target: String(chatId),
error: err,
});
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg,
agentId: route.agentId,
channel: "telegram",
accountId: route.accountId,
typing: {
start: sendTyping,
onStartError: (err) => {
logTypingFailure({
log: logVerbose,
channel: "telegram",
target: String(chatId),
error: err,
});
},
},
});
@@ -542,8 +541,7 @@ export const dispatchTelegramMessage = async ({
ctx: ctxPayload,
cfg,
dispatcherOptions: {
...prefixOptions,
typingCallbacks,
...replyPipeline,
deliver: async (payload, info) => {
if (payload.isError === true) {
hadErrorReplyFailureOrSkip = true;

View File

@@ -4,32 +4,27 @@ import {
TOGETHER_MODEL_CATALOG,
} from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
export const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5";
export function applyTogetherProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[TOGETHER_DEFAULT_MODEL_REF] = {
...models[TOGETHER_DEFAULT_MODEL_REF],
alias: models[TOGETHER_DEFAULT_MODEL_REF]?.alias ?? "Together AI",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
function applyTogetherPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
return applyProviderConfigWithModelCatalogPreset(cfg, {
providerId: "together",
api: "openai-completions",
baseUrl: TOGETHER_BASE_URL,
catalogModels: TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition),
aliases: [{ modelRef: TOGETHER_DEFAULT_MODEL_REF, alias: "Together AI" }],
primaryModelRef,
});
}
export function applyTogetherConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyTogetherProviderConfig(cfg),
TOGETHER_DEFAULT_MODEL_REF,
);
export function applyTogetherProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyTogetherPreset(cfg);
}
export function applyTogetherConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyTogetherPreset(cfg, TOGETHER_DEFAULT_MODEL_REF);
}

View File

@@ -5,29 +5,27 @@ import {
VENICE_MODEL_CATALOG,
} from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
export { VENICE_DEFAULT_MODEL_REF };
export function applyVeniceProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[VENICE_DEFAULT_MODEL_REF] = {
...models[VENICE_DEFAULT_MODEL_REF],
alias: models[VENICE_DEFAULT_MODEL_REF]?.alias ?? "Kimi K2.5",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
function applyVenicePreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
return applyProviderConfigWithModelCatalogPreset(cfg, {
providerId: "venice",
api: "openai-completions",
baseUrl: VENICE_BASE_URL,
catalogModels: VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition),
aliases: [{ modelRef: VENICE_DEFAULT_MODEL_REF, alias: "Kimi K2.5" }],
primaryModelRef,
});
}
export function applyVeniceConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyVeniceProviderConfig(cfg), VENICE_DEFAULT_MODEL_REF);
export function applyVeniceProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyVenicePreset(cfg);
}
export function applyVeniceConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyVenicePreset(cfg, VENICE_DEFAULT_MODEL_REF);
}

View File

@@ -1,6 +1,5 @@
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithDefaultModels,
applyProviderConfigWithDefaultModelsPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import { XAI_BASE_URL, XAI_DEFAULT_MODEL_ID } from "./model-definitions.js";
@@ -11,20 +10,16 @@ export const XAI_DEFAULT_MODEL_REF = `xai/${XAI_DEFAULT_MODEL_ID}`;
function applyXaiProviderConfigWithApi(
cfg: OpenClawConfig,
api: "openai-completions" | "openai-responses",
primaryModelRef?: string,
): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[XAI_DEFAULT_MODEL_REF] = {
...models[XAI_DEFAULT_MODEL_REF],
alias: models[XAI_DEFAULT_MODEL_REF]?.alias ?? "Grok",
};
return applyProviderConfigWithDefaultModels(cfg, {
agentModels: models,
return applyProviderConfigWithDefaultModelsPreset(cfg, {
providerId: "xai",
api,
baseUrl: XAI_BASE_URL,
defaultModels: buildXaiCatalogModels(),
defaultModelId: XAI_DEFAULT_MODEL_ID,
aliases: [{ modelRef: XAI_DEFAULT_MODEL_REF, alias: "Grok" }],
primaryModelRef,
});
}
@@ -37,5 +32,5 @@ export function applyXaiResponsesApiConfig(cfg: OpenClawConfig): OpenClawConfig
}
export function applyXaiConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyXaiProviderConfig(cfg), XAI_DEFAULT_MODEL_REF);
return applyXaiProviderConfigWithApi(cfg, "openai-completions", XAI_DEFAULT_MODEL_REF);
}

View File

@@ -1,6 +1,5 @@
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
@@ -19,32 +18,35 @@ const ZAI_DEFAULT_MODELS = [
buildZaiModelDefinition({ id: "glm-4.7-flashx" }),
];
function resolveZaiPresetBaseUrl(cfg: OpenClawConfig, endpoint?: string): string {
const existingProvider = cfg.models?.providers?.zai;
const existingBaseUrl =
typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl.trim() : "";
return endpoint ? resolveZaiBaseUrl(endpoint) : existingBaseUrl || resolveZaiBaseUrl();
}
function applyZaiPreset(
cfg: OpenClawConfig,
params?: { endpoint?: string; modelId?: string },
primaryModelRef?: string,
): OpenClawConfig {
const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID;
const modelRef = `zai/${modelId}`;
return applyProviderConfigWithModelCatalogPreset(cfg, {
providerId: "zai",
api: "openai-completions",
baseUrl: resolveZaiPresetBaseUrl(cfg, params?.endpoint),
catalogModels: ZAI_DEFAULT_MODELS,
aliases: [{ modelRef, alias: "GLM" }],
primaryModelRef,
});
}
export function applyZaiProviderConfig(
cfg: OpenClawConfig,
params?: { endpoint?: string; modelId?: string },
): OpenClawConfig {
const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID;
const modelRef = `zai/${modelId}`;
const existingProvider = cfg.models?.providers?.zai;
const models = { ...cfg.agents?.defaults?.models };
models[modelRef] = {
...models[modelRef],
alias: models[modelRef]?.alias ?? "GLM",
};
const existingBaseUrl =
typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl.trim() : "";
const baseUrl = params?.endpoint
? resolveZaiBaseUrl(params.endpoint)
: existingBaseUrl || resolveZaiBaseUrl();
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
providerId: "zai",
api: "openai-completions",
baseUrl,
catalogModels: ZAI_DEFAULT_MODELS,
});
return applyZaiPreset(cfg, params);
}
export function applyZaiConfig(
@@ -53,5 +55,5 @@ export function applyZaiConfig(
): OpenClawConfig {
const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID;
const modelRef = modelId === ZAI_DEFAULT_MODEL_ID ? ZAI_DEFAULT_MODEL_REF : `zai/${modelId}`;
return applyAgentDefaultModelPrimary(applyZaiProviderConfig(cfg, params), modelRef);
return applyZaiPreset(cfg, params, modelRef);
}

View File

@@ -30,11 +30,9 @@ import {
import { resolveZaloProxyFetch } from "./proxy.js";
import type { MarkdownTableMode, OpenClawConfig, OutboundReplyPayload } from "./runtime-api.js";
import {
createTypingCallbacks,
createScopedPairingAccess,
createReplyPrefixOptions,
createChannelPairingController,
createChannelReplyPipeline,
deliverTextOrMediaReply,
issuePairingChallenge,
resolveWebhookPath,
logTypingFailure,
resolveDefaultGroupPolicy,
@@ -330,7 +328,7 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
statusSink,
fetcher,
} = params;
const pairing = createScopedPairingAccess({
const pairing = createChannelPairingController({
core,
channel: "zalo",
accountId: account.accountId,
@@ -406,12 +404,10 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
}
if (directDmOutcome === "unauthorized") {
if (dmPolicy === "pairing") {
await issuePairingChallenge({
channel: "zalo",
await pairing.issueChallenge({
senderId,
senderIdLine: `Your Zalo user id: ${senderId}`,
meta: { name: senderName ?? undefined },
upsertPairingRequest: pairing.upsertPairingRequest,
onCreated: () => {
logVerbose(core, runtime, `zalo pairing request sender=${senderId}`);
},
@@ -507,32 +503,32 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
channel: "zalo",
accountId: account.accountId,
});
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg: config,
agentId: route.agentId,
channel: "zalo",
accountId: account.accountId,
});
const typingCallbacks = createTypingCallbacks({
start: async () => {
await sendChatAction(
token,
{
chat_id: chatId,
action: "typing",
},
fetcher,
ZALO_TYPING_TIMEOUT_MS,
);
},
onStartError: (err) => {
logTypingFailure({
log: (message) => logVerbose(core, runtime, message),
channel: "zalo",
action: "start",
target: chatId,
error: err,
});
typing: {
start: async () => {
await sendChatAction(
token,
{
chat_id: chatId,
action: "typing",
},
fetcher,
ZALO_TYPING_TIMEOUT_MS,
);
},
onStartError: (err) => {
logTypingFailure({
log: (message) => logVerbose(core, runtime, message),
channel: "zalo",
action: "start",
target: chatId,
error: err,
});
},
},
});
@@ -540,8 +536,7 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
ctx: ctxPayload,
cfg: config,
dispatcherOptions: {
...prefixOptions,
typingCallbacks,
...replyPipeline,
deliver: async (payload) => {
await deliverZaloReply({
payload,

View File

@@ -1,13 +1,6 @@
import {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "./runtime-api.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
};
} from "openclaw/plugin-sdk/secret-input";

View File

@@ -18,13 +18,11 @@ import type {
RuntimeEnv,
} from "../runtime-api.js";
import {
createTypingCallbacks,
createScopedPairingAccess,
createReplyPrefixOptions,
createChannelPairingController,
createChannelReplyPipeline,
deliverTextOrMediaReply,
evaluateGroupRouteAccessForPolicy,
isDangerousNameMatchingEnabled,
issuePairingChallenge,
mergeAllowlist,
resolveMentionGatingWithBypass,
resolveOpenProviderRuntimeGroupPolicy,
@@ -252,7 +250,7 @@ async function processMessage(
historyState: ZalouserGroupHistoryState,
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
): Promise<void> {
const pairing = createScopedPairingAccess({
const pairing = createChannelPairingController({
core,
channel: "zalouser",
accountId: account.accountId,
@@ -389,12 +387,10 @@ async function processMessage(
if (!isGroup && accessDecision.decision !== "allow") {
if (accessDecision.decision === "pairing") {
await issuePairingChallenge({
channel: "zalouser",
await pairing.issueChallenge({
senderId,
senderIdLine: `Your Zalo user id: ${senderId}`,
meta: { name: senderName || undefined },
upsertPairingRequest: pairing.upsertPairingRequest,
onCreated: () => {
logVerbose(core, runtime, `zalouser pairing request sender=${senderId}`);
},
@@ -630,24 +626,24 @@ async function processMessage(
},
});
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg: config,
agentId: route.agentId,
channel: "zalouser",
accountId: account.accountId,
});
const typingCallbacks = createTypingCallbacks({
start: async () => {
await sendTypingZalouser(chatId, {
profile: account.profile,
isGroup,
});
},
onStartError: (err) => {
runtime.error?.(
`[${account.accountId}] zalouser typing start failed for ${chatId}: ${String(err)}`,
);
logVerbose(core, runtime, `zalouser typing failed for ${chatId}: ${String(err)}`);
typing: {
start: async () => {
await sendTypingZalouser(chatId, {
profile: account.profile,
isGroup,
});
},
onStartError: (err) => {
runtime.error?.(
`[${account.accountId}] zalouser typing start failed for ${chatId}: ${String(err)}`,
);
logVerbose(core, runtime, `zalouser typing failed for ${chatId}: ${String(err)}`);
},
},
});
@@ -655,8 +651,7 @@ async function processMessage(
ctx: ctxPayload,
cfg: config,
dispatcherOptions: {
...prefixOptions,
typingCallbacks,
...replyPipeline,
deliver: async (payload) => {
await deliverZalouserReply({
payload: payload as { text?: string; mediaUrls?: string[]; mediaUrl?: string },

View File

@@ -78,6 +78,10 @@
"types": "./dist/plugin-sdk/setup.d.ts",
"default": "./dist/plugin-sdk/setup.js"
},
"./plugin-sdk/channel-setup": {
"types": "./dist/plugin-sdk/channel-setup.d.ts",
"default": "./dist/plugin-sdk/channel-setup.js"
},
"./plugin-sdk/setup-tools": {
"types": "./dist/plugin-sdk/setup-tools.d.ts",
"default": "./dist/plugin-sdk/setup-tools.js"
@@ -94,6 +98,10 @@
"types": "./dist/plugin-sdk/reply-payload.d.ts",
"default": "./dist/plugin-sdk/reply-payload.js"
},
"./plugin-sdk/channel-reply-pipeline": {
"types": "./dist/plugin-sdk/channel-reply-pipeline.d.ts",
"default": "./dist/plugin-sdk/channel-reply-pipeline.js"
},
"./plugin-sdk/channel-runtime": {
"types": "./dist/plugin-sdk/channel-runtime.d.ts",
"default": "./dist/plugin-sdk/channel-runtime.js"
@@ -254,6 +262,10 @@
"types": "./dist/plugin-sdk/channel-lifecycle.d.ts",
"default": "./dist/plugin-sdk/channel-lifecycle.js"
},
"./plugin-sdk/channel-pairing": {
"types": "./dist/plugin-sdk/channel-pairing.d.ts",
"default": "./dist/plugin-sdk/channel-pairing.js"
},
"./plugin-sdk/channel-policy": {
"types": "./dist/plugin-sdk/channel-policy.d.ts",
"default": "./dist/plugin-sdk/channel-policy.js"
@@ -334,6 +346,10 @@
"types": "./dist/plugin-sdk/request-url.d.ts",
"default": "./dist/plugin-sdk/request-url.js"
},
"./plugin-sdk/webhook-ingress": {
"types": "./dist/plugin-sdk/webhook-ingress.d.ts",
"default": "./dist/plugin-sdk/webhook-ingress.js"
},
"./plugin-sdk/webhook-path": {
"types": "./dist/plugin-sdk/webhook-path.d.ts",
"default": "./dist/plugin-sdk/webhook-path.js"
@@ -342,6 +358,10 @@
"types": "./dist/plugin-sdk/runtime-store.d.ts",
"default": "./dist/plugin-sdk/runtime-store.js"
},
"./plugin-sdk/secret-input": {
"types": "./dist/plugin-sdk/secret-input.d.ts",
"default": "./dist/plugin-sdk/secret-input.js"
},
"./plugin-sdk/web-media": {
"types": "./dist/plugin-sdk/web-media.d.ts",
"default": "./dist/plugin-sdk/web-media.js"

View File

@@ -9,10 +9,12 @@
"runtime",
"runtime-env",
"setup",
"channel-setup",
"setup-tools",
"config-runtime",
"reply-runtime",
"reply-payload",
"channel-reply-pipeline",
"channel-runtime",
"interactive-runtime",
"infra-runtime",
@@ -53,6 +55,7 @@
"channel-config-helpers",
"channel-config-schema",
"channel-lifecycle",
"channel-pairing",
"channel-policy",
"channel-send-result",
"group-access",
@@ -73,8 +76,10 @@
"reply-history",
"media-understanding",
"request-url",
"webhook-ingress",
"webhook-path",
"runtime-store",
"secret-input",
"web-media",
"speech",
"state-paths",

View File

@@ -3,9 +3,12 @@ import type { OpenClawConfig } from "../config/config.js";
import type { AgentModelEntryConfig } from "../config/types.agent-defaults.js";
import type { ModelDefinitionConfig } from "../config/types.models.js";
import {
applyProviderConfigWithDefaultModelPreset,
applyProviderConfigWithModelCatalogPreset,
applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModels,
applyProviderConfigWithModelCatalog,
withAgentModelAliases,
} from "../plugins/provider-onboarding-config.js";
function makeModel(id: string): ModelDefinitionConfig {
@@ -97,4 +100,76 @@ describe("onboard auth provider config merges", () => {
expect(next.models?.providers?.custom?.models?.map((m) => m.id)).toEqual(["model-z"]);
});
it("preserves explicit aliases when adding provider alias presets", () => {
expect(
withAgentModelAliases(
{
"custom/model-a": { alias: "Pinned" },
},
[{ modelRef: "custom/model-a", alias: "Preset" }, "custom/model-b"],
),
).toEqual({
"custom/model-a": { alias: "Pinned" },
"custom/model-b": {},
});
});
it("applies default-model presets with alias and primary model", () => {
const next = applyProviderConfigWithDefaultModelPreset(
{
agents: {
defaults: {
models: {
"custom/model-z": { alias: "Pinned" },
},
},
},
},
{
providerId: "custom",
api: "openai-completions",
baseUrl: "https://example.com/v1",
defaultModel: makeModel("model-z"),
aliases: [{ modelRef: "custom/model-z", alias: "Preset" }],
primaryModelRef: "custom/model-z",
},
);
expect(next.agents?.defaults?.models?.["custom/model-z"]).toEqual({ alias: "Pinned" });
expect(next.agents?.defaults?.model).toEqual({ primary: "custom/model-z" });
});
it("applies catalog presets with alias and merged catalog models", () => {
const next = applyProviderConfigWithModelCatalogPreset(
{
models: {
providers: {
custom: {
api: "openai-completions",
baseUrl: "https://example.com/v1",
models: [makeModel("model-a")],
},
},
},
},
{
providerId: "custom",
api: "openai-completions",
baseUrl: "https://example.com/v1",
catalogModels: [makeModel("model-a"), makeModel("model-b")],
aliases: [{ modelRef: "custom/model-b", alias: "Catalog Alias" }],
primaryModelRef: "custom/model-b",
},
);
expect(next.models?.providers?.custom?.models?.map((model) => model.id)).toEqual([
"model-a",
"model-b",
]);
expect(next.agents?.defaults?.models?.["custom/model-b"]).toEqual({
alias: "Catalog Alias",
});
expect(next.agents?.defaults?.model).toEqual({ primary: "custom/model-b" });
});
});

View File

@@ -0,0 +1,48 @@
import { describe, expect, it, vi } from "vitest";
import type { PluginRuntime } from "../plugins/runtime/types.js";
import { createChannelPairingController } from "./channel-pairing.js";
describe("createChannelPairingController", () => {
it("scopes store access and issues pairing challenges through the scoped store", async () => {
const readAllowFromStore = vi.fn(async () => ["alice"]);
const upsertPairingRequest = vi.fn(async () => ({ code: "123456", created: true }));
const replies: string[] = [];
const sendPairingReply = vi.fn(async (text: string) => {
replies.push(text);
});
const runtime = {
channel: {
pairing: {
readAllowFromStore,
upsertPairingRequest,
},
},
} as unknown as PluginRuntime;
const pairing = createChannelPairingController({
core: runtime,
channel: "googlechat",
accountId: "Primary",
});
await expect(pairing.readAllowFromStore()).resolves.toEqual(["alice"]);
await pairing.issueChallenge({
senderId: "user-1",
senderIdLine: "Your id: user-1",
sendPairingReply,
});
expect(readAllowFromStore).toHaveBeenCalledWith({
channel: "googlechat",
accountId: "primary",
});
expect(upsertPairingRequest).toHaveBeenCalledWith({
channel: "googlechat",
accountId: "primary",
id: "user-1",
meta: undefined,
});
expect(sendPairingReply).toHaveBeenCalledTimes(1);
expect(replies[0]).toContain("123456");
});
});

View File

@@ -0,0 +1,31 @@
import type { ChannelId } from "../channels/plugins/types.js";
import { issuePairingChallenge } from "../pairing/pairing-challenge.js";
import type { PluginRuntime } from "../plugins/runtime/types.js";
import { createScopedPairingAccess } from "./pairing-access.js";
export { createScopedPairingAccess } from "./pairing-access.js";
type ScopedPairingAccess = ReturnType<typeof createScopedPairingAccess>;
export type ChannelPairingController = ScopedPairingAccess & {
issueChallenge: (
params: Omit<Parameters<typeof issuePairingChallenge>[0], "channel" | "upsertPairingRequest">,
) => ReturnType<typeof issuePairingChallenge>;
};
export function createChannelPairingController(params: {
core: PluginRuntime;
channel: ChannelId;
accountId: string;
}): ChannelPairingController {
const access = createScopedPairingAccess(params);
return {
...access,
issueChallenge: (challenge) =>
issuePairingChallenge({
channel: params.channel,
upsertPairingRequest: access.upsertPairingRequest,
...challenge,
}),
};
}

View File

@@ -0,0 +1,39 @@
import { describe, expect, it, vi } from "vitest";
import { createChannelReplyPipeline } from "./channel-reply-pipeline.js";
describe("createChannelReplyPipeline", () => {
it("builds prefix options without forcing typing support", () => {
const pipeline = createChannelReplyPipeline({
cfg: {},
agentId: "main",
channel: "telegram",
accountId: "default",
});
expect(typeof pipeline.onModelSelected).toBe("function");
expect(typeof pipeline.responsePrefixContextProvider).toBe("function");
expect(pipeline.typingCallbacks).toBeUndefined();
});
it("builds typing callbacks when typing config is provided", async () => {
const start = vi.fn(async () => {});
const stop = vi.fn(async () => {});
const pipeline = createChannelReplyPipeline({
cfg: {},
agentId: "main",
channel: "discord",
accountId: "default",
typing: {
start,
stop,
onStartError: () => {},
},
});
await pipeline.typingCallbacks?.onReplyStart();
pipeline.typingCallbacks?.onIdle?.();
expect(start).toHaveBeenCalled();
expect(stop).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,38 @@
import {
createReplyPrefixContext,
createReplyPrefixOptions,
type ReplyPrefixContextBundle,
type ReplyPrefixOptions,
} from "../channels/reply-prefix.js";
import {
createTypingCallbacks,
type CreateTypingCallbacksParams,
type TypingCallbacks,
} from "../channels/typing.js";
export type ReplyPrefixContext = ReplyPrefixContextBundle["prefixContext"];
export type { ReplyPrefixContextBundle, ReplyPrefixOptions };
export type { CreateTypingCallbacksParams, TypingCallbacks };
export { createReplyPrefixContext, createReplyPrefixOptions, createTypingCallbacks };
export type ChannelReplyPipeline = ReplyPrefixOptions & {
typingCallbacks?: TypingCallbacks;
};
export function createChannelReplyPipeline(params: {
cfg: Parameters<typeof createReplyPrefixOptions>[0]["cfg"];
agentId: string;
channel?: string;
accountId?: string;
typing?: CreateTypingCallbacksParams;
}): ChannelReplyPipeline {
return {
...createReplyPrefixOptions({
cfg: params.cfg,
agentId: params.agentId,
channel: params.channel,
accountId: params.accountId,
}),
...(params.typing ? { typingCallbacks: createTypingCallbacks(params.typing) } : {}),
};
}

View File

@@ -0,0 +1,38 @@
import { describe, expect, it } from "vitest";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
describe("createOptionalChannelSetupSurface", () => {
it("returns a matched adapter and wizard for optional plugins", async () => {
const setup = createOptionalChannelSetupSurface({
channel: "example",
label: "Example",
npmSpec: "@openclaw/example",
docsPath: "/channels/example",
});
expect(setup.setupAdapter.resolveAccountId?.({ cfg: {} })).toBe("default");
expect(
setup.setupAdapter.validateInput?.({
cfg: {},
accountId: "default",
input: {},
}),
).toContain("@openclaw/example");
expect(setup.setupWizard.channel).toBe("example");
expect(setup.setupWizard.status.unconfiguredHint).toContain("/channels/example");
await expect(
setup.setupWizard.finalize?.({
cfg: {},
accountId: "default",
credentialValues: {},
runtime: {
log: () => {},
error: () => {},
exit: async () => {},
},
prompter: {} as never,
forceAllowFrom: false,
}),
).rejects.toThrow("@openclaw/example");
});
});

View File

@@ -0,0 +1,42 @@
import type { ChannelSetupWizard } from "../channels/plugins/setup-wizard.js";
import type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
export type { ChannelSetupDmPolicy, ChannelSetupWizard } from "./setup.js";
export {
DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy,
formatDocsLink,
setSetupChannelEnabled,
splitSetupEntries,
} from "./setup.js";
type OptionalChannelSetupParams = {
channel: string;
label: string;
npmSpec?: string;
docsPath?: string;
};
export type OptionalChannelSetupSurface = {
setupAdapter: ChannelSetupAdapter;
setupWizard: ChannelSetupWizard;
};
export {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export function createOptionalChannelSetupSurface(
params: OptionalChannelSetupParams,
): OptionalChannelSetupSurface {
return {
setupAdapter: createOptionalChannelSetupAdapter(params),
setupWizard: createOptionalChannelSetupWizard(params),
};
}

View File

@@ -38,7 +38,7 @@ export type {
} from "../channels/plugins/types.adapters.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixContext } from "../channels/reply-prefix.js";
export { createTypingCallbacks } from "../channels/typing.js";
export { createChannelReplyPipeline, createTypingCallbacks } from "./channel-reply-pipeline.js";
export type { OpenClawConfig as ClawdbotConfig, OpenClawConfig } from "../config/config.js";
export {
resolveAllowlistProviderRuntimeGroupPolicy,
@@ -47,13 +47,13 @@ export {
warnMissingProviderGroupPolicyFallbackOnce,
} from "../config/runtime-group-policy.js";
export type { DmPolicy, GroupToolPolicyConfig } from "../config/types.js";
export type { SecretInput } from "../config/types.secrets.js";
export type { SecretInput } from "./secret-input.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../config/types.secrets.js";
export { buildSecretInputSchema } from "./secret-input-schema.js";
} from "./secret-input.js";
export { createDedupeCache } from "../infra/dedupe.js";
export { installRequestBodyLimitGuard, readJsonBodyWithLimit } from "../infra/http-body.js";
export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
@@ -70,8 +70,7 @@ export type { WizardPrompter } from "../wizard/prompts.js";
export { feishuSetupWizard, feishuSetupAdapter } from "../../extensions/feishu/setup-api.js";
export { buildAgentMediaPayload } from "./agent-media-payload.js";
export { readJsonFileWithFallback } from "./json-store.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { createPersistentDedupe } from "./persistent-dedupe.js";
export {
buildBaseChannelStatusSummary,
@@ -85,9 +84,9 @@ export {
parseFeishuConversationId,
} from "../../extensions/feishu/src/conversation-id.js";
export {
createFixedWindowRateLimiter,
createWebhookAnomalyTracker,
createFixedWindowRateLimiter,
WEBHOOK_ANOMALY_COUNTER_DEFAULTS,
WEBHOOK_RATE_LIMIT_DEFAULTS,
} from "./webhook-memory-guards.js";
export { applyBasicWebhookRequestGuards } from "./webhook-request-guards.js";
} from "./webhook-ingress.js";
export { applyBasicWebhookRequestGuards } from "./webhook-ingress.js";

View File

@@ -2,10 +2,7 @@
// Keep this list additive and scoped to symbols used under extensions/googlechat.
import { resolveChannelGroupRequireMention } from "./channel-policy.js";
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
export {
createActionGate,
@@ -49,7 +46,7 @@ export type {
} from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { getChatChannelMeta } from "../channels/registry.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js";
export {
@@ -71,26 +68,23 @@ export { resolveDmGroupAccessWithLists } from "../security/dm-policy-shared.js";
export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "./inbound-envelope.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export {
evaluateGroupRouteAccessForPolicy,
resolveSenderScopedGroupPolicy,
} from "./group-access.js";
export { extractToolSend } from "./tool-send.js";
export { resolveWebhookPath } from "./webhook-path.js";
export type { WebhookInFlightLimiter } from "./webhook-request-guards.js";
export {
beginWebhookRequestPipelineOrReject,
createWebhookInFlightLimiter,
readJsonWebhookBodyOrReject,
} from "./webhook-request-guards.js";
export {
registerWebhookTargetWithPluginRoute,
resolveWebhookTargets,
resolveWebhookPath,
resolveWebhookTargetWithAuthOrReject,
resolveWebhookTargets,
type WebhookInFlightLimiter,
withResolvedWebhookRequestPipeline,
} from "./webhook-targets.js";
} from "./webhook-ingress.js";
type GoogleChatGroupContext = {
cfg: import("../config/config.js").OpenClawConfig;
@@ -107,16 +101,12 @@ export function resolveGoogleChatGroupRequireMention(params: GoogleChatGroupCont
});
}
export const googlechatSetupAdapter = createOptionalChannelSetupAdapter({
const googlechatSetup = createOptionalChannelSetupSurface({
channel: "googlechat",
label: "Google Chat",
npmSpec: "@openclaw/googlechat",
docsPath: "/channels/googlechat",
});
export const googlechatSetupWizard = createOptionalChannelSetupWizard({
channel: "googlechat",
label: "Google Chat",
npmSpec: "@openclaw/googlechat",
docsPath: "/channels/googlechat",
});
export const googlechatSetupAdapter = googlechatSetup.setupAdapter;
export const googlechatSetupWizard = googlechatSetup.setupWizard;

View File

@@ -23,7 +23,7 @@ export { patchScopedAccountConfig } from "../channels/plugins/setup-helpers.js";
export type { BaseProbeResult } from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { getChatChannelMeta } from "../channels/registry.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js";
export {
@@ -69,8 +69,7 @@ export {
} from "../security/dm-policy-shared.js";
export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { dispatchInboundReplyWithBase } from "./inbound-reply-dispatch.js";
export { ircSetupAdapter, ircSetupWizard } from "../../extensions/irc/api.js";
export type { OutboundReplyPayload } from "./reply-payload.js";

View File

@@ -1,10 +1,7 @@
// Narrow plugin-sdk surface for the bundled matrix plugin.
// Keep this list additive and scoped to symbols used under extensions/matrix.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
export {
createActionGate,
@@ -60,8 +57,8 @@ export type {
ChannelToolSend,
} from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createTypingCallbacks } from "../channels/typing.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export { createTypingCallbacks } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export {
GROUP_POLICY_BLOCKED_LABEL,
@@ -75,13 +72,13 @@ export type {
GroupToolPolicyConfig,
MarkdownTableMode,
} from "../config/types.js";
export type { SecretInput } from "../config/types.secrets.js";
export type { SecretInput } from "./secret-input.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../config/types.secrets.js";
export { buildSecretInputSchema } from "./secret-input-schema.js";
} from "./secret-input.js";
export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
export { MarkdownConfigSchema } from "../config/zod-schema.core.js";
export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
@@ -103,7 +100,7 @@ export {
evaluateGroupRouteAccessForPolicy,
resolveSenderScopedGroupPolicy,
} from "./group-access.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { formatResolvedUnresolvedNote } from "./resolution-notes.js";
export { runPluginCommandWithTimeout } from "./run-command.js";
export { dispatchReplyFromConfigWithSettledDispatcher } from "./inbound-reply-dispatch.js";
@@ -114,16 +111,12 @@ export {
collectStatusIssuesFromLastError,
} from "./status-helpers.js";
export const matrixSetupWizard = createOptionalChannelSetupWizard({
const matrixSetup = createOptionalChannelSetupSurface({
channel: "matrix",
label: "Matrix",
npmSpec: "@openclaw/matrix",
docsPath: "/channels/matrix",
});
export const matrixSetupAdapter = createOptionalChannelSetupAdapter({
channel: "matrix",
label: "Matrix",
npmSpec: "@openclaw/matrix",
docsPath: "/channels/matrix",
});
export const matrixSetupWizard = matrixSetup.setupWizard;
export const matrixSetupAdapter = matrixSetup.setupAdapter;

View File

@@ -1,10 +1,7 @@
// Narrow plugin-sdk surface for the bundled msteams plugin.
// Keep this list additive and scoped to symbols used under extensions/msteams.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
export type { ChunkMode } from "../auto-reply/chunk.js";
export type { HistoryEntry } from "../auto-reply/reply/history.js";
@@ -55,8 +52,8 @@ export type {
ChannelOutboundAdapter,
} from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createTypingCallbacks } from "../channels/typing.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export { createTypingCallbacks } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js";
export { resolveToolsBySender } from "../config/group-policy.js";
@@ -109,7 +106,7 @@ export { withFileLock } from "./file-lock.js";
export { dispatchReplyFromConfigWithSettledDispatcher } from "./inbound-reply-dispatch.js";
export { readJsonFileWithFallback, writeJsonFileAtomically } from "./json-store.js";
export { loadOutboundMediaFromUrl } from "./outbound-media.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { resolveInboundSessionEnvelopeContext } from "../channels/session-envelope.js";
export {
buildHostnameAllowlistPolicyFromSuffixAllowlist,
@@ -124,16 +121,12 @@ export {
} from "./status-helpers.js";
export { normalizeStringEntries } from "../shared/string-normalization.js";
export const msteamsSetupWizard = createOptionalChannelSetupWizard({
const msteamsSetup = createOptionalChannelSetupSurface({
channel: "msteams",
label: "Microsoft Teams",
npmSpec: "@openclaw/msteams",
docsPath: "/channels/msteams",
});
export const msteamsSetupAdapter = createOptionalChannelSetupAdapter({
channel: "msteams",
label: "Microsoft Teams",
npmSpec: "@openclaw/msteams",
docsPath: "/channels/msteams",
});
export const msteamsSetupWizard = msteamsSetup.setupWizard;
export const msteamsSetupAdapter = msteamsSetup.setupAdapter;

View File

@@ -32,7 +32,7 @@ export {
export { createAccountListHelpers } from "../channels/plugins/account-helpers.js";
export type { ChannelGroupContext, ChannelSetupInput } from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { mapAllowFromEntries } from "./channel-config-helpers.js";
export { evaluateMatchedGroupAccessForPolicy } from "./group-access.js";
@@ -49,13 +49,13 @@ export type {
GroupPolicy,
GroupToolPolicyConfig,
} from "../config/types.js";
export type { SecretInput } from "../config/types.secrets.js";
export type { SecretInput } from "./secret-input.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../config/types.secrets.js";
export { buildSecretInputSchema } from "./secret-input-schema.js";
} from "./secret-input.js";
export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
export {
BlockStreamingCoalesceSchema,
@@ -88,8 +88,7 @@ export {
listConfiguredAccountIds,
resolveAccountWithDefaultFallback,
} from "./account-resolution.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { createPersistentDedupe } from "./persistent-dedupe.js";
export type { OutboundReplyPayload } from "./reply-payload.js";
export {

View File

@@ -1,10 +1,7 @@
// Narrow plugin-sdk surface for the bundled nostr plugin.
// Keep this list additive and scoped to symbols used under extensions/nostr.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
@@ -25,16 +22,12 @@ export {
export { createFixedWindowRateLimiter } from "./webhook-memory-guards.js";
export { mapAllowFromEntries } from "./channel-config-helpers.js";
export const nostrSetupAdapter = createOptionalChannelSetupAdapter({
const nostrSetup = createOptionalChannelSetupSurface({
channel: "nostr",
label: "Nostr",
npmSpec: "@openclaw/nostr",
docsPath: "/channels/nostr",
});
export const nostrSetupWizard = createOptionalChannelSetupWizard({
channel: "nostr",
label: "Nostr",
npmSpec: "@openclaw/nostr",
docsPath: "/channels/nostr",
});
export const nostrSetupAdapter = nostrSetup.setupAdapter;
export const nostrSetupWizard = nostrSetup.setupWizard;

View File

@@ -9,8 +9,13 @@ export type {
export {
applyAgentDefaultModelPrimary,
applyOnboardAuthAgentModelsAndProviders,
applyProviderConfigWithDefaultModelPreset,
applyProviderConfigWithDefaultModelsPreset,
applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModels,
applyProviderConfigWithModelCatalogPreset,
applyProviderConfigWithModelCatalog,
withAgentModelAliases,
} from "../plugins/provider-onboarding-config.js";
export type { AgentModelAliasEntry } from "../plugins/provider-onboarding-config.js";
export { ensureModelAllowlistEntry } from "../plugins/provider-model-allowlist.js";

View File

@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import {
buildOptionalSecretInputSchema,
buildSecretInputArraySchema,
normalizeSecretInputString,
} from "./secret-input.js";
describe("plugin-sdk secret input helpers", () => {
it("accepts undefined for optional secret input", () => {
expect(buildOptionalSecretInputSchema().safeParse(undefined).success).toBe(true);
});
it("accepts arrays of secret inputs", () => {
const result = buildSecretInputArraySchema().safeParse([
"sk-plain",
{ source: "env", provider: "default", id: "OPENAI_API_KEY" },
]);
expect(result.success).toBe(true);
});
it("normalizes plaintext secret strings", () => {
expect(normalizeSecretInputString(" sk-test ")).toBe("sk-test");
});
});

View File

@@ -0,0 +1,23 @@
import { z } from "zod";
import {
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../config/types.secrets.js";
import { buildSecretInputSchema } from "./secret-input-schema.js";
export type { SecretInput } from "../config/types.secrets.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
};
export function buildOptionalSecretInputSchema() {
return buildSecretInputSchema().optional();
}
export function buildSecretInputArraySchema() {
return z.array(buildSecretInputSchema());
}

View File

@@ -1,6 +1,9 @@
import * as bluebubblesSdk from "openclaw/plugin-sdk/bluebubbles";
import * as channelPairingSdk from "openclaw/plugin-sdk/channel-pairing";
import * as channelReplyPipelineSdk from "openclaw/plugin-sdk/channel-reply-pipeline";
import * as channelRuntimeSdk from "openclaw/plugin-sdk/channel-runtime";
import * as channelSendResultSdk from "openclaw/plugin-sdk/channel-send-result";
import * as channelSetupSdk from "openclaw/plugin-sdk/channel-setup";
import * as coreSdk from "openclaw/plugin-sdk/core";
import type {
ChannelMessageActionContext as CoreChannelMessageActionContext,
@@ -18,11 +21,13 @@ import * as replyPayloadSdk from "openclaw/plugin-sdk/reply-payload";
import * as routingSdk from "openclaw/plugin-sdk/routing";
import * as runtimeSdk from "openclaw/plugin-sdk/runtime";
import * as sandboxSdk from "openclaw/plugin-sdk/sandbox";
import * as secretInputSdk from "openclaw/plugin-sdk/secret-input";
import * as selfHostedProviderSetupSdk from "openclaw/plugin-sdk/self-hosted-provider-setup";
import * as setupSdk from "openclaw/plugin-sdk/setup";
import * as slackSdk from "openclaw/plugin-sdk/slack";
import * as telegramSdk from "openclaw/plugin-sdk/telegram";
import * as testingSdk from "openclaw/plugin-sdk/testing";
import * as webhookIngressSdk from "openclaw/plugin-sdk/webhook-ingress";
import * as whatsappSdk from "openclaw/plugin-sdk/whatsapp";
import * as whatsappActionRuntimeSdk from "openclaw/plugin-sdk/whatsapp-action-runtime";
import * as whatsappLoginQrSdk from "openclaw/plugin-sdk/whatsapp-login-qr";
@@ -111,6 +116,21 @@ describe("plugin-sdk subpath exports", () => {
expect(typeof channelRuntimeSdk.sendPayloadMediaSequenceOrFallback).toBe("function");
});
it("exports channel setup helpers from the dedicated subpath", () => {
expect(typeof channelSetupSdk.createOptionalChannelSetupSurface).toBe("function");
expect(typeof channelSetupSdk.createTopLevelChannelDmPolicy).toBe("function");
});
it("exports channel pairing helpers from the dedicated subpath", () => {
expect(typeof channelPairingSdk.createChannelPairingController).toBe("function");
expect(typeof channelPairingSdk.createScopedPairingAccess).toBe("function");
});
it("exports channel reply pipeline helpers from the dedicated subpath", () => {
expect(typeof channelReplyPipelineSdk.createChannelReplyPipeline).toBe("function");
expect(typeof channelReplyPipelineSdk.createTypingCallbacks).toBe("function");
});
it("exports channel send-result helpers from the dedicated subpath", () => {
expect(typeof channelSendResultSdk.attachChannelToResult).toBe("function");
expect(typeof channelSendResultSdk.buildChannelSendResult).toBe("function");
@@ -162,6 +182,18 @@ describe("plugin-sdk subpath exports", () => {
expect(typeof sandboxSdk.runPluginCommandWithTimeout).toBe("function");
});
it("exports secret input helpers from the dedicated subpath", () => {
expect(typeof secretInputSdk.buildSecretInputSchema).toBe("function");
expect(typeof secretInputSdk.buildOptionalSecretInputSchema).toBe("function");
expect(typeof secretInputSdk.normalizeSecretInputString).toBe("function");
});
it("exports webhook ingress helpers from the dedicated subpath", () => {
expect(typeof webhookIngressSdk.resolveWebhookPath).toBe("function");
expect(typeof webhookIngressSdk.readJsonWebhookBodyOrReject).toBe("function");
expect(typeof webhookIngressSdk.withResolvedWebhookRequestPipeline).toBe("function");
});
it("exports shared core types used by bundled channels", () => {
expectTypeOf<CoreOpenClawPluginApi>().toMatchTypeOf<OpenClawPluginApi>();
expectTypeOf<CorePluginRuntime>().toMatchTypeOf<PluginRuntime>();

View File

@@ -1,10 +1,7 @@
// Narrow plugin-sdk surface for the bundled tlon plugin.
// Keep this list additive and scoped to symbols used under extensions/tlon.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
@@ -18,7 +15,7 @@ export type {
ChannelSetupInput,
} from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { createDedupeCache } from "../infra/dedupe.js";
export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
@@ -33,16 +30,12 @@ export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export { createLoggerBackedRuntime } from "./runtime.js";
export const tlonSetupAdapter = createOptionalChannelSetupAdapter({
const tlonSetup = createOptionalChannelSetupSurface({
channel: "tlon",
label: "Tlon",
npmSpec: "@openclaw/tlon",
docsPath: "/channels/tlon",
});
export const tlonSetupWizard = createOptionalChannelSetupWizard({
channel: "tlon",
label: "Tlon",
npmSpec: "@openclaw/tlon",
docsPath: "/channels/tlon",
});
export const tlonSetupAdapter = tlonSetup.setupAdapter;
export const tlonSetupWizard = tlonSetup.setupWizard;

View File

@@ -1,10 +1,7 @@
// Narrow plugin-sdk surface for the bundled twitch plugin.
// Keep this list additive and scoped to symbols used under extensions/twitch.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
@@ -27,7 +24,7 @@ export type {
ChannelStatusIssue,
} from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { MarkdownConfigSchema } from "../config/zod-schema.core.js";
export type { OutboundDeliveryResult } from "../infra/outbound/deliver.js";
@@ -39,14 +36,11 @@ export type { RuntimeEnv } from "../runtime.js";
export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export const twitchSetupAdapter = createOptionalChannelSetupAdapter({
const twitchSetup = createOptionalChannelSetupSurface({
channel: "twitch",
label: "Twitch",
npmSpec: "@openclaw/twitch",
});
export const twitchSetupWizard = createOptionalChannelSetupWizard({
channel: "twitch",
label: "Twitch",
npmSpec: "@openclaw/twitch",
});
export const twitchSetupAdapter = twitchSetup.setupAdapter;
export const twitchSetupWizard = twitchSetup.setupWizard;

View File

@@ -0,0 +1,38 @@
export {
createBoundedCounter,
createFixedWindowRateLimiter,
createWebhookAnomalyTracker,
WEBHOOK_ANOMALY_COUNTER_DEFAULTS,
WEBHOOK_ANOMALY_STATUS_CODES,
WEBHOOK_RATE_LIMIT_DEFAULTS,
type BoundedCounter,
type FixedWindowRateLimiter,
type WebhookAnomalyTracker,
} from "./webhook-memory-guards.js";
export {
applyBasicWebhookRequestGuards,
beginWebhookRequestPipelineOrReject,
createWebhookInFlightLimiter,
isJsonContentType,
readJsonWebhookBodyOrReject,
readWebhookBodyOrReject,
WEBHOOK_BODY_READ_DEFAULTS,
WEBHOOK_IN_FLIGHT_DEFAULTS,
type WebhookBodyReadProfile,
type WebhookInFlightLimiter,
} from "./webhook-request-guards.js";
export {
registerWebhookTarget,
registerWebhookTargetWithPluginRoute,
resolveSingleWebhookTarget,
resolveSingleWebhookTargetAsync,
resolveWebhookTargetWithAuthOrReject,
resolveWebhookTargetWithAuthOrRejectSync,
resolveWebhookTargets,
withResolvedWebhookRequestPipeline,
type RegisterWebhookPluginRouteOptions,
type RegisterWebhookTargetOptions,
type RegisteredWebhookTarget,
type WebhookTargetMatchResult,
} from "./webhook-targets.js";
export { normalizeWebhookPath, resolveWebhookPath } from "./webhook-path.js";

View File

@@ -34,9 +34,9 @@ export type {
ChannelStatusIssue,
} from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { logTypingFailure } from "../channels/logging.js";
export { createTypingCallbacks } from "../channels/typing.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export { createTypingCallbacks } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export {
resolveDefaultGroupPolicy,
@@ -44,13 +44,13 @@ export {
warnMissingProviderGroupPolicyFallbackOnce,
} from "../config/runtime-group-policy.js";
export type { GroupPolicy, MarkdownTableMode } from "../config/types.js";
export type { SecretInput } from "../config/types.secrets.js";
export type { SecretInput } from "./secret-input.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../config/types.secrets.js";
export { buildSecretInputSchema } from "./secret-input-schema.js";
} from "./secret-input.js";
export { MarkdownConfigSchema } from "../config/zod-schema.core.js";
export { waitForAbortSignal } from "../infra/abort-signal.js";
export { createDedupeCache } from "../infra/dedupe.js";
@@ -72,8 +72,7 @@ export { resolveChannelAccountConfigBasePath } from "./config-paths.js";
export { evaluateSenderGroupAccess } from "./group-access.js";
export type { SenderGroupAccessDecision } from "./group-access.js";
export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "./inbound-envelope.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { buildChannelSendResult } from "./channel-send-result.js";
export type { OutboundReplyPayload } from "./reply-payload.js";
export {
@@ -90,25 +89,21 @@ export {
export { chunkTextForOutbound } from "./text-chunking.js";
export { extractToolSend } from "./tool-send.js";
export {
applyBasicWebhookRequestGuards,
createFixedWindowRateLimiter,
createWebhookAnomalyTracker,
readJsonWebhookBodyOrReject,
registerWebhookTarget,
registerWebhookTargetWithPluginRoute,
resolveSingleWebhookTarget,
resolveWebhookPath,
resolveWebhookTargetWithAuthOrRejectSync,
resolveWebhookTargets,
WEBHOOK_ANOMALY_COUNTER_DEFAULTS,
WEBHOOK_RATE_LIMIT_DEFAULTS,
} from "./webhook-memory-guards.js";
export { resolveWebhookPath } from "./webhook-path.js";
export {
applyBasicWebhookRequestGuards,
readJsonWebhookBodyOrReject,
} from "./webhook-request-guards.js";
withResolvedWebhookRequestPipeline,
} from "./webhook-ingress.js";
export type {
RegisterWebhookPluginRouteOptions,
RegisterWebhookTargetOptions,
} from "./webhook-targets.js";
export {
registerWebhookTarget,
registerWebhookTargetWithPluginRoute,
resolveWebhookTargetWithAuthOrRejectSync,
resolveSingleWebhookTarget,
resolveWebhookTargets,
withResolvedWebhookRequestPipeline,
} from "./webhook-targets.js";
} from "./webhook-ingress.js";

View File

@@ -1,10 +1,7 @@
// Narrow plugin-sdk surface for the bundled zalouser plugin.
// Keep this list additive and scoped to symbols used under extensions/zalouser.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
import { createOptionalChannelSetupSurface } from "./channel-setup.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { mergeAllowlist, summarizeMapping } from "../channels/allowlists/resolve-utils.js";
@@ -36,8 +33,8 @@ export type {
ChannelStatusIssue,
} from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../channels/reply-prefix.js";
export { createTypingCallbacks } from "../channels/typing.js";
export { createChannelReplyPipeline, createReplyPrefixOptions } from "./channel-reply-pipeline.js";
export { createTypingCallbacks } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js";
export {
@@ -63,8 +60,7 @@ export {
resolveSenderScopedGroupPolicy,
} from "./group-access.js";
export { loadOutboundMediaFromUrl } from "./outbound-media.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { buildChannelSendResult } from "./channel-send-result.js";
export type { OutboundReplyPayload } from "./reply-payload.js";
export {
@@ -79,16 +75,12 @@ export { formatResolvedUnresolvedNote } from "./resolution-notes.js";
export { buildBaseAccountStatusSnapshot } from "./status-helpers.js";
export { chunkTextForOutbound } from "./text-chunking.js";
export const zalouserSetupAdapter = createOptionalChannelSetupAdapter({
const zalouserSetup = createOptionalChannelSetupSurface({
channel: "zalouser",
label: "Zalo Personal",
npmSpec: "@openclaw/zalouser",
docsPath: "/channels/zalouser",
});
export const zalouserSetupWizard = createOptionalChannelSetupWizard({
channel: "zalouser",
label: "Zalo Personal",
npmSpec: "@openclaw/zalouser",
docsPath: "/channels/zalouser",
});
export const zalouserSetupAdapter = zalouserSetup.setupAdapter;
export const zalouserSetupWizard = zalouserSetup.setupWizard;

View File

@@ -18,6 +18,38 @@ function extractAgentDefaultModelFallbacks(model: unknown): string[] | undefined
return Array.isArray(fallbacks) ? fallbacks.map((v) => String(v)) : undefined;
}
export type AgentModelAliasEntry =
| string
| {
modelRef: string;
alias?: string;
};
function normalizeAgentModelAliasEntry(entry: AgentModelAliasEntry): {
modelRef: string;
alias?: string;
} {
if (typeof entry === "string") {
return { modelRef: entry };
}
return entry;
}
export function withAgentModelAliases(
existing: Record<string, AgentModelEntryConfig> | undefined,
aliases: readonly AgentModelAliasEntry[],
): Record<string, AgentModelEntryConfig> {
const next = { ...existing };
for (const entry of aliases) {
const normalized = normalizeAgentModelAliasEntry(entry);
next[normalized.modelRef] = {
...next[normalized.modelRef],
...(normalized.alias ? { alias: next[normalized.modelRef]?.alias ?? normalized.alias } : {}),
};
}
return next;
}
export function applyOnboardAuthAgentModelsAndProviders(
cfg: OpenClawConfig,
params: {
@@ -117,6 +149,56 @@ export function applyProviderConfigWithDefaultModel(
});
}
export function applyProviderConfigWithDefaultModelPreset(
cfg: OpenClawConfig,
params: {
providerId: string;
api: ModelApi;
baseUrl: string;
defaultModel: ModelDefinitionConfig;
defaultModelId?: string;
aliases?: readonly AgentModelAliasEntry[];
primaryModelRef?: string;
},
): OpenClawConfig {
const next = applyProviderConfigWithDefaultModel(cfg, {
agentModels: withAgentModelAliases(cfg.agents?.defaults?.models, params.aliases ?? []),
providerId: params.providerId,
api: params.api,
baseUrl: params.baseUrl,
defaultModel: params.defaultModel,
defaultModelId: params.defaultModelId,
});
return params.primaryModelRef
? applyAgentDefaultModelPrimary(next, params.primaryModelRef)
: next;
}
export function applyProviderConfigWithDefaultModelsPreset(
cfg: OpenClawConfig,
params: {
providerId: string;
api: ModelApi;
baseUrl: string;
defaultModels: ModelDefinitionConfig[];
defaultModelId?: string;
aliases?: readonly AgentModelAliasEntry[];
primaryModelRef?: string;
},
): OpenClawConfig {
const next = applyProviderConfigWithDefaultModels(cfg, {
agentModels: withAgentModelAliases(cfg.agents?.defaults?.models, params.aliases ?? []),
providerId: params.providerId,
api: params.api,
baseUrl: params.baseUrl,
defaultModels: params.defaultModels,
defaultModelId: params.defaultModelId,
});
return params.primaryModelRef
? applyAgentDefaultModelPrimary(next, params.primaryModelRef)
: next;
}
export function applyProviderConfigWithModelCatalog(
cfg: OpenClawConfig,
params: {
@@ -149,6 +231,29 @@ export function applyProviderConfigWithModelCatalog(
});
}
export function applyProviderConfigWithModelCatalogPreset(
cfg: OpenClawConfig,
params: {
providerId: string;
api: ModelApi;
baseUrl: string;
catalogModels: ModelDefinitionConfig[];
aliases?: readonly AgentModelAliasEntry[];
primaryModelRef?: string;
},
): OpenClawConfig {
const next = applyProviderConfigWithModelCatalog(cfg, {
agentModels: withAgentModelAliases(cfg.agents?.defaults?.models, params.aliases ?? []),
providerId: params.providerId,
api: params.api,
baseUrl: params.baseUrl,
catalogModels: params.catalogModels,
});
return params.primaryModelRef
? applyAgentDefaultModelPrimary(next, params.primaryModelRef)
: next;
}
type ProviderModelMergeState = {
providers: Record<string, ModelProviderConfig>;
existingProvider?: ModelProviderConfig;