NODEDC_1C/llm_normalizer/backend/dist/services/assistantMcpDiscoveryRespon...

330 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASSISTANT_MCP_DISCOVERY_RESPONSE_CANDIDATE_SCHEMA_VERSION = void 0;
exports.buildAssistantMcpDiscoveryResponseCandidate = buildAssistantMcpDiscoveryResponseCandidate;
exports.ASSISTANT_MCP_DISCOVERY_RESPONSE_CANDIDATE_SCHEMA_VERSION = "assistant_mcp_discovery_response_candidate_v1";
function toRecordObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return null;
}
return value;
}
function toNonEmptyString(value) {
if (value === null || value === undefined) {
return null;
}
const text = String(value).trim();
return text.length > 0 ? text : null;
}
function toStringList(value) {
if (!Array.isArray(value)) {
return [];
}
return value.map((item) => toNonEmptyString(item)).filter((item) => Boolean(item));
}
function normalizeReasonCode(value) {
const normalized = value
.trim()
.replace(/[^\p{L}\p{N}_.:-]+/gu, "_")
.replace(/^_+|_+$/g, "")
.toLowerCase();
return normalized.length > 0 ? normalized.slice(0, 120) : null;
}
function pushReason(target, value) {
const normalized = normalizeReasonCode(value);
if (normalized && !target.includes(normalized)) {
target.push(normalized);
}
}
function uniqueStrings(values) {
const result = [];
for (const value of values) {
const text = String(value ?? "").trim();
if (text && !result.includes(text)) {
result.push(text);
}
}
return result;
}
function hasInternalMechanics(value) {
const text = value.toLowerCase();
return (text.includes("query_documents") ||
text.includes("query_movements") ||
text.includes("primitive") ||
text.includes("pilot_") ||
text.includes("runtime_") ||
text.includes("planner_") ||
text.includes("catalog_") ||
text.includes("select "));
}
function userFacingLines(values) {
return uniqueStrings(values).filter((line) => !hasInternalMechanics(line));
}
function localizeLine(value) {
if (/^1C activity rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки активности в запрошенном срезе.";
}
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки входящих денежных поступлений в запрошенном срезе.";
}
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки исходящих платежей и списаний в запрошенном срезе.";
}
const openScopeBidirectionalMatch = value.match(/^1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=(found|not_found), outgoing=(found|not_found)$/i);
if (openScopeBidirectionalMatch) {
const incoming = openScopeBidirectionalMatch[1] === "found"
? "входящие строки найдены"
: "входящие строки не найдены";
const outgoing = openScopeBidirectionalMatch[2] === "found"
? "исходящие строки найдены"
: "исходящие строки не найдены";
return `В 1С проверены входящие и исходящие денежные строки в запрошенном срезе: ${incoming}, ${outgoing}.`;
}
if (/^Requested period hit the MCP row limit, but the approved monthly recovery probe budget is smaller than the required subperiod count$/i.test(value)) {
return "Запрошенный период уперся в лимит строк MCP; доступного бюджета помесячных дозапросов не хватило, чтобы покрыть все подпериоды.";
}
const counterpartyMatch = value.match(/^1C activity rows were found for counterparty\s+(.+)$/i);
if (counterpartyMatch) {
return `В 1С найдены строки активности по контрагенту ${counterpartyMatch[1]}.`;
}
if (/^1C activity rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки активности по запрошенному контрагентскому контуру.";
}
const valueFlowMatch = value.match(/^1C value-flow rows were found for counterparty\s+(.+)$/i);
if (valueFlowMatch) {
return `В 1С найдены строки входящих денежных поступлений по контрагенту ${valueFlowMatch[1]}.`;
}
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки входящих денежных поступлений по запрошенному контрагентскому контуру.";
}
const documentRowsMatch = value.match(/^1C document rows were found for counterparty\s+(.+)$/i);
if (documentRowsMatch) {
return `В 1С найдены строки документов по контрагенту ${documentRowsMatch[1]}.`;
}
if (/^1C document rows were found for the requested scope$/i.test(value)) {
return "В 1С найдены строки документов по запрошенному контуру.";
}
const movementRowsMatch = value.match(/^1C movement rows were found for counterparty\s+(.+)$/i);
if (movementRowsMatch) {
return `Р1С найдены строки движений по контрагенту ${movementRowsMatch[1]}.`;
}
if (/^1C movement rows were found for the requested scope$/i.test(value)) {
return "Р1С найдены строки движений по запрошенному контуру.";
}
const supplierPayoutMatch = value.match(/^1C supplier-payout rows were found for counterparty\s+(.+)$/i);
if (supplierPayoutMatch) {
return `В 1С найдены строки исходящих платежей/списаний по контрагенту ${supplierPayoutMatch[1]}.`;
}
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки исходящих платежей/списаний по запрошенному контрагентскому контуру.";
}
const bidirectionalMatch = value.match(/^1C bidirectional value-flow rows were checked for counterparty\s+(.+): incoming=(found|not_found), outgoing=(found|not_found)$/i);
if (bidirectionalMatch) {
const incoming = bidirectionalMatch[2] === "found" ? "входящие строки найдены" : "входящие строки не найдены";
const outgoing = bidirectionalMatch[3] === "found" ? "исходящие строки найдены" : "исходящие строки не найдены";
return `В 1С проверены входящие и исходящие денежные строки по контрагенту ${bidirectionalMatch[1]}: ${incoming}, ${outgoing}.`;
}
const bidirectionalScopeMatch = value.match(/^1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=(found|not_found), outgoing=(found|not_found)$/i);
if (bidirectionalScopeMatch) {
const incoming = bidirectionalScopeMatch[1] === "found" ? "входящие строки найдены" : "входящие строки не найдены";
const outgoing = bidirectionalScopeMatch[2] === "found" ? "исходящие строки найдены" : "исходящие строки не найдены";
return `В 1С проверены входящие и исходящие денежные строки по запрошенному контрагентскому контуру: ${incoming}, ${outgoing}.`;
}
if (/^Business activity duration may be inferred from first and latest confirmed 1C activity rows$/i.test(value)) {
return "Длительность деловой активности можно оценивать только как вывод по первой и последней подтвержденной строке активности в 1С.";
}
if (/^Counterparty document evidence is limited to confirmed 1C document rows in the checked scope$/i.test(value)) {
return "Срез документов ограничен только подтвержденными строками документов в проверенном окне.";
}
if (/^Counterparty movement evidence is limited to confirmed 1C movement rows in the checked scope$/i.test(value)) {
return "Срез движений ограничен только подтвержденными строками движений в проверенном окне.";
}
if (/^Counterparty value-flow total was calculated from confirmed 1C movement rows$/i.test(value)) {
return "Сумма входящих поступлений рассчитана только по подтвержденным строкам поступлений в 1С.";
}
if (/^Counterparty monthly value-flow breakdown was grouped by month over confirmed 1C movement rows$/i.test(value)) {
return "Помесячная раскладка входящих поступлений построена только по подтвержденным строкам поступлений в 1С.";
}
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
}
if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) {
return "Нетто денежного потока рассчитано только как входящие подтвержденные строки 1С минус исходящие подтвержденные строки 1С.";
}
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
}
const metadataSurfaceMatch = value.match(/^Confirmed 1C metadata surface(?: for scope "([^"]+)")?: (\d+) rows and (\d+) matching objects$/i);
if (metadataSurfaceMatch) {
const scopePart = metadataSurfaceMatch[1] ? ` по области "${metadataSurfaceMatch[1]}"` : "";
return `В 1С подтверждена metadata-поверхность${scopePart}: ${metadataSurfaceMatch[2]} строк metadata-ответа и ${metadataSurfaceMatch[3]} совпавших объекта(ов).`;
}
const metadataObjectSetsMatch = value.match(/^Available metadata object sets: (.+)$/i);
if (metadataObjectSetsMatch) {
return `Доступные типы metadata-объектов: ${metadataObjectSetsMatch[1]}.`;
}
const selectedMetadataEntitySetMatch = value.match(/^Selected metadata entity set: (.+)$/i);
if (selectedMetadataEntitySetMatch) {
return `Выбранное семейство metadata-объектов: ${selectedMetadataEntitySetMatch[1]}.`;
}
const selectedMetadataObjectsMatch = value.match(/^Selected metadata objects: (.+)$/i);
if (selectedMetadataObjectsMatch) {
return `Выбранные metadata-объекты для следующего шага: ${selectedMetadataObjectsMatch[1]}.`;
}
const metadataFieldsMatch = value.match(/^Available metadata fields\/sections: (.+)$/i);
if (metadataFieldsMatch) {
return `Доступные metadata-поля/секции: ${metadataFieldsMatch[1]}.`;
}
const metadataLaneInferenceMatch = value.match(/^A likely next checked lane may be inferred as (document_evidence|movement_evidence|catalog_drilldown) from the confirmed metadata surface$/i);
if (metadataLaneInferenceMatch) {
const routeLabel = metadataLaneInferenceMatch[1] === "document_evidence"
? "контур документов"
: metadataLaneInferenceMatch[1] === "movement_evidence"
? "контур движений/регистров"
: "контур справочников и связанных объектов";
return `Следующий проверяемый контур по этой metadata-поверхности можно ограниченно оценить как ${routeLabel}.`;
}
if (/^Detailed metadata fields were not returned by this MCP metadata probe$/i.test(value)) {
return "Эта MCP-проверка metadata не вернула детальный список полей.";
}
const metadataAmbiguityMatch = value.match(/^Exact downstream metadata surface remains ambiguous across: (.+)$/i);
if (metadataAmbiguityMatch) {
return `Точная downstream metadata-поверхность пока неоднозначна между family: ${metadataAmbiguityMatch[1]}.`;
}
const noMatchingMetadataScopeMatch = value.match(/^No matching 1C metadata objects were confirmed for scope "([^"]+)"$/i);
if (noMatchingMetadataScopeMatch) {
return `В 1С не подтверждены metadata-объекты по области "${noMatchingMetadataScopeMatch[1]}".`;
}
if (/^No matching 1C metadata objects were confirmed by this MCP metadata probe$/i.test(value)) {
return "В 1С эта MCP-проверка не подтвердила подходящих metadata-объектов.";
}
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
return "Юридическая дата регистрации этим поиском не подтверждена.";
}
if (/^Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached$/i.test(value)) {
return "Полное покрытие запрошенного периода не подтверждено: проверка достигла лимита найденных строк.";
}
if (/^Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached$/i.test(value)) {
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено: хотя бы одна сторона проверки достигла лимита найденных строк.";
}
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
return "Полный объем входящих поступлений вне проверенного периода этим поиском не подтвержден.";
}
if (/^Full all-time turnover is not proven without an explicit checked period$/i.test(value)) {
return "Полный объем входящих поступлений за все время без явно проверенного периода не подтвержден.";
}
if (/^Full document history outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
return "Полный исторический срез документов вне проверенного периода этим поиском не подтвержден.";
}
if (/^Full document history is not proven without an explicit checked period$/i.test(value)) {
return "Полный срез документов без явно проверенного периода не подтвержден.";
}
if (/^Full movement history outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
return "Полный исторический срез движений вне проверенного периода этим поиском не подтвержден.";
}
if (/^Full movement history is not proven without an explicit checked period$/i.test(value)) {
return "Полный срез движений без явно проверенного периода не подтвержден.";
}
if (/^Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
return "Полный объем исходящих платежей вне проверенного периода этим поиском не подтвержден.";
}
if (/^Full all-time supplier-payout amount is not proven without an explicit checked period$/i.test(value)) {
return "Полный объем исходящих платежей за все время без явно проверенного периода не подтвержден.";
}
if (/^Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
return "Полный двусторонний денежный поток вне проверенного периода этим поиском не подтвержден.";
}
if (/^Full all-time bidirectional value-flow is not proven without an explicit checked period$/i.test(value)) {
return "Полный двусторонний денежный поток за все время без явно проверенного периода не подтвержден.";
}
if (/^Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit$/i.test(value)) {
return "Покрытие запрошенного периода восстановлено помесячными проверками 1С после того, как общая выборка уперлась в лимит строк.";
}
if (/^Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes after a broad probe hit the row limit$/i.test(value)) {
return "Покрытие запрошенного периода по двустороннему денежному потоку восстановлено помесячными проверками 1С после того, как общая выборка уперлась в лимит строк хотя бы по одной стороне.";
}
return value;
}
function section(title, lines) {
const clean = userFacingLines(lines.map(localizeLine));
if (clean.length === 0) {
return null;
}
return `${title}\n${clean.map((line) => `- ${line}`).join("\n")}`;
}
function statusFrom(entryPoint) {
if (!entryPoint || entryPoint.entry_status === "skipped_not_applicable") {
return "not_applicable";
}
if (entryPoint.entry_status === "skipped_needs_more_context") {
return "clarification_candidate";
}
const bridgeStatus = toNonEmptyString(toRecordObject(entryPoint.bridge)?.bridge_status);
if (bridgeStatus === "answer_draft_ready") {
return "ready_for_guarded_use";
}
if (bridgeStatus === "needs_clarification") {
return "clarification_candidate";
}
if (bridgeStatus === "checked_sources_only") {
return "checked_sources_only_candidate";
}
if (bridgeStatus === "blocked") {
return "blocked";
}
if (bridgeStatus === "unsupported") {
return "unsupported";
}
return "not_applicable";
}
function replyTypeFor(status) {
if (status === "clarification_candidate") {
return "clarification_required";
}
if (status === "blocked" || status === "unsupported" || status === "not_applicable") {
return "no_grounded_answer";
}
return "partial_coverage";
}
function buildReplyText(entryPoint, status) {
const bridge = toRecordObject(entryPoint.bridge);
const draft = toRecordObject(bridge?.answer_draft);
if (!draft) {
if (status === "clarification_candidate") {
return "Нужно уточнить контекст перед поиском в 1С: контрагента, период или организацию.";
}
return null;
}
const blocks = [
toNonEmptyString(draft.headline) ? `Коротко: ${localizeLine(String(draft.headline))}` : null,
section("Что подтверждено:", toStringList(draft.confirmed_lines)),
section("Что можно сказать только как вывод:", toStringList(draft.inference_lines)),
section("Что не подтверждено:", toStringList(draft.unknown_lines)),
section("Ограничения проверки:", toStringList(draft.limitation_lines)),
toNonEmptyString(draft.next_step_line) ? `Следующий шаг: ${localizeLine(String(draft.next_step_line))}` : null
].filter((item) => Boolean(item));
const reply = blocks.join("\n\n").trim();
return reply.length > 0 && !hasInternalMechanics(reply) ? reply : null;
}
function buildAssistantMcpDiscoveryResponseCandidate(entryPoint) {
const entry = entryPoint ?? null;
const status = statusFrom(entry);
const reasonCodes = uniqueStrings(entry?.reason_codes ?? []);
pushReason(reasonCodes, `mcp_discovery_response_candidate_${status}`);
pushReason(reasonCodes, "mcp_discovery_response_candidate_not_hot_wired");
const replyText = entry && (status === "ready_for_guarded_use" || status === "checked_sources_only_candidate" || status === "clarification_candidate")
? buildReplyText(entry, status)
: null;
return {
schema_version: exports.ASSISTANT_MCP_DISCOVERY_RESPONSE_CANDIDATE_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryResponseCandidate",
candidate_status: replyText ? status : status === "clarification_candidate" ? status : status,
hot_runtime_wired: false,
reply_type: replyTypeFor(status),
reply_text: replyText,
eligible_for_future_hot_runtime: Boolean(replyText) && (status === "ready_for_guarded_use" || status === "checked_sources_only_candidate" || status === "clarification_candidate"),
must_keep_internal_mechanics_hidden: true,
reason_codes: reasonCodes
};
}