diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index 78a062a..f81adb1 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -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) diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js index 0aa803c..0d21736 100644 --- a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js @@ -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; } diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeInputBuilder.js b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeInputBuilder.js new file mode 100644 index 0000000..a0e9b13 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeInputBuilder.js @@ -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 + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js index 43334d7..6676aff 100644 --- a/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js @@ -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 - }); + })); } diff --git a/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeInputBuilder.js b/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeInputBuilder.js new file mode 100644 index 0000000..15d1353 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeInputBuilder.js @@ -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 + }; +} diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts index f701ca9..fa570c4 100644 --- a/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts @@ -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; } diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeInputBuilder.ts b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeInputBuilder.ts new file mode 100644 index 0000000..e5e4688 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeInputBuilder.ts @@ -0,0 +1,54 @@ +import type { RunAssistantAddressLaneResponseRuntimeInput } from "./assistantAddressLaneResponseRuntimeAdapter"; + +export interface BuildAssistantAddressLaneResponseRuntimeInputInput { + sessionId: RunAssistantAddressLaneResponseRuntimeInput["sessionId"]; + userMessage: RunAssistantAddressLaneResponseRuntimeInput["userMessage"]; + effectiveAddressUserMessage: + RunAssistantAddressLaneResponseRuntimeInput["effectiveAddressUserMessage"]; + addressLane: RunAssistantAddressLaneResponseRuntimeInput["addressLane"]; + carryoverMeta?: RunAssistantAddressLaneResponseRuntimeInput["carryoverMeta"]; + llmPreDecomposeMeta?: RunAssistantAddressLaneResponseRuntimeInput["llmPreDecomposeMeta"]; + knownOrganizations: RunAssistantAddressLaneResponseRuntimeInput["knownOrganizations"]; + activeOrganization: RunAssistantAddressLaneResponseRuntimeInput["activeOrganization"]; + sanitizeOutgoingAssistantText: + RunAssistantAddressLaneResponseRuntimeInput["sanitizeOutgoingAssistantText"]; + buildAddressDebugPayload: + RunAssistantAddressLaneResponseRuntimeInput["buildAddressDebugPayload"]; + buildAddressFollowupOffer: + RunAssistantAddressLaneResponseRuntimeInput["buildAddressFollowupOffer"]; + mergeKnownOrganizations: + RunAssistantAddressLaneResponseRuntimeInput["mergeKnownOrganizations"]; + toNonEmptyString: RunAssistantAddressLaneResponseRuntimeInput["toNonEmptyString"]; + appendItem: RunAssistantAddressLaneResponseRuntimeInput["appendItem"]; + getSession: RunAssistantAddressLaneResponseRuntimeInput["getSession"]; + persistSession: RunAssistantAddressLaneResponseRuntimeInput["persistSession"]; + cloneConversation: RunAssistantAddressLaneResponseRuntimeInput["cloneConversation"]; + logEvent: RunAssistantAddressLaneResponseRuntimeInput["logEvent"]; + messageIdFactory: RunAssistantAddressLaneResponseRuntimeInput["messageIdFactory"]; +} + +export function buildAssistantAddressLaneResponseRuntimeInput( + input: BuildAssistantAddressLaneResponseRuntimeInputInput +): RunAssistantAddressLaneResponseRuntimeInput { + 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 + }; +} diff --git a/llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts index 7b524e9..4d4b8d0 100644 --- a/llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts @@ -6,6 +6,10 @@ import { runAssistantLivingChatLlmRuntime, type RunAssistantLivingChatLlmRuntimeInput } from "./assistantLivingChatLlmRuntimeAdapter"; +import { + buildAssistantLivingChatHandlerRuntimeInput, + buildAssistantLivingChatLlmRuntimeInput +} from "./assistantLivingChatAttemptRuntimeInputBuilder"; type AssistantLivingChatHandlerInput = TryHandleAssistantLivingChatRuntimeInput; type AssistantLivingChatLlmInput = RunAssistantLivingChatLlmRuntimeInput; @@ -32,17 +36,19 @@ function buildExecuteLlmChat( runLivingChatLlmSafe: (input: AssistantLivingChatLlmInput) => Promise ): AssistantLivingChatHandlerInput["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( @@ -51,43 +57,45 @@ export async function runAssistantLivingChatAttemptRuntime + extends Omit, "executeLlmChat"> { + executeLlmChat: TryHandleAssistantLivingChatRuntimeInput["executeLlmChat"]; +} + +export function buildAssistantLivingChatHandlerRuntimeInput( + input: BuildAssistantLivingChatHandlerRuntimeInputInput +): TryHandleAssistantLivingChatRuntimeInput { + 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 + }; +} diff --git a/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeInputBuilder.test.ts b/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeInputBuilder.test.ts new file mode 100644 index 0000000..e19ead3 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeInputBuilder.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it, vi } from "vitest"; +import { buildAssistantAddressLaneResponseRuntimeInput } from "../src/services/assistantAddressLaneResponseRuntimeInputBuilder"; + +function buildInput(overrides: Record = {}) { + 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" }); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantLivingChatAttemptRuntimeInputBuilder.test.ts b/llm_normalizer/backend/tests/assistantLivingChatAttemptRuntimeInputBuilder.test.ts new file mode 100644 index 0000000..6e81850 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantLivingChatAttemptRuntimeInputBuilder.test.ts @@ -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" + }); + }); +});