@saltcorn/copilot 0.4.2 → 0.4.4
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/user-copilot.js +168 -134
package/package.json
CHANGED
package/user-copilot.js
CHANGED
|
@@ -107,6 +107,13 @@ const configuration_workflow = (req) =>
|
|
|
107
107
|
calcOptions: ["table_name", list_view_opts],
|
|
108
108
|
},
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
name: "exclude_fields",
|
|
112
|
+
label: "Exclude fields",
|
|
113
|
+
sublabel:
|
|
114
|
+
"Exclude fields from the chat context. Comma-separated list.",
|
|
115
|
+
type: "String",
|
|
116
|
+
},
|
|
110
117
|
],
|
|
111
118
|
}),
|
|
112
119
|
],
|
|
@@ -189,6 +196,17 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
|
|
|
189
196
|
case "assistant":
|
|
190
197
|
case "system":
|
|
191
198
|
if (interact.tool_calls) {
|
|
199
|
+
if (interact.content) {
|
|
200
|
+
interactMarkups.push(
|
|
201
|
+
div(
|
|
202
|
+
{ class: "interaction-segment" },
|
|
203
|
+
span({ class: "badge bg-secondary" }, "Copilot"),
|
|
204
|
+
typeof interact.content === "string"
|
|
205
|
+
? md.render(interact.content)
|
|
206
|
+
: interact.content
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
}
|
|
192
210
|
for (const tool_call of interact.tool_calls) {
|
|
193
211
|
const action = config.actions.find(
|
|
194
212
|
(a) => a.trigger_name === tool_call.function.name
|
|
@@ -205,17 +223,14 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
|
|
|
205
223
|
"Copilot"
|
|
206
224
|
)
|
|
207
225
|
);
|
|
208
|
-
} else if (tool_call.function.name
|
|
226
|
+
} else if (tool_call.function.name === "TableQuery") {
|
|
209
227
|
const query = JSON.parse(tool_call.function.arguments);
|
|
210
228
|
const queryText = query.sql_id_query
|
|
211
229
|
? query.sql_id_query
|
|
212
230
|
: JSON.stringify(query, null, 2);
|
|
213
231
|
interactMarkups.push(
|
|
214
232
|
wrapSegment(
|
|
215
|
-
wrapCard(
|
|
216
|
-
"Query " + tool_call.function.name.replace("Query", ""),
|
|
217
|
-
pre(queryText)
|
|
218
|
-
),
|
|
233
|
+
wrapCard("Query " + query.table_name, pre(queryText)),
|
|
219
234
|
"Copilot"
|
|
220
235
|
)
|
|
221
236
|
);
|
|
@@ -233,25 +248,40 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
|
|
|
233
248
|
);
|
|
234
249
|
break;
|
|
235
250
|
case "tool":
|
|
236
|
-
if (interact.name
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
table,
|
|
243
|
-
JSON.parse(interact.content),
|
|
244
|
-
config,
|
|
245
|
-
req
|
|
251
|
+
if (interact.name === "TableQuery") {
|
|
252
|
+
const tool_call = run.context.interactions
|
|
253
|
+
.map(
|
|
254
|
+
(i) =>
|
|
255
|
+
i.tool_calls &&
|
|
256
|
+
i.tool_calls.find((tc) => tc.id === interact.tool_call_id)
|
|
246
257
|
)
|
|
247
|
-
|
|
258
|
+
.filter(Boolean)[0];
|
|
259
|
+
if (tool_call) {
|
|
260
|
+
const args = JSON.parse(tool_call.function.arguments);
|
|
261
|
+
const table = Table.findOne(args.table_name);
|
|
262
|
+
interactMarkups.push(
|
|
263
|
+
await renderQueryInteraction(
|
|
264
|
+
table,
|
|
265
|
+
JSON.parse(interact.content),
|
|
266
|
+
config,
|
|
267
|
+
req
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
248
271
|
} else if (interact.content !== "Action run") {
|
|
272
|
+
let markupContent;
|
|
273
|
+
try {
|
|
274
|
+
markupContent = JSON.stringify(
|
|
275
|
+
JSON.parse(interact.content),
|
|
276
|
+
null,
|
|
277
|
+
2
|
|
278
|
+
);
|
|
279
|
+
} catch {
|
|
280
|
+
markupContent = interact.content;
|
|
281
|
+
}
|
|
249
282
|
interactMarkups.push(
|
|
250
283
|
wrapSegment(
|
|
251
|
-
wrapCard(
|
|
252
|
-
interact.name,
|
|
253
|
-
pre(JSON.stringify(JSON.parse(interact.content), null, 2))
|
|
254
|
-
),
|
|
284
|
+
wrapCard(interact.name, pre(markupContent)),
|
|
255
285
|
"Copilot"
|
|
256
286
|
)
|
|
257
287
|
);
|
|
@@ -436,84 +466,40 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
|
|
|
436
466
|
const getCompletionArguments = async (config) => {
|
|
437
467
|
let tools = [];
|
|
438
468
|
const sysPrompts = [];
|
|
439
|
-
|
|
440
|
-
let properties = {};
|
|
469
|
+
let properties = {};
|
|
441
470
|
|
|
442
|
-
|
|
471
|
+
const tableNames = (config?.tables || []).map((t) => t.table_name);
|
|
472
|
+
properties.table_name = {
|
|
473
|
+
type: "string",
|
|
474
|
+
enum: tableNames,
|
|
475
|
+
description: `Which table is this query from. Every query has to select rows from one table, even if it is based on joins from different tables`,
|
|
476
|
+
};
|
|
477
|
+
properties.sql_id_query = {
|
|
478
|
+
type: "string",
|
|
479
|
+
description: `An SQL query for this table's primary keys. This must select only the primary keys (even if the user wants a count), for example SELECT ${
|
|
480
|
+
tableNames[0][0]
|
|
481
|
+
}."${Table.findOne(tableNames[0]).pk_name}" from "${tableNames[0]}" ${
|
|
482
|
+
tableNames[0][0]
|
|
483
|
+
} JOIN ... where... Use this to join other tables in the database.`,
|
|
484
|
+
};
|
|
485
|
+
properties.is_count = {
|
|
486
|
+
type: "boolean",
|
|
487
|
+
description: `Is the only desired output a count? Make this true if the user wants a count of rows`,
|
|
488
|
+
};
|
|
443
489
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
{
|
|
454
|
-
type: "object",
|
|
455
|
-
properties: {
|
|
456
|
-
ilike: {
|
|
457
|
-
type: "string",
|
|
458
|
-
description: "Case insensitive substring match.",
|
|
459
|
-
},
|
|
460
|
-
in: {
|
|
461
|
-
type: "array",
|
|
462
|
-
description: "Match any one of these values",
|
|
463
|
-
items: {
|
|
464
|
-
type: "string",
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
];
|
|
470
|
-
}
|
|
471
|
-
if (field.type.name === "Integer" || field.type.name === "Float") {
|
|
472
|
-
const basetype = field.type.name === "Integer" ? "integer" : "number";
|
|
473
|
-
properties[field.name].anyOf = [
|
|
474
|
-
{ type: basetype, description: "Match this value exactly" },
|
|
475
|
-
{
|
|
476
|
-
type: "object",
|
|
477
|
-
properties: {
|
|
478
|
-
gt: {
|
|
479
|
-
type: basetype,
|
|
480
|
-
description: "Greater than this value",
|
|
481
|
-
},
|
|
482
|
-
lt: {
|
|
483
|
-
type: basetype,
|
|
484
|
-
description: "Less than this value",
|
|
485
|
-
},
|
|
486
|
-
in: {
|
|
487
|
-
type: "array",
|
|
488
|
-
description: "Match any one of these values",
|
|
489
|
-
items: {
|
|
490
|
-
type: basetype,
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
},
|
|
495
|
-
];
|
|
496
|
-
} else Object.assign(fieldProperties(field), properties[field.name]);
|
|
497
|
-
});
|
|
498
|
-
properties.sql_id_query = {
|
|
499
|
-
type: "string",
|
|
500
|
-
description: `An SQL query for this table's primary keys (${table.pk_name}). This must select only the primary keys, for example SELECT ${table.name[0]}."${table.pk_name}" from "${table.name}" ${table.name[0]} JOIN ... where... Use this to join other tables in the database. If you use sql_id_query, use it alone, do not combine with other field queries. Any other fields you want to filter on must be accessed in the WHERE clause`,
|
|
501
|
-
};
|
|
502
|
-
tools.push({
|
|
503
|
-
type: "function",
|
|
504
|
-
function: {
|
|
505
|
-
name: "Query" + table.name,
|
|
506
|
-
description: `Query the ${table.name} table. ${
|
|
507
|
-
table.description || ""
|
|
508
|
-
}`,
|
|
509
|
-
parameters: {
|
|
510
|
-
type: "object",
|
|
511
|
-
//required: ["action_javascript_code", "action_name"],
|
|
512
|
-
properties,
|
|
513
|
-
},
|
|
490
|
+
tools.push({
|
|
491
|
+
type: "function",
|
|
492
|
+
function: {
|
|
493
|
+
name: "TableQuery",
|
|
494
|
+
description: `Query a table and show the results to the user in a grid format`,
|
|
495
|
+
parameters: {
|
|
496
|
+
type: "object",
|
|
497
|
+
required: ["table_name", "sql_id_query", "is_count"],
|
|
498
|
+
properties,
|
|
514
499
|
},
|
|
515
|
-
}
|
|
516
|
-
}
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
|
|
517
503
|
for (const action of config?.actions || []) {
|
|
518
504
|
let properties = {};
|
|
519
505
|
|
|
@@ -574,7 +560,14 @@ ${t.fields
|
|
|
574
560
|
.join(",\n")}
|
|
575
561
|
)`
|
|
576
562
|
)
|
|
577
|
-
.join(";\n\n")
|
|
563
|
+
.join(";\n\n") +
|
|
564
|
+
`
|
|
565
|
+
|
|
566
|
+
Use the TableQuery tool if the user asks to see, find or count or otherwise access rows from a table that matches what the user is looking for, or if
|
|
567
|
+
the user is asking for a summary or inference from such rows. The TableQuery query is parametrised by a SQL SELECT query which
|
|
568
|
+
selects primary key values from the specified table. You can join other tables or use complex logic in the WHERE clause, but you must
|
|
569
|
+
always return porimary key values from the specified table.
|
|
570
|
+
`;
|
|
578
571
|
//console.log("sysprompt", systemPrompt);
|
|
579
572
|
|
|
580
573
|
if (tools.length === 0) tools = undefined;
|
|
@@ -611,7 +604,24 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
|
|
|
611
604
|
};
|
|
612
605
|
|
|
613
606
|
const renderQueryInteraction = async (table, result, config, req) => {
|
|
614
|
-
if (result
|
|
607
|
+
if (typeof result === "number")
|
|
608
|
+
return wrapSegment(
|
|
609
|
+
wrapCard(
|
|
610
|
+
"Query " + table?.name || "",
|
|
611
|
+
//div("Query: ", code(JSON.stringify(query))),
|
|
612
|
+
`${result}`
|
|
613
|
+
),
|
|
614
|
+
"Copilot"
|
|
615
|
+
);
|
|
616
|
+
if (result.length === 0)
|
|
617
|
+
return wrapSegment(
|
|
618
|
+
wrapCard(
|
|
619
|
+
"Query " + table?.name || "",
|
|
620
|
+
//div("Query: ", code(JSON.stringify(query))),
|
|
621
|
+
"No rows found"
|
|
622
|
+
),
|
|
623
|
+
"Copilot"
|
|
624
|
+
);
|
|
615
625
|
|
|
616
626
|
const tableCfg = config.tables.find((t) => t.table_name === table.name);
|
|
617
627
|
let viewRes = "";
|
|
@@ -652,6 +662,7 @@ const process_interaction = async (
|
|
|
652
662
|
) => {
|
|
653
663
|
const complArgs = await getCompletionArguments(config);
|
|
654
664
|
complArgs.chat = run.context.interactions;
|
|
665
|
+
//complArgs.debugResult = true;
|
|
655
666
|
//console.log(complArgs);
|
|
656
667
|
console.log("complArgs", JSON.stringify(complArgs, null, 2));
|
|
657
668
|
|
|
@@ -660,12 +671,20 @@ const process_interaction = async (
|
|
|
660
671
|
await addToContext(run, {
|
|
661
672
|
interactions:
|
|
662
673
|
typeof answer === "object" && answer.tool_calls
|
|
663
|
-
? [
|
|
674
|
+
? [
|
|
675
|
+
{
|
|
676
|
+
role: "assistant",
|
|
677
|
+
tool_calls: answer.tool_calls,
|
|
678
|
+
content: answer.content,
|
|
679
|
+
},
|
|
680
|
+
]
|
|
664
681
|
: [{ role: "assistant", content: answer }],
|
|
665
682
|
});
|
|
666
683
|
const responses = [];
|
|
667
684
|
|
|
668
685
|
if (typeof answer === "object" && answer.tool_calls) {
|
|
686
|
+
if (answer.content)
|
|
687
|
+
responses.push(wrapSegment(md.render(answer.content), "Copilot"));
|
|
669
688
|
//const actions = [];
|
|
670
689
|
let hasResult = false;
|
|
671
690
|
for (const tool_call of answer.tool_calls) {
|
|
@@ -724,51 +743,66 @@ const process_interaction = async (
|
|
|
724
743
|
},
|
|
725
744
|
],
|
|
726
745
|
});
|
|
727
|
-
} else if (tool_call.function.name
|
|
746
|
+
} else if (tool_call.function.name == "TableQuery") {
|
|
747
|
+
const query = JSON.parse(tool_call.function.arguments);
|
|
728
748
|
const table = Table.findOne({
|
|
729
|
-
name:
|
|
749
|
+
name: query.table_name,
|
|
730
750
|
});
|
|
731
|
-
const
|
|
732
|
-
let result;
|
|
733
|
-
if (query.sql_id_query) {
|
|
734
|
-
const is_sqlite = db.isSQLite;
|
|
735
|
-
|
|
736
|
-
const client = is_sqlite ? db : await db.getClient();
|
|
737
|
-
await client.query(`BEGIN;`);
|
|
738
|
-
if (!is_sqlite) {
|
|
739
|
-
await client.query(
|
|
740
|
-
`SET LOCAL search_path TO "${db.getTenantSchema()}";`
|
|
741
|
-
);
|
|
742
|
-
await client.query(
|
|
743
|
-
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`
|
|
744
|
-
);
|
|
745
|
-
}
|
|
751
|
+
const tableCfg = config.tables.find((t) => t.table_name === table.name);
|
|
746
752
|
|
|
747
|
-
|
|
748
|
-
await client.query(`ROLLBACK;`);
|
|
753
|
+
const is_sqlite = db.isSQLite;
|
|
749
754
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
forPublic: !req.user,
|
|
756
|
-
}
|
|
755
|
+
const client = is_sqlite ? db : await db.getClient();
|
|
756
|
+
await client.query(`BEGIN;`);
|
|
757
|
+
if (!is_sqlite) {
|
|
758
|
+
await client.query(
|
|
759
|
+
`SET LOCAL search_path TO "${db.getTenantSchema()}";`
|
|
757
760
|
);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
wrapCard(
|
|
761
|
-
"Query " + tool_call.function.name.replace("Query", ""),
|
|
762
|
-
pre(query.sql_id_query)
|
|
763
|
-
),
|
|
764
|
-
"Copilot"
|
|
765
|
-
)
|
|
761
|
+
await client.query(
|
|
762
|
+
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`
|
|
766
763
|
);
|
|
767
|
-
}
|
|
768
|
-
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const { rows } = await client.query(query.sql_id_query);
|
|
767
|
+
await client.query(`ROLLBACK;`);
|
|
768
|
+
|
|
769
|
+
if (!is_sqlite) client.release(true);
|
|
770
|
+
let result;
|
|
771
|
+
const id_query = {
|
|
772
|
+
[table.pk_name]: { in: rows.map((r) => r[table.pk_name]) },
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
if (query.is_count) {
|
|
776
|
+
const role = req.user?.role_id || 100;
|
|
777
|
+
if (role <= table.min_role_read) {
|
|
778
|
+
result = await table.countRows(id_query);
|
|
779
|
+
} else result = "Not authorized";
|
|
780
|
+
} else {
|
|
781
|
+
result = await table.getRows(id_query, {
|
|
769
782
|
forUser: req.user,
|
|
770
783
|
forPublic: !req.user,
|
|
771
784
|
});
|
|
785
|
+
if (tableCfg.exclude_fields) {
|
|
786
|
+
const fields = tableCfg.exclude_fields
|
|
787
|
+
.split(",")
|
|
788
|
+
.map((s) => s.trim());
|
|
789
|
+
fields.forEach((f) => {
|
|
790
|
+
result.forEach((r) => {
|
|
791
|
+
delete r[f];
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
responses.push(
|
|
797
|
+
wrapSegment(
|
|
798
|
+
wrapCard(
|
|
799
|
+
"Query " + tool_call.function.name.replace("Query", ""),
|
|
800
|
+
pre(query.sql_id_query)
|
|
801
|
+
),
|
|
802
|
+
"Copilot"
|
|
803
|
+
)
|
|
804
|
+
);
|
|
805
|
+
|
|
772
806
|
await addToContext(run, {
|
|
773
807
|
interactions: [
|
|
774
808
|
{
|