@saltcorn/agents 0.6.7 → 0.6.9

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/agent-view.js CHANGED
@@ -32,6 +32,7 @@ const {
32
32
  a,
33
33
  br,
34
34
  img,
35
+ text,
35
36
  } = require("@saltcorn/markup/tags");
36
37
  const { getState } = require("@saltcorn/data/db/state");
37
38
  const {
@@ -50,7 +51,7 @@ const {
50
51
  } = require("./common");
51
52
  const MarkdownIt = require("markdown-it"),
52
53
  md = new MarkdownIt();
53
- const { isWeb } = require("@saltcorn/data/utils");
54
+ const { isWeb, escapeHtml } = require("@saltcorn/data/utils");
54
55
  const path = require("path");
55
56
 
56
57
  const configuration_workflow = (req) =>
@@ -240,7 +241,7 @@ const run = async (
240
241
  state,
241
242
  { res, req },
242
243
  ) => {
243
- const action = agent_action || await Trigger.findOne({ id: action_id });
244
+ const action = agent_action || (await Trigger.findOne({ id: action_id }));
244
245
  if (!action) throw new Error(`Action not found: ${action_id}`);
245
246
  const prevRuns = show_prev_runs
246
247
  ? (
@@ -652,7 +653,7 @@ const run = async (
652
653
  ).join(" ");
653
654
  }
654
655
  $("span.filename-label").text("").removeClass("me-2");
655
- _agentDT.items.clear();
656
+ window._agentDT.items.clear();
656
657
  $("input#attach_agent_image").val(null);
657
658
  if(!not_final || (!${JSON.stringify(dyn_updates)})) $("#sendbuttonicon").attr("class","far fa-paper-plane");
658
659
  const $runidin= $("input[name=run_id")
@@ -672,20 +673,20 @@ const run = async (
672
673
  window.final_agent_response = () => {
673
674
  $("#sendbuttonicon").attr("class","far fa-paper-plane");
674
675
  }
675
- const _agentDT = new DataTransfer();
676
+ window._agentDT = new DataTransfer();
676
677
  function setAgentFiles(files) {
677
- for (const f of files) _agentDT.items.add(f);
678
- document.getElementById('attach_agent_image').files = _agentDT.files;
678
+ for (const f of files) window._agentDT.items.add(f);
679
+ document.getElementById('attach_agent_image').files = window._agentDT.files;
679
680
  updateFileLabel();
680
681
  }
681
682
  function updateFileLabel() {
682
- const n = _agentDT.files.length;
683
+ const n = window._agentDT.files.length;
683
684
  const $label = $(".attach_agent_image_wrap span.filename-label");
684
685
  if (n === 0) {
685
686
  $label.html("").removeClass("me-2");
686
687
  } else {
687
688
  $label.addClass("me-2");
688
- const text = n === 1 ? _agentDT.files[0].name : n + " files";
689
+ const text = n === 1 ? window._agentDT.files[0].name : n + " files";
689
690
  $label.html(${
690
691
  isWeb(req)
691
692
  ? `text + ' <span class="badge text-bg-secondary" style="cursor:pointer;font-size:.65em;vertical-align:middle" onclick="clearAgentFiles()" title="Remove files">&times;</span>'`
@@ -694,13 +695,13 @@ const run = async (
694
695
  }
695
696
  }
696
697
  function clearAgentFiles() {
697
- _agentDT.items.clear();
698
+ window._agentDT.items.clear();
698
699
  $("input#attach_agent_image").val(null);
699
700
  updateFileLabel();
700
701
  }
701
702
  window.clearAgentFiles = clearAgentFiles;
702
703
  function agent_file_attach(e) {
703
- _agentDT.items.clear();
704
+ window._agentDT.items.clear();
704
705
  setAgentFiles(e.target.files);
705
706
  }
706
707
  function restore_old_button_elem(btn) {
@@ -820,7 +821,8 @@ const run = async (
820
821
 
821
822
  const interact = async (table_id, viewname, config, body, { req, res }) => {
822
823
  const { userinput, run_id, triggering_row_id } = body;
823
- const action = config.agent_action || await Trigger.findOne({ id: config.action_id });
824
+ const action =
825
+ config.agent_action || (await Trigger.findOne({ id: config.action_id }));
824
826
 
825
827
  let run;
826
828
  let triggering_row;
@@ -951,7 +953,8 @@ const delprevrun = async (table_id, viewname, config, body, { req, res }) => {
951
953
 
952
954
  const debug_info = async (table_id, viewname, config, body, { req, res }) => {
953
955
  const { run_id, triggering_row_id } = body;
954
- const action = config.agent_action || await Trigger.findOne({ id: config.action_id });
956
+ const action =
957
+ config.agent_action || (await Trigger.findOne({ id: config.action_id }));
955
958
  let triggering_row;
956
959
  if (table_id && triggering_row_id) {
957
960
  const table = Table.findOne(table_id);
@@ -974,10 +977,12 @@ const debug_info = async (table_id, viewname, config, body, { req, res }) => {
974
977
  sysPrompt = complArgs.systemPrompt;
975
978
  }
976
979
  const debug_html = div(
977
- div(h4("System prompt"), pre(sysPrompt)),
980
+ div(h4("System prompt"), pre(text(escapeHtml(sysPrompt)))),
978
981
  div(
979
982
  h4("API interactions"),
980
- pre(JSON.stringify(run.context.api_interactions, null, 2)),
983
+ pre(
984
+ text(escapeHtml(JSON.stringify(run.context.api_interactions, null, 2))),
985
+ ),
981
986
  ),
982
987
  );
983
988
  if (run && req.user?.role_id === 1)
@@ -993,7 +998,8 @@ const debug_info = async (table_id, viewname, config, body, { req, res }) => {
993
998
 
994
999
  const skillroute = async (table_id, viewname, config, body, { req, res }) => {
995
1000
  const { run_id, triggering_row_id, skillid } = body;
996
- const action = config.agent_action || await Trigger.findOne({ id: config.action_id });
1001
+ const action =
1002
+ config.agent_action || (await Trigger.findOne({ id: config.action_id }));
997
1003
  let triggering_row;
998
1004
  if (table_id && triggering_row_id) {
999
1005
  const table = Table.findOne(table_id);
@@ -1030,7 +1036,8 @@ const execute_user_action = async (
1030
1036
  ) => {
1031
1037
  const { run_id, rndid, uaname } = body;
1032
1038
 
1033
- const action = config.agent_action || await Trigger.findOne({ id: config.action_id });
1039
+ const action =
1040
+ config.agent_action || (await Trigger.findOne({ id: config.action_id }));
1034
1041
  const run = await WorkflowRun.findOne({ id: +run_id });
1035
1042
  //console.log("run uas",run.context.user_actions );
1036
1043
 
package/common.js CHANGED
@@ -35,6 +35,8 @@ const get_skills = () => {
35
35
  require("./skills/ModelContextProtocol"),
36
36
  require("./skills/PromptPicker"),
37
37
  require("./skills/RunJsCode"),
38
+ require("./skills/GenerateAndRunJsCode"),
39
+ require("./skills/Fetch"),
38
40
  //require("./skills/AdaptiveFeedback"),
39
41
  ...exchange_skills,
40
42
  ];
@@ -429,7 +431,7 @@ const process_interaction = async (
429
431
  { role: "system", content: postprocres.add_system_prompt },
430
432
  ],
431
433
  });
432
- if (postprocres.add_response)
434
+ if (postprocres.add_response) {
433
435
  add_response(
434
436
  wrapSegment(
435
437
  wrapCard(
@@ -439,6 +441,26 @@ const process_interaction = async (
439
441
  agent_label,
440
442
  ),
441
443
  );
444
+ //replace tool response with this
445
+ // run.context.interactions.forEach((ic) => {});
446
+ const result = postprocres.add_response;
447
+ await sysState.functions.llm_add_message.run(
448
+ "tool_response",
449
+ !result || typeof result === "string"
450
+ ? {
451
+ type: "text",
452
+ value: result || "Action run",
453
+ }
454
+ : {
455
+ type: "json",
456
+ value: JSON.parse(JSON.stringify(result)),
457
+ },
458
+ {
459
+ chat: run.context.interactions,
460
+ tool_call,
461
+ },
462
+ );
463
+ }
442
464
  if (postprocres.add_user_action && viewname) {
443
465
  const user_actions = Array.isArray()
444
466
  ? postprocres.add_user_action
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -0,0 +1,62 @@
1
+ const { div, pre, a } = require("@saltcorn/markup/tags");
2
+ const Workflow = require("@saltcorn/data/models/workflow");
3
+ const Form = require("@saltcorn/data/models/form");
4
+ const Table = require("@saltcorn/data/models/table");
5
+ const User = require("@saltcorn/data/models/user");
6
+ const File = require("@saltcorn/data/models/file");
7
+ const View = require("@saltcorn/data/models/view");
8
+ const Trigger = require("@saltcorn/data/models/trigger");
9
+ const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
10
+ const { getState } = require("@saltcorn/data/db/state");
11
+ const db = require("@saltcorn/data/db");
12
+ const { eval_expression } = require("@saltcorn/data/models/expression");
13
+ const { interpolate, sleep } = require("@saltcorn/data/utils");
14
+ const { features } = require("@saltcorn/data/db/state");
15
+ const { button } = require("@saltcorn/markup/tags");
16
+ const { validID } = require("@saltcorn/markup/layout_utils");
17
+
18
+ const vm = require("vm");
19
+
20
+ //const { fieldProperties } = require("./helpers");
21
+
22
+ class FetchSkill {
23
+ static skill_name = "Fetch";
24
+
25
+ get skill_label() {
26
+ return "Fetch";
27
+ }
28
+
29
+ constructor(cfg) {
30
+ Object.assign(this, cfg);
31
+ }
32
+
33
+ static async configFields() {
34
+ return [];
35
+ }
36
+
37
+ provideTools = () => {
38
+ return {
39
+ type: "function",
40
+ process: async (row) => {
41
+ const resp = await fetch(row.url);
42
+ return await resp.text();
43
+ },
44
+ function: {
45
+ name: "fetch_web_page",
46
+ description: "fetch a web page with HTTP(S) GET",
47
+ parameters: {
48
+ type: "object",
49
+ required: ["url"],
50
+ properties: {
51
+ url: {
52
+ description: "The URL to fetch with HTTP",
53
+ type: "string",
54
+ },
55
+ },
56
+ },
57
+ },
58
+ };
59
+ };
60
+ }
61
+
62
+ module.exports = FetchSkill;
@@ -0,0 +1,422 @@
1
+ const { div, pre, a } = require("@saltcorn/markup/tags");
2
+ const Workflow = require("@saltcorn/data/models/workflow");
3
+ const Form = require("@saltcorn/data/models/form");
4
+ const Table = require("@saltcorn/data/models/table");
5
+ const User = require("@saltcorn/data/models/user");
6
+ const File = require("@saltcorn/data/models/file");
7
+ const View = require("@saltcorn/data/models/view");
8
+ const Trigger = require("@saltcorn/data/models/trigger");
9
+ const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
10
+ const { getState } = require("@saltcorn/data/db/state");
11
+ const db = require("@saltcorn/data/db");
12
+ const { eval_expression } = require("@saltcorn/data/models/expression");
13
+ const { interpolate, sleep } = require("@saltcorn/data/utils");
14
+ const { features } = require("@saltcorn/data/db/state");
15
+ const { button } = require("@saltcorn/markup/tags");
16
+ const { validID } = require("@saltcorn/markup/layout_utils");
17
+
18
+ const vm = require("vm");
19
+
20
+ //const { fieldProperties } = require("./helpers");
21
+
22
+ class GenerateAndRunJsCodeSkill {
23
+ static skill_name = "Generate and run JavaScript code";
24
+
25
+ get skill_label() {
26
+ return "Generate and run JavaScript code";
27
+ }
28
+
29
+ constructor(cfg) {
30
+ Object.assign(this, cfg);
31
+ }
32
+
33
+ async runCode(code, { user, req }) {
34
+ const sysState = getState();
35
+
36
+ const f = vm.runInNewContext(`async () => {${code}\n}`, {
37
+ ...(this.allow_table
38
+ ? {
39
+ Table: Table.subClass
40
+ ? Table.subClass({ user, read_only: this.read_only })
41
+ : Table,
42
+ }
43
+ : {}),
44
+ ...(this.allow_fetch ? { fetch } : {}),
45
+ user,
46
+ console,
47
+ sleep,
48
+ setTimeout,
49
+ });
50
+ return await f();
51
+ }
52
+
53
+ async systemPrompt({ triggering_row, user }) {
54
+ return this.add_sys_prompt || "";
55
+ }
56
+
57
+ async skillRoute({ run, triggering_row, req }) {
58
+ return await this.runCode({ row: triggering_row, run, user: req.user });
59
+ }
60
+
61
+ static async configFields() {
62
+ return [
63
+ {
64
+ name: "tool_name",
65
+ label: "Tool name",
66
+ type: "String",
67
+ class: "validate-identifier",
68
+ },
69
+ {
70
+ name: "tool_description",
71
+ label: "Tool description",
72
+ type: "String",
73
+ },
74
+
75
+ {
76
+ name: "code_description",
77
+ label: "Code description",
78
+ type: "String",
79
+ fieldview: "textarea",
80
+ },
81
+ {
82
+ name: "add_sys_prompt",
83
+ label: "Additional system prompt",
84
+ type: "String",
85
+ fieldview: "textarea",
86
+ },
87
+ {
88
+ name: "allow_fetch",
89
+ label: "Allow HTTP fetch",
90
+ type: "Bool",
91
+ },
92
+ {
93
+ name: "allow_table",
94
+ label: "Allow access to tables",
95
+ type: "Bool",
96
+ },
97
+ ...(Table.subClass
98
+ ? [
99
+ {
100
+ name: "table_read_only",
101
+ label: "Read only?",
102
+ type: "Bool",
103
+ showIf: { allow_table: true },
104
+ },
105
+ ]
106
+ : []),
107
+ ];
108
+ }
109
+
110
+ provideTools = () => {
111
+ return {
112
+ type: "function",
113
+ process: async (row, { req }) => {
114
+ return "Code generation tool activated";
115
+ //return await this.runCode({ row, user: req.user });
116
+ },
117
+ /*renderToolCall({ phrase }, { req }) {
118
+ return div({ class: "border border-primary p-2 m-2" }, phrase);
119
+ },*/
120
+ postProcess: async ({ tool_call, req, generate, ...rest }) => {
121
+ //console.log("postprocess args", { tool_call, ...rest });
122
+
123
+ const str = await generate(
124
+ `You will now be asked to write JavaScript code.
125
+ ${this.code_description ? "\nSome more information: " + this.code_description : ""}
126
+ ${this.allow_fetch ? "\nYou can use the standard fetch JavaScript function to make HTTP(S) requests." : ""}
127
+ ${this.allow_table ? getTablePrompt(this.read_only) : ""}
128
+
129
+ The code you write can use await at the top level, and should return
130
+ (at the top level) a string (which can contain HTML tags) with the response which will be shown to the user.
131
+
132
+ Now generate the JavaScript code required by the user.`,
133
+ );
134
+ //console.log("gen answer", str);
135
+
136
+ const js_code = str.includes("```javascript")
137
+ ? str.split("```javascript")[1].split("```")[0]
138
+ : str;
139
+
140
+ const res = await this.runCode(js_code, { user: req.user });
141
+ //console.log("code response", res);
142
+
143
+ return {
144
+ //stop: true,
145
+ add_response: res,
146
+ };
147
+ },
148
+ function: {
149
+ name: this.tool_name,
150
+ description: this.tool_description,
151
+ parameters: {
152
+ type: "object",
153
+ properties: {},
154
+ },
155
+ },
156
+ };
157
+ };
158
+ }
159
+
160
+ const getTablePrompt = (read_only) => {
161
+ const state = getState();
162
+ const tables = state.tables;
163
+ const tableLines = [];
164
+ tables.forEach((table) => {
165
+ const fieldLines = table.fields.map(
166
+ (f) =>
167
+ ` * ${f.name} with type: ${f.pretty_type.replace(
168
+ "Key to",
169
+ "ForeignKey referencing",
170
+ )}.${f.description ? ` ${f.description}` : ""}`,
171
+ );
172
+ tableLines.push(
173
+ `${table.name}${
174
+ table.description ? `: ${table.description}.` : "."
175
+ } Contains the following fields:\n${fieldLines.join("\n")}`,
176
+ );
177
+ });
178
+ return `Use the Table variable to access the Table class which gives you access to database tables
179
+
180
+ Example:
181
+
182
+ const someTable = Table.findOne({name: "Orders"})
183
+ await someTable.insertRow({name: "Alex", age: 43})
184
+ await someTable.deleteRows({id: order})
185
+
186
+
187
+ You can use the Table class to access database tables. Use this to create or delete tables and
188
+ their properties, or to query or change table rows.
189
+
190
+ To query, update, insert or delete rows in an existing table, first you should find the
191
+ table object with findOne.
192
+
193
+ Example:
194
+
195
+ Table.findOne({name: "Customers"}) // find the table with name "Customers"
196
+ Table.findOne("Customers") // find the table with name "Customers" (shortcut)
197
+ Table.findOne({ id: 5 }) // find the table with id=5
198
+ Table.findOne(5) // find the table with id=5 (shortcut)
199
+
200
+ Table.findOne is synchronous (no need to await), But the functions that query and manipulate
201
+ (such as insertRow, getRows, updateRow, deleteRows) rows are mostly asyncronous, so you can
202
+ put the await in front of the whole expression.
203
+
204
+ Example:
205
+ To count the number of rows in the customer table:
206
+
207
+ const nrows = await Table.findOne("Customers").countRows({})
208
+
209
+ Querying table rows
210
+
211
+ There are several methods you can use to retrieve rows in the database:
212
+
213
+ countRows: Count the number of rows in db table. The argument is a where-expression with conditions the
214
+ counted rows should match. countRows returns the number of matching rows wrapped in a promise.
215
+
216
+ countRows(where?): Promise<number>
217
+ Count amount of rows in db table
218
+
219
+ Parameters
220
+ Optional where: Where
221
+ Returns Promise<number>
222
+
223
+ Example of using countRows:
224
+ const bookTable = Table.findOne({name: "books"})
225
+
226
+ // Count the total number of rows in the books table
227
+ const totalNumberOfBooks = await bookTable.countRows({})
228
+
229
+ // Count the number of books where the cover_color field has the value is "Red"
230
+ const numberOfRedBooks = await bookTable.countRows({cover_color: "Red"})
231
+
232
+ // Count number of books with more than 500 pages
233
+ const numberOfLongBooks = await bookTable.countRows({pages: {gt: 500}})
234
+
235
+ getRows: Get all matching rows from the table in the database.
236
+
237
+ The arguments are the same as for getRow. The first argument is where-expression
238
+ with the conditions to match, and the second argument is an optional object and
239
+ allows you to set ordering and limit options. Keywords that can be used in the
240
+ second argument are orderBy, orderDesc, limit and offset.
241
+
242
+ getRows will return an array of rows matching the where-expression in the first
243
+ argument, wrapped in a Promise (use await to read the array).
244
+
245
+
246
+ getRows(where?, selopts?): Promise<Row[]>
247
+ Get rows from Table in db
248
+
249
+ Parameters
250
+ where: Where = {}
251
+ selopts: SelectOptions & ForUserRequest = {}
252
+ Returns Promise<Row[]>
253
+
254
+ Example of using getRows:
255
+
256
+ const bookTable = Table.findOne({name: "books"})
257
+
258
+ // get the rows in the book table with author = "Henrik Pontoppidan"
259
+ const myBooks = await bookTable.getRows({author: "Henrik Pontoppidan"})
260
+
261
+ // get the 3 most recent books written by "Henrik Pontoppidan" with more that 500 pages
262
+ const myBooks = await bookTable.getRows({author: "Henrik Pontoppidan", pages: {gt: 500}}, {orderBy: "published", orderDesc: true})
263
+
264
+ getRow: Get one row from the table in the database. The matching row will be returned in a promise -
265
+ use await to read the value. If no matching rule can be found, null will be returned. If more than one
266
+ row matches, the first found row will be returned.
267
+
268
+ The first argument to get row is a where-expression With the conditions the returned row should match.
269
+
270
+ The second document is optional and is an object that can modify the search. This is mainly useful in
271
+ case there is more than one matching row for the where-expression in the first argument and you want to
272
+ give an explicit order. For example, use {orderBy: "name"} as the second argument to pick the first
273
+ row by the name field, ordered ascending. {orderBy: "name", orderDesc: true} to order by name, descending
274
+
275
+ This is however rare and usually getRow is run with a single argument of a Where expression that uniquely
276
+ determines the row to return, if it exisits.
277
+
278
+ getRow(where?, selopts?): Promise<null | Row>
279
+ Get one row from table in db
280
+
281
+ Parameters
282
+ where: Where = {}
283
+ selopts: SelectOptions & ForUserRequest = {}
284
+ Returns Promise<null | Row>
285
+
286
+ Example of using getRow:
287
+ const bookTable = Table.findOne({name: "books"})
288
+
289
+ // get the row in the book table with id = 5
290
+ const myBook = await bookTable.getRow({id: 5})
291
+
292
+ // get the row for the last book published by Leo Tolstoy
293
+ const myBook = await bookTable.getRow({author: "Leo Tolstoy"}, {orderBy: "published", orderDesc: true})
294
+
295
+ getJoinedRows: To retrieve rows together with joinfields and aggregations
296
+
297
+ getJoinedRows(opts?): Promise<Row[]>
298
+ Get rows along with joined and aggregated fields. The argument to getJoinedRows is an object with several different possible fields, all of which are optional
299
+
300
+ where: A Where expression indicating the criterion to match
301
+ joinFields: An object with the joinfields to retrieve
302
+ aggregations: An object with the aggregations to retrieve
303
+ orderBy: A string with the name of the field to order by
304
+ orderDesc: If true, descending order
305
+ limit: A number with the maximum number of rows to retrieve
306
+ offset: The number of rows to skip in the result before returning rows
307
+ Parameters
308
+ Optional opts: any = {}
309
+ Returns Promise<Row[]>
310
+
311
+ Example of using getJoinedRows:
312
+
313
+ const patients = Table.findOne({ name: "patients" });
314
+ const patients_rows = await patients.getJoinedRows({
315
+ where: { age: { gt: 65 } },
316
+ orderBy: "id",
317
+ aggregations: {
318
+ avg_temp: {
319
+ table: "readings",
320
+ ref: "patient_id",
321
+ field: "temperature",
322
+ aggregate: "avg",
323
+ },
324
+ },
325
+ joinFields: {
326
+ pages: { ref: "favbook", target: "pages" },
327
+ author: { ref: "favbook", target: "author" },
328
+ },
329
+ });
330
+
331
+ These functions all take "Where expressions" which are JavaScript objects describing
332
+ the criterion to match to. Some examples:
333
+
334
+ { name: "Jim" }: Match all rows with name="Jim"
335
+ { name: { ilike: "im"} }: Match all rows where name contains "im" (case insensitive)
336
+ { name: /im/ }: Match all rows with name matching regular expression "im"
337
+ { age: { lt: 18 } }: Match all rows with age<18
338
+ { age: { lt: 18, equal: true } }: Match all rows with age<=18
339
+ { age: { gt: 18, lt: 65} }: Match all rows with 18<age<65
340
+ { name: { or: ["Harry", "Sally"] } }: Match all rows with name="Harry" or "Sally"
341
+ { or: [{ name: "Joe"}, { age: 37 }] }: Match all rows with name="Joe" or age=37
342
+ { not: { id: 5 } }: All rows except id=5
343
+ { id: { in: [1, 2, 3] } }: Rows with id 1, 2, or 3
344
+
345
+ There are two nearly identical functions for updating rows depending on how you want failures treated
346
+
347
+ ${
348
+ !read_only
349
+ ? `updateRow Update a row in the database table, throws an exception if update is invalid
350
+
351
+ updateRow(v_in, id, user?): Promise<string | void>
352
+ Update row
353
+
354
+ Parameters
355
+ v_in: any. columns with values to update
356
+
357
+ id: number. id value, table primary key
358
+
359
+ Optional user: Row
360
+
361
+ Example of using updateRow:
362
+
363
+ const bookTable = Table.findOne({name: "books"})
364
+
365
+ // get the row in the book table for Moby Dick
366
+ const moby_dick = await bookTable.getRow({title: "Moby Dick"})
367
+
368
+ // Update the read field to true and the rating field to 5 in the retrieved row.
369
+ await bookTable.updateRow({read: true, rating: 5}, moby_dick.id)
370
+
371
+ // if you want to update more than one row, you must first retrieve all the rows and
372
+ // then update them individually
373
+
374
+ const allBooks = await bookTable.getRows()
375
+ for(const book of allBooks) {
376
+ await bookTable.updateRow({price: book.price*0.8}, book.id)
377
+ }
378
+
379
+ tryUpdateRow Update a row, return an error message if update is invalid
380
+
381
+ There are two nearly identical functions for inserting a new row depending on how you want failures treated
382
+
383
+ insertRow insert a row, throws an exception if it is invalid
384
+ insertRow(v_in, user): Promise<any>
385
+ Insert row into the table. By passing in the user as the second argument, tt will check write rights. If a user object is not supplied, the insert goes ahead without checking write permissions.
386
+
387
+ Returns the primary key value of the inserted row.
388
+
389
+ This will throw an exception if the row does not conform to the table constraints. If you would like to insert a row with a function that can return an error message, use tryInsertRow instead.
390
+
391
+ Parameters
392
+ v_in: Row
393
+ Optional user: Row
394
+ Returns Promise<any>
395
+
396
+ Example of using insertRow:
397
+ await Table.findOne("People").insertRow({ name: "Jim", age: 35 })
398
+
399
+ tryInsertRow insert a row, return an error message if it is invalid
400
+
401
+ Use deleteRows to delete any number (zero, one or many) of rows matching a criterion. It uses the same where expression as the functions for querying rows
402
+ deleteRows(where, user?, noTrigger?): Promise<void>
403
+ Delete rows from table
404
+
405
+ Parameters
406
+ where: Where
407
+ condition
408
+
409
+ Optional user: Row
410
+ optional user, if null then no authorization will be checked
411
+
412
+ Optional noTrigger: boolean
413
+ Returns Promise<void>`
414
+ : ""
415
+ }
416
+
417
+ The following tables are present in the database:
418
+
419
+ ${tableLines.join("\n\n")}`;
420
+ };
421
+
422
+ module.exports = GenerateAndRunJsCodeSkill;