ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.612.64 - вынос mapper для assistantAddressLaneResponseAttemptRuntimeAdapter и для assistantLivingChatAttemptRuntimeAdapter / сборка входов для living chat attempt в отдельный builder (LLM + handler)

This commit is contained in:
dctouch 2026-04-11 09:52:59 +03:00
parent 6c9b94ef34
commit 79b636bfe6
11 changed files with 489 additions and 78 deletions

View File

@ -1751,7 +1751,55 @@ 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 + 2.51 + 2.52 + 2.53 + 2.54 + 2.55 + 2.56 + 2.57 + 2.58 + 2.59 + 2.60 completed)**
Implemented in current pass (Phase 2.61 + 2.62 + 2.63 + 2.64):
1. Added dedicated runtime input builder for address lane response runtime:
- `assistantAddressLaneResponseRuntimeInputBuilder.ts`
- introduced:
- `buildAssistantAddressLaneResponseRuntimeInput(...)`
2. Rewired `assistantAddressLaneResponseAttemptRuntimeAdapter` to consume the new builder (behavior-preserving):
- removed inline mapping from attempt input to response runtime input.
3. Added dedicated runtime input builders for living-chat attempt orchestration:
- `assistantLivingChatAttemptRuntimeInputBuilder.ts`
- introduced:
- `buildAssistantLivingChatLlmRuntimeInput(...)`
- `buildAssistantLivingChatHandlerRuntimeInput(...)`
4. Rewired `assistantLivingChatAttemptRuntimeAdapter` to consume living-chat runtime input builders (behavior-preserving):
- `buildExecuteLlmChat(...)` now delegates llm payload assembly via builder;
- top-level living-chat handler invocation now uses builder payload.
5. Added focused builder tests:
- `assistantAddressLaneResponseRuntimeInputBuilder.test.ts`
- `assistantLivingChatAttemptRuntimeInputBuilder.test.ts`
Validation:
1. `npm run build` passed.
2. Targeted living/address/deep followup pack passed:
- `assistantAddressLaneResponseRuntimeInputBuilder.test.ts`
- `assistantLivingChatAttemptRuntimeInputBuilder.test.ts`
- `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts`
- `assistantLivingChatAttemptRuntimeAdapter.test.ts`
- `assistantLivingChatHandlerRuntimeAdapter.test.ts`
- `assistantLivingChatRuntimeAdapter.test.ts`
- `assistantAddressAttemptRuntimeAdapter.test.ts`
- `assistantAddressLaneAttemptRuntimeAdapter.test.ts`
- `assistantAddressLaneResponseRuntimeAdapter.test.ts`
- `assistantAddressRuntimeAdapter.test.ts`
- `assistantDeepTurnAttemptInputBuilder.test.ts`
- `assistantDeepTurnAnalysisAttemptInputBuilder.test.ts`
- `assistantDeepTurnResponseRuntimeInputBuilder.test.ts`
- `assistantDeepTurnAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnAnalysisAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnAnalysisRuntimeAdapter.test.ts`
- `assistantDeepTurnResponseRuntimeAdapter.test.ts`
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
- `assistantTurnRuntimeInputBuilder.test.ts`
- `assistantTurnAttemptRuntimeAdapter.test.ts`
- `assistantTurnRuntimeDepsAdapter.test.ts`
- `assistantOrganizationScopeRuntimeAdapter.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 + 2.52 + 2.53 + 2.54 + 2.55 + 2.56 + 2.57 + 2.58 + 2.59 + 2.60 + 2.61 + 2.62 + 2.63 + 2.64 completed)**
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)

View File

@ -2,9 +2,10 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantAddressLaneResponseAttemptRuntime = runAssistantAddressLaneResponseAttemptRuntime;
const assistantAddressLaneResponseRuntimeAdapter_1 = require("./assistantAddressLaneResponseRuntimeAdapter");
const assistantAddressLaneResponseRuntimeInputBuilder_1 = require("./assistantAddressLaneResponseRuntimeInputBuilder");
function runAssistantAddressLaneResponseAttemptRuntime(input) {
const runAddressLaneResponseRuntimeSafe = input.runAddressLaneResponseRuntime ?? assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime;
const runtime = runAddressLaneResponseRuntimeSafe({
const runtime = runAddressLaneResponseRuntimeSafe((0, assistantAddressLaneResponseRuntimeInputBuilder_1.buildAssistantAddressLaneResponseRuntimeInput)({
sessionId: input.sessionId,
userMessage: input.userMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
@ -24,6 +25,6 @@ function runAssistantAddressLaneResponseAttemptRuntime(input) {
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory
});
}));
return runtime.response;
}

View File

@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantAddressLaneResponseRuntimeInput = buildAssistantAddressLaneResponseRuntimeInput;
function buildAssistantAddressLaneResponseRuntimeInput(input) {
return {
sessionId: input.sessionId,
userMessage: input.userMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
addressLane: input.addressLane,
carryoverMeta: input.carryoverMeta,
llmPreDecomposeMeta: input.llmPreDecomposeMeta,
knownOrganizations: input.knownOrganizations,
activeOrganization: input.activeOrganization,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
buildAddressDebugPayload: input.buildAddressDebugPayload,
buildAddressFollowupOffer: input.buildAddressFollowupOffer,
mergeKnownOrganizations: input.mergeKnownOrganizations,
toNonEmptyString: input.toNonEmptyString,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory
};
}

View File

@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantLivingChatAttemptRuntime = runAssistantLivingChatAttemptRuntime;
const assistantLivingChatHandlerRuntimeAdapter_1 = require("./assistantLivingChatHandlerRuntimeAdapter");
const assistantLivingChatLlmRuntimeAdapter_1 = require("./assistantLivingChatLlmRuntimeAdapter");
const assistantLivingChatAttemptRuntimeInputBuilder_1 = require("./assistantLivingChatAttemptRuntimeInputBuilder");
function buildExecuteLlmChat(input, runLivingChatLlmSafe) {
return async () => runLivingChatLlmSafe({
return async () => runLivingChatLlmSafe((0, assistantLivingChatAttemptRuntimeInputBuilder_1.buildAssistantLivingChatLlmRuntimeInput)({
userMessage: input.userMessage,
sessionItems: input.sessionItems,
payload: input.payload,
@ -14,13 +15,13 @@ function buildExecuteLlmChat(input, runLivingChatLlmSafe) {
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
});
}));
}
async function runAssistantLivingChatAttemptRuntime(input) {
const runLivingChatHandlerSafe = input.runLivingChatHandler ?? assistantLivingChatHandlerRuntimeAdapter_1.tryHandleAssistantLivingChatRuntime;
const runLivingChatLlmSafe = input.runLivingChatLlm ?? assistantLivingChatLlmRuntimeAdapter_1.runAssistantLivingChatLlmRuntime;
const executeLlmChat = buildExecuteLlmChat(input, runLivingChatLlmSafe);
return runLivingChatHandlerSafe({
return runLivingChatHandlerSafe((0, assistantLivingChatAttemptRuntimeInputBuilder_1.buildAssistantLivingChatHandlerRuntimeInput)({
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
@ -58,5 +59,5 @@ async function runAssistantLivingChatAttemptRuntime(input) {
nowIso: input.nowIso,
runLivingChatRuntime: input.runLivingChatRuntime,
finalizeLivingChatTurn: input.finalizeLivingChatTurn
});
}));
}

View File

@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantLivingChatLlmRuntimeInput = buildAssistantLivingChatLlmRuntimeInput;
exports.buildAssistantLivingChatHandlerRuntimeInput = buildAssistantLivingChatHandlerRuntimeInput;
function buildAssistantLivingChatLlmRuntimeInput(input) {
return {
userMessage: input.userMessage,
sessionItems: input.sessionItems,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
};
}
function buildAssistantLivingChatHandlerRuntimeInput(input) {
return {
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision: input.modeDecision,
sessionScope: input.sessionScope,
addressRuntimeMeta: input.addressRuntimeMeta,
traceIdFactory: input.traceIdFactory,
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,
executeLlmChat: input.executeLlmChat,
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,
runLivingChatRuntime: input.runLivingChatRuntime,
finalizeLivingChatTurn: input.finalizeLivingChatTurn
};
}

View File

@ -4,6 +4,7 @@ import {
type RunAssistantAddressLaneResponseRuntimeInput,
type RunAssistantAddressLaneResponseRuntimeOutput
} from "./assistantAddressLaneResponseRuntimeAdapter";
import { buildAssistantAddressLaneResponseRuntimeInput } from "./assistantAddressLaneResponseRuntimeInputBuilder";
export interface RunAssistantAddressLaneResponseAttemptRuntimeInput<
ResponseType = AssistantMessageResponsePayload
@ -20,26 +21,28 @@ export function runAssistantAddressLaneResponseAttemptRuntime<
): ResponseType {
const runAddressLaneResponseRuntimeSafe =
input.runAddressLaneResponseRuntime ?? runAssistantAddressLaneResponseRuntime;
const runtime = runAddressLaneResponseRuntimeSafe({
sessionId: input.sessionId,
userMessage: input.userMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
addressLane: input.addressLane,
carryoverMeta: input.carryoverMeta,
llmPreDecomposeMeta: input.llmPreDecomposeMeta,
knownOrganizations: input.knownOrganizations,
activeOrganization: input.activeOrganization,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
buildAddressDebugPayload: input.buildAddressDebugPayload,
buildAddressFollowupOffer: input.buildAddressFollowupOffer,
mergeKnownOrganizations: input.mergeKnownOrganizations,
toNonEmptyString: input.toNonEmptyString,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory
});
const runtime = runAddressLaneResponseRuntimeSafe(
buildAssistantAddressLaneResponseRuntimeInput({
sessionId: input.sessionId,
userMessage: input.userMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
addressLane: input.addressLane,
carryoverMeta: input.carryoverMeta,
llmPreDecomposeMeta: input.llmPreDecomposeMeta,
knownOrganizations: input.knownOrganizations,
activeOrganization: input.activeOrganization,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
buildAddressDebugPayload: input.buildAddressDebugPayload,
buildAddressFollowupOffer: input.buildAddressFollowupOffer,
mergeKnownOrganizations: input.mergeKnownOrganizations,
toNonEmptyString: input.toNonEmptyString,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory
})
);
return runtime.response;
}

View File

@ -0,0 +1,54 @@
import type { RunAssistantAddressLaneResponseRuntimeInput } from "./assistantAddressLaneResponseRuntimeAdapter";
export interface BuildAssistantAddressLaneResponseRuntimeInputInput<ResponseType = unknown> {
sessionId: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["sessionId"];
userMessage: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["userMessage"];
effectiveAddressUserMessage:
RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["effectiveAddressUserMessage"];
addressLane: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["addressLane"];
carryoverMeta?: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["carryoverMeta"];
llmPreDecomposeMeta?: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["llmPreDecomposeMeta"];
knownOrganizations: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["knownOrganizations"];
activeOrganization: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["activeOrganization"];
sanitizeOutgoingAssistantText:
RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["sanitizeOutgoingAssistantText"];
buildAddressDebugPayload:
RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["buildAddressDebugPayload"];
buildAddressFollowupOffer:
RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["buildAddressFollowupOffer"];
mergeKnownOrganizations:
RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["mergeKnownOrganizations"];
toNonEmptyString: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["toNonEmptyString"];
appendItem: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["appendItem"];
getSession: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["getSession"];
persistSession: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["persistSession"];
cloneConversation: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["cloneConversation"];
logEvent: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["logEvent"];
messageIdFactory: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>["messageIdFactory"];
}
export function buildAssistantAddressLaneResponseRuntimeInput<ResponseType = unknown>(
input: BuildAssistantAddressLaneResponseRuntimeInputInput<ResponseType>
): RunAssistantAddressLaneResponseRuntimeInput<ResponseType> {
return {
sessionId: input.sessionId,
userMessage: input.userMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
addressLane: input.addressLane,
carryoverMeta: input.carryoverMeta,
llmPreDecomposeMeta: input.llmPreDecomposeMeta,
knownOrganizations: input.knownOrganizations,
activeOrganization: input.activeOrganization,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
buildAddressDebugPayload: input.buildAddressDebugPayload,
buildAddressFollowupOffer: input.buildAddressFollowupOffer,
mergeKnownOrganizations: input.mergeKnownOrganizations,
toNonEmptyString: input.toNonEmptyString,
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory
};
}

View File

@ -6,6 +6,10 @@ import {
runAssistantLivingChatLlmRuntime,
type RunAssistantLivingChatLlmRuntimeInput
} from "./assistantLivingChatLlmRuntimeAdapter";
import {
buildAssistantLivingChatHandlerRuntimeInput,
buildAssistantLivingChatLlmRuntimeInput
} from "./assistantLivingChatAttemptRuntimeInputBuilder";
type AssistantLivingChatHandlerInput<ResponseType> = TryHandleAssistantLivingChatRuntimeInput<ResponseType>;
type AssistantLivingChatLlmInput = RunAssistantLivingChatLlmRuntimeInput;
@ -32,17 +36,19 @@ function buildExecuteLlmChat<ResponseType>(
runLivingChatLlmSafe: (input: AssistantLivingChatLlmInput) => Promise<string>
): AssistantLivingChatHandlerInput<ResponseType>["executeLlmChat"] {
return async () =>
runLivingChatLlmSafe({
userMessage: input.userMessage,
sessionItems: input.sessionItems,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
});
runLivingChatLlmSafe(
buildAssistantLivingChatLlmRuntimeInput({
userMessage: input.userMessage,
sessionItems: input.sessionItems,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
})
);
}
export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknown>(
@ -51,43 +57,45 @@ export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknow
const runLivingChatHandlerSafe = input.runLivingChatHandler ?? tryHandleAssistantLivingChatRuntime;
const runLivingChatLlmSafe = input.runLivingChatLlm ?? runAssistantLivingChatLlmRuntime;
const executeLlmChat = buildExecuteLlmChat(input, runLivingChatLlmSafe);
return runLivingChatHandlerSafe({
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision: input.modeDecision,
sessionScope: input.sessionScope,
addressRuntimeMeta: input.addressRuntimeMeta,
traceIdFactory: input.traceIdFactory,
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,
executeLlmChat,
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,
runLivingChatRuntime: input.runLivingChatRuntime,
finalizeLivingChatTurn: input.finalizeLivingChatTurn
});
return runLivingChatHandlerSafe(
buildAssistantLivingChatHandlerRuntimeInput({
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision: input.modeDecision,
sessionScope: input.sessionScope,
addressRuntimeMeta: input.addressRuntimeMeta,
traceIdFactory: input.traceIdFactory,
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,
executeLlmChat,
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,
runLivingChatRuntime: input.runLivingChatRuntime,
finalizeLivingChatTurn: input.finalizeLivingChatTurn
})
);
}

View File

@ -0,0 +1,81 @@
import type {
TryHandleAssistantLivingChatRuntimeInput
} from "./assistantLivingChatHandlerRuntimeAdapter";
import type { RunAssistantLivingChatLlmRuntimeInput } from "./assistantLivingChatLlmRuntimeAdapter";
export interface BuildAssistantLivingChatLlmRuntimeInputInput {
userMessage: RunAssistantLivingChatLlmRuntimeInput["userMessage"];
sessionItems: RunAssistantLivingChatLlmRuntimeInput["sessionItems"];
payload: RunAssistantLivingChatLlmRuntimeInput["payload"];
chatClient: RunAssistantLivingChatLlmRuntimeInput["chatClient"];
loadAssistantCanonExcerpt: RunAssistantLivingChatLlmRuntimeInput["loadAssistantCanonExcerpt"];
sanitizeOutgoingAssistantText: RunAssistantLivingChatLlmRuntimeInput["sanitizeOutgoingAssistantText"];
defaultModel: RunAssistantLivingChatLlmRuntimeInput["defaultModel"];
defaultBaseUrl: RunAssistantLivingChatLlmRuntimeInput["defaultBaseUrl"];
defaultApiKey?: RunAssistantLivingChatLlmRuntimeInput["defaultApiKey"];
}
export function buildAssistantLivingChatLlmRuntimeInput(
input: BuildAssistantLivingChatLlmRuntimeInputInput
): RunAssistantLivingChatLlmRuntimeInput {
return {
userMessage: input.userMessage,
sessionItems: input.sessionItems,
payload: input.payload,
chatClient: input.chatClient,
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
defaultModel: input.defaultModel,
defaultBaseUrl: input.defaultBaseUrl,
defaultApiKey: input.defaultApiKey
};
}
export interface BuildAssistantLivingChatHandlerRuntimeInputInput<ResponseType = unknown>
extends Omit<TryHandleAssistantLivingChatRuntimeInput<ResponseType>, "executeLlmChat"> {
executeLlmChat: TryHandleAssistantLivingChatRuntimeInput<ResponseType>["executeLlmChat"];
}
export function buildAssistantLivingChatHandlerRuntimeInput<ResponseType = unknown>(
input: BuildAssistantLivingChatHandlerRuntimeInputInput<ResponseType>
): TryHandleAssistantLivingChatRuntimeInput<ResponseType> {
return {
sessionId: input.sessionId,
userMessage: input.userMessage,
sessionItems: input.sessionItems,
modeDecision: input.modeDecision,
sessionScope: input.sessionScope,
addressRuntimeMeta: input.addressRuntimeMeta,
traceIdFactory: input.traceIdFactory,
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,
executeLlmChat: input.executeLlmChat,
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,
runLivingChatRuntime: input.runLivingChatRuntime,
finalizeLivingChatTurn: input.finalizeLivingChatTurn
};
}

View File

@ -0,0 +1,48 @@
import { describe, expect, it, vi } from "vitest";
import { buildAssistantAddressLaneResponseRuntimeInput } from "../src/services/assistantAddressLaneResponseRuntimeInputBuilder";
function buildInput(overrides: Record<string, unknown> = {}) {
return {
sessionId: "asst-1",
userMessage: "where is settlement tail",
effectiveAddressUserMessage: "where is settlement tail for Org A",
addressLane: {
reply_text: "address reply",
reply_type: "factual_with_explanation",
debug: {}
},
carryoverMeta: { previousReplyType: "partial_coverage" },
llmPreDecomposeMeta: { mode: "supported", confidence: "high" },
knownOrganizations: ["Org A"],
activeOrganization: "Org A",
sanitizeOutgoingAssistantText: vi.fn((value: unknown, fallback = "") => {
const text = String(value ?? "").trim();
return text || fallback;
}),
buildAddressDebugPayload: vi.fn(() => ({})),
buildAddressFollowupOffer: vi.fn(() => null),
mergeKnownOrganizations: vi.fn((values: string[]) => values),
toNonEmptyString: vi.fn((value: unknown) =>
typeof value === "string" && value.trim().length > 0 ? value.trim() : null
),
appendItem: vi.fn(),
getSession: vi.fn(),
persistSession: vi.fn(),
cloneConversation: vi.fn((items: unknown[]) => items),
logEvent: vi.fn(),
messageIdFactory: vi.fn(() => "msg-1"),
...overrides
} as any;
}
describe("assistant address lane response runtime input builder", () => {
it("maps lane-response fields into runtime input", () => {
const runtimeInput = buildAssistantAddressLaneResponseRuntimeInput(buildInput());
expect(runtimeInput.sessionId).toBe("asst-1");
expect(runtimeInput.effectiveAddressUserMessage).toBe("where is settlement tail for Org A");
expect(runtimeInput.knownOrganizations).toEqual(["Org A"]);
expect(runtimeInput.carryoverMeta).toEqual({ previousReplyType: "partial_coverage" });
expect(runtimeInput.llmPreDecomposeMeta).toEqual({ mode: "supported", confidence: "high" });
});
});

View File

@ -0,0 +1,83 @@
import { describe, expect, it, vi } from "vitest";
import {
buildAssistantLivingChatHandlerRuntimeInput,
buildAssistantLivingChatLlmRuntimeInput
} from "../src/services/assistantLivingChatAttemptRuntimeInputBuilder";
describe("assistant living chat attempt runtime input builder", () => {
it("builds living-chat llm runtime input", () => {
const runtimeInput = buildAssistantLivingChatLlmRuntimeInput({
userMessage: "hello",
sessionItems: [{ role: "user", text: "ctx" }],
payload: { llmProvider: "openai" } as any,
chatClient: {} as any,
loadAssistantCanonExcerpt: vi.fn(() => "canon"),
sanitizeOutgoingAssistantText: vi.fn((value: unknown) => String(value ?? "")),
defaultModel: "gpt-5",
defaultBaseUrl: "http://localhost",
defaultApiKey: "key"
});
expect(runtimeInput.userMessage).toBe("hello");
expect(runtimeInput.sessionItems).toEqual([{ role: "user", text: "ctx" }]);
expect(runtimeInput.defaultModel).toBe("gpt-5");
expect(runtimeInput.defaultBaseUrl).toBe("http://localhost");
expect(runtimeInput.defaultApiKey).toBe("key");
});
it("builds living-chat handler runtime input with execute callback", async () => {
const executeLlmChat = vi.fn(async () => "llm-answer");
const runtimeInput = buildAssistantLivingChatHandlerRuntimeInput({
sessionId: "asst-1",
userMessage: "hello",
sessionItems: [],
modeDecision: { mode: "chat", reason: "living_chat_signal_detected" },
sessionScope: { knownOrganizations: [], selectedOrganization: null, activeOrganization: null },
addressRuntimeMeta: null,
traceIdFactory: vi.fn(() => "chat-1"),
toNonEmptyString: vi.fn((value: unknown) =>
typeof value === "string" && value.trim().length > 0 ? value.trim() : null
),
mergeKnownOrganizations: vi.fn((values: string[]) => values),
hasAssistantDataScopeMetaQuestionSignal: vi.fn(() => false),
shouldHandleAsAssistantCapabilityMetaQuery: vi.fn(() => false),
hasDestructiveDataActionSignal: vi.fn(() => false),
hasDangerOrCoercionSignal: vi.fn(() => false),
hasOperationalAdminActionRequestSignal: vi.fn(() => false),
hasOrganizationFactLookupSignal: vi.fn(() => false),
hasOrganizationFactFollowupSignal: vi.fn(() => false),
shouldEmitOrganizationSelectionReply: vi.fn(() => false),
hasAssistantCapabilityQuestionSignal: vi.fn(() => false),
resolveDataScopeProbe: vi.fn(async () => null),
executeLlmChat,
applyScriptGuard: vi.fn((text: string) => ({ text, applied: false, reason: null })),
applyGroundingGuard: vi.fn((payload: { chatText: string }) => ({
text: payload.chatText,
applied: false,
reason: null
})),
buildAssistantSafetyRefusalReply: vi.fn(() => "safety"),
buildAssistantDataScopeContractReply: vi.fn(() => "scope"),
buildAssistantOrganizationFactBoundaryReply: vi.fn(() => "boundary"),
buildAssistantDataScopeSelectionReply: vi.fn(() => "selection"),
buildAssistantOperationalBoundaryReply: vi.fn(() => "operational"),
buildAssistantCapabilityContractReply: vi.fn(() => "capability"),
appendItem: vi.fn(),
getSession: vi.fn(),
persistSession: vi.fn(),
cloneConversation: vi.fn((items: unknown[]) => items),
logEvent: vi.fn(),
messageIdFactory: vi.fn(() => "msg-1"),
nowIso: vi.fn(() => "2026-04-11T00:00:00.000Z"),
runLivingChatRuntime: vi.fn(async () => null),
finalizeLivingChatTurn: vi.fn()
});
await runtimeInput.executeLlmChat();
expect(executeLlmChat).toHaveBeenCalledTimes(1);
expect(runtimeInput.modeDecision).toEqual({
mode: "chat",
reason: "living_chat_signal_detected"
});
});
});