292 lines
19 KiB
JavaScript
292 lines
19 KiB
JavaScript
"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) {
|
||
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 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 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 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
|
||
};
|
||
}
|