@cargo-ai/cdk 0.1.1
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/README.md +187 -0
- package/build/src/core.d.ts +83 -0
- package/build/src/core.d.ts.map +1 -0
- package/build/src/core.js +205 -0
- package/build/src/deploy/apply.d.ts +55 -0
- package/build/src/deploy/apply.d.ts.map +1 -0
- package/build/src/deploy/apply.js +219 -0
- package/build/src/deploy/audit.d.ts +14 -0
- package/build/src/deploy/audit.d.ts.map +1 -0
- package/build/src/deploy/audit.js +25 -0
- package/build/src/deploy/compile.d.ts +36 -0
- package/build/src/deploy/compile.d.ts.map +1 -0
- package/build/src/deploy/compile.js +99 -0
- package/build/src/deploy/destroy.d.ts +20 -0
- package/build/src/deploy/destroy.d.ts.map +1 -0
- package/build/src/deploy/destroy.js +153 -0
- package/build/src/deploy/executors.live.d.ts +4 -0
- package/build/src/deploy/executors.live.d.ts.map +1 -0
- package/build/src/deploy/executors.live.js +623 -0
- package/build/src/deploy/import.d.ts +13 -0
- package/build/src/deploy/import.d.ts.map +1 -0
- package/build/src/deploy/import.js +87 -0
- package/build/src/deploy/index.d.ts +39 -0
- package/build/src/deploy/index.d.ts.map +1 -0
- package/build/src/deploy/index.js +51 -0
- package/build/src/deploy/load.d.ts +7 -0
- package/build/src/deploy/load.d.ts.map +1 -0
- package/build/src/deploy/load.js +58 -0
- package/build/src/deploy/lock.d.ts +10 -0
- package/build/src/deploy/lock.d.ts.map +1 -0
- package/build/src/deploy/lock.js +101 -0
- package/build/src/deploy/plan.d.ts +3 -0
- package/build/src/deploy/plan.d.ts.map +1 -0
- package/build/src/deploy/plan.js +39 -0
- package/build/src/deploy/readers.live.d.ts +4 -0
- package/build/src/deploy/readers.live.d.ts.map +1 -0
- package/build/src/deploy/readers.live.js +273 -0
- package/build/src/deploy/refresh.d.ts +32 -0
- package/build/src/deploy/refresh.d.ts.map +1 -0
- package/build/src/deploy/refresh.js +76 -0
- package/build/src/deploy/state.d.ts +23 -0
- package/build/src/deploy/state.d.ts.map +1 -0
- package/build/src/deploy/state.js +107 -0
- package/build/src/index.d.ts +30 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/src/index.js +30 -0
- package/build/src/refs.d.ts +28 -0
- package/build/src/refs.d.ts.map +1 -0
- package/build/src/refs.js +34 -0
- package/build/src/resources/actions.d.ts +31 -0
- package/build/src/resources/actions.d.ts.map +1 -0
- package/build/src/resources/actions.js +44 -0
- package/build/src/resources/agent.d.ts +71 -0
- package/build/src/resources/agent.d.ts.map +1 -0
- package/build/src/resources/agent.js +96 -0
- package/build/src/resources/app.d.ts +18 -0
- package/build/src/resources/app.d.ts.map +1 -0
- package/build/src/resources/app.js +33 -0
- package/build/src/resources/bundle.d.ts +15 -0
- package/build/src/resources/bundle.d.ts.map +1 -0
- package/build/src/resources/bundle.js +61 -0
- package/build/src/resources/connector.d.ts +30 -0
- package/build/src/resources/connector.d.ts.map +1 -0
- package/build/src/resources/connector.js +41 -0
- package/build/src/resources/context.d.ts +15 -0
- package/build/src/resources/context.d.ts.map +1 -0
- package/build/src/resources/context.js +58 -0
- package/build/src/resources/file.d.ts +15 -0
- package/build/src/resources/file.d.ts.map +1 -0
- package/build/src/resources/file.js +35 -0
- package/build/src/resources/folder.d.ts +20 -0
- package/build/src/resources/folder.d.ts.map +1 -0
- package/build/src/resources/folder.js +28 -0
- package/build/src/resources/mcpServer.d.ts +25 -0
- package/build/src/resources/mcpServer.d.ts.map +1 -0
- package/build/src/resources/mcpServer.js +34 -0
- package/build/src/resources/model.d.ts +34 -0
- package/build/src/resources/model.d.ts.map +1 -0
- package/build/src/resources/model.js +43 -0
- package/build/src/resources/play.d.ts +35 -0
- package/build/src/resources/play.d.ts.map +1 -0
- package/build/src/resources/play.js +49 -0
- package/build/src/resources/tool.d.ts +40 -0
- package/build/src/resources/tool.d.ts.map +1 -0
- package/build/src/resources/tool.js +36 -0
- package/build/src/resources/worker.d.ts +18 -0
- package/build/src/resources/worker.d.ts.map +1 -0
- package/build/src/resources/worker.js +40 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +71 -0
- package/templates/blank/README.md +24 -0
- package/templates/blank/cargo.ts +44 -0
- package/templates/blank/package.json +17 -0
- package/templates/blank/tsconfig.json +14 -0
- package/templates/full/README.md +69 -0
- package/templates/full/agents/enricher.ts +13 -0
- package/templates/full/agents/sdr.ts +28 -0
- package/templates/full/apps/dashboard/index.html +12 -0
- package/templates/full/apps/dashboard/package-lock.json +1080 -0
- package/templates/full/apps/dashboard/package.json +15 -0
- package/templates/full/apps/dashboard/src/main.tsx +12 -0
- package/templates/full/apps/dashboard/tsconfig.json +13 -0
- package/templates/full/apps/dashboard.ts +9 -0
- package/templates/full/connectors/hubspot.ts +12 -0
- package/templates/full/connectors/openai.ts +9 -0
- package/templates/full/context/context.ts +7 -0
- package/templates/full/context/icp.md +22 -0
- package/templates/full/context/personas/head-of-growth.md +26 -0
- package/templates/full/files/playbook.md +19 -0
- package/templates/full/files/playbook.ts +8 -0
- package/templates/full/folders/crm.ts +14 -0
- package/templates/full/mcp/crm.ts +15 -0
- package/templates/full/models/contacts.ts +15 -0
- package/templates/full/package.json +17 -0
- package/templates/full/plays/onboarding.ts +41 -0
- package/templates/full/tools/enrich.ts +34 -0
- package/templates/full/tsconfig.json +17 -0
- package/templates/full/workers/webhook/index.js +12 -0
- package/templates/full/workers/webhook/manifest.json +1 -0
- package/templates/full/workers/webhook/package-lock.json +12 -0
- package/templates/full/workers/webhook/package.json +6 -0
- package/templates/full/workers/webhook.ts +13 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
// Live executors — bind the apply engine's `Executors` contract to @cargo-ai/api.
|
|
2
|
+
// Each executor receives the token-resolved spec (real uuids in place of tokens)
|
|
3
|
+
// and the prior state entry, and decides: prior uuid → update; else (connector/
|
|
4
|
+
// model, which have addressable slugs) exists-by-slug → adopt; else create.
|
|
5
|
+
//
|
|
6
|
+
// This is the only file that imports @cargo-ai/api. Workspace is implicit — the
|
|
7
|
+
// authenticated client already sends the selected-workspace-uuid header.
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { isAbsolute, resolve } from "node:path";
|
|
10
|
+
import { readBundle } from "../resources/bundle.js";
|
|
11
|
+
const str = (v) => v;
|
|
12
|
+
const optStr = (v) => v === undefined || v === null ? undefined : v;
|
|
13
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
14
|
+
// Poll a hosting deployment until its build finishes; throw if it fails.
|
|
15
|
+
async function waitForDeployment(api, uuid, attemptsLeft = 150) {
|
|
16
|
+
const { deployment } = await api.hosting.deployment.get(uuid);
|
|
17
|
+
if (deployment.status === "success")
|
|
18
|
+
return;
|
|
19
|
+
if (deployment.status === "error" || deployment.status === "cancelled") {
|
|
20
|
+
throw new Error(`deployment ${uuid} ${deployment.status}`);
|
|
21
|
+
}
|
|
22
|
+
if (attemptsLeft <= 0) {
|
|
23
|
+
throw new Error(`deployment ${uuid} did not finish building in time`);
|
|
24
|
+
}
|
|
25
|
+
await sleep(2000);
|
|
26
|
+
return waitForDeployment(api, uuid, attemptsLeft - 1);
|
|
27
|
+
}
|
|
28
|
+
export function liveExecutors(api) {
|
|
29
|
+
// Resolve the dataset uuid auto-created alongside a connector — needed when a
|
|
30
|
+
// connector is ADOPTED (create returns it directly; list/adopt does not).
|
|
31
|
+
async function connectorDatasetUuid(connectorUuid) {
|
|
32
|
+
const { datasets } = await api.storage.dataset.all();
|
|
33
|
+
const match = datasets.find((d) => d.kind === "connector" && d.connectorUuid === connectorUuid);
|
|
34
|
+
if (match === undefined) {
|
|
35
|
+
throw new Error(`could not resolve the dataset for adopted connector ${connectorUuid}`);
|
|
36
|
+
}
|
|
37
|
+
return match.uuid;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
async connector(spec, prior) {
|
|
41
|
+
const slug = str(spec["slug"]);
|
|
42
|
+
const integrationSlug = str(spec["integrationSlug"]);
|
|
43
|
+
const config = spec["config"];
|
|
44
|
+
const rateLimit = spec["rateLimit"];
|
|
45
|
+
const cacheTtlMilliseconds = spec["cacheTtlMilliseconds"];
|
|
46
|
+
if (prior !== undefined) {
|
|
47
|
+
await api.connection.connector.update({
|
|
48
|
+
uuid: prior.uuid,
|
|
49
|
+
name: str(spec["name"]),
|
|
50
|
+
config: config ?? null,
|
|
51
|
+
rateLimit: rateLimit ?? null,
|
|
52
|
+
cacheTtlMilliseconds: cacheTtlMilliseconds ?? null,
|
|
53
|
+
});
|
|
54
|
+
// Preserve the adopted marker so destroy keeps releasing (not deleting).
|
|
55
|
+
const out = {
|
|
56
|
+
uuid: prior.uuid,
|
|
57
|
+
datasetUuid: prior.outputs["datasetUuid"],
|
|
58
|
+
};
|
|
59
|
+
if (prior.outputs["adopted"] !== undefined) {
|
|
60
|
+
out["adopted"] = prior.outputs["adopted"];
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
const adopt = spec["adopt"] === true ||
|
|
65
|
+
(await api.connection.connector.existsBySlug({ slug })).exists;
|
|
66
|
+
if (adopt) {
|
|
67
|
+
const { connectors } = await api.connection.connector.list({
|
|
68
|
+
integrationSlug,
|
|
69
|
+
});
|
|
70
|
+
const found = connectors.find((c) => c.slug === slug);
|
|
71
|
+
if (found === undefined) {
|
|
72
|
+
throw new Error(`connector "${slug}" reported to exist but was not found`);
|
|
73
|
+
}
|
|
74
|
+
// Mark as adopted — the CDK linked an existing connector it didn't
|
|
75
|
+
// create, so destroy must release it rather than delete it.
|
|
76
|
+
return {
|
|
77
|
+
uuid: found.uuid,
|
|
78
|
+
datasetUuid: await connectorDatasetUuid(found.uuid),
|
|
79
|
+
adopted: "true",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const created = await api.connection.connector.create({
|
|
83
|
+
name: str(spec["name"]),
|
|
84
|
+
slug,
|
|
85
|
+
integrationSlug,
|
|
86
|
+
config,
|
|
87
|
+
rateLimit,
|
|
88
|
+
cacheTtlMilliseconds,
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
uuid: created.connector.uuid,
|
|
92
|
+
datasetUuid: created.dataset.uuid,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
async model(spec, prior) {
|
|
96
|
+
const slug = str(spec["slug"]);
|
|
97
|
+
const datasetUuid = str(spec["datasetUuid"]);
|
|
98
|
+
const schedule = spec["schedule"];
|
|
99
|
+
if (prior !== undefined) {
|
|
100
|
+
// No extractorSlug — a model's extractor is immutable post-create
|
|
101
|
+
// (it's not part of UpdateModelPayload).
|
|
102
|
+
await api.storage.model.update({
|
|
103
|
+
uuid: prior.uuid,
|
|
104
|
+
name: str(spec["name"]),
|
|
105
|
+
description: optStr(spec["description"]) ?? null,
|
|
106
|
+
config: spec["config"] ?? {},
|
|
107
|
+
schedule: schedule ?? null,
|
|
108
|
+
});
|
|
109
|
+
const out = { uuid: prior.uuid };
|
|
110
|
+
if (prior.outputs["adopted"] !== undefined) {
|
|
111
|
+
out["adopted"] = prior.outputs["adopted"];
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
const exists = (await api.storage.model.existsBySlug({ slug, datasetUuid })).exists;
|
|
116
|
+
if (exists) {
|
|
117
|
+
const { models } = await api.storage.model.all();
|
|
118
|
+
const found = models.find((m) => m.slug === slug && m.datasetUuid === datasetUuid);
|
|
119
|
+
if (found === undefined) {
|
|
120
|
+
throw new Error(`model "${slug}" reported to exist but was not found`);
|
|
121
|
+
}
|
|
122
|
+
// Adopted an existing model — release on destroy, don't delete.
|
|
123
|
+
return { uuid: found.uuid, adopted: "true" };
|
|
124
|
+
}
|
|
125
|
+
const created = await api.storage.model.create({
|
|
126
|
+
slug,
|
|
127
|
+
name: str(spec["name"]),
|
|
128
|
+
description: optStr(spec["description"]),
|
|
129
|
+
datasetUuid,
|
|
130
|
+
extractorSlug: str(spec["extractorSlug"]),
|
|
131
|
+
config: spec["config"] ?? {},
|
|
132
|
+
schedule,
|
|
133
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
134
|
+
});
|
|
135
|
+
return { uuid: created.model.uuid };
|
|
136
|
+
},
|
|
137
|
+
async play(spec, prior) {
|
|
138
|
+
// Play has no slug — identity is purely the prior state uuid.
|
|
139
|
+
const base = {
|
|
140
|
+
name: str(spec["name"]),
|
|
141
|
+
description: optStr(spec["description"]),
|
|
142
|
+
modelUuid: str(spec["modelUuid"]),
|
|
143
|
+
changeKinds: spec["changeKinds"],
|
|
144
|
+
runCreationRule: spec["runCreationRule"],
|
|
145
|
+
filter: spec["filter"],
|
|
146
|
+
sort: spec["sort"],
|
|
147
|
+
limit: spec["limit"],
|
|
148
|
+
trackingColumnSlugs: spec["trackingColumnSlugs"],
|
|
149
|
+
schedule: spec["schedule"],
|
|
150
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
151
|
+
};
|
|
152
|
+
let uuid;
|
|
153
|
+
let workflowUuid;
|
|
154
|
+
if (prior !== undefined) {
|
|
155
|
+
// play.update rejects modelUuid (a play's model is fixed at creation) —
|
|
156
|
+
// strip it from the update payload.
|
|
157
|
+
const { modelUuid: _modelUuid, ...updateBase } = base;
|
|
158
|
+
await api.orchestration.play.update({
|
|
159
|
+
uuid: prior.uuid,
|
|
160
|
+
...updateBase,
|
|
161
|
+
});
|
|
162
|
+
uuid = prior.uuid;
|
|
163
|
+
workflowUuid = prior.outputs["workflowUuid"];
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const created = await api.orchestration.play.create(base);
|
|
167
|
+
uuid = created.play.uuid;
|
|
168
|
+
workflowUuid = created.play.workflowUuid;
|
|
169
|
+
}
|
|
170
|
+
// Deploy the play's backing workflow graph as its release, if provided.
|
|
171
|
+
const nodes = spec["nodes"];
|
|
172
|
+
if (nodes !== undefined && workflowUuid !== undefined) {
|
|
173
|
+
await api.orchestration.draftRelease.deploy({
|
|
174
|
+
workflowUuid,
|
|
175
|
+
nodes: nodes,
|
|
176
|
+
formFields: (spec["formFields"] ?? null),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const result = { uuid };
|
|
180
|
+
if (workflowUuid !== undefined)
|
|
181
|
+
result["workflowUuid"] = workflowUuid;
|
|
182
|
+
return result;
|
|
183
|
+
},
|
|
184
|
+
async agent(spec, prior) {
|
|
185
|
+
const connectorUuid = optStr(spec["connectorUuid"]);
|
|
186
|
+
const languageModelSlug = optStr(spec["languageModelSlug"]);
|
|
187
|
+
if (connectorUuid === undefined || languageModelSlug === undefined) {
|
|
188
|
+
throw new Error(`agent "${str(spec["slug"])}" needs a connectorUuid + languageModel to deploy.`);
|
|
189
|
+
}
|
|
190
|
+
// The release needs the integration slug — resolve it from the connector.
|
|
191
|
+
const integrationSlug = (await api.connection.connector.get({ uuid: connectorUuid })).connector.integrationSlug;
|
|
192
|
+
// ── phase A: ensure the agent row exists ──
|
|
193
|
+
// NOTE: agent + release are two calls. A crash between them orphans the
|
|
194
|
+
// agent (no slug to re-find it). Acceptable for v1; a partial-persist hook
|
|
195
|
+
// would close the window.
|
|
196
|
+
let agentUuid;
|
|
197
|
+
if (prior !== undefined) {
|
|
198
|
+
agentUuid = prior.uuid;
|
|
199
|
+
await api.ai.agent.update({
|
|
200
|
+
uuid: agentUuid,
|
|
201
|
+
name: str(spec["name"]),
|
|
202
|
+
icon: spec["icon"],
|
|
203
|
+
description: optStr(spec["description"]) ?? null,
|
|
204
|
+
triggers: mapAgentTriggers(spec["triggers"]),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const created = await api.ai.agent.create({
|
|
209
|
+
name: str(spec["name"]),
|
|
210
|
+
icon: spec["icon"],
|
|
211
|
+
description: optStr(spec["description"]),
|
|
212
|
+
triggers: mapAgentTriggers(spec["triggers"]),
|
|
213
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
214
|
+
});
|
|
215
|
+
agentUuid = created.agent.uuid;
|
|
216
|
+
}
|
|
217
|
+
// ── phase B: deploy the config release (full desired state) ──
|
|
218
|
+
// No explicit version — the backend auto-bumps (an explicit wrong value
|
|
219
|
+
// throws invalidReleaseVersion).
|
|
220
|
+
await api.ai.release.deployDraft({
|
|
221
|
+
agentUuid,
|
|
222
|
+
integrationSlug,
|
|
223
|
+
connectorUuid,
|
|
224
|
+
languageModelSlug,
|
|
225
|
+
systemPrompt: optStr(spec["systemPrompt"]),
|
|
226
|
+
withReasoning: spec["withReasoning"],
|
|
227
|
+
temperature: spec["temperature"],
|
|
228
|
+
maxSteps: spec["maxSteps"],
|
|
229
|
+
capabilities: spec["capabilities"],
|
|
230
|
+
mcpClients: spec["mcpClients"],
|
|
231
|
+
suggestedActions: [],
|
|
232
|
+
resources: mapModelResources(spec["models"]),
|
|
233
|
+
actions: [
|
|
234
|
+
...mapTools(spec["tools"]),
|
|
235
|
+
...mapSubAgents(spec["subAgents"]),
|
|
236
|
+
...mapConnectorActions(spec["connectorActions"]),
|
|
237
|
+
],
|
|
238
|
+
output: spec["output"],
|
|
239
|
+
heartbeat: spec["heartbeat"],
|
|
240
|
+
evaluator: spec["evaluator"],
|
|
241
|
+
});
|
|
242
|
+
return { uuid: agentUuid };
|
|
243
|
+
},
|
|
244
|
+
async folder(spec, prior) {
|
|
245
|
+
const name = str(spec["name"]);
|
|
246
|
+
const folderKind = str(spec["folderKind"]);
|
|
247
|
+
const parentUuid = optStr(spec["parentUuid"]);
|
|
248
|
+
if (prior !== undefined) {
|
|
249
|
+
await api.workspaceManagement.folder.update({
|
|
250
|
+
uuid: prior.uuid,
|
|
251
|
+
name,
|
|
252
|
+
emojiSlug: str(spec["emojiSlug"]),
|
|
253
|
+
});
|
|
254
|
+
return { uuid: prior.uuid };
|
|
255
|
+
}
|
|
256
|
+
// Adopt by (parentUuid, name, kind) — folder names are unique per location.
|
|
257
|
+
// Mark adopted: a folder matched by name is shared workspace structure the
|
|
258
|
+
// CDK linked but didn't create, so destroy must release (not delete) it.
|
|
259
|
+
const { folders } = await api.workspaceManagement.folder.all();
|
|
260
|
+
const found = folders.find((f) => f.name === name &&
|
|
261
|
+
f.kind === folderKind &&
|
|
262
|
+
(f.parentUuid ?? undefined) === parentUuid);
|
|
263
|
+
if (found !== undefined) {
|
|
264
|
+
const out = {
|
|
265
|
+
uuid: found.uuid,
|
|
266
|
+
adopted: "true",
|
|
267
|
+
};
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
const created = await api.workspaceManagement.folder.create({
|
|
271
|
+
name,
|
|
272
|
+
emojiSlug: str(spec["emojiSlug"]),
|
|
273
|
+
kind: folderKind,
|
|
274
|
+
parentUuid,
|
|
275
|
+
});
|
|
276
|
+
return { uuid: created.folder.uuid };
|
|
277
|
+
},
|
|
278
|
+
async tool(spec, prior) {
|
|
279
|
+
const nodes = spec["nodes"];
|
|
280
|
+
const formFields = spec["formFields"];
|
|
281
|
+
const triggers = mapToolTriggers(spec["triggers"]);
|
|
282
|
+
const publicForm = spec["publicForm"];
|
|
283
|
+
let toolUuid;
|
|
284
|
+
let workflowUuid;
|
|
285
|
+
if (prior !== undefined) {
|
|
286
|
+
toolUuid = prior.uuid;
|
|
287
|
+
workflowUuid = prior.outputs["workflowUuid"];
|
|
288
|
+
await api.orchestration.tool.update({
|
|
289
|
+
uuid: toolUuid,
|
|
290
|
+
name: str(spec["name"]),
|
|
291
|
+
icon: spec["icon"],
|
|
292
|
+
description: optStr(spec["description"]) ?? null,
|
|
293
|
+
triggers,
|
|
294
|
+
publicForm,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const created = await api.orchestration.tool.create({
|
|
299
|
+
name: str(spec["name"]),
|
|
300
|
+
icon: spec["icon"],
|
|
301
|
+
description: optStr(spec["description"]),
|
|
302
|
+
triggers,
|
|
303
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
304
|
+
});
|
|
305
|
+
toolUuid = created.tool.uuid;
|
|
306
|
+
workflowUuid = created.tool.workflowUuid;
|
|
307
|
+
// publicForm isn't a create param — apply it via update when set.
|
|
308
|
+
if (publicForm !== null && publicForm !== undefined) {
|
|
309
|
+
await api.orchestration.tool.update({ uuid: toolUuid, publicForm });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Deploy the backing workflow's graph as the tool's release.
|
|
313
|
+
await api.orchestration.draftRelease.deploy({
|
|
314
|
+
workflowUuid,
|
|
315
|
+
nodes,
|
|
316
|
+
formFields,
|
|
317
|
+
});
|
|
318
|
+
return { uuid: toolUuid, workflowUuid };
|
|
319
|
+
},
|
|
320
|
+
async mcpServer(spec, prior) {
|
|
321
|
+
const base = {
|
|
322
|
+
name: str(spec["name"]),
|
|
323
|
+
description: optStr(spec["description"]),
|
|
324
|
+
actions: [
|
|
325
|
+
...mapTools(spec["tools"]),
|
|
326
|
+
...mapSubAgents(spec["subAgents"]),
|
|
327
|
+
...mapConnectorActions(spec["connectorActions"]),
|
|
328
|
+
],
|
|
329
|
+
resources: mapModelResources(spec["models"]),
|
|
330
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
331
|
+
};
|
|
332
|
+
if (prior !== undefined) {
|
|
333
|
+
await api.ai.mcpServer.update({ uuid: prior.uuid, ...base });
|
|
334
|
+
return { uuid: prior.uuid };
|
|
335
|
+
}
|
|
336
|
+
const created = await api.ai.mcpServer.create(base);
|
|
337
|
+
return { uuid: created.mcpServer.uuid };
|
|
338
|
+
},
|
|
339
|
+
async worker(spec, prior) {
|
|
340
|
+
const files = readBundle(str(spec["path"]));
|
|
341
|
+
const slug = str(spec["slug"]);
|
|
342
|
+
let uuid;
|
|
343
|
+
let adopted = false;
|
|
344
|
+
if (prior !== undefined) {
|
|
345
|
+
uuid = prior.uuid;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
// Recover our own slot if a prior deploy created it but crashed before
|
|
349
|
+
// persisting (e.g. a failed build). The slug is CDK-chosen, so a worker
|
|
350
|
+
// with the same slug is *probably* ours — reuse it instead of
|
|
351
|
+
// conflicting. We can't be certain it isn't a foreign same-slug worker,
|
|
352
|
+
// so mark it adopted: destroy will release (not delete) it.
|
|
353
|
+
const { workers } = await api.hosting.worker.list();
|
|
354
|
+
const existing = workers.find((w) => w.slug === slug);
|
|
355
|
+
if (existing !== undefined) {
|
|
356
|
+
uuid = existing.uuid;
|
|
357
|
+
adopted = true;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
uuid = (await api.hosting.worker.create({
|
|
361
|
+
name: str(spec["name"]),
|
|
362
|
+
slug,
|
|
363
|
+
description: optStr(spec["description"]) ?? null,
|
|
364
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
365
|
+
})).worker.uuid;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const { deployment } = await api.hosting.deployment.create({
|
|
369
|
+
kind: "worker",
|
|
370
|
+
workerUuid: uuid,
|
|
371
|
+
files,
|
|
372
|
+
});
|
|
373
|
+
// Wait for the build to succeed before promoting (don't promote a
|
|
374
|
+
// still-building or failed deployment).
|
|
375
|
+
await waitForDeployment(api, deployment.uuid);
|
|
376
|
+
const { deployment: promoted } = await api.hosting.deployment.promote({
|
|
377
|
+
uuid: deployment.uuid,
|
|
378
|
+
});
|
|
379
|
+
const out = {
|
|
380
|
+
uuid,
|
|
381
|
+
url: promoted.url,
|
|
382
|
+
};
|
|
383
|
+
if (adopted)
|
|
384
|
+
out["adopted"] = "true";
|
|
385
|
+
return out;
|
|
386
|
+
},
|
|
387
|
+
async app(spec, prior) {
|
|
388
|
+
const files = readBundle(str(spec["path"]));
|
|
389
|
+
const slug = str(spec["slug"]);
|
|
390
|
+
let uuid;
|
|
391
|
+
let adopted = false;
|
|
392
|
+
if (prior !== undefined) {
|
|
393
|
+
uuid = prior.uuid;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
// Recover our own slot if a prior deploy created it but crashed before
|
|
397
|
+
// persisting — same-slug app is *probably* ours (the slug is
|
|
398
|
+
// CDK-chosen). We can't be certain, so mark it adopted: destroy will
|
|
399
|
+
// release (not delete) it.
|
|
400
|
+
const { apps } = await api.hosting.app.list();
|
|
401
|
+
const existing = apps.find((a) => a.slug === slug);
|
|
402
|
+
if (existing !== undefined) {
|
|
403
|
+
uuid = existing.uuid;
|
|
404
|
+
adopted = true;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
uuid = (await api.hosting.app.create({
|
|
408
|
+
name: str(spec["name"]),
|
|
409
|
+
slug,
|
|
410
|
+
description: optStr(spec["description"]),
|
|
411
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
412
|
+
})).app.uuid;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const { deployment } = await api.hosting.deployment.create({
|
|
416
|
+
kind: "app",
|
|
417
|
+
appUuid: uuid,
|
|
418
|
+
files,
|
|
419
|
+
});
|
|
420
|
+
// Wait for the build to succeed before promoting (don't promote a
|
|
421
|
+
// still-building or failed deployment).
|
|
422
|
+
await waitForDeployment(api, deployment.uuid);
|
|
423
|
+
const { deployment: promoted } = await api.hosting.deployment.promote({
|
|
424
|
+
uuid: deployment.uuid,
|
|
425
|
+
});
|
|
426
|
+
const out = {
|
|
427
|
+
uuid,
|
|
428
|
+
url: promoted.url,
|
|
429
|
+
};
|
|
430
|
+
if (adopted)
|
|
431
|
+
out["adopted"] = "true";
|
|
432
|
+
return out;
|
|
433
|
+
},
|
|
434
|
+
async file(spec, prior) {
|
|
435
|
+
const name = str(spec["name"]);
|
|
436
|
+
const folderUuid = optStr(spec["folderUuid"]);
|
|
437
|
+
const contentHash = str(spec["contentHash"]);
|
|
438
|
+
// Uploads the local file's current bytes and reconciles the new uuid by
|
|
439
|
+
// its s3Filename. Cargo mints a fresh uuid on every upload, so this is the
|
|
440
|
+
// only way to get content into a file — both for create and for replace.
|
|
441
|
+
const uploadContent = async () => {
|
|
442
|
+
const path = str(spec["path"]);
|
|
443
|
+
const abs = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
444
|
+
const upload = new File([readFileSync(abs)], name);
|
|
445
|
+
// The upload client is fire-and-forget (it resolves before the request
|
|
446
|
+
// finishes and reports the result through callbacks), so wait on the
|
|
447
|
+
// callback — not the returned promise — for the real completion signal.
|
|
448
|
+
// onComplete now carries the created uuid; reconcile by s3Filename only
|
|
449
|
+
// as a fallback for older clients that didn't surface it.
|
|
450
|
+
const created = await new Promise((complete, fail) => {
|
|
451
|
+
void api.content.file.upload({
|
|
452
|
+
file: upload,
|
|
453
|
+
folderUuid,
|
|
454
|
+
onProgress: () => { },
|
|
455
|
+
onComplete: (p) => {
|
|
456
|
+
complete({ uuid: p.uuid, s3Filename: p.s3Filename });
|
|
457
|
+
},
|
|
458
|
+
onError: () => {
|
|
459
|
+
fail(new Error(`upload of file "${name}" failed`));
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
if (created.uuid !== undefined)
|
|
464
|
+
return created.uuid;
|
|
465
|
+
const { files } = await api.content.file.all(folderUuid === undefined ? undefined : { folderUuid });
|
|
466
|
+
const found = files.find((f) => f.s3Filename === created.s3Filename);
|
|
467
|
+
if (found === undefined) {
|
|
468
|
+
throw new Error(`uploaded file "${name}" was not found after upload`);
|
|
469
|
+
}
|
|
470
|
+
return found.uuid;
|
|
471
|
+
};
|
|
472
|
+
if (prior !== undefined) {
|
|
473
|
+
if (prior.outputs["contentHash"] === contentHash) {
|
|
474
|
+
// Content unchanged — only name/folder can drift, so reconcile that
|
|
475
|
+
// metadata in place and keep the same uuid.
|
|
476
|
+
await api.content.file.update({
|
|
477
|
+
uuid: prior.uuid,
|
|
478
|
+
name,
|
|
479
|
+
folderUuid: folderUuid === undefined ? null : folderUuid,
|
|
480
|
+
});
|
|
481
|
+
return { uuid: prior.uuid, contentHash };
|
|
482
|
+
}
|
|
483
|
+
// Content changed. Files are immutable (no replace endpoint), so upload
|
|
484
|
+
// a fresh file and delete the old one. The uuid changes; any resource
|
|
485
|
+
// referencing this file by token resolves to the new uuid on this same
|
|
486
|
+
// apply run (apply runs in topological order from current outputs).
|
|
487
|
+
const uuid = await uploadContent();
|
|
488
|
+
await api.content.file.remove(prior.uuid);
|
|
489
|
+
return { uuid, contentHash };
|
|
490
|
+
}
|
|
491
|
+
const uuid = await uploadContent();
|
|
492
|
+
return { uuid, contentHash };
|
|
493
|
+
},
|
|
494
|
+
// The workspace's single context repository. Writes each declared file into
|
|
495
|
+
// the repo via the runtime, only when its content differs from what's live.
|
|
496
|
+
// Additive: files removed from code are left in place.
|
|
497
|
+
async context(spec, _prior) {
|
|
498
|
+
const files = (spec["files"] ?? {});
|
|
499
|
+
const { repository } = await api.context.repository.get();
|
|
500
|
+
for (const [path, content] of Object.entries(files)) {
|
|
501
|
+
let current;
|
|
502
|
+
try {
|
|
503
|
+
current = (await api.context.runtime.read({ path })).content;
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
current = undefined; // new file — read 404s
|
|
507
|
+
}
|
|
508
|
+
if (current !== content) {
|
|
509
|
+
await api.context.runtime.write({
|
|
510
|
+
path,
|
|
511
|
+
content,
|
|
512
|
+
commitMessage: `cdk: sync ${path}`,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return { uuid: repository.uuid };
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
// Tolerate a missing field (older/partial spec) by treating it as an empty list.
|
|
521
|
+
function arr(value) {
|
|
522
|
+
return value === undefined || value === null ? [] : value;
|
|
523
|
+
}
|
|
524
|
+
function mapModelResources(value) {
|
|
525
|
+
return arr(value).map((m) => ({
|
|
526
|
+
kind: "model",
|
|
527
|
+
name: null,
|
|
528
|
+
// use the model uuid as the resource slug (a stable per-agent identifier)
|
|
529
|
+
slug: m.uuid,
|
|
530
|
+
description: null,
|
|
531
|
+
prompt: m.prompt,
|
|
532
|
+
integrationSlug: null,
|
|
533
|
+
modelUuid: m.uuid,
|
|
534
|
+
filter: null,
|
|
535
|
+
selectedColumnSlugs: m.columns,
|
|
536
|
+
limit: null,
|
|
537
|
+
isReadOnly: m.readOnly,
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
// Agent's `tools` — rich refs (uuid token resolved + per-call options).
|
|
541
|
+
function mapTools(value) {
|
|
542
|
+
return arr(value).map((t) => ({
|
|
543
|
+
kind: "tool",
|
|
544
|
+
name: t.name,
|
|
545
|
+
slug: t.uuid,
|
|
546
|
+
description: t.description,
|
|
547
|
+
isBulkAllowed: t.isBulkAllowed,
|
|
548
|
+
config: {},
|
|
549
|
+
toolUuid: t.uuid,
|
|
550
|
+
templateSlug: null,
|
|
551
|
+
waitUntilFinished: t.waitUntilFinished,
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
// Agent's `subAgents` — rich refs (uuid token resolved + per-call options).
|
|
555
|
+
function mapSubAgents(value) {
|
|
556
|
+
return arr(value).map((s) => ({
|
|
557
|
+
kind: "agent",
|
|
558
|
+
name: s.name,
|
|
559
|
+
slug: s.uuid,
|
|
560
|
+
description: s.description,
|
|
561
|
+
isBulkAllowed: s.isBulkAllowed,
|
|
562
|
+
config: {},
|
|
563
|
+
agentUuid: s.uuid,
|
|
564
|
+
templateSlug: null,
|
|
565
|
+
waitUntilFinished: s.waitUntilFinished,
|
|
566
|
+
}));
|
|
567
|
+
}
|
|
568
|
+
// connector actions have no waitUntilFinished — the rest of the options apply.
|
|
569
|
+
function mapConnectorActions(value) {
|
|
570
|
+
return arr(value).map((a) => ({
|
|
571
|
+
kind: "connector",
|
|
572
|
+
name: a.name === undefined || a.name === null ? null : a.name,
|
|
573
|
+
slug: `${a.integration}_${a.actionSlug}`,
|
|
574
|
+
description: a.description === undefined || a.description === null
|
|
575
|
+
? null
|
|
576
|
+
: a.description,
|
|
577
|
+
isBulkAllowed: a.isBulkAllowed === undefined ? false : a.isBulkAllowed,
|
|
578
|
+
config: a.config === undefined ? {} : a.config,
|
|
579
|
+
integrationSlug: a.integration,
|
|
580
|
+
connectorUuid: a.connectorUuid === undefined ? null : a.connectorUuid,
|
|
581
|
+
actionSlug: a.actionSlug,
|
|
582
|
+
}));
|
|
583
|
+
}
|
|
584
|
+
function mapToolTriggers(value) {
|
|
585
|
+
// triggers are optional on both tool.create and tool.update — omit the key
|
|
586
|
+
// entirely when there are none rather than sending an empty array.
|
|
587
|
+
const triggers = value;
|
|
588
|
+
if (triggers === undefined || triggers.length === 0)
|
|
589
|
+
return undefined;
|
|
590
|
+
return triggers.map((t) => ({
|
|
591
|
+
name: t.name === undefined ? t.cron : t.name,
|
|
592
|
+
description: t.description === undefined ? null : t.description,
|
|
593
|
+
type: "cron",
|
|
594
|
+
cron: t.cron,
|
|
595
|
+
data: t.data === undefined ? {} : t.data,
|
|
596
|
+
}));
|
|
597
|
+
}
|
|
598
|
+
function mapAgentTriggers(value) {
|
|
599
|
+
// triggers are optional on both agent.create and agent.update — omit the key
|
|
600
|
+
// entirely when there are none rather than sending an empty array.
|
|
601
|
+
const triggers = value;
|
|
602
|
+
if (triggers === undefined || triggers.length === 0)
|
|
603
|
+
return undefined;
|
|
604
|
+
return triggers.map((t) => {
|
|
605
|
+
if (t.type === "cron") {
|
|
606
|
+
return {
|
|
607
|
+
name: t.name === undefined ? t.text : t.name,
|
|
608
|
+
description: null,
|
|
609
|
+
type: "cron",
|
|
610
|
+
cron: t.cron,
|
|
611
|
+
text: t.text,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
name: t.name === undefined ? t.integration : t.name,
|
|
616
|
+
description: null,
|
|
617
|
+
type: "connector",
|
|
618
|
+
connectorUuid: t.connectorUuid === undefined ? null : t.connectorUuid,
|
|
619
|
+
integrationSlug: t.integration,
|
|
620
|
+
config: t.config === undefined ? {} : t.config,
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Api } from "@cargo-ai/api";
|
|
2
|
+
import type { ResourceKind, ResourceNode } from "../core.js";
|
|
3
|
+
export type ImportResult = {
|
|
4
|
+
kind: ResourceKind;
|
|
5
|
+
outputs: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Import the live resource `uuid` as the code resource `id` (e.g. "agent:sdr").
|
|
9
|
+
* Loads the repo to confirm the id exists and capture its current hash, verifies
|
|
10
|
+
* the live resource is reachable, then writes/overwrites its state entry.
|
|
11
|
+
*/
|
|
12
|
+
export declare function importResource(root: string, api: Api, workspaceUuid: string, id: string, uuid: string, nodes?: readonly ResourceNode[]): Promise<ImportResult>;
|
|
13
|
+
//# sourceMappingURL=import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../../src/deploy/import.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM7D,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AA2CF;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,GAAG,EACR,aAAa,EAAE,MAAM,EACrB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EAGZ,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,GAC9B,OAAO,CAAC,YAAY,CAAC,CAoCvB"}
|