mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-08-21 06:33:48 +00:00
117 lines
4.9 KiB
TypeScript
117 lines
4.9 KiB
TypeScript
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import type { TelemetryAttributeDefinition, TelemetrySchemaDefinition } from "@earendil-works/pi-telemetry";
|
|
import { AI_TELEMETRY_SCHEMA, HARNESS_TELEMETRY_SCHEMA } from "../src/harness/telemetry.ts";
|
|
|
|
function escapeCell(value: string): string {
|
|
return value.replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
}
|
|
|
|
function allowedValues(definition: TelemetryAttributeDefinition): string {
|
|
if ("values" in definition && definition.values) return definition.values.map(String).join(", ");
|
|
if ("elementValues" in definition && definition.elementValues) {
|
|
return `elements: ${definition.elementValues.map(String).join(", ")}`;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function attributeNotes(definition: TelemetryAttributeDefinition): string {
|
|
return [definition.cardinality ? `${definition.cardinality} cardinality` : "", definition.sensitive ? "sensitive" : ""]
|
|
.filter(Boolean)
|
|
.join(", ");
|
|
}
|
|
|
|
function parentDescription(parent: TelemetrySchemaDefinition["spans"][string]["parents"]): string {
|
|
switch (parent.kind) {
|
|
case "any":
|
|
return "root or any caller span";
|
|
case "root_or_external":
|
|
return "root or caller-owned external span";
|
|
case "spans":
|
|
return parent.spans.map((span) => `\`${span}\``).join(", ");
|
|
}
|
|
}
|
|
|
|
function renderSchema(schema: TelemetrySchemaDefinition, title: string): string[] {
|
|
const lines = [`## ${title}`, "", `Schema version: ${schema.version}`, ""];
|
|
for (const [spanName, span] of Object.entries(schema.spans)) {
|
|
lines.push(`### \`${spanName}\``, "", span.description, "");
|
|
lines.push(`- Parents: ${parentDescription(span.parents)}`);
|
|
lines.push(`- Default status: \`${span.status.default}\``);
|
|
lines.push(`- Error when: ${span.status.errorWhen}`, "");
|
|
lines.push("#### Start attributes", "");
|
|
lines.push("| Name | Type | Required | Values | Notes | Description |");
|
|
lines.push("|---|---|---:|---|---|---|");
|
|
for (const [name, definition] of Object.entries(span.startAttributes)) {
|
|
lines.push(
|
|
`| \`${name}\` | \`${definition.type}\` | ${definition.required ? "yes" : "no"} | ${escapeCell(allowedValues(definition))} | ${escapeCell(attributeNotes(definition))} | ${escapeCell(definition.description)} |`,
|
|
);
|
|
}
|
|
if (Object.keys(span.startAttributes).length === 0) lines.push("| _none_ | | | | | |");
|
|
lines.push("", "#### End attributes", "");
|
|
lines.push("All end attributes are optional completion enrichment.", "");
|
|
lines.push("| Name | Type | Values | Notes | Description |");
|
|
lines.push("|---|---|---|---|---|");
|
|
for (const [name, definition] of Object.entries(span.endAttributes)) {
|
|
lines.push(
|
|
`| \`${name}\` | \`${definition.type}\` | ${escapeCell(allowedValues(definition))} | ${escapeCell(attributeNotes(definition))} | ${escapeCell(definition.description)} |`,
|
|
);
|
|
}
|
|
if (Object.keys(span.endAttributes).length === 0) lines.push("| _none_ | | | | |");
|
|
lines.push("", "#### Events", "");
|
|
const events = Object.entries(span.events ?? {});
|
|
if (events.length === 0) {
|
|
lines.push("No declared span events.", "");
|
|
continue;
|
|
}
|
|
for (const [eventName, event] of events) {
|
|
lines.push(`##### \`${eventName}\``, "", event.description, "");
|
|
lines.push("| Name | Type | Required | Values | Notes | Description |");
|
|
lines.push("|---|---|---:|---|---|---|");
|
|
for (const [name, definition] of Object.entries(event.attributes)) {
|
|
lines.push(
|
|
`| \`${name}\` | \`${definition.type}\` | ${definition.required ? "yes" : "no"} | ${escapeCell(allowedValues(definition))} | ${escapeCell(attributeNotes(definition))} | ${escapeCell(definition.description)} |`,
|
|
);
|
|
}
|
|
if (Object.keys(event.attributes).length === 0) lines.push("| _none_ | | | | | |");
|
|
lines.push("");
|
|
}
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
export function renderAgentTelemetrySchemaMarkdown(): string {
|
|
const lines = [
|
|
"# Pi Agent Telemetry Schemas",
|
|
"",
|
|
"<!-- Generated by generate-telemetry-docs.ts. Do not edit manually. -->",
|
|
"",
|
|
...renderSchema(AI_TELEMETRY_SCHEMA, "AI request schema"),
|
|
...renderSchema(HARNESS_TELEMETRY_SCHEMA, "Harness schema"),
|
|
];
|
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
}
|
|
|
|
export function generateTelemetryDocs(outputPath: string, check: boolean): void {
|
|
const expected = renderAgentTelemetrySchemaMarkdown();
|
|
if (check) {
|
|
let actual = "";
|
|
try {
|
|
actual = readFileSync(outputPath, "utf8");
|
|
} catch {
|
|
throw new Error(`${outputPath} is missing; run the telemetry documentation generator`);
|
|
}
|
|
if (actual !== expected) throw new Error(`${outputPath} is stale; run the telemetry documentation generator`);
|
|
return;
|
|
}
|
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
writeFileSync(outputPath, expected);
|
|
}
|
|
|
|
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
|
generateTelemetryDocs(
|
|
resolve(import.meta.dirname, "../docs/telemetry-schema.md"),
|
|
process.argv.includes("--check"),
|
|
);
|
|
}
|