@cargo-ai/cdk 1.0.11 → 1.0.12

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.
Files changed (47) hide show
  1. package/build/src/cli/commands/deploy.d.ts.map +1 -1
  2. package/build/src/cli/commands/deploy.js +105 -1
  3. package/build/src/deploy/executors.live.d.ts.map +1 -1
  4. package/build/src/deploy/executors.live.js +56 -2
  5. package/build/src/deploy/import.d.ts +12 -0
  6. package/build/src/deploy/import.d.ts.map +1 -1
  7. package/build/src/deploy/import.js +79 -9
  8. package/build/src/deploy/index.d.ts +2 -0
  9. package/build/src/deploy/index.d.ts.map +1 -1
  10. package/build/src/deploy/index.js +1 -0
  11. package/build/src/deploy/pull/enumerate.d.ts +8 -0
  12. package/build/src/deploy/pull/enumerate.d.ts.map +1 -0
  13. package/build/src/deploy/pull/enumerate.js +258 -0
  14. package/build/src/deploy/pull/index.d.ts +28 -0
  15. package/build/src/deploy/pull/index.d.ts.map +1 -0
  16. package/build/src/deploy/pull/index.js +152 -0
  17. package/build/src/deploy/pull/ir.d.ts +45 -0
  18. package/build/src/deploy/pull/ir.d.ts.map +1 -0
  19. package/build/src/deploy/pull/ir.js +72 -0
  20. package/build/src/deploy/pull/mappers.d.ts +10 -0
  21. package/build/src/deploy/pull/mappers.d.ts.map +1 -0
  22. package/build/src/deploy/pull/mappers.js +689 -0
  23. package/build/src/deploy/pull/print.d.ts +9 -0
  24. package/build/src/deploy/pull/print.d.ts.map +1 -0
  25. package/build/src/deploy/pull/print.js +59 -0
  26. package/build/src/deploy/pull/resolve.d.ts +24 -0
  27. package/build/src/deploy/pull/resolve.d.ts.map +1 -0
  28. package/build/src/deploy/pull/resolve.js +113 -0
  29. package/build/src/deploy/pull/untar.d.ts +6 -0
  30. package/build/src/deploy/pull/untar.d.ts.map +1 -0
  31. package/build/src/deploy/pull/untar.js +84 -0
  32. package/build/src/deploy/readers.live.d.ts +3 -0
  33. package/build/src/deploy/readers.live.d.ts.map +1 -1
  34. package/build/src/deploy/readers.live.js +12 -6
  35. package/build/src/index.d.ts +3 -3
  36. package/build/src/index.d.ts.map +1 -1
  37. package/build/src/index.js +1 -1
  38. package/build/src/refs.d.ts +7 -1
  39. package/build/src/refs.d.ts.map +1 -1
  40. package/build/src/refs.js +10 -0
  41. package/build/src/resources/customIntegration.d.ts +3 -2
  42. package/build/src/resources/customIntegration.d.ts.map +1 -1
  43. package/build/src/resources/model.d.ts +56 -8
  44. package/build/src/resources/model.d.ts.map +1 -1
  45. package/build/src/resources/model.js +48 -1
  46. package/build/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +2 -2
