NODEDC_1C/llm_normalizer/backend/src/services/assistantMcpDiscoveryRuntim...

195 lines
6.5 KiB
TypeScript

import {
getAssistantMcpCatalogPrimitive,
type AssistantMcpCatalogEvidenceFloor
} from "./assistantMcpCatalogIndex";
import {
type AssistantMcpDiscoveryPlannerContract,
type AssistantMcpDiscoveryPlannerStatus
} from "./assistantMcpDiscoveryPlanner";
import type { AssistantMcpDiscoveryPrimitive } from "./assistantMcpDiscoveryPolicy";
export const ASSISTANT_MCP_DISCOVERY_RUNTIME_DRY_RUN_SCHEMA_VERSION =
"assistant_mcp_discovery_runtime_dry_run_v1" as const;
export type AssistantMcpDiscoveryRuntimeAdapterStatus =
| "dry_run_ready"
| "needs_clarification"
| "blocked";
export type AssistantMcpDiscoveryRuntimeStepStatus = "ready" | "missing_axes" | "blocked";
export interface AssistantMcpDiscoveryRuntimeStepContract {
sequence: number;
primitive_id: AssistantMcpDiscoveryPrimitive;
step_status: AssistantMcpDiscoveryRuntimeStepStatus;
purpose: string;
provided_axes: string[];
required_axis_options: string[][];
missing_axis_options: string[][];
optional_axes: string[];
expected_fact_kinds: string[];
evidence_floor: AssistantMcpCatalogEvidenceFloor;
runtime_must_execute: true;
dry_run_only: true;
stop_condition: string;
}
export interface AssistantMcpDiscoveryEvidenceGateRequirement {
required: true;
expected_inputs: string[];
answer_may_use_raw_model_claims: false;
}
export interface AssistantMcpDiscoveryRuntimeDryRunContract {
schema_version: typeof ASSISTANT_MCP_DISCOVERY_RUNTIME_DRY_RUN_SCHEMA_VERSION;
policy_owner: "assistantMcpDiscoveryRuntimeAdapter";
adapter_status: AssistantMcpDiscoveryRuntimeAdapterStatus;
planner_status: AssistantMcpDiscoveryPlannerStatus;
mcp_execution_performed: false;
execution_steps: AssistantMcpDiscoveryRuntimeStepContract[];
execution_budget: {
max_probe_count: number;
max_rows_per_probe: number;
};
evidence_gate: AssistantMcpDiscoveryEvidenceGateRequirement;
user_facing_fallback: string | null;
reason_codes: string[];
}
function normalizeReasonCode(value: string): string | null {
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: string[], value: string): void {
const normalized = normalizeReasonCode(value);
if (normalized && !target.includes(normalized)) {
target.push(normalized);
}
}
function uniqueStrings(values: string[]): string[] {
const result: string[] = [];
for (const value of values) {
const text = value.trim();
if (text && !result.includes(text)) {
result.push(text);
}
}
return result;
}
function stepStatusFor(input: {
adapterStatus: AssistantMcpDiscoveryRuntimeAdapterStatus;
missingAxisOptions: string[][];
}): AssistantMcpDiscoveryRuntimeStepStatus {
if (input.adapterStatus === "blocked") {
return "blocked";
}
if (input.missingAxisOptions.length > 0) {
return "missing_axes";
}
return "ready";
}
function stopConditionFor(evidenceFloor: AssistantMcpCatalogEvidenceFloor): string {
if (evidenceFloor === "rows_matched") {
return "stop_after_allowed_probe_returns_matched_rows_or_reports_no_match";
}
if (evidenceFloor === "rows_received") {
return "stop_after_allowed_probe_returns_rows_or_reports_empty_source";
}
if (evidenceFloor === "source_summary") {
return "stop_after_allowed_probe_returns_source_summary_or_limitation";
}
return "stop_without_fact_claim";
}
function adapterStatusFor(planner: AssistantMcpDiscoveryPlannerContract): AssistantMcpDiscoveryRuntimeAdapterStatus {
if (planner.planner_status === "blocked" || planner.catalog_review.review_status === "catalog_blocked") {
return "blocked";
}
if (planner.planner_status !== "ready_for_execution" || planner.catalog_review.review_status !== "catalog_compatible") {
return "needs_clarification";
}
return "dry_run_ready";
}
function fallbackFor(status: AssistantMcpDiscoveryRuntimeAdapterStatus): string | null {
if (status === "dry_run_ready") {
return null;
}
if (status === "blocked") {
return "explain_that_runtime_policy_blocked_mcp_discovery";
}
return "ask_for_missing_scope_before_mcp_discovery";
}
export function buildAssistantMcpDiscoveryRuntimeDryRun(
planner: AssistantMcpDiscoveryPlannerContract
): AssistantMcpDiscoveryRuntimeDryRunContract {
const adapterStatus = adapterStatusFor(planner);
const reasonCodes = uniqueStrings([
...planner.reason_codes,
...planner.discovery_plan.reason_codes,
...planner.catalog_review.reason_codes
]);
const providedAxes = uniqueStrings(planner.required_axes);
const executionSteps = planner.discovery_plan.allowed_primitives.map((primitiveId, index) => {
const catalog = getAssistantMcpCatalogPrimitive(primitiveId);
const missingAxisOptions = planner.catalog_review.missing_axes_by_primitive[primitiveId] ?? [];
return {
sequence: index + 1,
primitive_id: primitiveId,
step_status: stepStatusFor({ adapterStatus, missingAxisOptions }),
purpose: catalog.purpose,
provided_axes: providedAxes,
required_axis_options: catalog.required_axes_any_of,
missing_axis_options: missingAxisOptions,
optional_axes: catalog.optional_axes,
expected_fact_kinds: catalog.output_fact_kinds,
evidence_floor: catalog.evidence_floor,
runtime_must_execute: true,
dry_run_only: true,
stop_condition: stopConditionFor(catalog.evidence_floor)
} satisfies AssistantMcpDiscoveryRuntimeStepContract;
});
if (adapterStatus === "dry_run_ready") {
pushReason(reasonCodes, "runtime_dry_run_ready_without_mcp_execution");
} else if (adapterStatus === "blocked") {
pushReason(reasonCodes, "runtime_dry_run_blocked_before_mcp_execution");
} else {
pushReason(reasonCodes, "runtime_dry_run_needs_clarification_before_mcp_execution");
}
return {
schema_version: ASSISTANT_MCP_DISCOVERY_RUNTIME_DRY_RUN_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryRuntimeAdapter",
adapter_status: adapterStatus,
planner_status: planner.planner_status,
mcp_execution_performed: false,
execution_steps: executionSteps,
execution_budget: planner.discovery_plan.execution_budget,
evidence_gate: {
required: true,
expected_inputs: [
"probe_results",
"confirmed_facts",
"inferred_facts",
"unknown_facts",
"source_rows_summary",
"query_limitations"
],
answer_may_use_raw_model_claims: false
},
user_facing_fallback: fallbackFor(adapterStatus),
reason_codes: reasonCodes
};
}