mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 17:01:53 +03:00
refactor(zalo): share outbound chunker
This commit is contained in:
@@ -9,10 +9,13 @@ import {
|
|||||||
buildChannelConfigSchema,
|
buildChannelConfigSchema,
|
||||||
DEFAULT_ACCOUNT_ID,
|
DEFAULT_ACCOUNT_ID,
|
||||||
deleteAccountFromConfigSection,
|
deleteAccountFromConfigSection,
|
||||||
|
chunkTextForOutbound,
|
||||||
|
formatAllowFromLowercase,
|
||||||
formatPairingApproveHint,
|
formatPairingApproveHint,
|
||||||
migrateBaseNameToDefaultAccount,
|
migrateBaseNameToDefaultAccount,
|
||||||
normalizeAccountId,
|
normalizeAccountId,
|
||||||
PAIRING_APPROVED_MESSAGE,
|
PAIRING_APPROVED_MESSAGE,
|
||||||
|
resolveChannelAccountConfigBasePath,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk";
|
||||||
import {
|
import {
|
||||||
@@ -63,11 +66,7 @@ export const zaloDock: ChannelDock = {
|
|||||||
String(entry),
|
String(entry),
|
||||||
),
|
),
|
||||||
formatAllowFrom: ({ allowFrom }) =>
|
formatAllowFrom: ({ allowFrom }) =>
|
||||||
allowFrom
|
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalo|zl):/i }),
|
||||||
.map((entry) => String(entry).trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((entry) => entry.replace(/^(zalo|zl):/i, ""))
|
|
||||||
.map((entry) => entry.toLowerCase()),
|
|
||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: () => true,
|
resolveRequireMention: () => true,
|
||||||
@@ -124,19 +123,16 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
|
|||||||
String(entry),
|
String(entry),
|
||||||
),
|
),
|
||||||
formatAllowFrom: ({ allowFrom }) =>
|
formatAllowFrom: ({ allowFrom }) =>
|
||||||
allowFrom
|
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalo|zl):/i }),
|
||||||
.map((entry) => String(entry).trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((entry) => entry.replace(/^(zalo|zl):/i, ""))
|
|
||||||
.map((entry) => entry.toLowerCase()),
|
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||||
const useAccountPath = Boolean(cfg.channels?.zalo?.accounts?.[resolvedAccountId]);
|
const basePath = resolveChannelAccountConfigBasePath({
|
||||||
const basePath = useAccountPath
|
cfg,
|
||||||
? `channels.zalo.accounts.${resolvedAccountId}.`
|
channelKey: "zalo",
|
||||||
: "channels.zalo.";
|
accountId: resolvedAccountId,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
policy: account.config.dmPolicy ?? "pairing",
|
policy: account.config.dmPolicy ?? "pairing",
|
||||||
allowFrom: account.config.allowFrom ?? [],
|
allowFrom: account.config.allowFrom ?? [],
|
||||||
@@ -275,37 +271,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
|
|||||||
},
|
},
|
||||||
outbound: {
|
outbound: {
|
||||||
deliveryMode: "direct",
|
deliveryMode: "direct",
|
||||||
chunker: (text, limit) => {
|
chunker: chunkTextForOutbound,
|
||||||
if (!text) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (limit <= 0 || text.length <= limit) {
|
|
||||||
return [text];
|
|
||||||
}
|
|
||||||
const chunks: string[] = [];
|
|
||||||
let remaining = text;
|
|
||||||
while (remaining.length > limit) {
|
|
||||||
const window = remaining.slice(0, limit);
|
|
||||||
const lastNewline = window.lastIndexOf("\n");
|
|
||||||
const lastSpace = window.lastIndexOf(" ");
|
|
||||||
let breakIdx = lastNewline > 0 ? lastNewline : lastSpace;
|
|
||||||
if (breakIdx <= 0) {
|
|
||||||
breakIdx = limit;
|
|
||||||
}
|
|
||||||
const rawChunk = remaining.slice(0, breakIdx);
|
|
||||||
const chunk = rawChunk.trimEnd();
|
|
||||||
if (chunk.length > 0) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
}
|
|
||||||
const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
|
|
||||||
const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
|
|
||||||
remaining = remaining.slice(nextStart).trimStart();
|
|
||||||
}
|
|
||||||
if (remaining.length) {
|
|
||||||
chunks.push(remaining);
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
},
|
|
||||||
chunkerMode: "text",
|
chunkerMode: "text",
|
||||||
textChunkLimit: 2000,
|
textChunkLimit: 2000,
|
||||||
sendText: async ({ to, text, accountId, cfg }) => {
|
sendText: async ({ to, text, accountId, cfg }) => {
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ import {
|
|||||||
applyAccountNameToChannelSection,
|
applyAccountNameToChannelSection,
|
||||||
buildChannelConfigSchema,
|
buildChannelConfigSchema,
|
||||||
DEFAULT_ACCOUNT_ID,
|
DEFAULT_ACCOUNT_ID,
|
||||||
|
chunkTextForOutbound,
|
||||||
deleteAccountFromConfigSection,
|
deleteAccountFromConfigSection,
|
||||||
|
formatAllowFromLowercase,
|
||||||
formatPairingApproveHint,
|
formatPairingApproveHint,
|
||||||
migrateBaseNameToDefaultAccount,
|
migrateBaseNameToDefaultAccount,
|
||||||
normalizeAccountId,
|
normalizeAccountId,
|
||||||
|
resolveChannelAccountConfigBasePath,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
} from "openclaw/plugin-sdk";
|
} from "openclaw/plugin-sdk";
|
||||||
import type { ZcaFriend, ZcaGroup, ZcaUserInfo } from "./types.js";
|
import type { ZcaFriend, ZcaGroup, ZcaUserInfo } from "./types.js";
|
||||||
@@ -117,11 +120,7 @@ export const zalouserDock: ChannelDock = {
|
|||||||
String(entry),
|
String(entry),
|
||||||
),
|
),
|
||||||
formatAllowFrom: ({ allowFrom }) =>
|
formatAllowFrom: ({ allowFrom }) =>
|
||||||
allowFrom
|
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }),
|
||||||
.map((entry) => String(entry).trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((entry) => entry.replace(/^(zalouser|zlu):/i, ""))
|
|
||||||
.map((entry) => entry.toLowerCase()),
|
|
||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: () => true,
|
resolveRequireMention: () => true,
|
||||||
@@ -193,19 +192,16 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|||||||
String(entry),
|
String(entry),
|
||||||
),
|
),
|
||||||
formatAllowFrom: ({ allowFrom }) =>
|
formatAllowFrom: ({ allowFrom }) =>
|
||||||
allowFrom
|
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }),
|
||||||
.map((entry) => String(entry).trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((entry) => entry.replace(/^(zalouser|zlu):/i, ""))
|
|
||||||
.map((entry) => entry.toLowerCase()),
|
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||||
const useAccountPath = Boolean(cfg.channels?.zalouser?.accounts?.[resolvedAccountId]);
|
const basePath = resolveChannelAccountConfigBasePath({
|
||||||
const basePath = useAccountPath
|
cfg,
|
||||||
? `channels.zalouser.accounts.${resolvedAccountId}.`
|
channelKey: "zalouser",
|
||||||
: "channels.zalouser.";
|
accountId: resolvedAccountId,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
policy: account.config.dmPolicy ?? "pairing",
|
policy: account.config.dmPolicy ?? "pairing",
|
||||||
allowFrom: account.config.allowFrom ?? [],
|
allowFrom: account.config.allowFrom ?? [],
|
||||||
@@ -519,37 +515,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|||||||
},
|
},
|
||||||
outbound: {
|
outbound: {
|
||||||
deliveryMode: "direct",
|
deliveryMode: "direct",
|
||||||
chunker: (text, limit) => {
|
chunker: chunkTextForOutbound,
|
||||||
if (!text) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (limit <= 0 || text.length <= limit) {
|
|
||||||
return [text];
|
|
||||||
}
|
|
||||||
const chunks: string[] = [];
|
|
||||||
let remaining = text;
|
|
||||||
while (remaining.length > limit) {
|
|
||||||
const window = remaining.slice(0, limit);
|
|
||||||
const lastNewline = window.lastIndexOf("\n");
|
|
||||||
const lastSpace = window.lastIndexOf(" ");
|
|
||||||
let breakIdx = lastNewline > 0 ? lastNewline : lastSpace;
|
|
||||||
if (breakIdx <= 0) {
|
|
||||||
breakIdx = limit;
|
|
||||||
}
|
|
||||||
const rawChunk = remaining.slice(0, breakIdx);
|
|
||||||
const chunk = rawChunk.trimEnd();
|
|
||||||
if (chunk.length > 0) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
}
|
|
||||||
const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
|
|
||||||
const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
|
|
||||||
remaining = remaining.slice(nextStart).trimStart();
|
|
||||||
}
|
|
||||||
if (remaining.length) {
|
|
||||||
chunks.push(remaining);
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
},
|
|
||||||
chunkerMode: "text",
|
chunkerMode: "text",
|
||||||
textChunkLimit: 2000,
|
textChunkLimit: 2000,
|
||||||
sendText: async ({ to, text, accountId, cfg }) => {
|
sendText: async ({ to, text, accountId, cfg }) => {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export function formatAllowFromLowercase(params: {
|
||||||
|
allowFrom: Array<string | number>;
|
||||||
|
stripPrefixRe?: RegExp;
|
||||||
|
}): string[] {
|
||||||
|
return params.allowFrom
|
||||||
|
.map((entry) => String(entry).trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((entry) => (params.stripPrefixRe ? entry.replace(params.stripPrefixRe, "") : entry))
|
||||||
|
.map((entry) => entry.toLowerCase());
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
|
|
||||||
|
export function resolveChannelAccountConfigBasePath(params: {
|
||||||
|
cfg: OpenClawConfig;
|
||||||
|
channelKey: string;
|
||||||
|
accountId: string;
|
||||||
|
}): string {
|
||||||
|
const channels = params.cfg.channels as unknown as Record<string, unknown> | undefined;
|
||||||
|
const channelSection = channels?.[params.channelKey] as Record<string, unknown> | undefined;
|
||||||
|
const accounts = channelSection?.accounts as Record<string, unknown> | undefined;
|
||||||
|
const useAccountPath = Boolean(accounts?.[params.accountId]);
|
||||||
|
return useAccountPath
|
||||||
|
? `channels.${params.channelKey}.accounts.${params.accountId}.`
|
||||||
|
: `channels.${params.channelKey}.`;
|
||||||
|
}
|
||||||
@@ -126,6 +126,9 @@ export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
|
|||||||
export type { RuntimeEnv } from "../runtime.js";
|
export type { RuntimeEnv } from "../runtime.js";
|
||||||
export type { WizardPrompter } from "../wizard/prompts.js";
|
export type { WizardPrompter } from "../wizard/prompts.js";
|
||||||
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||||
|
export { formatAllowFromLowercase } from "./allow-from.js";
|
||||||
|
export { resolveChannelAccountConfigBasePath } from "./config-paths.js";
|
||||||
|
export { chunkTextForOutbound } from "./text-chunking.js";
|
||||||
export type { ChatType } from "../channels/chat-type.js";
|
export type { ChatType } from "../channels/chat-type.js";
|
||||||
/** @deprecated Use ChatType instead */
|
/** @deprecated Use ChatType instead */
|
||||||
export type { RoutePeerKind } from "../routing/resolve-route.js";
|
export type { RoutePeerKind } from "../routing/resolve-route.js";
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export function chunkTextForOutbound(text: string, limit: number): string[] {
|
||||||
|
if (!text) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (limit <= 0 || text.length <= limit) {
|
||||||
|
return [text];
|
||||||
|
}
|
||||||
|
const chunks: string[] = [];
|
||||||
|
let remaining = text;
|
||||||
|
while (remaining.length > limit) {
|
||||||
|
const window = remaining.slice(0, limit);
|
||||||
|
const lastNewline = window.lastIndexOf("\n");
|
||||||
|
const lastSpace = window.lastIndexOf(" ");
|
||||||
|
let breakIdx = lastNewline > 0 ? lastNewline : lastSpace;
|
||||||
|
if (breakIdx <= 0) {
|
||||||
|
breakIdx = limit;
|
||||||
|
}
|
||||||
|
const rawChunk = remaining.slice(0, breakIdx);
|
||||||
|
const chunk = rawChunk.trimEnd();
|
||||||
|
if (chunk.length > 0) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
|
||||||
|
const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
|
||||||
|
remaining = remaining.slice(nextStart).trimStart();
|
||||||
|
}
|
||||||
|
if (remaining.length) {
|
||||||
|
chunks.push(remaining);
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user