import type { KdToolResultContract } from "./types.ts"; export interface ToolResultContractInput { toolName: string; status: KdToolResultContract["status"]; kind?: KdToolResultContract["kind"]; summary: string; paths?: string[]; modifiedFiles?: string[]; evidencePaths?: string[]; risk?: string; nextAction?: string; } export function createToolResultContract(input: ToolResultContractInput, sequence: number, now = new Date().toISOString()): KdToolResultContract { return { id: `T-${String(sequence).padStart(3, "0")}`, toolName: input.toolName.trim(), status: input.status, kind: input.kind ?? inferToolResultKind(input.toolName), summary: trimSummary(input.summary), paths: normalizeStringArray(input.paths), modifiedFiles: normalizeStringArray(input.modifiedFiles), evidencePaths: normalizeStringArray(input.evidencePaths), risk: input.risk?.trim() || undefined, nextAction: input.nextAction?.trim() || undefined, createdAt: now, }; } export function formatToolResultContracts(results: KdToolResultContract[] | undefined, limit = 12): string { const items = Array.isArray(results) ? results.slice(-Math.max(1, limit)) : []; if (items.length === 0) return "无。"; return items .map((result) => [ `- ${result.id} ${result.toolName} [${result.status}/${result.kind}]:${result.summary}`, result.paths?.length ? ` - paths:${result.paths.join(";")}` : undefined, result.modifiedFiles?.length ? ` - modifiedFiles:${result.modifiedFiles.join(";")}` : undefined, result.evidencePaths?.length ? ` - evidencePaths:${result.evidencePaths.join(";")}` : undefined, result.nextAction ? ` - nextAction:${result.nextAction}` : undefined, result.risk ? ` - risk:${result.risk}` : undefined, ] .filter(Boolean) .join("\n"), ) .join("\n"); } export function sanitizeToolResultContract(value: unknown): KdToolResultContract | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; const record = value as Record; const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : undefined; const toolName = typeof record.toolName === "string" && record.toolName.trim() ? record.toolName.trim() : undefined; const status = record.status === "blocked" || record.status === "failed" ? record.status : record.status === "success" ? "success" : undefined; const summary = typeof record.summary === "string" && record.summary.trim() ? record.summary.trim() : undefined; if (!id || !toolName || !status || !summary) return undefined; return { id, toolName, status, kind: isToolResultKind(record.kind) ? record.kind : inferToolResultKind(toolName), summary, paths: normalizeStringArray(record.paths), modifiedFiles: normalizeStringArray(record.modifiedFiles), evidencePaths: normalizeStringArray(record.evidencePaths), risk: typeof record.risk === "string" && record.risk.trim() ? record.risk.trim() : undefined, nextAction: typeof record.nextAction === "string" && record.nextAction.trim() ? record.nextAction.trim() : undefined, createdAt: typeof record.createdAt === "string" ? record.createdAt : new Date().toISOString(), }; } function inferToolResultKind(toolName: string): KdToolResultContract["kind"] { if (/find|search/i.test(toolName)) return "search"; if (/list|dir/i.test(toolName)) return "list"; if (/read|doc/i.test(toolName)) return "read"; if (/write|edit|patch/i.test(toolName)) return "write"; if (/build|verify|signature|metadata|ksql|config/i.test(toolName)) return "evidence"; if (/bash|shell|powershell/i.test(toolName)) return "shell"; if (/table|knowledge|api/i.test(toolName)) return "knowledge"; return "other"; } function isToolResultKind(value: unknown): value is KdToolResultContract["kind"] { return value === "read" || value === "search" || value === "list" || value === "shell" || value === "knowledge" || value === "evidence" || value === "write" || value === "other"; } function normalizeStringArray(value: unknown): string[] | undefined { if (!Array.isArray(value)) return undefined; const items = [...new Set(value.filter((item): item is string => typeof item === "string" && Boolean(item.trim())).map((item) => item.trim()))]; return items.length > 0 ? items : undefined; } function trimSummary(value: string, maxLength = 220): string { const normalized = value.trim().replace(/\s+/g, " "); return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength)}...`; }