94 lines
3.4 KiB
TypeScript
94 lines
3.4 KiB
TypeScript
import { z } from "zod";
|
|
import type { AgentTaskDiscussionContext } from "@/features/agent";
|
|
|
|
export type InvestigationDiscussion = { prompt?: string; context?: AgentTaskDiscussionContext };
|
|
|
|
const text = z.string().trim().min(1);
|
|
export const investigationDiagramSchema = z
|
|
.object({
|
|
title: text,
|
|
mermaid: text
|
|
.max(20000)
|
|
.refine(
|
|
(source) =>
|
|
/^\s*(?:flowchart|graph)\s+(?:TD|TB|BT|LR|RL)\b/.test(source) &&
|
|
!source.includes("```") &&
|
|
!source.includes("%%{") &&
|
|
!/(?:^|[;\n])\s*click\b/.test(source),
|
|
"调查图只接受不含指令或回调的 Mermaid 流程描述"
|
|
),
|
|
node_refs: z
|
|
.array(
|
|
z
|
|
.object({
|
|
node_id: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
|
|
label: text,
|
|
evidence_refs: z.array(text).default([]),
|
|
action_refs: z.array(z.string().uuid()).default([])
|
|
})
|
|
.strict()
|
|
)
|
|
.max(100)
|
|
.default([])
|
|
})
|
|
.strict()
|
|
.superRefine((diagram, ctx) => {
|
|
if (new Set(diagram.node_refs.map((node) => node.node_id)).size !== diagram.node_refs.length) {
|
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "调查图节点引用重复" });
|
|
}
|
|
});
|
|
const dataGapSchema = z
|
|
.object({
|
|
gap_id: text,
|
|
node_id: text.nullish(),
|
|
missing_data: text,
|
|
scope: text,
|
|
impact: text,
|
|
required_data: text,
|
|
evidence_refs: z.array(text).min(1),
|
|
blocking: z.boolean()
|
|
})
|
|
.strict();
|
|
|
|
/** Historical omissions remain unknown. Never infer outcomes from report prose. */
|
|
export const investigationReportSchema = z
|
|
.object({
|
|
title: text,
|
|
current_conclusion: text,
|
|
conclusion_boundary: text,
|
|
evidence_refs: z.array(text),
|
|
recommendations: z.array(text),
|
|
pending_items: z.array(text),
|
|
stage: z.enum(["interim", "final"]).nullish(),
|
|
outcome: z.enum(["concluded", "insufficient_data"]).nullish(),
|
|
stop_reason: text.nullish(),
|
|
data_gaps: z.array(dataGapSchema).nullish(),
|
|
diagram: investigationDiagramSchema.nullish()
|
|
})
|
|
.strict()
|
|
.superRefine((report, ctx) => {
|
|
const invalid = (message: string) => ctx.addIssue({ code: z.ZodIssueCode.custom, message });
|
|
if (report.outcome == null) {
|
|
if (report.stage != null || report.stop_reason != null || report.data_gaps != null)
|
|
invalid("报告结构化字段必须一起提供");
|
|
return;
|
|
}
|
|
if (report.stage == null || report.data_gaps == null) {
|
|
invalid("报告缺少阶段或数据缺口清单");
|
|
return;
|
|
}
|
|
if (new Set(report.data_gaps.map((gap) => gap.gap_id)).size !== report.data_gaps.length)
|
|
invalid("数据缺口编号重复");
|
|
const blocking = report.data_gaps.some((gap) => gap.blocking);
|
|
if (report.outcome === "insufficient_data") {
|
|
if (report.stage !== "final" || !report.stop_reason || !blocking)
|
|
invalid("证据不足必须提供最终报告、结束原因和关键缺口");
|
|
if (report.recommendations.length || report.pending_items.length)
|
|
invalid("证据不足退出后不再生成后续处置建议或待办");
|
|
} else if (report.stop_reason != null || blocking)
|
|
invalid("已形成结论不能包含结束原因或阻塞缺口");
|
|
});
|
|
|
|
export type InvestigationReport = z.infer<typeof investigationReportSchema>;
|
|
export type InvestigationDiagram = z.infer<typeof investigationDiagramSchema>;
|