Files
tutti/tools/scripts/generate-agent-live-protocol.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

186 lines
5.5 KiB
JavaScript

import { createHash } from "node:crypto";
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(scriptDir, "../..");
const schemaPath = resolve(
repoRoot,
"packages/agent/daemon/liveprotocol/schema/agent-activity-live-event.schema.json"
);
const wireContractPath = resolve(
repoRoot,
"packages/agent/daemon/liveprotocol/schema/agent-activity-live-wire-contract.json"
);
const canonicalPath = resolve(
repoRoot,
"packages/events/protocol/definitions/agent/activity.updated.event.json"
);
const goOutputPath = resolve(
repoRoot,
"packages/agent/daemon/liveprotocol/protocol_revision.gen.go"
);
const tsOutputPath = resolve(
repoRoot,
"packages/agent/activity-core/src/liveProtocolRevision.gen.ts"
);
const checkOnly = process.argv.includes("--check");
const liveSchema = JSON.parse(readFileSync(schemaPath, "utf8"));
const wireContract = JSON.parse(readFileSync(wireContractPath, "utf8"));
const canonicalDefinition = JSON.parse(readFileSync(canonicalPath, "utf8"));
const canonicalVariants = [
"session_audit",
"turn_update",
"interaction_update"
].map((eventType) => {
const variant = canonicalDefinition.payloadSchema.oneOf.find(
(candidate) => candidate?.properties?.eventType?.const === eventType
);
if (!variant) {
throw new Error(`canonical agent activity schema is missing ${eventType}`);
}
return variant;
});
validateWireContract(wireContract);
const digest = createHash("sha256")
.update(JSON.stringify({ liveSchema, wireContract, canonicalVariants }))
.digest("hex")
.slice(0, 16);
const revision = `sha256:${digest}`;
const goWireFieldConstants = [
...wireContract.frameFields,
...wireContract.deliveryFields
];
const goWireFieldConstantWidth = Math.max(
...goWireFieldConstants.map((field) => field.goConstant.length)
);
const goWireFieldDeclarations = goWireFieldConstants
.map(
(field) =>
`\t${field.goConstant.padEnd(goWireFieldConstantWidth)} = ${field.number} // ${field.name}: ${field.wireType}`
)
.join("\n");
const goDeliveryKindConstantWidth = Math.max(
...wireContract.deliveryKinds.map((kind) => kind.goConstant.length)
);
const goDeliveryKindConstants = wireContract.deliveryKinds
.map(
(kind) =>
`\t${kind.goConstant.padEnd(goDeliveryKindConstantWidth)} DeliveryKind = ${kind.value} // payload: ${kind.payloadField}`
)
.join("\n");
const outputs = new Map([
[
goOutputPath,
`// Code generated by tools/scripts/generate-agent-live-protocol.mjs. DO NOT EDIT.
package liveprotocol
const ProtocolRevision = ${JSON.stringify(revision)}
// Protobuf-wire field numbers and delivery kinds are generated from
// schema/agent-activity-live-wire-contract.json. They participate in
// ProtocolRevision and must not be edited independently.
const (
${goWireFieldDeclarations}
)
const (
${goDeliveryKindConstants}
)
`
],
[
tsOutputPath,
`// Code generated by tools/scripts/generate-agent-live-protocol.mjs. DO NOT EDIT.
export const AGENT_ACTIVITY_LIVE_PROTOCOL_REVISION =
${JSON.stringify(revision)} as const;
`
]
]);
for (const [path, expected] of outputs) {
if (checkOnly) {
const actual = readFileSync(path, "utf8");
if (actual !== expected) {
throw new Error(
`${path} is stale; run pnpm generate:agent-live-protocol`
);
}
} else {
writeFileSync(path, expected);
}
}
function validateWireContract(contract) {
if (
contract?.encoding !== "protobuf-wire" ||
!Array.isArray(contract.frameFields) ||
!Array.isArray(contract.deliveryFields) ||
!Array.isArray(contract.deliveryKinds) ||
typeof contract.controls !== "object" ||
contract.controls === null
) {
throw new Error("Agent live wire contract has an invalid root shape");
}
validateNumberedEntries(contract.frameFields, "frame field");
validateNumberedEntries(contract.deliveryFields, "delivery field");
validateNumberedEntries(contract.deliveryKinds, "delivery kind", "value");
const deliveryFieldNames = new Set(
contract.deliveryFields.map((field) => field.name)
);
for (const kind of contract.deliveryKinds) {
if (!deliveryFieldNames.has(kind.payloadField)) {
throw new Error(
`delivery kind ${kind.name} references unknown payload field ${kind.payloadField}`
);
}
}
for (const requiredControl of [
"discontinuity",
"attachmentChanged",
"attachmentCaughtUp",
"goalChanged",
"streamReady",
"rejected"
]) {
if (!(requiredControl in contract.controls)) {
throw new Error(`wire contract is missing ${requiredControl} control`);
}
}
}
function validateNumberedEntries(entries, label, numberKey = "number") {
const names = new Set();
const constants = new Set();
const numbers = new Set();
for (const entry of entries) {
if (
typeof entry?.name !== "string" ||
entry.name.length === 0 ||
typeof entry?.goConstant !== "string" ||
entry.goConstant.length === 0 ||
!Number.isSafeInteger(entry?.[numberKey]) ||
entry[numberKey] <= 0
) {
throw new Error(`${label} has an invalid declaration`);
}
if (
names.has(entry.name) ||
constants.has(entry.goConstant) ||
numbers.has(entry[numberKey])
) {
throw new Error(`${label} declarations must be unique`);
}
names.add(entry.name);
constants.add(entry.goConstant);
numbers.add(entry[numberKey]);
}
}