UNPKG

agent-contracts-runtime

Version:

Runtime bridge for executing agent-contracts workflows on Agent SDKs

296 lines (294 loc) 10.3 kB
// 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