ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.49: вынос turnRuntimeDeps (фабрику зависимостей) из assistantService в отдельный deps-adapter, чтобы handleMessage стал совсем тонким.

This commit is contained in:
dctouch 2026-04-11 00:47:38 +03:00
parent f1e621fccc
commit ca467cdecc
6 changed files with 465 additions and 147 deletions

View File

@ -1537,7 +1537,42 @@ Validation:
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
- `assistantWave10SettlementCorrectiveRegression.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 completed)**
Implemented in current pass (Phase 2.49):
1. Extracted turn runtime dependency factory from `assistantService` into dedicated adapter:
- `assistantTurnRuntimeDepsAdapter.ts`
- introduced:
- `buildAssistantTurnRuntimeDeps(...)`
2. Rewired `assistantService.handleMessage` to construct runtime deps via adapter (behavior-preserving):
- service-level wrappers for `sessions`, `sessionLogger`, `normalizerService`, `dataLayer`, `addressQueryService` moved under deps-adapter boundary;
- flags/defaults/helpers remain unchanged semantically and are passed as structured groups (`flags`, `defaults`, `helpers`).
3. Added focused unit tests:
- `assistantTurnRuntimeDepsAdapter.test.ts`
Validation:
1. `npm run build` passed.
2. Targeted living/address/deep followup pack passed:
- `assistantTurnRuntimeDepsAdapter.test.ts`
- `assistantTurnRuntimeInputBuilder.test.ts`
- `assistantTurnAttemptRuntimeAdapter.test.ts`
- `assistantAddressAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnAnalysisAttemptRuntimeAdapter.test.ts`
- `assistantDeepTurnAnalysisRuntimeAdapter.test.ts`
- `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts`
- `assistantLivingChatAttemptRuntimeAdapter.test.ts`
- `assistantAddressLaneAttemptRuntimeAdapter.test.ts`
- `assistantUserTurnBootstrapRuntimeAdapter.test.ts`
- `assistantLivingChatLlmRuntimeAdapter.test.ts`
- `assistantLivingChatHandlerRuntimeAdapter.test.ts`
- `assistantLivingChatRuntimeAdapter.test.ts`
- `assistantAddressRuntimeAdapter.test.ts`
- `assistantAddressLaneResponseRuntimeAdapter.test.ts`
- `assistantDeepTurnResponseRuntimeAdapter.test.ts`
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
- `assistantWave10SettlementCorrectiveRegression.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 completed)**
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)

View File

@ -66,6 +66,7 @@ const assistantAddressAttemptRuntimeAdapter_1 = __importStar(require("./assistan
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter"));
const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter"));
const assistantTurnRuntimeDepsAdapter_1 = __importStar(require("./assistantTurnRuntimeDepsAdapter"));
const assistantTurnRuntimeInputBuilder_1 = __importStar(require("./assistantTurnRuntimeInputBuilder"));
const assistantUserTurnBootstrapRuntimeAdapter_1 = __importStar(require("./assistantUserTurnBootstrapRuntimeAdapter"));
const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning"));
@ -4371,84 +4372,87 @@ class AssistantService {
return this.sessions.getSession(sessionId);
}
async handleMessage(payload) {
const turnRuntimeDeps = {
ensureSession: (targetSessionId) => this.sessions.ensureSession(targetSessionId),
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
setInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot),
normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload),
executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options),
tryAddressQueryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options),
const turnRuntimeDeps = (0, assistantTurnRuntimeDepsAdapter_1.buildAssistantTurnRuntimeDeps)({
sessions: this.sessions,
sessionLogger: this.sessionLogger,
normalizerService: this.normalizerService,
dataLayer: this.dataLayer,
addressQueryService: this.addressQueryService,
chatClient: this.chatClient,
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString(),
defaultApiKey: process.env.OPENAI_API_KEY ?? "",
logEvent: (runtimePayload) => (0, log_1.logJson)(runtimePayload),
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1,
featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11,
featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
defaultModel: config_1.DEFAULT_MODEL,
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
compactWhitespace,
repairAddressMojibake,
resolveRuntimeAnalysisContext,
runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage),
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
sanitizeAddressMessageForFallback,
toNonEmptyString,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision,
buildAddressDialogContinuationContractV2,
mergeFollowupContextWithOrganizationScope,
isRetryableAddressLimitedResult,
mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply,
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText,
buildAddressDebugPayload,
buildAddressFollowupOffer,
buildFollowupStateBinding,
resolveBusinessScopeAlignment,
inferP0DomainFromMessage,
resolveBusinessScopeFromLiveContext,
extractRequirements,
toExecutionPlan,
enforceRbpLiveRoutePlan,
enforceFaLiveRoutePlan,
mapNoRouteReason,
buildSkippedResult,
evaluateCoverage,
checkGrounding,
collectRbpLiveRouteAudit,
collectFaLiveRouteAudit,
hasExplicitPeriodAnchorFromNormalized,
extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload),
toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary),
extractExecutionState
};
flags: {
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1,
featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11,
featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1
},
defaults: {
defaultModel: config_1.DEFAULT_MODEL,
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL
},
helpers: {
compactWhitespace,
repairAddressMojibake,
resolveRuntimeAnalysisContext,
runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage),
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
sanitizeAddressMessageForFallback,
toNonEmptyString,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision,
buildAddressDialogContinuationContractV2,
mergeFollowupContextWithOrganizationScope,
isRetryableAddressLimitedResult,
mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply,
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText,
buildAddressDebugPayload,
buildAddressFollowupOffer,
buildFollowupStateBinding,
resolveBusinessScopeAlignment,
inferP0DomainFromMessage,
resolveBusinessScopeFromLiveContext,
extractRequirements,
toExecutionPlan,
enforceRbpLiveRoutePlan,
enforceFaLiveRoutePlan,
mapNoRouteReason,
buildSkippedResult,
evaluateCoverage,
checkGrounding,
collectRbpLiveRouteAudit,
collectFaLiveRouteAudit,
hasExplicitPeriodAnchorFromNormalized,
extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload),
toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary),
extractExecutionState
}
});
const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({
payload,
runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantUserTurnBootstrapRuntimeInput)(runtimePayload, turnRuntimeDeps)),

