node-red-contrib-ai-agent 0.0.3 → 0.0.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/tool/ai-tool.html DELETED
@@ -1,159 +0,0 @@
1
- <script type="text/javascript">
2
- RED.nodes.registerType('ai-tool', {
3
- category: 'AI Agent',
4
- inputs: 1,
5
- outputs: 1,
6
- icon: 'function.svg',
7
- paletteLabel: 'AI Tool',
8
- label: function() {
9
- return this.name || 'AI Tool';
10
- },
11
- color: '#a6bbcf',
12
- defaults: {
13
- name: { value: '' },
14
- toolType: { value: 'http', required: true },
15
- toolName: { value: '', required: true, validate: RED.validators.regex(/^[a-zA-Z0-9_-]+$/) },
16
- description: { value: '', required: true },
17
- httpMethod: { value: 'GET' },
18
- httpUrl: { value: '' },
19
- httpHeaders: { value: '{}', validate: function(v) {
20
- try {
21
- JSON.parse(v);
22
- return true;
23
- } catch(e) {
24
- return false;
25
- }
26
- }},
27
- httpBody: { value: '' },
28
- functionCode: { value: '// Write your function here\n// Available variables: input, context, node\n// Return value will be passed to the next tool or as agent output\nreturn input;' },
29
- nodeRedFlow: { value: 'flow' },
30
- nodeRedNode: { value: '' },
31
- nodeRedProperty: { value: 'payload' }
32
- },
33
- oneditprepare: function() {
34
- // Show/hide fields based on tool type
35
- $('#node-input-toolType').on('change', function() {
36
- $('.tool-type-options').hide();
37
- $(`#${this.value}-options`).show();
38
- }).trigger('change');
39
-
40
- // Initialize JSON editor for headers
41
- const headersEditor = RED.editor.createEditor({
42
- id: 'node-input-http-headers',
43
- value: $('#node-input-http-headers').val() || '{}',
44
- mode: 'application/json',
45
- onComplete: function() {
46
- $('#node-input-http-headers').val(headersEditor.getText());
47
- }
48
- });
49
- },
50
- oneditsave: function() {
51
- // Validate and format JSON before saving
52
- try {
53
- if (this.httpHeaders) {
54
- JSON.parse(this.httpHeaders);
55
- }
56
- } catch (e) {
57
- alert('Invalid JSON in Headers');
58
- return false;
59
- }
60
- return true;
61
- }
62
- });
63
- </script>
64
-
65
- <script type="text/html" data-template-name="ai-tool">
66
- <div class="form-row">
67
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
68
- <input type="text" id="node-input-name" placeholder="Name">
69
- </div>
70
-
71
- <div class="form-row">
72
- <label for="node-input-toolType">Tool Type</label>
73
- <select id="node-input-toolType" style="width: 70%;">
74
- <option value="http">HTTP Request</option>
75
- <option value="function">JavaScript Function</option>
76
- <option value="node-red">Node-RED Node</option>
77
- </select>
78
- </div>
79
-
80
- <div class="form-row">
81
- <label for="node-input-toolName">Tool Name</label>
82
- <input type="text" id="node-input-toolName" placeholder="unique_tool_identifier" style="font-family: monospace;">
83
- </div>
84
-
85
- <div class="form-row">
86
- <label for="node-input-description">Description</label>
87
- <textarea id="node-input-description" rows="2" placeholder="What this tool does"></textarea>
88
- </div>
89
-
90
- <!-- HTTP Options -->
91
- <div id="http-options" class="tool-type-options">
92
- <div class="form-row">
93
- <label for="node-input-httpMethod">Method</label>
94
- <select id="node-input-httpMethod" style="width: 30%;">
95
- <option value="GET">GET</option>
96
- <option value="POST">POST</option>
97
- <option value="PUT">PUT</option>
98
- <option value="DELETE">DELETE</option>
99
- <option value="PATCH">PATCH</option>
100
- </select>
101
- <input type="text" id="node-input-httpUrl" placeholder="https://example.com/endpoint" style="width: 70%;">
102
- </div>
103
-
104
- <div class="form-row">
105
- <label for="node-input-httpHeaders">Headers (JSON)</label>
106
- <div id="node-input-http-headers-editor" style="height: 100px; width: 100%;"></div>
107
- <input type="hidden" id="node-input-http-headers">
108
- </div>
109
-
110
- <div class="form-row">
111
- <label for="node-input-httpBody">Body (for POST/PUT/PATCH)</label>
112
- <textarea id="node-input-httpBody" rows="3" placeholder="Request body (leave empty for GET/DELETE)"></textarea>
113
- </div>
114
- </div>
115
-
116
- <!-- Function Options -->
117
- <div id="function-options" class="tool-type-options" style="display: none;">
118
- <div class="form-row">
119
- <label for="node-input-functionCode">Function Code</label>
120
- <div style="height: 200px;">
121
- <textarea id="node-input-functionCode" style="width: 100%; height: 100%; font-family: monospace;"></textarea>
122
- </div>
123
- </div>
124
- </div>
125
-
126
- <!-- Node-RED Options -->
127
- <div id="node-red-options" class="tool-type-options" style="display: none;">
128
- <div class="form-row">
129
- <label for="node-input-nodeRedFlow">Flow Context</label>
130
- <select id="node-input-nodeRedFlow" style="width: 100%;">
131
- <option value="flow">Current Flow</option>
132
- <option value="global">Global</option>
133
- </select>
134
- </div>
135
-
136
- <div class="form-row">
137
- <label for="node-input-nodeRedNode">Node ID</label>
138
- <input type="text" id="node-input-nodeRedNode" placeholder="Node ID to call" style="width: 100%;">
139
- </div>
140
-
141
- <div class="form-row">
142
- <label for="node-input-nodeRedProperty">Property</label>
143
- <input type="text" id="node-input-nodeRedProperty" value="payload" placeholder="Property to set (default: payload)" style="width: 100%;">
144
- </div>
145
- </div>
146
- </script>
147
-
148
- <script type="text/html" data-help-name="ai-tool">
149
- <p>Configures a tool that can be used by the AI Agent.</p>
150
- <p>Tools extend the capabilities of the AI Agent by allowing it to interact with external systems, perform calculations, or access Node-RED nodes.</p>
151
- <h3>Tool Types</h3>
152
- <ul>
153
- <li><strong>HTTP Request</strong>: Call external APIs</li>
154
- <li><strong>JavaScript Function</strong>: Define custom logic in JavaScript</li>
155
- <li><strong>Node-RED Node</strong>: Interact with other nodes in your flow</li>
156
- </ul>
157
- <h3>Outputs</h3>
158
- <p>Adds the tool definition to <code>msg.aiagent.tools</code> for use by the AI Agent node.</p>
159
- </script>
package/tool/ai-tool.js DELETED
@@ -1,220 +0,0 @@
1
- module.exports = function(RED) {
2
- 'use strict';
3
-
4
- function AIToolNode(config) {
5
- RED.nodes.createNode(this, config);
6
- const node = this;
7
-
8
- // Configuration
9
- node.name = config.name || `tool_${config.toolType}`;
10
- node.toolType = config.toolType || 'function';
11
- node.toolName = config.toolName || `${node.toolType}_${Date.now()}`;
12
- node.description = config.description || `${node.toolType} tool`;
13
-
14
- // Initialize based on tool type
15
- if (node.toolType === 'function') {
16
- // Create a safe function that can be called later
17
- try {
18
- node.fn = new Function('input', 'context', 'node',
19
- `try { ${config.functionCode || 'return input;'} } catch(e) { return { error: e.message }; }`
20
- );
21
- } catch (e) {
22
- node.error(`Error creating function: ${e.message}`);
23
- node.fn = (input) => ({ error: 'Invalid function' });
24
- }
25
- }
26
-
27
- // Process incoming messages
28
- node.on('input', function(msg, send, done) {
29
- try {
30
- // Clone the message to avoid modifying the original
31
- const newMsg = RED.util.cloneMessage(msg);
32
-
33
- // Initialize msg.aiagent if it doesn't exist
34
- newMsg.aiagent = newMsg.aiagent || {};
35
- newMsg.aiagent.tools = newMsg.aiagent.tools || [];
36
-
37
- // Create tool definition
38
- const toolDef = {
39
- name: node.toolName,
40
- description: node.description,
41
- type: node.toolType
42
- };
43
-
44
- // Add tool-specific configuration
45
- if (node.toolType === 'http') {
46
- toolDef.config = {
47
- method: config.httpMethod || 'GET',
48
- url: config.httpUrl || '',
49
- headers: tryParseJson(config.httpHeaders) || {},
50
- body: config.httpBody || null
51
- };
52
- toolDef.execute = (input) => executeHttpTool(toolDef.config, input, node);
53
- }
54
- else if (node.toolType === 'function') {
55
- toolDef.execute = (input, context) => executeFunctionTool(node.fn, input, context, node);
56
- }
57
- else if (node.toolType === 'node-red') {
58
- toolDef.config = {
59
- flowContext: config.nodeRedFlow || 'flow',
60
- nodeId: config.nodeRedNode || '',
61
- property: config.nodeRedProperty || 'payload'
62
- };
63
- toolDef.execute = (input) => executeNodeRedTool(toolDef.config, input, node, msg._msgid);
64
- }
65
-
66
- // Add tool to the list
67
- newMsg.aiagent.tools.push(toolDef);
68
-
69
- // Update node status
70
- node.status({ fill: 'green', shape: 'dot', text: 'Ready' });
71
-
72
- // Send the modified message
73
- send([newMsg, null]);
74
-
75
- // Complete the async operation
76
- if (done) {
77
- done();
78
- }
79
- } catch (error) {
80
- node.status({ fill: 'red', shape: 'ring', text: 'Error' });
81
- node.error('Error in AI Tool node: ' + error.message, msg);
82
- if (done) done();
83
- }
84
- });
85
-
86
- // Handle node cleanup
87
- node.on('close', function(done) {
88
- node.status({});
89
- if (done) done();
90
- });
91
- }
92
-
93
- // Helper function to execute HTTP tool
94
- async function executeHttpTool(config, input, node) {
95
- const axios = require('axios');
96
- const { method, url, headers, body: bodyTemplate } = config;
97
-
98
- try {
99
- // Process template strings in URL and headers
100
- const processedUrl = processTemplate(url, input);
101
- const processedHeaders = Object.fromEntries(
102
- Object.entries(headers).map(([key, value]) => [
103
- processTemplate(key, input),
104
- typeof value === 'string' ? processTemplate(value, input) : value
105
- ])
106
- );
107
-
108
- // Process request body if it's a template
109
- let requestBody = bodyTemplate;
110
- if (typeof bodyTemplate === 'string') {
111
- try {
112
- // First try to parse as JSON to handle JSON templates
113
- requestBody = JSON.parse(processTemplate(bodyTemplate, input));
114
- } catch (e) {
115
- // If not valid JSON, use as plain string
116
- requestBody = processTemplate(bodyTemplate, input);
117
- }
118
- }
119
-
120
- // Make the HTTP request
121
- const response = await axios({
122
- method,
123
- url: processedUrl,
124
- headers: processedHeaders,
125
- data: method !== 'GET' && method !== 'HEAD' ? requestBody : undefined,
126
- validateStatus: () => true // Always resolve, never reject
127
- });
128
-
129
- return {
130
- status: response.status,
131
- headers: response.headers,
132
- data: response.data
133
- };
134
-
135
- } catch (error) {
136
- node.error(`HTTP Tool Error: ${error.message}`, { payload: input });
137
- throw error;
138
- }
139
- }
140
-
141
- // Helper function to execute JavaScript function tool
142
- async function executeFunctionTool(fn, input, context, node) {
143
- try {
144
- return await fn(input, context, node);
145
- } catch (error) {
146
- node.error(`Function Tool Error: ${error.message}`, { payload: input });
147
- throw error;
148
- }
149
- }
150
-
151
- // Helper function to execute Node-RED node tool
152
- async function executeNodeRedTool(config, input, node, msgId) {
153
- const { flowContext, nodeId, property } = config;
154
-
155
- return new Promise((resolve, reject) => {
156
- try {
157
- // Get the target node
158
- const targetNode = RED.nodes.getNode(nodeId);
159
- if (!targetNode) {
160
- throw new Error(`Node ${nodeId} not found`);
161
- }
162
-
163
- // Create a new message to send to the target node
164
- const msg = {
165
- _msgid: msgId || RED.util.generateId(),
166
- [property]: input,
167
- _toolContext: {
168
- sourceNode: node.id,
169
- timestamp: Date.now()
170
- }
171
- };
172
-
173
- // Set up a one-time listener for the response
174
- const responseHandler = (response) => {
175
- // Clean up the listener to prevent memory leaks
176
- RED.events.off('node:' + nodeId, responseHandler);
177
-
178
- // Resolve with the response
179
- resolve(response[property] || response.payload || response);
180
- };
181
-
182
- // Listen for the response
183
- RED.events.on('node:' + nodeId, responseHandler);
184
-
185
- // Send the message to the target node
186
- targetNode.receive(msg);
187
-
188
- } catch (error) {
189
- node.error(`Node-RED Tool Error: ${error.message}`, { payload: input });
190
- reject(error);
191
- }
192
- });
193
- }
194
-
195
- // Helper function to process template strings with input data
196
- function processTemplate(template, data) {
197
- if (typeof template !== 'string') return template;
198
-
199
- return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
200
- const value = key.split('.').reduce((obj, k) => (obj || {})[k], data);
201
- return value !== undefined ? value : match;
202
- });
203
- }
204
-
205
- // Helper function to safely parse JSON
206
- function tryParseJson(jsonString) {
207
- try {
208
- return JSON.parse(jsonString);
209
- } catch (e) {
210
- return {};
211
- }
212
- }
213
-
214
- // Register the node type
215
- RED.nodes.registerType('ai-tool', AIToolNode, {
216
- settings: {
217
- // Any node settings can go here
218
- }
219
- });
220
- };