Z ZPI 文档 v0.3.8
介绍页

源码

packages/coding-agent/src/core/mentions.ts

typescript70 行查看原文
import { codeRanges } from "./markdown-code.ts";

export interface Mention {
  kind: "file" | "skill" | "session";
  label: string;
  path: string;
  start: number;
  end: number;
  markdown: string;
}
const pattern = /\[((?:\\.|[^\\\]])*)\]\((?:<((?:\\.|[^>])*?)>|((?:\\.|[^)])*))\)/g;
const unescapeMarkdown = (value: string) => value.replace(/\\(.)/gs, "$1");
export function buildMentionMarkdown(label: string, path: string): string {
  const title = label.replace(/\\/g, "\\\\").replace(/[[\]]/g, "\\$&");
  const target = path.replace(/\\/g, "\\\\").replace(/[<>]/g, "\\$&");
  return `[${title}](${/[\s()<>]/.test(path) ? `<${target}>` : target})`;
}
// ZCode's canonical # destination, adapted to ZPI's UUID session identities.
const sessionTarget = /^#(?:sess_[a-zA-Z0-9._-]+|[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12})$/i;
export function buildSessionMentionMarkdown(sessionId: string, label?: string): string {
  const title = label?.trim();
  return title && title !== sessionId ? buildMentionMarkdown(`#${title}`, `#${sessionId}`) : `#${sessionId}`;
}
export function extractSessionReferences(text: string): string[] {
  return [
    ...new Set(
      parseMentions(text)
        .filter((mention) => mention.kind === "session")
        .map((mention) => mention.path.slice(1)),
    ),
  ];
}
export function parseMentions(text: string): Mention[] {
  const result: Mention[] = [];
  const links = [...text.matchAll(pattern)];
  const code = codeRanges(text);
  const inCode = (index: number) => code.some(([start, end]) => index >= start && index < end);
  for (const match of links) {
    if (inCode(match.index)) continue;
    const label = unescapeMarkdown(match[1]),
      path = unescapeMarkdown(match[2] ?? match[3]);
    const session = sessionTarget.test(path);
    if (!session && !/^(?:\/|\.\.?[\\/]|[a-z]:[\\/]|\\\\)/i.test(path)) continue;
    result.push({
      kind: session ? "session" : label.startsWith("$") ? "skill" : "file",
      label: label.replace(session ? /^#/ : /^[$@]/, ""),
      path,
      start: match.index,
      end: match.index + match[0].length,
      markdown: match[0],
    });
  }
  for (const match of text.matchAll(
    /(^|\s)(#(?:sess_[a-zA-Z0-9._-]+|[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}))(?=$|\s)/gi,
  )) {
    const start = match.index + match[1].length;
    if (inCode(start)) continue;
    if (links.some((link) => start >= link.index && start < link.index + link[0].length)) continue;
    result.push({
      kind: "session",
      label: match[2].slice(1),
      path: match[2],
      start,
      end: start + match[2].length,
      markdown: match[2],
    });
  }
  return result.sort((a, b) => a.start - b.start);
}