View File

@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantTurnRuntimeDeps = buildAssistantTurnRuntimeDeps;
function buildAssistantTurnRuntimeDeps(input) {
return {
...input.helpers,
ensureSession: (sessionId) => input.sessions.ensureSession(sessionId),
appendItem: (sessionId, item) => input.sessions.appendItem(sessionId, item),
getSession: (sessionId) => input.sessions.getSession(sessionId),
persistSession: (sessionState) => input.sessionLogger.persistSession(sessionState),
setInvestigationState: (sessionId, snapshot) => input.sessions.setInvestigationState(sessionId, snapshot),
normalize: (payload) => input.normalizerService.normalize(payload),
executeRouteRuntime: (route, fragmentText, options) => input.dataLayer.executeRouteRuntime(route, fragmentText, options),
tryAddressQueryHandle: (messageUsed, options) => input.addressQueryService.tryHandle(messageUsed, options),
chatClient: input.chatClient,
messageIdFactory: input.messageIdFactory,
nowIso: input.nowIso,
defaultApiKey: input.defaultApiKey,
logEvent: input.logEvent,
featureAssistantAddressQueryV1: input.flags.featureAssistantAddressQueryV1,
featureAddressLlmPredecomposeV1: input.flags.featureAddressLlmPredecomposeV1,
featureInvestigationStateV1: input.flags.featureInvestigationStateV1,
featureStateFollowupBindingV1: input.flags.featureStateFollowupBindingV1,
featureContractsV11: input.flags.featureContractsV11,
featureAnswerPolicyV11: input.flags.featureAnswerPolicyV11,
featureProblemCentricAnswerV1: input.flags.featureProblemCentricAnswerV1,
featureLifecycleAnswerV1: input.flags.featureLifecycleAnswerV1,
defaultModel: input.defaults.defaultModel,
defaultBaseUrl: input.defaults.defaultBaseUrl
};
}

View File

