397 lines
20 KiB
JavaScript
397 lines
20 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime;
|
||
const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter");
|
||
const assistantCapabilityBindingResponseGuard_1 = require("./assistantCapabilityBindingResponseGuard");
|
||
const assistantCapabilityRuntimeBindingAdapter_1 = require("./assistantCapabilityRuntimeBindingAdapter");
|
||
const assistantMcpDiscoveryDebugAttachment_1 = require("./assistantMcpDiscoveryDebugAttachment");
|
||
const assistantMcpDiscoveryResponsePolicy_1 = require("./assistantMcpDiscoveryResponsePolicy");
|
||
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
||
const assistantStateTransitionRuntimeAdapter_1 = require("./assistantStateTransitionRuntimeAdapter");
|
||
const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter");
|
||
function toRecordObject(value) {
|
||
if (!value || typeof value !== "object") {
|
||
return null;
|
||
}
|
||
return value;
|
||
}
|
||
function toNullableString(value) {
|
||
if (typeof value !== "string") {
|
||
return null;
|
||
}
|
||
const trimmed = value.trim();
|
||
return trimmed.length > 0 ? trimmed : null;
|
||
}
|
||
function toNullableBoolean(value) {
|
||
if (typeof value === "boolean") {
|
||
return value;
|
||
}
|
||
return undefined;
|
||
}
|
||
function normalizeAddressReplyType(value) {
|
||
return value === "factual" || value === "partial_coverage" ? value : "partial_coverage";
|
||
}
|
||
function sameBusinessLabel(left, right) {
|
||
const normalizedLeft = toNullableString(left)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
|
||
const normalizedRight = toNullableString(right)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
|
||
return Boolean(normalizedLeft &&
|
||
normalizedRight &&
|
||
(normalizedLeft === normalizedRight ||
|
||
normalizedLeft.includes(normalizedRight) ||
|
||
normalizedRight.includes(normalizedLeft)));
|
||
}
|
||
function firstString(values) {
|
||
for (const value of values) {
|
||
const text = toNullableString(value);
|
||
if (text) {
|
||
return text;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function legalOrganizationLabelFromClarification(value) {
|
||
const text = toNullableString(value);
|
||
if (!text) {
|
||
return null;
|
||
}
|
||
const compact = text.replace(/\s+/gu, " ").replace(/[.!?]+$/u, "").trim();
|
||
if (compact.length > 120 || !/^(?:ООО|ПАО|АО|ИП)\s+\S/iu.test(compact)) {
|
||
return null;
|
||
}
|
||
return compact;
|
||
}
|
||
function cleanComparisonScopeCompanyLine(line, organization) {
|
||
let clean = String(line ?? "")
|
||
.replace(/\bcompany-level\b/giu, "общий по компании")
|
||
.replace(/\breusable bundle\b/giu, "сохраненный подтвержденный срез");
|
||
if (organization) {
|
||
clean = clean.replace(/по компании\s+Альтернатива Плюс/iu, `по компании ${organization}`);
|
||
}
|
||
return clean.trim();
|
||
}
|
||
function hasComparisonScopeProofCue(value) {
|
||
const text = toNullableString(value)?.toLocaleLowerCase("ru-RU").replace(/\s+/gu, " ").trim();
|
||
if (!text) {
|
||
return false;
|
||
}
|
||
return /(?:\bсравни\b|собери\s+коротк\p{L}*\s+итог|коротк\p{L}*\s+сравн|что\s+.*подтверд\p{L}*.*(?:компан|контрагент|свк)|что\s+.*отдельно\s+по\s+(?:группа\s+свк|выбранн\p{L}*\s+контрагент)|какие\s+выводы\s+можно\s+делать\s+и\s+какие\s+нельзя)/iu.test(text);
|
||
}
|
||
function buildComparisonScopeProofReply(input) {
|
||
if (!hasComparisonScopeProofCue(input.userMessage)) {
|
||
return null;
|
||
}
|
||
const entryPoint = toRecordObject(input.debug.assistant_mcp_discovery_entry_point_v1);
|
||
const turnInput = toRecordObject(entryPoint?.turn_input);
|
||
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
|
||
const isBusinessOverview = toNullableString(turnMeaning?.asked_domain_family) === "business_overview";
|
||
if (!isBusinessOverview) {
|
||
return null;
|
||
}
|
||
const separateCandidates = Array.isArray(turnMeaning?.business_overview_separate_entity_candidates)
|
||
? turnMeaning.business_overview_separate_entity_candidates
|
||
: [];
|
||
const separateSubject = firstString([...separateCandidates, turnMeaning?.metadata_scope_hint]);
|
||
if (!separateSubject) {
|
||
return null;
|
||
}
|
||
const sessionRecord = toRecordObject(input.session);
|
||
const addressNavigationState = toRecordObject(sessionRecord?.address_navigation_state);
|
||
const sessionContext = toRecordObject(addressNavigationState?.session_context);
|
||
const comparisonScope = toRecordObject(sessionContext?.comparison_scope);
|
||
const comparisonCounterparty = toRecordObject(comparisonScope?.counterparty);
|
||
const proofBundles = toRecordObject(comparisonScope?.proof_bundles);
|
||
const valueBundle = toRecordObject(proofBundles?.counterparty_value_flow_bundle);
|
||
const documentBundle = toRecordObject(proofBundles?.counterparty_document_bundle);
|
||
if (!valueBundle || !sameBusinessLabel(separateSubject, valueBundle.counterparty ?? comparisonCounterparty?.label)) {
|
||
return null;
|
||
}
|
||
const incoming = toRecordObject(valueBundle.incoming_customer_revenue);
|
||
const outgoing = toRecordObject(valueBundle.outgoing_supplier_payout);
|
||
const incomingAmount = toNullableString(incoming?.total_amount_human_ru);
|
||
const outgoingAmount = toNullableString(outgoing?.total_amount_human_ru);
|
||
const netAmount = toNullableString(valueBundle.net_amount_human_ru);
|
||
if (!incomingAmount && !outgoingAmount && !netAmount) {
|
||
return null;
|
||
}
|
||
const organization = legalOrganizationLabelFromClarification(input.userMessage)
|
||
?? toNullableString(turnMeaning?.explicit_organization_scope)
|
||
?? toNullableString(toRecordObject(comparisonScope?.organization)?.label);
|
||
const documentCount = Number(toRecordObject(documentBundle)?.document_count);
|
||
const documentText = Number.isFinite(documentCount) && documentCount > 0 ? `, документы: ${documentCount}` : "";
|
||
const lines = String(input.baseReply ?? "")
|
||
.split(/\r?\n/u)
|
||
.map((line) => line.trim())
|
||
.filter(Boolean);
|
||
const companyLine = cleanComparisonScopeCompanyLine(lines[0] ?? `Коротко: по компании ${organization ?? "выбранной организации"} подтвержден общий денежный срез.`, organization);
|
||
const netDirection = valueBundle.net_direction === "net_outgoing" ? "нетто в минус" : "нетто в нашу сторону";
|
||
const counterparty = toNullableString(valueBundle.counterparty) ?? separateSubject;
|
||
return {
|
||
reply: [
|
||
`${companyLine}; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ${netDirection} ${netAmount ?? "0 руб."}${documentText}.`,
|
||
`Отдельно по контрагенту ${counterparty}: это ранее подтвержденный контрагентский срез, а не перенос общих сумм компании на контрагента.`,
|
||
`Нельзя утверждать: чистую прибыль, полноценный финрезультат, юридические роли клиентов/поставщиков и выводы по ${counterparty} из общих сумм компании без отдельного контрагентского среза.`
|
||
].join("\n"),
|
||
audit: {
|
||
applied: true,
|
||
source: "address_navigation_state.comparison_scope.proof_bundles",
|
||
counterparty,
|
||
organization: organization ?? null,
|
||
document_count: Number.isFinite(documentCount) && documentCount > 0 ? documentCount : null
|
||
}
|
||
};
|
||
}
|
||
function buildAppliedMcpDiscoveryRoutePatch(debug, applied) {
|
||
if (!applied) {
|
||
return {};
|
||
}
|
||
const selectedChain = toNullableString(debug.mcp_discovery_selected_chain_id);
|
||
if (selectedChain === "business_overview") {
|
||
return {
|
||
detected_intent: "business_overview",
|
||
detected_intent_confidence: "high",
|
||
selected_recipe: "business_overview",
|
||
response_type: "LIMITED_WITH_REASON",
|
||
mcp_discovery_effective_response_route: "business_overview"
|
||
};
|
||
}
|
||
return {};
|
||
}
|
||
function normalizeAddressLaneDebug(value) {
|
||
return (toRecordObject(value) ?? {});
|
||
}
|
||
function normalizeCarryoverMeta(value) {
|
||
const source = toRecordObject(value);
|
||
if (!source) {
|
||
return null;
|
||
}
|
||
const followupContext = toRecordObject(source.followupContext);
|
||
const previousAddressIntent = toNullableString(source.previousAddressIntent) ??
|
||
toNullableString(followupContext?.previous_intent);
|
||
const previousAddressAnchor = toNullableString(source.previousAddressAnchor) ??
|
||
toNullableString(followupContext?.previous_anchor);
|
||
if (!previousAddressIntent && !previousAddressAnchor) {
|
||
return null;
|
||
}
|
||
return {
|
||
previousAddressIntent,
|
||
previousAddressAnchor
|
||
};
|
||
}
|
||
function normalizeLlmPreDecomposeMeta(value) {
|
||
const source = toRecordObject(value);
|
||
if (!source) {
|
||
return null;
|
||
}
|
||
const dialogContinuationContractRaw = toRecordObject(source.dialogContinuationContract);
|
||
const addressRetryAuditRaw = toRecordObject(source.addressRetryAudit);
|
||
const predecomposeContractRaw = toRecordObject(source.predecomposeContract);
|
||
const predecomposePeriodRaw = toRecordObject(predecomposeContractRaw?.period);
|
||
const semanticExtractionContractRaw = toRecordObject(source.semanticExtractionContract);
|
||
const normalized = {};
|
||
const attempted = toNullableBoolean(source.attempted);
|
||
if (attempted !== undefined)
|
||
normalized.attempted = attempted;
|
||
const applied = toNullableBoolean(source.applied);
|
||
if (applied !== undefined)
|
||
normalized.applied = applied;
|
||
const provider = toNullableString(source.provider);
|
||
if (provider)
|
||
normalized.provider = provider;
|
||
const traceId = toNullableString(source.traceId);
|
||
if (traceId)
|
||
normalized.traceId = traceId;
|
||
const reason = toNullableString(source.reason);
|
||
if (reason)
|
||
normalized.reason = reason;
|
||
const fallbackRuleHit = toNullableString(source.fallbackRuleHit);
|
||
if (fallbackRuleHit)
|
||
normalized.fallbackRuleHit = fallbackRuleHit;
|
||
const sanitizedUserMessage = toNullableString(source.sanitizedUserMessage);
|
||
if (sanitizedUserMessage)
|
||
normalized.sanitizedUserMessage = sanitizedUserMessage;
|
||
const toolGateDecision = toNullableString(source.toolGateDecision);
|
||
if (toolGateDecision)
|
||
normalized.toolGateDecision = toolGateDecision;
|
||
const toolGateReason = toNullableString(source.toolGateReason);
|
||
if (toolGateReason)
|
||
normalized.toolGateReason = toolGateReason;
|
||
if (dialogContinuationContractRaw) {
|
||
const decision = toNullableString(dialogContinuationContractRaw.decision);
|
||
const targetIntent = toNullableString(dialogContinuationContractRaw.target_intent);
|
||
if (decision || targetIntent) {
|
||
normalized.dialogContinuationContract = {
|
||
decision,
|
||
target_intent: targetIntent
|
||
};
|
||
}
|
||
}
|
||
if (addressRetryAuditRaw) {
|
||
const retryAttempted = toNullableBoolean(addressRetryAuditRaw.attempted);
|
||
const retryReason = toNullableString(addressRetryAuditRaw.reason);
|
||
const initialLimitedCategory = toNullableString(addressRetryAuditRaw.initial_limited_category);
|
||
const retryResultCategory = toNullableString(addressRetryAuditRaw.retry_result_category);
|
||
if (retryAttempted !== undefined ||
|
||
retryReason ||
|
||
initialLimitedCategory ||
|
||
retryResultCategory) {
|
||
normalized.addressRetryAudit = {
|
||
attempted: retryAttempted,
|
||
reason: retryReason,
|
||
initial_limited_category: initialLimitedCategory,
|
||
retry_result_category: retryResultCategory
|
||
};
|
||
}
|
||
}
|
||
if (predecomposeContractRaw) {
|
||
const intent = toNullableString(predecomposeContractRaw.intent);
|
||
const aggregationProfile = toNullableString(predecomposeContractRaw.aggregation_profile);
|
||
const periodScope = toNullableString(predecomposePeriodRaw?.scope);
|
||
if (intent || aggregationProfile || periodScope) {
|
||
normalized.predecomposeContract = {
|
||
intent,
|
||
aggregation_profile: aggregationProfile,
|
||
period: periodScope ? { scope: periodScope } : null
|
||
};
|
||
}
|
||
}
|
||
if (semanticExtractionContractRaw) {
|
||
const valid = toNullableBoolean(semanticExtractionContractRaw.valid);
|
||
const quality = toNullableString(semanticExtractionContractRaw.quality);
|
||
const applyCanonicalRecommended = toNullableBoolean(semanticExtractionContractRaw.apply_canonical_recommended);
|
||
const reasonCodes = Array.isArray(semanticExtractionContractRaw.reason_codes)
|
||
? semanticExtractionContractRaw.reason_codes
|
||
.map((item) => toNullableString(item))
|
||
.filter((item) => Boolean(item))
|
||
: [];
|
||
if (valid !== undefined || quality || applyCanonicalRecommended !== undefined || reasonCodes.length > 0) {
|
||
normalized.semanticExtractionContract = {
|
||
valid: valid ?? null,
|
||
quality,
|
||
apply_canonical_recommended: applyCanonicalRecommended ?? null,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
}
|
||
const hasUsefulField = Object.values(normalized).some((item) => item !== undefined && item !== null);
|
||
return hasUsefulField ? normalized : null;
|
||
}
|
||
function runAssistantAddressLaneResponseRuntime(input) {
|
||
const finalizeAddressTurnSafe = input.finalizeAddressTurn ?? assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn;
|
||
const safeAddressReply = input.sanitizeOutgoingAssistantText(input.addressLane.reply_text);
|
||
const debug = input.buildAddressDebugPayload(input.addressLane.debug, input.llmPreDecomposeMeta);
|
||
const followupOffer = input.buildAddressFollowupOffer(debug);
|
||
if (followupOffer) {
|
||
debug.address_followup_offer = followupOffer;
|
||
}
|
||
const laneOrganizationCandidates = Array.isArray(input.addressLane.debug?.organization_candidates)
|
||
? input.addressLane.debug.organization_candidates
|
||
: [];
|
||
const debugKnownOrganizations = input.mergeKnownOrganizations([
|
||
...input.knownOrganizations,
|
||
...laneOrganizationCandidates
|
||
]);
|
||
const debugFilters = debug?.extracted_filters && typeof debug.extracted_filters === "object"
|
||
? debug.extracted_filters
|
||
: null;
|
||
const debugActiveOrganization = input.toNonEmptyString(debugFilters?.organization) ??
|
||
input.toNonEmptyString(input.activeOrganization);
|
||
const followupContextSource = input.carryoverMeta?.followupContext && typeof input.carryoverMeta.followupContext === "object"
|
||
? input.carryoverMeta.followupContext
|
||
: null;
|
||
if (debugKnownOrganizations.length > 0) {
|
||
debug.assistant_known_organizations = debugKnownOrganizations;
|
||
}
|
||
if (debugActiveOrganization) {
|
||
debug.assistant_active_organization = debugActiveOrganization;
|
||
}
|
||
const rootIntent = input.toNonEmptyString(followupContextSource?.root_intent);
|
||
const currentFrameKind = input.toNonEmptyString(followupContextSource?.current_frame_kind);
|
||
const rootFilters = followupContextSource?.root_filters && typeof followupContextSource.root_filters === "object"
|
||
? followupContextSource.root_filters
|
||
: null;
|
||
if (rootIntent || currentFrameKind) {
|
||
debug.address_root_frame_context = {
|
||
root_intent: rootIntent,
|
||
current_frame_kind: currentFrameKind,
|
||
organization: input.toNonEmptyString(rootFilters?.organization),
|
||
as_of_date: input.toNonEmptyString(rootFilters?.as_of_date),
|
||
period_from: input.toNonEmptyString(rootFilters?.period_from),
|
||
period_to: input.toNonEmptyString(rootFilters?.period_to)
|
||
};
|
||
}
|
||
const debugWithRuntimeContracts = (0, assistantRuntimeContractResolver_1.attachAssistantRuntimeContractShadow)(debug, {
|
||
userMessage: input.userMessage,
|
||
addressRuntimeMeta: input.llmPreDecomposeMeta
|
||
});
|
||
const debugWithTruthAnswerPolicy = (0, assistantTruthAnswerPolicyRuntimeAdapter_1.attachAssistantTruthAnswerPolicy)(debugWithRuntimeContracts, {
|
||
addressRuntimeMeta: input.llmPreDecomposeMeta,
|
||
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
|
||
});
|
||
const debugWithStateTransition = (0, assistantStateTransitionRuntimeAdapter_1.attachAssistantStateTransition)(debugWithTruthAnswerPolicy, {
|
||
addressRuntimeMeta: input.llmPreDecomposeMeta,
|
||
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
|
||
});
|
||
const debugWithCapabilityBinding = (0, assistantCapabilityRuntimeBindingAdapter_1.attachAssistantCapabilityRuntimeBinding)(debugWithStateTransition, {
|
||
addressRuntimeMeta: input.llmPreDecomposeMeta,
|
||
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
|
||
});
|
||
const debugWithMcpDiscovery = (0, assistantMcpDiscoveryDebugAttachment_1.attachAssistantMcpDiscoveryDebug)(debugWithCapabilityBinding, {
|
||
addressRuntimeMeta: input.llmPreDecomposeMeta
|
||
});
|
||
const guardedResponse = (0, assistantCapabilityBindingResponseGuard_1.applyAssistantCapabilityBindingResponseGuard)({
|
||
assistantReply: safeAddressReply,
|
||
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
|
||
capabilityBinding: debugWithMcpDiscovery.assistant_capability_binding_v1
|
||
});
|
||
const debugWithResponseGuard = {
|
||
...debugWithMcpDiscovery,
|
||
capability_binding_response_guard: guardedResponse.audit
|
||
};
|
||
const mcpDiscoveryResponsePolicy = (0, assistantMcpDiscoveryResponsePolicy_1.applyAssistantMcpDiscoveryResponsePolicy)({
|
||
currentReply: guardedResponse.assistantReply,
|
||
currentReplySource: "address_query_runtime_v1",
|
||
currentReplyType: guardedResponse.replyType,
|
||
addressRuntimeMeta: debugWithResponseGuard
|
||
});
|
||
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
|
||
? mcpDiscoveryResponsePolicy.reply_text
|
||
: guardedResponse.assistantReply;
|
||
const comparisonScopeProofReply = buildComparisonScopeProofReply({
|
||
baseReply: finalAssistantReply,
|
||
debug: debugWithResponseGuard,
|
||
session: input.getSession(input.sessionId),
|
||
userMessage: input.userMessage
|
||
});
|
||
const finalAssistantReplyWithComparisonProof = comparisonScopeProofReply?.reply ?? finalAssistantReply;
|
||
const finalReplyType = mcpDiscoveryResponsePolicy.applied ? "partial_coverage" : guardedResponse.replyType;
|
||
const finalDebug = {
|
||
...debugWithResponseGuard,
|
||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
|
||
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
|
||
};
|
||
const finalization = finalizeAddressTurnSafe({
|
||
sessionId: input.sessionId,
|
||
userMessage: input.userMessage,
|
||
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
|
||
assistantReply: finalAssistantReplyWithComparisonProof,
|
||
replyType: finalReplyType,
|
||
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
||
debug: finalDebug,
|
||
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
|
||
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
|
||
appendItem: input.appendItem,
|
||
getSession: input.getSession,
|
||
persistSession: input.persistSession,
|
||
cloneConversation: input.cloneConversation,
|
||
logEvent: input.logEvent,
|
||
messageIdFactory: input.messageIdFactory
|
||
});
|
||
return {
|
||
response: finalization.response,
|
||
debug: finalDebug
|
||
};
|
||
}
|