Download src/routing/binding-scope.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 4.27 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/routing/binding-scope.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/routing/binding-scope.ts
-
curl -L -o binding-scope.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/routing/binding-scope.ts
4.27 kB
| // Binding scope helpers normalize route binding scope values. | |
| import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; | |
| import { normalizeChatChannelId } from "../channels/ids.js"; | |
| import type { AgentRouteBinding } from "../config/types.agents.js"; | |
| import { normalizeAccountId, normalizeAgentId } from "./session-key.js"; | |
| // Route binding scopes constrain a configured agent/account binding to a guild, | |
| // team, group space, and optionally channel/platform role ids. | |
| type RouteBindingScopeConstraint = { | |
| guildId?: string | null; | |
| teamId?: string | null; | |
| roles?: string[] | null; | |
| }; | |
| type RouteBindingScope = { | |
| guildId?: string | null; | |
| teamId?: string | null; | |
| groupSpace?: string | null; | |
| memberRoleIds?: Iterable<string> | null; | |
| }; | |
| type NormalizedRouteBindingMatch = { | |
| agentId: string; | |
| accountId: string; | |
| channelId: string; | |
| }; | |
| export function normalizeRouteBindingId(value: unknown): string { | |
| if (typeof value === "string") { | |
| return value.trim(); | |
| } | |
| if (typeof value === "number" || typeof value === "bigint") { | |
| return String(value).trim(); | |
| } | |
| return ""; | |
| } | |
| export function normalizeRouteBindingRoles(value: string[] | null | undefined): string[] | null { | |
| return Array.isArray(value) && value.length > 0 ? value : null; | |
| } | |
| export function normalizeRouteBindingChannelId(raw?: string | null): string | null { | |
| const normalized = normalizeChatChannelId(raw); | |
| if (normalized) { | |
| return normalized; | |
| } | |
| const fallback = normalizeLowercaseStringOrEmpty(raw); | |
| return fallback || null; | |
| } | |
| // Convert a binding match into the same canonical ids used by session routing. | |
| // Diagnostics include implicit defaults; outbound account selection requires explicit bindings. | |
| export function resolveNormalizedRouteBindingMatch( | |
| binding: AgentRouteBinding, | |
| options?: { includeImplicitDefaultAccount?: boolean }, | |
| ): NormalizedRouteBindingMatch | null { | |
| if (!binding || typeof binding !== "object") { | |
| return null; | |
| } | |
| const match = binding.match; | |
| if (!match || typeof match !== "object") { | |
| return null; | |
| } | |
| const channelId = normalizeRouteBindingChannelId(match.channel); | |
| if (!channelId) { | |
| return null; | |
| } | |
| if (match.accountId !== undefined && typeof match.accountId !== "string") { | |
| return null; | |
| } | |
| const accountId = match.accountId?.trim(); | |
| if (accountId === "*" || (!accountId && !options?.includeImplicitDefaultAccount)) { | |
| return null; | |
| } | |
| return { | |
| agentId: normalizeAgentId(binding.agentId), | |
| accountId: normalizeAccountId(accountId), | |
| channelId, | |
| }; | |
| } | |
| function scopeIdMatches(params: { | |
| constraint: string | null | undefined; | |
| exact: string; | |
| groupSpace: string; | |
| }): boolean { | |
| if (!params.constraint) { | |
| return true; | |
| } | |
| return params.constraint === params.exact || params.constraint === params.groupSpace; | |
| } | |
| function hasRoleLookup( | |
| memberRoleIds: Iterable<string>, | |
| ): memberRoleIds is Iterable<string> & { has(roleId: string): boolean } { | |
| return typeof (memberRoleIds as { has?: unknown }).has === "function"; | |
| } | |
| function hasAnyRouteBindingRole( | |
| roles: readonly string[], | |
| memberRoleIds: Iterable<string> | null | undefined, | |
| ): boolean { | |
| if (!memberRoleIds) { | |
| return false; | |
| } | |
| if (hasRoleLookup(memberRoleIds)) { | |
| return roles.some((role) => memberRoleIds.has(role)); | |
| } | |
| // Most callers pass a Set, but arrays/iterables from channel adapters are | |
| // accepted to avoid forcing allocation at every routing call site. | |
| const memberRoleIdSet = new Set(memberRoleIds); | |
| return roles.some((role) => memberRoleIdSet.has(role)); | |
| } | |
| export function routeBindingScopeMatches( | |
| constraint: RouteBindingScopeConstraint, | |
| scope: RouteBindingScope, | |
| ): boolean { | |
| const guildId = normalizeRouteBindingId(scope.guildId); | |
| const teamId = normalizeRouteBindingId(scope.teamId); | |
| const groupSpace = normalizeRouteBindingId(scope.groupSpace); | |
| if (!scopeIdMatches({ constraint: constraint.guildId, exact: guildId, groupSpace })) { | |
| return false; | |
| } | |
| if (!scopeIdMatches({ constraint: constraint.teamId, exact: teamId, groupSpace })) { | |
| return false; | |
| } | |
| const roles = normalizeRouteBindingRoles(constraint.roles); | |
| if (!roles) { | |
| return true; | |
| } | |
| return hasAnyRouteBindingRole(roles, scope.memberRoleIds); | |
| } | |