@ -20,6 +20,7 @@ import * as assistantAddressAttemptRuntimeAdapter_1 from "./assistantAddressAtte
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter";
import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter";
import * as assistantTurnRuntimeDepsAdapter_1 from "./assistantTurnRuntimeDepsAdapter";
import * as assistantTurnRuntimeInputBuilder_1 from "./assistantTurnRuntimeInputBuilder";
import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter";
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
@ -4326,84 +4327,87 @@ export class AssistantService {
return this.sessions.getSession(sessionId);
}
async handleMessage(payload) {
const turnRuntimeDeps = {
ensureSession: (targetSessionId) => this.sessions.ensureSession(targetSessionId),
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
setInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot),
normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload),
executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options),
tryAddressQueryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options),
const turnRuntimeDeps = (0, assistantTurnRuntimeDepsAdapter_1.buildAssistantTurnRuntimeDeps)({
sessions: this.sessions,
sessionLogger: this.sessionLogger,
normalizerService: this.normalizerService,
dataLayer: this.dataLayer,
addressQueryService: this.addressQueryService,
chatClient: this.chatClient,
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString(),
defaultApiKey: process.env.OPENAI_API_KEY ?? "",
logEvent: (runtimePayload) => (0, log_1.logJson)(runtimePayload),
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1,
featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11,
featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
defaultModel: config_1.DEFAULT_MODEL,
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
compactWhitespace,
repairAddressMojibake,
resolveRuntimeAnalysisContext,
runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage),
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
sanitizeAddressMessageForFallback,
toNonEmptyString,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision,
buildAddressDialogContinuationContractV2,
mergeFollowupContextWithOrganizationScope,
isRetryableAddressLimitedResult,
mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply,
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText,
buildAddressDebugPayload,
buildAddressFollowupOffer,
buildFollowupStateBinding,
resolveBusinessScopeAlignment,
inferP0DomainFromMessage,
resolveBusinessScopeFromLiveContext,
extractRequirements,
toExecutionPlan,
enforceRbpLiveRoutePlan,
enforceFaLiveRoutePlan,
mapNoRouteReason,
buildSkippedResult,
evaluateCoverage,
checkGrounding,
collectRbpLiveRouteAudit,
collectFaLiveRouteAudit,
hasExplicitPeriodAnchorFromNormalized,
extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload),
toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary),
extractExecutionState
};
flags: {
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1,
featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11,
featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1
},
defaults: {
defaultModel: config_1.DEFAULT_MODEL,
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL
},
helpers: {
compactWhitespace,
repairAddressMojibake,
resolveRuntimeAnalysisContext,
runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage),
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
sanitizeAddressMessageForFallback,
toNonEmptyString,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision,
buildAddressDialogContinuationContractV2,
mergeFollowupContextWithOrganizationScope,
isRetryableAddressLimitedResult,
mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply,
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText,
buildAddressDebugPayload,
buildAddressFollowupOffer,
buildFollowupStateBinding,
resolveBusinessScopeAlignment,
inferP0DomainFromMessage,
resolveBusinessScopeFromLiveContext,
extractRequirements,
toExecutionPlan,
enforceRbpLiveRoutePlan,
enforceFaLiveRoutePlan,
mapNoRouteReason,
buildSkippedResult,
evaluateCoverage,
checkGrounding,
collectRbpLiveRouteAudit,
collectFaLiveRouteAudit,
hasExplicitPeriodAnchorFromNormalized,
extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload),
toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary),
extractExecutionState
}
});
const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({
payload,
runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantUserTurnBootstrapRuntimeInput)(runtimePayload, turnRuntimeDeps)),

View File

