@j-o-r/hello-dave 0.0.0
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/LICENSE +73 -0
- package/README.md +207 -0
- package/bin/hdAsk.js +103 -0
- package/bin/hdClear.js +13 -0
- package/bin/hdCode.js +110 -0
- package/bin/hdConnect.js +230 -0
- package/bin/hdInspect.js +28 -0
- package/bin/hdNpm.js +114 -0
- package/bin/hdPrompt.js +108 -0
- package/examples/claude-test.js +89 -0
- package/examples/claude.js +143 -0
- package/examples/gpt.js +127 -0
- package/examples/gpt_code.js +125 -0
- package/examples/gpt_note_keeping.js +117 -0
- package/examples/grok.js +119 -0
- package/examples/grok_code.js +114 -0
- package/examples/grok_note_keeping.js +111 -0
- package/lib/API/anthropic.com/text.js +402 -0
- package/lib/API/brave.com/search.js +239 -0
- package/lib/API/openai.com/README.md +1 -0
- package/lib/API/openai.com/reponses/MESSAGES.md +69 -0
- package/lib/API/openai.com/reponses/text.js +416 -0
- package/lib/API/x.ai/text.js +415 -0
- package/lib/AgentClient.js +197 -0
- package/lib/AgentManager.js +144 -0
- package/lib/AgentServer.js +336 -0
- package/lib/Cli.js +256 -0
- package/lib/Prompt.js +728 -0
- package/lib/Session.js +231 -0
- package/lib/ToolSet.js +186 -0
- package/lib/fafs.js +93 -0
- package/lib/genericToolset.js +170 -0
- package/lib/index.js +34 -0
- package/lib/promptHelpers.js +132 -0
- package/lib/testToolset.js +42 -0
- package/module.md +189 -0
- package/package.json +49 -0
- package/types/API/anthropic.com/text.d.ts +207 -0
- package/types/API/brave.com/search.d.ts +156 -0
- package/types/API/openai.com/reponses/text.d.ts +225 -0
- package/types/API/x.ai/text.d.ts +286 -0
- package/types/AgentClient.d.ts +70 -0
- package/types/AgentManager.d.ts +112 -0
- package/types/AgentServer.d.ts +38 -0
- package/types/Cli.d.ts +52 -0
- package/types/Prompt.d.ts +298 -0
- package/types/Session.d.ts +31 -0
- package/types/ToolSet.d.ts +95 -0
- package/types/fafs.d.ts +47 -0
- package/types/genericToolset.d.ts +3 -0
- package/types/index.d.ts +23 -0
- package/types/promptHelpers.d.ts +1 -0
- package/types/testToolset.d.ts +3 -0
package/lib/Session.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records sessions
|
|
3
|
+
* Get sessions
|
|
4
|
+
* This is an optional extension for the prompt
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { CacheSync } from '@j-o-r/cache';
|
|
9
|
+
import Prompt from './Prompt.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {import('./Prompt.js').Message} Message
|
|
13
|
+
* @typedef {import('./Prompt.js').Record} Record
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the next version number for a given key in the array.
|
|
18
|
+
*
|
|
19
|
+
* @param {string[]} arr - The array of versioned strings.
|
|
20
|
+
* @param {string} key - The key to check for the highest version.
|
|
21
|
+
* @returns {number} - The next version number.
|
|
22
|
+
*/
|
|
23
|
+
const getNextVersion = (arr, key) => {
|
|
24
|
+
const regex = new RegExp(`^${key}_(\\d+)$`);
|
|
25
|
+
const versions = arr
|
|
26
|
+
.map(item => {
|
|
27
|
+
const match = item.match(regex);
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
return match ? parseInt(match[1], 10) : null;
|
|
30
|
+
})
|
|
31
|
+
.filter(version => version !== null);
|
|
32
|
+
return versions.length > 0 ? Math.max(...versions) + 1 : 0;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
*/
|
|
37
|
+
class Session {
|
|
38
|
+
/**
|
|
39
|
+
* sticky message que
|
|
40
|
+
* @type {Message[]}
|
|
41
|
+
*/
|
|
42
|
+
#que = [];
|
|
43
|
+
/** @type {string} session name */
|
|
44
|
+
#sessionName = '';
|
|
45
|
+
/** @type {number} count the number of session iterations */
|
|
46
|
+
#sessionCounter = 0;
|
|
47
|
+
/** @type {CacheSync} */
|
|
48
|
+
#sessionCache;
|
|
49
|
+
/** @type {CacheSync} */
|
|
50
|
+
#recordCache;
|
|
51
|
+
/** @type {CacheSync} */
|
|
52
|
+
#logCache;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create and set the cache folders
|
|
56
|
+
* @param {string} promptName - The name of the prompt.
|
|
57
|
+
* @param {Prompt} prompt - The prompt
|
|
58
|
+
* @param {string} [baseFolder] - The base storage folder, default this is process.cwd()
|
|
59
|
+
*/
|
|
60
|
+
#inititializeCache = (promptName, prompt, baseFolder) => {
|
|
61
|
+
// If not base folder, we take FOLDERS.ROOT
|
|
62
|
+
// else we use the baseFolder
|
|
63
|
+
if (!baseFolder) {
|
|
64
|
+
// Relative from cwd()
|
|
65
|
+
baseFolder = path.resolve('.cache', 'hello-dave');
|
|
66
|
+
}
|
|
67
|
+
// Create the root folder, just in case
|
|
68
|
+
const root = new CacheSync(baseFolder, true, 'any');
|
|
69
|
+
// this.#baseStorage = baseFolder;
|
|
70
|
+
this.name = root.sanitizeKey(promptName);
|
|
71
|
+
const sessionStorage = path.resolve(baseFolder, this.name);
|
|
72
|
+
// Create the session folder
|
|
73
|
+
new CacheSync(sessionStorage, true, 'any');
|
|
74
|
+
// Create the records cache
|
|
75
|
+
this.#logCache = new CacheSync(path.resolve(sessionStorage, 'logs'), true, 'ndjson');
|
|
76
|
+
this.#recordCache = new CacheSync(path.resolve(sessionStorage, 'records'), true, 'ndjson');
|
|
77
|
+
if (this.name.length === 0) {
|
|
78
|
+
throw new Error(`Invalid session name: ${promptName}`);
|
|
79
|
+
}
|
|
80
|
+
// create and init the session storage
|
|
81
|
+
this.#sessionCache = new CacheSync(path.resolve(sessionStorage, 'sessions'), true, 'ndjson');
|
|
82
|
+
prompt.on(prompt.EVENTS.message, (msg) => {
|
|
83
|
+
this.#msg(msg);
|
|
84
|
+
});
|
|
85
|
+
prompt.on(prompt.EVENTS.record, (rec) => {
|
|
86
|
+
this.#rec(rec);
|
|
87
|
+
});
|
|
88
|
+
prompt.on(prompt.EVENTS.reset, () => {
|
|
89
|
+
// Prompt has only sticky messages
|
|
90
|
+
// fill the que
|
|
91
|
+
this.#sessionCounter = 0;
|
|
92
|
+
this.#sessionName = '';
|
|
93
|
+
this.#que = prompt.messages;
|
|
94
|
+
});
|
|
95
|
+
prompt.on(prompt.EVENTS.truncated, () => {
|
|
96
|
+
// Prompt has only sticky messages
|
|
97
|
+
// fill the que
|
|
98
|
+
this.#sessionCounter = 0;
|
|
99
|
+
this.#sessionName = '';
|
|
100
|
+
this.#que = prompt.messages;
|
|
101
|
+
});
|
|
102
|
+
const events = Object.keys(prompt.EVENTS);
|
|
103
|
+
events.forEach((evt) => {
|
|
104
|
+
prompt.on(evt, (msg) => {
|
|
105
|
+
this.#log({ evt, msg });
|
|
106
|
+
});
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Constructs a new Prompt instance.
|
|
111
|
+
* If contextWindow = 0 (defaut) the prompt will have no context building up (ONE_SHOT)
|
|
112
|
+
* @param {string} name - The name of the prompt.
|
|
113
|
+
* @param {Prompt} prompt - The prompt
|
|
114
|
+
* @param {string} [storage] - The base storage folder, default this is process.cwd()
|
|
115
|
+
*/
|
|
116
|
+
constructor(name, prompt, storage) {
|
|
117
|
+
this.#inititializeCache(name, prompt, storage)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
info() {
|
|
121
|
+
const name = this.name;
|
|
122
|
+
const session = this.#sessionName;
|
|
123
|
+
const sessionCount = this.#sessionCounter;
|
|
124
|
+
const path = this.#sessionCache.folder;
|
|
125
|
+
return `Name: ${name}\nSession: ${session}\nSessionCount: ${sessionCount}\nPath: ${path}`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Add message to the cache
|
|
129
|
+
* @param {import('./Prompt.js').Message} msg
|
|
130
|
+
*/
|
|
131
|
+
#msg(msg) {
|
|
132
|
+
const role = msg.role;
|
|
133
|
+
const sticky = msg.sticky;
|
|
134
|
+
let sessKey;
|
|
135
|
+
if (this.#sessionName === '' && role === 'user' && sticky === false) {
|
|
136
|
+
// Create a sessioname
|
|
137
|
+
if (msg.content.length > 0 && msg.content[0].type === 'text') {
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} str
|
|
140
|
+
*/
|
|
141
|
+
const truncateString = (str) => str.length > 50 ? str.slice(0, 80) : str;
|
|
142
|
+
// @ts-ignore
|
|
143
|
+
this.#sessionName = this.#sessionCache.sanitizeKey(truncateString(msg.content[0].text).trim());
|
|
144
|
+
this.#sessionCounter = getNextVersion(this.#sessionCache.list(), this.#sessionName);
|
|
145
|
+
sessKey = this.#sessionCache.sanitizeKey(`${this.#sessionName}_${this.#sessionCounter}`);
|
|
146
|
+
}
|
|
147
|
+
} else if(this.#sessionName !== '') {
|
|
148
|
+
sessKey = this.#sessionCache.sanitizeKey(`${this.#sessionName}_${this.#sessionCounter}`);
|
|
149
|
+
}
|
|
150
|
+
if (sessKey) {
|
|
151
|
+
// Alway write the que if there is a name
|
|
152
|
+
// May the must be written sticky nots or a truncated previous sessions
|
|
153
|
+
if (this.#que.length > 0) {
|
|
154
|
+
// Write outstanding messages
|
|
155
|
+
let i = 0;
|
|
156
|
+
const len = this.#que.length;
|
|
157
|
+
for (; i < len; i++) {
|
|
158
|
+
this.#sessionCache.append(sessKey, this.#que[i]);
|
|
159
|
+
}
|
|
160
|
+
this.#que = [];
|
|
161
|
+
}
|
|
162
|
+
this.#sessionCache.append(sessKey, msg);
|
|
163
|
+
} else {
|
|
164
|
+
this.#que.push(msg);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* store a record
|
|
170
|
+
* @param {Record} record - Records for billing and optimizing
|
|
171
|
+
* @throws {Error} If the record is invalid.
|
|
172
|
+
*/
|
|
173
|
+
#rec(record) {
|
|
174
|
+
const key = new Date().toISOString().split('T')[0];
|
|
175
|
+
this.#recordCache.append(key, record);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* log all events
|
|
180
|
+
* @param {*} o - just log
|
|
181
|
+
* @throws {Error} If the record is invalid.
|
|
182
|
+
*/
|
|
183
|
+
#log(o) {
|
|
184
|
+
const key = new Date().toISOString().split('T')[0];
|
|
185
|
+
this.#logCache.append(key, o);
|
|
186
|
+
}
|
|
187
|
+
sessionList() {
|
|
188
|
+
const list = this.#sessionCache.list();
|
|
189
|
+
const processedSet = new Set(
|
|
190
|
+
list.map(str => {
|
|
191
|
+
// Remove the last '_[number]' part
|
|
192
|
+
const withoutNumber = str.replace(/_\d+$/, '');
|
|
193
|
+
// Replace '_' with ' '
|
|
194
|
+
return withoutNumber.replace(/_/g, ' ');
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
return [...processedSet];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get messages fro, a session
|
|
202
|
+
*
|
|
203
|
+
* @param {string} sess - The session name.
|
|
204
|
+
* @return {Message[]}
|
|
205
|
+
*/
|
|
206
|
+
set(sess) {
|
|
207
|
+
const counter = getNextVersion(this.#sessionCache.list(), sess);
|
|
208
|
+
if (counter === 0) {
|
|
209
|
+
this.#sessionCounter = counter;
|
|
210
|
+
} else {
|
|
211
|
+
this.#sessionCounter = counter - 1;
|
|
212
|
+
}
|
|
213
|
+
// Load last know session
|
|
214
|
+
const sessKey = this.#sessionCache.sanitizeKey(`${sess}_${this.#sessionCounter}`);
|
|
215
|
+
const res = this.#sessionCache.read(sessKey) || [];
|
|
216
|
+
|
|
217
|
+
if (res) {
|
|
218
|
+
this.#sessionName = sess;
|
|
219
|
+
}
|
|
220
|
+
return res;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Remove all previous sessions and records
|
|
224
|
+
*/
|
|
225
|
+
empty() {
|
|
226
|
+
this.#sessionCache.empty();
|
|
227
|
+
this.#recordCache.empty();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default Session;
|
package/lib/ToolSet.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import Prompt from "./Prompt.js";
|
|
2
|
+
/**
|
|
3
|
+
* @module @j-o-r/toolset
|
|
4
|
+
* Register function_calls / tools
|
|
5
|
+
* This library can be used stand-alone or as a wrapper for AI functions_calls tools
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} TSSchema
|
|
10
|
+
* @property {string} type - Type of the object
|
|
11
|
+
* @property {object} properties - Description of properties
|
|
12
|
+
* @property {string[]} [required] - Required properties
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} TSTool
|
|
16
|
+
* @property {string} description - Command description
|
|
17
|
+
* @property {TSSchema} parameters - JS JSON schema
|
|
18
|
+
* @property {function(object): Promise} method
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} TSToolListItem
|
|
22
|
+
* @property {string} name - Command name
|
|
23
|
+
* @property {string} description - Command description
|
|
24
|
+
* @property {TSSchema} parameters - JS JSON schema
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Type of choices */
|
|
28
|
+
const CHOICES = {
|
|
29
|
+
NONE: 'none',
|
|
30
|
+
AUTO: 'auto',
|
|
31
|
+
REQUIRED: 'required'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The function name is limited
|
|
36
|
+
* @param {string} s
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
const isValidName = (s) => /^[#!a-z_0-9]{2,}$/.test(s);
|
|
40
|
+
|
|
41
|
+
class ToolSet {
|
|
42
|
+
/** @type {Map<string, TSTool>} */
|
|
43
|
+
#tools = new Map();
|
|
44
|
+
/** default auto */
|
|
45
|
+
#toolChoice = 'auto';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} [choice] - Default 'auto' auto|none|required
|
|
49
|
+
*/
|
|
50
|
+
constructor(choice = 'auto') {
|
|
51
|
+
if (choice && Object.values(CHOICES).includes(choice)) {
|
|
52
|
+
this.#toolChoice = choice;
|
|
53
|
+
} else if (choice) {
|
|
54
|
+
throw new Error('Tool choice not defined');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* How many functions have we registered
|
|
60
|
+
* @returns {number}
|
|
61
|
+
*/
|
|
62
|
+
get length() {
|
|
63
|
+
return this.#tools.size;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register a Tool/Command/Function
|
|
68
|
+
* @param {string} name - [a-z_0-9]{2,} The lowercase string name of the callback e.g. 'get_node_version'
|
|
69
|
+
* @param {string} description - What does it do
|
|
70
|
+
* @param {TSSchema} parameters - SCHEMA object describing the function's input parameters
|
|
71
|
+
* @param {function(object): Promise<*>} method - Async function to call
|
|
72
|
+
*/
|
|
73
|
+
add(name, description, parameters, method) {
|
|
74
|
+
if (!isValidName(name)) {
|
|
75
|
+
throw new Error('Invalid name /[a-z_0-9]{2,}/');
|
|
76
|
+
}
|
|
77
|
+
if (this.has(name)) {
|
|
78
|
+
// throw new Error('Function already defined');
|
|
79
|
+
}
|
|
80
|
+
this.#tools.set(name, { description, parameters, method });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a tool from the toolset
|
|
85
|
+
* @param {string} name
|
|
86
|
+
* @returns {TSTool}
|
|
87
|
+
*/
|
|
88
|
+
get(name) {
|
|
89
|
+
if (!this.has(name)) {
|
|
90
|
+
throw new Error('Function not found');
|
|
91
|
+
}
|
|
92
|
+
return this.#tools.get(name);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Delete a function_call
|
|
96
|
+
* @param {string} name
|
|
97
|
+
*/
|
|
98
|
+
delete(name) {
|
|
99
|
+
this.#tools.delete(name);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Is 'name' already registered
|
|
103
|
+
* @param {string} name
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
has(name) {
|
|
107
|
+
return this.#tools.has(name);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get a list of tools available
|
|
112
|
+
* @returns {TSToolListItem[]}
|
|
113
|
+
*/
|
|
114
|
+
list() {
|
|
115
|
+
return Array.from(this.#tools.entries()).map(([name, value]) => ({
|
|
116
|
+
name,
|
|
117
|
+
description: value.description,
|
|
118
|
+
parameters: value.parameters
|
|
119
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get toolChoice() {
|
|
123
|
+
return this.#toolChoice;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Execute a method
|
|
127
|
+
* @param {string} name
|
|
128
|
+
* @param {object} params
|
|
129
|
+
*/
|
|
130
|
+
async call(name, params) {
|
|
131
|
+
if (!this.has(name)) {
|
|
132
|
+
throw new Error('Function not found');
|
|
133
|
+
}
|
|
134
|
+
const tool = this.#tools.get(name);
|
|
135
|
+
return tool.method(params);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Execute function requests from the last message.
|
|
139
|
+
* @param {Prompt} prompt - Handle tool calls.
|
|
140
|
+
*/
|
|
141
|
+
async execute(prompt) {
|
|
142
|
+
const lastMessage = prompt.getLastMessage();
|
|
143
|
+
if (!lastMessage) return;
|
|
144
|
+
if (!(lastMessage.content.length > 0 && lastMessage.content[0].type === 'function_request')) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
/** @type {import("./Prompt.js").FunctionResponse[]} */
|
|
148
|
+
const functionResponses = [];
|
|
149
|
+
/** @type {import("./Prompt.js").FunctionRequest[]} */
|
|
150
|
+
// @ts-ignore
|
|
151
|
+
const calls = lastMessage.content;
|
|
152
|
+
const len = calls.length;
|
|
153
|
+
for (let i = 0; i < len; i++) {
|
|
154
|
+
const call = calls[i];
|
|
155
|
+
const startTime = new Date().getTime();
|
|
156
|
+
let response = '';
|
|
157
|
+
prompt.emit(prompt.EVENTS.tool_request, { name: call.function_request.name, call_id: call.function_request.call_id });
|
|
158
|
+
try {
|
|
159
|
+
response = await this.call(call.function_request.name, JSON.parse(call.function_request.parameters));
|
|
160
|
+
} catch (error) {
|
|
161
|
+
prompt.emit(prompt.EVENTS.tool_error, { name: call.function_request.name, call_id: call.function_request.call_id, error });
|
|
162
|
+
response = `Error: ${error.name} - ${error.message}`;
|
|
163
|
+
}
|
|
164
|
+
const duration = new Date().getTime() - startTime;
|
|
165
|
+
prompt.emit(prompt.EVENTS.tool_response, { name: call.function_request.name, call_id: call.function_request.call_id, duration });
|
|
166
|
+
let functionRes = response;
|
|
167
|
+
if (typeof functionRes !== 'string') {
|
|
168
|
+
functionRes = JSON.stringify(functionRes);
|
|
169
|
+
}
|
|
170
|
+
/** @type {import("./Prompt.js").FunctionResponse} */
|
|
171
|
+
const responseObj = {
|
|
172
|
+
type: 'function_response',
|
|
173
|
+
function_response: {
|
|
174
|
+
name: call.function_request.name,
|
|
175
|
+
id: call.function_request.id,
|
|
176
|
+
call_id: call.function_request.call_id,
|
|
177
|
+
response: functionRes
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
functionResponses.push(responseObj);
|
|
181
|
+
}
|
|
182
|
+
prompt.addMultiModal('tool', functionResponses);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default ToolSet;
|
package/lib/fafs.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* Frequently asked functions */
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { CacheAsync, path } from '@j-o-r/cache';
|
|
4
|
+
import { SH, jsType } from '@j-o-r/sh'
|
|
5
|
+
|
|
6
|
+
const APPNAME = 'hello-dave';
|
|
7
|
+
|
|
8
|
+
const RELATIVE_CACHE = path.resolve('.cache');
|
|
9
|
+
new CacheAsync(RELATIVE_CACHE, true, 'bin');
|
|
10
|
+
const APP_CACHE = path.resolve(RELATIVE_CACHE, APPNAME);
|
|
11
|
+
new CacheAsync(APP_CACHE, true, 'bin');
|
|
12
|
+
|
|
13
|
+
const GLOBAL = {
|
|
14
|
+
max_recursive_requests: 20, // max number recursive of request
|
|
15
|
+
default_cache: APP_CACHE,
|
|
16
|
+
secret: 'tdb.e3a0cd73dd6a429283f921f9fc1bad41'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} EnvironmentInfo
|
|
21
|
+
* @property {string} name - User's name
|
|
22
|
+
* @property {string} system - Linux OS / PROC
|
|
23
|
+
* @property {string} city
|
|
24
|
+
* @property {string} region
|
|
25
|
+
* @property {string} country
|
|
26
|
+
* @property {string} timezone
|
|
27
|
+
* @property {string} external_ip
|
|
28
|
+
* @property {string} cwd - current working folder
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @returns {Promise<string>}
|
|
33
|
+
*/
|
|
34
|
+
async function systemInfo() {
|
|
35
|
+
let system = 'system: ' + (await SH`cat /etc/issue`.run()).trim().replace('\\n', '').replace('\\l', '');
|
|
36
|
+
system += (await SH`uname -m`.run()).trim().replace('\\n', '').replace('\\l', '');
|
|
37
|
+
system += '' + (await SH`uname -o`.run()).trim().replace('\\n', '').replace('\\l', '');
|
|
38
|
+
system += '\ncwd: ' + process.cwd();
|
|
39
|
+
// try {
|
|
40
|
+
// await SH` which xdg-open`.run();
|
|
41
|
+
// system += ' and with xdg-open';
|
|
42
|
+
// } catch (_e) { }
|
|
43
|
+
// try {
|
|
44
|
+
// await SH` which msmtp`.run();
|
|
45
|
+
// system += ', msmtp';
|
|
46
|
+
// } catch (_e) { }
|
|
47
|
+
return system
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Gather information about this environment
|
|
52
|
+
* @returns {Promise<EnvironmentInfo>}
|
|
53
|
+
*/
|
|
54
|
+
async function env() {
|
|
55
|
+
const CACHE_PATH = (await SH`echo ~/.cache/${APPNAME}`.run()).trim();
|
|
56
|
+
const KEY = 'env';
|
|
57
|
+
const storage = new CacheAsync(CACHE_PATH, true, 'bin');
|
|
58
|
+
storage.secret = GLOBAL.secret;
|
|
59
|
+
const day = 1000 * 60 * 60 *24
|
|
60
|
+
// Expire after 31 days, refresh info after that,
|
|
61
|
+
await storage.expire(day * 31);
|
|
62
|
+
|
|
63
|
+
const wd = process.cwd();
|
|
64
|
+
const home = os.homedir();
|
|
65
|
+
const cwd = wd.replace(home, '~');
|
|
66
|
+
let info = await storage.read(KEY)
|
|
67
|
+
if (info) {
|
|
68
|
+
const res = JSON.parse(info);
|
|
69
|
+
res.cwd = cwd;
|
|
70
|
+
return res;
|
|
71
|
+
}
|
|
72
|
+
const name = (await SH`getent passwd "$USER" | cut -d: -f5`.run()).replaceAll(',',' ').trim();
|
|
73
|
+
const system = await systemInfo();
|
|
74
|
+
const userInfo = JSON.parse((await SH`curl -fsSL https://ipinfo.io/json`.run()));
|
|
75
|
+
info = {
|
|
76
|
+
name,
|
|
77
|
+
system,
|
|
78
|
+
city: userInfo.city,
|
|
79
|
+
region: userInfo.region,
|
|
80
|
+
country: userInfo.country,
|
|
81
|
+
timezone: userInfo.timezone,
|
|
82
|
+
external_ip: userInfo.ip
|
|
83
|
+
}
|
|
84
|
+
await storage.write(KEY, JSON.stringify(info));
|
|
85
|
+
return info;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
jsType,
|
|
90
|
+
GLOBAL,
|
|
91
|
+
env,
|
|
92
|
+
systemInfo
|
|
93
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { SH } from '@j-o-r/sh'
|
|
2
|
+
import { ToolSet, env } from './index.js'
|
|
3
|
+
|
|
4
|
+
const user = await env();
|
|
5
|
+
const environment =`
|
|
6
|
+
Name: ${user.name}
|
|
7
|
+
System: ${user.system}
|
|
8
|
+
City: ${user.city}
|
|
9
|
+
Region: ${user.region}
|
|
10
|
+
Country: ${user.country}
|
|
11
|
+
Timezone: ${user.timezone}
|
|
12
|
+
ExternalIp: ${user.external_ip}
|
|
13
|
+
`.trim();
|
|
14
|
+
const tools = new ToolSet('auto');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* reduce the error output to essential info only, if possible
|
|
18
|
+
* @param {string} errorStr
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
const getJSError = (errorStr) => {
|
|
22
|
+
let result = '';
|
|
23
|
+
const linematch = errorStr.match(/\[eval\]:(\d+)/);
|
|
24
|
+
const lineNumber = linematch ? linematch[1] : '';
|
|
25
|
+
const match = errorStr.split(/\" \"\[eval\]:\d+/s);
|
|
26
|
+
if (match.length > 1) {
|
|
27
|
+
// Remove last 10 lines
|
|
28
|
+
const res = match[1].split('\n').slice(0, -10).join('\n');
|
|
29
|
+
result = `Error: line ${lineNumber}\n${res} `
|
|
30
|
+
} else {
|
|
31
|
+
result = errorStr;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* bash escape a string suitable as an argument on the commandline
|
|
38
|
+
* for javascript code
|
|
39
|
+
* @param {string} x
|
|
40
|
+
* @retruns {string}
|
|
41
|
+
*/
|
|
42
|
+
const bashEscape = (x) => {
|
|
43
|
+
let str = String(x).trim();
|
|
44
|
+
// the trick is to double escape escape vars first
|
|
45
|
+
str = str.replace(/\\/g, '\\\\');
|
|
46
|
+
// then add escaping for oddities
|
|
47
|
+
// Replace literal escape sequences like \n, \t, \r in quoted strings
|
|
48
|
+
str = str.replace(/(['"])\\(.)\1/g, '$1\\\\$2$1');
|
|
49
|
+
// escape $ (is a BASH var)
|
|
50
|
+
str = str.replace(/\$/g, '\\$');
|
|
51
|
+
// Escape backticks
|
|
52
|
+
str = str.replace(/`/g, '\\`');
|
|
53
|
+
// Escape quotes
|
|
54
|
+
str = str.replace(/"/g, '\\"');
|
|
55
|
+
return str;
|
|
56
|
+
}
|
|
57
|
+
tools.add(
|
|
58
|
+
'javascript_interpreter',
|
|
59
|
+
`Execute ESM ES6 javascript on \`node\`.`,
|
|
60
|
+
{
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
script: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: `ES6 ESM Javascript eval. 'console.log' to capture the response. cwd: ${user.cwd}`,
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
required: ['script']
|
|
69
|
+
},
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
async (params) => {
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
const script = bashEscape(params.script);
|
|
74
|
+
let response = '';
|
|
75
|
+
try {
|
|
76
|
+
// Execute script
|
|
77
|
+
response = await SH`node -e "${script}"`.run();
|
|
78
|
+
} catch (e) {
|
|
79
|
+
const errorStr = e.toString()
|
|
80
|
+
response = getJSError(errorStr)
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
tools.add(
|
|
86
|
+
'bash_cmd',
|
|
87
|
+
`Execute a Bash command on ${user.system}.`,
|
|
88
|
+
{
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {
|
|
91
|
+
command: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'The bash command.',
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
required: ['command']
|
|
97
|
+
},
|
|
98
|
+
// @ts-ignore
|
|
99
|
+
async (params) => (await SH`${params.command}`.run())
|
|
100
|
+
);
|
|
101
|
+
tools.add(
|
|
102
|
+
'get_user_env', // name
|
|
103
|
+
'Get the user location, name and OS environment', // desciption
|
|
104
|
+
{
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
async (_params) => {
|
|
110
|
+
return environment;
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
tools.add(
|
|
115
|
+
'execute_bash_script',
|
|
116
|
+
'Execute a bash script.',
|
|
117
|
+
{
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
bash_script: { type: 'string', description: `The bash script to execute (${user.system})` }
|
|
121
|
+
},
|
|
122
|
+
required: ['bash_script']
|
|
123
|
+
},
|
|
124
|
+
async (params) => {
|
|
125
|
+
const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
126
|
+
return await SH`bash <<'${delim}'
|
|
127
|
+
${params.bash_script}
|
|
128
|
+
${delim}
|
|
129
|
+
`.run()
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
tools.add(
|
|
133
|
+
'send_email',
|
|
134
|
+
'Send an email.',
|
|
135
|
+
{
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {
|
|
138
|
+
to: { type: 'string', description: 'Recipient email' },
|
|
139
|
+
subject: { type: 'string', description: 'Subject' },
|
|
140
|
+
body: { type: 'string', description: 'Message body' }
|
|
141
|
+
},
|
|
142
|
+
required: ['to', 'subject', 'body']
|
|
143
|
+
},
|
|
144
|
+
async (params) => {
|
|
145
|
+
const delim = `END_EMAIL_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
146
|
+
return await SH`msmtp ${params.to} <<'${delim}'
|
|
147
|
+
To: ${params.to}
|
|
148
|
+
Subject: ${params.subject}
|
|
149
|
+
|
|
150
|
+
${params.body}
|
|
151
|
+
${delim}
|
|
152
|
+
`.run();
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
tools.add(
|
|
156
|
+
'open_link',
|
|
157
|
+
'Open an url in local user environment.',
|
|
158
|
+
{
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
url: { type: 'string', description: 'Link to open' }
|
|
162
|
+
},
|
|
163
|
+
required: ['url']
|
|
164
|
+
},
|
|
165
|
+
async (params) => {
|
|
166
|
+
return await SH`xdg-open ${[params.url]}`.run();
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
export default tools
|
|
170
|
+
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {request as gpt} from './API/openai.com/reponses/text.js';
|
|
2
|
+
import {request as grok} from './API/x.ai/text.js';
|
|
3
|
+
import {request as claude} from './API/anthropic.com/text.js';
|
|
4
|
+
import {request as brave} from './API/brave.com/search.js';
|
|
5
|
+
|
|
6
|
+
import { env, GLOBAL } from './fafs.js';
|
|
7
|
+
import ToolSet from './ToolSet.js';
|
|
8
|
+
import AgentServer from './AgentServer.js';
|
|
9
|
+
import AgentClient from './AgentClient.js';
|
|
10
|
+
import Session from './Session.js';
|
|
11
|
+
import Prompt from './Prompt.js';
|
|
12
|
+
import Cli from './Cli.js';
|
|
13
|
+
|
|
14
|
+
const API = {
|
|
15
|
+
text:{
|
|
16
|
+
gpt,
|
|
17
|
+
grok,
|
|
18
|
+
claude
|
|
19
|
+
},
|
|
20
|
+
search: {
|
|
21
|
+
brave
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
AgentServer,
|
|
26
|
+
AgentClient,
|
|
27
|
+
Prompt,
|
|
28
|
+
ToolSet,
|
|
29
|
+
Session,
|
|
30
|
+
API,
|
|
31
|
+
Cli,
|
|
32
|
+
env,
|
|
33
|
+
GLOBAL
|
|
34
|
+
};
|