feat(agent): add structured outputs and media archive support
This commit is contained in:
171
scripts/compile-agent-docs.ts
Normal file
171
scripts/compile-agent-docs.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { AGENT_DEFINITIONS, type AgentDefinitionId } from "../lib/agent-definitions";
|
||||
|
||||
export const AGENT_PROMPT_SEGMENT_KEYS = [
|
||||
"role",
|
||||
"style-rules",
|
||||
"decision-framework",
|
||||
"channel-notes",
|
||||
] as const;
|
||||
|
||||
export type AgentPromptSegmentKey = (typeof AGENT_PROMPT_SEGMENT_KEYS)[number];
|
||||
|
||||
export type AgentPromptSegments = Record<AgentPromptSegmentKey, string>;
|
||||
|
||||
type CompileOptions = {
|
||||
sourcePath: string;
|
||||
};
|
||||
|
||||
function normalizeSegmentBody(raw: string): string {
|
||||
return raw
|
||||
.replace(/\r\n/g, "\n")
|
||||
.split("\n")
|
||||
.map((line) => line.trimEnd())
|
||||
.join("\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function countMarkerOccurrences(markdown: string, marker: string): number {
|
||||
let count = 0;
|
||||
let currentIndex = 0;
|
||||
|
||||
while (currentIndex >= 0) {
|
||||
currentIndex = markdown.indexOf(marker, currentIndex);
|
||||
if (currentIndex < 0) {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
currentIndex += marker.length;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
function extractSegment(markdown: string, key: AgentPromptSegmentKey, sourcePath: string): string {
|
||||
const startMarker = `<!-- AGENT_PROMPT_SEGMENT:${key}:start -->`;
|
||||
const endMarker = `<!-- AGENT_PROMPT_SEGMENT:${key}:end -->`;
|
||||
const startCount = countMarkerOccurrences(markdown, startMarker);
|
||||
const endCount = countMarkerOccurrences(markdown, endMarker);
|
||||
|
||||
if (startCount !== 1 || endCount !== 1) {
|
||||
throw new Error(
|
||||
`[agent-docs] Missing or duplicate markers for '${key}' in ${sourcePath}. Expected exactly one start and one end marker.`,
|
||||
);
|
||||
}
|
||||
|
||||
const startIndex = markdown.indexOf(startMarker);
|
||||
const contentStart = startIndex + startMarker.length;
|
||||
const endIndex = markdown.indexOf(endMarker, contentStart);
|
||||
if (startIndex < 0 || endIndex < 0 || endIndex <= contentStart) {
|
||||
throw new Error(`[agent-docs] Invalid marker placement for '${key}' in ${sourcePath}.`);
|
||||
}
|
||||
|
||||
const segment = normalizeSegmentBody(markdown.slice(contentStart, endIndex));
|
||||
if (segment === "") {
|
||||
throw new Error(`[agent-docs] Empty segment '${key}' in ${sourcePath}.`);
|
||||
}
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
export function compileAgentDocSegmentsFromMarkdown(
|
||||
markdown: string,
|
||||
options: CompileOptions,
|
||||
): AgentPromptSegments {
|
||||
const compiled = {} as AgentPromptSegments;
|
||||
|
||||
for (const key of AGENT_PROMPT_SEGMENT_KEYS) {
|
||||
compiled[key] = extractSegment(markdown, key, options.sourcePath);
|
||||
}
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
export function compileAgentDocSegmentsFromSources(
|
||||
sources: ReadonlyArray<{ agentId: AgentDefinitionId; markdown: string; sourcePath: string }>,
|
||||
): Record<AgentDefinitionId, AgentPromptSegments> {
|
||||
const compiled = {} as Record<AgentDefinitionId, AgentPromptSegments>;
|
||||
|
||||
for (const source of sources) {
|
||||
compiled[source.agentId] = compileAgentDocSegmentsFromMarkdown(source.markdown, {
|
||||
sourcePath: source.sourcePath,
|
||||
});
|
||||
}
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
function escapeForTsString(raw: string): string {
|
||||
return raw.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
||||
}
|
||||
|
||||
export function renderCompiledAgentDocSegmentsModule(
|
||||
compiled: Record<AgentDefinitionId, AgentPromptSegments>,
|
||||
): string {
|
||||
const lines: string[] = [
|
||||
"// This file is generated by scripts/compile-agent-docs.ts",
|
||||
"// Do not edit manually.",
|
||||
"",
|
||||
'import type { AgentDefinitionId } from "@/lib/agent-definitions";',
|
||||
"",
|
||||
"export type AgentDocPromptSegmentKey =",
|
||||
' | "role"',
|
||||
' | "style-rules"',
|
||||
' | "decision-framework"',
|
||||
' | "channel-notes";',
|
||||
"",
|
||||
"export type AgentDocPromptSegments = Record<AgentDocPromptSegmentKey, string>;",
|
||||
"",
|
||||
"export const AGENT_DOC_SEGMENTS: Record<AgentDefinitionId, AgentDocPromptSegments> = {",
|
||||
];
|
||||
|
||||
const agentIds = Object.keys(compiled).sort() as AgentDefinitionId[];
|
||||
for (const agentId of agentIds) {
|
||||
lines.push(` \"${agentId}\": {`);
|
||||
for (const key of AGENT_PROMPT_SEGMENT_KEYS) {
|
||||
lines.push(` \"${key}\": \`${escapeForTsString(compiled[agentId][key])}\`,`);
|
||||
}
|
||||
lines.push(" },");
|
||||
}
|
||||
|
||||
lines.push("};", "");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export async function compileAgentDocsToGeneratedModule(projectRoot: string): Promise<string> {
|
||||
const sources: Array<{ agentId: AgentDefinitionId; markdown: string; sourcePath: string }> = [];
|
||||
|
||||
for (const definition of AGENT_DEFINITIONS) {
|
||||
const sourcePath = definition.docs.markdownPath;
|
||||
const absoluteSourcePath = path.resolve(projectRoot, sourcePath);
|
||||
const markdown = await fs.readFile(absoluteSourcePath, "utf8");
|
||||
sources.push({ agentId: definition.id, markdown, sourcePath });
|
||||
}
|
||||
|
||||
const compiled = compileAgentDocSegmentsFromSources(sources);
|
||||
const output = renderCompiledAgentDocSegmentsModule(compiled);
|
||||
const outputPath = path.resolve(projectRoot, "lib/generated/agent-doc-segments.ts");
|
||||
|
||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||
await fs.writeFile(outputPath, output, "utf8");
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
const thisFilePath = fileURLToPath(import.meta.url);
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
|
||||
if (invokedPath === thisFilePath) {
|
||||
compileAgentDocsToGeneratedModule(process.cwd())
|
||||
.then((outputPath) => {
|
||||
process.stdout.write(`[agent-docs] Wrote ${outputPath}\n`);
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
process.stderr.write(`${message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user