import { nanoid } from "nanoid"; import type { AssistantConversationItem, AssistantDebugPayload, AssistantMessageResponsePayload, AssistantReplyType } from "../types/assistant"; import type { AddressExecutionDebug } from "../types/addressQuery"; import type { CommitAssistantTurnAndLogOutput } from "./assistantTurnCommitRuntimeAdapter"; import { commitAssistantTurnAndLog } from "./assistantTurnCommitRuntimeAdapter"; export interface AddressCarryoverMetaLogInput { previousAddressIntent?: string | null; previousAddressAnchor?: string | null; } export interface AddressLlmPreDecomposeMetaLogInput { attempted?: boolean; applied?: boolean; provider?: string | null; traceId?: string | null; reason?: string | null; fallbackRuleHit?: string | null; sanitizedUserMessage?: string | null; toolGateDecision?: string | null; toolGateReason?: string | null; dialogContinuationContract?: { decision?: string | null; target_intent?: string | null; } | null; addressRetryAudit?: { attempted?: boolean; reason?: string | null; initial_limited_category?: string | null; retry_result_category?: string | null; } | null; predecomposeContract?: { intent?: string | null; aggregation_profile?: string | null; period?: { scope?: string | null; } | null; } | null; semanticExtractionContract?: { valid?: boolean | null; quality?: string | null; apply_canonical_recommended?: boolean | null; reason_codes?: string[] | null; } | null; } export interface FinalizeAssistantAddressTurnInput { sessionId: string; userMessage: string; effectiveAddressUserMessage: string; assistantReply: string; replyType: AssistantReplyType; addressLaneDebug: AddressExecutionDebug; debug: AssistantDebugPayload | Record; carryoverMeta?: AddressCarryoverMetaLogInput | null; llmPreDecomposeMeta?: AddressLlmPreDecomposeMetaLogInput | null; appendItem: Parameters[0]["appendItem"]; getSession: Parameters[0]["getSession"]; persistSession: Parameters[0]["persistSession"]; cloneConversation: Parameters[0]["cloneConversation"]; logEvent: Parameters[0]["logEvent"]; nowIso?: () => string; messageIdFactory?: () => string; commitFn?: typeof commitAssistantTurnAndLog; } export interface FinalizeAssistantAddressTurnOutput { assistantItem: AssistantConversationItem; commitResult: CommitAssistantTurnAndLogOutput; response: AssistantMessageResponsePayload; } function toTraceId(debug: AssistantDebugPayload | Record): string | null { const value = (debug as Record | null | undefined)?.trace_id; if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function buildAddressProcessedLogDetails(input: FinalizeAssistantAddressTurnInput, assistantItem: AssistantConversationItem) { const laneDebug = input.addressLaneDebug; const llmMeta = input.llmPreDecomposeMeta; const carryover = input.carryoverMeta; return { session_id: input.sessionId, message_id: assistantItem.message_id, user_message: input.userMessage, effective_address_user_message: input.effectiveAddressUserMessage, address_followup_context_applied: Boolean(carryover), address_followup_context_previous_intent: carryover?.previousAddressIntent ?? null, address_followup_context_previous_anchor: carryover?.previousAddressAnchor ?? null, address_llm_predecompose_attempted: Boolean(llmMeta?.attempted), address_llm_predecompose_applied: Boolean(llmMeta?.applied), address_llm_predecompose_provider: llmMeta?.provider ?? null, address_llm_predecompose_trace_id: llmMeta?.traceId ?? null, address_llm_predecompose_reason: llmMeta?.reason ?? null, address_fallback_rule_hit: llmMeta?.fallbackRuleHit ?? null, address_sanitized_user_message: llmMeta?.sanitizedUserMessage ?? null, address_tool_gate_decision: llmMeta?.toolGateDecision ?? null, address_tool_gate_reason: llmMeta?.toolGateReason ?? null, address_dialog_continuation_decision: llmMeta?.dialogContinuationContract?.decision ?? null, address_dialog_continuation_target_intent: llmMeta?.dialogContinuationContract?.target_intent ?? null, address_retry_attempted: Boolean(llmMeta?.addressRetryAudit?.attempted), address_retry_reason: llmMeta?.addressRetryAudit?.reason ?? null, address_retry_initial_limited_category: llmMeta?.addressRetryAudit?.initial_limited_category ?? null, address_retry_result_category: llmMeta?.addressRetryAudit?.retry_result_category ?? null, address_llm_predecompose_contract_intent: llmMeta?.predecomposeContract?.intent ?? null, address_llm_predecompose_contract_aggregation_profile: llmMeta?.predecomposeContract?.aggregation_profile ?? null, address_llm_predecompose_contract_period_scope: llmMeta?.predecomposeContract?.period?.scope ?? null, address_semantic_contract_valid: llmMeta?.semanticExtractionContract?.valid ?? null, address_semantic_contract_quality: llmMeta?.semanticExtractionContract?.quality ?? null, address_semantic_apply_canonical_recommended: llmMeta?.semanticExtractionContract?.apply_canonical_recommended ?? null, address_semantic_reason_codes: Array.isArray(llmMeta?.semanticExtractionContract?.reason_codes) ? llmMeta.semanticExtractionContract?.reason_codes : [], detected_mode: laneDebug.detected_mode, query_shape: laneDebug.query_shape, detected_intent: laneDebug.detected_intent, extracted_filters: laneDebug.extracted_filters, selected_recipe: laneDebug.selected_recipe, mcp_call_status_legacy: laneDebug.mcp_call_status_legacy, account_scope_mode: laneDebug.account_scope_mode, account_scope_fallback_applied: laneDebug.account_scope_fallback_applied, anchor_type: laneDebug.anchor_type, resolver_confidence: laneDebug.resolver_confidence, match_failure_stage: laneDebug.match_failure_stage, match_failure_reason: laneDebug.match_failure_reason, mcp_call_status: laneDebug.mcp_call_status, rows_fetched: laneDebug.rows_fetched, raw_rows_received: laneDebug.raw_rows_received, rows_after_account_scope: laneDebug.rows_after_account_scope, rows_after_recipe_filter: laneDebug.rows_after_recipe_filter, rows_materialized: laneDebug.rows_materialized, rows_matched: laneDebug.rows_matched, materialization_drop_reason: laneDebug.materialization_drop_reason, account_token_raw: laneDebug.account_token_raw, account_token_normalized: laneDebug.account_token_normalized, account_scope_fields_checked: laneDebug.account_scope_fields_checked, account_scope_match_strategy: laneDebug.account_scope_match_strategy, account_scope_drop_reason: laneDebug.account_scope_drop_reason, runtime_readiness: laneDebug.runtime_readiness, limited_reason_category: laneDebug.limited_reason_category, response_type: laneDebug.response_type, limitations: laneDebug.limitations, assistant_reply: assistantItem.text, reply_type: assistantItem.reply_type, trace_id: assistantItem.trace_id }; } export function finalizeAssistantAddressTurn( input: FinalizeAssistantAddressTurnInput ): FinalizeAssistantAddressTurnOutput { const nowIso = input.nowIso ?? (() => new Date().toISOString()); const messageIdFactory = input.messageIdFactory ?? (() => `msg-${nanoid(10)}`); const commitSafe = input.commitFn ?? commitAssistantTurnAndLog; const assistantItem: AssistantConversationItem = { message_id: messageIdFactory(), session_id: input.sessionId, role: "assistant", text: input.assistantReply, reply_type: input.replyType, created_at: nowIso(), trace_id: toTraceId(input.debug), debug: input.debug as AssistantDebugPayload }; const logDetails = buildAddressProcessedLogDetails(input, assistantItem); const commitResult = commitSafe({ sessionId: input.sessionId, assistantItem, eventType: "assistant_message_address", logDetails, appendItem: input.appendItem, getSession: input.getSession, persistSession: input.persistSession, cloneConversation: input.cloneConversation, logEvent: input.logEvent }); const response: AssistantMessageResponsePayload = { ok: true, session_id: input.sessionId, assistant_reply: assistantItem.text, reply_type: assistantItem.reply_type as AssistantReplyType, conversation_item: assistantItem, debug: input.debug as AssistantDebugPayload, conversation: commitResult.conversation }; return { assistantItem, commitResult, response }; }