@saltcorn/copilot 0.7.5 → 0.8.0
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/actions/generate-js-action.js +43 -5
- package/actions/generate-tables.js +281 -96
- package/actions/generate-workflow.js +83 -36
- package/actions/install-plugin-action.js +103 -0
- package/agent-skills/app-constructor-context.js +25 -0
- package/agent-skills/database-design.js +139 -87
- package/agent-skills/install-plugin.js +111 -0
- package/agent-skills/js-action.js +183 -0
- package/agent-skills/registry-editor.js +911 -0
- package/agent-skills/viewgen.js +239 -21
- package/agent-skills/workflow.js +52 -2
- package/app-constructor/common.js +12 -0
- package/app-constructor/errors.js +102 -0
- package/app-constructor/feedback-action.js +175 -0
- package/app-constructor/feedback.js +112 -0
- package/app-constructor/progress.js +116 -0
- package/app-constructor/prompts.js +58 -0
- package/app-constructor/requirements.js +156 -0
- package/app-constructor/run_task.js +128 -0
- package/app-constructor/schema.js +186 -0
- package/app-constructor/taskchart.js +70 -0
- package/app-constructor/tasks.js +401 -0
- package/app-constructor/tools.js +81 -0
- package/app-constructor/view.js +209 -0
- package/builder-gen.js +534 -66
- package/builder-schema.js +26 -6
- package/common.js +20 -0
- package/copilot-as-agent.js +6 -1
- package/index.js +23 -1
- package/js-code-gen.js +65 -0
- package/package.json +1 -1
- package/tests/builder-gen.test.js +56 -0
|
@@ -39,7 +39,7 @@ const summarizeTables = (tables) =>
|
|
|
39
39
|
})
|
|
40
40
|
.join(", ");
|
|
41
41
|
const ellipsis = table.fields.length > fields.length ? "..." : "";
|
|
42
|
-
return `${idx + 1}. ${table.table_name || "(missing name)"}
|
|
42
|
+
return `${idx + 1}. ${table.table_name || "(missing name)"} \u2013 ${
|
|
43
43
|
table.fields.length
|
|
44
44
|
} field(s)${fieldSummary ? ` (${fieldSummary}${ellipsis})` : ""}`;
|
|
45
45
|
});
|
|
@@ -96,12 +96,14 @@ const partitionTablesByExistence = async (tables = []) => {
|
|
|
96
96
|
const newTables = [];
|
|
97
97
|
const skippedExisting = [];
|
|
98
98
|
const skippedDuplicates = [];
|
|
99
|
+
const existingTablesData = [];
|
|
99
100
|
tables.forEach((table) => {
|
|
100
101
|
const tableName =
|
|
101
102
|
typeof table?.table_name === "string" ? table.table_name.trim() : "";
|
|
102
103
|
const normalized = tableName.toLowerCase();
|
|
103
104
|
if (tableName && existingNames.has(normalized)) {
|
|
104
105
|
skippedExisting.push(tableName);
|
|
106
|
+
existingTablesData.push(table);
|
|
105
107
|
return;
|
|
106
108
|
}
|
|
107
109
|
if (tableName && seenNewNames.has(normalized)) {
|
|
@@ -111,7 +113,7 @@ const partitionTablesByExistence = async (tables = []) => {
|
|
|
111
113
|
if (tableName) seenNewNames.add(normalized);
|
|
112
114
|
newTables.push(table);
|
|
113
115
|
});
|
|
114
|
-
return { newTables, skippedExisting, skippedDuplicates };
|
|
116
|
+
return { newTables, skippedExisting, skippedDuplicates, existingTablesData };
|
|
115
117
|
};
|
|
116
118
|
|
|
117
119
|
const partitionTablesByValidity = (tables = []) => {
|
|
@@ -136,6 +138,34 @@ const partitionTablesByValidity = (tables = []) => {
|
|
|
136
138
|
return { validTables, skippedMissingNames, skippedMissingFields };
|
|
137
139
|
};
|
|
138
140
|
|
|
141
|
+
const renderExistingTablesPreview = (existingTablesData) => {
|
|
142
|
+
if (!existingTablesData.length) return "";
|
|
143
|
+
return existingTablesData
|
|
144
|
+
.map((table) => {
|
|
145
|
+
const rows = (table.fields || [])
|
|
146
|
+
.filter((f) => (f?.name || "").toLowerCase() !== "id")
|
|
147
|
+
.map((f) => {
|
|
148
|
+
const cfg = f.type_and_configuration || {};
|
|
149
|
+
const typeParts = [
|
|
150
|
+
cfg.data_type === "ForeignKey"
|
|
151
|
+
? `ForeignKey \u2192 ${cfg.reference_table || "?"}`
|
|
152
|
+
: cfg.data_type || "Unknown",
|
|
153
|
+
];
|
|
154
|
+
if (f.calculated) typeParts.push(f.stored ? "stored calc" : "calc");
|
|
155
|
+
return `<tr><td>${f.name || ""}</td><td>${f.label || ""}</td><td>${typeParts.join(" ")}</td><td>${f.expression || ""}</td></tr>`;
|
|
156
|
+
})
|
|
157
|
+
.join("");
|
|
158
|
+
return (
|
|
159
|
+
`<div class="mt-3"><b>Add / update fields on existing table: ` +
|
|
160
|
+
`<code>${table.table_name}</code></b>` +
|
|
161
|
+
`<table class="table table-sm mt-1"><thead><tr>` +
|
|
162
|
+
`<th>Name</th><th>Label</th><th>Type</th><th>Expression</th></tr></thead>` +
|
|
163
|
+
`<tbody>${rows}</tbody></table></div>`
|
|
164
|
+
);
|
|
165
|
+
})
|
|
166
|
+
.join("");
|
|
167
|
+
};
|
|
168
|
+
|
|
139
169
|
const payloadFromToolCall = (tool_call) => {
|
|
140
170
|
if (!tool_call) return { tables: [] };
|
|
141
171
|
if (tool_call.input) return normalizeTablesPayload(tool_call.input);
|
|
@@ -161,67 +191,64 @@ class GenerateTablesSkill {
|
|
|
161
191
|
|
|
162
192
|
get userActions() {
|
|
163
193
|
return {
|
|
164
|
-
async apply_copilot_tables({ user, tables }) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
async apply_copilot_tables({ user, tables, existing_tables }) {
|
|
195
|
+
const notifyParts = [];
|
|
196
|
+
|
|
197
|
+
// Create brand-new tables
|
|
198
|
+
if (tables?.length) {
|
|
199
|
+
const { newTables, skippedDuplicates } =
|
|
200
|
+
await partitionTablesByExistence(tables);
|
|
201
|
+
const { validTables, skippedMissingNames, skippedMissingFields } =
|
|
202
|
+
partitionTablesByValidity(newTables);
|
|
203
|
+
if (validTables.length) {
|
|
204
|
+
await GenerateTables.execute({ tables: validTables }, { user });
|
|
205
|
+
notifyParts.push(
|
|
206
|
+
`Created tables: ${validTables.map((t) => t.table_name).join(", ")}`,
|
|
175
207
|
);
|
|
208
|
+
}
|
|
176
209
|
if (skippedDuplicates.length)
|
|
177
|
-
|
|
178
|
-
`
|
|
210
|
+
notifyParts.push(
|
|
211
|
+
`Ignored duplicate definitions: ${skippedDuplicates.join(", ")}`,
|
|
179
212
|
);
|
|
180
213
|
if (skippedMissingNames.length)
|
|
181
|
-
|
|
214
|
+
notifyParts.push(
|
|
182
215
|
`Missing table_name: ${skippedMissingNames.join(", ")}`,
|
|
183
216
|
);
|
|
184
217
|
if (skippedMissingFields.length)
|
|
185
|
-
|
|
218
|
+
notifyParts.push(
|
|
186
219
|
`Tables without fields: ${skippedMissingFields.join(", ")}`,
|
|
187
220
|
);
|
|
188
|
-
return {
|
|
189
|
-
notify:
|
|
190
|
-
skippedMessages.length > 0
|
|
191
|
-
? `Nothing to create. Skipped ${skippedMessages.join("; ")}.`
|
|
192
|
-
: "Nothing to create.",
|
|
193
|
-
};
|
|
194
221
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
|
|
223
|
+
// Add/update fields on existing tables
|
|
224
|
+
if (existing_tables?.length) {
|
|
225
|
+
for (const table of existing_tables) {
|
|
226
|
+
if (!table?.table_name || !Array.isArray(table.fields)) continue;
|
|
227
|
+
const { added, updated } =
|
|
228
|
+
await GenerateTables.execute_add_or_update_fields(
|
|
229
|
+
{ table_name: table.table_name, fields: table.fields },
|
|
230
|
+
{ user },
|
|
231
|
+
);
|
|
232
|
+
const parts = [];
|
|
233
|
+
if (added.length) parts.push(`added: ${added.join(", ")}`);
|
|
234
|
+
if (updated.length) parts.push(`updated: ${updated.join(", ")}`);
|
|
235
|
+
if (parts.length)
|
|
236
|
+
notifyParts.push(
|
|
237
|
+
`${table.table_name} \u2014 ${parts.join("; ")}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
214
242
|
return {
|
|
215
|
-
notify:
|
|
216
|
-
". "
|
|
217
|
-
|
|
243
|
+
notify: notifyParts.length
|
|
244
|
+
? notifyParts.join(". ")
|
|
245
|
+
: "Nothing to apply.",
|
|
218
246
|
};
|
|
219
247
|
},
|
|
220
248
|
};
|
|
221
249
|
}
|
|
222
250
|
|
|
223
251
|
provideTools = () => {
|
|
224
|
-
const parameters = GenerateTables.json_schema();
|
|
225
252
|
return {
|
|
226
253
|
type: "function",
|
|
227
254
|
process: async (input) => {
|
|
@@ -230,24 +257,28 @@ class GenerateTablesSkill {
|
|
|
230
257
|
if (!tables.length) {
|
|
231
258
|
return "No tables were provided for generate_tables.";
|
|
232
259
|
}
|
|
233
|
-
const {
|
|
234
|
-
|
|
260
|
+
const {
|
|
261
|
+
newTables,
|
|
262
|
+
skippedExisting,
|
|
263
|
+
skippedDuplicates,
|
|
264
|
+
existingTablesData,
|
|
265
|
+
} = await partitionTablesByExistence(tables);
|
|
235
266
|
const { validTables, skippedMissingNames, skippedMissingFields } =
|
|
236
267
|
partitionTablesByValidity(newTables);
|
|
237
268
|
const summaryLines = validTables.length
|
|
238
269
|
? summarizeTables(validTables).map((line) => `- ${line}`)
|
|
239
270
|
: [];
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
271
|
+
const existingSummaryLines = existingTablesData.map(
|
|
272
|
+
(t) =>
|
|
273
|
+
`- "${t.table_name}" (exists) \u2014 ${(t.fields || []).length} field(s) will be added/updated`,
|
|
274
|
+
);
|
|
275
|
+
const warnings = collectTableWarnings(
|
|
276
|
+
tables.filter((t) => !skippedExisting.includes(t.table_name)),
|
|
277
|
+
);
|
|
247
278
|
if (skippedDuplicates.length)
|
|
248
279
|
skippedDuplicates.forEach((name) =>
|
|
249
280
|
warnings.push(
|
|
250
|
-
`Table "${name}" was defined multiple times
|
|
281
|
+
`Table "${name}" was defined multiple times; only the first definition will be used.`,
|
|
251
282
|
),
|
|
252
283
|
);
|
|
253
284
|
skippedMissingNames.forEach((label) =>
|
|
@@ -263,26 +294,31 @@ class GenerateTablesSkill {
|
|
|
263
294
|
const warningLines = warnings.length
|
|
264
295
|
? ["Warnings:", ...warnings.map((w) => `- ${w}`)]
|
|
265
296
|
: [];
|
|
266
|
-
const
|
|
297
|
+
const newSection = summaryLines.length
|
|
267
298
|
? [
|
|
268
|
-
`Ready to create ${validTables.length} new table${
|
|
269
|
-
validTables.length === 1 ? "" : "s"
|
|
270
|
-
}:`,
|
|
299
|
+
`Ready to create ${validTables.length} new table${validTables.length === 1 ? "" : "s"}:`,
|
|
271
300
|
...summaryLines,
|
|
272
301
|
]
|
|
273
|
-
: [
|
|
274
|
-
|
|
275
|
-
|
|
302
|
+
: [];
|
|
303
|
+
const existingSection = existingSummaryLines.length
|
|
304
|
+
? [
|
|
305
|
+
"Existing tables (fields will be added/updated):",
|
|
306
|
+
...existingSummaryLines,
|
|
307
|
+
]
|
|
308
|
+
: [];
|
|
309
|
+
const nothingToDo = !newSection.length && !existingSection.length;
|
|
276
310
|
return [
|
|
277
311
|
`Received ${tables.length} table definition${tables.length === 1 ? "" : "s"}.`,
|
|
278
|
-
...
|
|
312
|
+
...(nothingToDo
|
|
313
|
+
? ["Nothing to do after filtering."]
|
|
314
|
+
: [...newSection, ...existingSection]),
|
|
279
315
|
...warningLines,
|
|
280
316
|
].join("\n");
|
|
281
317
|
},
|
|
282
|
-
postProcess: async ({ tool_call }) => {
|
|
318
|
+
postProcess: async ({ tool_call, req }) => {
|
|
283
319
|
const payload = payloadFromToolCall(tool_call);
|
|
284
320
|
const tables = payload.tables || [];
|
|
285
|
-
const { newTables,
|
|
321
|
+
const { newTables, skippedDuplicates, existingTablesData } =
|
|
286
322
|
await partitionTablesByExistence(tables);
|
|
287
323
|
const { validTables, skippedMissingNames, skippedMissingFields } =
|
|
288
324
|
partitionTablesByValidity(newTables);
|
|
@@ -290,22 +326,16 @@ class GenerateTablesSkill {
|
|
|
290
326
|
try {
|
|
291
327
|
if (validTables.length) {
|
|
292
328
|
preview = GenerateTables.render_html({ tables: validTables });
|
|
293
|
-
} else {
|
|
294
|
-
preview =
|
|
295
|
-
'<div class="alert alert-info">No new tables to preview because every provided table already exists or was invalid.</div>';
|
|
296
329
|
}
|
|
297
330
|
} catch (e) {
|
|
298
|
-
console.log("
|
|
331
|
+
console.log("generate_tables postProcess render failed", {
|
|
299
332
|
e,
|
|
300
333
|
time: new Date(),
|
|
301
334
|
});
|
|
302
335
|
preview = `<pre>${JSON.stringify(payload, null, 2)}</pre>`;
|
|
303
336
|
}
|
|
337
|
+
const existingPreview = renderExistingTablesPreview(existingTablesData);
|
|
304
338
|
const warningChunks = [];
|
|
305
|
-
if (skippedExisting.length)
|
|
306
|
-
warningChunks.push(
|
|
307
|
-
`Skipped existing tables: ${skippedExisting.join(", ")}`,
|
|
308
|
-
);
|
|
309
339
|
if (skippedDuplicates.length)
|
|
310
340
|
warningChunks.push(
|
|
311
341
|
`Ignored duplicate definitions: ${skippedDuplicates.join(", ")}`,
|
|
@@ -321,26 +351,48 @@ class GenerateTablesSkill {
|
|
|
321
351
|
const warningHtml = warningChunks.length
|
|
322
352
|
? `<div class="alert alert-warning">${warningChunks.join("<br/>")}</div>`
|
|
323
353
|
: "";
|
|
354
|
+
|
|
355
|
+
if (this.yoloMode) {
|
|
356
|
+
this.userActions.apply_copilot_tables({
|
|
357
|
+
user: req?.user,
|
|
358
|
+
tables: validTables,
|
|
359
|
+
});
|
|
360
|
+
return { stop: true, add_response: `${warningHtml}${preview}` };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const hasAnything =
|
|
364
|
+
validTables.length > 0 || existingTablesData.length > 0;
|
|
365
|
+
const labelParts = [];
|
|
366
|
+
if (validTables.length)
|
|
367
|
+
labelParts.push(
|
|
368
|
+
`Create ${validTables.map((t) => t.table_name).join(", ")}`,
|
|
369
|
+
);
|
|
370
|
+
if (existingTablesData.length)
|
|
371
|
+
labelParts.push(
|
|
372
|
+
`Update fields on ${existingTablesData
|
|
373
|
+
.map((t) => t.table_name)
|
|
374
|
+
.join(", ")}`,
|
|
375
|
+
);
|
|
324
376
|
return {
|
|
325
377
|
stop: true,
|
|
326
|
-
add_response: `${warningHtml}${preview}`,
|
|
327
|
-
add_user_action:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
378
|
+
add_response: `${warningHtml}${preview}${existingPreview}`,
|
|
379
|
+
add_user_action: hasAnything
|
|
380
|
+
? {
|
|
381
|
+
name: "apply_copilot_tables",
|
|
382
|
+
type: "button",
|
|
383
|
+
label: labelParts.join(" + "),
|
|
384
|
+
input: {
|
|
385
|
+
tables: validTables,
|
|
386
|
+
existing_tables: existingTablesData,
|
|
387
|
+
},
|
|
388
|
+
}
|
|
389
|
+
: undefined,
|
|
338
390
|
};
|
|
339
391
|
},
|
|
340
392
|
function: {
|
|
341
393
|
name: GenerateTables.function_name,
|
|
342
394
|
description: GenerateTables.description,
|
|
343
|
-
parameters,
|
|
395
|
+
parameters: GenerateTables.json_schema(),
|
|
344
396
|
},
|
|
345
397
|
};
|
|
346
398
|
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const InstallPluginAction = require("../actions/install-plugin-action");
|
|
2
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
3
|
+
|
|
4
|
+
class InstallPluginSkill {
|
|
5
|
+
static skill_name = "Install Plugin";
|
|
6
|
+
|
|
7
|
+
get skill_label() {
|
|
8
|
+
return "Install Plugin";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
constructor(cfg) {
|
|
12
|
+
Object.assign(this, cfg);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async systemPrompt() {
|
|
16
|
+
return (
|
|
17
|
+
`Use list_plugins to browse available Saltcorn store plugins before installing. ` +
|
|
18
|
+
`Use install_plugin to install a plugin. Prefer store plugins (plugin_name) over npm packages. ` +
|
|
19
|
+
`Do not install a plugin that is already installed.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get userActions() {
|
|
24
|
+
return {
|
|
25
|
+
async install_copilot_plugin(input, req) {
|
|
26
|
+
const result = await InstallPluginAction.execute(input, req);
|
|
27
|
+
return {
|
|
28
|
+
notify: result.postExec || "Plugin installation complete.",
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
provideTools = () => {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
type: "function",
|
|
38
|
+
process: async ({ category }) => {
|
|
39
|
+
try {
|
|
40
|
+
const plugins = await Plugin.store_plugins_available();
|
|
41
|
+
const filtered =
|
|
42
|
+
category === "theme"
|
|
43
|
+
? plugins.filter((p) => p.has_theme)
|
|
44
|
+
: category === "auth"
|
|
45
|
+
? plugins.filter((p) => p.has_auth)
|
|
46
|
+
: plugins;
|
|
47
|
+
if (!filtered.length) return "No plugins found.";
|
|
48
|
+
return filtered
|
|
49
|
+
.map(
|
|
50
|
+
(p) => `${p.name}${p.description ? `: ${p.description}` : ""}`,
|
|
51
|
+
)
|
|
52
|
+
.join("\n");
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return `Error listing plugins: ${e.message}`;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
function: {
|
|
58
|
+
name: "list_plugins",
|
|
59
|
+
description:
|
|
60
|
+
"List available plugins from the Saltcorn store. Call this before installing to find the right plugin name.",
|
|
61
|
+
parameters: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
category: {
|
|
65
|
+
type: "string",
|
|
66
|
+
enum: ["theme", "auth", "all"],
|
|
67
|
+
description:
|
|
68
|
+
"Filter plugins: 'theme' for UI themes, 'auth' for authentication plugins, 'all' for everything",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ["category"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "function",
|
|
77
|
+
process: async (input) => {
|
|
78
|
+
const label = input.plugin_name || input.npm_package;
|
|
79
|
+
if (!label) return "Please provide a plugin name or npm package.";
|
|
80
|
+
return `Installing plugin: ${label}...`;
|
|
81
|
+
},
|
|
82
|
+
postProcess: async ({ tool_call, req }) => {
|
|
83
|
+
const input = tool_call.input || {};
|
|
84
|
+
if (this.yoloMode) {
|
|
85
|
+
const result = await InstallPluginAction.execute(input, req);
|
|
86
|
+
return {
|
|
87
|
+
stop: true,
|
|
88
|
+
add_response: result.postExec || "Plugin installation complete.",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
stop: true,
|
|
93
|
+
add_user_action: {
|
|
94
|
+
name: "install_copilot_plugin",
|
|
95
|
+
type: "button",
|
|
96
|
+
label: `Install plugin ${input.plugin_name || input.npm_package}`,
|
|
97
|
+
input,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
function: {
|
|
102
|
+
name: InstallPluginAction.function_name,
|
|
103
|
+
description: InstallPluginAction.description,
|
|
104
|
+
parameters: InstallPluginAction.json_schema(),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = InstallPluginSkill;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const GenerateJsAction = require("../actions/generate-js-action");
|
|
2
|
+
const { getPromptFromTemplate } = require("../common");
|
|
3
|
+
|
|
4
|
+
class GenerateJsActionSkill {
|
|
5
|
+
static skill_name = "Javascript Action";
|
|
6
|
+
|
|
7
|
+
get skill_label() {
|
|
8
|
+
return "Javascript Action";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
constructor(cfg) {
|
|
12
|
+
Object.assign(this, cfg);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get userActions() {
|
|
16
|
+
return {
|
|
17
|
+
async build_copilot_js_action(input) {
|
|
18
|
+
const name = input.name;
|
|
19
|
+
const code = input.code;
|
|
20
|
+
const description = input.description;
|
|
21
|
+
const when_trigger = input.when_trigger;
|
|
22
|
+
const trigger_table = input.trigger_table;
|
|
23
|
+
const user = input.user;
|
|
24
|
+
if (!name || !code) {
|
|
25
|
+
return {
|
|
26
|
+
notify:
|
|
27
|
+
"Both name and code are required to generate a Javascript action.",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const result = await GenerateJsAction.execute(
|
|
31
|
+
{
|
|
32
|
+
action_javascript_code: code,
|
|
33
|
+
action_name: name,
|
|
34
|
+
action_description: description,
|
|
35
|
+
when_trigger,
|
|
36
|
+
trigger_table,
|
|
37
|
+
},
|
|
38
|
+
{ user },
|
|
39
|
+
);
|
|
40
|
+
return {
|
|
41
|
+
notify: result?.postExec || `Javascript action saved: ${name}`,
|
|
42
|
+
name,
|
|
43
|
+
code,
|
|
44
|
+
description,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
provideTools = () => {
|
|
51
|
+
return {
|
|
52
|
+
type: "function",
|
|
53
|
+
process: async (input) => {
|
|
54
|
+
const name = input.name;
|
|
55
|
+
const description = input.description;
|
|
56
|
+
if (!name) {
|
|
57
|
+
return "A name is required to generate a Javascript action.";
|
|
58
|
+
}
|
|
59
|
+
return [
|
|
60
|
+
`Generating Javascript action: ${name}.`,
|
|
61
|
+
description ? `Description: ${description}` : null,
|
|
62
|
+
]
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.join("\n");
|
|
65
|
+
},
|
|
66
|
+
postProcess: async ({ tool_call, generate }) => {
|
|
67
|
+
const input = tool_call.input || {};
|
|
68
|
+
const name = input.name;
|
|
69
|
+
const description = input.description;
|
|
70
|
+
const when_trigger = input.when_trigger;
|
|
71
|
+
const trigger_table = input.trigger_table;
|
|
72
|
+
if (!name) {
|
|
73
|
+
return {
|
|
74
|
+
stop: true,
|
|
75
|
+
add_response: "Cannot create Javascript action: name is required.",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const partPrompt = await getPromptFromTemplate(
|
|
80
|
+
"action-builder.txt",
|
|
81
|
+
"",
|
|
82
|
+
);
|
|
83
|
+
const contextParts = [
|
|
84
|
+
description ? `Action description: ${description}` : null,
|
|
85
|
+
when_trigger ? `Trigger: ${when_trigger}` : null,
|
|
86
|
+
trigger_table ? `Table: ${trigger_table}` : null,
|
|
87
|
+
].filter(Boolean);
|
|
88
|
+
|
|
89
|
+
const prompt = [
|
|
90
|
+
partPrompt,
|
|
91
|
+
contextParts.length ? contextParts.join("\n") : null,
|
|
92
|
+
`Generate the JavaScript code for the action named "${name}" by calling the generate_js_code tool.`,
|
|
93
|
+
]
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.join("\n\n");
|
|
96
|
+
|
|
97
|
+
const answer = await generate(prompt, {
|
|
98
|
+
tools: [
|
|
99
|
+
{
|
|
100
|
+
type: "function",
|
|
101
|
+
function: {
|
|
102
|
+
name: "generate_js_code",
|
|
103
|
+
description: "Provide the JavaScript code for the action",
|
|
104
|
+
parameters: {
|
|
105
|
+
type: "object",
|
|
106
|
+
required: ["code"],
|
|
107
|
+
properties: {
|
|
108
|
+
code: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "JavaScript code for the action",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
tool_choice: {
|
|
118
|
+
type: "function",
|
|
119
|
+
function: { name: "generate_js_code" },
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const tc = answer.getToolCalls()[0];
|
|
124
|
+
const code = tc?.input?.code;
|
|
125
|
+
|
|
126
|
+
if (!code) {
|
|
127
|
+
return {
|
|
128
|
+
stop: true,
|
|
129
|
+
add_response: "Failed to generate JavaScript code for the action.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
stop: true,
|
|
135
|
+
add_response: GenerateJsAction.render_html({
|
|
136
|
+
action_javascript_code: code,
|
|
137
|
+
action_name: name,
|
|
138
|
+
action_description: description,
|
|
139
|
+
when_trigger,
|
|
140
|
+
trigger_table,
|
|
141
|
+
}),
|
|
142
|
+
add_user_action: {
|
|
143
|
+
name: "build_copilot_js_action",
|
|
144
|
+
type: "button",
|
|
145
|
+
label: `Save Javascript action (${name})`,
|
|
146
|
+
input: { name, code, description, when_trigger, trigger_table },
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
function: {
|
|
151
|
+
name: GenerateJsAction.function_name,
|
|
152
|
+
description: GenerateJsAction.description,
|
|
153
|
+
parameters: {
|
|
154
|
+
type: "object",
|
|
155
|
+
required: ["name"],
|
|
156
|
+
properties: {
|
|
157
|
+
name: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Name of the Javascript action.",
|
|
160
|
+
},
|
|
161
|
+
description: {
|
|
162
|
+
type: "string",
|
|
163
|
+
description: "Description of the action.",
|
|
164
|
+
},
|
|
165
|
+
when_trigger: {
|
|
166
|
+
type: "string",
|
|
167
|
+
enum: ["Insert", "Update", "Delete", "Daily", "Hourly", "Weekly"],
|
|
168
|
+
description:
|
|
169
|
+
"The event that fires this action. Only set if the user explicitly wants this trigger to run on a table row event or schedule. Leave unset if the user has not specified when it should run.",
|
|
170
|
+
},
|
|
171
|
+
trigger_table: {
|
|
172
|
+
type: "string",
|
|
173
|
+
description:
|
|
174
|
+
"The table whose row events (Insert/Update/Delete) should fire this action. Only set if the user explicitly says this action should be triggered by changes to that table. Do NOT set just because the code reads data from a table.",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = GenerateJsActionSkill;
|