@ -0,0 +1,112 @@
import type { AssistantTurnRuntimeBuilderDeps } from "./assistantTurnRuntimeInputBuilder";
export interface AssistantTurnRuntimeDepsSessionsLike {
ensureSession: (sessionId: string) => unknown;
appendItem: (sessionId: string, item: unknown) => void;
getSession: (sessionId: string) => unknown;
setInvestigationState: (sessionId: string, snapshot: unknown) => void;
}
export interface AssistantTurnRuntimeDepsSessionLoggerLike {
persistSession: (sessionState: unknown) => void;
}
export interface AssistantTurnRuntimeDepsNormalizerLike {
normalize: (payload: unknown) => Promise<unknown>;
}
export interface AssistantTurnRuntimeDepsDataLayerLike {
executeRouteRuntime: (route: string, fragmentText: string, options?: unknown) => Promise<unknown>;
}
export interface AssistantTurnRuntimeDepsAddressQueryServiceLike {
tryHandle: (messageUsed: string, options?: unknown) => Promise<unknown>;
}
type BuilderDepsProvidedByAdapter = Pick<
AssistantTurnRuntimeBuilderDeps,
| "ensureSession"
| "appendItem"
| "getSession"
| "persistSession"
| "setInvestigationState"
| "normalize"
| "executeRouteRuntime"
| "tryAddressQueryHandle"
| "chatClient"
| "messageIdFactory"
| "nowIso"
| "defaultApiKey"
| "logEvent"
| "featureAssistantAddressQueryV1"
| "featureAddressLlmPredecomposeV1"
| "featureInvestigationStateV1"
| "featureStateFollowupBindingV1"
| "featureContractsV11"
| "featureAnswerPolicyV11"
| "featureProblemCentricAnswerV1"
| "featureLifecycleAnswerV1"
| "defaultModel"
| "defaultBaseUrl"
>;
type BuilderDepsPassThrough = Omit<AssistantTurnRuntimeBuilderDeps, keyof BuilderDepsProvidedByAdapter>;
export interface BuildAssistantTurnRuntimeDepsInput {
sessions: AssistantTurnRuntimeDepsSessionsLike;
sessionLogger: AssistantTurnRuntimeDepsSessionLoggerLike;
normalizerService: AssistantTurnRuntimeDepsNormalizerLike;
dataLayer: AssistantTurnRuntimeDepsDataLayerLike;
addressQueryService: AssistantTurnRuntimeDepsAddressQueryServiceLike;
chatClient: unknown;
messageIdFactory: () => string;
nowIso: () => string;
defaultApiKey: string;
logEvent: (payload: Record<string, unknown>) => void;
flags: {
featureAssistantAddressQueryV1: boolean;
featureAddressLlmPredecomposeV1: boolean;
featureInvestigationStateV1: boolean;
featureStateFollowupBindingV1: boolean;
featureContractsV11: boolean;
featureAnswerPolicyV11: boolean;
featureProblemCentricAnswerV1: boolean;
featureLifecycleAnswerV1: boolean;
};
defaults: {
defaultModel: string;
defaultBaseUrl: string;
};
helpers: BuilderDepsPassThrough;
}
export function buildAssistantTurnRuntimeDeps(
input: BuildAssistantTurnRuntimeDepsInput
): AssistantTurnRuntimeBuilderDeps {
return {
...input.helpers,
ensureSession: (sessionId) => input.sessions.ensureSession(sessionId) as any,
appendItem: (sessionId, item) => input.sessions.appendItem(sessionId, item as any),
getSession: (sessionId) => input.sessions.getSession(sessionId) as any,
persistSession: (sessionState) => input.sessionLogger.persistSession(sessionState as any),
setInvestigationState: (sessionId, snapshot) => input.sessions.setInvestigationState(sessionId, snapshot),
normalize: (payload) => input.normalizerService.normalize(payload),
executeRouteRuntime: (route, fragmentText, options) => input.dataLayer.executeRouteRuntime(route, fragmentText, options),
tryAddressQueryHandle: (messageUsed, options) => input.addressQueryService.tryHandle(messageUsed, options),
chatClient: input.chatClient,
messageIdFactory: input.messageIdFactory,
nowIso: input.nowIso,
defaultApiKey: input.defaultApiKey,
logEvent: input.logEvent,
featureAssistantAddressQueryV1: input.flags.featureAssistantAddressQueryV1,
featureAddressLlmPredecomposeV1: input.flags.featureAddressLlmPredecomposeV1,
featureInvestigationStateV1: input.flags.featureInvestigationStateV1,
featureStateFollowupBindingV1: input.flags.featureStateFollowupBindingV1,
featureContractsV11: input.flags.featureContractsV11,
featureAnswerPolicyV11: input.flags.featureAnswerPolicyV11,
featureProblemCentricAnswerV1: input.flags.featureProblemCentricAnswerV1,
featureLifecycleAnswerV1: input.flags.featureLifecycleAnswerV1,
defaultModel: input.defaults.defaultModel,
defaultBaseUrl: input.defaults.defaultBaseUrl
};
}

View File

