280 lines
9.9 KiB
TypeScript
280 lines
9.9 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { runAssistantAddressRuntime } from "../src/services/assistantAddressRuntimeAdapter";
|
|
|
|
describe("assistant address runtime adapter", () => {
|
|
it("returns unhandled when address feature is disabled", async () => {
|
|
const result = await runAssistantAddressRuntime({
|
|
featureAssistantAddressQueryV1: false,
|
|
sessionId: "asst-1",
|
|
userMessage: "question",
|
|
sessionItems: [],
|
|
llmProvider: "openai",
|
|
useMock: false,
|
|
featureAddressLlmPredecomposeV1: true,
|
|
runAddressLlmPreDecompose: async () => ({}),
|
|
buildAddressLlmPredecomposeContractV1: () => ({}),
|
|
sanitizeAddressMessageForFallback: (value) => value,
|
|
toNonEmptyString: () => null,
|
|
resolveAddressFollowupCarryoverContext: () => null,
|
|
resolveAssistantOrchestrationDecision: () => ({}),
|
|
buildAddressDialogContinuationContractV2: () => ({}),
|
|
runtimeAnalysisContextAsOfDate: null,
|
|
payloadContextPeriodHint: null,
|
|
compactWhitespace: (value) => value.trim(),
|
|
runAddressLaneAttempt: async () => null,
|
|
isRetryableAddressLimitedResult: () => false,
|
|
finalizeAddressLaneResponse: () => ({ ok: true }),
|
|
tryHandleLivingChat: async () => null,
|
|
logEvent: () => {},
|
|
nowIso: () => "2026-04-10T00:00:00.000Z"
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
handled: false,
|
|
response: null,
|
|
addressRuntimeMetaForDeep: null
|
|
});
|
|
});
|
|
|
|
it("returns early when tool-gate chat fallback handles", async () => {
|
|
const runAddressOrchestrationRuntime = vi.fn(async () => ({
|
|
addressPreDecompose: {},
|
|
addressInputMessage: "canon",
|
|
carryover: null,
|
|
orchestrationDecision: { runAddressLane: false },
|
|
addressRuntimeMeta: { attempted: true },
|
|
livingModeDecision: { mode: "chat", reason: "x" }
|
|
}));
|
|
const runAddressToolGateRuntime = vi.fn(async () => ({
|
|
handled: true,
|
|
response: { ok: "chat" }
|
|
}));
|
|
const runAddressLaneRuntime = vi.fn(async () => ({
|
|
handled: false,
|
|
selection: null,
|
|
retryAudit: {
|
|
attempted: false,
|
|
reason: null,
|
|
initial_limited_category: null,
|
|
retry_message: null,
|
|
retry_used_followup_context: false,
|
|
retry_result_category: null
|
|
}
|
|
}));
|
|
|
|
const result = await runAssistantAddressRuntime({
|
|
featureAssistantAddressQueryV1: true,
|
|
sessionId: "asst-2",
|
|
userMessage: "question",
|
|
sessionItems: [],
|
|
llmProvider: "openai",
|
|
useMock: false,
|
|
featureAddressLlmPredecomposeV1: true,
|
|
runAddressLlmPreDecompose: async () => ({}),
|
|
buildAddressLlmPredecomposeContractV1: () => ({}),
|
|
sanitizeAddressMessageForFallback: (value) => value,
|
|
toNonEmptyString: () => null,
|
|
resolveAddressFollowupCarryoverContext: () => null,
|
|
resolveAssistantOrchestrationDecision: () => ({}),
|
|
buildAddressDialogContinuationContractV2: () => ({}),
|
|
runtimeAnalysisContextAsOfDate: null,
|
|
payloadContextPeriodHint: null,
|
|
compactWhitespace: (value) => value.trim(),
|
|
runAddressLaneAttempt: async () => null,
|
|
isRetryableAddressLimitedResult: () => false,
|
|
finalizeAddressLaneResponse: () => ({ ok: true }),
|
|
tryHandleLivingChat: async () => ({ ok: true }),
|
|
logEvent: () => {},
|
|
nowIso: () => "2026-04-10T00:00:00.000Z",
|
|
runAddressOrchestrationRuntime,
|
|
runAddressToolGateRuntime,
|
|
runAddressLaneRuntime
|
|
});
|
|
|
|
expect(result.handled).toBe(true);
|
|
expect(result.response).toEqual({ ok: "chat" });
|
|
expect(result.addressRuntimeMetaForDeep).toEqual({ attempted: true });
|
|
expect(runAddressLaneRuntime).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("finalizes address lane when lane runtime resolves handled selection", async () => {
|
|
const runAddressLaneAttempt = vi.fn(async () => ({
|
|
handled: true
|
|
}));
|
|
const finalizeAddressLaneResponse = vi.fn(() => ({ ok: "address" }));
|
|
const runAddressLaneRuntime = vi.fn(async (input) => {
|
|
await input.runAddressLaneAttempt("canon", null);
|
|
return {
|
|
handled: true,
|
|
selection: {
|
|
addressLane: {
|
|
handled: true
|
|
},
|
|
messageUsed: "canon",
|
|
carryMeta: null
|
|
},
|
|
retryAudit: {
|
|
attempted: true,
|
|
reason: "limited_result_retry_with_raw_message",
|
|
initial_limited_category: "missing_anchor",
|
|
retry_message: "raw",
|
|
retry_used_followup_context: false,
|
|
retry_result_category: null
|
|
}
|
|
};
|
|
});
|
|
|
|
const result = await runAssistantAddressRuntime({
|
|
featureAssistantAddressQueryV1: true,
|
|
sessionId: "asst-3",
|
|
userMessage: "raw question",
|
|
sessionItems: [],
|
|
llmProvider: "openai",
|
|
useMock: false,
|
|
featureAddressLlmPredecomposeV1: true,
|
|
runAddressLlmPreDecompose: async () => ({}),
|
|
buildAddressLlmPredecomposeContractV1: () => ({}),
|
|
sanitizeAddressMessageForFallback: (value) => value,
|
|
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
|
resolveAddressFollowupCarryoverContext: () => ({
|
|
followupContext: {
|
|
intent: "x"
|
|
}
|
|
}),
|
|
resolveAssistantOrchestrationDecision: () => ({}),
|
|
buildAddressDialogContinuationContractV2: () => ({}),
|
|
runtimeAnalysisContextAsOfDate: null,
|
|
payloadContextPeriodHint: "2020-07-31",
|
|
compactWhitespace: (value) => value.replace(/\s+/g, " ").trim(),
|
|
runAddressLaneAttempt,
|
|
isRetryableAddressLimitedResult: () => false,
|
|
finalizeAddressLaneResponse,
|
|
tryHandleLivingChat: async () => null,
|
|
logEvent: () => {},
|
|
nowIso: () => "2026-04-10T00:00:00.000Z",
|
|
runAddressOrchestrationRuntime: async () => ({
|
|
addressPreDecompose: {},
|
|
addressInputMessage: "canon",
|
|
carryover: {
|
|
followupContext: {
|
|
intent: "x"
|
|
}
|
|
},
|
|
orchestrationDecision: { runAddressLane: true },
|
|
addressRuntimeMeta: {
|
|
attempted: true
|
|
},
|
|
livingModeDecision: { mode: "deep_analysis", reason: "x" }
|
|
}),
|
|
runAddressToolGateRuntime: async () => ({
|
|
handled: false,
|
|
response: null
|
|
}),
|
|
runAddressLaneRuntime
|
|
});
|
|
|
|
expect(runAddressLaneAttempt).toHaveBeenCalledWith("canon", null, "2020-07-31", null);
|
|
expect(finalizeAddressLaneResponse).toHaveBeenCalledWith(
|
|
{ handled: true },
|
|
"canon",
|
|
null,
|
|
expect.objectContaining({
|
|
attempted: true,
|
|
addressRetryAudit: expect.objectContaining({
|
|
attempted: true
|
|
})
|
|
})
|
|
);
|
|
expect(result).toEqual({
|
|
handled: true,
|
|
response: { ok: "address" },
|
|
addressRuntimeMetaForDeep: {
|
|
attempted: true
|
|
}
|
|
});
|
|
});
|
|
|
|
it("passes llm semantic hints from orchestration metadata into lane attempts", async () => {
|
|
const runAddressLaneAttempt = vi.fn(async () => ({
|
|
handled: true
|
|
}));
|
|
|
|
const result = await runAssistantAddressRuntime({
|
|
featureAssistantAddressQueryV1: true,
|
|
sessionId: "asst-4",
|
|
userMessage: "что на складе конторы альтернатива",
|
|
sessionItems: [],
|
|
llmProvider: "local",
|
|
useMock: false,
|
|
featureAddressLlmPredecomposeV1: true,
|
|
runAddressLlmPreDecompose: async () => ({}),
|
|
buildAddressLlmPredecomposeContractV1: () => ({}),
|
|
sanitizeAddressMessageForFallback: (value) => value,
|
|
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
|
resolveAddressFollowupCarryoverContext: () => null,
|
|
resolveAssistantOrchestrationDecision: () => ({}),
|
|
buildAddressDialogContinuationContractV2: () => ({}),
|
|
runtimeAnalysisContextAsOfDate: null,
|
|
payloadContextPeriodHint: null,
|
|
compactWhitespace: (value) => value.replace(/\s+/g, " ").trim(),
|
|
runAddressLaneAttempt,
|
|
isRetryableAddressLimitedResult: () => false,
|
|
finalizeAddressLaneResponse: () => ({ ok: "address" }),
|
|
tryHandleLivingChat: async () => null,
|
|
logEvent: () => {},
|
|
nowIso: () => "2026-04-10T00:00:00.000Z",
|
|
runAddressOrchestrationRuntime: async () => ({
|
|
addressPreDecompose: {},
|
|
addressInputMessage: "что на складе конторы альтернатива",
|
|
carryover: null,
|
|
orchestrationDecision: { runAddressLane: true },
|
|
addressRuntimeMeta: {
|
|
attempted: true,
|
|
semanticHints: {
|
|
scope_target_kind: "organization",
|
|
scope_target_text: "Альтернатива",
|
|
date_scope_kind: "implicit_current",
|
|
self_scope_detected: false,
|
|
selected_object_scope_detected: false
|
|
}
|
|
},
|
|
livingModeDecision: { mode: "address_data", reason: "address_lane_triggered" }
|
|
}),
|
|
runAddressToolGateRuntime: async () => ({
|
|
handled: false,
|
|
response: null
|
|
}),
|
|
runAddressLaneRuntime: async (input) => {
|
|
const addressLane = await input.runAddressLaneAttempt(input.addressInputMessage, null, input.llmSemanticHints ?? null);
|
|
return {
|
|
handled: true,
|
|
selection: {
|
|
addressLane: addressLane ?? { handled: true },
|
|
messageUsed: input.addressInputMessage,
|
|
carryMeta: null
|
|
},
|
|
retryAudit: {
|
|
attempted: false,
|
|
reason: null,
|
|
initial_limited_category: null,
|
|
retry_message: null,
|
|
retry_used_followup_context: false,
|
|
retry_result_category: null
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
expect(runAddressLaneAttempt).toHaveBeenCalledWith(
|
|
"что на складе конторы альтернатива",
|
|
null,
|
|
null,
|
|
expect.objectContaining({
|
|
scope_target_kind: "organization",
|
|
scope_target_text: "Альтернатива"
|
|
})
|
|
);
|
|
expect(result.handled).toBe(true);
|
|
});
|
|
});
|