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/plugin-entry` for plugin registration primitives.
- `openclaw/plugin-sdk/core` for the generic shared plugin-facing contract. - `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`, - Domain subpaths such as `openclaw/plugin-sdk/channel-config-helpers`,
`openclaw/plugin-sdk/channel-config-schema`, `openclaw/plugin-sdk/channel-config-schema`,
`openclaw/plugin-sdk/channel-policy`, `openclaw/plugin-sdk/channel-policy`,
@ -961,6 +967,9 @@ authoring plugins:
Compatibility note: Compatibility note:
- Avoid the root `openclaw/plugin-sdk` barrel for new code. - 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 - 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 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 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 ```typescript
// Correct: focused subpaths // Correct: focused subpaths
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core"; 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 { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import { createOptionalChannelSetupSurface } from "openclaw/plugin-sdk/channel-setup";
import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy"; import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
// Wrong: monolithic root (lint will reject this) // Wrong: monolithic root (lint will reject this)
@ -105,17 +107,24 @@ import { ... } from "openclaw/plugin-sdk";
Common subpaths: Common subpaths:
| Subpath | Purpose | | Subpath | Purpose |
| ---------------------------------- | ------------------------------------ | | ----------------------------------- | ------------------------------------ |
| `plugin-sdk/core` | Plugin entry definitions, base types | | `plugin-sdk/core` | Plugin entry definitions, base types |
| `plugin-sdk/channel-runtime` | Channel runtime helpers | | `plugin-sdk/channel-setup` | Optional setup adapters/wizards |
| `plugin-sdk/channel-config-schema` | Config schema builders | | `plugin-sdk/channel-pairing` | DM pairing primitives |
| `plugin-sdk/channel-policy` | Group/DM policy helpers | | `plugin-sdk/channel-reply-pipeline` | Prefix + typing reply wiring |
| `plugin-sdk/setup` | Setup wizard adapters | | `plugin-sdk/channel-config-schema` | Config schema builders |
| `plugin-sdk/runtime-store` | Persistent plugin storage | | `plugin-sdk/channel-policy` | Group/DM policy helpers |
| `plugin-sdk/allow-from` | Allowlist resolution | | `plugin-sdk/secret-input` | Secret input parsing/helpers |
| `plugin-sdk/reply-payload` | Message reply types | | `plugin-sdk/webhook-ingress` | Webhook request/target helpers |
| `plugin-sdk/testing` | Test utilities | | `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 ## 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 { export {
buildSecretInputSchema, buildSecretInputSchema,
hasConfiguredSecretInput, hasConfiguredSecretInput,
normalizeResolvedSecretInputString, normalizeResolvedSecretInputString,
normalizeSecretInputString, normalizeSecretInputString,
}; } from "openclaw/plugin-sdk/secret-input";

View File

@ -6,7 +6,7 @@ import {
} from "openclaw/plugin-sdk/provider-models"; } from "openclaw/plugin-sdk/provider-models";
import { import {
applyAgentDefaultModelPrimary, applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog, applyProviderConfigWithModelCatalogPreset,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } 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.). * Registers all catalog models and sets provider aliases (chutes-fast, etc.).
*/ */
export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig { export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models }; return applyProviderConfigWithModelCatalogPreset(cfg, {
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,
providerId: "chutes", providerId: "chutes",
api: "openai-completions", api: "openai-completions",
baseUrl: CHUTES_BASE_URL, 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 { ChannelType, type RequestClient } from "@buape/carbon";
import { resolveAckReaction, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime"; import { resolveAckReaction, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
import { EmbeddedBlockChunker } 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 { shouldAckReaction as shouldAckReactionGate } from "openclaw/plugin-sdk/channel-runtime";
import { logTypingFailure, logAckFailure } 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 { recordInboundSession } from "openclaw/plugin-sdk/channel-runtime";
import { import {
createStatusReactionController, createStatusReactionController,
DEFAULT_TIMING, DEFAULT_TIMING,
type StatusReactionAdapter, type StatusReactionAdapter,
} from "openclaw/plugin-sdk/channel-runtime"; } from "openclaw/plugin-sdk/channel-runtime";
import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime"; import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime";
import { resolveDiscordPreviewStreamMode } from "openclaw/plugin-sdk/config-runtime"; import { resolveDiscordPreviewStreamMode } from "openclaw/plugin-sdk/config-runtime";
import { resolveMarkdownTableMode } 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) ? deliverTarget.slice("channel:".length)
: messageChannelId; : messageChannelId;
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({ const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg, cfg,
agentId: route.agentId, agentId: route.agentId,
channel: "discord", channel: "discord",
accountId: route.accountId, 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({ const tableMode = resolveMarkdownTableMode({
cfg, cfg,
@ -438,20 +450,6 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
}); });
const chunkMode = resolveChunkMode(cfg, "discord", accountId); 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) --- // --- Discord draft stream (edit-based preview streaming) ---
const discordStreamMode = resolveDiscordPreviewStreamMode(discordConfig); const discordStreamMode = resolveDiscordPreviewStreamMode(discordConfig);
const draftMaxChars = Math.min(textLimit, 2000); const draftMaxChars = Math.min(textLimit, 2000);
@ -597,9 +595,8 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } = const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } =
createReplyDispatcherWithTyping({ createReplyDispatcherWithTyping({
...prefixOptions, ...replyPipeline,
humanDelay: resolveHumanDelayConfig(cfg, route.agentId), humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
typingCallbacks,
deliver: async (payload: ReplyPayload, info) => { deliver: async (payload: ReplyPayload, info) => {
if (isProcessAborted(abortSignal)) { if (isProcessAborted(abortSignal)) {
return; return;
@ -715,7 +712,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
if (isProcessAborted(abortSignal)) { if (isProcessAborted(abortSignal)) {
return; return;
} }
await typingCallbacks.onReplyStart(); await replyPipeline.typingCallbacks?.onReplyStart();
await statusReactions.setThinking(); await statusReactions.setThinking();
}, },
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { import {
applyAgentDefaultModelPrimary, applyProviderConfigWithModelCatalogPreset,
applyProviderConfigWithModelCatalog,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } from "openclaw/plugin-sdk/provider-onboard";
import { import {
@ -15,26 +14,19 @@ export { MODELSTUDIO_CN_BASE_URL, MODELSTUDIO_DEFAULT_MODEL_REF, MODELSTUDIO_GLO
function applyModelStudioProviderConfigWithBaseUrl( function applyModelStudioProviderConfigWithBaseUrl(
cfg: OpenClawConfig, cfg: OpenClawConfig,
baseUrl: string, baseUrl: string,
primaryModelRef?: string,
): OpenClawConfig { ): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
const provider = buildModelStudioProvider(); const provider = buildModelStudioProvider();
for (const model of provider.models ?? []) { return applyProviderConfigWithModelCatalogPreset(cfg, {
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,
providerId: "modelstudio", providerId: "modelstudio",
api: provider.api ?? "openai-completions", api: provider.api ?? "openai-completions",
baseUrl, baseUrl,
catalogModels: provider.models ?? [], 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 { export function applyModelStudioConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary( return applyModelStudioProviderConfigWithBaseUrl(
applyModelStudioProviderConfig(cfg), cfg,
MODELSTUDIO_GLOBAL_BASE_URL,
MODELSTUDIO_DEFAULT_MODEL_REF, MODELSTUDIO_DEFAULT_MODEL_REF,
); );
} }
export function applyModelStudioConfigCn(cfg: OpenClawConfig): OpenClawConfig { export function applyModelStudioConfigCn(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary( return applyModelStudioProviderConfigWithBaseUrl(
applyModelStudioProviderConfigCn(cfg), cfg,
MODELSTUDIO_CN_BASE_URL,
MODELSTUDIO_DEFAULT_MODEL_REF, MODELSTUDIO_DEFAULT_MODEL_REF,
); );
} }

View File

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

View File

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

View File

@ -1,13 +1,6 @@
import {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../runtime-api.js";
export { export {
buildSecretInputSchema, buildSecretInputSchema,
hasConfiguredSecretInput, hasConfiguredSecretInput,
normalizeResolvedSecretInputString, normalizeResolvedSecretInputString,
normalizeSecretInputString, 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 { OPENCODE_GO_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models";
import { import {
applyAgentDefaultModelPrimary, applyAgentDefaultModelPrimary,
withAgentModelAliases,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } from "openclaw/plugin-sdk/provider-onboard";
@ -13,21 +14,19 @@ const OPENCODE_GO_ALIAS_DEFAULTS: Record<string, string> = {
}; };
export function applyOpencodeGoProviderConfig(cfg: OpenClawConfig): OpenClawConfig { 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 { return {
...cfg, ...cfg,
agents: { agents: {
...cfg.agents, ...cfg.agents,
defaults: { defaults: {
...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 { OPENCODE_ZEN_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models";
import { import {
applyAgentDefaultModelPrimary, applyAgentDefaultModelPrimary,
withAgentModelAliases,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } from "openclaw/plugin-sdk/provider-onboard";
export { OPENCODE_ZEN_DEFAULT_MODEL_REF }; export { OPENCODE_ZEN_DEFAULT_MODEL_REF };
export function applyOpencodeZenProviderConfig(cfg: OpenClawConfig): OpenClawConfig { 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 { return {
...cfg, ...cfg,
agents: { agents: {
...cfg.agents, ...cfg.agents,
defaults: { defaults: {
...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 { import {
applyAgentDefaultModelPrimary, applyProviderConfigWithDefaultModelsPreset,
applyProviderConfigWithDefaultModels,
type ModelApi, type ModelApi,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } from "openclaw/plugin-sdk/provider-onboard";
@ -12,12 +11,11 @@ import {
export const QIANFAN_DEFAULT_MODEL_REF = `qianfan/${QIANFAN_DEFAULT_MODEL_ID}`; export const QIANFAN_DEFAULT_MODEL_REF = `qianfan/${QIANFAN_DEFAULT_MODEL_ID}`;
export function applyQianfanProviderConfig(cfg: OpenClawConfig): OpenClawConfig { function resolveQianfanPreset(cfg: OpenClawConfig): {
const models = { ...cfg.agents?.defaults?.models }; api: ModelApi;
models[QIANFAN_DEFAULT_MODEL_REF] = { baseUrl: string;
...models[QIANFAN_DEFAULT_MODEL_REF], defaultModels: NonNullable<ReturnType<typeof buildQianfanProvider>["models"]>;
alias: models[QIANFAN_DEFAULT_MODEL_REF]?.alias ?? "QIANFAN", } {
};
const defaultProvider = buildQianfanProvider(); const defaultProvider = buildQianfanProvider();
const existingProvider = cfg.models?.providers?.qianfan as const existingProvider = cfg.models?.providers?.qianfan as
| { | {
@ -27,22 +25,35 @@ export function applyQianfanProviderConfig(cfg: OpenClawConfig): OpenClawConfig
| undefined; | undefined;
const existingBaseUrl = const existingBaseUrl =
typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl.trim() : ""; typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl.trim() : "";
const resolvedBaseUrl = existingBaseUrl || QIANFAN_BASE_URL; const api =
const resolvedApi =
typeof existingProvider?.api === "string" typeof existingProvider?.api === "string"
? (existingProvider.api as ModelApi) ? (existingProvider.api as ModelApi)
: "openai-completions"; : "openai-completions";
return applyProviderConfigWithDefaultModels(cfg, { return {
agentModels: models, api,
providerId: "qianfan", baseUrl: existingBaseUrl || QIANFAN_BASE_URL,
api: resolvedApi,
baseUrl: resolvedBaseUrl,
defaultModels: defaultProvider.models ?? [], 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, defaultModelId: QIANFAN_DEFAULT_MODEL_ID,
aliases: [{ modelRef: QIANFAN_DEFAULT_MODEL_REF, alias: "QIANFAN" }],
primaryModelRef,
}); });
} }
export function applyQianfanConfig(cfg: OpenClawConfig): OpenClawConfig { export function applyQianfanProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyQianfanProviderConfig(cfg), QIANFAN_DEFAULT_MODEL_REF); 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 { 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 { removeAckReactionAfterReply } from "openclaw/plugin-sdk/channel-runtime";
import { logAckFailure, logTypingFailure } 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 { resolveStorePath, updateLastRoute } from "openclaw/plugin-sdk/config-runtime";
import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/infra-runtime"; import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/infra-runtime";
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload"; 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 typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
const typingReaction = ctx.typingReaction; const typingReaction = ctx.typingReaction;
const typingCallbacks = createTypingCallbacks({ const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
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({
cfg, cfg,
agentId: route.agentId, agentId: route.agentId,
channel: "slack", channel: "slack",
accountId: route.accountId, 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({ const slackStreaming = resolveSlackStreamingConfig({
@ -299,9 +297,8 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
}; };
const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({ const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
...prefixOptions, ...replyPipeline,
humanDelay: resolveHumanDelayConfig(cfg, route.agentId), humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
typingCallbacks,
deliver: async (payload) => { deliver: async (payload) => {
if (useStreaming) { if (useStreaming) {
await deliverWithStreaming(payload); await deliverWithStreaming(payload);
@ -367,7 +364,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
}, },
onError: (err, info) => { onError: (err, info) => {
runtime.error?.(danger(`slack ${info.kind} reply failed: ${String(err)}`)); 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, SYNTHETIC_MODEL_CATALOG,
} from "openclaw/plugin-sdk/provider-models"; } from "openclaw/plugin-sdk/provider-models";
import { import {
applyAgentDefaultModelPrimary, applyProviderConfigWithModelCatalogPreset,
applyProviderConfigWithModelCatalog,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } from "openclaw/plugin-sdk/provider-onboard";
export { SYNTHETIC_DEFAULT_MODEL_REF }; export { SYNTHETIC_DEFAULT_MODEL_REF };
export function applySyntheticProviderConfig(cfg: OpenClawConfig): OpenClawConfig { function applySyntheticPreset(cfg: OpenClawConfig, primaryModelRef?: string): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models }; return applyProviderConfigWithModelCatalogPreset(cfg, {
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,
providerId: "synthetic", providerId: "synthetic",
api: "anthropic-messages", api: "anthropic-messages",
baseUrl: SYNTHETIC_BASE_URL, baseUrl: SYNTHETIC_BASE_URL,
catalogModels: SYNTHETIC_MODEL_CATALOG.map(buildSyntheticModelDefinition), catalogModels: SYNTHETIC_MODEL_CATALOG.map(buildSyntheticModelDefinition),
aliases: [{ modelRef: SYNTHETIC_DEFAULT_MODEL_REF, alias: "MiniMax M2.5" }],
primaryModelRef,
}); });
} }
export function applySyntheticConfig(cfg: OpenClawConfig): OpenClawConfig { export function applySyntheticProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary( return applySyntheticPreset(cfg);
applySyntheticProviderConfig(cfg), }
SYNTHETIC_DEFAULT_MODEL_REF,
); export function applySyntheticConfig(cfg: OpenClawConfig): OpenClawConfig {
return applySyntheticPreset(cfg, SYNTHETIC_DEFAULT_MODEL_REF);
} }

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { import {
applyAgentDefaultModelPrimary, applyProviderConfigWithDefaultModelsPreset,
applyProviderConfigWithDefaultModels,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } from "openclaw/plugin-sdk/provider-onboard";
import { XAI_BASE_URL, XAI_DEFAULT_MODEL_ID } from "./model-definitions.js"; 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( function applyXaiProviderConfigWithApi(
cfg: OpenClawConfig, cfg: OpenClawConfig,
api: "openai-completions" | "openai-responses", api: "openai-completions" | "openai-responses",
primaryModelRef?: string,
): OpenClawConfig { ): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models }; return applyProviderConfigWithDefaultModelsPreset(cfg, {
models[XAI_DEFAULT_MODEL_REF] = {
...models[XAI_DEFAULT_MODEL_REF],
alias: models[XAI_DEFAULT_MODEL_REF]?.alias ?? "Grok",
};
return applyProviderConfigWithDefaultModels(cfg, {
agentModels: models,
providerId: "xai", providerId: "xai",
api, api,
baseUrl: XAI_BASE_URL, baseUrl: XAI_BASE_URL,
defaultModels: buildXaiCatalogModels(), defaultModels: buildXaiCatalogModels(),
defaultModelId: XAI_DEFAULT_MODEL_ID, 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 { 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 { import {
applyAgentDefaultModelPrimary, applyProviderConfigWithModelCatalogPreset,
applyProviderConfigWithModelCatalog,
type OpenClawConfig, type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard"; } from "openclaw/plugin-sdk/provider-onboard";
import { import {
@ -19,32 +18,35 @@ const ZAI_DEFAULT_MODELS = [
buildZaiModelDefinition({ id: "glm-4.7-flashx" }), 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( export function applyZaiProviderConfig(
cfg: OpenClawConfig, cfg: OpenClawConfig,
params?: { endpoint?: string; modelId?: string }, params?: { endpoint?: string; modelId?: string },
): OpenClawConfig { ): OpenClawConfig {
const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID; return applyZaiPreset(cfg, params);
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,
});
} }
export function applyZaiConfig( export function applyZaiConfig(
@ -53,5 +55,5 @@ export function applyZaiConfig(
): OpenClawConfig { ): OpenClawConfig {
const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID; const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID;
const modelRef = modelId === ZAI_DEFAULT_MODEL_ID ? ZAI_DEFAULT_MODEL_REF : `zai/${modelId}`; 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 { resolveZaloProxyFetch } from "./proxy.js";
import type { MarkdownTableMode, OpenClawConfig, OutboundReplyPayload } from "./runtime-api.js"; import type { MarkdownTableMode, OpenClawConfig, OutboundReplyPayload } from "./runtime-api.js";
import { import {
createTypingCallbacks, createChannelPairingController,
createScopedPairingAccess, createChannelReplyPipeline,
createReplyPrefixOptions,
deliverTextOrMediaReply, deliverTextOrMediaReply,
issuePairingChallenge,
resolveWebhookPath, resolveWebhookPath,
logTypingFailure, logTypingFailure,
resolveDefaultGroupPolicy, resolveDefaultGroupPolicy,
@ -330,7 +328,7 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
statusSink, statusSink,
fetcher, fetcher,
} = params; } = params;
const pairing = createScopedPairingAccess({ const pairing = createChannelPairingController({
core, core,
channel: "zalo", channel: "zalo",
accountId: account.accountId, accountId: account.accountId,
@ -406,12 +404,10 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
} }
if (directDmOutcome === "unauthorized") { if (directDmOutcome === "unauthorized") {
if (dmPolicy === "pairing") { if (dmPolicy === "pairing") {
await issuePairingChallenge({ await pairing.issueChallenge({
channel: "zalo",
senderId, senderId,
senderIdLine: `Your Zalo user id: ${senderId}`, senderIdLine: `Your Zalo user id: ${senderId}`,
meta: { name: senderName ?? undefined }, meta: { name: senderName ?? undefined },
upsertPairingRequest: pairing.upsertPairingRequest,
onCreated: () => { onCreated: () => {
logVerbose(core, runtime, `zalo pairing request sender=${senderId}`); logVerbose(core, runtime, `zalo pairing request sender=${senderId}`);
}, },
@ -507,32 +503,32 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
channel: "zalo", channel: "zalo",
accountId: account.accountId, accountId: account.accountId,
}); });
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({ const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg: config, cfg: config,
agentId: route.agentId, agentId: route.agentId,
channel: "zalo", channel: "zalo",
accountId: account.accountId, accountId: account.accountId,
}); typing: {
const typingCallbacks = createTypingCallbacks({ start: async () => {
start: async () => { await sendChatAction(
await sendChatAction( token,
token, {
{ chat_id: chatId,
chat_id: chatId, action: "typing",
action: "typing", },
}, fetcher,
fetcher, ZALO_TYPING_TIMEOUT_MS,
ZALO_TYPING_TIMEOUT_MS, );
); },
}, onStartError: (err) => {
onStartError: (err) => { logTypingFailure({
logTypingFailure({ log: (message) => logVerbose(core, runtime, message),
log: (message) => logVerbose(core, runtime, message), channel: "zalo",
channel: "zalo", action: "start",
action: "start", target: chatId,
target: chatId, error: err,
error: err, });
}); },
}, },
}); });
@ -540,8 +536,7 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
ctx: ctxPayload, ctx: ctxPayload,
cfg: config, cfg: config,
dispatcherOptions: { dispatcherOptions: {
...prefixOptions, ...replyPipeline,
typingCallbacks,
deliver: async (payload) => { deliver: async (payload) => {
await deliverZaloReply({ await deliverZaloReply({
payload, payload,

View File

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

View File

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

View File

@ -78,6 +78,10 @@
"types": "./dist/plugin-sdk/setup.d.ts", "types": "./dist/plugin-sdk/setup.d.ts",
"default": "./dist/plugin-sdk/setup.js" "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": { "./plugin-sdk/setup-tools": {
"types": "./dist/plugin-sdk/setup-tools.d.ts", "types": "./dist/plugin-sdk/setup-tools.d.ts",
"default": "./dist/plugin-sdk/setup-tools.js" "default": "./dist/plugin-sdk/setup-tools.js"
@ -94,6 +98,10 @@
"types": "./dist/plugin-sdk/reply-payload.d.ts", "types": "./dist/plugin-sdk/reply-payload.d.ts",
"default": "./dist/plugin-sdk/reply-payload.js" "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": { "./plugin-sdk/channel-runtime": {
"types": "./dist/plugin-sdk/channel-runtime.d.ts", "types": "./dist/plugin-sdk/channel-runtime.d.ts",
"default": "./dist/plugin-sdk/channel-runtime.js" "default": "./dist/plugin-sdk/channel-runtime.js"
@ -254,6 +262,10 @@
"types": "./dist/plugin-sdk/channel-lifecycle.d.ts", "types": "./dist/plugin-sdk/channel-lifecycle.d.ts",
"default": "./dist/plugin-sdk/channel-lifecycle.js" "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": { "./plugin-sdk/channel-policy": {
"types": "./dist/plugin-sdk/channel-policy.d.ts", "types": "./dist/plugin-sdk/channel-policy.d.ts",
"default": "./dist/plugin-sdk/channel-policy.js" "default": "./dist/plugin-sdk/channel-policy.js"
@ -334,6 +346,10 @@
"types": "./dist/plugin-sdk/request-url.d.ts", "types": "./dist/plugin-sdk/request-url.d.ts",
"default": "./dist/plugin-sdk/request-url.js" "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": { "./plugin-sdk/webhook-path": {
"types": "./dist/plugin-sdk/webhook-path.d.ts", "types": "./dist/plugin-sdk/webhook-path.d.ts",
"default": "./dist/plugin-sdk/webhook-path.js" "default": "./dist/plugin-sdk/webhook-path.js"
@ -342,6 +358,10 @@
"types": "./dist/plugin-sdk/runtime-store.d.ts", "types": "./dist/plugin-sdk/runtime-store.d.ts",
"default": "./dist/plugin-sdk/runtime-store.js" "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": { "./plugin-sdk/web-media": {
"types": "./dist/plugin-sdk/web-media.d.ts", "types": "./dist/plugin-sdk/web-media.d.ts",
"default": "./dist/plugin-sdk/web-media.js" "default": "./dist/plugin-sdk/web-media.js"

View File

@ -9,10 +9,12 @@
"runtime", "runtime",
"runtime-env", "runtime-env",
"setup", "setup",
"channel-setup",
"setup-tools", "setup-tools",
"config-runtime", "config-runtime",
"reply-runtime", "reply-runtime",
"reply-payload", "reply-payload",
"channel-reply-pipeline",
"channel-runtime", "channel-runtime",
"interactive-runtime", "interactive-runtime",
"infra-runtime", "infra-runtime",
@ -53,6 +55,7 @@
"channel-config-helpers", "channel-config-helpers",
"channel-config-schema", "channel-config-schema",
"channel-lifecycle", "channel-lifecycle",
"channel-pairing",
"channel-policy", "channel-policy",
"channel-send-result", "channel-send-result",
"group-access", "group-access",
@ -73,8 +76,10 @@
"reply-history", "reply-history",
"media-understanding", "media-understanding",
"request-url", "request-url",
"webhook-ingress",
"webhook-path", "webhook-path",
"runtime-store", "runtime-store",
"secret-input",
"web-media", "web-media",
"speech", "speech",
"state-paths", "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 { AgentModelEntryConfig } from "../config/types.agent-defaults.js";
import type { ModelDefinitionConfig } from "../config/types.models.js"; import type { ModelDefinitionConfig } from "../config/types.models.js";
import { import {
applyProviderConfigWithDefaultModelPreset,
applyProviderConfigWithModelCatalogPreset,
applyProviderConfigWithDefaultModel, applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModels, applyProviderConfigWithDefaultModels,
applyProviderConfigWithModelCatalog, applyProviderConfigWithModelCatalog,
withAgentModelAliases,
} from "../plugins/provider-onboarding-config.js"; } from "../plugins/provider-onboarding-config.js";
function makeModel(id: string): ModelDefinitionConfig { 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"]); 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"; } from "../channels/plugins/types.adapters.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { createReplyPrefixContext } from "../channels/reply-prefix.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 type { OpenClawConfig as ClawdbotConfig, OpenClawConfig } from "../config/config.js";
export { export {
resolveAllowlistProviderRuntimeGroupPolicy, resolveAllowlistProviderRuntimeGroupPolicy,
@ -47,13 +47,13 @@ export {
warnMissingProviderGroupPolicyFallbackOnce, warnMissingProviderGroupPolicyFallbackOnce,
} from "../config/runtime-group-policy.js"; } from "../config/runtime-group-policy.js";
export type { DmPolicy, GroupToolPolicyConfig } from "../config/types.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 { export {
buildSecretInputSchema,
hasConfiguredSecretInput, hasConfiguredSecretInput,
normalizeResolvedSecretInputString, normalizeResolvedSecretInputString,
normalizeSecretInputString, normalizeSecretInputString,
} from "../config/types.secrets.js"; } from "./secret-input.js";
export { buildSecretInputSchema } from "./secret-input-schema.js";
export { createDedupeCache } from "../infra/dedupe.js"; export { createDedupeCache } from "../infra/dedupe.js";
export { installRequestBodyLimitGuard, readJsonBodyWithLimit } from "../infra/http-body.js"; export { installRequestBodyLimitGuard, readJsonBodyWithLimit } from "../infra/http-body.js";
export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.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 { feishuSetupWizard, feishuSetupAdapter } from "../../extensions/feishu/setup-api.js";
export { buildAgentMediaPayload } from "./agent-media-payload.js"; export { buildAgentMediaPayload } from "./agent-media-payload.js";
export { readJsonFileWithFallback } from "./json-store.js"; export { readJsonFileWithFallback } from "./json-store.js";
export { createScopedPairingAccess } from "./pairing-access.js"; export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { createPersistentDedupe } from "./persistent-dedupe.js"; export { createPersistentDedupe } from "./persistent-dedupe.js";
export { export {
buildBaseChannelStatusSummary, buildBaseChannelStatusSummary,
@ -85,9 +84,9 @@ export {
parseFeishuConversationId, parseFeishuConversationId,
} from "../../extensions/feishu/src/conversation-id.js"; } from "../../extensions/feishu/src/conversation-id.js";
export { export {
createFixedWindowRateLimiter,
createWebhookAnomalyTracker, createWebhookAnomalyTracker,
createFixedWindowRateLimiter,
WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_ANOMALY_COUNTER_DEFAULTS,
WEBHOOK_RATE_LIMIT_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS,
} from "./webhook-memory-guards.js"; } from "./webhook-ingress.js";
export { applyBasicWebhookRequestGuards } from "./webhook-request-guards.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. // Keep this list additive and scoped to symbols used under extensions/googlechat.
import { resolveChannelGroupRequireMention } from "./channel-policy.js"; import { resolveChannelGroupRequireMention } from "./channel-policy.js";
import { import { createOptionalChannelSetupSurface } from "./channel-setup.js";
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export { export {
createActionGate, createActionGate,
@ -49,7 +46,7 @@ export type {
} from "../channels/plugins/types.js"; } from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { getChatChannelMeta } from "../channels/registry.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 type { OpenClawConfig } from "../config/config.js";
export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js"; export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js";
export { export {
@ -71,26 +68,23 @@ export { resolveDmGroupAccessWithLists } from "../security/dm-policy-shared.js";
export { formatDocsLink } from "../terminal/links.js"; export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js"; export type { WizardPrompter } from "../wizard/prompts.js";
export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "./inbound-envelope.js"; export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "./inbound-envelope.js";
export { createScopedPairingAccess } from "./pairing-access.js"; export { createChannelPairingController, createScopedPairingAccess } from "./channel-pairing.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
export { export {
evaluateGroupRouteAccessForPolicy, evaluateGroupRouteAccessForPolicy,
resolveSenderScopedGroupPolicy, resolveSenderScopedGroupPolicy,
} from "./group-access.js"; } from "./group-access.js";
export { extractToolSend } from "./tool-send.js"; export { extractToolSend } from "./tool-send.js";
export { resolveWebhookPath } from "./webhook-path.js";
export type { WebhookInFlightLimiter } from "./webhook-request-guards.js";
export { export {
beginWebhookRequestPipelineOrReject, beginWebhookRequestPipelineOrReject,
createWebhookInFlightLimiter, createWebhookInFlightLimiter,
readJsonWebhookBodyOrReject, readJsonWebhookBodyOrReject,
} from "./webhook-request-guards.js";
export {
registerWebhookTargetWithPluginRoute, registerWebhookTargetWithPluginRoute,
resolveWebhookTargets, resolveWebhookPath,
resolveWebhookTargetWithAuthOrReject, resolveWebhookTargetWithAuthOrReject,
resolveWebhookTargets,
type WebhookInFlightLimiter,
withResolvedWebhookRequestPipeline, withResolvedWebhookRequestPipeline,
} from "./webhook-targets.js"; } from "./webhook-ingress.js";
type GoogleChatGroupContext = { type GoogleChatGroupContext = {
cfg: import("../config/config.js").OpenClawConfig; cfg: import("../config/config.js").OpenClawConfig;
@ -107,16 +101,12 @@ export function resolveGoogleChatGroupRequireMention(params: GoogleChatGroupCont
}); });
} }
export const googlechatSetupAdapter = createOptionalChannelSetupAdapter({ const googlechatSetup = createOptionalChannelSetupSurface({
channel: "googlechat", channel: "googlechat",
label: "Google Chat", label: "Google Chat",
npmSpec: "@openclaw/googlechat", npmSpec: "@openclaw/googlechat",
docsPath: "/channels/googlechat", docsPath: "/channels/googlechat",
}); });
export const googlechatSetupWizard = createOptionalChannelSetupWizard({ export const googlechatSetupAdapter = googlechatSetup.setupAdapter;
channel: "googlechat", export const googlechatSetupWizard = googlechatSetup.setupWizard;
label: "Google Chat",
npmSpec: "@openclaw/googlechat",
docsPath: "/channels/googlechat",
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,8 +9,13 @@ export type {
export { export {
applyAgentDefaultModelPrimary, applyAgentDefaultModelPrimary,
applyOnboardAuthAgentModelsAndProviders, applyOnboardAuthAgentModelsAndProviders,
applyProviderConfigWithDefaultModelPreset,
applyProviderConfigWithDefaultModelsPreset,
applyProviderConfigWithDefaultModel, applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModels, applyProviderConfigWithDefaultModels,
applyProviderConfigWithModelCatalogPreset,
applyProviderConfigWithModelCatalog, applyProviderConfigWithModelCatalog,
withAgentModelAliases,
} from "../plugins/provider-onboarding-config.js"; } from "../plugins/provider-onboarding-config.js";
export type { AgentModelAliasEntry } from "../plugins/provider-onboarding-config.js";
export { ensureModelAllowlistEntry } from "../plugins/provider-model-allowlist.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 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 channelRuntimeSdk from "openclaw/plugin-sdk/channel-runtime";
import * as channelSendResultSdk from "openclaw/plugin-sdk/channel-send-result"; 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 * as coreSdk from "openclaw/plugin-sdk/core";
import type { import type {
ChannelMessageActionContext as CoreChannelMessageActionContext, 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 routingSdk from "openclaw/plugin-sdk/routing";
import * as runtimeSdk from "openclaw/plugin-sdk/runtime"; import * as runtimeSdk from "openclaw/plugin-sdk/runtime";
import * as sandboxSdk from "openclaw/plugin-sdk/sandbox"; 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 selfHostedProviderSetupSdk from "openclaw/plugin-sdk/self-hosted-provider-setup";
import * as setupSdk from "openclaw/plugin-sdk/setup"; import * as setupSdk from "openclaw/plugin-sdk/setup";
import * as slackSdk from "openclaw/plugin-sdk/slack"; import * as slackSdk from "openclaw/plugin-sdk/slack";
import * as telegramSdk from "openclaw/plugin-sdk/telegram"; import * as telegramSdk from "openclaw/plugin-sdk/telegram";
import * as testingSdk from "openclaw/plugin-sdk/testing"; 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 whatsappSdk from "openclaw/plugin-sdk/whatsapp";
import * as whatsappActionRuntimeSdk from "openclaw/plugin-sdk/whatsapp-action-runtime"; import * as whatsappActionRuntimeSdk from "openclaw/plugin-sdk/whatsapp-action-runtime";
import * as whatsappLoginQrSdk from "openclaw/plugin-sdk/whatsapp-login-qr"; 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"); 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", () => { it("exports channel send-result helpers from the dedicated subpath", () => {
expect(typeof channelSendResultSdk.attachChannelToResult).toBe("function"); expect(typeof channelSendResultSdk.attachChannelToResult).toBe("function");
expect(typeof channelSendResultSdk.buildChannelSendResult).toBe("function"); expect(typeof channelSendResultSdk.buildChannelSendResult).toBe("function");
@ -162,6 +182,18 @@ describe("plugin-sdk subpath exports", () => {
expect(typeof sandboxSdk.runPluginCommandWithTimeout).toBe("function"); 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", () => { it("exports shared core types used by bundled channels", () => {
expectTypeOf<CoreOpenClawPluginApi>().toMatchTypeOf<OpenClawPluginApi>(); expectTypeOf<CoreOpenClawPluginApi>().toMatchTypeOf<OpenClawPluginApi>();
expectTypeOf<CorePluginRuntime>().toMatchTypeOf<PluginRuntime>(); expectTypeOf<CorePluginRuntime>().toMatchTypeOf<PluginRuntime>();

View File

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

View File

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

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

View File

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

View File

@ -18,6 +18,38 @@ function extractAgentDefaultModelFallbacks(model: unknown): string[] | undefined
return Array.isArray(fallbacks) ? fallbacks.map((v) => String(v)) : 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( export function applyOnboardAuthAgentModelsAndProviders(
cfg: OpenClawConfig, cfg: OpenClawConfig,
params: { 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( export function applyProviderConfigWithModelCatalog(
cfg: OpenClawConfig, cfg: OpenClawConfig,
params: { 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 = { type ProviderModelMergeState = {
providers: Record<string, ModelProviderConfig>; providers: Record<string, ModelProviderConfig>;
existingProvider?: ModelProviderConfig; existingProvider?: ModelProviderConfig;