@domesystems/templates 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.mjs +10 -8
- package/src/generate.mjs +28 -12
- package/src/manifest.mjs +26 -5
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -61,10 +61,11 @@ function generate(options, output) {
|
|
|
61
61
|
output(line("success", `Generated ${file}`, options));
|
|
62
62
|
}
|
|
63
63
|
function runtimeFor(directory, manifest, selectedAgent) {
|
|
64
|
-
const
|
|
65
|
-
|
|
64
|
+
const external = manifest.agents.filter((agent) => agent.type === "external");
|
|
65
|
+
const candidates = selectedAgent ? manifest.agents.filter((agent) => agent.name === selectedAgent) : (external.length ? external : manifest.agents);
|
|
66
|
+
if (candidates.length !== 1) throw new Error(`templates up requires exactly one agent to run; found ${candidates.length}. Pass --agent <name> to select one.`);
|
|
66
67
|
const agent = candidates[0];
|
|
67
|
-
if (agent.type
|
|
68
|
+
if (agent.type === "managed") return { ...agent, managed: true };
|
|
68
69
|
const configPath = path.join("agents", agent.name);
|
|
69
70
|
const configFile = path.join(directory, configPath, "agent.yaml");
|
|
70
71
|
if (!fs.existsSync(configFile)) throw new Error(`External agent ${agent.name} needs ${configPath}/agent.yaml.`);
|
|
@@ -115,10 +116,10 @@ async function up(options, output) {
|
|
|
115
116
|
const target = path.resolve(process.cwd(), options.directory ?? ".");
|
|
116
117
|
const manifest = readManifest(target);
|
|
117
118
|
const runtimeAgent = runtimeFor(target, manifest, options.agent);
|
|
118
|
-
const needsCredentials = !configured(target);
|
|
119
|
+
const needsCredentials = !runtimeAgent.managed && !configured(target);
|
|
119
120
|
output(line("info", `Checking ${target}`, options));
|
|
120
|
-
fs.writeFileSync(path.join(target, ".dome-compose.yaml"), renderCompose(runtimeAgent.runtime, options.port, { debug: options.debug, configPath: runtimeAgent.configPath }));
|
|
121
|
-
if (options.importJob) await configureRuntime(target, options.importJob, runtimeAgent.name, runtimeAgent.localKey, options, output);
|
|
121
|
+
if (!runtimeAgent.managed) fs.writeFileSync(path.join(target, ".dome-compose.yaml"), renderCompose(runtimeAgent.runtime, options.port, { debug: options.debug, configPath: runtimeAgent.configPath }));
|
|
122
|
+
if (options.importJob && !runtimeAgent.managed) await configureRuntime(target, options.importJob, runtimeAgent.name, runtimeAgent.localKey, options, output);
|
|
122
123
|
else if (options.provision && (needsCredentials || options.forceProvision)) {
|
|
123
124
|
output(line("info", "Setting up Dome resources", options));
|
|
124
125
|
let imported;
|
|
@@ -126,10 +127,11 @@ async function up(options, output) {
|
|
|
126
127
|
catch (caught) { const dashboardURL = importDashboardURL(target); throw new Error(`${caught.message}${dashboardURL ? ` View the import details: ${dashboardURL}` : ""}`); }
|
|
127
128
|
const job = imported?.data?.job;
|
|
128
129
|
if (!job?.id) throw new Error("Dome import did not return a job ID.");
|
|
129
|
-
if (importCreatedResources(job) && importHasPendingOutputs(job)) await configureRuntime(target, job.id, runtimeAgent.name, runtimeAgent.localKey, options, output);
|
|
130
|
+
if (!runtimeAgent.managed && importCreatedResources(job) && importHasPendingOutputs(job)) await configureRuntime(target, job.id, runtimeAgent.name, runtimeAgent.localKey, options, output);
|
|
130
131
|
else if (needsCredentials) throw new Error(`Dome matched existing resources and did not create a new runtime token. Recover a pending import with templates up --dir ${options.directory ?? "."} --import-job <job-id>, or rotate the local agent key and set DOME_TOKEN and DOME_GATEWAY_URL in .env.`);
|
|
131
132
|
}
|
|
132
|
-
if (options.start) { const compose = composeCommands(options); run("docker", compose.pull, target); run("docker", compose.up, target); }
|
|
133
|
+
if (options.start && !runtimeAgent.managed) { const compose = composeCommands(options); run("docker", compose.pull, target); run("docker", compose.up, target); }
|
|
134
|
+
if (runtimeAgent.managed) output(line("success", `Managed agent ${runtimeAgent.name} is provisioned in Dome; Dome owns its loop and triggers.`, options));
|
|
133
135
|
}
|
|
134
136
|
async function publish(options, output, fetchImplementation) { const target = path.resolve(process.cwd(), options.directory ?? "."); const manifest = readPublishManifest(target); const source = publishSource(target); const name = manifest.metadata.name; const [scope, slug] = name.split("/"); const archive = options.dryRun ? null : createArchive(source); output(line("info", `Checking ${name}@${manifest.metadata.version}`, options)); const payload = { name, version: manifest.metadata.version, title: manifest.metadata.title, summary: manifest.metadata.summary ?? "", sha256: archive?.sha256, size_bytes: archive?.buffer.length, source: { repository: manifest.metadata.source.repository, path: manifest.metadata.source.path, commit: source.commit, remote: source.remote, files: source.files } };
|
|
135
137
|
const remote = remoteTarget(options); let registry; let attempt;
|
package/src/generate.mjs
CHANGED
|
@@ -64,6 +64,30 @@ function quotaBlocks(quotas, agents) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function managedModelReference(manifest, name) {
|
|
68
|
+
if (manifest.pools.some((pool) => pool.name === name)) return `dome_llm_pool.${label(name)}.name`;
|
|
69
|
+
if (manifest.models.some((model) => model.name === name)) return `dome_llm_connection.${label(name)}.name`;
|
|
70
|
+
throw new Error(`Managed agent default_model ${name} must name a pool or model.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function agentAttributes(agent) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
if (agent.parent) lines.push(attr("parent", `dome_agent.${label(agent.parent)}.name`));
|
|
76
|
+
if (agent.metadata) lines.push(attr("metadata", `{ ${Object.entries(agent.metadata).map(([key, value]) => `${q(key)} = ${q(value)}`).join(", ")} }`));
|
|
77
|
+
for (const [key, resource] of [["allowed_pools", "dome_llm_pool"], ["allowed_models", "dome_llm_connection"], ["allowed_gateways", "dome_gateway"]]) {
|
|
78
|
+
if (agent[key] !== undefined) lines.push(attr(key, `[${agent[key].map((name) => `${resource}.${label(name)}.name`).join(", ")}]`));
|
|
79
|
+
}
|
|
80
|
+
if (agent.allowed_tools !== undefined) lines.push(attr("allowed_tools", `[${agent.allowed_tools.map(q).join(", ")}]`));
|
|
81
|
+
for (const key of ["suspended", "token_ttl", "interactive_access", "secrets_version"]) if (agent[key] !== undefined) lines.push(attr(key, typeof agent[key] === "string" ? q(agent[key]) : String(agent[key])));
|
|
82
|
+
if (agent.act_as) {
|
|
83
|
+
const values = Object.entries(agent.act_as).filter(([key]) => !["hmac_secret"].includes(key)).map(([key, value]) => `${key} = ${typeof value === "string" ? q(value) : String(value)}`);
|
|
84
|
+
lines.push(attr("act_as", `{ ${values.join(", ")} }`));
|
|
85
|
+
if (agent.act_as.hmac_secret) lines.push(attr("act_as_hmac_secret", expression(agent.act_as.hmac_secret)));
|
|
86
|
+
}
|
|
87
|
+
for (const key of ["actas_allowed_groups", "actas_allowed_emails", "actas_allowed_subjects"]) if (agent[key] !== undefined) lines.push(attr(key, `[${agent[key].map(q).join(", ")}]`));
|
|
88
|
+
return lines;
|
|
89
|
+
}
|
|
90
|
+
|
|
67
91
|
export function renderDomeTerraform(manifest) {
|
|
68
92
|
if (!manifest.gateways.length) throw new Error("template.yaml must declare at least one gateway.");
|
|
69
93
|
const blocks = [];
|
|
@@ -94,19 +118,11 @@ export function renderDomeTerraform(manifest) {
|
|
|
94
118
|
}
|
|
95
119
|
for (const agent of manifest.agents) {
|
|
96
120
|
const agentLabel = label(agent.name); const managed = agent.type === "managed"; const resourceType = managed ? "dome_managed_agent" : "dome_agent";
|
|
97
|
-
const lines = [attr("workspace_id", "var.workspace_id"), attr("name", q(agent.name)),
|
|
98
|
-
if (!managed && agent.act_as) {
|
|
99
|
-
const { method, required: actAsRequired, hmac_secret: hmacSecret, allowed_subjects: allowedSubjects } = agent.act_as;
|
|
100
|
-
if (!method) throw new Error(`Agent ${agent.name} act_as needs method.`);
|
|
101
|
-
lines.push(attr("act_as", `{ method = ${q(method)}, required = ${Boolean(actAsRequired)} }`));
|
|
102
|
-
if (hmacSecret) lines.push(attr("act_as_hmac_secret", expression(hmacSecret)));
|
|
103
|
-
if (allowedSubjects) lines.push(attr("actas_allowed_subjects", `[${allowedSubjects.map(q).join(", ")}]`));
|
|
104
|
-
}
|
|
121
|
+
const lines = [attr("workspace_id", "var.workspace_id"), attr("name", q(agent.name)), ...agentAttributes(agent)];
|
|
105
122
|
if (managed) {
|
|
106
|
-
if (!agent.system_prompt?.
|
|
107
|
-
lines.push(attr("system_prompt",
|
|
108
|
-
|
|
109
|
-
if (Object.keys(config).length) lines.push(attr("trigger_config", `jsonencode(${JSON.stringify(config)})`));
|
|
123
|
+
if (!agent.system_prompt?.content || !agent.default_model) throw new Error(`Managed agent ${agent.name} needs system_prompt and default_model.`);
|
|
124
|
+
lines.push(attr("system_prompt", "<<-PROMPT"), ...agent.system_prompt.content.split("\n").map((line) => line.trimEnd()), "PROMPT", attr("default_model", managedModelReference(manifest, agent.default_model)));
|
|
125
|
+
if (agent.trigger_config !== undefined) lines.push(attr("trigger_config", `jsonencode(${JSON.stringify(agent.trigger_config)})`));
|
|
110
126
|
}
|
|
111
127
|
blocks.push(block(resourceType, agentLabel, lines), ...rulesBlock(agent, resourceType, agentLabel));
|
|
112
128
|
if (!managed) for (const key of agent.keys ?? []) blocks.push(block("dome_agent_key", `${agentLabel}_${label(required(key, "name", `Agent ${agent.name} keys need a name.`))}`, [attr("workspace_id", "var.workspace_id"), attr("agent", `dome_agent.${agentLabel}.name`), attr("name", q(key.name))]));
|
package/src/manifest.mjs
CHANGED
|
@@ -19,10 +19,13 @@ function sourcePath(root, reference, label) {
|
|
|
19
19
|
function expandAgent(root, entry, index) {
|
|
20
20
|
if (!entry || typeof entry !== "object") throw new Error(`agents[${index}] must be an object or path.`);
|
|
21
21
|
if (entry.path === undefined) return entry;
|
|
22
|
-
if (Object.keys(entry).
|
|
23
|
-
|
|
22
|
+
if (Object.keys(entry).some((key) => key !== "name" && key !== "path")) throw new Error(`agents[${index}] may contain only name and path when path-backed.`);
|
|
23
|
+
if (typeof entry.name !== "string" || !entry.name.trim()) throw new Error(`agents[${index}].name is required when path-backed.`);
|
|
24
|
+
const file = sourcePath(root, entry.path, `agents[${index}]`);
|
|
25
|
+
const agent = parseYAML(file, `agent fragment ${entry.path}`);
|
|
24
26
|
if (!agent || typeof agent !== "object" || Array.isArray(agent)) throw new Error(`Agent fragment ${entry.path} must be an object.`);
|
|
25
|
-
|
|
27
|
+
if (agent.name !== undefined) throw new Error(`Agent fragment ${entry.path} must not declare name; template.yaml owns the agent identity.`);
|
|
28
|
+
return { ...agent, name: entry.name, __source_directory: path.dirname(file) };
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
function expandFilters(root, filters) {
|
|
@@ -67,6 +70,16 @@ function normalizeRuleFiles(root, agent, index) {
|
|
|
67
70
|
return { ...agent, rules: { ...agent.rules, files: normalized } };
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
function normalizeManagedAgent(root, agent, index) {
|
|
74
|
+
if (agent.type !== "managed") return agent;
|
|
75
|
+
const prompt = agent.system_prompt;
|
|
76
|
+
if (!prompt || typeof prompt !== "object" || Array.isArray(prompt)) throw new Error(`agents[${index}].system_prompt must declare path or inline.`);
|
|
77
|
+
const hasPath = typeof prompt.path === "string";
|
|
78
|
+
const hasInline = typeof prompt.inline === "string";
|
|
79
|
+
if (hasPath === hasInline) throw new Error(`agents[${index}].system_prompt must declare exactly one of path or inline.`);
|
|
80
|
+
return { ...agent, system_prompt: { content: hasPath ? fs.readFileSync(sourcePath(root, prompt.path, `agents[${index}].system_prompt`), "utf8") : prompt.inline, source: hasPath ? prompt.path : "inline" } };
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
function named(items, label) {
|
|
71
84
|
const names = new Set();
|
|
72
85
|
for (const item of items ?? []) {
|
|
@@ -84,15 +97,23 @@ export function readManifest(templateDirectory) {
|
|
|
84
97
|
if (manifest.apiVersion !== "templates.domesystems.ai/v1" || manifest.kind !== "Template") throw new Error("template.yaml must declare apiVersion templates.domesystems.ai/v1 and kind Template.");
|
|
85
98
|
if (!Array.isArray(manifest.agents) || manifest.agents.length === 0) throw new Error("template.yaml must declare at least one agent.");
|
|
86
99
|
const agents = manifest.agents.map((entry, index) => {
|
|
87
|
-
const
|
|
100
|
+
const expanded = expandAgent(root, entry, index);
|
|
101
|
+
const agentRoot = expanded.__source_directory ?? root;
|
|
102
|
+
const agent = normalizeManagedAgent(agentRoot, normalizeRuleFiles(agentRoot, expanded, index), index);
|
|
88
103
|
if (typeof agent.name !== "string" || !agent.name.trim()) throw new Error(`agents[${index}].name is required.`);
|
|
89
104
|
const type = agent.type;
|
|
90
105
|
if (!type) throw new Error(`agents[${index}].type is required.`);
|
|
91
106
|
if (!["external", "managed"].includes(type)) throw new Error(`agents[${index}].type must be external or managed.`);
|
|
92
107
|
if (type === "managed" && agent.keys?.length) throw new Error(`agents[${index}] is managed and cannot declare keys.`);
|
|
93
|
-
|
|
108
|
+
const { __source_directory, ...cleanAgent } = agent;
|
|
109
|
+
return { ...cleanAgent, type };
|
|
94
110
|
});
|
|
95
111
|
const filters = normalizeFilterConfigs(root, expandFilters(root, manifest.filters));
|
|
112
|
+
if (manifest.triggers !== undefined) throw new Error("triggers is not a top-level template field; declare trigger_config on the managed agent it controls.");
|
|
113
|
+
for (const agent of agents) {
|
|
114
|
+
if (agent.type === "managed" && (!agent.system_prompt || !agent.default_model)) throw new Error(`Managed agent ${agent.name} needs system_prompt and default_model.`);
|
|
115
|
+
if (agent.type === "external" && (agent.system_prompt !== undefined || agent.default_model !== undefined || agent.trigger_config !== undefined)) throw new Error(`External agent ${agent.name} cannot declare managed-agent loop fields.`);
|
|
116
|
+
}
|
|
96
117
|
named(agents, "agents"); named(manifest.gateways ?? [], "gateways"); named(manifest.tool_connections ?? [], "tool_connections"); named(manifest.models ?? [], "models"); named(manifest.pools ?? [], "pools"); named(filters, "filters");
|
|
97
118
|
return { ...manifest, variables: manifest.variables ?? {}, gateways: manifest.gateways ?? [], tool_connections: manifest.tool_connections ?? [], models: manifest.models ?? [], pools: manifest.pools ?? [], filters, quotas: manifest.quotas ?? [], agents };
|
|
98
119
|
}
|