agent-contracts-runtime
Version:
Runtime bridge for executing agent-contracts workflows on Agent SDKs
296 lines (294 loc) • 10.3 kB
JavaScript
// src/generator/template-engine.ts
import { readFile } from "fs/promises";
import { resolve, join } from "path";
import { fileURLToPath } from "url";
import Handlebars from "handlebars";
import { existsSync } from "fs";
var __dirname = fileURLToPath(new URL(".", import.meta.url));
function findPackageRoot() {
let dir = __dirname;
for (let i = 0; i < 5; i++) {
if (existsSync(join(dir, "package.json"))) return dir;
dir = resolve(dir, "..");
}
return resolve(__dirname, "..");
}
var BUILTIN_TEMPLATES_DIR = join(findPackageRoot(), "templates");
function toCamelCase(str) {
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
}
function toPascalCase(str) {
const camel = toCamelCase(str);
return camel.charAt(0).toUpperCase() + camel.slice(1);
}
var hbs = Handlebars.create();
hbs.registerHelper("camelCase", (str) => toCamelCase(str));
hbs.registerHelper("PascalCase", (str) => toPascalCase(str));
hbs.registerHelper("eq", (a, b) => a === b);
hbs.registerHelper(
"notEmpty",
(arr) => Array.isArray(arr) ? arr.length > 0 : !!arr
);
hbs.registerHelper(
"json",
(value) => new Handlebars.SafeString(JSON.stringify(value, null, 2))
);
hbs.registerHelper(
"hasDependsOn",
(step) => "depends_on" in step && Array.isArray(step.depends_on)
);
hbs.registerHelper(
"jsonInline",
(value) => new Handlebars.SafeString(JSON.stringify(value))
);
hbs.registerHelper("indent", (text, spaces) => {
if (typeof text !== "string") return text;
const pad = " ".repeat(spaces);
return text.split("\n").map((line, i) => i === 0 ? line : pad + line).join("\n");
});
function escapeRegexPatternForSource(pattern) {
return pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
}
function appendStringValidations(base, schema) {
let result = base;
if (schema.minLength !== void 0) result += `.min(${schema.minLength})`;
if (schema.maxLength !== void 0) result += `.max(${schema.maxLength})`;
if (schema.pattern !== void 0) {
const escaped = escapeRegexPatternForSource(schema.pattern);
result += `.regex(/${escaped}/)`;
}
return result;
}
function appendNumberValidations(base, schema, isInteger) {
let result = base;
if (isInteger) result += ".int()";
if (schema.minimum !== void 0) result += `.min(${schema.minimum})`;
if (schema.maximum !== void 0) result += `.max(${schema.maximum})`;
return result;
}
function appendArrayValidations(base, schema) {
let result = base;
if (schema.minItems !== void 0) result += `.min(${schema.minItems})`;
if (schema.maxItems !== void 0) result += `.max(${schema.maxItems})`;
return result;
}
hbs.registerHelper("zodType", function zodType(schema) {
if (!schema || typeof schema !== "object") return "z.unknown()";
if (schema.enum) {
const vals = schema.enum.map((v) => `"${v}"`).join(", ");
return `z.enum([${vals}])`;
}
if (schema.const !== void 0) {
return `z.literal(${JSON.stringify(schema.const)})`;
}
const type = schema.type;
if (type === "string") return appendStringValidations("z.string()", schema);
if (type === "integer") return appendNumberValidations("z.number()", schema, true);
if (type === "number") return appendNumberValidations("z.number()", schema, false);
if (type === "boolean") return "z.boolean()";
if (type === "array") {
const items = schema.items;
const inner = items ? zodType(items) : "z.unknown()";
return appendArrayValidations(`z.array(${inner})`, schema);
}
if (type === "object") {
const props = schema.properties;
if (!props) return "z.record(z.string(), z.unknown())";
const required = schema.required ?? [];
const fields = Object.entries(props).map(([key, val]) => {
const base = zodType(val);
const opt = required.includes(key) ? "" : ".optional()";
const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
return ` ${safeKey}: ${base}${opt},`;
}).join("\n");
return `z.object({
${fields}
})`;
}
if (schema.allOf) {
const parts = schema.allOf.map(zodType);
if (parts.length === 1) return parts[0];
return parts.reduce((acc, part) => `${acc}.extend(${part}.shape)`);
}
return "z.unknown()";
});
hbs.registerHelper("regexEscape", (str) => {
if (typeof str !== "string") return str;
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
});
hbs.registerHelper("stringArray", (arr) => {
if (!Array.isArray(arr)) return "[]";
return "[" + arr.map((s) => `"${s}"`).join(", ") + "]";
});
async function loadTemplate(name, customDir) {
if (customDir) {
try {
return await readFile(join(customDir, name), "utf8");
} catch {
}
}
return readFile(join(BUILTIN_TEMPLATES_DIR, name), "utf8");
}
async function renderTemplate(name, context, customDir) {
const source = await loadTemplate(name, customDir);
const compiled = hbs.compile(source, { noEscape: true });
return compiled(context);
}
// src/generator/context.ts
function isTaskOptional(taskId, workflows) {
for (const wf of Object.values(workflows)) {
for (const step of wf.steps) {
if (step.type === "delegate" && step.task === taskId && step.optional) {
return true;
}
}
}
return false;
}
function buildContractContext(dsl) {
const agents = dsl.agents ?? {};
const tasks = dsl.tasks ?? {};
const workflows = dsl.workflow ?? {};
const handoffTypes = dsl.handoff_types ?? {};
const agentEntries = Object.entries(agents).map(([id, agent]) => ({
id,
varName: toCamelCase(id),
role_name: agent.role_name ?? id,
purpose: agent.purpose ?? "",
mode: agent.mode ?? "read-write",
dispatch_only: agent.dispatch_only ?? false,
can_read_artifacts: agent.can_read_artifacts ?? [],
can_write_artifacts: agent.can_write_artifacts ?? [],
can_execute_tools: agent.can_execute_tools ?? [],
can_invoke_agents: agent.can_invoke_agents ?? [],
can_return_handoffs: agent.can_return_handoffs ?? [],
responsibilities: agent.responsibilities ?? [],
constraints: agent.constraints ?? [],
rules: agent.rules ?? [],
escalation_criteria: agent.escalation_criteria ?? []
}));
const taskEntries = Object.entries(tasks).map(([id, task]) => ({
id,
varName: toCamelCase(id),
description: task.description ?? "",
target_agent: task.target_agent ?? "",
allowed_from_agents: task.allowed_from_agents ?? [],
workflow: task.workflow ?? "",
invocation_handoff: task.invocation_handoff ?? "",
result_handoff: task.result_handoff ?? "",
input_artifacts: task.input_artifacts ?? [],
responsibilities: task.responsibilities ?? [],
completion_criteria: task.completion_criteria ?? [],
optional: isTaskOptional(id, workflows),
...task.model_class ? { model_class: task.model_class } : {}
}));
const workflowEntries = Object.entries(workflows).map(
([id, wf]) => {
const steps = (wf.steps ?? []).map((step) => {
const entry = { type: step.type };
if (step.type === "delegate") {
entry.task = step.task;
entry.from_agent = step.from_agent;
entry.description = step.description ?? "";
const wfStep = step;
entry.optional = isTaskOptional(
step.task,
workflows
);
entry.max_retries = wfStep.max_retries ?? (wfStep.retry ? 1 : 0);
entry.max_follow_ups = wfStep.max_follow_ups;
if (wfStep.depends_on !== void 0) {
entry.depends_on = wfStep.depends_on;
}
if (wfStep.retry) {
const r = wfStep.retry;
entry.retry = {
condition: r.condition,
fix_task: r.fix_task,
revalidate_task: r.revalidate_task
};
}
} else if (step.type === "gate") {
entry.gate_kind = step.gate_kind;
entry.description = step.description ?? "";
if (step.depends_on !== void 0) {
entry.depends_on = step.depends_on;
}
}
return entry;
});
return {
id,
varName: toCamelCase(id),
description: wf.description ?? "",
trigger: wf.trigger ?? "",
entry_conditions: wf.entry_conditions ?? [],
steps
};
}
);
const handoffEntries = Object.entries(handoffTypes).map(
([id, ht]) => ({
id,
varName: toCamelCase(id),
pascalName: toPascalCase(id),
schemaName: toPascalCase(id) + "Schema",
description: ht.description ?? "",
schema: ht.schema ?? {}
})
);
return { agentEntries, taskEntries, workflowEntries, handoffEntries };
}
function buildGuardrailContext(dsl, binding, policyName) {
const empty = {
commandChecks: [],
fileChecks: [],
contentChecks: [],
allChecks: [],
hasGuardrails: false
};
if (!binding?.guardrail_impl) return empty;
const guardrails = dsl.guardrails ?? {};
const policies = dsl.guardrail_policies ?? {};
const policy = policyName ? policies[policyName] : void 0;
const allChecks = [];
for (const [guardrailId, impl] of Object.entries(
binding.guardrail_impl
)) {
const guardrail = guardrails[guardrailId];
const description = guardrail?.description ?? guardrailId;
const policyRule = policy?.rules?.find(
(r) => r.guardrail === guardrailId
);
const severity = policyRule?.severity ?? "warning";
const action = policyRule?.action ?? "warn";
for (const check of impl.checks) {
const matcher = check.matcher;
if (!matcher) continue;
allChecks.push({
guardrail_id: guardrailId,
description,
severity,
action,
matcher_type: matcher.type,
pattern: matcher.pattern,
message: check.message ?? description,
file_glob: matcher.file_glob,
exclude_glob: matcher.exclude_glob
});
}
}
return {
commandChecks: allChecks.filter((c) => c.matcher_type === "command_regex"),
fileChecks: allChecks.filter((c) => c.matcher_type === "file_glob"),
contentChecks: allChecks.filter((c) => c.matcher_type === "content_regex"),
allChecks,
hasGuardrails: allChecks.length > 0
};
}
export {
renderTemplate,
buildContractContext,
buildGuardrailContext
};
//# sourceMappingURL=chunk-LTZA6QWC.js.map