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
411 lines
13 KiB
TypeScript
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,
|
|
};
|
|
}
|