@saltcorn/agents 0.4.6 → 0.4.8

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
@@ -93,7 +93,6 @@ const configuration_workflow = (req) =>
93
93
  name: "stream",
94
94
  label: "Stream response",
95
95
  type: "Bool",
96
- sublabel: "Requires dynamic update (Event settings)",
97
96
  },
98
97
  {
99
98
  name: "placeholder",
@@ -159,6 +158,45 @@ const uploadForm = (viewname, req) =>
159
158
  span({ class: "ms-2 filename-label" })
160
159
  );
161
160
 
161
+ const realTimeCollabScript = (viewname) => {
162
+ const view = View.findOne({ name: viewname });
163
+ return script(
164
+ domReady(`
165
+ const callback = () => {
166
+ const collabCfg = {
167
+ events: {
168
+ ['${view.getRealTimeEventName(
169
+ "STREAM_CHUNK"
170
+ )}' + \`?page_load_tag=\${_sc_pageloadtag}\`]: async (data) => {
171
+ $('form.agent-view div.next_response_scratch').append(
172
+ data.content
173
+ );
174
+ }
175
+ }
176
+ };
177
+ let retries = 0
178
+ function init_it() {
179
+ if(window.io) init_collab_room('${viewname}', collabCfg);
180
+ else setTimeout(init_it, retries * 100);
181
+ retries+=1;
182
+ }
183
+ init_it();
184
+ };
185
+
186
+ if (ensure_script_loaded.length >= 2) {
187
+ ensure_script_loaded("/static_assets/${
188
+ db.connectObj.version_tag
189
+ }/socket.io.min.js", callback);
190
+ }
191
+ else {
192
+ ensure_script_loaded("/static_assets/${
193
+ db.connectObj.version_tag
194
+ }/socket.io.min.js");
195
+ callback();
196
+ }`)
197
+ );
198
+ };
199
+
162
200
  const run = async (
163
201
  table_id,
164
202
  viewname,
@@ -388,7 +426,8 @@ const run = async (
388
426
  skill_form_widgets,
389
427
  explainer && small({ class: "explainer" }, i(explainer))
390
428
  ),
391
- stream && div({ class: "next_response_scratch" })
429
+ stream &&
430
+ realTimeCollabScript(viewname) + div({ class: "next_response_scratch" })
392
431
  );
393
432
 
394
433
  const prev_runs_side_bar = div(
@@ -692,7 +731,7 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
692
731
  action.name,
693
732
  [],
694
733
  triggering_row,
695
- config.stream
734
+ config
696
735
  );
697
736
  };
698
737
 
package/common.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { getState } = require("@saltcorn/data/db/state");
2
2
  const { div, span } = require("@saltcorn/markup/tags");
3
3
  const Trigger = require("@saltcorn/data/models/trigger");
4
+ const View = require("@saltcorn/data/models/view");
4
5
  const { interpolate } = require("@saltcorn/data/utils");
5
6
  const db = require("@saltcorn/data/db");
6
7
 
@@ -163,11 +164,13 @@ const addToContext = async (run, newCtx) => {
163
164
  };
164
165
 
165
166
  const wrapSegment = (html, who) =>
166
- '<div class="interaction-segment"><span class="badge bg-secondary">' +
167
- who +
168
- "</span>" +
169
- html +
170
- "</div>";
167
+ who === null
168
+ ? html
169
+ : '<div class="interaction-segment"><span class="badge bg-secondary">' +
170
+ who +
171
+ "</span>" +
172
+ html +
173
+ "</div>";
171
174
 
172
175
  const wrapCard = (title, ...inners) =>
173
176
  span({ class: "badge bg-info ms-1" }, title) +
