openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
587 lines (586 loc) • 21 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import "./agent-scope-MrLta7Pq.js";
import { n as normalizeAccountId } from "./account-id-Df9e41E6.js";
import { f as resolveAgentIdFromSessionKey, h as sanitizeAgentId } from "./session-key-B_NoIfpX.js";
import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir, r as resolveAgentConfig } from "./agent-scope-config-CgCYpZfK.js";
import { t as getChannelPlugin } from "./registry-MkxNn1Ue.js";
import "./plugins-t2ejWcVy.js";
import { r as listConfiguredBindings } from "./bindings-CI-O7TMQ.js";
import { r as pickFirstExistingAgentId } from "./resolve-route-xcIS8NFL.js";
import { createHash } from "node:crypto";
//#region src/acp/persistent-bindings.types.ts
/** Types and normalization helpers for configured channel-to-ACP persistent bindings. */
/** Normalizes binding mode, defaulting to persistent sessions. */
function normalizeMode(value) {
return normalizeOptionalLowercaseString(value) === "oneshot" ? "oneshot" : "persistent";
}
/** Extracts supported ACP binding config keys from unknown plugin config. */
function normalizeBindingConfig(raw) {
if (!raw || typeof raw !== "object") return {};
const shape = raw;
const mode = normalizeOptionalString(shape.mode);
return {
mode: mode ? normalizeMode(mode) : void 0,
cwd: normalizeOptionalString(shape.cwd),
backend: normalizeOptionalString(shape.backend),
label: normalizeOptionalString(shape.label)
};
}
function buildBindingHash(params) {
return createHash("sha256").update(`${params.channel}:${params.accountId}:${params.conversationId}`).digest("hex").slice(0, 16);
}
/** Builds the stable generated ACP session key for a configured binding. */
function buildConfiguredAcpSessionKey(spec) {
const hash = buildBindingHash({
channel: spec.channel,
accountId: spec.accountId,
conversationId: spec.conversationId
});
return `agent:${sanitizeAgentId(spec.agentId)}:acp:binding:${spec.channel}:${spec.accountId}:${hash}`;
}
/** Converts a configured ACP binding spec into an outbound session binding record. */
function toConfiguredAcpBindingRecord(spec) {
return {
bindingId: `config:acp:${spec.channel}:${spec.accountId}:${spec.conversationId}`,
targetSessionKey: buildConfiguredAcpSessionKey(spec),
targetKind: "session",
conversation: {
channel: spec.channel,
accountId: spec.accountId,
conversationId: spec.conversationId,
parentConversationId: spec.parentConversationId
},
status: "active",
boundAt: 0,
metadata: {
source: "config",
mode: spec.mode,
agentId: spec.agentId,
...spec.acpAgentId ? { acpAgentId: spec.acpAgentId } : {},
label: spec.label,
...spec.backend ? { backend: spec.backend } : {},
...spec.cwd ? { cwd: spec.cwd } : {}
}
};
}
/** Parses generated configured-binding session keys back to channel/account identity. */
function parseConfiguredAcpSessionKey(sessionKey) {
const trimmed = sessionKey.trim();
if (!trimmed.startsWith("agent:")) return null;
const rest = trimmed.slice(trimmed.indexOf(":") + 1);
const nextSeparator = rest.indexOf(":");
if (nextSeparator === -1) return null;
const tokens = rest.slice(nextSeparator + 1).split(":");
if (tokens.length !== 5 || tokens[0] !== "acp" || tokens[1] !== "binding") return null;
const channel = normalizeOptionalLowercaseString(tokens[2]);
if (!channel) return null;
return {
channel,
accountId: normalizeAccountId(tokens[3] ?? "default")
};
}
function resolveConfiguredAcpBindingSpecFromRecord(record) {
if (record.targetKind !== "session") return null;
const conversationId = record.conversation.conversationId.trim();
if (!conversationId) return null;
const agentId = normalizeOptionalString(record.metadata?.agentId) ?? resolveAgentIdFromSessionKey(record.targetSessionKey);
if (!agentId) return null;
return {
channel: record.conversation.channel,
accountId: normalizeAccountId(record.conversation.accountId),
conversationId,
parentConversationId: normalizeOptionalString(record.conversation.parentConversationId),
agentId,
acpAgentId: normalizeOptionalString(record.metadata?.acpAgentId),
mode: normalizeMode(record.metadata?.mode),
cwd: normalizeOptionalString(record.metadata?.cwd),
backend: normalizeOptionalString(record.metadata?.backend),
label: normalizeOptionalString(record.metadata?.label)
};
}
function toResolvedConfiguredAcpBinding(record) {
const spec = resolveConfiguredAcpBindingSpecFromRecord(record);
if (!spec) return null;
return {
spec,
record
};
}
//#endregion
//#region src/channels/plugins/acp-configured-binding-consumer.ts
/**
* ACP configured binding consumer.
*
* Converts channel configured-binding rules into persistent ACP binding records.
*/
function resolveAgentRuntimeAcpDefaults(params) {
const ownerAgentId = normalizeLowercaseStringOrEmpty(params.ownerAgentId);
const agent = params.cfg.agents?.list?.find((entry) => normalizeOptionalLowercaseString(entry.id) === ownerAgentId);
if (!agent || agent.runtime?.type !== "acp") return {};
return {
acpAgentId: normalizeOptionalString(agent.runtime.acp?.agent),
mode: normalizeOptionalString(agent.runtime.acp?.mode),
cwd: normalizeOptionalString(agent.runtime.acp?.cwd),
backend: normalizeOptionalString(agent.runtime.acp?.backend)
};
}
function resolveConfiguredBindingWorkspaceCwd(params) {
if (normalizeOptionalString(resolveAgentConfig(params.cfg, params.agentId)?.workspace)) return resolveAgentWorkspaceDir(params.cfg, params.agentId);
if (params.agentId === resolveDefaultAgentId(params.cfg)) {
if (normalizeOptionalString(params.cfg.agents?.defaults?.workspace)) return resolveAgentWorkspaceDir(params.cfg, params.agentId);
}
}
function buildConfiguredAcpSpec(params) {
return {
channel: params.channel,
accountId: params.accountId,
conversationId: params.conversation.conversationId,
parentConversationId: params.conversation.parentConversationId,
agentId: params.agentId,
acpAgentId: params.acpAgentId,
mode: params.mode,
cwd: params.cwd,
backend: params.backend,
label: params.label
};
}
function buildAcpTargetFactory(params) {
if (params.binding.type !== "acp") return null;
const runtimeDefaults = resolveAgentRuntimeAcpDefaults({
cfg: params.cfg,
ownerAgentId: params.agentId
});
const bindingOverrides = normalizeBindingConfig(params.binding.acp);
const mode = normalizeMode(bindingOverrides.mode ?? runtimeDefaults.mode);
const cwd = bindingOverrides.cwd ?? runtimeDefaults.cwd ?? resolveConfiguredBindingWorkspaceCwd({
cfg: params.cfg,
agentId: params.agentId
});
const backend = bindingOverrides.backend ?? runtimeDefaults.backend;
const label = bindingOverrides.label;
const acpAgentId = normalizeOptionalString(runtimeDefaults.acpAgentId);
return {
driverId: "acp",
materialize: ({ accountId, conversation }) => {
const spec = buildConfiguredAcpSpec({
channel: params.channel,
accountId,
conversation,
agentId: params.agentId,
acpAgentId,
mode,
cwd,
backend,
label
});
return {
record: toConfiguredAcpBindingRecord(spec),
statefulTarget: {
kind: "stateful",
driverId: "acp",
sessionKey: buildConfiguredAcpSessionKey(spec),
agentId: params.agentId,
...label ? { label } : {}
}
};
}
};
}
/**
* Configured binding consumer that materializes ACP persistent or oneshot targets.
*/
const acpConfiguredBindingConsumer = {
id: "acp",
supports: (binding) => binding.type === "acp",
buildTargetFactory: (params) => buildAcpTargetFactory({
cfg: params.cfg,
binding: params.binding,
channel: params.channel,
agentId: params.agentId
}),
parseSessionKey: ({ sessionKey }) => parseConfiguredAcpSessionKey(sessionKey),
matchesSessionKey: ({ sessionKey, materializedTarget }) => materializedTarget.record.targetSessionKey === sessionKey
};
//#endregion
//#region src/channels/plugins/configured-binding-consumers.ts
const registeredConfiguredBindingConsumers = /* @__PURE__ */ new Map();
/**
* Lists registered configured binding consumers in registration order.
*/
function listConfiguredBindingConsumers() {
return [...registeredConfiguredBindingConsumers.values()];
}
/**
* Finds the first configured binding consumer that supports a raw binding rule.
*/
function resolveConfiguredBindingConsumer(binding) {
for (const consumer of listConfiguredBindingConsumers()) if (consumer.supports(binding)) return consumer;
return null;
}
/**
* Registers a configured binding consumer idempotently by trimmed id.
*/
function registerConfiguredBindingConsumer(consumer) {
const id = consumer.id.trim();
if (!id) throw new Error("Configured binding consumer id is required");
if (registeredConfiguredBindingConsumers.get(id)) return;
registeredConfiguredBindingConsumers.set(id, {
...consumer,
id
});
}
//#endregion
//#region src/channels/plugins/configured-binding-builtins.ts
/**
* Configured binding built-in registration.
*
* Registers core configured binding consumers exactly when the registry facade needs them.
*/
/**
* Registers configured binding consumers bundled with core.
*/
function ensureConfiguredBindingBuiltinsRegistered() {
registerConfiguredBindingConsumer(acpConfiguredBindingConsumer);
}
//#endregion
//#region src/channels/plugins/binding-provider.ts
/**
* Returns the configured binding provider exposed by a channel plugin, when present.
*/
function resolveChannelConfiguredBindingProvider(plugin) {
return plugin?.bindings;
}
//#endregion
//#region src/channels/plugins/configured-binding-compiler.ts
/**
* Configured binding compiler.
*
* Compiles config rules into channel/provider-specific binding registry entries.
*/
function resolveLoadedChannelPlugin(channel) {
const normalized = normalizeOptionalLowercaseString(channel);
if (!normalized) return;
return getChannelPlugin(normalized);
}
function resolveConfiguredBindingAdapter(channel) {
const normalized = normalizeOptionalLowercaseString(channel);
if (!normalized) return null;
const plugin = resolveLoadedChannelPlugin(normalized);
const provider = resolveChannelConfiguredBindingProvider(plugin);
if (!plugin || !provider || !provider.compileConfiguredBinding || !provider.matchInboundConversation) return null;
return {
channel: plugin.id,
provider
};
}
function resolveBindingConversationId(binding) {
return normalizeOptionalString(binding.match?.peer?.id) ?? null;
}
function compileConfiguredBindingTarget(params) {
return params.provider.compileConfiguredBinding({
binding: params.binding,
conversationId: params.conversationId
});
}
function compileConfiguredBindingRule(params) {
const agentId = pickFirstExistingAgentId(params.cfg, params.binding.agentId ?? "main");
const consumer = resolveConfiguredBindingConsumer(params.binding);
if (!consumer) return null;
const targetFactory = consumer.buildTargetFactory({
cfg: params.cfg,
binding: params.binding,
channel: params.channel,
agentId,
target: params.target,
bindingConversationId: params.bindingConversationId
});
if (!targetFactory) return null;
return {
channel: params.channel,
accountPattern: normalizeOptionalString(params.binding.match.accountId),
binding: params.binding,
bindingConversationId: params.bindingConversationId,
target: params.target,
agentId,
provider: params.provider,
targetFactory
};
}
function pushCompiledRule(target, rule) {
const existing = target.get(rule.channel);
if (existing) {
existing.push(rule);
return;
}
target.set(rule.channel, [rule]);
}
function compileConfiguredBindingRegistry(params) {
const rulesByChannel = /* @__PURE__ */ new Map();
for (const binding of listConfiguredBindings(params.cfg)) {
const bindingConversationId = resolveBindingConversationId(binding);
if (!bindingConversationId) continue;
const resolvedChannel = resolveConfiguredBindingAdapter(binding.match.channel);
if (!resolvedChannel) continue;
const target = compileConfiguredBindingTarget({
provider: resolvedChannel.provider,
binding,
conversationId: bindingConversationId
});
if (!target) continue;
const rule = compileConfiguredBindingRule({
cfg: params.cfg,
channel: resolvedChannel.channel,
binding,
target,
bindingConversationId,
provider: resolvedChannel.provider
});
if (!rule) continue;
pushCompiledRule(rulesByChannel, rule);
}
return { rulesByChannel };
}
function resolveCompiledBindingRegistry(cfg) {
return compileConfiguredBindingRegistry({ cfg });
}
function primeCompiledBindingRegistry(cfg) {
return compileConfiguredBindingRegistry({ cfg });
}
function countCompiledBindingRegistry(registry) {
return {
bindingCount: [...registry.rulesByChannel.values()].reduce((sum, rules) => sum + rules.length, 0),
channelCount: registry.rulesByChannel.size
};
}
//#endregion
//#region src/channels/plugins/configured-binding-match.ts
/**
* Configured binding matching helpers.
*
* Matches compiled binding rules against inbound conversations and materializes targets.
*/
/**
* Ranks account pattern matches for configured binding rules.
*/
function resolveAccountMatchPriority(match, actual) {
const trimmed = (match ?? "").trim();
if (!trimmed) return actual === "default" ? 2 : 0;
if (trimmed === "*") return 1;
return normalizeAccountId(trimmed) === actual ? 2 : 0;
}
function matchCompiledBindingConversation(params) {
return params.rule.provider.matchInboundConversation({
binding: params.rule.binding,
compiledBinding: params.rule.target,
conversationId: params.conversationId,
parentConversationId: params.parentConversationId
});
}
/**
* Normalizes a raw channel id into a configured-binding channel id.
*/
function resolveCompiledBindingChannel(raw) {
const normalized = normalizeOptionalLowercaseString(raw);
return normalized ? normalized : null;
}
/**
* Converts an outbound conversation ref into configured-binding match input.
*/
function toConfiguredBindingConversationRef(conversation) {
const channel = resolveCompiledBindingChannel(conversation.channel);
const conversationId = conversation.conversationId.trim();
if (!channel || !conversationId) return null;
return {
channel,
accountId: normalizeAccountId(conversation.accountId),
conversationId,
parentConversationId: normalizeOptionalString(conversation.parentConversationId)
};
}
/**
* Materializes a configured binding record from the winning rule and conversation.
*/
function materializeConfiguredBindingRecord(params) {
return params.rule.targetFactory.materialize({
accountId: normalizeAccountId(params.accountId),
conversation: params.conversation
});
}
/**
* Resolves the best configured binding rule for a conversation.
*/
function resolveMatchingConfiguredBinding(params) {
if (!params.conversation) return null;
let wildcardMatch = null;
let exactMatch = null;
for (const rule of params.rules) {
const accountMatchPriority = resolveAccountMatchPriority(rule.accountPattern, params.conversation.accountId);
if (accountMatchPriority === 0) continue;
const match = matchCompiledBindingConversation({
rule,
conversationId: params.conversation.conversationId,
parentConversationId: params.conversation.parentConversationId
});
if (!match) continue;
const matchPriority = match.matchPriority ?? 0;
if (accountMatchPriority === 2) {
if (!exactMatch || matchPriority > (exactMatch.match.matchPriority ?? 0)) exactMatch = {
rule,
match
};
continue;
}
if (!wildcardMatch || matchPriority > (wildcardMatch.match.matchPriority ?? 0)) wildcardMatch = {
rule,
match
};
}
return exactMatch ?? wildcardMatch;
}
//#endregion
//#region src/channels/plugins/configured-binding-session-lookup.ts
/**
* Resolves a configured binding record from a stateful target session key.
*/
function resolveConfiguredBindingRecordBySessionKeyFromRegistry(params) {
const sessionKey = params.sessionKey.trim();
if (!sessionKey) return null;
for (const consumer of listConfiguredBindingConsumers()) {
const parsed = consumer.parseSessionKey?.({ sessionKey });
if (!parsed) continue;
const channel = resolveCompiledBindingChannel(parsed.channel);
if (!channel) continue;
const rules = params.registry.rulesByChannel.get(channel);
if (!rules || rules.length === 0) continue;
let wildcardMatch = null;
let exactMatch = null;
for (const rule of rules) {
if (rule.targetFactory.driverId !== consumer.id) continue;
const accountMatchPriority = resolveAccountMatchPriority(rule.accountPattern, parsed.accountId);
if (accountMatchPriority === 0) continue;
const materializedTarget = materializeConfiguredBindingRecord({
rule,
accountId: parsed.accountId,
conversation: rule.target
});
if (consumer.matchesSessionKey?.({
sessionKey,
compiledBinding: rule,
accountId: parsed.accountId,
materializedTarget
}) ?? materializedTarget.record.targetSessionKey === sessionKey) {
if (accountMatchPriority === 2) {
exactMatch = materializedTarget;
break;
}
wildcardMatch = materializedTarget;
}
}
if (exactMatch) return exactMatch;
if (wildcardMatch) return wildcardMatch;
}
return null;
}
//#endregion
//#region src/channels/plugins/configured-binding-registry.ts
function resolveMaterializedConfiguredBinding(params) {
const conversation = toConfiguredBindingConversationRef(params.conversation);
if (!conversation) return null;
const rules = resolveCompiledBindingRegistry(params.cfg).rulesByChannel.get(conversation.channel);
if (!rules || rules.length === 0) return null;
const resolved = resolveMatchingConfiguredBinding({
rules,
conversation
});
if (!resolved) return null;
return {
conversation,
resolved,
materializedTarget: materializeConfiguredBindingRecord({
rule: resolved.rule,
accountId: conversation.accountId,
conversation: resolved.match
})
};
}
/**
* Warms and counts the compiled configured binding registry for a config snapshot.
*/
function primeConfiguredBindingRegistry$1(params) {
return countCompiledBindingRegistry(primeCompiledBindingRegistry(params.cfg));
}
/**
* Resolves a configured binding record from explicit channel/account/conversation ids.
*/
function resolveConfiguredBindingRecord$1(params) {
const conversation = toConfiguredBindingConversationRef({
channel: params.channel,
accountId: params.accountId,
conversationId: params.conversationId,
parentConversationId: params.parentConversationId
});
if (!conversation) return null;
return resolveConfiguredBindingRecordForConversation$1({
cfg: params.cfg,
conversation
});
}
/**
* Resolves a configured binding record from a normalized conversation reference.
*/
function resolveConfiguredBindingRecordForConversation$1(params) {
const resolved = resolveMaterializedConfiguredBinding(params);
if (!resolved) return null;
return resolved.materializedTarget;
}
/**
* Resolves the full configured binding match, including compiled rule and match diagnostics.
*/
function resolveConfiguredBinding$1(params) {
const resolved = resolveMaterializedConfiguredBinding(params);
if (!resolved) return null;
return {
conversation: resolved.conversation,
compiledBinding: resolved.resolved.rule,
match: resolved.resolved.match,
...resolved.materializedTarget
};
}
/**
* Resolves a configured binding record by the stateful target session key.
*/
function resolveConfiguredBindingRecordBySessionKey$1(params) {
return resolveConfiguredBindingRecordBySessionKeyFromRegistry({
registry: resolveCompiledBindingRegistry(params.cfg),
sessionKey: params.sessionKey
});
}
//#endregion
//#region src/channels/plugins/binding-registry.ts
/**
* Configured binding registry public facade.
*
* Lazily registers built-in binding providers before resolving configured bindings.
*/
function primeConfiguredBindingRegistry(...args) {
ensureConfiguredBindingBuiltinsRegistered();
return primeConfiguredBindingRegistry$1(...args);
}
function resolveConfiguredBindingRecord(...args) {
ensureConfiguredBindingBuiltinsRegistered();
return resolveConfiguredBindingRecord$1(...args);
}
function resolveConfiguredBindingRecordForConversation(...args) {
ensureConfiguredBindingBuiltinsRegistered();
return resolveConfiguredBindingRecordForConversation$1(...args);
}
function resolveConfiguredBinding(...args) {
ensureConfiguredBindingBuiltinsRegistered();
return resolveConfiguredBinding$1(...args);
}
function resolveConfiguredBindingRecordBySessionKey(...args) {
ensureConfiguredBindingBuiltinsRegistered();
return resolveConfiguredBindingRecordBySessionKey$1(...args);
}
//#endregion
export { resolveConfiguredBindingRecordForConversation as a, resolveConfiguredAcpBindingSpecFromRecord as c, resolveConfiguredBindingRecordBySessionKey as i, toResolvedConfiguredAcpBinding as l, resolveConfiguredBinding as n, buildConfiguredAcpSessionKey as o, resolveConfiguredBindingRecord as r, normalizeBindingConfig as s, primeConfiguredBindingRegistry as t };