@saltcorn/agents 0.3.0 → 0.3.2
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 +61 -45
- package/common.js +59 -8
- package/package.json +1 -1
- package/skills/GenerateImage.js +98 -0
- package/skills/ModelContextProtocol.js +67 -0
- package/skills/PreloadData.js +0 -1
package/agent-view.js
CHANGED
|
@@ -39,6 +39,7 @@ const {
|
|
|
39
39
|
wrapCard,
|
|
40
40
|
wrapSegment,
|
|
41
41
|
process_interaction,
|
|
42
|
+
find_image_tool,
|
|
42
43
|
} = require("./common");
|
|
43
44
|
const MarkdownIt = require("markdown-it"),
|
|
44
45
|
md = new MarkdownIt();
|
|
@@ -157,7 +158,6 @@ const run = async (
|
|
|
157
158
|
for (const interact of run.context.interactions) {
|
|
158
159
|
switch (interact.role) {
|
|
159
160
|
case "user":
|
|
160
|
-
console.log(interact.content);
|
|
161
161
|
if (interact.content?.[0]?.type === "image_url") {
|
|
162
162
|
const image_url = interact.content[0].image_url.url;
|
|
163
163
|
if (image_url.startsWith("data"))
|
|
@@ -187,58 +187,74 @@ const run = async (
|
|
|
187
187
|
break;
|
|
188
188
|
case "assistant":
|
|
189
189
|
case "system":
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
190
|
+
|
|
191
|
+
for (const tool_call of interact.tool_calls || []) {
|
|
192
|
+
const toolSkill = find_tool(
|
|
193
|
+
tool_call.function.name,
|
|
194
|
+
action.configuration
|
|
195
|
+
);
|
|
196
|
+
if (toolSkill) {
|
|
197
|
+
const row = JSON.parse(tool_call.function.arguments);
|
|
198
|
+
if (toolSkill.tool.renderToolCall) {
|
|
199
|
+
const rendered = await toolSkill.tool.renderToolCall(row, {
|
|
200
|
+
req,
|
|
201
|
+
});
|
|
202
|
+
if (rendered)
|
|
203
|
+
interactMarkups.push(
|
|
204
|
+
wrapSegment(
|
|
205
|
+
wrapCard(
|
|
206
|
+
toolSkill.skill.skill_label ||
|
|
207
|
+
toolSkill.skill.constructor.skill_name,
|
|
208
|
+
rendered
|
|
209
|
+
),
|
|
210
|
+
action.name
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
201
214
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (toolSkill) {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
215
|
+
}
|
|
216
|
+
for (const image_call of interact.content?.image_calls || []) {
|
|
217
|
+
|
|
218
|
+
const toolSkill = find_image_tool(action.configuration);
|
|
219
|
+
if (toolSkill) {
|
|
220
|
+
if (toolSkill.tool.renderToolResponse) {
|
|
221
|
+
const rendered = await toolSkill.tool.renderToolResponse(
|
|
222
|
+
image_call,
|
|
223
|
+
{
|
|
211
224
|
req,
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (rendered)
|
|
229
|
+
interactMarkups.push(
|
|
230
|
+
wrapSegment(
|
|
231
|
+
wrapCard(
|
|
232
|
+
toolSkill.skill.skill_label ||
|
|
233
|
+
toolSkill.skill.constructor.skill_name,
|
|
234
|
+
rendered
|
|
235
|
+
),
|
|
236
|
+
action.name
|
|
237
|
+
)
|
|
238
|
+
);
|
|
225
239
|
}
|
|
226
240
|
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interactMarkups.push(
|
|
244
|
+
div(
|
|
245
|
+
{ class: "interaction-segment" },
|
|
246
|
+
span({ class: "badge bg-secondary" }, action.name),
|
|
247
|
+
typeof interact.content === "string"
|
|
248
|
+
? md.render(interact.content)
|
|
249
|
+
: typeof interact.content?.content === "string"
|
|
250
|
+
? md.render(interact.content.content)
|
|
251
|
+
: interact.content
|
|
252
|
+
)
|
|
253
|
+
);
|
|
237
254
|
break;
|
|
238
255
|
case "tool":
|
|
239
256
|
if (interact.content !== "Action run") {
|
|
240
257
|
let markupContent;
|
|
241
|
-
console.log("interact", interact);
|
|
242
258
|
const toolSkill = find_tool(interact.name, action.configuration);
|
|
243
259
|
try {
|
|
244
260
|
if (toolSkill?.tool?.renderToolResponse)
|
package/common.js
CHANGED
|
@@ -12,6 +12,8 @@ const get_skills = () => {
|
|
|
12
12
|
require("./skills/Trigger"),
|
|
13
13
|
require("./skills/Table"),
|
|
14
14
|
require("./skills/PreloadData"),
|
|
15
|
+
require("./skills/GenerateImage"),
|
|
16
|
+
require("./skills/ModelContextProtocol"),
|
|
15
17
|
//require("./skills/AdaptiveFeedback"),
|
|
16
18
|
];
|
|
17
19
|
};
|
|
@@ -34,7 +36,7 @@ const get_skill_instances = (config) => {
|
|
|
34
36
|
const find_tool = (name, config) => {
|
|
35
37
|
const skills = get_skill_instances(config);
|
|
36
38
|
for (const skill of skills) {
|
|
37
|
-
const skillTools = skill.provideTools();
|
|
39
|
+
const skillTools = skill.provideTools?.();
|
|
38
40
|
const tools = !skillTools
|
|
39
41
|
? []
|
|
40
42
|
: Array.isArray(skillTools)
|
|
@@ -45,6 +47,20 @@ const find_tool = (name, config) => {
|
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
|
|
50
|
+
const find_image_tool = (config) => {
|
|
51
|
+
const skills = get_skill_instances(config);
|
|
52
|
+
for (const skill of skills) {
|
|
53
|
+
const skillTools = skill.provideTools?.();
|
|
54
|
+
const tools = !skillTools
|
|
55
|
+
? []
|
|
56
|
+
: Array.isArray(skillTools)
|
|
57
|
+
? skillTools
|
|
58
|
+
: [skillTools];
|
|
59
|
+
const found = tools.find((t) => t?.type === "image_generation");
|
|
60
|
+
if (found) return { tool: found, skill };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
48
64
|
const getCompletionArguments = async (config, user) => {
|
|
49
65
|
let tools = [];
|
|
50
66
|
|
|
@@ -52,7 +68,7 @@ const getCompletionArguments = async (config, user) => {
|
|
|
52
68
|
|
|
53
69
|
const skills = get_skill_instances(config);
|
|
54
70
|
for (const skill of skills) {
|
|
55
|
-
const sysPr = await skill.systemPrompt({ user });
|
|
71
|
+
const sysPr = await skill.systemPrompt?.({ user });
|
|
56
72
|
if (sysPr) sysPrompts.push(sysPr);
|
|
57
73
|
const skillTools = skill.provideTools?.();
|
|
58
74
|
if (skillTools && Array.isArray(skillTools)) tools.push(...skillTools);
|
|
@@ -133,10 +149,43 @@ const process_interaction = async (
|
|
|
133
149
|
const complArgs = await getCompletionArguments(config, req.user);
|
|
134
150
|
complArgs.chat = run.context.interactions;
|
|
135
151
|
//complArgs.debugResult = true;
|
|
136
|
-
console.log("complArgs", JSON.stringify(complArgs, null, 2));
|
|
152
|
+
//console.log("complArgs", JSON.stringify(complArgs, null, 2));
|
|
137
153
|
|
|
138
154
|
const answer = await getState().functions.llm_generate.run("", complArgs);
|
|
139
155
|
console.log("answer", answer);
|
|
156
|
+
|
|
157
|
+
const responses = [];
|
|
158
|
+
if (answer && typeof answer === "object" && answer.image_calls) {
|
|
159
|
+
for (const image_call of answer.image_calls) {
|
|
160
|
+
const tool = find_image_tool(config);
|
|
161
|
+
let prcRes;
|
|
162
|
+
if (tool?.tool.process) {
|
|
163
|
+
prcRes = await tool.tool.process(image_call, { req });
|
|
164
|
+
if (prcRes?.result === null) delete image_call.result;
|
|
165
|
+
if (prcRes?.filename) image_call.filename = prcRes?.filename;
|
|
166
|
+
}
|
|
167
|
+
if (tool?.tool.renderToolResponse) {
|
|
168
|
+
const rendered = await tool.tool.renderToolResponse(
|
|
169
|
+
{ ...image_call, ...(prcRes || {}) },
|
|
170
|
+
{
|
|
171
|
+
req,
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
if (rendered)
|
|
175
|
+
responses.push(
|
|
176
|
+
wrapSegment(
|
|
177
|
+
wrapCard(
|
|
178
|
+
tool.skill.skill_label || tool.skill.constructor.skill_name,
|
|
179
|
+
rendered
|
|
180
|
+
),
|
|
181
|
+
agent_label
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (answer.content && !answer.tool_calls)
|
|
187
|
+
responses.push(wrapSegment(md.render(answer.content), agent_label));
|
|
188
|
+
}
|
|
140
189
|
await addToContext(run, {
|
|
141
190
|
interactions:
|
|
142
191
|
typeof answer === "object" && answer.tool_calls
|
|
@@ -149,14 +198,13 @@ const process_interaction = async (
|
|
|
149
198
|
]
|
|
150
199
|
: [{ role: "assistant", content: answer }],
|
|
151
200
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (typeof answer === "object" && answer.tool_calls) {
|
|
201
|
+
if (answer && typeof answer === "object" && (answer.tool_calls || answer.mcp_calls)) {
|
|
155
202
|
if (answer.content)
|
|
156
203
|
responses.push(wrapSegment(md.render(answer.content), agent_label));
|
|
157
204
|
//const actions = [];
|
|
158
205
|
let hasResult = false;
|
|
159
|
-
|
|
206
|
+
if ((answer.mcp_calls || []).length && !answer.content) hasResult = true;
|
|
207
|
+
for (const tool_call of answer.tool_calls || []) {
|
|
160
208
|
console.log("call function", tool_call.function);
|
|
161
209
|
|
|
162
210
|
await addToContext(run, {
|
|
@@ -213,6 +261,7 @@ const process_interaction = async (
|
|
|
213
261
|
{
|
|
214
262
|
role: "tool",
|
|
215
263
|
tool_call_id: tool_call.id,
|
|
264
|
+
call_id: tool_call.call_id,
|
|
216
265
|
name: tool_call.function.name,
|
|
217
266
|
content:
|
|
218
267
|
result && typeof result !== "string"
|
|
@@ -228,7 +277,8 @@ const process_interaction = async (
|
|
|
228
277
|
...prevResponses,
|
|
229
278
|
...responses,
|
|
230
279
|
]);
|
|
231
|
-
} else
|
|
280
|
+
} else if (typeof answer === "string")
|
|
281
|
+
responses.push(wrapSegment(md.render(answer), agent_label));
|
|
232
282
|
|
|
233
283
|
return {
|
|
234
284
|
json: {
|
|
@@ -251,4 +301,5 @@ module.exports = {
|
|
|
251
301
|
wrapCard,
|
|
252
302
|
wrapSegment,
|
|
253
303
|
process_interaction,
|
|
304
|
+
find_image_tool,
|
|
254
305
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { div, pre } = 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 File = require("@saltcorn/data/models/file");
|
|
6
|
+
const View = require("@saltcorn/data/models/view");
|
|
7
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
8
|
+
const db = require("@saltcorn/data/db");
|
|
9
|
+
const { eval_expression } = require("@saltcorn/data/models/expression");
|
|
10
|
+
const { interpolate } = require("@saltcorn/data/utils");
|
|
11
|
+
|
|
12
|
+
class GenerateImage {
|
|
13
|
+
static skill_name = "Image generation";
|
|
14
|
+
|
|
15
|
+
get skill_label() {
|
|
16
|
+
return `Image generation`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(cfg) {
|
|
20
|
+
Object.assign(this, cfg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async configFields() {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
name: "quality",
|
|
27
|
+
label: "Quality",
|
|
28
|
+
type: "String",
|
|
29
|
+
required: true,
|
|
30
|
+
attributes: { options: ["auto", "low", "medium", "high"] },
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "size",
|
|
34
|
+
label: "Size",
|
|
35
|
+
type: "String",
|
|
36
|
+
required: true,
|
|
37
|
+
attributes: {
|
|
38
|
+
options: ["auto", "1024x1024", "1536x1024", "1024x1536"],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "format",
|
|
43
|
+
label: "Format",
|
|
44
|
+
type: "String",
|
|
45
|
+
required: true,
|
|
46
|
+
attributes: { options: ["png", "jpeg", "webp"] },
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "transparent",
|
|
50
|
+
label: "Transparent",
|
|
51
|
+
type: "Bool",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "save_file",
|
|
55
|
+
label: "Save file",
|
|
56
|
+
type: "String",
|
|
57
|
+
required: true,
|
|
58
|
+
attributes: { options: ["Always", "Never"] }, //, "Button"] },
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
provideTools() {
|
|
64
|
+
const tool = {
|
|
65
|
+
type: "image_generation",
|
|
66
|
+
size: this.size,
|
|
67
|
+
quality: this.quality,
|
|
68
|
+
process: async (v, { req }) => {
|
|
69
|
+
if (this.save_file === "Always") {
|
|
70
|
+
const buf = Buffer.from(v.result, "base64");
|
|
71
|
+
const file = await File.from_contents(
|
|
72
|
+
`genimg.${v.output_format}`,
|
|
73
|
+
`image/${v.output_format}`,
|
|
74
|
+
buf,
|
|
75
|
+
req?.user?.id,
|
|
76
|
+
100
|
|
77
|
+
);
|
|
78
|
+
return { filename: file.path_to_serve, result: null };
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
renderToolResponse: (v) => {
|
|
82
|
+
const [ws, hs] = v.size.split("x");
|
|
83
|
+
if (v.filename)
|
|
84
|
+
return `<img height="${+hs / 4}" width="${
|
|
85
|
+
+ws / 4
|
|
86
|
+
}" src="/files/serve/${v.filename}" />`;
|
|
87
|
+
else
|
|
88
|
+
return `<img height="${+hs / 4}" width="${+ws / 4}" src="data:image/${
|
|
89
|
+
v.output_format
|
|
90
|
+
};base64, ${v.result}" />`;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
if (this.transparent) tool.background = "transparent";
|
|
94
|
+
return tool;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = GenerateImage;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const { div, pre } = 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 File = require("@saltcorn/data/models/file");
|
|
6
|
+
const View = require("@saltcorn/data/models/view");
|
|
7
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
8
|
+
const db = require("@saltcorn/data/db");
|
|
9
|
+
const { eval_expression } = require("@saltcorn/data/models/expression");
|
|
10
|
+
const { interpolate } = require("@saltcorn/data/utils");
|
|
11
|
+
|
|
12
|
+
class ModelContextProtocol {
|
|
13
|
+
static skill_name = "Model Context Protocol";
|
|
14
|
+
|
|
15
|
+
get skill_label() {
|
|
16
|
+
return `Model Context Protocol: ${this.server_label}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(cfg) {
|
|
20
|
+
Object.assign(this, cfg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async configFields() {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
name: "server_label",
|
|
27
|
+
label: "Server label",
|
|
28
|
+
type: "String",
|
|
29
|
+
required: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "server_url",
|
|
33
|
+
label: "Server URL",
|
|
34
|
+
type: "String",
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "header_key",
|
|
39
|
+
label: "Header key",
|
|
40
|
+
type: "String",
|
|
41
|
+
sublabel:
|
|
42
|
+
"Optional, for authorization. Which HTTP header should be set? For example <code>Authorization</code>",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "header_value",
|
|
46
|
+
label: "Header value",
|
|
47
|
+
type: "String",
|
|
48
|
+
sublabel:
|
|
49
|
+
"Optional, for authorization. What is the value of the HTTP header? For example <code>Bearer {api_key}</code>",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
provideTools() {
|
|
55
|
+
const tool = {
|
|
56
|
+
type: "mcp",
|
|
57
|
+
server_label: this.server_label,
|
|
58
|
+
server_url: this.server_url,
|
|
59
|
+
require_approval: "never",
|
|
60
|
+
};
|
|
61
|
+
if (this.header_key && this.header_value)
|
|
62
|
+
tool.headers = { [this.header_key]: this.header_value };
|
|
63
|
+
return tool;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = ModelContextProtocol;
|
package/skills/PreloadData.js
CHANGED