mirror of https://github.com/openclaw/openclaw.git
refactor: share remaining account config helpers
This commit is contained in:
parent
66beff726b
commit
bddb6fca7b
|
|
@ -1,5 +1,8 @@
|
|||
import { createAccountListHelpers, mergeAccountConfig } from "openclaw/plugin-sdk/account-helpers";
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import {
|
||||
createAccountListHelpers,
|
||||
normalizeAccountId,
|
||||
resolveMergedAccountConfig,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
||||
import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
|
||||
import { normalizeBlueBubblesServerUrl, type BlueBubblesAccountConfig } from "./types.js";
|
||||
|
|
@ -19,29 +22,19 @@ const {
|
|||
} = createAccountListHelpers("bluebubbles");
|
||||
export { listBlueBubblesAccountIds, resolveDefaultBlueBubblesAccountId };
|
||||
|
||||
function resolveAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): BlueBubblesAccountConfig | undefined {
|
||||
const accounts = cfg.channels?.bluebubbles?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return accounts[accountId] as BlueBubblesAccountConfig | undefined;
|
||||
}
|
||||
|
||||
function mergeBlueBubblesAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): BlueBubblesAccountConfig {
|
||||
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
||||
const merged = mergeAccountConfig<BlueBubblesAccountConfig>({
|
||||
const merged = resolveMergedAccountConfig<BlueBubblesAccountConfig>({
|
||||
channelConfig: cfg.channels?.bluebubbles as BlueBubblesAccountConfig | undefined,
|
||||
accountConfig: account,
|
||||
accounts: cfg.channels?.bluebubbles?.accounts as
|
||||
| Record<string, Partial<BlueBubblesAccountConfig>>
|
||||
| undefined,
|
||||
accountId,
|
||||
omitKeys: ["defaultAccount"],
|
||||
});
|
||||
const chunkMode = account.chunkMode ?? merged.chunkMode ?? "length";
|
||||
return { ...merged, chunkMode };
|
||||
return { ...merged, chunkMode: merged.chunkMode ?? "length" };
|
||||
}
|
||||
|
||||
export function resolveBlueBubblesAccount(params: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
mergeAccountConfig,
|
||||
createAccountListHelpers,
|
||||
normalizeAccountId,
|
||||
normalizeOptionalAccountId,
|
||||
resolveMergedAccountConfig,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
|
||||
|
|
@ -13,29 +15,15 @@ import type {
|
|||
ResolvedFeishuAccount,
|
||||
} from "./types.js";
|
||||
|
||||
/**
|
||||
* List all configured account IDs from the accounts field.
|
||||
*/
|
||||
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(accounts).filter(Boolean);
|
||||
}
|
||||
const {
|
||||
listConfiguredAccountIds,
|
||||
listAccountIds: listFeishuAccountIds,
|
||||
resolveDefaultAccountId,
|
||||
} = createAccountListHelpers("feishu", {
|
||||
allowUnlistedDefaultAccount: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* List all Feishu account IDs.
|
||||
* If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
|
||||
*/
|
||||
export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const ids = listConfiguredAccountIds(cfg);
|
||||
if (ids.length === 0) {
|
||||
// Backward compatibility: no accounts configured, use default
|
||||
return [DEFAULT_ACCOUNT_ID];
|
||||
}
|
||||
return [...ids].toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
export { listFeishuAccountIds };
|
||||
|
||||
/**
|
||||
* Resolve the default account selection and its source.
|
||||
|
|
@ -44,8 +32,9 @@ export function resolveDefaultFeishuAccountSelection(cfg: ClawdbotConfig): {
|
|||
accountId: string;
|
||||
source: FeishuDefaultAccountSelectionSource;
|
||||
} {
|
||||
const preferredRaw = (cfg.channels?.feishu as FeishuConfig | undefined)?.defaultAccount?.trim();
|
||||
const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : undefined;
|
||||
const preferred = normalizeOptionalAccountId(
|
||||
(cfg.channels?.feishu as FeishuConfig | undefined)?.defaultAccount,
|
||||
);
|
||||
if (preferred) {
|
||||
return {
|
||||
accountId: preferred,
|
||||
|
|
@ -69,21 +58,7 @@ export function resolveDefaultFeishuAccountSelection(cfg: ClawdbotConfig): {
|
|||
* Resolve the default account ID.
|
||||
*/
|
||||
export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
|
||||
return resolveDefaultFeishuAccountSelection(cfg).accountId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw account-specific config.
|
||||
*/
|
||||
function resolveAccountConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): FeishuAccountConfig | undefined {
|
||||
const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return accounts[accountId];
|
||||
return resolveDefaultAccountId(cfg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,9 +67,10 @@ function resolveAccountConfig(
|
|||
*/
|
||||
function mergeFeishuAccountConfig(cfg: ClawdbotConfig, accountId: string): FeishuConfig {
|
||||
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
||||
return mergeAccountConfig<FeishuConfig>({
|
||||
return resolveMergedAccountConfig<FeishuConfig>({
|
||||
channelConfig: feishuCfg,
|
||||
accountConfig: resolveAccountConfig(cfg, accountId),
|
||||
accounts: feishuCfg?.accounts as Record<string, Partial<FeishuConfig>> | undefined,
|
||||
accountId,
|
||||
omitKeys: ["defaultAccount"],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { createAccountListHelpers, mergeAccountConfig } from "openclaw/plugin-sdk/account-helpers";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import {
|
||||
createAccountListHelpers,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
resolveAccountEntry,
|
||||
resolveMergedAccountConfig,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import { isSecretRef, type OpenClawConfig } from "openclaw/plugin-sdk/core";
|
||||
import type { GoogleChatAccountConfig } from "./types.config.js";
|
||||
|
||||
|
|
@ -24,31 +29,24 @@ const {
|
|||
} = createAccountListHelpers("googlechat");
|
||||
export { listGoogleChatAccountIds, resolveDefaultGoogleChatAccountId };
|
||||
|
||||
function resolveAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): GoogleChatAccountConfig | undefined {
|
||||
const accounts = cfg.channels?.["googlechat"]?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return accounts[accountId];
|
||||
}
|
||||
|
||||
function mergeGoogleChatAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): GoogleChatAccountConfig {
|
||||
const raw = cfg.channels?.["googlechat"] ?? {};
|
||||
const base = mergeAccountConfig<GoogleChatAccountConfig>({
|
||||
const base = resolveMergedAccountConfig<GoogleChatAccountConfig>({
|
||||
channelConfig: raw as GoogleChatAccountConfig,
|
||||
accountConfig: undefined,
|
||||
accounts: raw.accounts as Record<string, Partial<GoogleChatAccountConfig>> | undefined,
|
||||
accountId,
|
||||
omitKeys: ["defaultAccount"],
|
||||
});
|
||||
const defaultAccountConfig = resolveAccountConfig(cfg, DEFAULT_ACCOUNT_ID) ?? {};
|
||||
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
||||
const defaultAccountConfig =
|
||||
resolveAccountEntry(
|
||||
raw.accounts as Record<string, GoogleChatAccountConfig> | undefined,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
) ?? {};
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return { ...base, ...defaultAccountConfig } as GoogleChatAccountConfig;
|
||||
return base;
|
||||
}
|
||||
const {
|
||||
enabled: _ignoredEnabled,
|
||||
|
|
@ -60,7 +58,7 @@ function mergeGoogleChatAccountConfig(
|
|||
} = defaultAccountConfig;
|
||||
// In multi-account setups, allow accounts.default to provide shared defaults
|
||||
// (for example webhook/audience fields) while preserving top-level and account overrides.
|
||||
return { ...defaultAccountShared, ...base, ...account } as GoogleChatAccountConfig;
|
||||
return { ...defaultAccountShared, ...base } as GoogleChatAccountConfig;
|
||||
}
|
||||
|
||||
function parseServiceAccount(value: unknown): Record<string, unknown> | null {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveWhatsAppAuthDir } from "./accounts.js";
|
||||
import { resolveWhatsAppAccount, resolveWhatsAppAuthDir } from "./accounts.js";
|
||||
|
||||
describe("resolveWhatsAppAuthDir", () => {
|
||||
const stubCfg = { channels: { whatsapp: { accounts: {} } } } as Parameters<
|
||||
|
|
@ -44,4 +44,31 @@ describe("resolveWhatsAppAuthDir", () => {
|
|||
});
|
||||
expect(authDir).toMatch(/whatsapp[/\\]my-account-1$/);
|
||||
});
|
||||
|
||||
it("merges top-level and account-specific config through shared helpers", () => {
|
||||
const resolved = resolveWhatsAppAccount({
|
||||
cfg: {
|
||||
messages: {
|
||||
messagePrefix: "[global]",
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
sendReadReceipts: false,
|
||||
messagePrefix: "[root]",
|
||||
debounceMs: 100,
|
||||
accounts: {
|
||||
work: {
|
||||
debounceMs: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Parameters<typeof resolveWhatsAppAccount>[0]["cfg"],
|
||||
accountId: "work",
|
||||
});
|
||||
|
||||
expect(resolved.sendReadReceipts).toBe(false);
|
||||
expect(resolved.messagePrefix).toBe("[root]");
|
||||
expect(resolved.debounceMs).toBe(250);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
resolveAccountEntry,
|
||||
resolveMergedAccountConfig,
|
||||
resolveUserPath,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
|
|
@ -123,34 +124,38 @@ export function resolveWhatsAppAccount(params: {
|
|||
}): ResolvedWhatsAppAccount {
|
||||
const rootCfg = params.cfg.channels?.whatsapp;
|
||||
const accountId = params.accountId?.trim() || resolveDefaultWhatsAppAccountId(params.cfg);
|
||||
const accountCfg = resolveAccountConfig(params.cfg, accountId);
|
||||
const enabled = accountCfg?.enabled !== false;
|
||||
const merged = resolveMergedAccountConfig<WhatsAppAccountConfig>({
|
||||
channelConfig: rootCfg as WhatsAppAccountConfig | undefined,
|
||||
accounts: rootCfg?.accounts as Record<string, Partial<WhatsAppAccountConfig>> | undefined,
|
||||
accountId,
|
||||
omitKeys: ["defaultAccount"],
|
||||
});
|
||||
const enabled = merged.enabled !== false;
|
||||
const { authDir, isLegacy } = resolveWhatsAppAuthDir({
|
||||
cfg: params.cfg,
|
||||
accountId,
|
||||
});
|
||||
return {
|
||||
accountId,
|
||||
name: accountCfg?.name?.trim() || undefined,
|
||||
name: merged.name?.trim() || undefined,
|
||||
enabled,
|
||||
sendReadReceipts: accountCfg?.sendReadReceipts ?? rootCfg?.sendReadReceipts ?? true,
|
||||
messagePrefix:
|
||||
accountCfg?.messagePrefix ?? rootCfg?.messagePrefix ?? params.cfg.messages?.messagePrefix,
|
||||
defaultTo: accountCfg?.defaultTo ?? rootCfg?.defaultTo,
|
||||
sendReadReceipts: merged.sendReadReceipts ?? true,
|
||||
messagePrefix: merged.messagePrefix ?? params.cfg.messages?.messagePrefix,
|
||||
defaultTo: merged.defaultTo,
|
||||
authDir,
|
||||
isLegacyAuthDir: isLegacy,
|
||||
selfChatMode: accountCfg?.selfChatMode ?? rootCfg?.selfChatMode,
|
||||
dmPolicy: accountCfg?.dmPolicy ?? rootCfg?.dmPolicy,
|
||||
allowFrom: accountCfg?.allowFrom ?? rootCfg?.allowFrom,
|
||||
groupAllowFrom: accountCfg?.groupAllowFrom ?? rootCfg?.groupAllowFrom,
|
||||
groupPolicy: accountCfg?.groupPolicy ?? rootCfg?.groupPolicy,
|
||||
textChunkLimit: accountCfg?.textChunkLimit ?? rootCfg?.textChunkLimit,
|
||||
chunkMode: accountCfg?.chunkMode ?? rootCfg?.chunkMode,
|
||||
mediaMaxMb: accountCfg?.mediaMaxMb ?? rootCfg?.mediaMaxMb,
|
||||
blockStreaming: accountCfg?.blockStreaming ?? rootCfg?.blockStreaming,
|
||||
ackReaction: accountCfg?.ackReaction ?? rootCfg?.ackReaction,
|
||||
groups: accountCfg?.groups ?? rootCfg?.groups,
|
||||
debounceMs: accountCfg?.debounceMs ?? rootCfg?.debounceMs,
|
||||
selfChatMode: merged.selfChatMode,
|
||||
dmPolicy: merged.dmPolicy,
|
||||
allowFrom: merged.allowFrom,
|
||||
groupAllowFrom: merged.groupAllowFrom,
|
||||
groupPolicy: merged.groupPolicy,
|
||||
textChunkLimit: merged.textChunkLimit,
|
||||
chunkMode: merged.chunkMode,
|
||||
mediaMaxMb: merged.mediaMaxMb,
|
||||
blockStreaming: merged.blockStreaming,
|
||||
ackReaction: merged.ackReaction,
|
||||
groups: merged.groups,
|
||||
debounceMs: merged.debounceMs,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,16 @@ describe("createAccountListHelpers", () => {
|
|||
it('returns "default" for empty config', () => {
|
||||
expect(resolveDefaultAccountId({} as OpenClawConfig)).toBe("default");
|
||||
});
|
||||
|
||||
it("can preserve configured defaults that are not present in accounts", () => {
|
||||
const preserveDefault = createAccountListHelpers("testchannel", {
|
||||
allowUnlistedDefaultAccount: true,
|
||||
});
|
||||
|
||||
expect(preserveDefault.resolveDefaultAccountId(cfg({ default: {}, zeta: {} }, "ops"))).toBe(
|
||||
"ops",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ import {
|
|||
|
||||
export function createAccountListHelpers(
|
||||
channelKey: string,
|
||||
options?: { normalizeAccountId?: (id: string) => string },
|
||||
options?: {
|
||||
normalizeAccountId?: (id: string) => string;
|
||||
allowUnlistedDefaultAccount?: boolean;
|
||||
},
|
||||
) {
|
||||
function resolveConfiguredDefaultAccountId(cfg: OpenClawConfig): string | undefined {
|
||||
const channel = cfg.channels?.[channelKey] as Record<string, unknown> | undefined;
|
||||
|
|
@ -22,6 +25,9 @@ export function createAccountListHelpers(
|
|||
return undefined;
|
||||
}
|
||||
const ids = listAccountIds(cfg);
|
||||
if (options?.allowUnlistedDefaultAccount) {
|
||||
return preferred;
|
||||
}
|
||||
if (ids.some((id) => normalizeAccountId(id) === preferred)) {
|
||||
return preferred;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue