openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
237 lines (236 loc) • 9.54 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-1VuiLaaR.mjs";
import { l as getActivePluginRegistry } from "./runtime-NsVZTERq.mjs";
import "./active-runtime-registry-Cofpt8d2.mjs";
import { i as normalizeCapabilityProviderId, t as buildCapabilityProviderIndex } from "./provider-registry-shared-Cov0065J.mjs";
import { a as resolvePluginCapabilityProviders, i as resolvePluginCapabilityProvider } from "./capability-provider-runtime-DMiCsZ75.mjs";
import { t as extractTtsDirectiveFacts } from "./directive-facts-HQmjQ9Tk.mjs";
//#region src/tts/provider-registry-core.ts
/** Order speech providers by priority and provider ID for deterministic equal-priority fallback. */
function compareSpeechProviderOrder(left, right) {
const leftOrder = left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) return leftOrder - rightOrder;
return left.id.localeCompare(right.id);
}
/** Create a registry facade with canonical listing, alias lookup, and ID canonicalization. */
function createSpeechProviderRegistry(resolver) {
const buildAliasIndex = (cfg) => buildCapabilityProviderIndex(resolver.listProviders(cfg), "aliases");
const listProviders = (cfg) => [...buildCapabilityProviderIndex(resolver.listProviders(cfg), "canonical").values()];
const getProvider = (providerId, cfg) => {
const normalized = normalizeCapabilityProviderId(providerId);
if (!normalized) return;
return resolver.getProvider(normalized, cfg) ?? buildAliasIndex(cfg).get(normalized);
};
const canonicalizeProviderId = (providerId, cfg) => {
const normalized = normalizeCapabilityProviderId(providerId);
if (!normalized) return;
return getProvider(normalized, cfg)?.id ?? normalized;
};
return {
canonicalizeSpeechProviderId: canonicalizeProviderId,
getSpeechProvider: getProvider,
listSpeechProviders: listProviders
};
}
//#endregion
//#region src/tts/provider-registry.ts
/** Config-aware registry used by setup/status/runtime paths before plugins are loaded. */
const defaultSpeechProviderRegistry = createSpeechProviderRegistry({
getProvider: (providerId, cfg) => resolvePluginCapabilityProvider({
key: "speechProviders",
providerId,
cfg
}),
listProviders: (cfg) => resolvePluginCapabilityProviders({
key: "speechProviders",
cfg
})
});
/** List configured speech providers using manifest/capability discovery. */
const listSpeechProviders = defaultSpeechProviderRegistry.listSpeechProviders;
/** List currently loaded speech providers from the active runtime registry. */
function listLoadedSpeechProviders(_cfg) {
const providers = (getActivePluginRegistry()?.speechProviders ?? []).map((entry) => entry.provider);
return [...buildCapabilityProviderIndex(providers, "canonical").values()];
}
/** Resolve a configured speech provider by canonical ID or alias. */
const getSpeechProvider = defaultSpeechProviderRegistry.getSpeechProvider;
/** Resolve an input provider ID or alias to the provider's canonical ID. */
const canonicalizeSpeechProviderId = defaultSpeechProviderRegistry.canonicalizeSpeechProviderId;
//#endregion
//#region src/tts/directives.ts
function resolveDirectiveProviders(options) {
return (options?.providers ?? listSpeechProviders(options?.cfg)).toSorted(compareSpeechProviderOrder);
}
function prioritizeProvider(providers, providerId) {
if (!providerId) return [...providers];
const preferredProvider = resolveDirectiveProvider(providers, providerId);
if (!preferredProvider) return [...providers];
return [preferredProvider, ...providers.filter((provider) => provider.id !== preferredProvider.id)];
}
function resolveDirectiveProvider(providers, providerId) {
const normalized = normalizeLowercaseStringOrEmpty(providerId);
if (!normalized) return;
return providers.find((provider) => provider.id === normalized || provider.aliases?.some((alias) => normalizeLowercaseStringOrEmpty(alias) === normalized));
}
function parseGenericSpeakerDirective(params) {
if (!params.policy.allowVoice) return;
switch (params.key) {
case "speakervoice":
case "speaker_voice": return {
speakerVoice: params.value,
voice: params.value,
voiceName: params.value
};
case "speakervoiceid":
case "speaker_voice_id": return {
speakerVoiceId: params.value,
voiceId: params.value
};
default: return;
}
}
function classifyTtsTag(body) {
const normalized = body.trim().replace(/\s+/g, "").toLowerCase();
if (normalized === "tts:text") return "hidden-open";
if (normalized === "/tts:text") return "hidden-close";
if (normalized === "tts" || normalized.startsWith("tts:") || normalized === "/tts" || normalized.startsWith("/tts:")) return "tts";
return "other";
}
/** Create an incremental cleaner for hiding [[tts:*]] directive text while streaming. */
function createTtsDirectiveTextStreamCleaner() {
let pending = "";
let insideHiddenTextBlock = false;
return {
push(text) {
const input = pending + text;
pending = "";
let output = "";
let index = 0;
while (index < input.length) {
const tagStart = input.indexOf("[[", index);
if (tagStart === -1) {
const tail = input.endsWith("[") ? input.length - 1 : input.length;
if (!insideHiddenTextBlock) output += input.slice(index, tail);
pending = input.slice(tail);
break;
}
if (!insideHiddenTextBlock) output += input.slice(index, tagStart);
const tagEnd = input.indexOf("]]", tagStart + 2);
if (tagEnd === -1) {
pending = input.slice(tagStart);
break;
}
const rawTag = input.slice(tagStart, tagEnd + 2);
const tag = classifyTtsTag(input.slice(tagStart + 2, tagEnd));
if (tag === "hidden-open") insideHiddenTextBlock = true;
else if (tag === "hidden-close") insideHiddenTextBlock = false;
else if (tag === "other" && !insideHiddenTextBlock) output += rawTag;
index = tagEnd + 2;
}
return output;
},
flush() {
const tail = insideHiddenTextBlock ? "" : pending;
pending = "";
insideHiddenTextBlock = false;
return tail;
},
hasBufferedDirectiveText() {
return pending.length > 0 || insideHiddenTextBlock;
}
};
}
/** Resolve persisted TTS facts against the active model/provider policy. */
function resolveTtsDirectiveFacts(facts, policy, options) {
if (!facts || !policy.enabled) return {
overrides: {},
warnings: [],
hasDirective: false
};
let providers;
const getProviders = () => {
providers ??= resolveDirectiveProviders(options);
return providers;
};
const overrides = {};
const warnings = [];
if (policy.allowText && facts.text != null) overrides.ttsText = facts.text;
for (const directive of facts.directives ?? []) {
const declaredProviderId = policy.allowProvider ? directive.provider : void 0;
if (declaredProviderId) overrides.provider = declaredProviderId;
let directiveProviders;
const getDirectiveProviders = () => {
if (directiveProviders) return directiveProviders;
if (declaredProviderId) {
const declaredProvider = resolveDirectiveProvider(getProviders(), declaredProviderId);
if (!declaredProvider) {
warnings.push(`unknown provider "${declaredProviderId}"`);
directiveProviders = [];
return directiveProviders;
}
directiveProviders = [declaredProvider];
return directiveProviders;
}
directiveProviders = prioritizeProvider(getProviders(), normalizeLowercaseStringOrEmpty(options?.preferredProviderId));
return directiveProviders;
};
for (const [key, value] of Object.entries(directive.values)) {
let handled = false;
const directiveProvidersLocal = getDirectiveProviders();
for (const provider of directiveProvidersLocal) {
const genericSpeakerOverrides = parseGenericSpeakerDirective({
key,
value,
policy
});
const parsed = genericSpeakerOverrides ? {
handled: true,
overrides: genericSpeakerOverrides
} : provider.parseDirectiveToken?.({
key,
value,
policy,
selectedProvider: declaredProviderId ? provider.id : void 0,
providerConfig: options?.providerConfigs?.[provider.id],
currentOverrides: overrides.providerOverrides?.[provider.id]
});
if (!parsed?.handled) continue;
if (parsed.overrides) overrides.providerOverrides = {
...overrides.providerOverrides,
[provider.id]: {
...overrides.providerOverrides?.[provider.id],
...parsed.overrides
}
};
if (parsed.warnings?.length) warnings.push(...parsed.warnings);
handled = true;
break;
}
if (!handled && declaredProviderId && directiveProvidersLocal.length > 0) warnings.push(`unsupported ${declaredProviderId} directive key "${key}"`);
}
}
return {
ttsText: overrides.ttsText,
hasDirective: true,
overrides,
warnings
};
}
/** Parse TTS directives from final message text, leaving markdown code spans unchanged. */
function parseTtsDirectives(text, policy, options) {
if (!policy.enabled) return {
cleanedText: text,
overrides: {},
warnings: [],
hasDirective: false
};
const extracted = extractTtsDirectiveFacts(text);
const resolved = resolveTtsDirectiveFacts(extracted.facts, policy, options);
return {
cleanedText: extracted.cleanedText,
...resolved
};
}
//#endregion
export { getSpeechProvider as a, compareSpeechProviderOrder as c, canonicalizeSpeechProviderId as i, createSpeechProviderRegistry as l, parseTtsDirectives as n, listLoadedSpeechProviders as o, resolveTtsDirectiveFacts as r, listSpeechProviders as s, createTtsDirectiveTextStreamCleaner as t };