235 lines
9.0 KiB
TypeScript
235 lines
9.0 KiB
TypeScript
import {
|
|
buildAssistantMcpDiscoveryAnswerDraft,
|
|
type AssistantMcpDiscoveryAnswerDraftContract
|
|
} from "./assistantMcpDiscoveryAnswerAdapter";
|
|
import {
|
|
executeAssistantMcpDiscoveryPilot,
|
|
type AssistantMcpDiscoveryPilotExecutionContract,
|
|
type AssistantMcpDiscoveryPilotExecutorDeps
|
|
} from "./assistantMcpDiscoveryPilotExecutor";
|
|
import {
|
|
planAssistantMcpDiscovery,
|
|
type AssistantMcpDiscoveryChainId,
|
|
type AssistantMcpDiscoveryMetadataSurfaceRef,
|
|
type AssistantMcpDiscoveryPlannerContract
|
|
} from "./assistantMcpDiscoveryPlanner";
|
|
import type { AssistantMcpDiscoveryDataNeedGraphContract } from "./assistantMcpDiscoveryDataNeedGraph";
|
|
import type { AssistantMcpDiscoveryTurnMeaningRef } from "./assistantMcpDiscoveryPolicy";
|
|
|
|
export const ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION =
|
|
"assistant_mcp_discovery_runtime_bridge_v1" as const;
|
|
export const ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION =
|
|
"assistant_mcp_discovery_loop_state_v1" as const;
|
|
|
|
export type AssistantMcpDiscoveryRuntimeBridgeStatus =
|
|
| "answer_draft_ready"
|
|
| "checked_sources_only"
|
|
| "needs_clarification"
|
|
| "blocked"
|
|
| "unsupported";
|
|
export type AssistantMcpDiscoveryLoopStatus =
|
|
| "awaiting_clarification"
|
|
| "ready_for_next_hop"
|
|
| "blocked";
|
|
|
|
export interface AssistantMcpDiscoveryRuntimeBridgeInput {
|
|
semanticDataNeed?: string | null;
|
|
dataNeedGraph?: AssistantMcpDiscoveryDataNeedGraphContract | null;
|
|
metadataSurface?: AssistantMcpDiscoveryMetadataSurfaceRef | null;
|
|
turnMeaning?: AssistantMcpDiscoveryTurnMeaningRef | null;
|
|
deps?: AssistantMcpDiscoveryPilotExecutorDeps;
|
|
}
|
|
|
|
export interface AssistantMcpDiscoveryLoopStateContract {
|
|
schema_version: typeof ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION;
|
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge";
|
|
loop_status: AssistantMcpDiscoveryLoopStatus;
|
|
selected_chain_id: AssistantMcpDiscoveryChainId;
|
|
pilot_scope: AssistantMcpDiscoveryPilotExecutionContract["pilot_scope"];
|
|
asked_domain_family: string | null;
|
|
asked_action_family: string | null;
|
|
unsupported_but_understood_family: string | null;
|
|
ranking_need: string | null;
|
|
pending_axes: string[];
|
|
provided_axes: string[];
|
|
explicit_entity_candidates: string[];
|
|
metadata_scope_hint: string | null;
|
|
subject_resolution_optional: boolean;
|
|
explicit_organization_scope: string | null;
|
|
explicit_date_scope: string | null;
|
|
}
|
|
|
|
export interface AssistantMcpDiscoveryRuntimeBridgeContract {
|
|
schema_version: typeof ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION;
|
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge";
|
|
bridge_status: AssistantMcpDiscoveryRuntimeBridgeStatus;
|
|
hot_runtime_wired: false;
|
|
planner: AssistantMcpDiscoveryPlannerContract;
|
|
pilot: AssistantMcpDiscoveryPilotExecutionContract;
|
|
answer_draft: AssistantMcpDiscoveryAnswerDraftContract;
|
|
loop_state: AssistantMcpDiscoveryLoopStateContract;
|
|
user_facing_response_allowed: boolean;
|
|
business_fact_answer_allowed: boolean;
|
|
requires_user_clarification: boolean;
|
|
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 = String(value ?? "").trim();
|
|
if (text && !result.includes(text)) {
|
|
result.push(text);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function bridgeStatusFor(
|
|
pilot: AssistantMcpDiscoveryPilotExecutionContract,
|
|
draft: AssistantMcpDiscoveryAnswerDraftContract
|
|
): AssistantMcpDiscoveryRuntimeBridgeStatus {
|
|
if (draft.answer_mode === "blocked" || pilot.pilot_status === "blocked") {
|
|
return "blocked";
|
|
}
|
|
if (draft.answer_mode === "needs_clarification" || pilot.pilot_status === "skipped_needs_clarification") {
|
|
return "needs_clarification";
|
|
}
|
|
if (pilot.pilot_status === "unsupported") {
|
|
return "unsupported";
|
|
}
|
|
if (draft.answer_mode === "checked_sources_only") {
|
|
return "checked_sources_only";
|
|
}
|
|
return "answer_draft_ready";
|
|
}
|
|
|
|
function businessFactAnswerAllowed(draft: AssistantMcpDiscoveryAnswerDraftContract): boolean {
|
|
return draft.answer_mode === "confirmed_with_bounded_inference" || draft.answer_mode === "bounded_inference_only";
|
|
}
|
|
|
|
function loopStatusFor(
|
|
bridgeStatus: AssistantMcpDiscoveryRuntimeBridgeStatus
|
|
): AssistantMcpDiscoveryLoopStatus {
|
|
if (bridgeStatus === "needs_clarification") {
|
|
return "awaiting_clarification";
|
|
}
|
|
if (bridgeStatus === "blocked" || bridgeStatus === "unsupported") {
|
|
return "blocked";
|
|
}
|
|
return "ready_for_next_hop";
|
|
}
|
|
|
|
function flattenAxes(
|
|
pilot: AssistantMcpDiscoveryPilotExecutionContract,
|
|
source: "provided_axes" | "missing_axis_options"
|
|
): string[] {
|
|
const result: string[] = [];
|
|
for (const step of pilot.dry_run.execution_steps) {
|
|
if (source === "provided_axes") {
|
|
for (const axis of step.provided_axes) {
|
|
if (axis && !result.includes(axis)) {
|
|
result.push(axis);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
for (const option of step.missing_axis_options) {
|
|
for (const axis of option) {
|
|
if (axis && !result.includes(axis)) {
|
|
result.push(axis);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function entityCandidatesFromPlanner(planner: AssistantMcpDiscoveryPlannerContract): string[] {
|
|
const values = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
|
return uniqueStrings(values);
|
|
}
|
|
|
|
function buildLoopState(
|
|
planner: AssistantMcpDiscoveryPlannerContract,
|
|
pilot: AssistantMcpDiscoveryPilotExecutionContract,
|
|
bridgeStatus: AssistantMcpDiscoveryRuntimeBridgeStatus
|
|
): AssistantMcpDiscoveryLoopStateContract {
|
|
const plannerClarificationGaps = planner.discovery_plan.clarification_gaps ?? [];
|
|
return {
|
|
schema_version: ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION,
|
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
|
loop_status: loopStatusFor(bridgeStatus),
|
|
selected_chain_id: planner.selected_chain_id,
|
|
pilot_scope: pilot.pilot_scope,
|
|
asked_domain_family: planner.discovery_plan.turn_meaning_ref?.asked_domain_family ?? null,
|
|
asked_action_family: planner.discovery_plan.turn_meaning_ref?.asked_action_family ?? null,
|
|
unsupported_but_understood_family:
|
|
planner.discovery_plan.turn_meaning_ref?.unsupported_but_understood_family ?? null,
|
|
ranking_need: planner.data_need_graph?.ranking_need ?? planner.discovery_plan.turn_meaning_ref?.seeded_ranking_need ?? null,
|
|
pending_axes: plannerClarificationGaps.length > 0 ? plannerClarificationGaps : flattenAxes(pilot, "missing_axis_options"),
|
|
provided_axes: flattenAxes(pilot, "provided_axes"),
|
|
explicit_entity_candidates: entityCandidatesFromPlanner(planner),
|
|
metadata_scope_hint:
|
|
planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint ??
|
|
planner.data_need_graph?.metadata_scope_hint ??
|
|
null,
|
|
subject_resolution_optional:
|
|
planner.discovery_plan.turn_meaning_ref?.subject_resolution_optional === true ||
|
|
planner.data_need_graph?.subject_resolution_optional === true,
|
|
explicit_organization_scope: planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope ?? null,
|
|
explicit_date_scope: planner.discovery_plan.turn_meaning_ref?.explicit_date_scope ?? null
|
|
};
|
|
}
|
|
|
|
export async function runAssistantMcpDiscoveryRuntimeBridge(
|
|
input: AssistantMcpDiscoveryRuntimeBridgeInput
|
|
): Promise<AssistantMcpDiscoveryRuntimeBridgeContract> {
|
|
const planner = planAssistantMcpDiscovery({
|
|
semanticDataNeed: input.semanticDataNeed,
|
|
dataNeedGraph: input.dataNeedGraph,
|
|
metadataSurface: input.metadataSurface,
|
|
turnMeaning: input.turnMeaning
|
|
});
|
|
const pilot = await executeAssistantMcpDiscoveryPilot(planner, input.deps);
|
|
const answerDraft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
|
const bridgeStatus = bridgeStatusFor(pilot, answerDraft);
|
|
const loopState = buildLoopState(planner, pilot, bridgeStatus);
|
|
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
|
|
|
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
|
pushReason(reasonCodes, "runtime_bridge_not_wired_to_hot_assistant_answer");
|
|
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
|
|
|
|
return {
|
|
schema_version: ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION,
|
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
|
bridge_status: bridgeStatus,
|
|
hot_runtime_wired: false,
|
|
planner,
|
|
pilot,
|
|
answer_draft: answerDraft,
|
|
loop_state: loopState,
|
|
user_facing_response_allowed: bridgeStatus !== "blocked",
|
|
business_fact_answer_allowed: businessFactAnswerAllowed(answerDraft),
|
|
requires_user_clarification: bridgeStatus === "needs_clarification",
|
|
reason_codes: reasonCodes
|
|
};
|
|
}
|