export function createFinalRecallInjection(deps = {}) { const normalizeRecallInputText = (value = "") => deps.normalizeRecallInputText?.(value) ?? String(value || "").trim(); const getContext = (...args) => deps.getContext?.(...args); const getSettings = (...args) => deps.getSettings?.(...args); const getLastRecallSentUserMessage = () => deps.getLastRecallSentUserMessage?.() || {}; const getLastInjectionContent = () => String(deps.getLastInjectionContent?.() || ""); const setLastInjectionContent = (value = "") => { deps.setLastInjectionContent?.(String(value || "")); }; const setRuntimeStatus = (value) => { deps.setRuntimeStatus?.(value); }; function persistRecallInjectionRecord({ recallInput = {}, result = {}, injectionText = "", tokenEstimate = 0, } = {}) { const chat = getContext()?.chat; if (!Array.isArray(chat)) return null; const generationType = String(recallInput?.generationType || "normal").trim() || "normal"; const lastRecallSentUserMessage = getLastRecallSentUserMessage(); let resolvedTargetIndex = deps.resolveRecallPersistenceTargetUserMessageIndex( chat, { generationType, explicitTargetUserMessageIndex: recallInput?.targetUserMessageIndex, candidateTexts: [ recallInput?.userMessage, recallInput?.overrideUserMessage, lastRecallSentUserMessage?.text, ], preferredRecord: lastRecallSentUserMessage, }, ); if (!Number.isFinite(resolvedTargetIndex)) { deps.debugPersistedRecallPersistence?.("目标 user 楼层解析失败", { generationType, explicitTargetUserMessageIndex: recallInput?.targetUserMessageIndex, lastSentUserMessageId: lastRecallSentUserMessage?.messageId, recallInputSource: String(recallInput?.source || ""), }); return null; } if (!chat[resolvedTargetIndex]?.is_user) { deps.debugPersistedRecallPersistence?.("目标楼层不是 user 消息,跳过持久化", { targetUserMessageIndex: resolvedTargetIndex, messageKeys: Object.keys(chat[resolvedTargetIndex] || {}), }); return null; } const targetUserFloorText = normalizeRecallInputText( chat[resolvedTargetIndex]?.mes || "", ); const boundUserFloorText = normalizeRecallInputText( recallInput?.boundUserFloorText || targetUserFloorText, ); const record = deps.buildPersistedRecallRecord( { injectionText, selectedNodeIds: result?.selectedNodeIds || [], recallInput: String(recallInput?.userMessage || ""), recallSource: String(recallInput?.source || ""), hookName: String(recallInput?.hookName || ""), tokenEstimate, manuallyEdited: false, authoritativeInputUsed: Boolean(recallInput?.authoritativeInputUsed), boundUserFloorText, }, deps.readPersistedRecallFromUserMessage(chat, resolvedTargetIndex), ); if (!String(record?.injectionText || "").trim()) { deps.debugPersistedRecallPersistence?.("无有效 injectionText,跳过持久化", { targetUserMessageIndex: resolvedTargetIndex, selectedNodeCount: Array.isArray(result?.selectedNodeIds) ? result.selectedNodeIds.length : 0, }); return null; } if (!deps.writePersistedRecallToUserMessage(chat, resolvedTargetIndex, record)) { deps.debugPersistedRecallPersistence?.("写入 user 楼层失败", { targetUserMessageIndex: resolvedTargetIndex, }); return null; } deps.triggerChatMetadataSave(getContext(), { immediate: false }); deps.schedulePersistedRecallMessageUiRefresh(); deps.debugPersistedRecallPersistence?.( "召回记录已写入 user 楼层", { targetUserMessageIndex: resolvedTargetIndex, injectionTextLength: String(record?.injectionText || "").length, selectedNodeCount: Array.isArray(record?.selectedNodeIds) ? record.selectedNodeIds.length : 0, }, `persist-success:${resolvedTargetIndex}`, ); return { index: resolvedTargetIndex, record, }; } function ensurePersistedRecallRecordForGeneration({ generationType = "normal", recallResult = null, transaction = null, recallOptions = null, hookName = "", } = {}) { const injectionText = String(recallResult?.injectionText || "").trim(); if ( recallResult?.status !== "completed" || !recallResult?.didRecall || !injectionText ) { return { persisted: false, reason: "no-fresh-recall", targetUserMessageIndex: null, record: null, }; } const chat = getContext()?.chat; if (!Array.isArray(chat) || chat.length === 0) { return { persisted: false, reason: "missing-chat", targetUserMessageIndex: null, record: null, }; } const frozenRecallOptions = transaction?.frozenRecallOptions && typeof transaction.frozenRecallOptions === "object" ? transaction.frozenRecallOptions : null; const lastRecallSentUserMessage = getLastRecallSentUserMessage(); const targetUserMessageIndex = deps.resolveRecallPersistenceTargetUserMessageIndex( chat, { generationType, explicitTargetUserMessageIndex: frozenRecallOptions?.targetUserMessageIndex ?? recallOptions?.targetUserMessageIndex ?? recallOptions?.explicitTargetUserMessageIndex ?? null, candidateTexts: [ frozenRecallOptions?.overrideUserMessage, frozenRecallOptions?.userMessage, recallOptions?.overrideUserMessage, recallOptions?.userMessage, recallResult?.recallInput, recallResult?.userMessage, ...(Array.isArray(recallResult?.sourceCandidates) ? recallResult.sourceCandidates.map((candidate) => candidate?.text) : []), lastRecallSentUserMessage?.text, ], preferredRecord: lastRecallSentUserMessage, }, ); if ( !Number.isFinite(targetUserMessageIndex) || !chat[targetUserMessageIndex]?.is_user ) { return { persisted: false, reason: "target-unresolved", targetUserMessageIndex: Number.isFinite(targetUserMessageIndex) ? targetUserMessageIndex : null, record: null, }; } const selectedNodeIds = deps.normalizeRecallNodeIdList( recallResult?.selectedNodeIds || [], ); const existingRecord = deps.readPersistedRecallFromUserMessage( chat, targetUserMessageIndex, ); const nextAuthoritativeInputUsed = Boolean( recallResult?.authoritativeInputUsed ?? frozenRecallOptions?.authoritativeInputUsed ?? recallOptions?.authoritativeInputUsed, ); const targetUserFloorText = normalizeRecallInputText( chat[targetUserMessageIndex]?.mes || "", ); const nextBoundUserFloorText = normalizeRecallInputText( recallResult?.boundUserFloorText || frozenRecallOptions?.boundUserFloorText || recallOptions?.boundUserFloorText || targetUserFloorText || "", ); const existingBoundUserFloorText = normalizeRecallInputText( existingRecord?.boundUserFloorText || "", ); const existingMetadataUpToDate = Boolean(existingRecord?.authoritativeInputUsed) === nextAuthoritativeInputUsed && (!nextBoundUserFloorText || existingBoundUserFloorText === nextBoundUserFloorText); if ( existingRecord && String(existingRecord.injectionText || "").trim() === injectionText && deps.areRecallNodeIdListsEqual(existingRecord.selectedNodeIds, selectedNodeIds) && String(existingRecord.recallInput || "").trim() && existingMetadataUpToDate ) { return { persisted: false, reason: "already-up-to-date", targetUserMessageIndex, record: existingRecord, }; } const nextRecord = deps.buildPersistedRecallRecord( { injectionText, selectedNodeIds, recallInput: String( recallResult?.recallInput || recallResult?.userMessage || frozenRecallOptions?.overrideUserMessage || recallOptions?.overrideUserMessage || recallOptions?.userMessage || "", ), recallSource: String( recallResult?.source || frozenRecallOptions?.lockedSource || frozenRecallOptions?.overrideSource || recallOptions?.overrideSource || "", ), hookName: String( hookName || recallResult?.hookName || frozenRecallOptions?.hookName || recallOptions?.hookName || "", ), tokenEstimate: deps.estimateTokens(injectionText), manuallyEdited: false, authoritativeInputUsed: nextAuthoritativeInputUsed, boundUserFloorText: nextBoundUserFloorText, }, existingRecord, ); if (!deps.writePersistedRecallToUserMessage(chat, targetUserMessageIndex, nextRecord)) { return { persisted: false, reason: "write-failed", targetUserMessageIndex, record: null, }; } deps.triggerChatMetadataSave(getContext(), { immediate: false }); deps.schedulePersistedRecallMessageUiRefresh(); deps.debugPersistedRecallPersistence?.( "最终阶段已补写召回记录", { targetUserMessageIndex, hookName: String( hookName || recallResult?.hookName || frozenRecallOptions?.hookName || recallOptions?.hookName || "", ) || "", injectionTextLength: injectionText.length, selectedNodeCount: selectedNodeIds.length, }, `finalize-persist:${targetUserMessageIndex}`, ); return { persisted: true, reason: "backfilled", targetUserMessageIndex, record: nextRecord, }; } function rewriteRecallPayloadWithInjection( promptData = null, injectionText = "", ) { const normalizedInjectionText = normalizeRecallInputText(injectionText); if (!normalizedInjectionText) { return { applied: false, path: "", field: "", reason: "empty-injection-text", }; } const finalMesSend = Array.isArray(promptData?.finalMesSend) ? promptData.finalMesSend : null; if (Array.isArray(finalMesSend) && finalMesSend.length > 0) { for (let index = finalMesSend.length - 1; index >= 0; index--) { const entry = finalMesSend[index]; if (!entry || typeof entry !== "object") continue; if (entry.injected === true) continue; const messageText = normalizeRecallInputText( entry.message || entry.mes || entry.content || "", ); if (!messageText) continue; entry.extensionPrompts = Array.isArray(entry.extensionPrompts) ? entry.extensionPrompts : []; const alreadyPresent = entry.extensionPrompts.some((chunk) => String(chunk || "").includes(normalizedInjectionText), ); if (!alreadyPresent) { entry.extensionPrompts.push(`${normalizedInjectionText}\n`); } return { applied: true, path: "finalMesSend", field: `finalMesSend[${index}].extensionPrompts`, reason: alreadyPresent ? "rewrite-already-present" : "finalMesSend-extensionPrompt-appended", targetIndex: index, }; } return { applied: false, path: "finalMesSend", field: "", reason: "no-rewritable-finalMesSend-entry", }; } if ( typeof promptData?.combinedPrompt === "string" && promptData.combinedPrompt.trim() ) { if (!promptData.combinedPrompt.includes(normalizedInjectionText)) { promptData.combinedPrompt = `${normalizedInjectionText}\n\n${promptData.combinedPrompt}`; } return { applied: true, path: "combinedPrompt", field: "combinedPrompt", reason: "combinedPrompt-prefixed", }; } if (typeof promptData?.prompt === "string" && promptData.prompt.trim()) { if (!promptData.prompt.includes(normalizedInjectionText)) { promptData.prompt = `${normalizedInjectionText}\n\n${promptData.prompt}`; } return { applied: true, path: "prompt", field: "prompt", reason: "prompt-prefixed", }; } return { applied: false, path: "", field: "", reason: "prompt-payload-unavailable", }; } function rewriteRecallPayloadWithAuthoritativeUserInput( promptData = null, authoritativeText = "", boundUserFloorText = "", ) { const normalizedAuthoritativeText = normalizeRecallInputText(authoritativeText); const normalizedBoundUserFloorText = normalizeRecallInputText(boundUserFloorText); if (!normalizedAuthoritativeText) { return { applied: false, changed: false, path: "", field: "", reason: "empty-authoritative-text", }; } const finalMesSend = Array.isArray(promptData?.finalMesSend) ? promptData.finalMesSend : null; if (!Array.isArray(finalMesSend) || finalMesSend.length <= 0) { return { applied: false, changed: false, path: "", field: "", reason: "finalMesSend-unavailable", }; } let fallbackIndex = -1; let matchedIndex = -1; for (let index = finalMesSend.length - 1; index >= 0; index--) { const entry = finalMesSend[index]; if (!entry || typeof entry !== "object") continue; if (entry.injected === true) continue; const messageText = normalizeRecallInputText( entry.message || entry.mes || entry.content || "", ); if (!messageText) continue; if (fallbackIndex < 0) { fallbackIndex = index; } if ( messageText === normalizedAuthoritativeText || (normalizedBoundUserFloorText && messageText === normalizedBoundUserFloorText) ) { matchedIndex = index; break; } } const targetIndex = matchedIndex >= 0 ? matchedIndex : normalizedBoundUserFloorText ? -1 : fallbackIndex; if (targetIndex < 0) { return { applied: false, changed: false, path: "finalMesSend", field: "", reason: normalizedBoundUserFloorText ? "bound-user-floor-text-not-found" : "no-rewritable-finalMesSend-entry", }; } const entry = finalMesSend[targetIndex]; const fieldName = Object.prototype.hasOwnProperty.call(entry, "message") ? "message" : Object.prototype.hasOwnProperty.call(entry, "mes") ? "mes" : Object.prototype.hasOwnProperty.call(entry, "content") ? "content" : "message"; const previousText = normalizeRecallInputText( entry?.[fieldName] || entry?.message || entry?.mes || entry?.content || "", ); const changed = previousText !== normalizedAuthoritativeText; if (changed) { entry[fieldName] = normalizedAuthoritativeText; } return { applied: true, changed, path: "finalMesSend", field: `finalMesSend[${targetIndex}].${fieldName}`, reason: changed ? "finalMesSend-authoritative-user-rewritten" : "authoritative-user-already-matched", targetIndex, }; } function applyFinalRecallInjectionForGeneration({ generationType = "normal", freshRecallResult = null, transaction = null, promptData = null, hookName = "", } = {}) { const existingFinalResolution = deps.readGenerationRecallTransactionFinalResolution(transaction); if (existingFinalResolution) { if ( promptData && transaction?.frozenRecallOptions?.authoritativeInputUsed === true ) { const recallResult = freshRecallResult || deps.getGenerationRecallTransactionResult(transaction) || null; const inputRewrite = rewriteRecallPayloadWithAuthoritativeUserInput( promptData, transaction?.frozenRecallOptions?.overrideUserMessage || "", transaction?.frozenRecallOptions?.boundUserFloorText || "", ); const rewrite = rewriteRecallPayloadWithInjection( promptData, existingFinalResolution.usedText || recallResult?.injectionText || "", ); const nextFinalResolution = { ...existingFinalResolution, deliveryMode: "deferred", applicationMode: rewrite.applied || inputRewrite.applied ? "rewrite" : existingFinalResolution.applicationMode, rewrite, inputRewrite, }; deps.recordInjectionSnapshot("recall", { taskType: "recall", source: String( recallResult?.source || transaction?.frozenRecallOptions?.lockedSource || transaction?.frozenRecallOptions?.overrideSource || "", ).trim() || "unknown", sourceLabel: String( recallResult?.sourceLabel || transaction?.frozenRecallOptions?.lockedSourceLabel || transaction?.frozenRecallOptions?.overrideSourceLabel || "", ).trim() || "未知", reason: String( recallResult?.reason || transaction?.frozenRecallOptions?.lockedReason || transaction?.frozenRecallOptions?.overrideReason || "", ).trim() || "final-application-reused", sourceCandidates: Array.isArray(recallResult?.sourceCandidates) ? recallResult.sourceCandidates.map((candidate) => ({ ...candidate })) : Array.isArray(transaction?.frozenRecallOptions?.sourceCandidates) ? transaction.frozenRecallOptions.sourceCandidates.map((candidate) => ({ ...candidate, })) : [], hookName: String(hookName || recallResult?.hookName || "").trim(), selectedNodeIds: recallResult?.selectedNodeIds || [], retrievalMeta: recallResult?.retrievalMeta || {}, llmMeta: recallResult?.llmMeta || {}, stats: recallResult?.stats || {}, injectionText: nextFinalResolution.usedText || "", deliveryMode: nextFinalResolution.deliveryMode || "", applicationMode: nextFinalResolution.applicationMode || "none", transport: nextFinalResolution.transport || { applied: false, source: "none", mode: "none", }, rewrite: nextFinalResolution.rewrite || { applied: false, path: "", field: "", reason: "final-resolution-reused", }, inputRewrite, targetUserMessageIndex: nextFinalResolution.targetUserMessageIndex, sourceKind: nextFinalResolution.source || "none", authoritativeInputUsed: true, boundUserFloorText: String( transaction?.frozenRecallOptions?.boundUserFloorText || "", ), }); deps.storeGenerationRecallTransactionFinalResolution( transaction, nextFinalResolution, ); deps.refreshPanelLiveState(); deps.schedulePersistedRecallMessageUiRefresh(); return nextFinalResolution; } return existingFinalResolution; } const recallResult = freshRecallResult || deps.getGenerationRecallTransactionResult(transaction) || null; const hookResolvedDeliveryMode = String( deps.resolveGenerationRecallDeliveryMode( hookName, generationType, transaction?.frozenRecallOptions || {}, ), ).trim() || "immediate"; const deliveryMode = String( promptData && hookName === "GENERATE_BEFORE_COMBINE_PROMPTS" ? hookResolvedDeliveryMode : recallResult?.deliveryMode || transaction?.lastDeliveryMode || hookResolvedDeliveryMode, ).trim() || "immediate"; const chat = getContext()?.chat; let transport = { applied: false, source: "none", mode: "none", }; let targetUserMessageIndex = null; let resolved = { source: "none", injectionText: "", record: null, }; const authoritativeInputRewrite = deliveryMode === "deferred" && transaction?.frozenRecallOptions?.authoritativeInputUsed === true ? rewriteRecallPayloadWithAuthoritativeUserInput( promptData, transaction?.frozenRecallOptions?.overrideUserMessage || "", transaction?.frozenRecallOptions?.boundUserFloorText || "", ) : { applied: false, changed: false, path: "", field: "", reason: deliveryMode === "deferred" ? "authoritative-input-unused" : "non-deferred-delivery", }; const rewrite = { applied: false, path: "", field: "", reason: "no-recall-source", }; let applicationMode = "none"; if (!Array.isArray(chat)) { transport = deps.applyModuleInjectionPrompt("", getSettings()) || transport; const emptyResolution = { source: "none", isFallback: false, targetUserMessageIndex: null, usedText: "", deliveryMode, applicationMode: "none", rewrite, transport, }; deps.storeGenerationRecallTransactionFinalResolution( transaction, emptyResolution, ); return emptyResolution; } const ensuredPersistence = ensurePersistedRecallRecordForGeneration({ generationType, recallResult, transaction, recallOptions: transaction?.frozenRecallOptions || null, hookName, }); const lastRecallSentUserMessage = getLastRecallSentUserMessage(); targetUserMessageIndex = deps.resolveRecallPersistenceTargetUserMessageIndex(chat, { generationType, explicitTargetUserMessageIndex: transaction?.frozenRecallOptions?.targetUserMessageIndex, candidateTexts: [ transaction?.frozenRecallOptions?.overrideUserMessage, recallResult?.recallInput, recallResult?.userMessage, recallResult?.sourceCandidates?.[0]?.text, lastRecallSentUserMessage?.text, ], preferredRecord: lastRecallSentUserMessage, }); if (Number.isFinite(ensuredPersistence?.targetUserMessageIndex)) { targetUserMessageIndex = ensuredPersistence.targetUserMessageIndex; } const persistedRecord = Number.isFinite(targetUserMessageIndex) ? deps.readPersistedRecallFromUserMessage(chat, targetUserMessageIndex) : null; resolved = deps.resolveFinalRecallInjectionSource({ freshRecallResult: recallResult, persistedRecord, }); if (resolved.source === "fresh" && deliveryMode === "deferred") { const rewriteResult = rewriteRecallPayloadWithInjection( promptData, resolved.injectionText || "", ); Object.assign(rewrite, rewriteResult); setLastInjectionContent(resolved.injectionText || ""); if (rewriteResult.applied) { applicationMode = "rewrite"; transport = deps.clearLiveRecallInjectionPromptForRewrite() || { applied: false, source: "rewrite-cleared", mode: "rewrite-cleared", }; setRuntimeStatus(deps.createUiStatus( "召回已改写", `本轮发送载荷已 rewrite · ${rewriteResult.path || rewriteResult.field || "payload"}`, "success", )); } else { applicationMode = "fallback-injection"; transport = deps.applyModuleInjectionPrompt( resolved.injectionText || "", getSettings(), ) || transport; setRuntimeStatus(deps.createUiStatus( "召回回退", `rewrite 未命中,已回退注入 · ${rewriteResult.reason}`, "warning", )); } } else if (resolved.source === "fresh") { applicationMode = "injection"; transport = deps.applyModuleInjectionPrompt(resolved.injectionText || "", getSettings()) || transport; setLastInjectionContent(resolved.injectionText || ""); rewrite.reason = "immediate-injection"; setRuntimeStatus(deps.createUiStatus( "召回已注入", "本轮已使用最新召回结果", "success", )); } else if (resolved.source === "persisted") { applicationMode = "persisted-injection"; transport = deps.applyModuleInjectionPrompt(resolved.injectionText || "", getSettings()) || transport; setLastInjectionContent(resolved.injectionText || ""); rewrite.reason = "persisted-record-fallback"; setRuntimeStatus(deps.createUiStatus( "召回回退", "已使用消息楼层持久化注入", "info", )); } else { transport = deps.applyModuleInjectionPrompt("", getSettings()) || transport; setLastInjectionContent(""); setRuntimeStatus(deps.createUiStatus("待命", "当前无有效注入内容", "idle")); } if ( resolved.source === "persisted" && Number.isFinite(targetUserMessageIndex) ) { deps.bumpPersistedRecallGenerationCount(chat, targetUserMessageIndex); deps.triggerChatMetadataSave(getContext(), { immediate: false }); } deps.recordInjectionSnapshot("recall", { taskType: "recall", source: String( recallResult?.source || transaction?.frozenRecallOptions?.lockedSource || transaction?.frozenRecallOptions?.overrideSource || "", ).trim() || "unknown", sourceLabel: String( recallResult?.sourceLabel || transaction?.frozenRecallOptions?.lockedSourceLabel || transaction?.frozenRecallOptions?.overrideSourceLabel || "", ).trim() || "未知", reason: String( recallResult?.reason || transaction?.frozenRecallOptions?.lockedReason || transaction?.frozenRecallOptions?.overrideReason || "", ).trim() || "final-application", sourceCandidates: Array.isArray(recallResult?.sourceCandidates) ? recallResult.sourceCandidates.map((candidate) => ({ ...candidate })) : Array.isArray(transaction?.frozenRecallOptions?.sourceCandidates) ? transaction.frozenRecallOptions.sourceCandidates.map((candidate) => ({ ...candidate, })) : [], hookName: String(hookName || recallResult?.hookName || "").trim(), selectedNodeIds: recallResult?.selectedNodeIds || [], retrievalMeta: recallResult?.retrievalMeta || {}, llmMeta: recallResult?.llmMeta || {}, stats: recallResult?.stats || {}, injectionText: resolved.injectionText || "", deliveryMode, applicationMode, transport, rewrite, inputRewrite: authoritativeInputRewrite, targetUserMessageIndex, sourceKind: resolved.source, authoritativeInputUsed: Boolean( recallResult?.authoritativeInputUsed ?? transaction?.frozenRecallOptions?.authoritativeInputUsed, ), boundUserFloorText: String( recallResult?.boundUserFloorText || transaction?.frozenRecallOptions?.boundUserFloorText || "", ), }); deps.refreshPanelLiveState(); deps.schedulePersistedRecallMessageUiRefresh(); const finalResolution = { source: resolved.source, isFallback: resolved.source === "persisted" || applicationMode === "fallback-injection", targetUserMessageIndex, usedText: resolved.injectionText || "", deliveryMode, applicationMode, rewrite, transport, inputRewrite: authoritativeInputRewrite, authoritativeInputUsed: Boolean( recallResult?.authoritativeInputUsed ?? transaction?.frozenRecallOptions?.authoritativeInputUsed, ), boundUserFloorText: String( recallResult?.boundUserFloorText || transaction?.frozenRecallOptions?.boundUserFloorText || "", ), }; deps.storeGenerationRecallTransactionFinalResolution(transaction, finalResolution); return finalResolution; } return { persistRecallInjectionRecord, ensurePersistedRecallRecordForGeneration, rewriteRecallPayloadWithInjection, rewriteRecallPayloadWithAuthoritativeUserInput, applyFinalRecallInjectionForGeneration, getLastInjectionContent, }; }