Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

411 lines
13 KiB
TypeScript

/**
* Config resolution for the BlueSky plugin. Merges per-account settings,
* top-level character settings, and env vars (in that priority order) into a
* validated `BlueSkyConfig`, and exposes the account-enumeration and
* account-id normalization helpers (`listBlueSkyAccountIds`,
* `normalizeBlueSkyAccountId`, `readBlueSkyAccountId`,
* `resolveDefaultBlueSkyAccountId`) plus the per-setting typed getters
* (intervals, limits, feature flags) used by the service and agent manager.
*/
import { ElizaError, type IAgentRuntime } from "@elizaos/core";
import {
BLUESKY_ACTION_INTERVAL,
BLUESKY_MAX_ACTIONS,
BLUESKY_POLL_INTERVAL,
BLUESKY_POST_INTERVAL_MAX,
BLUESKY_POST_INTERVAL_MIN,
BLUESKY_SERVICE_URL,
type BlueSkyConfig,
BlueSkyConfigSchema,
} from "../types";
export type { BlueSkyConfig };
export type ResolvedBlueSkyConfig = BlueSkyConfig & { accountId: string };
export const DEFAULT_BLUESKY_ACCOUNT_ID = "default";
type RawBlueSkyAccountConfig = Partial<BlueSkyConfig> &
Record<string, unknown> & {
accountId?: string;
id?: string;
};
type BlueSkyMultiAccountConfig = RawBlueSkyAccountConfig & {
accounts?: Record<string, RawBlueSkyAccountConfig>;
};
export function getApiKeyOptional(
runtime: IAgentRuntime,
key: string,
): string | undefined {
const value = runtime.getSetting(key);
return typeof value === "string" ? value : undefined;
}
function stringSetting(
runtime: IAgentRuntime,
key: string,
): string | undefined {
const value = runtime.getSetting(key);
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function characterConfig(runtime: IAgentRuntime): BlueSkyMultiAccountConfig {
const settings = runtime.character.settings as
| Record<string, unknown>
| undefined;
const raw = settings?.bluesky;
return raw && typeof raw === "object"
? (raw as BlueSkyMultiAccountConfig)
: {};
}
function parseAccountsJson(
runtime: IAgentRuntime,
): Record<string, RawBlueSkyAccountConfig> {
const raw = stringSetting(runtime, "BLUESKY_ACCOUNTS");
if (!raw) return {};
try {
const parsed = JSON.parse(raw) as unknown;
if (Array.isArray(parsed)) {
return Object.fromEntries(
parsed
.filter(
(item): item is RawBlueSkyAccountConfig =>
Boolean(item) && typeof item === "object",
)
.map((item) => [
normalizeBlueSkyAccountId(item.accountId ?? item.id),
item,
]),
);
}
return parsed && typeof parsed === "object"
? (parsed as Record<string, RawBlueSkyAccountConfig>)
: {};
} catch (error) {
throw new ElizaError("BlueSky accounts config is not valid JSON.", {
code: "BLUESKY_CONFIG_INVALID",
cause: error,
context: { setting: "BLUESKY_ACCOUNTS" },
severity: "fatal",
});
}
}
function allAccountConfigs(
runtime: IAgentRuntime,
): Record<string, RawBlueSkyAccountConfig> {
return {
...(characterConfig(runtime).accounts ?? {}),
...parseAccountsJson(runtime),
};
}
function accountConfig(
runtime: IAgentRuntime,
accountId: string,
): RawBlueSkyAccountConfig {
const accounts = allAccountConfigs(runtime);
return (
accounts[accountId] ?? accounts[normalizeBlueSkyAccountId(accountId)] ?? {}
);
}
function readRawField(
record: RawBlueSkyAccountConfig | undefined,
keys: string[],
): string | undefined {
if (!record) return undefined;
for (const key of keys) {
const value = record[key];
if (typeof value === "string" && value.trim()) return value.trim();
if (typeof value === "number" || typeof value === "boolean")
return String(value);
}
return undefined;
}
function boolValue(value: unknown, fallback = false): boolean {
if (typeof value === "boolean") return value;
if (typeof value === "string") return value.trim().toLowerCase() === "true";
return fallback;
}
function intValue(value: unknown, fallback: number): number {
const parsed = Number.parseInt(String(value ?? ""), 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
export function normalizeBlueSkyAccountId(accountId?: unknown): string {
if (typeof accountId !== "string") return DEFAULT_BLUESKY_ACCOUNT_ID;
const trimmed = accountId.trim();
return trimmed || DEFAULT_BLUESKY_ACCOUNT_ID;
}
export function listBlueSkyAccountIds(runtime: IAgentRuntime): string[] {
const ids = new Set<string>();
const config = characterConfig(runtime);
if (
stringSetting(runtime, "BLUESKY_HANDLE") ||
(config.handle && config.password)
) {
ids.add(DEFAULT_BLUESKY_ACCOUNT_ID);
}
for (const id of Object.keys(allAccountConfigs(runtime))) {
ids.add(normalizeBlueSkyAccountId(id));
}
return Array.from(
ids.size ? ids : new Set([DEFAULT_BLUESKY_ACCOUNT_ID]),
).sort((a, b) => a.localeCompare(b));
}
export function resolveDefaultBlueSkyAccountId(runtime: IAgentRuntime): string {
const requested =
stringSetting(runtime, "BLUESKY_DEFAULT_ACCOUNT_ID") ??
stringSetting(runtime, "BLUESKY_ACCOUNT_ID");
if (requested) return normalizeBlueSkyAccountId(requested);
const ids = listBlueSkyAccountIds(runtime);
return ids.includes(DEFAULT_BLUESKY_ACCOUNT_ID)
? DEFAULT_BLUESKY_ACCOUNT_ID
: (ids[0] ?? DEFAULT_BLUESKY_ACCOUNT_ID);
}
export function readBlueSkyAccountId(
...sources: unknown[]
): string | undefined {
for (const source of sources) {
if (!source || typeof source !== "object") continue;
const record = source as Record<string, unknown>;
const parameters =
record.parameters && typeof record.parameters === "object"
? (record.parameters as Record<string, unknown>)
: {};
const data =
record.data && typeof record.data === "object"
? (record.data as Record<string, unknown>)
: {};
const metadata =
record.metadata && typeof record.metadata === "object"
? (record.metadata as Record<string, unknown>)
: {};
const bluesky =
data.bluesky && typeof data.bluesky === "object"
? (data.bluesky as Record<string, unknown>)
: {};
const value =
record.accountId ??
parameters.accountId ??
data.accountId ??
bluesky.accountId ??
metadata.accountId;
if (typeof value === "string" && value.trim())
return normalizeBlueSkyAccountId(value);
}
return undefined;
}
export function hasBlueSkyEnabled(
runtime: IAgentRuntime,
accountId?: string | null,
): boolean {
const normalizedAccountId = normalizeBlueSkyAccountId(
accountId ?? resolveDefaultBlueSkyAccountId(runtime),
);
const base = characterConfig(runtime);
const account = accountConfig(runtime, normalizedAccountId);
const allowEnv = normalizedAccountId === DEFAULT_BLUESKY_ACCOUNT_ID;
const enabled =
readRawField(account, ["enabled", "BLUESKY_ENABLED"]) ??
readRawField(base, ["enabled", "BLUESKY_ENABLED"]) ??
(allowEnv ? runtime.getSetting("BLUESKY_ENABLED") : undefined);
if (enabled) return String(enabled).toLowerCase() === "true";
return Boolean(
(readRawField(account, ["handle", "BLUESKY_HANDLE"]) ??
readRawField(base, ["handle", "BLUESKY_HANDLE"]) ??
(allowEnv ? stringSetting(runtime, "BLUESKY_HANDLE") : undefined)) &&
(readRawField(account, ["password", "BLUESKY_PASSWORD"]) ??
readRawField(base, ["password", "BLUESKY_PASSWORD"]) ??
(allowEnv ? stringSetting(runtime, "BLUESKY_PASSWORD") : undefined)),
);
}
export function validateBlueSkyConfig(
runtime: IAgentRuntime,
accountId?: string | null,
): ResolvedBlueSkyConfig {
const normalizedAccountId = normalizeBlueSkyAccountId(
accountId ?? resolveDefaultBlueSkyAccountId(runtime),
);
const base = characterConfig(runtime);
const account = accountConfig(runtime, normalizedAccountId);
const allowEnv = normalizedAccountId === DEFAULT_BLUESKY_ACCOUNT_ID;
const result = BlueSkyConfigSchema.safeParse({
handle:
readRawField(account, ["handle", "BLUESKY_HANDLE"]) ??
readRawField(base, ["handle", "BLUESKY_HANDLE"]) ??
(allowEnv ? stringSetting(runtime, "BLUESKY_HANDLE") : undefined) ??
"",
password:
readRawField(account, ["password", "BLUESKY_PASSWORD"]) ??
readRawField(base, ["password", "BLUESKY_PASSWORD"]) ??
(allowEnv ? stringSetting(runtime, "BLUESKY_PASSWORD") : undefined) ??
"",
service:
readRawField(account, ["service", "BLUESKY_SERVICE"]) ??
readRawField(base, ["service", "BLUESKY_SERVICE"]) ??
(allowEnv ? stringSetting(runtime, "BLUESKY_SERVICE") : undefined) ??
BLUESKY_SERVICE_URL,
dryRun: boolValue(
readRawField(account, ["dryRun", "BLUESKY_DRY_RUN"]) ??
readRawField(base, ["dryRun", "BLUESKY_DRY_RUN"]) ??
(allowEnv ? runtime.getSetting("BLUESKY_DRY_RUN") : undefined),
),
pollInterval: intValue(
readRawField(account, ["pollInterval", "BLUESKY_POLL_INTERVAL"]) ??
readRawField(base, ["pollInterval", "BLUESKY_POLL_INTERVAL"]) ??
(allowEnv ? runtime.getSetting("BLUESKY_POLL_INTERVAL") : undefined),
BLUESKY_POLL_INTERVAL,
),
enablePost:
String(
readRawField(account, ["enablePost", "BLUESKY_ENABLE_POSTING"]) ??
readRawField(base, ["enablePost", "BLUESKY_ENABLE_POSTING"]) ??
(allowEnv
? runtime.getSetting("BLUESKY_ENABLE_POSTING")
: undefined) ??
"true",
).toLowerCase() !== "false",
postIntervalMin: intValue(
readRawField(account, ["postIntervalMin", "BLUESKY_POST_INTERVAL_MIN"]) ??
readRawField(base, ["postIntervalMin", "BLUESKY_POST_INTERVAL_MIN"]) ??
(allowEnv
? runtime.getSetting("BLUESKY_POST_INTERVAL_MIN")
: undefined),
BLUESKY_POST_INTERVAL_MIN,
),
postIntervalMax: intValue(
readRawField(account, ["postIntervalMax", "BLUESKY_POST_INTERVAL_MAX"]) ??
readRawField(base, ["postIntervalMax", "BLUESKY_POST_INTERVAL_MAX"]) ??
(allowEnv
? runtime.getSetting("BLUESKY_POST_INTERVAL_MAX")
: undefined),
BLUESKY_POST_INTERVAL_MAX,
),
enableActionProcessing:
String(
readRawField(account, [
"enableActionProcessing",
"BLUESKY_ENABLE_ACTION_PROCESSING",
]) ??
readRawField(base, [
"enableActionProcessing",
"BLUESKY_ENABLE_ACTION_PROCESSING",
]) ??
(allowEnv
? runtime.getSetting("BLUESKY_ENABLE_ACTION_PROCESSING")
: undefined) ??
"true",
).toLowerCase() !== "false",
actionInterval: intValue(
readRawField(account, ["actionInterval", "BLUESKY_ACTION_INTERVAL"]) ??
readRawField(base, ["actionInterval", "BLUESKY_ACTION_INTERVAL"]) ??
(allowEnv ? runtime.getSetting("BLUESKY_ACTION_INTERVAL") : undefined),
BLUESKY_ACTION_INTERVAL,
),
postImmediately: boolValue(
readRawField(account, ["postImmediately", "BLUESKY_POST_IMMEDIATELY"]) ??
readRawField(base, ["postImmediately", "BLUESKY_POST_IMMEDIATELY"]) ??
(allowEnv ? runtime.getSetting("BLUESKY_POST_IMMEDIATELY") : undefined),
),
maxActionsProcessing: intValue(
readRawField(account, [
"maxActionsProcessing",
"BLUESKY_MAX_ACTIONS_PROCESSING",
]) ??
readRawField(base, [
"maxActionsProcessing",
"BLUESKY_MAX_ACTIONS_PROCESSING",
]) ??
(allowEnv
? runtime.getSetting("BLUESKY_MAX_ACTIONS_PROCESSING")
: undefined),
BLUESKY_MAX_ACTIONS,
),
enableDMs:
String(
readRawField(account, ["enableDMs", "BLUESKY_ENABLE_DMS"]) ??
readRawField(base, ["enableDMs", "BLUESKY_ENABLE_DMS"]) ??
(allowEnv ? runtime.getSetting("BLUESKY_ENABLE_DMS") : undefined) ??
"true",
).toLowerCase() !== "false",
});
if (!result.success) {
const errors =
(
result.error as { errors?: { path: string[]; message: string }[] }
).errors
?.map((e) => `${e.path.join(".")}: ${e.message}`)
.join(", ") || result.error.toString();
throw new Error(`Invalid BlueSky configuration: ${errors}`);
}
return { ...result.data, accountId: normalizedAccountId };
}
export function getPollInterval(
runtime: IAgentRuntime,
accountId?: string,
): number {
const config = validateBlueSkyConfig(runtime, accountId);
return config.pollInterval * 1000;
}
export function getActionInterval(
runtime: IAgentRuntime,
accountId?: string,
): number {
const config = validateBlueSkyConfig(runtime, accountId);
return config.actionInterval * 1000;
}
export function getMaxActionsProcessing(
runtime: IAgentRuntime,
accountId?: string,
): number {
const config = validateBlueSkyConfig(runtime, accountId);
return config.maxActionsProcessing;
}
export function isPostingEnabled(
runtime: IAgentRuntime,
accountId?: string,
): boolean {
const config = validateBlueSkyConfig(runtime, accountId);
return config.enablePost;
}
export function shouldPostImmediately(
runtime: IAgentRuntime,
accountId?: string,
): boolean {
const config = validateBlueSkyConfig(runtime, accountId);
return config.postImmediately;
}
export function getPostIntervalRange(
runtime: IAgentRuntime,
accountId?: string,
): {
min: number;
max: number;
} {
const config = validateBlueSkyConfig(runtime, accountId);
return {
min: config.postIntervalMin * 1000,
max: config.postIntervalMax * 1000,
};
}