UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

138 lines (137 loc) 6.44 kB
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 };