mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-18 05:20:48 +00:00
refactor: dedupe bundled plugin entrypoints
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
|
||||
const PROVIDER_ID = "amazon-bedrock";
|
||||
const CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
|
||||
|
||||
const amazonBedrockPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Amazon Bedrock Provider",
|
||||
description: "Bundled Amazon Bedrock provider policy plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Amazon Bedrock",
|
||||
@@ -18,6 +17,4 @@ const amazonBedrockPlugin = {
|
||||
CLAUDE_46_MODEL_RE.test(modelId.trim()) ? "adaptive" : undefined,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default amazonBedrockPlugin;
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
||||
import { parseDurationMs } from "openclaw/plugin-sdk/cli-runtime";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderResolveDynamicModelContext,
|
||||
type ProviderRuntimeModel,
|
||||
@@ -312,12 +311,11 @@ async function runAnthropicSetupTokenNonInteractive(ctx: {
|
||||
});
|
||||
}
|
||||
|
||||
const anthropicPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Anthropic Provider",
|
||||
description: "Bundled Anthropic provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Anthropic",
|
||||
@@ -399,6 +397,4 @@ const anthropicPlugin = {
|
||||
});
|
||||
api.registerMediaUnderstandingProvider(anthropicMediaUnderstandingProvider);
|
||||
},
|
||||
};
|
||||
|
||||
export default anthropicPlugin;
|
||||
});
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
createPluginBackedWebSearchProvider,
|
||||
getTopLevelCredentialValue,
|
||||
setTopLevelCredentialValue,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
|
||||
const bravePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "brave",
|
||||
name: "Brave Plugin",
|
||||
description: "Bundled Brave plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerWebSearchProvider(
|
||||
createPluginBackedWebSearchProvider({
|
||||
id: "brave",
|
||||
@@ -26,6 +25,4 @@ const bravePlugin = {
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default bravePlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-catalog.js";
|
||||
@@ -6,12 +6,11 @@ import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-c
|
||||
const PROVIDER_ID = "byteplus";
|
||||
const BYTEPLUS_DEFAULT_MODEL_REF = "byteplus-plan/ark-code-latest";
|
||||
|
||||
const byteplusPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "BytePlus Provider",
|
||||
description: "Bundled BytePlus provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "BytePlus",
|
||||
@@ -60,6 +59,4 @@ const byteplusPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default byteplusPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
buildApiKeyCredential,
|
||||
@@ -84,12 +84,11 @@ async function resolveCloudflareGatewayMetadataInteractive(ctx: {
|
||||
return { accountId, gatewayId };
|
||||
}
|
||||
|
||||
const cloudflareAiGatewayPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Cloudflare AI Gateway Provider",
|
||||
description: "Bundled Cloudflare AI Gateway provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Cloudflare AI Gateway",
|
||||
@@ -252,6 +251,4 @@ const cloudflareAiGatewayPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default cloudflareAiGatewayPlugin;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthResult,
|
||||
} from "openclaw/plugin-sdk/copilot-proxy";
|
||||
@@ -71,12 +70,11 @@ function buildModelDefinition(modelId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
const copilotProxyPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "copilot-proxy",
|
||||
name: "Copilot Proxy",
|
||||
description: "Local Copilot Proxy (VS Code LM) provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: "copilot-proxy",
|
||||
label: "Copilot Proxy",
|
||||
@@ -157,6 +155,4 @@ const copilotProxyPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default copilotProxyPlugin;
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import os from "node:os";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/device-pair";
|
||||
import {
|
||||
approveDevicePairing,
|
||||
definePluginEntry,
|
||||
issueDeviceBootstrapToken,
|
||||
listDevicePairing,
|
||||
resolveGatewayBindUrl,
|
||||
runPluginCommandWithTimeout,
|
||||
resolveTailnetHostWithRunner,
|
||||
type OpenClawPluginApi,
|
||||
} from "openclaw/plugin-sdk/device-pair";
|
||||
import qrcode from "qrcode-terminal";
|
||||
import {
|
||||
@@ -325,226 +326,233 @@ function formatSetupInstructions(): string {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
registerPairingNotifierService(api);
|
||||
export default definePluginEntry({
|
||||
id: "device-pair",
|
||||
name: "Device Pair",
|
||||
description: "QR/bootstrap pairing helpers for OpenClaw devices",
|
||||
register(api: OpenClawPluginApi) {
|
||||
registerPairingNotifierService(api);
|
||||
|
||||
api.registerCommand({
|
||||
name: "pair",
|
||||
description: "Generate setup codes and approve device pairing requests.",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = tokens[0]?.toLowerCase() ?? "";
|
||||
api.logger.info?.(
|
||||
`device-pair: /pair invoked channel=${ctx.channel} sender=${ctx.senderId ?? "unknown"} action=${
|
||||
action || "new"
|
||||
}`,
|
||||
);
|
||||
api.registerCommand({
|
||||
name: "pair",
|
||||
description: "Generate setup codes and approve device pairing requests.",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = tokens[0]?.toLowerCase() ?? "";
|
||||
api.logger.info?.(
|
||||
`device-pair: /pair invoked channel=${ctx.channel} sender=${ctx.senderId ?? "unknown"} action=${
|
||||
action || "new"
|
||||
}`,
|
||||
);
|
||||
|
||||
if (action === "status" || action === "pending") {
|
||||
const list = await listDevicePairing();
|
||||
return { text: formatPendingRequests(list.pending) };
|
||||
}
|
||||
|
||||
if (action === "notify") {
|
||||
const notifyAction = tokens[1]?.trim().toLowerCase() ?? "status";
|
||||
return await handleNotifyCommand({
|
||||
api,
|
||||
ctx,
|
||||
action: notifyAction,
|
||||
});
|
||||
}
|
||||
|
||||
if (action === "approve") {
|
||||
const requested = tokens[1]?.trim();
|
||||
const list = await listDevicePairing();
|
||||
if (list.pending.length === 0) {
|
||||
return { text: "No pending device pairing requests." };
|
||||
if (action === "status" || action === "pending") {
|
||||
const list = await listDevicePairing();
|
||||
return { text: formatPendingRequests(list.pending) };
|
||||
}
|
||||
|
||||
let pending: (typeof list.pending)[number] | undefined;
|
||||
if (requested) {
|
||||
if (requested.toLowerCase() === "latest") {
|
||||
pending = [...list.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0];
|
||||
} else {
|
||||
pending = list.pending.find((entry) => entry.requestId === requested);
|
||||
if (action === "notify") {
|
||||
const notifyAction = tokens[1]?.trim().toLowerCase() ?? "status";
|
||||
return await handleNotifyCommand({
|
||||
api,
|
||||
ctx,
|
||||
action: notifyAction,
|
||||
});
|
||||
}
|
||||
|
||||
if (action === "approve") {
|
||||
const requested = tokens[1]?.trim();
|
||||
const list = await listDevicePairing();
|
||||
if (list.pending.length === 0) {
|
||||
return { text: "No pending device pairing requests." };
|
||||
}
|
||||
} else if (list.pending.length === 1) {
|
||||
pending = list.pending[0];
|
||||
} else {
|
||||
|
||||
let pending: (typeof list.pending)[number] | undefined;
|
||||
if (requested) {
|
||||
if (requested.toLowerCase() === "latest") {
|
||||
pending = [...list.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0];
|
||||
} else {
|
||||
pending = list.pending.find((entry) => entry.requestId === requested);
|
||||
}
|
||||
} else if (list.pending.length === 1) {
|
||||
pending = list.pending[0];
|
||||
} else {
|
||||
return {
|
||||
text:
|
||||
`${formatPendingRequests(list.pending)}\n\n` +
|
||||
"Multiple pending requests found. Approve one explicitly:\n" +
|
||||
"/pair approve <requestId>\n" +
|
||||
"Or approve the most recent:\n" +
|
||||
"/pair approve latest",
|
||||
};
|
||||
}
|
||||
if (!pending) {
|
||||
return { text: "Pairing request not found." };
|
||||
}
|
||||
const approved = await approveDevicePairing(pending.requestId);
|
||||
if (!approved) {
|
||||
return { text: "Pairing request not found." };
|
||||
}
|
||||
const label = approved.device.displayName?.trim() || approved.device.deviceId;
|
||||
const platform = approved.device.platform?.trim();
|
||||
const platformLabel = platform ? ` (${platform})` : "";
|
||||
return { text: `✅ Paired ${label}${platformLabel}.` };
|
||||
}
|
||||
|
||||
const authLabelResult = resolveAuthLabel(api.config);
|
||||
if (authLabelResult.error) {
|
||||
return { text: `Error: ${authLabelResult.error}` };
|
||||
}
|
||||
|
||||
const urlResult = await resolveGatewayUrl(api);
|
||||
if (!urlResult.url) {
|
||||
return { text: `Error: ${urlResult.error ?? "Gateway URL unavailable."}` };
|
||||
}
|
||||
|
||||
const payload: SetupPayload = {
|
||||
url: urlResult.url,
|
||||
bootstrapToken: (await issueDeviceBootstrapToken()).token,
|
||||
};
|
||||
|
||||
if (action === "qr") {
|
||||
const setupCode = encodeSetupCode(payload);
|
||||
const qrAscii = await renderQrAscii(setupCode);
|
||||
const authLabel = authLabelResult.label ?? "auth";
|
||||
|
||||
const channel = ctx.channel;
|
||||
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||
let autoNotifyArmed = false;
|
||||
|
||||
if (channel === "telegram" && target) {
|
||||
try {
|
||||
autoNotifyArmed = await armPairNotifyOnce({ api, ctx });
|
||||
} catch (err) {
|
||||
api.logger.warn?.(
|
||||
`device-pair: failed to arm one-shot pairing notify (${String(
|
||||
(err as Error)?.message ?? err,
|
||||
)})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (channel === "telegram" && target) {
|
||||
try {
|
||||
const send = api.runtime?.channel?.telegram?.sendMessageTelegram;
|
||||
if (send) {
|
||||
await send(
|
||||
target,
|
||||
["Scan this QR code with the OpenClaw iOS app:", "", "```", qrAscii, "```"].join(
|
||||
"\n",
|
||||
),
|
||||
{
|
||||
...(ctx.messageThreadId != null
|
||||
? { messageThreadId: ctx.messageThreadId }
|
||||
: {}),
|
||||
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
||||
},
|
||||
);
|
||||
return {
|
||||
text: [
|
||||
`Gateway: ${payload.url}`,
|
||||
`Auth: ${authLabel}`,
|
||||
"",
|
||||
autoNotifyArmed
|
||||
? "After scanning, wait here for the pairing request ping."
|
||||
: "After scanning, come back here and run `/pair approve` to complete pairing.",
|
||||
...(autoNotifyArmed
|
||||
? [
|
||||
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
||||
"If the ping does not arrive, run `/pair approve latest` manually.",
|
||||
]
|
||||
: []),
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
api.logger.warn?.(
|
||||
`device-pair: telegram QR send failed, falling back (${String(
|
||||
(err as Error)?.message ?? err,
|
||||
)})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Render based on channel capability
|
||||
api.logger.info?.(`device-pair: QR fallback channel=${channel} target=${target}`);
|
||||
const infoLines = [
|
||||
`Gateway: ${payload.url}`,
|
||||
`Auth: ${authLabel}`,
|
||||
"",
|
||||
autoNotifyArmed
|
||||
? "After scanning, wait here for the pairing request ping."
|
||||
: "After scanning, run `/pair approve` to complete pairing.",
|
||||
...(autoNotifyArmed
|
||||
? [
|
||||
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
||||
"If the ping does not arrive, run `/pair approve latest` manually.",
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
// WebUI + CLI/TUI: ASCII QR
|
||||
return {
|
||||
text:
|
||||
`${formatPendingRequests(list.pending)}\n\n` +
|
||||
"Multiple pending requests found. Approve one explicitly:\n" +
|
||||
"/pair approve <requestId>\n" +
|
||||
"Or approve the most recent:\n" +
|
||||
"/pair approve latest",
|
||||
text: [
|
||||
"Scan this QR code with the OpenClaw iOS app:",
|
||||
"",
|
||||
"```",
|
||||
qrAscii,
|
||||
"```",
|
||||
"",
|
||||
...infoLines,
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
if (!pending) {
|
||||
return { text: "Pairing request not found." };
|
||||
}
|
||||
const approved = await approveDevicePairing(pending.requestId);
|
||||
if (!approved) {
|
||||
return { text: "Pairing request not found." };
|
||||
}
|
||||
const label = approved.device.displayName?.trim() || approved.device.deviceId;
|
||||
const platform = approved.device.platform?.trim();
|
||||
const platformLabel = platform ? ` (${platform})` : "";
|
||||
return { text: `✅ Paired ${label}${platformLabel}.` };
|
||||
}
|
||||
|
||||
const authLabelResult = resolveAuthLabel(api.config);
|
||||
if (authLabelResult.error) {
|
||||
return { text: `Error: ${authLabelResult.error}` };
|
||||
}
|
||||
|
||||
const urlResult = await resolveGatewayUrl(api);
|
||||
if (!urlResult.url) {
|
||||
return { text: `Error: ${urlResult.error ?? "Gateway URL unavailable."}` };
|
||||
}
|
||||
|
||||
const payload: SetupPayload = {
|
||||
url: urlResult.url,
|
||||
bootstrapToken: (await issueDeviceBootstrapToken()).token,
|
||||
};
|
||||
|
||||
if (action === "qr") {
|
||||
const setupCode = encodeSetupCode(payload);
|
||||
const qrAscii = await renderQrAscii(setupCode);
|
||||
const authLabel = authLabelResult.label ?? "auth";
|
||||
|
||||
const channel = ctx.channel;
|
||||
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||
let autoNotifyArmed = false;
|
||||
const authLabel = authLabelResult.label ?? "auth";
|
||||
|
||||
if (channel === "telegram" && target) {
|
||||
try {
|
||||
autoNotifyArmed = await armPairNotifyOnce({ api, ctx });
|
||||
} catch (err) {
|
||||
api.logger.warn?.(
|
||||
`device-pair: failed to arm one-shot pairing notify (${String(
|
||||
(err as Error)?.message ?? err,
|
||||
)})`,
|
||||
const runtimeKeys = Object.keys(api.runtime ?? {});
|
||||
const channelKeys = Object.keys(api.runtime?.channel ?? {});
|
||||
api.logger.debug?.(
|
||||
`device-pair: runtime keys=${runtimeKeys.join(",") || "none"} channel keys=${
|
||||
channelKeys.join(",") || "none"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (channel === "telegram" && target) {
|
||||
try {
|
||||
const send = api.runtime?.channel?.telegram?.sendMessageTelegram;
|
||||
if (send) {
|
||||
await send(
|
||||
target,
|
||||
["Scan this QR code with the OpenClaw iOS app:", "", "```", qrAscii, "```"].join(
|
||||
"\n",
|
||||
),
|
||||
{
|
||||
...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}),
|
||||
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
||||
},
|
||||
if (!send) {
|
||||
throw new Error(
|
||||
`telegram runtime unavailable (runtime keys: ${runtimeKeys.join(",")}; channel keys: ${channelKeys.join(
|
||||
",",
|
||||
)})`,
|
||||
);
|
||||
return {
|
||||
text: [
|
||||
`Gateway: ${payload.url}`,
|
||||
`Auth: ${authLabel}`,
|
||||
"",
|
||||
autoNotifyArmed
|
||||
? "After scanning, wait here for the pairing request ping."
|
||||
: "After scanning, come back here and run `/pair approve` to complete pairing.",
|
||||
...(autoNotifyArmed
|
||||
? [
|
||||
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
||||
"If the ping does not arrive, run `/pair approve latest` manually.",
|
||||
]
|
||||
: []),
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
await send(target, formatSetupInstructions(), {
|
||||
...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}),
|
||||
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
||||
});
|
||||
api.logger.info?.(
|
||||
`device-pair: telegram split send ok target=${target} account=${ctx.accountId ?? "none"} thread=${
|
||||
ctx.messageThreadId ?? "none"
|
||||
}`,
|
||||
);
|
||||
return { text: encodeSetupCode(payload) };
|
||||
} catch (err) {
|
||||
api.logger.warn?.(
|
||||
`device-pair: telegram QR send failed, falling back (${String(
|
||||
`device-pair: telegram split send failed, falling back to single message (${String(
|
||||
(err as Error)?.message ?? err,
|
||||
)})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Render based on channel capability
|
||||
api.logger.info?.(`device-pair: QR fallback channel=${channel} target=${target}`);
|
||||
const infoLines = [
|
||||
`Gateway: ${payload.url}`,
|
||||
`Auth: ${authLabel}`,
|
||||
"",
|
||||
autoNotifyArmed
|
||||
? "After scanning, wait here for the pairing request ping."
|
||||
: "After scanning, run `/pair approve` to complete pairing.",
|
||||
...(autoNotifyArmed
|
||||
? [
|
||||
"I’ll auto-ping here when the pairing request arrives, then auto-disable.",
|
||||
"If the ping does not arrive, run `/pair approve latest` manually.",
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
// WebUI + CLI/TUI: ASCII QR
|
||||
return {
|
||||
text: [
|
||||
"Scan this QR code with the OpenClaw iOS app:",
|
||||
"",
|
||||
"```",
|
||||
qrAscii,
|
||||
"```",
|
||||
"",
|
||||
...infoLines,
|
||||
].join("\n"),
|
||||
text: formatSetupReply(payload, authLabel),
|
||||
};
|
||||
}
|
||||
|
||||
const channel = ctx.channel;
|
||||
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||
const authLabel = authLabelResult.label ?? "auth";
|
||||
|
||||
if (channel === "telegram" && target) {
|
||||
try {
|
||||
const runtimeKeys = Object.keys(api.runtime ?? {});
|
||||
const channelKeys = Object.keys(api.runtime?.channel ?? {});
|
||||
api.logger.debug?.(
|
||||
`device-pair: runtime keys=${runtimeKeys.join(",") || "none"} channel keys=${
|
||||
channelKeys.join(",") || "none"
|
||||
}`,
|
||||
);
|
||||
const send = api.runtime?.channel?.telegram?.sendMessageTelegram;
|
||||
if (!send) {
|
||||
throw new Error(
|
||||
`telegram runtime unavailable (runtime keys: ${runtimeKeys.join(",")}; channel keys: ${channelKeys.join(
|
||||
",",
|
||||
)})`,
|
||||
);
|
||||
}
|
||||
await send(target, formatSetupInstructions(), {
|
||||
...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}),
|
||||
...(ctx.accountId ? { accountId: ctx.accountId } : {}),
|
||||
});
|
||||
api.logger.info?.(
|
||||
`device-pair: telegram split send ok target=${target} account=${ctx.accountId ?? "none"} thread=${
|
||||
ctx.messageThreadId ?? "none"
|
||||
}`,
|
||||
);
|
||||
return { text: encodeSetupCode(payload) };
|
||||
} catch (err) {
|
||||
api.logger.warn?.(
|
||||
`device-pair: telegram split send failed, falling back to single message (${String(
|
||||
(err as Error)?.message ?? err,
|
||||
)})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
text: formatSetupReply(payload, authLabel),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diagnostics-otel";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/diagnostics-otel";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createDiagnosticsOtelService } from "./src/service.js";
|
||||
|
||||
const plugin = {
|
||||
export default definePluginEntry({
|
||||
id: "diagnostics-otel",
|
||||
name: "Diagnostics OpenTelemetry",
|
||||
description: "Export diagnostics events to OpenTelemetry",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerService(createDiagnosticsOtelService());
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
});
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { buildElevenLabsSpeechProvider } from "openclaw/plugin-sdk/speech";
|
||||
|
||||
const elevenLabsPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "elevenlabs",
|
||||
name: "ElevenLabs Speech",
|
||||
description: "Bundled ElevenLabs speech provider",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerSpeechProvider(buildElevenLabsSpeechProvider());
|
||||
},
|
||||
};
|
||||
|
||||
export default elevenLabsPlugin;
|
||||
});
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type AnyAgentTool,
|
||||
type OpenClawPluginApi,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry, type AnyAgentTool } from "openclaw/plugin-sdk/core";
|
||||
import { createFirecrawlScrapeTool } from "./src/firecrawl-scrape-tool.js";
|
||||
import { createFirecrawlWebSearchProvider } from "./src/firecrawl-search-provider.js";
|
||||
import { createFirecrawlSearchTool } from "./src/firecrawl-search-tool.js";
|
||||
|
||||
const firecrawlPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "firecrawl",
|
||||
name: "Firecrawl Plugin",
|
||||
description: "Bundled Firecrawl search and scrape plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerWebSearchProvider(createFirecrawlWebSearchProvider());
|
||||
api.registerTool(createFirecrawlSearchTool(api) as AnyAgentTool);
|
||||
api.registerTool(createFirecrawlScrapeTool(api) as AnyAgentTool);
|
||||
},
|
||||
};
|
||||
|
||||
export default firecrawlPlugin;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderResolveDynamicModelContext,
|
||||
type ProviderRuntimeModel,
|
||||
@@ -116,12 +115,11 @@ async function runGitHubCopilotAuth(ctx: ProviderAuthContext) {
|
||||
};
|
||||
}
|
||||
|
||||
const githubCopilotPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "github-copilot",
|
||||
name: "GitHub Copilot Provider",
|
||||
description: "Bundled GitHub Copilot provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "GitHub Copilot",
|
||||
@@ -196,6 +194,4 @@ const githubCopilotPlugin = {
|
||||
await fetchCopilotUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default githubCopilotPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { buildGoogleImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import {
|
||||
@@ -14,12 +14,11 @@ import { registerGoogleGeminiCliProvider } from "./gemini-cli-provider.js";
|
||||
import { googleMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
import { isModernGoogleModel, resolveGoogle31ForwardCompatModel } from "./provider-models.js";
|
||||
|
||||
const googlePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "google",
|
||||
name: "Google Plugin",
|
||||
description: "Bundled Google plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: "google",
|
||||
label: "Google AI Studio",
|
||||
@@ -70,6 +69,4 @@ const googlePlugin = {
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default googlePlugin;
|
||||
});
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { applyHuggingfaceConfig, HUGGINGFACE_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
import { buildHuggingfaceProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "huggingface";
|
||||
|
||||
const huggingfacePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Hugging Face Provider",
|
||||
description: "Bundled Hugging Face provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Hugging Face",
|
||||
@@ -56,6 +55,4 @@ const huggingfacePlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default huggingfacePlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import {
|
||||
@@ -10,12 +10,11 @@ import { buildKilocodeProviderWithDiscovery } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "kilocode";
|
||||
|
||||
const kilocodePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Kilo Gateway Provider",
|
||||
description: "Bundled Kilo Gateway provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Kilo Gateway",
|
||||
@@ -66,6 +65,4 @@ const kilocodePlugin = {
|
||||
isCacheTtlEligible: (ctx) => ctx.modelId.startsWith("anthropic/"),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default kilocodePlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { isRecord } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { applyKimiCodeConfig, KIMI_CODING_MODEL_REF } from "./onboard.js";
|
||||
@@ -7,12 +7,11 @@ import { buildKimiCodingProvider } from "./provider-catalog.js";
|
||||
const PLUGIN_ID = "kimi";
|
||||
const PROVIDER_ID = "kimi";
|
||||
|
||||
const kimiCodingPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PLUGIN_ID,
|
||||
name: "Kimi Provider",
|
||||
description: "Bundled Kimi provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Kimi",
|
||||
@@ -82,6 +81,4 @@ const kimiCodingPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default kimiCodingPlugin;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/llm-task";
|
||||
import {
|
||||
definePluginEntry,
|
||||
type AnyAgentTool,
|
||||
type OpenClawPluginApi,
|
||||
} from "openclaw/plugin-sdk/llm-task";
|
||||
import { createLlmTaskTool } from "./src/llm-task-tool.js";
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true });
|
||||
}
|
||||
export default definePluginEntry({
|
||||
id: "llm-task",
|
||||
name: "LLM Task",
|
||||
description: "Optional tool for structured subtask execution",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import type {
|
||||
AnyAgentTool,
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginToolFactory,
|
||||
import {
|
||||
definePluginEntry,
|
||||
type AnyAgentTool,
|
||||
type OpenClawPluginApi,
|
||||
type OpenClawPluginToolFactory,
|
||||
} from "openclaw/plugin-sdk/lobster";
|
||||
import { createLobsterTool } from "./src/lobster-tool.js";
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
api.registerTool(
|
||||
((ctx) => {
|
||||
if (ctx.sandboxed) {
|
||||
return null;
|
||||
}
|
||||
return createLobsterTool(api) as AnyAgentTool;
|
||||
}) as OpenClawPluginToolFactory,
|
||||
{ optional: true },
|
||||
);
|
||||
}
|
||||
export default definePluginEntry({
|
||||
id: "lobster",
|
||||
name: "Lobster",
|
||||
description: "Optional local shell helper tools",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerTool(
|
||||
((ctx) => {
|
||||
if (ctx.sandboxed) {
|
||||
return null;
|
||||
}
|
||||
return createLobsterTool(api) as AnyAgentTool;
|
||||
}) as OpenClawPluginToolFactory,
|
||||
{ optional: true },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/memory-core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
|
||||
const memoryCorePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "memory-core",
|
||||
name: "Memory (Core)",
|
||||
description: "File-backed memory search tools and CLI",
|
||||
kind: "memory",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerTool(
|
||||
(ctx) => {
|
||||
const memorySearchTool = api.runtime.tools.createMemorySearchTool({
|
||||
@@ -33,6 +31,4 @@ const memoryCorePlugin = {
|
||||
{ commands: ["memory"] },
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default memoryCorePlugin;
|
||||
});
|
||||
|
||||
@@ -18,6 +18,18 @@ const HAS_OPENAI_KEY = Boolean(process.env.OPENAI_API_KEY);
|
||||
const liveEnabled = HAS_OPENAI_KEY && process.env.OPENCLAW_LIVE_TEST === "1";
|
||||
const describeLive = liveEnabled ? describe : describe.skip;
|
||||
|
||||
type MemoryPluginTestConfig = {
|
||||
embedding?: {
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
dimensions?: number;
|
||||
};
|
||||
dbPath?: string;
|
||||
captureMaxChars?: number;
|
||||
autoCapture?: boolean;
|
||||
autoRecall?: boolean;
|
||||
};
|
||||
|
||||
function installTmpDirHarness(params: { prefix: string }) {
|
||||
let tmpDir = "";
|
||||
let dbPath = "";
|
||||
@@ -51,7 +63,7 @@ describe("memory plugin e2e", () => {
|
||||
},
|
||||
dbPath: getDbPath(),
|
||||
...overrides,
|
||||
});
|
||||
}) as MemoryPluginTestConfig | undefined;
|
||||
}
|
||||
|
||||
test("memory plugin registers and initializes correctly", async () => {
|
||||
@@ -89,7 +101,7 @@ describe("memory plugin e2e", () => {
|
||||
apiKey: "${TEST_MEMORY_API_KEY}",
|
||||
},
|
||||
dbPath: getDbPath(),
|
||||
});
|
||||
}) as MemoryPluginTestConfig | undefined;
|
||||
|
||||
expect(config?.embedding?.apiKey).toBe("test-key-123");
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { randomUUID } from "node:crypto";
|
||||
import type * as LanceDB from "@lancedb/lancedb";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import OpenAI from "openai";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-lancedb";
|
||||
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/memory-lancedb";
|
||||
import {
|
||||
DEFAULT_CAPTURE_MAX_CHARS,
|
||||
MEMORY_CATEGORIES,
|
||||
@@ -289,7 +289,7 @@ export function detectCategory(text: string): MemoryCategory {
|
||||
// Plugin Definition
|
||||
// ============================================================================
|
||||
|
||||
const memoryPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "memory-lancedb",
|
||||
name: "Memory (LanceDB)",
|
||||
description: "LanceDB-backed long-term memory with auto-recall/capture",
|
||||
@@ -673,6 +673,4 @@ const memoryPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default memoryPlugin;
|
||||
});
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { buildMicrosoftSpeechProvider } from "openclaw/plugin-sdk/speech";
|
||||
|
||||
const microsoftPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "microsoft",
|
||||
name: "Microsoft Speech",
|
||||
description: "Bundled Microsoft speech provider",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerSpeechProvider(buildMicrosoftSpeechProvider());
|
||||
},
|
||||
};
|
||||
|
||||
export default microsoftPlugin;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
buildOauthProviderAuthResult,
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthResult,
|
||||
type ProviderCatalogContext,
|
||||
@@ -159,12 +158,11 @@ function createOAuthHandler(region: MiniMaxRegion) {
|
||||
};
|
||||
}
|
||||
|
||||
const minimaxPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: API_PROVIDER_ID,
|
||||
name: "MiniMax",
|
||||
description: "Bundled MiniMax API-key and OAuth provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: API_PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
@@ -280,6 +278,4 @@ const minimaxPlugin = {
|
||||
api.registerMediaUnderstandingProvider(minimaxMediaUnderstandingProvider);
|
||||
api.registerMediaUnderstandingProvider(minimaxPortalMediaUnderstandingProvider);
|
||||
},
|
||||
};
|
||||
|
||||
export default minimaxPlugin;
|
||||
});
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
import { applyMistralConfig, MISTRAL_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
|
||||
const PROVIDER_ID = "mistral";
|
||||
|
||||
const mistralPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Mistral Provider",
|
||||
description: "Bundled Mistral provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Mistral",
|
||||
@@ -53,6 +52,4 @@ const mistralPlugin = {
|
||||
});
|
||||
api.registerMediaUnderstandingProvider(mistralMediaUnderstandingProvider);
|
||||
},
|
||||
};
|
||||
|
||||
export default mistralPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import {
|
||||
@@ -10,12 +10,11 @@ import { buildModelStudioProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "modelstudio";
|
||||
|
||||
const modelStudioPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Model Studio Provider",
|
||||
description: "Bundled Model Studio provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Model Studio",
|
||||
@@ -89,6 +88,4 @@ const modelStudioPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default modelStudioPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import {
|
||||
@@ -20,12 +20,11 @@ import { buildMoonshotProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "moonshot";
|
||||
|
||||
const moonshotPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Moonshot Provider",
|
||||
description: "Bundled Moonshot provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Moonshot",
|
||||
@@ -108,6 +107,4 @@ const moonshotPlugin = {
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default moonshotPlugin;
|
||||
});
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { buildNvidiaProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "nvidia";
|
||||
|
||||
const nvidiaPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "NVIDIA Provider",
|
||||
description: "Bundled NVIDIA provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "NVIDIA",
|
||||
@@ -27,6 +26,4 @@ const nvidiaPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default nvidiaPlugin;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
definePluginEntry,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
@@ -15,11 +15,10 @@ async function loadProviderSetup() {
|
||||
return await import("openclaw/plugin-sdk/ollama-setup");
|
||||
}
|
||||
|
||||
const ollamaPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "ollama",
|
||||
name: "Ollama Provider",
|
||||
description: "Bundled Ollama provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
@@ -123,6 +122,4 @@ const ollamaPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default ollamaPlugin;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/open-prose";
|
||||
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/open-prose";
|
||||
|
||||
export default function register(_api: OpenClawPluginApi) {
|
||||
// OpenProse is delivered via plugin-shipped skills.
|
||||
}
|
||||
export default definePluginEntry({
|
||||
id: "open-prose",
|
||||
name: "OpenProse",
|
||||
description: "Plugin-shipped prose skills bundle",
|
||||
register(_api: OpenClawPluginApi) {
|
||||
// OpenProse is delivered via plugin-shipped skills.
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { buildOpenAIImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
||||
import { buildOpenAISpeechProvider } from "openclaw/plugin-sdk/speech";
|
||||
import { openaiMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js";
|
||||
import { buildOpenAIProvider } from "./openai-provider.js";
|
||||
|
||||
const openAIPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "openai",
|
||||
name: "OpenAI Provider",
|
||||
description: "Bundled OpenAI provider plugins",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider(buildOpenAIProvider());
|
||||
api.registerProvider(buildOpenAICodexProviderPlugin());
|
||||
api.registerSpeechProvider(buildOpenAISpeechProvider());
|
||||
api.registerMediaUnderstandingProvider(openaiMediaUnderstandingProvider);
|
||||
api.registerImageGenerationProvider(buildOpenAIImageGenerationProvider());
|
||||
},
|
||||
};
|
||||
|
||||
export default openAIPlugin;
|
||||
});
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { OPENCODE_GO_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models";
|
||||
import { applyOpencodeGoConfig } from "./onboard.js";
|
||||
|
||||
const PROVIDER_ID = "opencode-go";
|
||||
|
||||
const opencodeGoPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "OpenCode Go Provider",
|
||||
description: "Bundled OpenCode Go provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "OpenCode Go",
|
||||
@@ -53,6 +52,4 @@ const opencodeGoPlugin = {
|
||||
isModernModelRef: () => true,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default opencodeGoPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { OPENCODE_ZEN_DEFAULT_MODEL } from "openclaw/plugin-sdk/provider-models";
|
||||
import { applyOpencodeZenConfig } from "./onboard.js";
|
||||
@@ -14,12 +14,11 @@ function isModernOpencodeModel(modelId: string): boolean {
|
||||
return !lower.startsWith(MINIMAX_PREFIX);
|
||||
}
|
||||
|
||||
const opencodePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "OpenCode Zen Provider",
|
||||
description: "Bundled OpenCode Zen provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "OpenCode Zen",
|
||||
@@ -63,6 +62,4 @@ const opencodePlugin = {
|
||||
isModernModelRef: ({ modelId }) => isModernOpencodeModel(modelId),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default opencodePlugin;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
definePluginEntry,
|
||||
type ProviderResolveDynamicModelContext,
|
||||
type ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
@@ -74,12 +73,11 @@ function isOpenRouterCacheTtlModel(modelId: string): boolean {
|
||||
return OPENROUTER_CACHE_TTL_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
|
||||
}
|
||||
|
||||
const openRouterPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "openrouter",
|
||||
name: "OpenRouter Provider",
|
||||
description: "Bundled OpenRouter provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "OpenRouter",
|
||||
@@ -151,6 +149,4 @@ const openRouterPlugin = {
|
||||
isCacheTtlEligible: (ctx) => isOpenRouterCacheTtlModel(ctx.modelId),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default openRouterPlugin;
|
||||
});
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
createPluginBackedWebSearchProvider,
|
||||
getScopedCredentialValue,
|
||||
setScopedCredentialValue,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
|
||||
const perplexityPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "perplexity",
|
||||
name: "Perplexity Plugin",
|
||||
description: "Bundled Perplexity plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerWebSearchProvider(
|
||||
createPluginBackedWebSearchProvider({
|
||||
id: "perplexity",
|
||||
@@ -27,6 +26,4 @@ const perplexityPlugin = {
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default perplexityPlugin;
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("phone-control plugin", () => {
|
||||
});
|
||||
|
||||
let command: OpenClawPluginCommandDefinition | undefined;
|
||||
registerPhoneControl(
|
||||
registerPhoneControl.register(
|
||||
createApi({
|
||||
stateDir,
|
||||
getConfig: () => config,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { OpenClawPluginApi, OpenClawPluginService } from "openclaw/plugin-sdk/phone-control";
|
||||
import {
|
||||
definePluginEntry,
|
||||
type OpenClawPluginApi,
|
||||
type OpenClawPluginService,
|
||||
} from "openclaw/plugin-sdk/phone-control";
|
||||
|
||||
type ArmGroup = "camera" | "screen" | "writes" | "all";
|
||||
|
||||
@@ -283,139 +287,144 @@ function formatStatus(state: ArmStateFile | null): string {
|
||||
return `Phone control: armed (${until}).\nTemporarily allowed: ${cmdLabel}`;
|
||||
}
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
let expiryInterval: ReturnType<typeof setInterval> | null = null;
|
||||
export default definePluginEntry({
|
||||
id: "phone-control",
|
||||
name: "Phone Control",
|
||||
description: "Temporary allowlist control for phone automation commands",
|
||||
register(api: OpenClawPluginApi) {
|
||||
let expiryInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const timerService: OpenClawPluginService = {
|
||||
id: "phone-control-expiry",
|
||||
start: async (ctx) => {
|
||||
const statePath = resolveStatePath(ctx.stateDir);
|
||||
const tick = async () => {
|
||||
const state = await readArmState(statePath);
|
||||
if (!state || state.expiresAtMs == null) {
|
||||
return;
|
||||
}
|
||||
if (Date.now() < state.expiresAtMs) {
|
||||
return;
|
||||
}
|
||||
await disarmNow({
|
||||
api,
|
||||
stateDir: ctx.stateDir,
|
||||
statePath,
|
||||
reason: "expired",
|
||||
});
|
||||
};
|
||||
|
||||
// Best effort; don't crash the gateway if state is corrupt.
|
||||
await tick().catch(() => {});
|
||||
|
||||
expiryInterval = setInterval(() => {
|
||||
tick().catch(() => {});
|
||||
}, 15_000);
|
||||
expiryInterval.unref?.();
|
||||
|
||||
return;
|
||||
},
|
||||
stop: async () => {
|
||||
if (expiryInterval) {
|
||||
clearInterval(expiryInterval);
|
||||
expiryInterval = null;
|
||||
}
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
api.registerService(timerService);
|
||||
|
||||
api.registerCommand({
|
||||
name: "phone",
|
||||
description: "Arm/disarm high-risk phone node commands (camera/screen/writes).",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = tokens[0]?.toLowerCase() ?? "";
|
||||
|
||||
const stateDir = api.runtime.state.resolveStateDir();
|
||||
const statePath = resolveStatePath(stateDir);
|
||||
|
||||
if (!action || action === "help") {
|
||||
const state = await readArmState(statePath);
|
||||
return { text: `${formatStatus(state)}\n\n${formatHelp()}` };
|
||||
}
|
||||
|
||||
if (action === "status") {
|
||||
const state = await readArmState(statePath);
|
||||
return { text: formatStatus(state) };
|
||||
}
|
||||
|
||||
if (action === "disarm") {
|
||||
const res = await disarmNow({
|
||||
api,
|
||||
stateDir,
|
||||
statePath,
|
||||
reason: "manual",
|
||||
});
|
||||
if (!res.changed) {
|
||||
return { text: "Phone control: disarmed." };
|
||||
}
|
||||
const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none";
|
||||
const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none";
|
||||
return {
|
||||
text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "arm") {
|
||||
const group = parseGroup(tokens[1]);
|
||||
if (!group) {
|
||||
return { text: `Usage: /phone arm <group> [duration]\nGroups: ${formatGroupList()}` };
|
||||
}
|
||||
const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000;
|
||||
const expiresAtMs = Date.now() + durationMs;
|
||||
|
||||
const commands = resolveCommandsForGroup(group);
|
||||
const cfg = api.runtime.config.loadConfig();
|
||||
const allowSet = new Set(normalizeAllowList(cfg));
|
||||
const denySet = new Set(normalizeDenyList(cfg));
|
||||
|
||||
const addedToAllow: string[] = [];
|
||||
const removedFromDeny: string[] = [];
|
||||
for (const cmd of commands) {
|
||||
if (!allowSet.has(cmd)) {
|
||||
allowSet.add(cmd);
|
||||
addedToAllow.push(cmd);
|
||||
const timerService: OpenClawPluginService = {
|
||||
id: "phone-control-expiry",
|
||||
start: async (ctx) => {
|
||||
const statePath = resolveStatePath(ctx.stateDir);
|
||||
const tick = async () => {
|
||||
const state = await readArmState(statePath);
|
||||
if (!state || state.expiresAtMs == null) {
|
||||
return;
|
||||
}
|
||||
if (denySet.delete(cmd)) {
|
||||
removedFromDeny.push(cmd);
|
||||
if (Date.now() < state.expiresAtMs) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const next = patchConfigNodeLists(cfg, {
|
||||
allowCommands: uniqSorted([...allowSet]),
|
||||
denyCommands: uniqSorted([...denySet]),
|
||||
});
|
||||
await api.runtime.config.writeConfigFile(next);
|
||||
|
||||
await writeArmState(statePath, {
|
||||
version: STATE_VERSION,
|
||||
armedAtMs: Date.now(),
|
||||
expiresAtMs,
|
||||
group,
|
||||
armedCommands: uniqSorted(commands),
|
||||
addedToAllow: uniqSorted(addedToAllow),
|
||||
removedFromDeny: uniqSorted(removedFromDeny),
|
||||
});
|
||||
|
||||
const allowedLabel = uniqSorted(commands).join(", ");
|
||||
return {
|
||||
text:
|
||||
`Phone control: armed for ${formatDuration(durationMs)}.\n` +
|
||||
`Temporarily allowed: ${allowedLabel}\n` +
|
||||
`To disarm early: /phone disarm`,
|
||||
await disarmNow({
|
||||
api,
|
||||
stateDir: ctx.stateDir,
|
||||
statePath,
|
||||
reason: "expired",
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return { text: formatHelp() };
|
||||
},
|
||||
});
|
||||
}
|
||||
// Best effort; don't crash the gateway if state is corrupt.
|
||||
await tick().catch(() => {});
|
||||
|
||||
expiryInterval = setInterval(() => {
|
||||
tick().catch(() => {});
|
||||
}, 15_000);
|
||||
expiryInterval.unref?.();
|
||||
|
||||
return;
|
||||
},
|
||||
stop: async () => {
|
||||
if (expiryInterval) {
|
||||
clearInterval(expiryInterval);
|
||||
expiryInterval = null;
|
||||
}
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
api.registerService(timerService);
|
||||
|
||||
api.registerCommand({
|
||||
name: "phone",
|
||||
description: "Arm/disarm high-risk phone node commands (camera/screen/writes).",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = tokens[0]?.toLowerCase() ?? "";
|
||||
|
||||
const stateDir = api.runtime.state.resolveStateDir();
|
||||
const statePath = resolveStatePath(stateDir);
|
||||
|
||||
if (!action || action === "help") {
|
||||
const state = await readArmState(statePath);
|
||||
return { text: `${formatStatus(state)}\n\n${formatHelp()}` };
|
||||
}
|
||||
|
||||
if (action === "status") {
|
||||
const state = await readArmState(statePath);
|
||||
return { text: formatStatus(state) };
|
||||
}
|
||||
|
||||
if (action === "disarm") {
|
||||
const res = await disarmNow({
|
||||
api,
|
||||
stateDir,
|
||||
statePath,
|
||||
reason: "manual",
|
||||
});
|
||||
if (!res.changed) {
|
||||
return { text: "Phone control: disarmed." };
|
||||
}
|
||||
const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none";
|
||||
const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none";
|
||||
return {
|
||||
text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "arm") {
|
||||
const group = parseGroup(tokens[1]);
|
||||
if (!group) {
|
||||
return { text: `Usage: /phone arm <group> [duration]\nGroups: ${formatGroupList()}` };
|
||||
}
|
||||
const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000;
|
||||
const expiresAtMs = Date.now() + durationMs;
|
||||
|
||||
const commands = resolveCommandsForGroup(group);
|
||||
const cfg = api.runtime.config.loadConfig();
|
||||
const allowSet = new Set(normalizeAllowList(cfg));
|
||||
const denySet = new Set(normalizeDenyList(cfg));
|
||||
|
||||
const addedToAllow: string[] = [];
|
||||
const removedFromDeny: string[] = [];
|
||||
for (const cmd of commands) {
|
||||
if (!allowSet.has(cmd)) {
|
||||
allowSet.add(cmd);
|
||||
addedToAllow.push(cmd);
|
||||
}
|
||||
if (denySet.delete(cmd)) {
|
||||
removedFromDeny.push(cmd);
|
||||
}
|
||||
}
|
||||
const next = patchConfigNodeLists(cfg, {
|
||||
allowCommands: uniqSorted([...allowSet]),
|
||||
denyCommands: uniqSorted([...denySet]),
|
||||
});
|
||||
await api.runtime.config.writeConfigFile(next);
|
||||
|
||||
await writeArmState(statePath, {
|
||||
version: STATE_VERSION,
|
||||
armedAtMs: Date.now(),
|
||||
expiresAtMs,
|
||||
group,
|
||||
armedCommands: uniqSorted(commands),
|
||||
addedToAllow: uniqSorted(addedToAllow),
|
||||
removedFromDeny: uniqSorted(removedFromDeny),
|
||||
});
|
||||
|
||||
const allowedLabel = uniqSorted(commands).join(", ");
|
||||
return {
|
||||
text:
|
||||
`Phone control: armed for ${formatDuration(durationMs)}.\n` +
|
||||
`Temporarily allowed: ${allowedLabel}\n` +
|
||||
`To disarm early: /phone disarm`,
|
||||
};
|
||||
}
|
||||
|
||||
return { text: formatHelp() };
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { applyQianfanConfig, QIANFAN_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
@@ -6,12 +6,11 @@ import { buildQianfanProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "qianfan";
|
||||
|
||||
const qianfanPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Qianfan Provider",
|
||||
description: "Bundled Qianfan provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Qianfan",
|
||||
@@ -50,6 +49,4 @@ const qianfanPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default qianfanPlugin;
|
||||
});
|
||||
|
||||
@@ -2,8 +2,7 @@ import { ensureAuthProfileStore, listProfilesForProvider } from "openclaw/plugin
|
||||
import { QWEN_OAUTH_MARKER } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import {
|
||||
buildOauthProviderAuthResult,
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderCatalogContext,
|
||||
} from "openclaw/plugin-sdk/qwen-portal-auth";
|
||||
@@ -55,12 +54,11 @@ function resolveCatalog(ctx: ProviderCatalogContext) {
|
||||
};
|
||||
}
|
||||
|
||||
const qwenPortalPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "qwen-portal-auth",
|
||||
name: "Qwen OAuth",
|
||||
description: "OAuth flow for Qwen (free-tier) models",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
@@ -146,6 +144,4 @@ const qwenPortalPlugin = {
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default qwenPortalPlugin;
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
SGLANG_PROVIDER_LABEL,
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
definePluginEntry,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
@@ -16,11 +16,10 @@ async function loadProviderSetup() {
|
||||
return await import("openclaw/plugin-sdk/self-hosted-provider-setup");
|
||||
}
|
||||
|
||||
const sglangPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "sglang",
|
||||
name: "SGLang Provider",
|
||||
description: "Bundled SGLang provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
@@ -87,6 +86,4 @@ const sglangPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default sglangPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { applySyntheticConfig, SYNTHETIC_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
@@ -6,12 +6,11 @@ import { buildSyntheticProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "synthetic";
|
||||
|
||||
const syntheticPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Synthetic Provider",
|
||||
description: "Bundled Synthetic provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Synthetic",
|
||||
@@ -50,6 +49,4 @@ const syntheticPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default syntheticPlugin;
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ function createHarness(config: Record<string, unknown>) {
|
||||
command = definition;
|
||||
}),
|
||||
};
|
||||
register(api as never);
|
||||
register.register(api as never);
|
||||
if (!command) {
|
||||
throw new Error("talk-voice command not registered");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { resolveActiveTalkProviderConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/talk-voice";
|
||||
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/talk-voice";
|
||||
|
||||
function mask(s: string, keep: number = 6): string {
|
||||
const trimmed = s.trim();
|
||||
@@ -99,119 +99,124 @@ function asProviderBaseUrl(value: unknown): string | undefined {
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
api.registerCommand({
|
||||
name: "voice",
|
||||
nativeNames: {
|
||||
discord: "talkvoice",
|
||||
},
|
||||
description: "List/set Talk provider voices (affects iOS Talk playback).",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const commandLabel = resolveCommandLabel(ctx.channel);
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = (tokens[0] ?? "status").toLowerCase();
|
||||
export default definePluginEntry({
|
||||
id: "talk-voice",
|
||||
name: "Talk Voice",
|
||||
description: "Command helpers for managing Talk voice configuration",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerCommand({
|
||||
name: "voice",
|
||||
nativeNames: {
|
||||
discord: "talkvoice",
|
||||
},
|
||||
description: "List/set Talk provider voices (affects iOS Talk playback).",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const commandLabel = resolveCommandLabel(ctx.channel);
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = (tokens[0] ?? "status").toLowerCase();
|
||||
|
||||
const cfg = api.runtime.config.loadConfig();
|
||||
const active = resolveActiveTalkProviderConfig(cfg.talk);
|
||||
if (!active) {
|
||||
return {
|
||||
text:
|
||||
"Talk voice is not configured.\n\n" +
|
||||
"Missing: talk.provider and talk.providers.<provider>.\n" +
|
||||
"Set it on the gateway, then retry.",
|
||||
};
|
||||
}
|
||||
const providerId = active.provider;
|
||||
const providerLabel = resolveProviderLabel(providerId);
|
||||
const apiKey = asTrimmedString(active.config.apiKey);
|
||||
const baseUrl = asProviderBaseUrl(active.config.baseUrl);
|
||||
|
||||
const currentVoiceId =
|
||||
asTrimmedString(active.config.voiceId) || asTrimmedString(cfg.talk?.voiceId);
|
||||
|
||||
if (action === "status") {
|
||||
return {
|
||||
text:
|
||||
"Talk voice status:\n" +
|
||||
`- provider: ${providerId}\n` +
|
||||
`- talk.voiceId: ${currentVoiceId ? currentVoiceId : "(unset)"}\n` +
|
||||
`- ${providerId}.apiKey: ${apiKey ? mask(apiKey) : "(unset)"}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "list") {
|
||||
const limit = Number.parseInt(tokens[1] ?? "12", 10);
|
||||
try {
|
||||
const voices = await api.runtime.tts.listVoices({
|
||||
provider: providerId,
|
||||
cfg,
|
||||
apiKey: apiKey || undefined,
|
||||
baseUrl,
|
||||
});
|
||||
const cfg = api.runtime.config.loadConfig();
|
||||
const active = resolveActiveTalkProviderConfig(cfg.talk);
|
||||
if (!active) {
|
||||
return {
|
||||
text: formatVoiceList(voices, Number.isFinite(limit) ? limit : 12, providerId),
|
||||
text:
|
||||
"Talk voice is not configured.\n\n" +
|
||||
"Missing: talk.provider and talk.providers.<provider>.\n" +
|
||||
"Set it on the gateway, then retry.",
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return { text: `${providerLabel} voice list failed: ${message}` };
|
||||
}
|
||||
}
|
||||
const providerId = active.provider;
|
||||
const providerLabel = resolveProviderLabel(providerId);
|
||||
const apiKey = asTrimmedString(active.config.apiKey);
|
||||
const baseUrl = asProviderBaseUrl(active.config.baseUrl);
|
||||
|
||||
if (action === "set") {
|
||||
const query = tokens.slice(1).join(" ").trim();
|
||||
if (!query) {
|
||||
return { text: `Usage: ${commandLabel} set <voiceId|name>` };
|
||||
}
|
||||
let voices: SpeechVoiceOption[];
|
||||
try {
|
||||
voices = await api.runtime.tts.listVoices({
|
||||
provider: providerId,
|
||||
cfg,
|
||||
apiKey: apiKey || undefined,
|
||||
baseUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return { text: `${providerLabel} voice lookup failed: ${message}` };
|
||||
}
|
||||
const chosen = findVoice(voices, query);
|
||||
if (!chosen) {
|
||||
const hint = isLikelyVoiceId(query) ? query : `"${query}"`;
|
||||
return { text: `No voice found for ${hint}. Try: ${commandLabel} list` };
|
||||
const currentVoiceId =
|
||||
asTrimmedString(active.config.voiceId) || asTrimmedString(cfg.talk?.voiceId);
|
||||
|
||||
if (action === "status") {
|
||||
return {
|
||||
text:
|
||||
"Talk voice status:\n" +
|
||||
`- provider: ${providerId}\n` +
|
||||
`- talk.voiceId: ${currentVoiceId ? currentVoiceId : "(unset)"}\n` +
|
||||
`- ${providerId}.apiKey: ${apiKey ? mask(apiKey) : "(unset)"}`,
|
||||
};
|
||||
}
|
||||
|
||||
const nextConfig = {
|
||||
...cfg,
|
||||
talk: {
|
||||
...cfg.talk,
|
||||
provider: providerId,
|
||||
providers: {
|
||||
...(cfg.talk?.providers ?? {}),
|
||||
[providerId]: {
|
||||
...(cfg.talk?.providers?.[providerId] ?? {}),
|
||||
voiceId: chosen.id,
|
||||
if (action === "list") {
|
||||
const limit = Number.parseInt(tokens[1] ?? "12", 10);
|
||||
try {
|
||||
const voices = await api.runtime.tts.listVoices({
|
||||
provider: providerId,
|
||||
cfg,
|
||||
apiKey: apiKey || undefined,
|
||||
baseUrl,
|
||||
});
|
||||
return {
|
||||
text: formatVoiceList(voices, Number.isFinite(limit) ? limit : 12, providerId),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return { text: `${providerLabel} voice list failed: ${message}` };
|
||||
}
|
||||
}
|
||||
|
||||
if (action === "set") {
|
||||
const query = tokens.slice(1).join(" ").trim();
|
||||
if (!query) {
|
||||
return { text: `Usage: ${commandLabel} set <voiceId|name>` };
|
||||
}
|
||||
let voices: SpeechVoiceOption[];
|
||||
try {
|
||||
voices = await api.runtime.tts.listVoices({
|
||||
provider: providerId,
|
||||
cfg,
|
||||
apiKey: apiKey || undefined,
|
||||
baseUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return { text: `${providerLabel} voice lookup failed: ${message}` };
|
||||
}
|
||||
const chosen = findVoice(voices, query);
|
||||
if (!chosen) {
|
||||
const hint = isLikelyVoiceId(query) ? query : `"${query}"`;
|
||||
return { text: `No voice found for ${hint}. Try: ${commandLabel} list` };
|
||||
}
|
||||
|
||||
const nextConfig = {
|
||||
...cfg,
|
||||
talk: {
|
||||
...cfg.talk,
|
||||
provider: providerId,
|
||||
providers: {
|
||||
...(cfg.talk?.providers ?? {}),
|
||||
[providerId]: {
|
||||
...(cfg.talk?.providers?.[providerId] ?? {}),
|
||||
voiceId: chosen.id,
|
||||
},
|
||||
},
|
||||
...(providerId === "elevenlabs" ? { voiceId: chosen.id } : {}),
|
||||
},
|
||||
...(providerId === "elevenlabs" ? { voiceId: chosen.id } : {}),
|
||||
},
|
||||
};
|
||||
await api.runtime.config.writeConfigFile(nextConfig);
|
||||
|
||||
const name = (chosen.name ?? "").trim() || "(unnamed)";
|
||||
return { text: `✅ ${providerLabel} Talk voice set to ${name}\n${chosen.id}` };
|
||||
}
|
||||
|
||||
return {
|
||||
text: [
|
||||
"Voice commands:",
|
||||
"",
|
||||
`${commandLabel} status`,
|
||||
`${commandLabel} list [limit]`,
|
||||
`${commandLabel} set <voiceId|name>`,
|
||||
].join("\n"),
|
||||
};
|
||||
await api.runtime.config.writeConfigFile(nextConfig);
|
||||
|
||||
const name = (chosen.name ?? "").trim() || "(unnamed)";
|
||||
return { text: `✅ ${providerLabel} Talk voice set to ${name}\n${chosen.id}` };
|
||||
}
|
||||
|
||||
return {
|
||||
text: [
|
||||
"Voice commands:",
|
||||
"",
|
||||
`${commandLabel} status`,
|
||||
`${commandLabel} list [limit]`,
|
||||
`${commandLabel} set <voiceId|name>`,
|
||||
].join("\n"),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ describe("thread-ownership plugin", () => {
|
||||
});
|
||||
|
||||
it("registers message_received and message_sending hooks", () => {
|
||||
register(api as any);
|
||||
register.register(api as any);
|
||||
|
||||
expect(api.on).toHaveBeenCalledTimes(2);
|
||||
expect(api.on).toHaveBeenCalledWith("message_received", expect.any(Function));
|
||||
@@ -48,7 +48,7 @@ describe("thread-ownership plugin", () => {
|
||||
|
||||
describe("message_sending", () => {
|
||||
beforeEach(() => {
|
||||
register(api as any);
|
||||
register.register(api as any);
|
||||
});
|
||||
|
||||
async function sendSlackThreadMessage() {
|
||||
@@ -120,7 +120,7 @@ describe("thread-ownership plugin", () => {
|
||||
|
||||
describe("message_received @-mention tracking", () => {
|
||||
beforeEach(() => {
|
||||
register(api as any);
|
||||
register.register(api as any);
|
||||
});
|
||||
|
||||
it("tracks @-mentions and skips ownership check for mentioned threads", async () => {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/thread-ownership";
|
||||
import {
|
||||
definePluginEntry,
|
||||
type OpenClawConfig,
|
||||
type OpenClawPluginApi,
|
||||
} from "openclaw/plugin-sdk/thread-ownership";
|
||||
|
||||
type ThreadOwnershipConfig = {
|
||||
forwarderUrl?: string;
|
||||
@@ -39,95 +43,79 @@ function resolveOwnershipAgent(config: OpenClawConfig): { id: string; name: stri
|
||||
return { id, name };
|
||||
}
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
const pluginCfg = (api.pluginConfig ?? {}) as ThreadOwnershipConfig;
|
||||
const forwarderUrl = (
|
||||
pluginCfg.forwarderUrl ??
|
||||
process.env.SLACK_FORWARDER_URL ??
|
||||
"http://slack-forwarder:8750"
|
||||
).replace(/\/$/, "");
|
||||
export default definePluginEntry({
|
||||
id: "thread-ownership",
|
||||
name: "Thread Ownership",
|
||||
description: "Slack thread claim coordination for multi-agent setups",
|
||||
register(api: OpenClawPluginApi) {
|
||||
const pluginCfg = (api.pluginConfig ?? {}) as ThreadOwnershipConfig;
|
||||
const forwarderUrl = (
|
||||
pluginCfg.forwarderUrl ??
|
||||
process.env.SLACK_FORWARDER_URL ??
|
||||
"http://slack-forwarder:8750"
|
||||
).replace(/\/$/, "");
|
||||
|
||||
const abTestChannels = new Set(
|
||||
pluginCfg.abTestChannels ??
|
||||
process.env.THREAD_OWNERSHIP_CHANNELS?.split(",").filter(Boolean) ??
|
||||
[],
|
||||
);
|
||||
const abTestChannels = new Set(
|
||||
pluginCfg.abTestChannels ??
|
||||
process.env.THREAD_OWNERSHIP_CHANNELS?.split(",").filter(Boolean) ??
|
||||
[],
|
||||
);
|
||||
|
||||
const { id: agentId, name: agentName } = resolveOwnershipAgent(api.config);
|
||||
const botUserId = process.env.SLACK_BOT_USER_ID ?? "";
|
||||
const { id: agentId, name: agentName } = resolveOwnershipAgent(api.config);
|
||||
const botUserId = process.env.SLACK_BOT_USER_ID ?? "";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// message_received: track @-mentions so the agent can reply even if it
|
||||
// doesn't own the thread.
|
||||
// ---------------------------------------------------------------------------
|
||||
api.on("message_received", async (event, ctx) => {
|
||||
if (ctx.channelId !== "slack") return;
|
||||
api.on("message_received", async (event, ctx) => {
|
||||
if (ctx.channelId !== "slack") return;
|
||||
|
||||
const text = event.content ?? "";
|
||||
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
||||
const channelId = (event.metadata?.channelId as string) ?? ctx.conversationId ?? "";
|
||||
const text = event.content ?? "";
|
||||
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
||||
const channelId = (event.metadata?.channelId as string) ?? ctx.conversationId ?? "";
|
||||
if (!threadTs || !channelId) return;
|
||||
|
||||
if (!threadTs || !channelId) return;
|
||||
const mentioned =
|
||||
(agentName && text.includes(`@${agentName}`)) ||
|
||||
(botUserId && text.includes(`<@${botUserId}>`));
|
||||
if (mentioned) {
|
||||
cleanExpiredMentions();
|
||||
mentionedThreads.set(`${channelId}:${threadTs}`, Date.now());
|
||||
}
|
||||
});
|
||||
|
||||
// Check if this agent was @-mentioned.
|
||||
const mentioned =
|
||||
(agentName && text.includes(`@${agentName}`)) ||
|
||||
(botUserId && text.includes(`<@${botUserId}>`));
|
||||
api.on("message_sending", async (event, ctx) => {
|
||||
if (ctx.channelId !== "slack") return;
|
||||
|
||||
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
||||
const channelId = (event.metadata?.channelId as string) ?? event.to;
|
||||
if (!threadTs) return;
|
||||
if (abTestChannels.size > 0 && !abTestChannels.has(channelId)) return;
|
||||
|
||||
if (mentioned) {
|
||||
cleanExpiredMentions();
|
||||
mentionedThreads.set(`${channelId}:${threadTs}`, Date.now());
|
||||
}
|
||||
});
|
||||
if (mentionedThreads.has(`${channelId}:${threadTs}`)) return;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// message_sending: check thread ownership before sending to Slack.
|
||||
// Returns { cancel: true } if another agent owns the thread.
|
||||
// ---------------------------------------------------------------------------
|
||||
api.on("message_sending", async (event, ctx) => {
|
||||
if (ctx.channelId !== "slack") return;
|
||||
try {
|
||||
const resp = await fetch(`${forwarderUrl}/api/v1/ownership/${channelId}/${threadTs}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ agent_id: agentId }),
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
|
||||
const threadTs = (event.metadata?.threadTs as string) ?? "";
|
||||
const channelId = (event.metadata?.channelId as string) ?? event.to;
|
||||
|
||||
// Top-level messages (no thread) are always allowed.
|
||||
if (!threadTs) return;
|
||||
|
||||
// Only enforce in A/B test channels (if set is empty, skip entirely).
|
||||
if (abTestChannels.size > 0 && !abTestChannels.has(channelId)) return;
|
||||
|
||||
// If this agent was @-mentioned in this thread recently, skip ownership check.
|
||||
cleanExpiredMentions();
|
||||
if (mentionedThreads.has(`${channelId}:${threadTs}`)) return;
|
||||
|
||||
// Try to claim ownership via the forwarder HTTP API.
|
||||
try {
|
||||
const resp = await fetch(`${forwarderUrl}/api/v1/ownership/${channelId}/${threadTs}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ agent_id: agentId }),
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
// We own it (or just claimed it), proceed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp.status === 409) {
|
||||
// Another agent owns this thread — cancel the send.
|
||||
const body = (await resp.json()) as { owner?: string };
|
||||
api.logger.info?.(
|
||||
`thread-ownership: cancelled send to ${channelId}:${threadTs} — owned by ${body.owner}`,
|
||||
if (resp.ok) {
|
||||
return;
|
||||
}
|
||||
if (resp.status === 409) {
|
||||
const body = (await resp.json()) as { owner?: string };
|
||||
api.logger.info?.(
|
||||
`thread-ownership: cancelled send to ${channelId}:${threadTs} — owned by ${body.owner}`,
|
||||
);
|
||||
return { cancel: true };
|
||||
}
|
||||
api.logger.warn?.(`thread-ownership: unexpected status ${resp.status}, allowing send`);
|
||||
} catch (err) {
|
||||
api.logger.warn?.(
|
||||
`thread-ownership: ownership check failed (${String(err)}), allowing send`,
|
||||
);
|
||||
return { cancel: true };
|
||||
}
|
||||
|
||||
// Unexpected status — fail open.
|
||||
api.logger.warn?.(`thread-ownership: unexpected status ${resp.status}, allowing send`);
|
||||
} catch (err) {
|
||||
// Network error — fail open.
|
||||
api.logger.warn?.(`thread-ownership: ownership check failed (${String(err)}), allowing send`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { applyTogetherConfig, TOGETHER_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
@@ -6,12 +6,11 @@ import { buildTogetherProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "together";
|
||||
|
||||
const togetherPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Together Provider",
|
||||
description: "Bundled Together provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Together",
|
||||
@@ -50,6 +49,4 @@ const togetherPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default togetherPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
@@ -6,12 +6,11 @@ import { buildVeniceProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "venice";
|
||||
|
||||
const venicePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Venice Provider",
|
||||
description: "Bundled Venice provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Venice",
|
||||
@@ -56,6 +55,4 @@ const venicePlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default venicePlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { applyVercelAiGatewayConfig, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
@@ -6,12 +6,11 @@ import { buildVercelAiGatewayProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "vercel-ai-gateway";
|
||||
|
||||
const vercelAiGatewayPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Vercel AI Gateway Provider",
|
||||
description: "Bundled Vercel AI Gateway provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Vercel AI Gateway",
|
||||
@@ -50,6 +49,4 @@ const vercelAiGatewayPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default vercelAiGatewayPlugin;
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
VLLM_PROVIDER_LABEL,
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
definePluginEntry,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
@@ -16,11 +16,10 @@ async function loadProviderSetup() {
|
||||
return await import("openclaw/plugin-sdk/self-hosted-provider-setup");
|
||||
}
|
||||
|
||||
const vllmPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "vllm",
|
||||
name: "vLLM Provider",
|
||||
description: "Bundled vLLM provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
@@ -87,6 +86,4 @@ const vllmPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default vllmPlugin;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type {
|
||||
GatewayRequestHandlerOptions,
|
||||
OpenClawPluginApi,
|
||||
import {
|
||||
definePluginEntry,
|
||||
type GatewayRequestHandlerOptions,
|
||||
type OpenClawPluginApi,
|
||||
} from "openclaw/plugin-sdk/voice-call";
|
||||
import { registerVoiceCallCli } from "./src/cli.js";
|
||||
import {
|
||||
@@ -143,7 +144,7 @@ const VoiceCallToolSchema = Type.Union([
|
||||
}),
|
||||
]);
|
||||
|
||||
const voiceCallPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "voice-call",
|
||||
name: "Voice Call",
|
||||
description: "Voice-call plugin with Telnyx/Twilio/Plivo providers",
|
||||
@@ -560,6 +561,4 @@ const voiceCallPlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default voiceCallPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catalog.js";
|
||||
@@ -6,12 +6,11 @@ import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catal
|
||||
const PROVIDER_ID = "volcengine";
|
||||
const VOLCENGINE_DEFAULT_MODEL_REF = "volcengine-plan/ark-code-latest";
|
||||
|
||||
const volcenginePlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Volcengine Provider",
|
||||
description: "Bundled Volcengine provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Volcengine",
|
||||
@@ -60,6 +59,4 @@ const volcenginePlugin = {
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default volcenginePlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-models";
|
||||
import {
|
||||
@@ -16,12 +16,11 @@ function matchesModernXaiModel(modelId: string): boolean {
|
||||
return XAI_MODERN_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
||||
}
|
||||
|
||||
const xaiPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: "xai",
|
||||
name: "xAI Plugin",
|
||||
description: "Bundled xAI plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "xAI",
|
||||
@@ -68,6 +67,4 @@ const xaiPlugin = {
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default xaiPlugin;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { PROVIDER_LABELS } from "openclaw/plugin-sdk/provider-usage";
|
||||
@@ -7,12 +7,11 @@ import { buildXiaomiProvider } from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "xiaomi";
|
||||
|
||||
const xiaomiPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Xiaomi Provider",
|
||||
description: "Bundled Xiaomi provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Xiaomi",
|
||||
@@ -62,6 +61,4 @@ const xiaomiPlugin = {
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default xiaomiPlugin;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthMethod,
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
@@ -226,12 +225,11 @@ function buildZaiApiKeyMethod(params: {
|
||||
};
|
||||
}
|
||||
|
||||
const zaiPlugin = {
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Z.AI Provider",
|
||||
description: "Bundled Z.AI provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Z.AI",
|
||||
@@ -311,6 +309,4 @@ const zaiPlugin = {
|
||||
});
|
||||
api.registerMediaUnderstandingProvider(zaiMediaUnderstandingProvider);
|
||||
},
|
||||
};
|
||||
|
||||
export default zaiPlugin;
|
||||
});
|
||||
|
||||
@@ -113,7 +113,7 @@ export async function resolveSharedMemoryStatusSnapshot(params: {
|
||||
purpose: "status";
|
||||
}) => Promise<{
|
||||
manager: {
|
||||
probeVectorAvailability(): Promise<void>;
|
||||
probeVectorAvailability(): Promise<boolean>;
|
||||
status(): MemoryProviderStatus;
|
||||
close?(): Promise<void>;
|
||||
} | null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
type JsonSchemaObject = {
|
||||
type?: string | string[];
|
||||
properties?: Record<string, JsonSchemaObject>;
|
||||
additionalProperties?: JsonSchemaObject | boolean;
|
||||
items?: JsonSchemaObject | JsonSchemaObject[];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Narrow plugin-sdk surface for the bundled copilot-proxy plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/copilot-proxy.
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export type {
|
||||
OpenClawPluginApi,
|
||||
ProviderAuthContext,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginCommandDefinition,
|
||||
OpenClawPluginConfigSchema,
|
||||
OpenClawPluginDefinition,
|
||||
PluginInteractiveTelegramHandlerContext,
|
||||
} from "../plugins/types.js";
|
||||
|
||||
@@ -42,6 +43,7 @@ export type {
|
||||
ProviderAuthMethod,
|
||||
ProviderAuthResult,
|
||||
OpenClawPluginCommandDefinition,
|
||||
OpenClawPluginDefinition,
|
||||
PluginInteractiveTelegramHandlerContext,
|
||||
} from "../plugins/types.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
@@ -88,11 +90,53 @@ type DefineChannelPluginEntryOptions<TPlugin extends ChannelPlugin = ChannelPlug
|
||||
name: string;
|
||||
description: string;
|
||||
plugin: TPlugin;
|
||||
configSchema?: () => OpenClawPluginConfigSchema;
|
||||
configSchema?: DefinePluginEntryOptions["configSchema"];
|
||||
setRuntime?: (runtime: PluginRuntime) => void;
|
||||
registerFull?: (api: OpenClawPluginApi) => void;
|
||||
};
|
||||
|
||||
type DefinePluginEntryOptions = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
kind?: OpenClawPluginDefinition["kind"];
|
||||
configSchema?: OpenClawPluginConfigSchema | (() => OpenClawPluginConfigSchema);
|
||||
register: (api: OpenClawPluginApi) => void;
|
||||
};
|
||||
|
||||
type DefinedPluginEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
configSchema: OpenClawPluginConfigSchema;
|
||||
register: NonNullable<OpenClawPluginDefinition["register"]>;
|
||||
} & Pick<OpenClawPluginDefinition, "kind">;
|
||||
|
||||
function resolvePluginConfigSchema(
|
||||
configSchema: DefinePluginEntryOptions["configSchema"] = emptyPluginConfigSchema,
|
||||
): OpenClawPluginConfigSchema {
|
||||
return typeof configSchema === "function" ? configSchema() : configSchema;
|
||||
}
|
||||
|
||||
// Shared generic plugin-entry boilerplate for bundled and third-party plugins.
|
||||
export function definePluginEntry({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
kind,
|
||||
configSchema = emptyPluginConfigSchema,
|
||||
register,
|
||||
}: DefinePluginEntryOptions): DefinedPluginEntry {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
...(kind ? { kind } : {}),
|
||||
configSchema: resolvePluginConfigSchema(configSchema),
|
||||
register,
|
||||
};
|
||||
}
|
||||
|
||||
// Shared channel-plugin entry boilerplate for bundled and third-party channels.
|
||||
export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
|
||||
id,
|
||||
@@ -103,11 +147,11 @@ export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
|
||||
setRuntime,
|
||||
registerFull,
|
||||
}: DefineChannelPluginEntryOptions<TPlugin>) {
|
||||
return {
|
||||
return definePluginEntry({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
configSchema: configSchema(),
|
||||
configSchema,
|
||||
register(api: OpenClawPluginApi) {
|
||||
setRuntime?.(api.runtime);
|
||||
api.registerChannel({ plugin });
|
||||
@@ -116,7 +160,7 @@ export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
|
||||
}
|
||||
registerFull?.(api);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Shared setup-entry shape so bundled channels do not duplicate `{ plugin }`.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Narrow plugin-sdk surface for the bundled device-pair plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/device-pair.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export { approveDevicePairing, listDevicePairing } from "../infra/device-pairing.js";
|
||||
export { issueDeviceBootstrapToken } from "../infra/device-bootstrap.js";
|
||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Narrow plugin-sdk surface for the bundled llm-task plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/llm-task.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
export {
|
||||
formatThinkingLevels,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Narrow plugin-sdk surface for the bundled lobster plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/lobster.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export {
|
||||
applyWindowsSpawnProgramPolicy,
|
||||
materializeWindowsSpawnProgram,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Narrow plugin-sdk surface for the bundled memory-lancedb plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/memory-lancedb.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Narrow plugin-sdk surface for MiniMax OAuth helpers used by the bundled minimax plugin.
|
||||
// Keep this list additive and scoped to MiniMax OAuth support code.
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
|
||||
export type {
|
||||
OpenClawPluginApi,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Narrow plugin-sdk surface for the bundled open-prose plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/open-prose.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Narrow plugin-sdk surface for the bundled phone-control plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/phone-control.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export type {
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginCommandDefinition,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Narrow plugin-sdk surface for the bundled qwen-portal-auth plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/qwen-portal-auth.
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
|
||||
export type {
|
||||
OpenClawPluginApi,
|
||||
|
||||
@@ -49,6 +49,7 @@ describe("plugin-sdk subpath exports", () => {
|
||||
|
||||
it("keeps core focused on generic shared exports", () => {
|
||||
expect(typeof coreSdk.emptyPluginConfigSchema).toBe("function");
|
||||
expect(typeof coreSdk.definePluginEntry).toBe("function");
|
||||
expect(typeof coreSdk.defineChannelPluginEntry).toBe("function");
|
||||
expect(typeof coreSdk.defineSetupPluginEntry).toBe("function");
|
||||
expect("runPassiveAccountLifecycle" in asExports(coreSdk)).toBe(false);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Narrow plugin-sdk surface for the bundled talk-voice plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/talk-voice.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Narrow plugin-sdk surface for the bundled thread-ownership plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/thread-ownership.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Narrow plugin-sdk surface for the bundled voice-call plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/voice-call.
|
||||
|
||||
export { definePluginEntry } from "./core.js";
|
||||
export {
|
||||
TtsAutoSchema,
|
||||
TtsConfigSchema,
|
||||
|
||||
@@ -45,7 +45,7 @@ type RegisterCliContext = {
|
||||
function setup(config: Record<string, unknown>): Registered {
|
||||
const methods = new Map<string, unknown>();
|
||||
const tools: unknown[] = [];
|
||||
plugin.register({
|
||||
void plugin.register({
|
||||
id: "voice-call",
|
||||
name: "Voice Call",
|
||||
description: "test",
|
||||
|
||||
Reference in New Issue
Block a user