@tbrandenburg/node-red-agents 0.2.0 → 0.3.1
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/nodes/agent/agent.html
CHANGED
|
@@ -84,6 +84,9 @@
|
|
|
84
84
|
runtime: { value: 'direct' },
|
|
85
85
|
invocation: { value: 'prompt' },
|
|
86
86
|
|
|
87
|
+
agentName: { value: '' },
|
|
88
|
+
agentNameType: { value: 'str' },
|
|
89
|
+
|
|
87
90
|
model: { value: '' },
|
|
88
91
|
modelType: { value: 'str' },
|
|
89
92
|
|
|
@@ -140,6 +143,11 @@
|
|
|
140
143
|
MODEL_SUGGESTIONS.forEach((m) => datalist.append('<option value="' + m + '">'));
|
|
141
144
|
}
|
|
142
145
|
|
|
146
|
+
$('#node-input-agentName').typedInput({
|
|
147
|
+
typeField: '#node-input-agentNameType',
|
|
148
|
+
types: ['str', 'msg', 'flow', 'global', 'env']
|
|
149
|
+
});
|
|
150
|
+
|
|
143
151
|
$('#node-input-prompt').typedInput({
|
|
144
152
|
typeField: '#node-input-promptType',
|
|
145
153
|
types: ['msg', 'str', 'flow', 'global', 'env']
|
|
@@ -317,6 +325,15 @@
|
|
|
317
325
|
<input type="text" id="node-input-name" placeholder="Agent">
|
|
318
326
|
</div>
|
|
319
327
|
|
|
328
|
+
<div class="form-row">
|
|
329
|
+
<label for="node-input-agentName"><i class="fa fa-id-badge"></i> Agent name
|
|
330
|
+
<i class="fa fa-info-circle" style="color:#888; cursor:help; margin-left:4px;"
|
|
331
|
+
title="Reported as msg.agentName on the result/event outputs. Leave blank (str) to use the Name field above; set to msg/flow/global to make it dynamic per message."></i>
|
|
332
|
+
</label>
|
|
333
|
+
<input type="text" id="node-input-agentName" placeholder="leave blank to use the Name field above" style="width:60%">
|
|
334
|
+
<input type="hidden" id="node-input-agentNameType">
|
|
335
|
+
</div>
|
|
336
|
+
|
|
320
337
|
<div class="form-row">
|
|
321
338
|
<label for="node-input-agent"><i class="fa fa-cube"></i> Agent</label>
|
|
322
339
|
<select id="node-input-agent" style="width:70%">
|
|
@@ -494,6 +511,16 @@
|
|
|
494
511
|
conversation on the next message.</dd>
|
|
495
512
|
<dt>agentExecution <span class="property-type">object</span></dt>
|
|
496
513
|
<dd>id, status, exitCode, signal, timedOut, durationMs, sessionID.</dd>
|
|
514
|
+
<dt>agentId <span class="property-type">string</span></dt>
|
|
515
|
+
<dd>This node instance's own Node-RED id, so a flow with
|
|
516
|
+
several agent nodes can tell which one produced a
|
|
517
|
+
given message.</dd>
|
|
518
|
+
<dt>agentName <span class="property-type">string</span></dt>
|
|
519
|
+
<dd>The resolved Agent name field, falling back to this node
|
|
520
|
+
instance's configured Name if the Agent name field is
|
|
521
|
+
blank/unresolved for this message (e.g. via
|
|
522
|
+
<code>msg.agentName</code> when configured as a msg
|
|
523
|
+
property).</dd>
|
|
497
524
|
</dl>
|
|
498
525
|
</li>
|
|
499
526
|
<li>Events
|
|
@@ -504,6 +531,10 @@
|
|
|
504
531
|
<dt>executionId <span class="property-type">string</span></dt>
|
|
505
532
|
<dd>Correlates events (and the eventual result) belonging to
|
|
506
533
|
the same execution when several run concurrently.</dd>
|
|
534
|
+
<dt>agentId <span class="property-type">string</span></dt>
|
|
535
|
+
<dd>This node instance's own Node-RED id (see Result output).</dd>
|
|
536
|
+
<dt>agentName <span class="property-type">string</span></dt>
|
|
537
|
+
<dd>The resolved Agent name for this execution (see Result output).</dd>
|
|
507
538
|
</dl>
|
|
508
539
|
</li>
|
|
509
540
|
</ol>
|
package/nodes/agent/agent.js
CHANGED
|
@@ -45,6 +45,14 @@ module.exports = function (RED) {
|
|
|
45
45
|
node.runtime = config.runtime || "direct";
|
|
46
46
|
node.invocation = config.invocation || "prompt";
|
|
47
47
|
|
|
48
|
+
// Typed-input override of the agentName reported on outgoing/event
|
|
49
|
+
// messages (see lifecycleEnvelope below). Same typed-input pattern as
|
|
50
|
+
// every other overridable field: blank/unresolved falls back to
|
|
51
|
+
// node.name, so existing flows relying on the (previously static)
|
|
52
|
+
// node.name value keep working unchanged.
|
|
53
|
+
node.agentName = config.agentName !== undefined ? config.agentName : "";
|
|
54
|
+
node.agentNameType = config.agentNameType || "str";
|
|
55
|
+
|
|
48
56
|
node.model = config.model || "";
|
|
49
57
|
node.modelType = config.modelType || "str";
|
|
50
58
|
|
|
@@ -150,19 +158,30 @@ module.exports = function (RED) {
|
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
160
|
|
|
161
|
+
// Per-message resolution of the reported agentName: the typed-input
|
|
162
|
+
// agentName/agentNameType field (e.g. msg.agentName) if configured and
|
|
163
|
+
// present, else node.name -- same fallback behavior a blank/unset
|
|
164
|
+
// typed-input field has always had for every other field.
|
|
165
|
+
function resolveAgentName(msg) {
|
|
166
|
+
const v = resolveTyped(node.agentName, node.agentNameType, msg, node.name);
|
|
167
|
+
return v === undefined || v === null || v === "" ? node.name : String(v).trim();
|
|
168
|
+
}
|
|
169
|
+
|
|
153
170
|
// Common envelope for every message on output 2 (the lifecycle/event
|
|
154
171
|
// stream): correlation (topic, executionId), live scheduler counts
|
|
155
172
|
// (so a widget bound to this stream always has the current
|
|
156
173
|
// active/queued numbers, no separate polling needed) and a
|
|
157
174
|
// timestamp (so external aggregation/history doesn't have to rely
|
|
158
175
|
// on message-arrival time).
|
|
159
|
-
function lifecycleEnvelope(msg, executionId, payload) {
|
|
176
|
+
function lifecycleEnvelope(msg, executionId, payload, agentName) {
|
|
160
177
|
return {
|
|
161
178
|
_msgid: msg._msgid,
|
|
162
179
|
topic: msg.topic,
|
|
163
180
|
payload,
|
|
164
181
|
agent: node.agent,
|
|
165
182
|
runtime: node.runtime,
|
|
183
|
+
agentId: node.id,
|
|
184
|
+
agentName,
|
|
166
185
|
executionId,
|
|
167
186
|
active: node.scheduler.activeCount,
|
|
168
187
|
queued: node.scheduler.queuedCount,
|
|
@@ -170,8 +189,8 @@ module.exports = function (RED) {
|
|
|
170
189
|
};
|
|
171
190
|
}
|
|
172
191
|
|
|
173
|
-
function emitEvent(send, msg, executionId, type) {
|
|
174
|
-
send([null, lifecycleEnvelope(msg, executionId, { type })]);
|
|
192
|
+
function emitEvent(send, msg, executionId, type, agentName) {
|
|
193
|
+
send([null, lifecycleEnvelope(msg, executionId, { type }, agentName)]);
|
|
175
194
|
}
|
|
176
195
|
|
|
177
196
|
// The actual work for one execution. Only ever invoked by the
|
|
@@ -188,11 +207,11 @@ module.exports = function (RED) {
|
|
|
188
207
|
resolved,
|
|
189
208
|
executionId,
|
|
190
209
|
onEvent: (event) => {
|
|
191
|
-
send([null, lifecycleEnvelope(msg, executionId, event)]);
|
|
210
|
+
send([null, lifecycleEnvelope(msg, executionId, event, resolved.agentName)]);
|
|
192
211
|
},
|
|
193
212
|
onStatus: (status) => {
|
|
194
213
|
if (status === "running") {
|
|
195
|
-
emitEvent(send, msg, executionId, "running");
|
|
214
|
+
emitEvent(send, msg, executionId, "running", resolved.agentName);
|
|
196
215
|
} else {
|
|
197
216
|
// Terminal (completed/failed/timeout): stash rather
|
|
198
217
|
// than emit immediately -- the scheduler hasn't
|
|
@@ -214,6 +233,8 @@ module.exports = function (RED) {
|
|
|
214
233
|
payload: result.payload,
|
|
215
234
|
agent: node.agent,
|
|
216
235
|
runtime: node.runtime,
|
|
236
|
+
agentId: node.id,
|
|
237
|
+
agentName: resolved.agentName,
|
|
217
238
|
// Top-level, in addition to agentExecution.sessionID
|
|
218
239
|
// below: matches the agent-server node's convention
|
|
219
240
|
// so this output can be fed straight back into the
|
|
@@ -261,13 +282,22 @@ module.exports = function (RED) {
|
|
|
261
282
|
node.scheduler = new ExecutionScheduler({
|
|
262
283
|
concurrency: node.concurrency,
|
|
263
284
|
onStart: startExecution,
|
|
264
|
-
onQueued: (item) =>
|
|
285
|
+
onQueued: (item) =>
|
|
286
|
+
emitEvent(item.send, item.msg, item.executionId, "queued", item.resolved.agentName),
|
|
265
287
|
// Runs after this item is removed from `active` and any newly-
|
|
266
288
|
// eligible queued item has already been started, so the terminal
|
|
267
289
|
// event's active/queued counts are accurate (see the onStatus
|
|
268
290
|
// comment in startExecution for why it's deferred to here).
|
|
269
291
|
onSettled: (item) => {
|
|
270
|
-
if (item.finalStatus)
|
|
292
|
+
if (item.finalStatus) {
|
|
293
|
+
emitEvent(
|
|
294
|
+
item.send,
|
|
295
|
+
item.msg,
|
|
296
|
+
item.executionId,
|
|
297
|
+
item.finalStatus,
|
|
298
|
+
item.resolved.agentName,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
271
301
|
updateStatus();
|
|
272
302
|
},
|
|
273
303
|
});
|
|
@@ -287,6 +317,7 @@ module.exports = function (RED) {
|
|
|
287
317
|
return;
|
|
288
318
|
}
|
|
289
319
|
|
|
320
|
+
const agentName = resolveAgentName(msg);
|
|
290
321
|
const result = node.scheduler.cancel(executionId);
|
|
291
322
|
if (!result) {
|
|
292
323
|
done(new Error(`agent: unknown or already-finished executionId "${executionId}"`));
|
|
@@ -295,7 +326,13 @@ module.exports = function (RED) {
|
|
|
295
326
|
|
|
296
327
|
if (result.status === "queued") {
|
|
297
328
|
const queuedItem = result.item;
|
|
298
|
-
emitEvent(
|
|
329
|
+
emitEvent(
|
|
330
|
+
queuedItem.send,
|
|
331
|
+
queuedItem.msg,
|
|
332
|
+
executionId,
|
|
333
|
+
"cancelled",
|
|
334
|
+
queuedItem.resolved ? queuedItem.resolved.agentName : agentName,
|
|
335
|
+
);
|
|
299
336
|
queuedItem.done(
|
|
300
337
|
new Error("agent: execution cancelled before it started (terminate requested)"),
|
|
301
338
|
);
|
|
@@ -303,6 +340,8 @@ module.exports = function (RED) {
|
|
|
303
340
|
send([
|
|
304
341
|
Object.assign({}, msg, {
|
|
305
342
|
payload: { executionId, terminated: true, status: "cancelled" },
|
|
343
|
+
agentId: node.id,
|
|
344
|
+
agentName,
|
|
306
345
|
}),
|
|
307
346
|
null,
|
|
308
347
|
]);
|
|
@@ -320,6 +359,8 @@ module.exports = function (RED) {
|
|
|
320
359
|
send([
|
|
321
360
|
Object.assign({}, msg, {
|
|
322
361
|
payload: { executionId, terminated: true, status: "terminating" },
|
|
362
|
+
agentId: node.id,
|
|
363
|
+
agentName,
|
|
323
364
|
}),
|
|
324
365
|
null,
|
|
325
366
|
]);
|
|
@@ -354,6 +395,7 @@ module.exports = function (RED) {
|
|
|
354
395
|
let resolved;
|
|
355
396
|
try {
|
|
356
397
|
resolved = {
|
|
398
|
+
agentName: resolveAgentName(msg),
|
|
357
399
|
invocation: node.invocation,
|
|
358
400
|
prompt:
|
|
359
401
|
node.invocation === "prompt"
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
defaults: {
|
|
19
19
|
name: { value: '' },
|
|
20
20
|
|
|
21
|
+
serverName: { value: '' },
|
|
22
|
+
serverNameType: { value: 'str' },
|
|
23
|
+
|
|
21
24
|
operation: { value: 'message' },
|
|
22
25
|
|
|
23
26
|
hostname: { value: '127.0.0.1' },
|
|
@@ -71,6 +74,10 @@
|
|
|
71
74
|
typeField: '#node-input-modelType',
|
|
72
75
|
types: ['str', 'msg', 'flow', 'global', 'env']
|
|
73
76
|
});
|
|
77
|
+
$('#node-input-serverName').typedInput({
|
|
78
|
+
typeField: '#node-input-serverNameType',
|
|
79
|
+
types: ['str', 'msg', 'flow', 'global', 'env']
|
|
80
|
+
});
|
|
74
81
|
|
|
75
82
|
function updateOperationRows() {
|
|
76
83
|
const op = $('#node-input-operation').val();
|
|
@@ -141,6 +148,15 @@
|
|
|
141
148
|
<input type="text" id="node-input-name" placeholder="Agent Server">
|
|
142
149
|
</div>
|
|
143
150
|
|
|
151
|
+
<div class="form-row">
|
|
152
|
+
<label for="node-input-serverName"><i class="fa fa-id-badge"></i> Server name
|
|
153
|
+
<i class="fa fa-info-circle" style="color:#888; cursor:help; margin-left:4px;"
|
|
154
|
+
title="Reported as msg.serverName on the result/event outputs. Leave blank (str) to use the Name field above; set to msg/flow/global to make it dynamic per message."></i>
|
|
155
|
+
</label>
|
|
156
|
+
<input type="text" id="node-input-serverName" placeholder="leave blank to use the Name field above" style="width:60%">
|
|
157
|
+
<input type="hidden" id="node-input-serverNameType">
|
|
158
|
+
</div>
|
|
159
|
+
|
|
144
160
|
<div class="form-row">
|
|
145
161
|
<label for="node-input-operation"><i class="fa fa-play"></i> Operation</label>
|
|
146
162
|
<select id="node-input-operation" style="width:70%">
|
|
@@ -354,6 +370,16 @@
|
|
|
354
370
|
<dd>Operation-dependent: assistant reply text (message), a status object, etc.</dd>
|
|
355
371
|
<dt>sessionID <span class="property-type">string</span></dt>
|
|
356
372
|
<dd>Echoed back so a later trigger can reuse the same daemon/session.</dd>
|
|
373
|
+
<dt>serverId <span class="property-type">string</span></dt>
|
|
374
|
+
<dd>This node instance's own Node-RED id, so a flow with
|
|
375
|
+
several agent-server nodes can tell which one produced a
|
|
376
|
+
given message.</dd>
|
|
377
|
+
<dt>serverName <span class="property-type">string</span></dt>
|
|
378
|
+
<dd>The resolved Server name field, falling back to this node
|
|
379
|
+
instance's configured Name if the Server name field is
|
|
380
|
+
blank/unresolved for this message (e.g. via
|
|
381
|
+
<code>msg.serverName</code> when configured as a msg
|
|
382
|
+
property).</dd>
|
|
357
383
|
</dl>
|
|
358
384
|
</li>
|
|
359
385
|
<li>Lifecycle events
|
|
@@ -363,6 +389,10 @@
|
|
|
363
389
|
<code>failed</code> / <code>closed</code>, each with <code>sessionID</code>,
|
|
364
390
|
<code>timestamp</code>, and this node instance's live <code>active</code>
|
|
365
391
|
(busy) / <code>tracked</code> (total) daemon counts.</dd>
|
|
392
|
+
<dt>serverId <span class="property-type">string</span></dt>
|
|
393
|
+
<dd>This node instance's own Node-RED id (see Result output).</dd>
|
|
394
|
+
<dt>serverName <span class="property-type">string</span></dt>
|
|
395
|
+
<dd>The resolved Server name for this event (see Result output).</dd>
|
|
366
396
|
</dl>
|
|
367
397
|
</li>
|
|
368
398
|
</ol>
|
|
@@ -21,6 +21,12 @@ module.exports = function (RED) {
|
|
|
21
21
|
|
|
22
22
|
node.operation = config.operation || "message";
|
|
23
23
|
|
|
24
|
+
// Typed-input override of the serverName reported on outgoing/event
|
|
25
|
+
// messages (see emitEvent below). Same typed-input pattern as every
|
|
26
|
+
// other overridable field: blank/unresolved falls back to node.name.
|
|
27
|
+
node.serverName = config.serverName !== undefined ? config.serverName : "";
|
|
28
|
+
node.serverNameType = config.serverNameType || "str";
|
|
29
|
+
|
|
24
30
|
node.sessionIdProp = config.sessionIdProp !== undefined ? config.sessionIdProp : "sessionID";
|
|
25
31
|
node.sessionIdPropType = config.sessionIdPropType || "msg";
|
|
26
32
|
|
|
@@ -105,6 +111,8 @@ module.exports = function (RED) {
|
|
|
105
111
|
topic: msg && msg.topic,
|
|
106
112
|
payload: { type },
|
|
107
113
|
sessionID,
|
|
114
|
+
serverId: node.id,
|
|
115
|
+
serverName: resolveServerName(msg),
|
|
108
116
|
active: summary.busy,
|
|
109
117
|
tracked: summary.total,
|
|
110
118
|
timestamp: Date.now(),
|
|
@@ -122,6 +130,16 @@ module.exports = function (RED) {
|
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
// Per-message resolution of the reported serverName: the typed-input
|
|
134
|
+
// serverName/serverNameType field (e.g. msg.serverName) if configured
|
|
135
|
+
// and present, else node.name -- same fallback every other typed-input
|
|
136
|
+
// field has when blank/unresolved. Tolerates a missing msg (emitEvent
|
|
137
|
+
// is sometimes called without one, e.g. from spawnNewInstance's own
|
|
138
|
+
// internal bookkeeping) by resolving against an empty object.
|
|
139
|
+
function resolveServerName(msg) {
|
|
140
|
+
return resolveTyped(node.serverName, node.serverNameType, msg || {}, node.name);
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
function authOptions() {
|
|
126
144
|
return node.authUsername || node.authPassword
|
|
127
145
|
? { username: node.authUsername, password: node.authPassword }
|
|
@@ -236,6 +254,8 @@ module.exports = function (RED) {
|
|
|
236
254
|
const resultMsg = Object.assign({}, msg, {
|
|
237
255
|
payload: extractText(response),
|
|
238
256
|
sessionID,
|
|
257
|
+
serverId: node.id,
|
|
258
|
+
serverName: resolveServerName(msg),
|
|
239
259
|
agentServer: {
|
|
240
260
|
sessionID,
|
|
241
261
|
host: record && record.host,
|
|
@@ -330,7 +350,14 @@ module.exports = function (RED) {
|
|
|
330
350
|
if (!sessionID) {
|
|
331
351
|
// Aggregate across every daemon this node instance is
|
|
332
352
|
// tracking -- purely local, no network calls.
|
|
333
|
-
send([
|
|
353
|
+
send([
|
|
354
|
+
Object.assign({}, msg, {
|
|
355
|
+
payload: node.registry.summary(),
|
|
356
|
+
serverId: node.id,
|
|
357
|
+
serverName: resolveServerName(msg),
|
|
358
|
+
}),
|
|
359
|
+
null,
|
|
360
|
+
]);
|
|
334
361
|
done();
|
|
335
362
|
return;
|
|
336
363
|
}
|
|
@@ -355,6 +382,8 @@ module.exports = function (RED) {
|
|
|
355
382
|
startedAt: record.startedAt,
|
|
356
383
|
lastUsed: record.lastUsed,
|
|
357
384
|
},
|
|
385
|
+
serverId: node.id,
|
|
386
|
+
serverName: resolveServerName(msg),
|
|
358
387
|
}),
|
|
359
388
|
null,
|
|
360
389
|
]);
|
|
@@ -387,7 +416,15 @@ module.exports = function (RED) {
|
|
|
387
416
|
.then(() => {
|
|
388
417
|
node.registry.setBusy(sessionID, false);
|
|
389
418
|
updateStatus();
|
|
390
|
-
send([
|
|
419
|
+
send([
|
|
420
|
+
Object.assign({}, msg, {
|
|
421
|
+
payload: true,
|
|
422
|
+
sessionID,
|
|
423
|
+
serverId: node.id,
|
|
424
|
+
serverName: resolveServerName(msg),
|
|
425
|
+
}),
|
|
426
|
+
null,
|
|
427
|
+
]);
|
|
391
428
|
done();
|
|
392
429
|
})
|
|
393
430
|
.catch((err) => done(new Error(`agent-server: abort failed: ${err.message}`)));
|
|
@@ -416,7 +453,15 @@ module.exports = function (RED) {
|
|
|
416
453
|
...authOptions(),
|
|
417
454
|
})
|
|
418
455
|
.then((history) => {
|
|
419
|
-
send([
|
|
456
|
+
send([
|
|
457
|
+
Object.assign({}, msg, {
|
|
458
|
+
payload: history,
|
|
459
|
+
sessionID,
|
|
460
|
+
serverId: node.id,
|
|
461
|
+
serverName: resolveServerName(msg),
|
|
462
|
+
}),
|
|
463
|
+
null,
|
|
464
|
+
]);
|
|
420
465
|
done();
|
|
421
466
|
})
|
|
422
467
|
.catch((err) => done(new Error(`agent-server: history fetch failed: ${err.message}`)));
|
|
@@ -450,7 +495,15 @@ module.exports = function (RED) {
|
|
|
450
495
|
.then(() => {
|
|
451
496
|
emitEvent(sessionID, "terminated", msg);
|
|
452
497
|
updateStatus();
|
|
453
|
-
send([
|
|
498
|
+
send([
|
|
499
|
+
Object.assign({}, msg, {
|
|
500
|
+
payload: true,
|
|
501
|
+
sessionID,
|
|
502
|
+
serverId: node.id,
|
|
503
|
+
serverName: resolveServerName(msg),
|
|
504
|
+
}),
|
|
505
|
+
null,
|
|
506
|
+
]);
|
|
454
507
|
done();
|
|
455
508
|
})
|
|
456
509
|
.catch((err) => done(new Error(`agent-server: terminate failed: ${err.message}`)));
|