import { composeAssistantSystemPrompt } from './toolkit/AssistantPromptComposer'; import type { KnowledgeRepository } from './KnowledgeRepository'; const SAMPLE_FILE_LIMIT = 8; const SAMPLE_CHARS_PER_FILE = 1_200; export interface KnowledgeTopicSample { path: string; title: string; excerpt: string; } /** * 知识库工具包贡献的提示词能力段:由资料主题归纳可答范围,用户不可见具体文件。 * 无可检索资料时返回 undefined —— 本包此刻没有能力,不该占一段提示词。 */ export function describeKnowledgeCapability( repository: KnowledgeRepository, signal?: AbortSignal, ): string | undefined { const samples = collectKnowledgeSamples(repository, signal); const topics = uniqueTitles(samples.map((sample) => sample.title)); if (topics.length === 0) { return undefined; } return [ '- 查阅工作资料并回答下列主题相关的问题:', ...topics.map((topic) => ` · ${topic}`), ].join('\n'); } /** * @deprecated 仅知识库一种能力时的旧入口,保留给存量调用方; * 新代码走 AssistantToolRegistry 的能力段 + composeAssistantSystemPrompt。 */ export function resolveAssistantSystemPrompt( repository: KnowledgeRepository, configuredPrompt: string | undefined, signal?: AbortSignal, ): string { const capability = describeKnowledgeCapability(repository, signal); return composeAssistantSystemPrompt(configuredPrompt, capability ? [capability] : []); } export function collectKnowledgeSamples( repository: KnowledgeRepository, signal?: AbortSignal, ): KnowledgeTopicSample[] { const files = repository.listFiles(undefined, signal).files .slice() .sort((left, right) => right.mtime.localeCompare(left.mtime) || left.path.localeCompare(right.path)) .slice(0, SAMPLE_FILE_LIMIT); const samples: KnowledgeTopicSample[] = []; for (const file of files) { signal?.throwIfAborted(); let excerpt = ''; try { excerpt = repository.readFile(file.path, 0, signal).content.slice(0, SAMPLE_CHARS_PER_FILE); } catch { continue; } samples.push({ path: file.path, title: extractTitle(file.path, excerpt), excerpt: excerpt.replace(/\s+/g, ' ').trim(), }); } return samples; } export function extractTitle(logicalPath: string, content: string): string { const heading = content.match(/^\s{0,3}#{1,3}\s+(.+?)\s*$/m); if (heading?.[1]) { return sanitizeTitle(heading[1]); } const base = logicalPath.split('/').pop() ?? logicalPath; return sanitizeTitle(base.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ')); } export function uniqueTitles(titles: string[]): string[] { const seen = new Set(); const result: string[] = []; for (const title of titles) { const key = title.toLocaleLowerCase(); if (seen.has(key)) { continue; } seen.add(key); result.push(title); } return result; } function sanitizeTitle(value: string): string { return value .replace(/[`*_~\[\]]/g, '') .replace(/\s+/g, ' ') .trim() .slice(0, 32) || '工作资料'; }