mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-23 16:01:17 +00:00
refactor: share normalized account lookups
This commit is contained in:
@@ -81,6 +81,29 @@ describe("resolveDefaultIrcAccountId", () => {
|
||||
});
|
||||
|
||||
describe("resolveIrcAccount", () => {
|
||||
it("matches normalized configured account ids", () => {
|
||||
const account = resolveIrcAccount({
|
||||
cfg: asConfig({
|
||||
channels: {
|
||||
irc: {
|
||||
accounts: {
|
||||
"Ops Team": {
|
||||
host: "irc.example.com",
|
||||
nick: "claw",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
accountId: "ops-team",
|
||||
});
|
||||
|
||||
expect(account.accountId).toBe("ops-team");
|
||||
expect(account.host).toBe("irc.example.com");
|
||||
expect(account.nick).toBe("claw");
|
||||
expect(account.configured).toBe(true);
|
||||
});
|
||||
|
||||
it("parses delimited IRC_CHANNELS env values for the default account", () => {
|
||||
const previousChannels = process.env.IRC_CHANNELS;
|
||||
process.env.IRC_CHANNELS = "alpha, beta\ngamma; delta";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createAccountListHelpers, mergeAccountConfig } from "openclaw/plugin-sdk/account-helpers";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-resolution";
|
||||
import { parseOptionalDelimitedEntries } from "openclaw/plugin-sdk/core";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
@@ -46,17 +47,11 @@ const { listAccountIds: listIrcAccountIds, resolveDefaultAccountId: resolveDefau
|
||||
export { listIrcAccountIds, resolveDefaultIrcAccountId };
|
||||
|
||||
function resolveAccountConfig(cfg: CoreConfig, accountId: string): IrcAccountConfig | undefined {
|
||||
const accounts = cfg.channels?.irc?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const direct = accounts[accountId] as IrcAccountConfig | undefined;
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
|
||||
return matchKey ? (accounts[matchKey] as IrcAccountConfig | undefined) : undefined;
|
||||
return resolveNormalizedAccountEntry(
|
||||
cfg.channels?.irc?.accounts as Record<string, IrcAccountConfig> | undefined,
|
||||
accountId,
|
||||
normalizeAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
function mergeIrcAccountConfig(cfg: CoreConfig, accountId: string): IrcAccountConfig {
|
||||
|
||||
@@ -6,6 +6,29 @@ import { resolveNextcloudTalkAccount } from "./accounts.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
describe("resolveNextcloudTalkAccount", () => {
|
||||
it("matches normalized configured account ids", () => {
|
||||
const account = resolveNextcloudTalkAccount({
|
||||
cfg: {
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
accounts: {
|
||||
"Ops Team": {
|
||||
baseUrl: "https://cloud.example.com",
|
||||
botSecret: "bot-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig,
|
||||
accountId: "ops-team",
|
||||
});
|
||||
|
||||
expect(account.accountId).toBe("ops-team");
|
||||
expect(account.baseUrl).toBe("https://cloud.example.com");
|
||||
expect(account.secret).toBe("bot-secret");
|
||||
expect(account.secretSource).toBe("config");
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")("rejects symlinked botSecretFile paths", () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-nextcloud-talk-"));
|
||||
const secretFile = path.join(dir, "secret.txt");
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { mergeAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
||||
import {
|
||||
mergeAccountConfig,
|
||||
resolveNormalizedAccountEntry,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import {
|
||||
createAccountListHelpers,
|
||||
@@ -48,17 +51,13 @@ function resolveAccountConfig(
|
||||
cfg: CoreConfig,
|
||||
accountId: string,
|
||||
): NextcloudTalkAccountConfig | undefined {
|
||||
const accounts = cfg.channels?.["nextcloud-talk"]?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const direct = accounts[accountId] as NextcloudTalkAccountConfig | undefined;
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
|
||||
return matchKey ? (accounts[matchKey] as NextcloudTalkAccountConfig | undefined) : undefined;
|
||||
return resolveNormalizedAccountEntry(
|
||||
cfg.channels?.["nextcloud-talk"]?.accounts as
|
||||
| Record<string, NextcloudTalkAccountConfig>
|
||||
| undefined,
|
||||
accountId,
|
||||
normalizeAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
function mergeNextcloudTalkAccountConfig(
|
||||
|
||||
@@ -117,6 +117,23 @@ describe("resolveTelegramToken", () => {
|
||||
expect(res.source).toBe("config");
|
||||
});
|
||||
|
||||
it("resolves per-account tokens when config keys normalize spaces to dashes", () => {
|
||||
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
"Carey Notifications": { botToken: "acct-token" },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const res = resolveTelegramToken(cfg, { accountId: "carey-notifications" });
|
||||
expect(res.token).toBe("acct-token");
|
||||
expect(res.source).toBe("config");
|
||||
});
|
||||
|
||||
it("falls back to top-level token for non-default accounts without account token", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-resolution";
|
||||
import type { BaseTokenResolution } from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/infra-runtime";
|
||||
@@ -28,17 +29,9 @@ export function resolveTelegramToken(
|
||||
// be normalized, so resolve per-account config by matching normalized IDs.
|
||||
const resolveAccountCfg = (id: string): TelegramAccountConfig | undefined => {
|
||||
const accounts = telegramCfg?.accounts;
|
||||
if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) {
|
||||
return undefined;
|
||||
}
|
||||
// Direct hit (already normalized key)
|
||||
const direct = accounts[id];
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
// Fallback: match by normalized key
|
||||
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === id);
|
||||
return matchKey ? accounts[matchKey] : undefined;
|
||||
return Array.isArray(accounts)
|
||||
? undefined
|
||||
: resolveNormalizedAccountEntry(accounts, id, normalizeAccountId);
|
||||
};
|
||||
|
||||
const accountCfg = resolveAccountCfg(
|
||||
|
||||
@@ -7,7 +7,7 @@ import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { resetDirectoryCache } from "../infra/outbound/target-resolver.js";
|
||||
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
import { resolveAccountEntry } from "../routing/account-lookup.js";
|
||||
import { resolveAccountEntry, resolveNormalizedAccountEntry } from "../routing/account-lookup.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
@@ -162,13 +162,15 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
if (!normalizedAccountId) {
|
||||
return undefined;
|
||||
}
|
||||
const matchKey = Object.keys(channelConfig.accounts).find(
|
||||
(key) => normalizeAccountId(key) === normalizedAccountId,
|
||||
const match = resolveNormalizedAccountEntry(
|
||||
channelConfig.accounts,
|
||||
normalizedAccountId,
|
||||
normalizeAccountId,
|
||||
);
|
||||
if (!matchKey) {
|
||||
if (typeof match?.healthMonitor?.enabled !== "boolean") {
|
||||
return undefined;
|
||||
}
|
||||
return channelConfig.accounts[matchKey]?.healthMonitor?.enabled;
|
||||
return match.healthMonitor.enabled;
|
||||
};
|
||||
|
||||
const isHealthMonitorEnabled = (channelId: ChannelId, accountId: string): boolean => {
|
||||
|
||||
@@ -6,7 +6,7 @@ export {
|
||||
mergeAccountConfig,
|
||||
} from "../channels/plugins/account-helpers.js";
|
||||
export { normalizeChatType } from "../channels/chat-type.js";
|
||||
export { resolveAccountEntry } from "../routing/account-lookup.js";
|
||||
export { resolveAccountEntry, resolveNormalizedAccountEntry } from "../routing/account-lookup.js";
|
||||
export {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAccountEntry } from "./account-lookup.js";
|
||||
import { resolveAccountEntry, resolveNormalizedAccountEntry } from "./account-lookup.js";
|
||||
|
||||
describe("resolveAccountEntry", () => {
|
||||
it("resolves direct and case-insensitive account keys", () => {
|
||||
@@ -17,3 +17,26 @@ describe("resolveAccountEntry", () => {
|
||||
expect(resolveAccountEntry(accounts, "default")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveNormalizedAccountEntry", () => {
|
||||
it("resolves normalized account keys with a custom normalizer", () => {
|
||||
const accounts = {
|
||||
"Ops Team": { id: "ops" },
|
||||
};
|
||||
|
||||
expect(
|
||||
resolveNormalizedAccountEntry(accounts, "ops-team", (accountId) =>
|
||||
accountId.trim().toLowerCase().replaceAll(" ", "-"),
|
||||
),
|
||||
).toEqual({ id: "ops" });
|
||||
});
|
||||
|
||||
it("ignores prototype-chain values", () => {
|
||||
const inherited = { default: { id: "polluted" } };
|
||||
const accounts = Object.create(inherited) as Record<string, { id: string }>;
|
||||
|
||||
expect(
|
||||
resolveNormalizedAccountEntry(accounts, "default", (accountId) => accountId),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,3 +12,19 @@ export function resolveAccountEntry<T>(
|
||||
const matchKey = Object.keys(accounts).find((key) => key.toLowerCase() === normalized);
|
||||
return matchKey ? accounts[matchKey] : undefined;
|
||||
}
|
||||
|
||||
export function resolveNormalizedAccountEntry<T>(
|
||||
accounts: Record<string, T> | undefined,
|
||||
accountId: string,
|
||||
normalizeAccountId: (accountId: string) => string,
|
||||
): T | undefined {
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
if (Object.hasOwn(accounts, accountId)) {
|
||||
return accounts[accountId];
|
||||
}
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
|
||||
return matchKey ? accounts[matchKey] : undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user