@@ -0,0 +1,689 @@
1
+ // Pure live-row → IRResource mappers, one per supported kind. These invert the
2
+ // stored-spec field renames the define* builders apply (integrationSlug →
3
+ // integration, datasetUuid → dataset, extractorSlug → extractSlug, modelUuid →
4
+ // model, folderUuid → folder, parentUuid → parent, …) and embed RefMarker /
5
+ // SecretMarker where a raw value can't be printed as JSON.
6
+ //
7
+ // Unlike the drift readers (readers.live.ts), which project only the *updatable*
8
+ // fields, these emit the full *create* spec — including immutable fields like
9
+ // folder.kind — because the generated code must reproduce the resource, not just
10
+ // its editable surface.
11
+ //
12
+ // A mapper returns `null` when the live resource can't be faithfully expressed by
13
+ // its define* (e.g. a unified model, which has no source connector) — the caller
14
+ // skips and reports it rather than emitting code that wouldn't deploy.
15
+ //
16
+ // Rule for optional fields: include iff the live value is non-null/defined. A
17
+ // null live field maps to the define* default (also null), so omitting it keeps
18
+ // the content hash identical — which is what makes a post-pull `plan` a no-op.
19
+ import { deepOmit } from "../readers.live.js";
20
+ import { call, isCallMarker, isRefMarker, isSecretMarker, ref, } from "./ir.js";
21
+ function defined(value) {
22
+ return value !== null && value !== undefined;
23
+ }
24
+ // Markers are opaque leaves to spec normalization — a call marker's workflow
25
+ // wire nodes keep their canvas positions etc. (draftRelease.deploy expects the
26
+ // full graph back), and a ref's fallback expression ships verbatim.
27
+ function isMarker(value) {
28
+ return isRefMarker(value) || isSecretMarker(value) || isCallMarker(value);
29
+ }
30
+ // Strip the shared VOLATILE keys (per-resource ids, canvas coords) that leak
31
+ // into config/schedule, so the generated spec is stable and hash-matches the
32
+ // drift readers' projection.
33
+ function stripVolatile(value) {
34
+ return deepOmit(value, isMarker);
35
+ }
36
+ // Derive a valid define* slug from a name (for kinds with no server slug, e.g.
37
+ // folders). Base slug rule: /^[a-z0-9][a-z0-9_-]*$/.
38
+ function slugify(name) {
39
+ const base = name
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9]+/g, "_")
42
+ .replace(/^_+|_+$/g, "");
43
+ if (base.length === 0)
44
+ return "resource";
45
+ return /^[0-9]/.test(base) ? `f_${base}` : base;
46
+ }
47
+ // Name for kinds with no server-side slug — the slug is derived from it.
48
+ function nameOr(row, fallback) {
49
+ return defined(row["name"]) ? String(row["name"]) : fallback;
50
+ }
51
+ // What can print as bare property access / a bare object key.
52
+ const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
53
+ function screaming(value) {
54
+ return value
55
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
56
+ .replace(/[^A-Za-z0-9]+/g, "_")
57
+ .replace(/^_+|_+$/g, "")
58
+ .toUpperCase();
59
+ }
60
+ function isEncryptionEnvelope(value) {
61
+ return (typeof value === "object" &&
62
+ value !== null &&
63
+ value.type === "encryption");
64
+ }
65
+ // Replace every encrypted credential field in a connector config with a
66
+ // `secret("NAME")` placeholder — the plaintext is never returned by the API.
67
+ function maskSecrets(value, slug, path) {
68
+ if (isEncryptionEnvelope(value)) {
69
+ const marker = {
70
+ __secret: true,
71
+ envName: [screaming(slug), ...path.map(screaming)].join("_"),
72
+ };
73
+ return marker;
74
+ }
75
+ if (Array.isArray(value)) {
76
+ return value.map((item, index) => maskSecrets(item, slug, [...path, String(index)]));
77
+ }
78
+ if (typeof value === "object" && value !== null) {
79
+ const out = {};
80
+ for (const [key, inner] of Object.entries(value)) {
81
+ out[key] = maskSecrets(inner, slug, [...path, key]);
82
+ }
83
+ return out;
84
+ }
85
+ return value;
86
+ }
87
+ // Assign `spec[key] = value` only when the live value is meaningful (non-nullish).
88
+ function put(spec, key, value) {
89
+ if (defined(value))
90
+ spec[key] = value;
91
+ }
92
+ // The pervasive `<key>Uuid → handle-or-literal` inversion: assign a ref marker
93
+ // when the live row carries a uuid, omit otherwise.
94
+ function putRef(spec, key, uuid, fallbackFn) {
95
+ if (typeof uuid === "string")
96
+ spec[key] = ref(uuid, fallbackFn);
97
+ }
98
+ // Omit a define-time default so simple resources stay minimal (and the content
99
+ // hash matches the define* default).
100
+ function putUnlessDefault(spec, key, value, defaultValue) {
101
+ if (defined(value) && value !== defaultValue)
102
+ spec[key] = value;
103
+ }
104
+ // Copy a row keeping only non-nullish fields (the optional-field rule).
105
+ function cleanRow(row) {
106
+ const out = {};
107
+ for (const [key, value] of Object.entries(row))
108
+ put(out, key, value);
109
+ return out;
110
+ }
111
+ // Where a pulled file's content lives, derived from its (dedupe-final) slug.
112
+ // Shared with generateFiles' post-dedupe fixup so the rule has one home.
113
+ export function fileAssetPath(slug, sourceName) {
114
+ const extension = /\.[A-Za-z0-9]+$/.exec(sourceName);
115
+ return `files/assets/${slug}${extension === null ? "" : extension[0]}`;
116
+ }
117
+ const connector = (row) => {
118
+ const slug = String(row["slug"]);
119
+ const spec = { integration: row["integrationSlug"] };
120
+ put(spec, "name", row["name"]);
121
+ const config = row["config"];
122
+ if (defined(config))
123
+ spec["config"] = maskSecrets(config, slug, []);
124
+ put(spec, "rateLimit", row["rateLimit"]);
125
+ put(spec, "cacheTtlMilliseconds", row["cacheTtlMilliseconds"]);
126
+ // A pulled connector's credentials are never recoverable (secrets are masked),
127
+ // so the generated code links the live connector instead of re-creating it.
128
+ spec["adopt"] = true;
129
+ return { kind: "connector", slug, uuid: String(row["uuid"]), spec };
130
+ };
131
+ // Native models produced by unification (not user-authored — the backend
132
+ // rejects creating models with these extractors, they come from syncUnifyModel).
133
+ export const UNIFY_EXTRACTOR_SLUGS = new Set([
134
+ "unifyAccounts",
135
+ "unifyAccountEvents",
136
+ "unifyContacts",
137
+ "unifyContactEvents",
138
+ ]);
139
+ const model = (row) => {
140
+ // A unify model is auto-generated from its sources' unification config — it
141
+ // can't be created through the API, so it can't be represented in code. Skip.
142
+ if (UNIFY_EXTRACTOR_SLUGS.has(String(row["extractorSlug"])))
143
+ return null;
144
+ const slug = String(row["slug"]);
145
+ const spec = {};
146
+ put(spec, "name", row["name"]);
147
+ put(spec, "description", row["description"]);
148
+ const connectorUuid = row["connectorUuid"];
149
+ const datasetUuid = row["datasetUuid"];
150
+ if (typeof connectorUuid === "string") {
151
+ // Authored as the source connector's handle; if that connector isn't in the
152
+ // pull, fall back to a literal datasetRef(datasetUuid).
153
+ spec["dataset"] = ref(connectorUuid, "datasetRef", {
154
+ fallbackUuid: typeof datasetUuid === "string" ? datasetUuid : connectorUuid,
155
+ });
156
+ }
157
+ else {
158
+ // A native model (kind "native" — no connectorUuid key at all) has no
159
+ // connector to reference: a dataset uuid never matches a pulled resource,
160
+ // so this always prints as the literal datasetRef.
161
+ spec["dataset"] = ref(String(datasetUuid), "datasetRef");
162
+ }
163
+ put(spec, "extractSlug", row["extractorSlug"]);
164
+ put(spec, "config", row["config"]);
165
+ put(spec, "schedule", row["schedule"]);
166
+ putRef(spec, "folder", row["folderUuid"], "folderRef");
167
+ const additionalColumns = row["additionalColumns"];
168
+ if (Array.isArray(additionalColumns) && additionalColumns.length > 0) {
169
+ spec["additionalColumns"] = additionalColumns.map((column) => mapAdditionalColumn(column));
170
+ }
171
+ const unification = row["unification"];
172
+ // `{source:"integration"}` is every source's default — omitting it keeps the
173
+ // spec at the define* default (undefined = keep the source's mapping), so
174
+ // only a custom unification is worth authoring.
175
+ if (defined(unification) && unification["source"] === "custom") {
176
+ spec["unification"] = mapUnification(unification);
177
+ }
178
+ return { kind: "model", slug, uuid: String(row["uuid"]), spec };
179
+ };
180
+ // Invert `compileUnification`: everything passes through except a `model`-kind
181
+ // parent, whose `parentModelUuid` becomes a model ref (a handle import when the
182
+ // parent is also pulled).
183
+ function mapUnification(unification) {
184
+ const spec = { source: "custom" };
185
+ put(spec, "type", unification["type"]);
186
+ put(spec, "uniqueColumns", unification["uniqueColumns"]);
187
+ put(spec, "selectedColumnSlugs", unification["selectedColumnSlugs"]);
188
+ put(spec, "timeColumnSlug", unification["timeColumnSlug"]);
189
+ put(spec, "filter", unification["filter"]);
190
+ const parent = unification["parent"];
191
+ if (defined(parent)) {
192
+ spec["parent"] =
193
+ parent["kind"] === "model"
194
+ ? {
195
+ kind: "model",
196
+ columnSlug: parent["columnSlug"],
197
+ parent: ref(String(parent["parentModelUuid"]), "modelRef"),
198
+ }
199
+ : parent;
200
+ }
201
+ return spec;
202
+ }
203
+ // The spec fields each column kind accepts — live rows can carry extra server
204
+ // fields, and passing them through would make the generated spec fail tsc.
205
+ const COLUMN_BASE_KEYS = [
206
+ "kind",
207
+ "slug",
208
+ "type",
209
+ "label",
210
+ "description",
211
+ "properties",
212
+ ];
213
+ const COLUMN_KEYS = {
214
+ custom: COLUMN_BASE_KEYS,
215
+ computed: [...COLUMN_BASE_KEYS, "expression", "columnsUsed"],
216
+ metric: [...COLUMN_BASE_KEYS, "aggregation", "filter"],
217
+ lookup: [...COLUMN_BASE_KEYS, "extractColumnSlug", "filter"],
218
+ };
219
+ // Invert `compileAdditionalColumn`. Both cross-resource references print as
220
+ // LITERAL calls, never handle imports: a metric's relationship points back at
221
+ // the model declaring the column, and two models' lookups can point at each
222
+ // other — either way a handle import would be a module cycle that fails to
223
+ // load. Only the kind's known spec fields are kept, nullish dropped (the
224
+ // optional-field rule).
225
+ function mapAdditionalColumn(raw) {
226
+ const keys = COLUMN_KEYS[String(raw["kind"])];
227
+ const column = {};
228
+ for (const key of keys === undefined ? Object.keys(raw) : keys) {
229
+ put(column, key, raw[key]);
230
+ }
231
+ if (column["kind"] === "metric") {
232
+ column["relationship"] = call("relationshipRef", [
233
+ String(raw["relationshipUuid"]),
234
+ ]);
235
+ }
236
+ if (column["kind"] === "lookup") {
237
+ const join = raw["join"];
238
+ column["join"] = {
239
+ model: call("modelRef", [String(join["toModelUuid"])]),
240
+ fromColumnSlug: join["fromColumnSlug"],
241
+ toColumnSlug: join["toColumnSlug"],
242
+ };
243
+ }
244
+ return column;
245
+ }
246
+ const segment = (row) => {
247
+ // A play's hidden backing segment is managed by the play (pull folds it into
248
+ // the play spec via __segment); the backend rejects direct updates to it.
249
+ if (row["fromPlay"] === true)
250
+ return null;
251
+ const slug = String(row["slug"]);
252
+ const spec = {};
253
+ put(spec, "name", row["name"]);
254
+ putRef(spec, "model", row["modelUuid"], "modelRef");
255
+ put(spec, "filter", row["filter"]);
256
+ put(spec, "sort", row["sort"]);
257
+ put(spec, "limit", row["limit"]);
258
+ put(spec, "trackingColumnSlugs", row["trackingColumnSlugs"]);
259
+ put(spec, "columnSlugs", row["columnSlugs"]);
260
+ return { kind: "segment", slug, uuid: String(row["uuid"]), spec };
261
+ };
262
+ const folder = (row) => {
263
+ const spec = {};
264
+ put(spec, "kind", row["kind"]); // required create field, named `kind` on the row
265
+ put(spec, "name", row["name"]);
266
+ put(spec, "emojiSlug", row["emojiSlug"]);
267
+ putRef(spec, "parent", row["parentUuid"], "folderRef");
268
+ return {
269
+ kind: "folder",
270
+ slug: slugify(nameOr(row, "folder")),
271
+ uuid: String(row["uuid"]),
272
+ spec,
273
+ };
274
+ };
275
+ const capacity = (row) => {
276
+ // Capacities have no server-side slug — derive one from the name.
277
+ const slug = slugify(nameOr(row, "capacity"));
278
+ const spec = {};
279
+ put(spec, "name", row["name"]);
280
+ put(spec, "color", row["color"]);
281
+ put(spec, "description", row["description"]);
282
+ putRef(spec, "model", row["modelUuid"], "modelRef");
283
+ put(spec, "idColumnSlug", row["idColumnSlug"]);
284
+ put(spec, "timeColumnSlug", row["timeColumnSlug"]);
285
+ put(spec, "memberCapacity", row["memberCapacity"]);
286
+ put(spec, "allocationExpirationPolicy", row["allocationExpirationPolicy"]);
287
+ put(spec, "filter", row["filter"]);
288
+ return { kind: "capacity", slug, uuid: String(row["uuid"]), spec };
289
+ };
290
+ const relationship = (row) => {
291
+ // Relationships auto-generated by unification (either endpoint is a unify
292
+ // model — flagged by enumerate, which has the model rows) aren't authorable.
293
+ if (row["__touchesUnifyModel"] === true)
294
+ return null;
295
+ // No server-side slug — derive a readable local id from the join columns.
296
+ const slug = slugify(`${String(row["fromColumnSlug"])}_to_${String(row["toColumnSlug"])}`);
297
+ const endpoint = (side) => {
298
+ const out = {
299
+ model: ref(String(row[`${side}ModelUuid`]), "modelRef"),
300
+ column: row[`${side}ColumnSlug`],
301
+ };
302
+ put(out, "property", row[`${side}PropertySlug`]);
303
+ return out;
304
+ };
305
+ const spec = {
306
+ from: endpoint("from"),
307
+ to: endpoint("to"),
308
+ relation: row["relation"],
309
+ };
310
+ return { kind: "relationship", slug, uuid: String(row["uuid"]), spec };
311
+ };
312
+ const customIntegration = (row) => {
313
+ // Only worker-backed custom integrations have a builder; the `external`
314
+ // (baseUrl) variant can't be expressed in code yet.
315
+ if (row["kind"] !== "worker")
316
+ return null;
317
+ const spec = {
318
+ worker: ref(String(row["workerUuid"]), "workerRef"),
319
+ };
320
+ return {
321
+ kind: "customIntegration",
322
+ slug: String(row["slug"]),
323
+ uuid: String(row["uuid"]),
324
+ spec,
325
+ };
326
+ };
327
+ const territory = (row) => {
328
+ // Territories have no server-side slug — derive one from the label.
329
+ const slug = slugify(defined(row["label"]) ? String(row["label"]) : "territory");
330
+ const spec = {};
331
+ put(spec, "label", row["label"]);
332
+ put(spec, "color", row["color"]);
333
+ put(spec, "description", row["description"]);
334
+ const members = row["members"];
335
+ if (Array.isArray(members) && members.length > 0) {
336
+ // Members aren't a pulled resource — always literal memberRefs.
337
+ spec["members"] = members.map((member) => {
338
+ const entry = {
339
+ ref: ref(String(member["uuid"]), "memberRef"),
340
+ };
341
+ put(entry, "weight", member["weight"]);
342
+ return entry;
343
+ });
344
+ }
345
+ putRef(spec, "fallback", row["fallbackMemberUuid"], "memberRef");
346
+ return { kind: "territory", slug, uuid: String(row["uuid"]), spec };
347
+ };
348
+ // A play/tool's workflow body — the deployed release's wire nodes, embedded
349
+ // verbatim as `defineWorkflowFromNodes(slug, { nodes, formFields })`.
350
+ function workflowCall(slug, workflow) {
351
+ const formFields = workflow["formFields"];
352
+ return call("defineWorkflowFromNodes", [
353
+ slug,
354
+ {
355
+ nodes: workflow["nodes"],
356
+ formFields: defined(formFields) ? formFields : [],
357
+ },
358
+ ]);
359
+ }
360
+ // Wrap a use ref with its options — a bare ref when every option is default.
361
+ function wrapUse(refValue, opts) {
362
+ return Object.keys(opts).length === 0 ? refValue : { ref: refValue, ...opts };
363
+ }
364
+ // A release's connector action, in the friendliest printable form: the typed
365
+ // `hubspot.actions.emailFinder` accessor when the connector is pulled AND the
366
+ // slug is identifier-safe (a kebab-case slug as bare property access would be
367
+ // a syntax error); else the literal `connectorActionRef(connectorRef("uuid"),
368
+ // slug, integration)`. A null connectorUuid (integration default) has no ref
369
+ // helper — emit the raw ConnectorActionRef object shape.
370
+ function connectorActionMarker(connectorUuid, actionSlug, integration) {
371
+ if (typeof connectorUuid !== "string") {
372
+ return { resource: "connectorAction", integration, actionSlug };
373
+ }
374
+ const literal = call("connectorActionRef", [
375
+ call("connectorRef", [connectorUuid]),
376
+ actionSlug,
377
+ integration,
378
+ ]);
379
+ if (!IDENTIFIER.test(actionSlug))
380
+ return literal;
381
+ return ref(connectorUuid, "connectorActionRef", {
382
+ accessor: `.actions.${actionSlug}`,
383
+ fallbackExpr: literal,
384
+ });
385
+ }
386
+ // Invert `normalizeUses` + the executor's release mapping: re-collapse a
387
+ // release's `resources` (data models) and `actions` (tool / agent / connector
388
+ // kinds) into the single `uses` array defineAgent/defineMcpServer take.
389
+ function mapUses(resources, actions) {
390
+ const uses = [];
391
+ const resourceRows = Array.isArray(resources) ? resources : [];
392
+ for (const resource of resourceRows) {
393
+ if (resource["kind"] !== "model")
394
+ continue;
395
+ const opts = {};
396
+ if (resource["isReadOnly"] === false)
397
+ opts["readOnly"] = false;
398
+ put(opts, "columns", resource["selectedColumnSlugs"]);
399
+ put(opts, "prompt", resource["prompt"]);
400
+ uses.push(wrapUse(ref(String(resource["modelUuid"]), "modelRef"), opts));
401
+ }
402
+ const actionRows = Array.isArray(actions) ? actions : [];
403
+ for (const action of actionRows) {
404
+ const opts = {};
405
+ put(opts, "name", action["name"]);
406
+ put(opts, "description", action["description"]);
407
+ if (action["isBulkAllowed"] === true)
408
+ opts["isBulkAllowed"] = true;
409
+ if (action["kind"] === "tool" || action["kind"] === "agent") {
410
+ if (action["waitUntilFinished"] === false) {
411
+ opts["waitUntilFinished"] = false;
412
+ }
413
+ const marker = action["kind"] === "tool"
414
+ ? ref(String(action["toolUuid"]), "toolRef")
415
+ : ref(String(action["agentUuid"]), "agentRef");
416
+ uses.push(wrapUse(marker, opts));
417
+ continue;
418
+ }
419
+ if (action["kind"] === "connector") {
420
+ const config = action["config"];
421
+ if (defined(config) && Object.keys(config).length > 0) {
422
+ opts["config"] = config;
423
+ }
424
+ const marker = connectorActionMarker(action["connectorUuid"], String(action["actionSlug"]), String(action["integrationSlug"]));
425
+ uses.push(wrapUse(marker, opts));
426
+ }
427
+ }
428
+ return uses;
429
+ }
430
+ // Live tool trigger rows carry executor-side defaults (name falls back to the
431
+ // cron expression, data to {}); invert both so the spec stays minimal.
432
+ function mapToolTriggers(triggers) {
433
+ const rows = Array.isArray(triggers) ? triggers : [];
434
+ if (rows.length === 0)
435
+ return undefined;
436
+ return rows.map((trigger) => {
437
+ const out = { cron: trigger["cron"] };
438
+ if (trigger["name"] !== trigger["cron"])
439
+ put(out, "name", trigger["name"]);
440
+ put(out, "description", trigger["description"]);
441
+ const data = trigger["data"];
442
+ if (defined(data) && Object.keys(data).length > 0)
443
+ out["data"] = data;
444
+ return out;
445
+ });
446
+ }
447
+ const tool = (row) => {
448
+ // Template-installed tools are read-only; a tool with no deployed release
449
+ // (enumerate attaches __workflow) has no body to reproduce.
450
+ if (row["isReadOnly"] === true)
451
+ return null;
452
+ const workflow = row["__workflow"];
453
+ if (!defined(workflow))
454
+ return null;
455
+ const slug = slugify(nameOr(row, "tool"));
456
+ const spec = {};
457
+ put(spec, "name", row["name"]);
458
+ put(spec, "description", row["description"]);
459
+ const icon = row["icon"];
460
+ if (defined(icon))
461
+ put(spec, "emojiSlug", icon["emojiSlug"]);
462
+ putRef(spec, "folder", row["folderUuid"], "folderRef");
463
+ put(spec, "triggers", mapToolTriggers(row["triggers"]));
464
+ put(spec, "publicForm", row["publicForm"]);
465
+ spec["workflow"] = workflowCall(slug, workflow);
466
+ return { kind: "tool", slug, uuid: String(row["uuid"]), spec };
467
+ };
468
+ // Invert `toWireSchedule`: a dependency schedule's playUuid becomes a play ref;
469
+ // a watch schedule's server-managed `meta` is dropped.
470
+ function mapPlaySchedule(schedule) {
471
+ if (schedule["type"] === "dependency") {
472
+ return {
473
+ type: "dependency",
474
+ play: ref(String(schedule["playUuid"]), "playRef"),
475
+ };
476
+ }
477
+ const { meta: _meta, ...rest } = schedule;
478
+ return cleanRow(rest);
479
+ }
480
+ const play = (row) => {
481
+ const slug = slugify(nameOr(row, "play"));
482
+ const spec = {};
483
+ put(spec, "name", row["name"]);
484
+ put(spec, "description", row["description"]);
485
+ spec["model"] = ref(String(row["modelUuid"]), "modelRef");
486
+ // Omit define-time defaults so simple plays stay minimal.
487
+ const changeKinds = row["changeKinds"];
488
+ if (Array.isArray(changeKinds) &&
489
+ JSON.stringify(changeKinds) !== JSON.stringify(["added"])) {
490
+ spec["changeKinds"] = changeKinds;
491
+ }
492
+ putUnlessDefault(spec, "runCreationRule", row["runCreationRule"], "always");
493
+ // filter/sort/limit/trackingColumnSlugs live on the play's backing segment —
494
+ // enumerate attaches that row as __segment.
495
+ const segmentRow = row["__segment"];
496
+ if (defined(segmentRow)) {
497
+ put(spec, "filter", segmentRow["filter"]);
498
+ put(spec, "sort", segmentRow["sort"]);
499
+ put(spec, "limit", segmentRow["limit"]);
500
+ put(spec, "trackingColumnSlugs", segmentRow["trackingColumnSlugs"]);
501
+ }
502
+ const schedule = row["schedule"];
503
+ if (defined(schedule))
504
+ spec["schedule"] = mapPlaySchedule(schedule);
505
+ putRef(spec, "folder", row["folderUuid"], "folderRef");
506
+ const workflow = row["__workflow"];
507
+ if (defined(workflow))
508
+ spec["workflow"] = workflowCall(slug, workflow);
509
+ return { kind: "play", slug, uuid: String(row["uuid"]), spec };
510
+ };
511
+ const mcpServer = (row) => {
512
+ // Template-installed servers aren't user-authored config.
513
+ if (defined(row["templateSlug"]))
514
+ return null;
515
+ if (row["isReadOnly"] === true)
516
+ return null;
517
+ const slug = slugify(nameOr(row, "mcp_server"));
518
+ const spec = {};
519
+ put(spec, "name", row["name"]);
520
+ put(spec, "description", row["description"]);
521
+ const uses = mapUses(row["resources"], row["actions"]);
522
+ if (uses.length > 0)
523
+ spec["uses"] = uses;
524
+ putRef(spec, "folder", row["folderUuid"], "folderRef");
525
+ return { kind: "mcpServer", slug, uuid: String(row["uuid"]), spec };
526
+ };
527
+ // Invert `normalizeTriggers`: a connector trigger's connectorUuid becomes a
528
+ // connector ref; cron triggers pass through (nullish fields dropped).
529
+ function mapAgentTriggers(triggers) {
530
+ const rows = Array.isArray(triggers) ? triggers : [];
531
+ if (rows.length === 0)
532
+ return undefined;
533
+ return rows.map((trigger) => {
534
+ if (trigger["type"] !== "connector")
535
+ return cleanRow(trigger);
536
+ const { connectorUuid, ...rest } = trigger;
537
+ const out = cleanRow(rest);
538
+ putRef(out, "connector", connectorUuid, "connectorRef");
539
+ return out;
540
+ });
541
+ }
542
+ const agent = (row) => {
543
+ // An agent that was never deployed has no release — nothing to reproduce.
544
+ const release = row["deployedRelease"];
545
+ if (!defined(release))
546
+ return null;
547
+ // defineAgent (and the deploy executor) require a full LLM binding; a release
548
+ // without a connector (a Cargo-managed default) can't be expressed yet.
549
+ if (typeof release["connectorUuid"] !== "string" ||
550
+ typeof release["languageModelSlug"] !== "string") {
551
+ return null;
552
+ }
553
+ const slug = slugify(nameOr(row, "agent"));
554
+ const spec = {};
555
+ put(spec, "name", row["name"]);
556
+ put(spec, "description", row["description"]);
557
+ const icon = row["icon"];
558
+ if (defined(icon))
559
+ putUnlessDefault(spec, "color", icon["color"], "grey");
560
+ putRef(spec, "connector", release["connectorUuid"], "connectorRef");
561
+ put(spec, "languageModel", release["languageModelSlug"]);
562
+ put(spec, "systemPrompt", release["systemPrompt"]);
563
+ // Omit define-time defaults (temperature 0.2, maxSteps 8, reasoning off).
564
+ putUnlessDefault(spec, "temperature", release["temperature"], 0.2);
565
+ putUnlessDefault(spec, "maxSteps", release["maxSteps"], 8);
566
+ if (release["withReasoning"] === true)
567
+ spec["withReasoning"] = true;
568
+ const capabilities = release["capabilities"];
569
+ if (Array.isArray(capabilities) && capabilities.length > 0) {
570
+ spec["capabilities"] = capabilities;
571
+ }
572
+ put(spec, "triggers", mapAgentTriggers(row["triggers"]));
573
+ const uses = mapUses(release["resources"], release["actions"]);
574
+ if (uses.length > 0)
575
+ spec["uses"] = uses;
576
+ const mcpClients = release["mcpClients"];
577
+ if (Array.isArray(mcpClients) && mcpClients.length > 0) {
578
+ spec["mcpClients"] = mcpClients;
579
+ }
580
+ const output = release["output"];
581
+ // {type:"text"} is the define default.
582
+ if (defined(output) && output["type"] !== "text")
583
+ spec["output"] = output;
584
+ put(spec, "heartbeat", release["heartbeat"]);
585
+ put(spec, "evaluator", release["evaluator"]);
586
+ putRef(spec, "folder", row["folderUuid"], "folderRef");
587
+ return { kind: "agent", slug, uuid: String(row["uuid"]), spec };
588
+ };
589
+ // ── code-backed kinds: the define references files written alongside it ──────
590
+ const file = (row) => {
591
+ // Content downloaded by enumerate; unreadable file → nothing to author.
592
+ const bytes = row["__bytes"];
593
+ if (!(bytes instanceof Uint8Array))
594
+ return null;
595
+ const name = nameOr(row, "file");
596
+ const slug = slugify(name);
597
+ const assetPath = fileAssetPath(slug, name);
598
+ const spec = { path: assetPath };
599
+ put(spec, "name", row["name"]);
600
+ putRef(spec, "folder", row["folderUuid"], "folderRef");
601
+ return {
602
+ kind: "file",
603
+ slug,
604
+ uuid: String(row["uuid"]),
605
+ spec,
606
+ assets: [{ path: assetPath, bytes }],
607
+ };
608
+ };
609
+ // Shared by worker/app: the promoted deployment's source tree (downloaded and
610
+ // extracted by enumerate) is written under `<dir>/<slug>/` and referenced as
611
+ // the bundle `path`. Env-var VALUES are not recoverable from the API, so `env`
612
+ // is omitted — fill it in before changing and re-deploying the bundle.
613
+ function hostingMapper(kind, dir) {
614
+ return (row) => {
615
+ const sourceFiles = row["__sourceFiles"];
616
+ if (!Array.isArray(sourceFiles) || sourceFiles.length === 0)
617
+ return null;
618
+ const slug = String(row["slug"]);
619
+ const path = `${dir}/${slug}`;
620
+ const spec = { path };
621
+ put(spec, "name", row["name"]);
622
+ put(spec, "description", row["description"]);
623
+ putRef(spec, "folder", row["folderUuid"], "folderRef");
624
+ if (kind === "worker") {
625
+ const triggers = row["triggers"];
626
+ if (Array.isArray(triggers) && triggers.length > 0) {
627
+ spec["triggers"] = triggers.map((trigger) => cleanRow(trigger));
628
+ }
629
+ }
630
+ return {
631
+ kind,
632
+ slug,
633
+ uuid: String(row["uuid"]),
634
+ spec,
635
+ assets: sourceFiles.map((sourceFile) => ({
636
+ path: `${path}/${sourceFile.path}`,
637
+ bytes: sourceFile.bytes,
638
+ })),
639
+ };
640
+ };
641
+ }
642
+ const worker = hostingMapper("worker", "workers");
643
+ const app = hostingMapper("app", "apps");
644
+ const context = (row) => {
645
+ // The repo's markdown files, read by enumerate. An empty repo has nothing to
646
+ // author (and defineContext would throw on a missing dir).
647
+ const files = row["__files"];
648
+ if (files === undefined || Object.keys(files).length === 0)
649
+ return null;
650
+ const encoder = new TextEncoder();
651
+ return {
652
+ kind: "context",
653
+ slug: "repository",
654
+ uuid: String(row["uuid"]),
655
+ spec: { dir: "context/files" },
656
+ assets: Object.entries(files).map(([path, content]) => ({
657
+ path: `context/files/${path}`,
658
+ bytes: encoder.encode(content),
659
+ })),
660
+ };
661
+ };
662
+ export const MAPPERS = {
663
+ connector,
664
+ customIntegration,
665
+ model,
666
+ relationship,
667
+ segment,
668
+ folder,
669
+ capacity,
670
+ territory,
671
+ tool,
672
+ play,
673
+ mcpServer,
674
+ agent,
675
+ file,
676
+ worker,
677
+ app,
678
+ context,
679
+ };
680
+ export function mapRow(kind, row) {
681
+ const mapper = MAPPERS[kind];
682
+ if (mapper === undefined) {
683
+ throw new Error(`cdk pull: no mapper for kind "${kind}"`);
684
+ }
685
+ const ir = mapper(row);
686
+ if (ir === null)
687
+ return null;
688
+ return { ...ir, spec: stripVolatile(ir.spec) };
689
+ }