openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
658 lines (657 loc) • 29.5 kB
JavaScript
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { a as getReplyPayloadMetadata, b as stripReplyMediaFailureFallback, v as readPairingQrReplyChannelData } from "./reply-payload-DahKk-7n.js";
import { i as stripInlineDirectiveTagsForDelivery } from "./directive-tags-Wp1S72ap.js";
import { At as withTranscriptWriteLock, F as persistSessionTranscriptTurn, i as readSessionTranscriptWatermark, in as applyAssistantDeliveryDirectives, kt as rewriteTranscriptEventRowsExact } from "./session-accessor-YsytfDtG.js";
import { m as findTranscriptEvent, y as loadTranscriptEventRowsAfterSeqSync } from "./session-accessor.sqlite-transcript-store-B0zw3fAU.js";
import { d as patchSessionEntryCore } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { _ as publishTranscriptUpdate } from "./session-accessor.sqlite-lifecycle-state-BTh4yZ7R.js";
import { a as readSessionTranscriptRunId, s as resolveTerminalAssistantTranscriptRunId } from "./transcript-events-wgPr4ILk.js";
import { t as extractAssistantPhaseText } from "./chat-message-content-Dws_XUEQ.js";
import { i as retainAssistantModelContent, n as projectAssistantDisplayContent, t as ASSISTANT_DISPLAY_CONTENT_FIELD } from "./assistant-display-content-BZljc_tT.js";
import { t as resolveMirroredTranscriptText } from "./transcript-mirror-BTvMHa-8.js";
import { n as splitMediaFromOutput } from "./parse-BR5Zk6rK.js";
import { t as normalizeMediaReferenceForComparison } from "./media-reference-comparison-DpA8NCy-.js";
import { t as createOutboundPayloadPlan } from "./payloads-DC1mf-yF.js";
import { M as stripEnvelopeFromMessage } from "./session-transcript-readers-CYDRQsH5.js";
import { r as loadGatewaySessionEntry } from "./session-utils-store-CInT2loy.js";
import "./session-utils-Cai0_C6U.js";
import { n as renderQrPngDataUrl } from "./qr-image-CnPhdkea.js";
import { t as renderQrTerminal } from "./qr-terminal-27AasTys.js";
import { t as formatForLog } from "./ws-log-Dkg9wKNU.js";
import { a as buildManagedMediaFailureBlock, s as createManagedOutgoingMediaBlocks, u as prepareOutgoingMediaFromReplyPayload } from "./managed-image-attachments-CwIi4Xb-.js";
//#region src/gateway/server-methods/chat-assistant-content.ts
const MANAGED_OUTGOING_MEDIA_PATH_PREFIX = "/api/chat/media/outgoing/";
/** Recombine non-streamed text without destroying Markdown's meaningful indentation. */
function combineNonStreamingReplyParts(parts) {
let combined = "";
for (const part of parts) {
if (!part.trim()) continue;
if (!combined) {
combined = part;
continue;
}
const separator = /[\r\n]$/.test(combined) || /^[\r\n]/.test(part) ? "" : /^[\t ]+\S/.test(part) ? "\n" : "\n\n";
combined += separator + part;
}
return combined.trim();
}
function isMediaBearingPayload(payload) {
if (payload.isReasoning === true) return false;
if (payload.mediaUrl?.trim()) return true;
return Boolean(payload.mediaUrls?.some((url) => url.trim()));
}
function hasSensitiveMediaPayload(payloads) {
return payloads.some((payload) => payload.sensitiveMedia === true && (isMediaBearingPayload(payload) || Boolean(readPairingQrReplyChannelData(payload))));
}
async function buildPairingQrAssistantContentBlock(payload) {
const qr = readPairingQrReplyChannelData(payload);
if (!qr) return;
const [imageUrl, terminalText] = await Promise.all([renderQrPngDataUrl(qr.setupCode), renderQrTerminal(qr.setupCode, { small: true })]);
return {
type: "openclaw_pairing_qr",
image_url: imageUrl,
terminalText,
alt: "OpenClaw pairing QR code",
expiresAtMs: qr.expiresAtMs,
sensitive: true
};
}
function sanitizeAssistantDisplayText(value, options) {
if (!value) return;
const withoutEnvelope = stripEnvelopeFromMessage(value);
const normalized = typeof withoutEnvelope === "string" ? withoutEnvelope : value;
const stripped = stripInlineDirectiveTagsForDelivery(normalized);
const visible = stripped.text.trim();
return visible ? options?.preserveBoundaries && !stripped.changed ? normalized : visible : void 0;
}
function extractAssistantDisplayTextFromContent(content) {
if (!Array.isArray(content) || content.length === 0) return;
return combineNonStreamingReplyParts(content.map((block) => {
if (block?.type !== "text" || typeof block.text !== "string") return "";
return block.text;
}).filter(Boolean)) || void 0;
}
async function buildAssistantDisplayContentFromReplyPayloads(params) {
const rawTextPayloadCount = params.payloads.filter((payload) => payload.isReasoning !== true && typeof payload.text === "string" && payload.text.trim().length > 0).length;
const plan = createOutboundPayloadPlan(params.payloads);
if (plan.length === 0) {
const failureBlocks = params.payloads.flatMap((payload) => (getReplyPayloadMetadata(payload)?.assistantMediaFailures ?? []).map(buildManagedMediaFailureBlock));
if (failureBlocks.length > 0) return failureBlocks;
return rawTextPayloadCount > 0 ? [{
type: "text",
text: ""
}] : void 0;
}
const preserveTextBoundaries = plan.filter(({ payload }) => typeof payload.text === "string" && payload.text.trim()).length > 1;
const content = [];
let strippedTextPayloadCount = 0;
for (const entry of plan) {
const payload = entry.payload;
const metadataSource = params.payloads[entry.sourceIndex] ?? payload;
const mediaFailures = getReplyPayloadMetadata(metadataSource)?.assistantMediaFailures ?? [];
const text = sanitizeAssistantDisplayText(stripReplyMediaFailureFallback(payload.text, mediaFailures), { preserveBoundaries: preserveTextBoundaries });
if (text) {
const previousBlock = content.at(-1);
if (previousBlock?.type === "text" && typeof previousBlock.text === "string") previousBlock.text = combineNonStreamingReplyParts([previousBlock.text, text]);
else content.push({
type: "text",
text
});
} else if (typeof payload.text === "string" && payload.text.trim().length > 0) strippedTextPayloadCount += 1;
if (params.includeSensitiveDisplay === true) try {
const pairingQrBlock = await buildPairingQrAssistantContentBlock(payload);
if (pairingQrBlock) content.push(pairingQrBlock);
} catch (err) {
params.onSensitiveDisplayPrepareError?.(formatForLog(err));
}
if (params.includeSensitiveMedia === false && payload.sensitiveMedia === true) continue;
const mediaBlocks = await createManagedOutgoingMediaBlocks({
sessionKey: params.sessionKey,
...params.agentId ? { agentId: params.agentId } : {},
items: prepareOutgoingMediaFromReplyPayload(payload, metadataSource),
localRoots: params.managedMediaLocalRoots,
continueOnPrepareError: true,
onPrepareError: (error) => {
params.onManagedMediaPrepareError?.(error.message);
}
});
if (payload.audioAsVoice === true) {
for (const block of mediaBlocks) if (block.type === "audio") block.isVoiceNote = true;
}
content.push(...mediaBlocks);
content.push(...mediaFailures.map(buildManagedMediaFailureBlock));
}
if (content.length > 0) return content;
return strippedTextPayloadCount > 0 ? [{
type: "text",
text: ""
}] : void 0;
}
function replaceAssistantContentTextBlocks(content, transcriptMediaMessage) {
const transcriptTextBlocks = (transcriptMediaMessage?.content ?? []).filter((block) => Boolean(block) && typeof block === "object" && block.type === "text" && typeof block.text === "string");
if (transcriptTextBlocks.length === 0) return content ? [...content] : void 0;
if (!content || content.length === 0) return [...transcriptTextBlocks];
const merged = [];
let transcriptTextIndex = 0;
for (const block of content) {
if (block?.type === "text" && typeof block.text === "string" && transcriptTextIndex < transcriptTextBlocks.length) {
const replacement = expectDefined(transcriptTextBlocks[transcriptTextIndex++], "transcript text blocks entry at transcript text index++");
merged.push(replacement);
continue;
}
merged.push(block);
}
if (transcriptTextIndex < transcriptTextBlocks.length) merged.unshift(...transcriptTextBlocks.slice(transcriptTextIndex));
return merged;
}
function isManagedOutgoingMediaUrl(value) {
if (typeof value !== "string" || !value.trim()) return false;
try {
return new URL(value, "http://localhost").pathname.startsWith(MANAGED_OUTGOING_MEDIA_PATH_PREFIX);
} catch {
return false;
}
}
function stripManagedOutgoingAssistantContentBlocks(content) {
if (!content || content.length === 0) return;
const filtered = content.filter((block) => {
const attachment = block?.type === "attachment" ? asOptionalRecord(block.attachment) : void 0;
if (block?.type !== "image" && block?.type !== "audio" && block?.type !== "video" && !attachment) return true;
return !(isManagedOutgoingMediaUrl(block.url) || isManagedOutgoingMediaUrl(block.openUrl) || isManagedOutgoingMediaUrl(attachment?.url));
});
return filtered.length > 0 ? filtered : void 0;
}
function extractAssistantDisplayText(content) {
if (!content || content.length === 0) return;
return combineNonStreamingReplyParts(content.map((block) => block?.type === "text" && typeof block.text === "string" ? block.text : "")) || void 0;
}
function hasAssistantDisplayMediaContent(content) {
return Boolean(content?.some((block) => block?.type !== "text"));
}
function hasVisibleAssistantFinalMessage(message) {
if (!message) return false;
if (typeof message.text === "string" && message.text.trim()) return true;
return (Array.isArray(message.content) ? message.content : []).some((block) => {
if (!block || typeof block !== "object") return false;
const record = block;
if (record.type === "text") return typeof record.text === "string" && record.text.trim().length > 0;
return true;
});
}
function hasManagedOutgoingAssistantContent(content) {
return Boolean(content?.some((block) => (block?.type === "image" || block?.type === "audio" || block?.type === "video") && (isManagedOutgoingMediaUrl(block.url) || isManagedOutgoingMediaUrl(block.openUrl)) || block?.type === "attachment" && isManagedOutgoingMediaUrl(asOptionalRecord(block.attachment)?.url)));
}
//#endregion
//#region src/gateway/server-methods/chat-transcript-inject.ts
function resolveInjectedAssistantContent(params) {
const labelPrefix = params.label ? `[${params.label}]\n\n` : "";
if (params.content && params.content.length > 0) {
if (!labelPrefix) return params.content;
const first = params.content[0];
if (first && typeof first === "object" && first.type === "text" && typeof first.text === "string") return [{
...first,
text: `${labelPrefix}${first.text}`
}, ...params.content.slice(1)];
return [{
type: "text",
text: labelPrefix.trim()
}, ...params.content];
}
return [{
type: "text",
text: `${labelPrefix}${params.message}`
}];
}
/** Append a gateway-authored assistant message while preserving transcript parent links. */
async function appendInjectedAssistantMessageToTranscript(params) {
const now = params.now ?? Date.now();
const usage = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0
}
};
const displayMessage = {
role: "assistant",
content: resolveInjectedAssistantContent({
message: params.message,
label: params.label,
content: params.content
}).map((block) => Object.assign({}, block))
};
const preparedDisplayMessage = applyAssistantDeliveryDirectives(displayMessage);
const displayContent = preparedDisplayMessage.content;
const canonicalContent = retainAssistantModelContent(displayContent);
const rawDeliveryFacts = preparedDisplayMessage.openclawDelivery;
const abortRunId = params.abortMeta?.runId;
const messageBody = applyAssistantDeliveryDirectives({
role: "assistant",
content: canonicalContent,
[ASSISTANT_DISPLAY_CONTENT_FIELD]: displayContent,
timestamp: now,
stopReason: params.stopReason ?? "stop",
usage,
api: "openai-responses",
provider: "openclaw",
model: "gateway-injected",
...params.idempotencyKey ? { idempotencyKey: params.idempotencyKey } : {},
...params.ttsSupplement ? { openclawTtsSupplement: params.ttsSupplement } : {},
...params.abortMeta ? { openclawAbort: {
aborted: true,
origin: params.abortMeta.origin,
runId: params.abortMeta.runId
} } : {}
});
if (rawDeliveryFacts && messageBody.openclawDelivery === void 0) messageBody.openclawDelivery = rawDeliveryFacts;
try {
if (!params.transcriptPath && (!params.storePath || !params.sessionId || !params.sessionKey)) return {
ok: false,
error: "transcript identity not resolved"
};
let predicateDeclined = false;
const turn = await persistSessionTranscriptTurn({
sessionKey: params.sessionKey ?? "",
...params.transcriptPath ? { sessionFile: params.transcriptPath } : {},
...params.storePath ? { storePath: params.storePath } : {},
...params.sessionId ? { sessionId: params.sessionId } : {},
...params.agentId ? { agentId: params.agentId } : {}
}, {
expectedSessionId: params.expectedSessionId,
expectedLifecycleRevision: params.expectedLifecycleRevision,
updateMode: "inline",
...params.abortMeta ? { runId: params.abortMeta.runId } : {},
touchSessionEntry: Boolean(params.storePath && params.sessionId && params.sessionKey),
...params.config ? { config: params.config } : {},
messages: [{
message: messageBody,
idempotencyLookup: "scan-assistant",
...params.abortMeta ? { shouldAppendInTransaction: (latestAssistantMessage) => {
const committedRunId = resolveTerminalAssistantTranscriptRunId(latestAssistantMessage, readSessionTranscriptRunId(latestAssistantMessage));
const committedText = extractAssistantPhaseText(latestAssistantMessage)?.trim();
predicateDeclined = committedRunId === abortRunId && committedText === params.message.trim();
return !predicateDeclined;
} } : {},
now,
useRawWhenLinear: true
}]
});
if (turn.rejectedReason) return {
ok: false,
error: turn.rejectedReason
};
const appended = turn.messages[0];
if (!appended) {
if (predicateDeclined) return {
ok: true,
skipped: true
};
return {
ok: false,
error: "gateway-injected assistant message was not appended"
};
}
return {
ok: true,
messageId: appended.messageId,
message: projectAssistantDisplayContent(appended.message)
};
} catch (err) {
return {
ok: false,
error: formatErrorMessage(err)
};
}
}
//#endregion
//#region src/gateway/server-methods/chat-transcript-persistence.ts
function mergeAssistantDisplayContent(modelContent, preparedDisplayContent) {
const remainingDisplayContent = [...preparedDisplayContent];
const content = [];
for (const block of modelContent) {
if (block.type !== "text" || typeof block.text !== "string") {
content.push(block);
continue;
}
const matchingTextIndex = remainingDisplayContent.findIndex((candidate) => candidate.type === "text" && candidate.text === block.text);
if (matchingTextIndex < 0) {
content.push(block);
continue;
}
const nextTextOffset = remainingDisplayContent.slice(matchingTextIndex + 1).findIndex((candidate) => candidate.type === "text");
const segmentEnd = nextTextOffset < 0 ? remainingDisplayContent.length : matchingTextIndex + nextTextOffset + 1;
content.push(...remainingDisplayContent.splice(0, segmentEnd));
}
content.push(...remainingDisplayContent);
return content;
}
function buildAssistantDisplayRewrite(params) {
const prepared = applyAssistantDeliveryDirectives({
...params.message,
content: params.displayContent.map((block) => Object.assign({}, block))
}, { managedMediaUrls: params.managedMediaUrls });
const original = Array.isArray(params.message.content) ? params.message.content : [];
const content = [];
const seenText = /* @__PURE__ */ new Set();
for (const block of original) {
if (block.type === "thinking" || block.type === "toolCall") {
content.push(block);
continue;
}
if (block.type !== "text" || typeof block.text !== "string" || !params.retainOriginalText && !prepared.content.some((candidate) => candidate.type === "text" && candidate.text === block.text)) continue;
const splitText = splitMediaFromOutput(block.text).text;
if (splitText === block.text && /\bMEDIA:/iu.test(block.text)) continue;
const text = sanitizeAssistantDisplayText(splitText, { preserveBoundaries: true });
if (text) {
if (text === block.text) content.push(block);
else {
const { textSignature: _textSignature, ...rest } = block;
content.push({
...rest,
text
});
}
seenText.add(text);
}
}
for (const block of prepared.content) if (block.type === "text" && typeof block.text === "string" && !seenText.has(block.text)) content.push(block);
return {
...prepared,
content,
[ASSISTANT_DISPLAY_CONTENT_FIELD]: mergeAssistantDisplayContent(content, prepared.content)
};
}
function assistantTranscriptScope(params) {
const sessionKey = params.sessionKey.trim();
if (!sessionKey || !params.sessionId.trim()) return null;
return {
sessionKey,
sessionId: params.sessionId,
...params.storePath ? { storePath: params.storePath } : {},
...params.agentId ? { agentId: params.agentId } : {}
};
}
function transcriptEventId(event) {
const id = asOptionalRecord(event)?.id;
return typeof id === "string" && id.trim().length > 0 ? id : void 0;
}
function transcriptEventMessage(event) {
return asOptionalRecord(asOptionalRecord(event)?.message);
}
function findAssistantTranscriptMessageByIdempotencyKeyInEvents(events, idempotencyKey) {
const trimmedIdempotencyKey = idempotencyKey.trim();
if (!trimmedIdempotencyKey) return null;
const target = events.toReversed().find((event) => {
const message = transcriptEventMessage(event);
return message?.role === "assistant" && message.idempotencyKey === trimmedIdempotencyKey;
});
const message = target ? transcriptEventMessage(target) : void 0;
const messageId = target ? transcriptEventId(target) : void 0;
if (!messageId || !message) return null;
return {
messageId,
message
};
}
function findAssistantTranscriptMessageByTurnIndexAndMediaInEvents(events, params) {
const expectedMedia = new Set(params.mediaUrls.map((value) => normalizeMediaReferenceForComparison(value)).filter((value) => value.length > 0));
if (expectedMedia.size === 0 || !Number.isSafeInteger(params.assistantMessageIndex) || params.assistantMessageIndex < 1) return null;
const target = events.filter((event) => transcriptEventMessage(event)?.role === "assistant")[params.assistantMessageIndex - 1];
const message = target ? transcriptEventMessage(target) : void 0;
const messageId = target ? transcriptEventId(target) : void 0;
const text = message ? extractAssistantTranscriptText(message) : void 0;
if (!messageId || !message || !text) return null;
const actualMedia = new Set((splitMediaFromOutput(text).mediaUrls ?? []).map((value) => normalizeMediaReferenceForComparison(value)).filter((value) => value.length > 0));
return actualMedia.size === expectedMedia.size && [...expectedMedia].every((value) => actualMedia.has(value)) ? {
messageId,
message
} : null;
}
function findSourceReplyTranscriptMirrorByIdempotencyKeyInEvents(events, idempotencyKey) {
const found = findAssistantTranscriptMessageByIdempotencyKeyInEvents(events, idempotencyKey);
if (found?.message.provider !== "openclaw" || found.message.model !== "delivery-mirror") return null;
return found;
}
function extractAssistantTranscriptText(message) {
const content = message.content;
if (!Array.isArray(content)) return;
return content.map((block) => block && typeof block === "object" && block.type === "text" && typeof block.text === "string" ? block.text.trim() ?? "" : "").filter(Boolean).join("\n").trim() || void 0;
}
function findSourceReplyTranscriptMirrorByMetadataInEvents(params) {
const byIdempotencyKey = findSourceReplyTranscriptMirrorByIdempotencyKeyInEvents(params.events, params.idempotencyKey);
if (byIdempotencyKey) return byIdempotencyKey;
const expectedText = resolveMirroredTranscriptText({
text: params.metadata?.text,
mediaUrls: params.metadata?.mediaUrls
});
if (!expectedText) return null;
const target = params.events.toReversed().find((event) => {
const message = transcriptEventMessage(event);
return typeof transcriptEventId(event) === "string" && message?.role === "assistant" && message.provider === "openclaw" && message.model === "delivery-mirror" && extractAssistantTranscriptText(message) === expectedText;
});
const message = target ? transcriptEventMessage(target) : void 0;
const messageId = target ? transcriptEventId(target) : void 0;
if (!messageId || !message) return null;
return {
messageId,
message
};
}
async function transcriptExists(scope) {
const sessionId = scope.sessionId;
if (!sessionId) return false;
return await findTranscriptEvent({
...scope,
sessionId
}, () => true).catch(() => void 0) !== void 0;
}
async function appendAssistantTranscriptMessage(params) {
const scope = assistantTranscriptScope(params);
if (!scope) return {
ok: false,
error: "transcript identity not resolved"
};
if (!params.createIfMissing && !await transcriptExists(scope)) return {
ok: false,
error: "transcript not found"
};
return await appendInjectedAssistantMessageToTranscript({
expectedSessionId: params.expectedSessionId,
expectedLifecycleRevision: params.expectedLifecycleRevision,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
storePath: params.storePath,
...params.agentId ? { agentId: params.agentId } : {},
message: params.message,
label: params.label,
content: params.content,
idempotencyKey: params.idempotencyKey,
stopReason: params.stopReason,
abortMeta: params.abortMeta,
ttsSupplement: params.ttsSupplement,
config: params.cfg
});
}
function captureAbortedPartial(params) {
const { runId, abortOrigin } = params;
try {
const session = params.session ?? {
ok: true,
value: loadGatewaySessionEntry(params.sessionKey, params.agentId ? { agentId: params.agentId } : void 0)
};
if (!session.ok) throw session.error;
const { cfg, storePath, entry, canonicalKey, agentId } = session.value;
if (entry?.sessionId !== params.sessionId) throw new Error("Aborted partial transcript session changed before persistence");
return {
runId,
abortOrigin,
ok: true,
value: {
sessionKey: canonicalKey,
sessionId: params.sessionId,
expectedSessionId: params.sessionId,
expectedLifecycleRevision: entry.lifecycleRevision ?? null,
agentId,
storePath,
cfg,
message: params.text,
createIfMissing: true,
idempotencyKey: `${runId}:assistant`,
abortMeta: {
aborted: true,
origin: abortOrigin,
runId
}
}
};
} catch (error) {
return {
runId,
abortOrigin,
ok: false,
error
};
}
}
async function persistAbortedPartials(params) {
for (const snapshot of params.snapshots) {
if (!snapshot.ok) throw snapshot.error;
const appended = await appendAssistantTranscriptMessage(snapshot.value);
if (appended.skipped) continue;
if (!appended.ok) {
const error = `chat.abort transcript append failed: ${appended.error ?? "unknown error"}`;
params.context.logGateway.warn(error);
if (snapshot.abortOrigin === "placement-abandon") throw new Error(error);
}
}
}
async function touchAssistantTranscriptSessionEntry(scope) {
if (!scope.storePath || !scope.sessionKey || !scope.sessionId) return;
const transcriptMarkerUpdatedAt = Date.now();
await patchSessionEntryCore({
storePath: scope.storePath,
sessionKey: scope.sessionKey,
...scope.agentId ? { agentId: scope.agentId } : {}
}, (current) => current.sessionId === scope.sessionId ? { updatedAt: transcriptMarkerUpdatedAt } : null, { skipMaintenance: true });
}
async function rewriteSourceReplyTranscriptMirrors(params) {
if (params.requests.length === 0 || params.candidates.length === 0) return [];
return await withTranscriptWriteLock(params.scope, async (transcript) => {
const events = await transcript.readEvents();
const allowedSourceReplyMirrorIds = /* @__PURE__ */ new Set();
for (const candidate of params.candidates) {
const target = findSourceReplyTranscriptMirrorByMetadataInEvents({
events,
idempotencyKey: candidate.idempotencyKey,
metadata: candidate.metadata
});
if (target) allowedSourceReplyMirrorIds.add(target.messageId);
}
const rewriteTargets = [];
for (const request of params.requests) {
const target = findSourceReplyTranscriptMirrorByMetadataInEvents({
events,
idempotencyKey: request.idempotencyKey,
metadata: request.metadata
});
if (target) rewriteTargets.push({
request,
...target
});
}
if (rewriteTargets.length === 0) return [];
const rewriteTargetIds = new Set(rewriteTargets.map((target) => target.messageId));
const firstRewriteEntryIndex = events.findIndex((event) => {
const id = transcriptEventId(event);
return id ? rewriteTargetIds.has(id) : false;
});
if (!(firstRewriteEntryIndex >= 0 && events.slice(firstRewriteEntryIndex).every((event) => {
const id = transcriptEventId(event);
return !id || allowedSourceReplyMirrorIds.has(id);
}))) return [];
const replacementsById = new Map(rewriteTargets.map((target) => [target.messageId, target]));
const rewrittenEvents = events.map((event) => {
const id = transcriptEventId(event);
const replacement = id ? replacementsById.get(id) : void 0;
if (!replacement) return event;
const message = buildAssistantDisplayRewrite({
message: {
...replacement.message,
idempotencyKey: replacement.request.idempotencyKey
},
displayContent: replacement.request.state.persistedContent,
managedMediaUrls: replacement.request.metadata?.mediaUrls
});
return Object.assign({}, event, { message });
});
await transcript.replaceEvents(rewrittenEvents);
return rewriteTargets.map((target) => ({
messageId: target.messageId,
request: target.request
}));
});
}
async function rewriteAssistantTranscriptMessageByIdempotencyKey(params) {
const idempotencyKey = params.idempotencyKey.trim();
if (!idempotencyKey || params.content.length === 0) return null;
return await withTranscriptWriteLock(params.scope, async (transcript) => {
const events = await transcript.readEvents();
const target = findAssistantTranscriptMessageByIdempotencyKeyInEvents(events, idempotencyKey);
if (!target) return null;
const rewrittenEvents = events.map((event) => transcriptEventId(event) === target.messageId ? Object.assign({}, event, { message: buildAssistantDisplayRewrite({
message: target.message,
displayContent: params.content,
managedMediaUrls: params.managedMediaUrls
}) }) : event);
await transcript.replaceEvents(rewrittenEvents);
return { messageId: target.messageId };
});
}
async function rewriteAssistantTranscriptMessageByTurnIndexAndMedia(params) {
if (params.content.length === 0 || params.mediaUrls.length === 0) return null;
const currentWatermark = readSessionTranscriptWatermark(params.scope);
const initialGenerationMaterialized = params.expectedGeneration === null && params.afterSeq === 0;
if (currentWatermark.generation !== params.expectedGeneration && !initialGenerationMaterialized) return null;
const currentTurnRows = loadTranscriptEventRowsAfterSeqSync(params.scope, params.afterSeq);
const target = findAssistantTranscriptMessageByTurnIndexAndMediaInEvents(currentTurnRows.map((row) => row.event), params);
if (!target) return null;
const targetRow = currentTurnRows.find((row) => transcriptEventId(row.event) === target.messageId);
if (!targetRow) return null;
const rewrittenMessage = buildAssistantDisplayRewrite({
message: target.message,
displayContent: params.content,
managedMediaUrls: params.mediaUrls,
retainOriginalText: true
});
const rewrittenEvent = Object.assign({}, targetRow.event, { message: rewrittenMessage });
const rewritten = await rewriteTranscriptEventRowsExact(params.scope, {
allowInitialGenerationMaterialization: initialGenerationMaterialized,
expectedGeneration: params.expectedGeneration,
rows: [{
event: rewrittenEvent,
expectedEventJson: JSON.stringify(targetRow.event),
seq: targetRow.seq
}]
});
return rewritten ? {
generation: rewritten.generation,
messageId: target.messageId
} : null;
}
async function publishAssistantTranscriptRewrite(params) {
if (params.rewritten.length === 0) return;
await touchAssistantTranscriptSessionEntry(params.scope);
await publishTranscriptUpdate(params.scope, { messageId: params.rewritten.at(-1)?.messageId });
}
//#endregion
export { isMediaBearingPayload as _, publishAssistantTranscriptRewrite as a, stripManagedOutgoingAssistantContentBlocks as b, rewriteSourceReplyTranscriptMirrors as c, extractAssistantDisplayText as d, extractAssistantDisplayTextFromContent as f, hasVisibleAssistantFinalMessage as g, hasSensitiveMediaPayload as h, persistAbortedPartials as i, buildAssistantDisplayContentFromReplyPayloads as l, hasManagedOutgoingAssistantContent as m, assistantTranscriptScope as n, rewriteAssistantTranscriptMessageByIdempotencyKey as o, hasAssistantDisplayMediaContent as p, captureAbortedPartial as r, rewriteAssistantTranscriptMessageByTurnIndexAndMedia as s, appendAssistantTranscriptMessage as t, combineNonStreamingReplyParts as u, replaceAssistantContentTextBlocks as v, sanitizeAssistantDisplayText as y };