@ -0,0 +1,132 @@
import { describe, expect, it, vi } from "vitest";
import { buildAssistantTurnRuntimeDeps } from "../src/services/assistantTurnRuntimeDepsAdapter";
describe("assistant turn runtime deps adapter", () => {
it("builds runtime deps with service wrappers and static flags/defaults", async () => {
const sessions = {
ensureSession: vi.fn(() => ({ session_id: "asst-1" })),
appendItem: vi.fn(),
getSession: vi.fn(() => ({ session_id: "asst-1" })),
setInvestigationState: vi.fn()
};
const sessionLogger = {
persistSession: vi.fn()
};
const normalizerService = {
normalize: vi.fn(async () => ({ trace_id: "trace-1" }))
};
const dataLayer = {
executeRouteRuntime: vi.fn(async () => ({ status: "ok" }))
};
const addressQueryService = {
tryHandle: vi.fn(async () => ({ response_type: "READY" }))
};
const logEvent = vi.fn();
const helperFn = vi.fn(() => "ok");
const deps = buildAssistantTurnRuntimeDeps({
sessions,
sessionLogger,
normalizerService,
dataLayer,
addressQueryService,
chatClient: { kind: "chat" },
messageIdFactory: () => "msg-1",
nowIso: () => "2026-04-11T00:00:00.000Z",
defaultApiKey: "api-key",
logEvent,
flags: {
featureAssistantAddressQueryV1: true,
featureAddressLlmPredecomposeV1: true,
featureInvestigationStateV1: true,
featureStateFollowupBindingV1: false,
featureContractsV11: true,
featureAnswerPolicyV11: true,
featureProblemCentricAnswerV1: true,
featureLifecycleAnswerV1: false
},
defaults: {
defaultModel: "gpt-5",
defaultBaseUrl: "http://localhost"
},
helpers: {
compactWhitespace: helperFn
} as any
});
deps.ensureSession("asst-1");
deps.appendItem("asst-1", { role: "assistant" } as any);
deps.getSession("asst-1");
deps.persistSession({ session_id: "asst-1" } as any);
deps.setInvestigationState("asst-1", { scope: "x" });
await deps.normalize({ user_message: "q" });
await deps.executeRouteRuntime("store_canonical", "fragment");
await deps.tryAddressQueryHandle("message", { analysisDateHint: "2020-07-31" });
deps.logEvent({ event: "ok" });
expect(sessions.ensureSession).toHaveBeenCalledWith("asst-1");
expect(sessions.appendItem).toHaveBeenCalledWith("asst-1", { role: "assistant" });
expect(sessions.getSession).toHaveBeenCalledWith("asst-1");
expect(sessionLogger.persistSession).toHaveBeenCalledWith({ session_id: "asst-1" });
expect(sessions.setInvestigationState).toHaveBeenCalledWith("asst-1", { scope: "x" });
expect(normalizerService.normalize).toHaveBeenCalledWith({ user_message: "q" });
expect(dataLayer.executeRouteRuntime).toHaveBeenCalledWith("store_canonical", "fragment", undefined);
expect(addressQueryService.tryHandle).toHaveBeenCalledWith("message", { analysisDateHint: "2020-07-31" });
expect(logEvent).toHaveBeenCalledWith({ event: "ok" });
expect(deps.featureContractsV11).toBe(true);
expect(deps.featureLifecycleAnswerV1).toBe(false);
expect(deps.defaultModel).toBe("gpt-5");
expect(deps.defaultBaseUrl).toBe("http://localhost");
expect(deps.defaultApiKey).toBe("api-key");
});
it("preserves helper functions in merged deps payload", () => {
const helperCompactWhitespace = vi.fn((value: unknown) => String(value ?? "").trim());
const deps = buildAssistantTurnRuntimeDeps({
sessions: {
ensureSession: () => null,
appendItem: () => {},
getSession: () => null,
setInvestigationState: () => {}
},
sessionLogger: {
persistSession: () => {}
},
normalizerService: {
normalize: async () => ({})
},
dataLayer: {
executeRouteRuntime: async () => ({})
},
addressQueryService: {
tryHandle: async () => null
},
chatClient: {},
messageIdFactory: () => "msg-2",
nowIso: () => "2026-04-11T00:00:00.000Z",
defaultApiKey: "",
logEvent: () => {},
flags: {
featureAssistantAddressQueryV1: false,
featureAddressLlmPredecomposeV1: false,
featureInvestigationStateV1: false,
featureStateFollowupBindingV1: false,
featureContractsV11: false,
featureAnswerPolicyV11: false,
featureProblemCentricAnswerV1: false,
featureLifecycleAnswerV1: false
},
defaults: {
defaultModel: "model",
defaultBaseUrl: "base"
},
helpers: {
compactWhitespace: helperCompactWhitespace
} as any
});
expect(deps.compactWhitespace(" value ")).toBe("value");
expect(helperCompactWhitespace).toHaveBeenCalledWith(" value ");
});
});