@@ -202,8 +205,9 @@ const process_interaction = async (
202
205
  agent_label = "Copilot",
203
206
  prevResponses = [],
204
207
  triggering_row = {},
205
- stream = false
208
+ agentsViewCfg = { stream: false }
206
209
  ) => {
210
+ const { stream, viewname } = agentsViewCfg;
207
211
  const sysState = getState();
208
212
  const complArgs = await getCompletionArguments(
209
213
  config,
@@ -217,21 +221,19 @@ const process_interaction = async (
217
221
  const debugMode = is_debug_mode(config, req.user);
218
222
  const debugCollector = {};
219
223
  if (debugMode) complArgs.debugCollector = debugCollector;
220
- if (stream && sysState.getConfig("enable_dynamic_updates") && req.user) {
224
+ if (stream && viewname) {
225
+ const view = View.findOne({ name: viewname });
221
226
  complArgs.streamCallback = (response) => {
222
227
  const content =
223
- response.choices[0].content || response.choices[0].delta?.content;
224
- if (content)
225
- sysState?.emitDynamicUpdate(
226
- db.getTenantSchema(),
227
- {
228
- eval_js: `$('form.agent-view div.next_response_scratch').append(${JSON.stringify(
229
- content
230
- )})`,
231
- page_load_tag: req.body.page_load_tag,
232
- },
233
- [req.user.id]
234
- );
228
+ typeof response === "string"
229
+ ? response
230
+ : response.choices[0].content || response.choices[0].delta?.content;
231
+ if (content) {
232
+ const pageLoadTag = req.body.page_load_tag;
233
+ view.emitRealTimeEvent(`STREAM_CHUNK?page_load_tag=${pageLoadTag}`, {
234
+ content,
235
+ });
236
+ }
235
237
  };
236
238
  }
237
239
  const answer = await sysState.functions.llm_generate.run("", complArgs);
@@ -272,61 +274,77 @@ const process_interaction = async (
272
274
  }
273
275
  }
274
276
  if (answer.content && !answer.tool_calls)
275
- responses.push(wrapSegment(md.render(answer.content), agent_label));
277
+ responses.push(
278
+ req.disable_markdown_render
279
+ ? answer
280
+ : wrapSegment(md.render(answer.content), agent_label)
281
+ );
276
282
  }
277
- await addToContext(run, {
278
- interactions:
279
- typeof answer === "object" && answer.tool_calls
280
- ? [
281
- {
282
- role: "assistant",
283
- tool_calls: answer.tool_calls,
284
- content: answer.content,
285
- },
286
- ]
287
- : [{ role: "assistant", content: answer }],
288
- });
283
+ if (answer.ai_sdk)
284
+ await addToContext(run, {
285
+ interactions: answer.messages,
286
+ });
287
+ else
288
+ await addToContext(run, {
289
+ interactions:
290
+ typeof answer === "object" && answer.tool_calls
291
+ ? [
292
+ {
293
+ role: "assistant",
294
+ tool_calls: answer.tool_calls,
295
+ content: answer.content,
296
+ },
297
+ ]
298
+ : [{ role: "assistant", content: answer }],
299
+ });
289
300
  if (
290
301
  answer &&
291
302
  typeof answer === "object" &&
292
303
  (answer.tool_calls || answer.mcp_calls)
293
304
  ) {
294
305
  if (answer.content)
295
- responses.push(wrapSegment(md.render(answer.content), agent_label));
306
+ responses.push(
307
+ req.disable_markdown_render
308
+ ? answer
309
+ : wrapSegment(md.render(answer.content), agent_label)
310
+ );
296
311
  //const actions = [];
297
312
  let hasResult = false;
298
313
  if ((answer.mcp_calls || []).length && !answer.content) hasResult = true;
299
314
  for (const tool_call of answer.tool_calls || []) {
300
- console.log("call function", tool_call.function?.name);
315
+ console.log(
316
+ "call function",
317
+ tool_call.toolName || tool_call.function?.name
318
+ );
301
319
 
302
320
  await addToContext(run, {
303
- funcalls: { [tool_call.id]: tool_call.function },
321
+ funcalls: {
322
+ [tool_call.id || tool_call.toolCallId]: answer.ai_sdk
323
+ ? tool_call
324
+ : tool_call.function,
325
+ },
304
326
  });
305
327
 
306
- const tool = find_tool(tool_call.function?.name, config);
328
+ const tool = find_tool(
329
+ tool_call.toolName || tool_call.function?.name,
330
+ config
331
+ );
307
332
 
308
333
  if (tool) {
309
- if (
310
- stream &&
311
- sysState.getConfig("enable_dynamic_updates") &&
312
- req.user
313
- ) {
334
+ if (stream && viewname) {
314
335
  let content =
315
336
  "Using skill: " + tool.skill.skill_label ||
316
337
  tool.skill.constructor.skill_name;
317
- sysState?.emitDynamicUpdate(
318
- db.getTenantSchema(),
319
- {
320
- eval_js: `$('form.agent-view div.next_response_scratch').append(${JSON.stringify(
321
- content
322
- )})`,
323
- page_load_tag: req.body.page_load_tag,
324
- },
325
- [req.user.id]
326
- );
338
+ const view = View.findOne({ name: viewname });
339
+ const pageLoadTag = req.body.page_load_tag;
340
+ view.emitRealTimeEvent(`STREAM_CHUNK?page_load_tag=${pageLoadTag}`, {
341
+ content,
342
+ });
327
343
  }
328
344
  if (tool.tool.renderToolCall) {
329
- const row = JSON.parse(tool_call.function.arguments);
345
+ const row = answer.ai_sdk
346
+ ? tool_call.input
347
+ : JSON.parse(tool_call.function.arguments);
330
348
  const rendered = await tool.tool.renderToolCall(row, {
331
349
  req,
332
350
  });
@@ -343,7 +361,9 @@ const process_interaction = async (
343
361
  }
344
362
  hasResult = true;
345
363
  const result = await tool.tool.process(
346
- JSON.parse(tool_call.function.arguments),
364
+ answer.ai_sdk
365
+ ? tool_call.input
366
+ : JSON.parse(tool_call.function.arguments),
347
367
  { req }
348
368
  );
349
369
  if (
@@ -367,20 +387,46 @@ const process_interaction = async (
367
387
  }
368
388
  hasResult = true;
369
389
  }
370
- await addToContext(run, {
371
- interactions: [
372
- {
373
- role: "tool",
374
- tool_call_id: tool_call.id,
375
- call_id: tool_call.call_id,
376
- name: tool_call.function.name,
377
- content:
378
- result && typeof result !== "string"
379
- ? JSON.stringify(result)
380
- : result || "Action run",
381
- },
382
- ],
383
- });
390
+ if (answer.ai_sdk)
391
+ await addToContext(run, {
392
+ interactions: [
393
+ {
394
+ role: "tool",
395
+ content: [
396
+ {
397
+ type: "tool-result",
398
+ toolCallId: tool_call.toolCallId,
399
+ toolName: tool_call.toolName,
400
+ output:
401
+ !result || typeof result === "string"
402
+ ? {
403
+ type: "text",
404
+ value: result || "Action run",
405
+ }
406
+ : {
407
+ type: "json",
408
+ value: JSON.parse(JSON.stringify(result)),
409
+ },
410
+ },
411
+ ],
412
+ },
413
+ ],
414
+ });
415
+ else
416
+ await addToContext(run, {
417
+ interactions: [
418
+ {
419
+ role: "tool",
420
+ tool_call_id: tool_call.toolCallId || tool_call.id,
421
+ call_id: tool_call.call_id,
422
+ name: tool_call.toolName || tool_call.function.name,
423
+ content:
424
+ result && typeof result !== "string"
425
+ ? JSON.stringify(result)
426
+ : result || "Action run",
427
+ },
428
+ ],
429
+ });
384
430
  }
385
431
  }
386
432
  if (hasResult)
@@ -391,10 +437,14 @@ const process_interaction = async (
391
437
  agent_label,
392
438
  [...prevResponses, ...responses],
393
439
  triggering_row,
394
- stream
440
+ agentsViewCfg
395
441
  );
396
442
  } else if (typeof answer === "string")
397
- responses.push(wrapSegment(md.render(answer), agent_label));
443
+ responses.push(
444
+ req.disable_markdown_render
445
+ ? answer
446
+ : wrapSegment(md.render(answer), agent_label)
447
+ );
398
448
 
399
449
  return {
400
450
  json: {
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const Workflow = require("@saltcorn/data/models/workflow");
2
2
  const Form = require("@saltcorn/data/models/form");
3
3
  const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
4
- const { features } = require("@saltcorn/data/db/state");
4
+ const Trigger = require("@saltcorn/data/models/trigger");
5
5
  const {
6
6
  get_skills,
7
7
  getCompletionArguments,
@@ -101,6 +101,40 @@ module.exports = {
101
101
  },
102
102
  },
103
103
  },
104
+ functions: {
105
+ agent_generate: {
106
+ run: async (agent_name, prompt, opts = {}) => {
107
+ const action = await Trigger.findOne({ name: agent_name });
108
+ const run = await WorkflowRun.create({
109
+ status: "Running",
110
+ started_by: opts.user?.id,
111
+ trigger_id: action.id,
112
+ context: {
113
+ implemented_fcall_ids: [],
114
+ interactions: [{ role: "user", content: prompt }],
115
+ funcalls: {},
116
+ },
117
+ });
118
+ const result = await process_interaction(
119
+ run,
120
+ action.configuration,
121
+ {
122
+ user: opts?.user,
123
+ body: {},
124
+ disable_markdown: opts?.disable_markdown_render
125
+ },
126
+ null
127
+ );
128
+ return result.json.response;
129
+ },
130
+ isAsync: true,
131
+ description: "Run an agent on a prompt",
132
+ arguments: [
133
+ { name: "agent_name", type: "String" },
134
+ { name: "prompt", type: "String" },
135
+ ],
136
+ },
137
+ },
104
138
  };
105
139
 
106
140
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {