ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.51 - вынос living-chat guard chain (script/grounding/meta boundary wiring) в отдельный input-builder adapter, чтобы ужать assistantService.

This commit is contained in:
dctouch 2026-04-11 00:57:52 +03:00
parent 5e4cc0ed67
commit b612615219
6 changed files with 338 additions and 51 deletions

View File

@ -1610,7 +1610,42 @@ Validation:
- `assistantWave10SettlementCorrectiveRegression.test.ts`
- `assistantLivingChatMode.test.ts`
Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 + 2.49 + 2.50 completed)**
Implemented in current pass (Phase 2.51):
1. Extracted living-chat attempt input assembly from `assistantAddressAttemptRuntimeAdapter` into dedicated builder:
- `assistantLivingChatAttemptInputBuilder.ts`
- introduced:
- `buildAssistantLivingChatAttemptRuntimeInput(...)`
2. Rewired `assistantAddressAttemptRuntimeAdapter` to consume the new builder (behavior-preserving):
- moved inline living-chat payload mapping (including `traceIdFactory` derivation and scope/meta wiring) behind a single input-builder boundary.
3. Added focused unit tests:
- `assistantLivingChatAttemptInputBuilder.test.ts`
Validation:
1. `npm run build` passed.
2. Targeted living/address/deep followup pack passed:
- `assistantLivingChatAttemptInputBuilder.test.ts`
- `assistantAddressAttemptRuntimeAdapter.test.ts`
- `assistantLivingChatAttemptRuntimeAdapter.test.ts`
- `assistantLivingChatHandlerRuntimeAdapter.test.ts`
- `assistantLivingChatRuntimeAdapter.test.ts`
- `assistantTurnRuntimeDepsAdapter.test.ts`
- `assistantTurnRuntimeInputBuilder.test.ts`
- `assistantTurnAttemptRuntimeAdapter.test.ts`
- `assistantOrganizationScopeRuntimeAdapter.test.ts`
- `assistantAddressLaneAttemptRuntimeAdapter.test.ts`
- `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts`
- `assistantAddressRuntimeAdapter.test.ts`
- `assistantAddressLaneResponseRuntimeAdapter.test.ts`
- `assistantDeepTurnAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnAnalysisAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnAnalysisRuntimeAdapter.test.ts`
- `assistantDeepTurnResponseRuntimeAdapter.test.ts`
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
- `assistantWave10SettlementCorrectiveRegression.test.ts`
- `assistantLivingChatMode.test.ts`
Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 + 2.49 + 2.50 + 2.51 completed)**
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)

View File

@ -5,6 +5,7 @@ const assistantAddressRuntimeAdapter_1 = require("./assistantAddressRuntimeAdapt
const assistantAddressLaneAttemptRuntimeAdapter_1 = require("./assistantAddressLaneAttemptRuntimeAdapter");
const assistantAddressLaneResponseAttemptRuntimeAdapter_1 = require("./assistantAddressLaneResponseAttemptRuntimeAdapter");
const assistantLivingChatAttemptRuntimeAdapter_1 = require("./assistantLivingChatAttemptRuntimeAdapter");
const assistantLivingChatAttemptInputBuilder_1 = require("./assistantLivingChatAttemptInputBuilder");
async function runAssistantAddressAttemptRuntime(input) {
const runAddressRuntimeSafe = input.runAddressRuntime ?? assistantAddressRuntimeAdapter_1.runAssistantAddressRuntime;
const runAddressLaneAttemptRuntimeSafe = input.runAddressLaneAttemptRuntime ?? assistantAddressLaneAttemptRuntimeAdapter_1.runAssistantAddressLaneAttemptRuntime;
@ -31,7 +32,7 @@ async function runAssistantAddressAttemptRuntime(input) {
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory
});
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => runLivingChatAttemptRuntimeSafe({
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => runLivingChatAttemptRuntimeSafe((0, assistantLivingChatAttemptInputBuilder_1.buildAssistantLivingChatAttemptRuntimeInput)({
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
@ -42,7 +43,6 @@ async function runAssistantAddressAttemptRuntime(input) {
activeOrganization: input.sessionScope.activeOrganization
},
addressRuntimeMeta,
traceIdFactory: () => `chat-${input.messageIdFactory().replace(/^msg-/, "")}`,
toNonEmptyString: input.toNonEmptyString,
mergeKnownOrganizations: input.mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal,
@ -77,7 +77,7 @@ async function runAssistantAddressAttemptRuntime(input) {
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
});
}));
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => runAddressLaneAttemptRuntimeSafe({
messageUsed,
carryMeta,

View File

@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantLivingChatAttemptRuntimeInput = buildAssistantLivingChatAttemptRuntimeInput;
function buildAssistantLivingChatAttemptRuntimeInput(input) {
return {
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision: input.modeDecision,
sessionScope: {
knownOrganizations: input.sessionScope.knownOrganizations,
selectedOrganization: input.sessionScope.selectedOrganization,
activeOrganization: input.sessionScope.activeOrganization
},
addressRuntimeMeta: input.addressRuntimeMeta ?? null,
traceIdFactory: () => `chat-${input.messageIdFactory().replace(/^msg-/, "")}`,
toNonEmptyString: input.toNonEmptyString,
mergeKnownOrganizations: input.mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: input.resolveDataScopeProbe,
applyScriptGuard: input.applyScriptGuard,
applyGroundingGuard: input.applyGroundingGuard,
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory,
nowIso: input.nowIso,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
};
}

View File

@ -15,6 +15,7 @@ import {
runAssistantLivingChatAttemptRuntime,
type RunAssistantLivingChatAttemptRuntimeInput
} from "./assistantLivingChatAttemptRuntimeAdapter";
import { buildAssistantLivingChatAttemptRuntimeInput } from "./assistantLivingChatAttemptInputBuilder";
interface AddressAttemptPayload {
llmProvider?: unknown;
@ -135,53 +136,54 @@ export async function runAssistantAddressAttemptRuntime<ResponseType = unknown>(
modeDecision,
addressRuntimeMeta = null
) =>
runLivingChatAttemptRuntimeSafe({
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision,
sessionScope: {
knownOrganizations: input.sessionScope.knownOrganizations,
selectedOrganization: input.sessionScope.selectedOrganization,
activeOrganization: input.sessionScope.activeOrganization
},
addressRuntimeMeta,
traceIdFactory: () => `chat-${input.messageIdFactory().replace(/^msg-/, "")}`,
toNonEmptyString: input.toNonEmptyString,
mergeKnownOrganizations: input.mergeKnownOrganizations as any,
hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: input.resolveDataScopeProbe,
applyScriptGuard: input.applyScriptGuard,
applyGroundingGuard: input.applyGroundingGuard,
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory,
nowIso: input.nowIso,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
} as RunAssistantLivingChatAttemptRuntimeInput<ResponseType>);
runLivingChatAttemptRuntimeSafe(
buildAssistantLivingChatAttemptRuntimeInput({
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision,
sessionScope: {
knownOrganizations: input.sessionScope.knownOrganizations,
selectedOrganization: input.sessionScope.selectedOrganization,
activeOrganization: input.sessionScope.activeOrganization
},
addressRuntimeMeta,
toNonEmptyString: input.toNonEmptyString,
mergeKnownOrganizations: input.mergeKnownOrganizations as any,
hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: input.resolveDataScopeProbe,
applyScriptGuard: input.applyScriptGuard,
applyGroundingGuard: input.applyGroundingGuard,
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory,
nowIso: input.nowIso,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
} as any) as RunAssistantLivingChatAttemptRuntimeInput<ResponseType>
);
const runAddressLaneAttempt: RunAssistantAddressRuntimeInput<ResponseType>["runAddressLaneAttempt"] = async (
messageUsed,

View File

@ -0,0 +1,102 @@
import type { RunAssistantLivingChatAttemptRuntimeInput } from "./assistantLivingChatAttemptRuntimeAdapter";
interface AssistantLivingChatAttemptSessionScope {
knownOrganizations: string[];
selectedOrganization: string | null;
activeOrganization: string | null;
}
export interface BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType = unknown> {
sessionId: string;
userMessage: string;
sessionItems: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["sessionItems"];
modeDecision: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["modeDecision"];
sessionScope: AssistantLivingChatAttemptSessionScope;
addressRuntimeMeta?: Record<string, unknown> | null;
toNonEmptyString: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["toNonEmptyString"];
mergeKnownOrganizations: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["mergeKnownOrganizations"];
hasAssistantDataScopeMetaQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantDataScopeMetaQuestionSignal"];
shouldHandleAsAssistantCapabilityMetaQuery: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldHandleAsAssistantCapabilityMetaQuery"];
hasDestructiveDataActionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasDestructiveDataActionSignal"];
hasDangerOrCoercionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasDangerOrCoercionSignal"];
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
applyScriptGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyScriptGuard"];
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
buildAssistantCapabilityContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantCapabilityContractReply"];
appendItem: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["appendItem"];
getSession: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["getSession"];
persistSession: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["persistSession"];
cloneConversation: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["cloneConversation"];
logEvent: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["logEvent"];
messageIdFactory: NonNullable<RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["messageIdFactory"]>;
nowIso: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["nowIso"];
payload: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["payload"];
chatClient: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["chatClient"];
loadAssistantCanonExcerpt: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["loadAssistantCanonExcerpt"];
sanitizeOutgoingAssistantText: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["sanitizeOutgoingAssistantText"];
defaultModel: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["defaultModel"];
defaultBaseUrl: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["defaultBaseUrl"];
defaultApiKey?: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["defaultApiKey"];
}
export function buildAssistantLivingChatAttemptRuntimeInput<ResponseType = unknown>(
input: BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType>
): RunAssistantLivingChatAttemptRuntimeInput<ResponseType> {
return {
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision: input.modeDecision,
sessionScope: {
knownOrganizations: input.sessionScope.knownOrganizations,
selectedOrganization: input.sessionScope.selectedOrganization,
activeOrganization: input.sessionScope.activeOrganization
},
addressRuntimeMeta: input.addressRuntimeMeta ?? null,
traceIdFactory: () => `chat-${input.messageIdFactory().replace(/^msg-/, "")}`,
toNonEmptyString: input.toNonEmptyString,
mergeKnownOrganizations: input.mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: input.resolveDataScopeProbe,
applyScriptGuard: input.applyScriptGuard,
applyGroundingGuard: input.applyGroundingGuard,
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory,
nowIso: input.nowIso,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
};
}

View File

@ -0,0 +1,96 @@
import { describe, expect, it, vi } from "vitest";
import { buildAssistantLivingChatAttemptRuntimeInput } from "../src/services/assistantLivingChatAttemptInputBuilder";
function buildInput(overrides: Record<string, unknown> = {}) {
return {
sessionId: "asst-1",
userMessage: "че там",
sessionItems: [],
modeDecision: {
mode: "chat",
reason: "living_chat_signal_detected"
},
sessionScope: {
knownOrganizations: ["Org A"],
selectedOrganization: "Org A",
activeOrganization: "Org A"
},
addressRuntimeMeta: {
source: "address_runtime"
},
toNonEmptyString: (value: unknown) =>
typeof value === "string" && value.trim().length > 0 ? value.trim() : null,
mergeKnownOrganizations: (values: string[]) => values,
hasAssistantDataScopeMetaQuestionSignal: () => false,
shouldHandleAsAssistantCapabilityMetaQuery: () => false,
hasDestructiveDataActionSignal: () => false,
hasDangerOrCoercionSignal: () => false,
hasOperationalAdminActionRequestSignal: () => false,
hasOrganizationFactLookupSignal: () => false,
hasOrganizationFactFollowupSignal: () => false,
shouldEmitOrganizationSelectionReply: () => false,
hasAssistantCapabilityQuestionSignal: () => false,
resolveDataScopeProbe: () => null,
applyScriptGuard: (chatText: string) => chatText,
applyGroundingGuard: (guardInput: Record<string, unknown>) => guardInput,
buildAssistantSafetyRefusalReply: () => "safety",
buildAssistantDataScopeContractReply: () => "scope",
buildAssistantOrganizationFactBoundaryReply: () => "boundary",
buildAssistantDataScopeSelectionReply: () => "selection",
buildAssistantOperationalBoundaryReply: () => "operational",
buildAssistantCapabilityContractReply: () => "capability",
appendItem: () => {},
getSession: () => null,
persistSession: () => {},
cloneConversation: (items: unknown[]) => items,
logEvent: () => {},
messageIdFactory: vi.fn(() => "msg-abc123"),
nowIso: () => "2026-04-11T00:00:00.000Z",
payload: {
llmProvider: "openai"
},
chatClient: {},
loadAssistantCanonExcerpt: () => "",
sanitizeOutgoingAssistantText: (value: unknown, fallback = "") => {
const text = typeof value === "string" ? value.trim() : "";
return text || fallback;
},
defaultModel: "gpt-5",
defaultBaseUrl: "http://localhost",
defaultApiKey: "key",
...overrides
} as any;
}
describe("assistant living chat attempt input builder", () => {
it("builds living-chat runtime input with derived trace id and session scope", () => {
const runtimeInput = buildAssistantLivingChatAttemptRuntimeInput(buildInput());
expect(runtimeInput.sessionId).toBe("asst-1");
expect(runtimeInput.userMessage).toBe("че там");
expect(runtimeInput.traceIdFactory()).toBe("chat-abc123");
expect(runtimeInput.sessionScope).toEqual({
knownOrganizations: ["Org A"],
selectedOrganization: "Org A",
activeOrganization: "Org A"
});
expect(runtimeInput.modeDecision).toEqual({
mode: "chat",
reason: "living_chat_signal_detected"
});
});
it("normalizes absent address runtime meta to null and preserves optional api key", () => {
const runtimeInput = buildAssistantLivingChatAttemptRuntimeInput(
buildInput({
addressRuntimeMeta: undefined,
defaultApiKey: undefined
})
);
expect(runtimeInput.addressRuntimeMeta).toBeNull();
expect(runtimeInput.defaultApiKey).toBeUndefined();
expect(runtimeInput.defaultBaseUrl).toBe("http://localhost");
expect(runtimeInput.defaultModel).toBe("gpt-5");
});
});