mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-15 00:19:14 +00:00
* docs: align custom agent naming with API * docs: document custom agent API gate * docs: correct custom agent storage guidance * docs: correct custom agent config path * docs: clarify custom agent name scope * docs: align agent storage placeholder * docs: clarify custom agent file updates * docs: qualify custom agent storage * docs: clarify agent database storage * Fix formatting in docs-links.test.ts --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
import { readFileSync, readdirSync } from "node:fs";
|
|
import { join, relative, sep } from "node:path";
|
|
|
|
import { describe, expect, it } from "@rstest/core";
|
|
|
|
const CONTENT_ROOT = join(process.cwd(), "src/content");
|
|
const DOC_LANGUAGES = ["en", "zh"] as const;
|
|
const DOCS_ORIGIN = "https://docs.example";
|
|
const LINK_PATTERN =
|
|
/(?<!!)\[[^\]]*\]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^"']*["'])?\s*\)|\bhref\s*=\s*(?:\{\s*)?["']([^"']+)["'](?:\s*\})?/g;
|
|
const UNLOCALIZED_DOCS_PATH = /^\/docs(?=\/|[?#]|$)/;
|
|
|
|
function findMdxFiles(directory: string): string[] {
|
|
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
const path = join(directory, entry.name);
|
|
return entry.isDirectory()
|
|
? findMdxFiles(path)
|
|
: entry.name.endsWith(".mdx")
|
|
? [path]
|
|
: [];
|
|
});
|
|
}
|
|
|
|
function routeForMdx(path: string, lang: string): string {
|
|
const localeRoot = join(CONTENT_ROOT, lang);
|
|
const relativePath = relative(localeRoot, path).split(sep).join("/");
|
|
const pagePath = relativePath
|
|
.replace(/\.mdx$/, "")
|
|
.replace(/(?:^|\/)index$/, "");
|
|
return `/${lang}/docs${pagePath ? `/${pagePath}` : ""}`;
|
|
}
|
|
|
|
function extractLinks(source: string): Array<{ href: string; line: number }> {
|
|
return [...source.matchAll(LINK_PATTERN)].map((match) => ({
|
|
href: match[1] ?? match[2] ?? match[3] ?? "",
|
|
line: source.slice(0, match.index).split("\n").length,
|
|
}));
|
|
}
|
|
|
|
function resolveDocsPath(
|
|
href: string,
|
|
lang: string,
|
|
sourceRoute: string,
|
|
): string | undefined {
|
|
const localizedHref = UNLOCALIZED_DOCS_PATH.test(href)
|
|
? `/${lang}${href}`
|
|
: href;
|
|
const url = new URL(localizedHref, `${DOCS_ORIGIN}${sourceRoute}`);
|
|
if (
|
|
url.origin !== DOCS_ORIGIN ||
|
|
!/^\/(?:en|zh)\/docs(?:\/|$)/.test(url.pathname)
|
|
) {
|
|
return undefined;
|
|
}
|
|
return url.pathname.replace(/\/$/, "");
|
|
}
|
|
|
|
describe("documentation content links", () => {
|
|
it("only links to documentation routes backed by MDX pages", () => {
|
|
const mdxFiles = DOC_LANGUAGES.flatMap((lang) =>
|
|
findMdxFiles(join(CONTENT_ROOT, lang)),
|
|
);
|
|
const routes = new Set(
|
|
DOC_LANGUAGES.flatMap((lang) =>
|
|
mdxFiles
|
|
.filter((path) => path.startsWith(join(CONTENT_ROOT, lang)))
|
|
.map((path) => routeForMdx(path, lang)),
|
|
),
|
|
);
|
|
|
|
const brokenLinks = DOC_LANGUAGES.flatMap((lang) =>
|
|
findMdxFiles(join(CONTENT_ROOT, lang)).flatMap((path) => {
|
|
const source = readFileSync(path, "utf8");
|
|
const sourceRoute = routeForMdx(path, lang);
|
|
return extractLinks(source).flatMap(({ href, line }) => {
|
|
const targetRoute = resolveDocsPath(href, lang, sourceRoute);
|
|
if (!targetRoute || routes.has(targetRoute)) {
|
|
return [];
|
|
}
|
|
return [
|
|
`${relative(CONTENT_ROOT, path).split(sep).join("/")}:${line} -> ${href} (${targetRoute})`,
|
|
];
|
|
});
|
|
}),
|
|
);
|
|
|
|
expect(brokenLinks).toEqual([]);
|
|
});
|
|
|
|
it("documents the custom-agent create request naming contract", () => {
|
|
const customAgentApiDocs = DOC_LANGUAGES.map((lang) =>
|
|
readFileSync(
|
|
join(CONTENT_ROOT, lang, "application/agents-and-threads.mdx"),
|
|
"utf8",
|
|
),
|
|
);
|
|
const customAgentDocs = [
|
|
...customAgentApiDocs,
|
|
...DOC_LANGUAGES.map((lang) =>
|
|
readFileSync(
|
|
join(CONTENT_ROOT, lang, "harness/lead-agent.mdx"),
|
|
"utf8",
|
|
),
|
|
),
|
|
];
|
|
|
|
for (const source of customAgentDocs) {
|
|
expect(source).toContain("agents_api.enabled");
|
|
expect(source).toContain("agent_storage.backend: file");
|
|
expect(source).toContain("agent_storage.backend: db");
|
|
expect(source).toContain("database.backend: sqlite");
|
|
expect(source).toContain("database.backend: postgres");
|
|
expect(source).toContain("backend/scripts/migrate_agents_to_db.py");
|
|
expect(source).toContain("users/{user_id}/agents/{name}/config.yaml");
|
|
expect(source).not.toContain("display_name");
|
|
}
|
|
|
|
for (const source of customAgentApiDocs) {
|
|
expect(source).toContain("^[A-Za-z0-9-]+$");
|
|
expect(source).toContain("backend/scripts/migrate_user_isolation.py");
|
|
}
|
|
|
|
const chineseApiGuide = readFileSync(
|
|
join(CONTENT_ROOT, "zh/application/agents-and-threads.mdx"),
|
|
"utf8",
|
|
);
|
|
expect(chineseApiGuide).toContain('"name": "data-analyst"');
|
|
expect(chineseApiGuide).toContain("下一次 Agent 调用时自动加载,无需重启");
|
|
|
|
const englishApiGuide = readFileSync(
|
|
join(CONTENT_ROOT, "en/application/agents-and-threads.mdx"),
|
|
"utf8",
|
|
);
|
|
expect(englishApiGuide).toContain(
|
|
"changes are picked up on the agent's next invocation",
|
|
);
|
|
});
|
|
});
|