openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
138 lines (137 loc) • 6.44 kB
JavaScript
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { c as stripLeadingSilentToken, i as isSilentReplyPayloadText, l as stripSilentToken, o as isSilentReplyText, r as isInternalFormattingArtifact, s as startsWithSilentToken } from "./tokens-DbeAFqg7.js";
import { a as getReplyPayloadMetadata, i as copyReplyPayloadMetadata, y as setReplyPayloadMetadata } from "./reply-payload-DahKk-7n.js";
import { r as sanitizeUserFacingText, t as renderUserFacingText } from "./user-facing-text-0KenW-TQ.js";
import { o as hasReplyPayloadContent } from "./payload-DYE7bQ2k.js";
import { u as stripHeartbeatToken } from "./heartbeat-ofet59jF.js";
//#region src/auto-reply/reply/response-prefix-template.ts
const TEMPLATE_VAR_PATTERN = /\{([a-zA-Z][a-zA-Z0-9.]*)\}/g;
/**
* Interpolate template variables in a response prefix string.
*
* @param template - The template string with `{variable}` placeholders
* @param context - Context object with values for interpolation
* @returns The interpolated string, or undefined if template is undefined
*
* @example
* resolveResponsePrefixTemplate("[{model} | think:{thinkingLevel}]", {
* model: "gpt-5.4",
* thinkingLevel: "high"
* })
* // Returns: "[gpt-5.4 | think:high]"
*/
function resolveResponsePrefixTemplate(template, context) {
if (!template) return;
return template.replace(TEMPLATE_VAR_PATTERN, (match, varName) => {
switch (normalizeLowercaseStringOrEmpty(varName)) {
case "model": return context.model ?? match;
case "modelfull": return context.modelFull ?? match;
case "provider": return context.provider ?? match;
case "thinkinglevel":
case "think": return context.thinkingLevel ?? match;
case "identity.name":
case "identityname": return context.identityName ?? match;
default: return match;
}
});
}
/**
* Extract short model name from a full model string.
*
* Strips:
* - Provider prefix (e.g., "openai/" from "openai/gpt-5.4")
* - Date suffixes (e.g., "-20260205" from "claude-opus-4-6-20260205")
* - Common version suffixes (e.g., "-latest")
*
* @example
* extractShortModelName("openai/gpt-5.6-sol") // "gpt-5.6-sol"
* extractShortModelName("claude-opus-4-6-20260205") // "claude-opus-4-6"
* extractShortModelName("gpt-5.4-latest") // "gpt-5.4"
*/
function extractShortModelName(fullModel) {
const slash = fullModel.lastIndexOf("/");
return (slash >= 0 ? fullModel.slice(slash + 1) : fullModel).replace(/-\d{8}$/, "").replace(/-latest$/, "");
}
//#endregion
//#region src/auto-reply/reply/normalize-reply.ts
const channelReplyTransformOwners = /* @__PURE__ */ new WeakMap();
function bindNormalizeReplyTransformOwner(transform, owner) {
channelReplyTransformOwners.set(transform, owner);
return transform;
}
function resolveNormalizeReplyTransformOwner(transform) {
return transform ? channelReplyTransformOwners.get(transform) ?? transform : void 0;
}
function normalizeReplyPayloadOutcome(payload, opts = {}) {
const suppress = (reason) => {
opts.onSkip?.(reason);
return {
kind: "suppress",
reason
};
};
const applyChannelTransforms = opts.applyChannelTransforms ?? true;
const hasContent = (text) => hasReplyPayloadContent({
...payload,
text
}, { trimText: true });
const trimmed = normalizeOptionalString(payload.text) ?? "";
if (!hasContent(trimmed)) return suppress("empty");
const silentToken = opts.silentToken ?? "NO_REPLY";
let text = payload.text ?? void 0;
if (text && isSilentReplyPayloadText(text, silentToken)) {
if (!hasContent("")) return suppress("silent");
text = "";
}
if (text && !isSilentReplyText(text, silentToken)) {
const hasLeadingSilentToken = startsWithSilentToken(text, silentToken);
if (hasLeadingSilentToken) text = stripLeadingSilentToken(text, silentToken);
if (hasLeadingSilentToken || text.toLowerCase().includes(silentToken.toLowerCase())) {
text = stripSilentToken(text, silentToken);
if (!hasContent(text)) return suppress("silent");
}
}
if (text && !trimmed) text = "";
if (text?.includes("HEARTBEAT_OK")) {
const stripped = stripHeartbeatToken(text, { mode: "message" });
if (stripped.didStrip) opts.onHeartbeatStrip?.();
if (stripped.shouldSkip && !hasContent(stripped.text)) return suppress("heartbeat");
text = stripped.text;
}
if (text && isInternalFormattingArtifact(text) && !hasContent("")) return suppress("silent");
if (text) text = payload.isError ? renderUserFacingText(text, {
errorContext: true,
conversationContext: opts.conversationContext
}) : sanitizeUserFacingText(text, { conversationContext: opts.conversationContext });
if (!hasContent(text)) return suppress("empty");
let enrichedPayload = copyReplyPayloadMetadata(payload, {
...payload,
text
});
const channelTransformOwner = resolveNormalizeReplyTransformOwner(opts.transformReplyPayload);
const transformAlreadyApplied = channelTransformOwner != null && getReplyPayloadMetadata(enrichedPayload)?.channelReplyTransformOwner === channelTransformOwner;
if (applyChannelTransforms && opts.transformReplyPayload && !transformAlreadyApplied) {
const transformedPayload = opts.transformReplyPayload(enrichedPayload);
if (transformedPayload === null) return suppress("channel_transform");
const copiedPayload = transformedPayload ? copyReplyPayloadMetadata(enrichedPayload, transformedPayload) : enrichedPayload;
const appliedOwner = resolveNormalizeReplyTransformOwner(opts.transformReplyPayload);
enrichedPayload = appliedOwner ? setReplyPayloadMetadata(copiedPayload, { channelReplyTransformOwner: appliedOwner }) : copiedPayload;
text = enrichedPayload.text;
}
const effectivePrefix = opts.responsePrefixContext ? resolveResponsePrefixTemplate(opts.responsePrefix, opts.responsePrefixContext) : opts.responsePrefix;
if (effectivePrefix && text && text.trim() !== "HEARTBEAT_OK" && !text.startsWith(effectivePrefix)) text = `${effectivePrefix} ${text}`;
enrichedPayload = copyReplyPayloadMetadata(enrichedPayload, {
...enrichedPayload,
text
});
return {
kind: "deliver",
payload: enrichedPayload
};
}
function normalizeReplyPayload(payload, opts = {}) {
const outcome = normalizeReplyPayloadOutcome(payload, opts);
return outcome.kind === "deliver" ? outcome.payload : null;
}
//#endregion
export { resolveResponsePrefixTemplate as a, extractShortModelName as i, normalizeReplyPayload as n, normalizeReplyPayloadOutcome as r, bindNormalizeReplyTransformOwner as t };