@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.
Files changed (53) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +207 -0
  3. package/bin/hdAsk.js +103 -0
  4. package/bin/hdClear.js +13 -0
  5. package/bin/hdCode.js +110 -0
  6. package/bin/hdConnect.js +230 -0
  7. package/bin/hdInspect.js +28 -0
  8. package/bin/hdNpm.js +114 -0
  9. package/bin/hdPrompt.js +108 -0
  10. package/examples/claude-test.js +89 -0
  11. package/examples/claude.js +143 -0
  12. package/examples/gpt.js +127 -0
  13. package/examples/gpt_code.js +125 -0
  14. package/examples/gpt_note_keeping.js +117 -0
  15. package/examples/grok.js +119 -0
  16. package/examples/grok_code.js +114 -0
  17. package/examples/grok_note_keeping.js +111 -0
  18. package/lib/API/anthropic.com/text.js +402 -0
  19. package/lib/API/brave.com/search.js +239 -0
  20. package/lib/API/openai.com/README.md +1 -0
  21. package/lib/API/openai.com/reponses/MESSAGES.md +69 -0
  22. package/lib/API/openai.com/reponses/text.js +416 -0
  23. package/lib/API/x.ai/text.js +415 -0
  24. package/lib/AgentClient.js +197 -0
  25. package/lib/AgentManager.js +144 -0
  26. package/lib/AgentServer.js +336 -0
  27. package/lib/Cli.js +256 -0
  28. package/lib/Prompt.js +728 -0
  29. package/lib/Session.js +231 -0
  30. package/lib/ToolSet.js +186 -0
  31. package/lib/fafs.js +93 -0
  32. package/lib/genericToolset.js +170 -0
  33. package/lib/index.js +34 -0
  34. package/lib/promptHelpers.js +132 -0
  35. package/lib/testToolset.js +42 -0
  36. package/module.md +189 -0
  37. package/package.json +49 -0
  38. package/types/API/anthropic.com/text.d.ts +207 -0
  39. package/types/API/brave.com/search.d.ts +156 -0
  40. package/types/API/openai.com/reponses/text.d.ts +225 -0
  41. package/types/API/x.ai/text.d.ts +286 -0
  42. package/types/AgentClient.d.ts +70 -0
  43. package/types/AgentManager.d.ts +112 -0
  44. package/types/AgentServer.d.ts +38 -0
  45. package/types/Cli.d.ts +52 -0
  46. package/types/Prompt.d.ts +298 -0
  47. package/types/Session.d.ts +31 -0
  48. package/types/ToolSet.d.ts +95 -0
  49. package/types/fafs.d.ts +47 -0
  50. package/types/genericToolset.d.ts +3 -0
  51. package/types/index.d.ts +23 -0
  52. package/types/promptHelpers.d.ts +1 -0
  53. package/types/testToolset.d.ts +3 -0
@@ -0,0 +1,144 @@
1
+ import { Prompt, Cli, AgentClient, AgentServer, API, ToolSet, Session, env } from './index.js';
2
+ import toolsPool from './genericToolset.js'
3
+ /**
4
+ * @typedef {import('./API/x.ai/text.js').XOptions} XOptions
5
+ * @typedef {import('./API/openai.com/reponses/text.js').OAOptions} OAOptions
6
+ * @typedef {import('./API/anthropic.com/text.js').ANTHOptions} ANTHOptions
7
+ */
8
+ /**
9
+ * @typedef {Object} Setup
10
+ * @property {string} prompt - Initial system prompt
11
+ * @property {OAOptions|XOptions|ANTHOptions} options - Model options
12
+ * @property {'gpt'|'grok'|'claude'} api - AI endpoint to use
13
+ * @property {number} [contextWindow] - The size in tokens for the context/session
14
+ * @property {'auto'|'required'|void} [toolsetMode] -
15
+ * @property {boolean} [debug] - verbose output
16
+ */
17
+ /**
18
+ * @typedef {Object} Options
19
+ * @property {string} name - Agent name
20
+ * @property {string} [cachePath] - path to session/record storage
21
+ */
22
+ class AgentManager {
23
+ /** @type {Prompt} */
24
+ #prompt;
25
+ /** @type {Session} */
26
+ #sessionStorage;
27
+ #name = 'agent';
28
+ #cachePath = '.cache/hello-dave';
29
+ #debug = false;
30
+ /**
31
+ * @param {Options} options
32
+ */
33
+ constructor(options) {
34
+ this.#name = options.name || this.#name;
35
+ this.#cachePath = options.cachePath || this.#cachePath;
36
+ }
37
+
38
+ /**
39
+ * @param {Setup} setup
40
+ */
41
+ setup(setup) {
42
+ const {
43
+ prompt,
44
+ api = 'gpt',
45
+ contextWindow = 300000,
46
+ options = null,
47
+ toolsetMode = null,
48
+ debug = false
49
+ } = setup;
50
+ let toolset;
51
+ if (toolsetMode) {
52
+ toolset = new ToolSet(toolsetMode);
53
+ }
54
+ if (debug) {
55
+ this.#debug = true;
56
+ }
57
+ this.#prompt = new Prompt(contextWindow);
58
+ this.#sessionStorage = new Session(this.#name, this.#prompt, this.#cachePath);
59
+ this.#prompt.add('system', prompt, true);
60
+ this.#prompt.setAdaptor(API.text[api], toolset, options);
61
+ return this; // For chaining
62
+ }
63
+
64
+ /**
65
+ * Start a CLI for local usage
66
+ * @param {string} description - introduction message for the CLI user
67
+ */
68
+ startCli(description) {
69
+ if (!this.#prompt) throw new Error('Run setup first');
70
+ const cli = new Cli({ prompt: this.#prompt, session: this.#sessionStorage, description });
71
+ setTimeout(() => {
72
+ // This resolves issues with log output after the console has been started
73
+ cli.start();
74
+ }, 1000);
75
+ }
76
+
77
+ /**
78
+ * Attach a session to an AgentServer
79
+ * DO NOT ATTACH TO SELF, however, when in server mode
80
+ * An AgentServer can attach as client to another server
81
+ * @param {string} name - function_call name
82
+ * @param {string} description - function_call description
83
+ * @param {string} [url='ws://127.0.0.1:8000/ws'] - ws URL
84
+ */
85
+ attach(name, description, url = 'ws://127.0.0.1:8000/ws') {
86
+ if (!this.#prompt) throw new Error('Run setup first');
87
+ return new AgentClient({ prompt: this.#prompt, name, description, url });
88
+ }
89
+
90
+ /**
91
+ * Start the Agent in server mode.
92
+ * A server is always started on localhost
93
+ * Clients can attach now and are accesible as via function_calls (toolset)
94
+ * @param {string} name - function_call name
95
+ * @param {string} description - function_call description
96
+ * @param {number} [port=8000] - Start the dynamic toolset server on this port
97
+ */
98
+ enableServer(name, description = '', port = 8000) {
99
+ if (!this.#prompt) throw new Error('Run setup first');
100
+ if (!this.#prompt.toolset) throw new Error('No toolset defined');
101
+ const server = new AgentServer({
102
+ port,
103
+ name,
104
+ description,
105
+ prompt: this.#prompt,
106
+ session: this.#sessionStorage,
107
+ debug: this.#debug
108
+
109
+ });
110
+ // trigger reset on all Agents
111
+ this.#prompt.on('reset', () => {
112
+ // Generate new context for all Agents
113
+ server.resetAll();
114
+ })
115
+ }
116
+ /**
117
+ * Direct call to the prompt
118
+ * @param {string} input
119
+ * @returns {Promise<string>}
120
+ */
121
+ async directCall(input) {
122
+ const res = await this.#prompt.call(input);
123
+ return res;
124
+ }
125
+ /** @returns {Prompt} */
126
+ getPrompt() { return this.#prompt; }
127
+ /** @returns {ToolSet|void} */
128
+ getToolset() { return this.#prompt.toolset; }
129
+ /** @returns {Promise<import('./fafs.js').EnvironmentInfo>} */
130
+ async environment() {
131
+ return env();
132
+ }
133
+ /**
134
+ * @param {string} name - the toolname
135
+ */
136
+ addGenericToolcall(name) {
137
+ if (!this.#prompt.toolset) throw new Error('No ToolSet defined')
138
+ let tool = toolsPool.get(name);
139
+ if (!tool) throw new Error('Tool not defined')
140
+ this.#prompt.toolset.add(name, tool.description, tool.parameters, tool.method);
141
+ }
142
+ }
143
+
144
+ export default AgentManager;
@@ -0,0 +1,336 @@
1
+ import { WebSocketServer, server } from '@j-o-r/apiserver';
2
+
3
+ const TIMEOUT = 30000;
4
+ const MAX_INTERVALS = 20; // 10 minutes (30 sec * 20)
5
+
6
+ let ws;
7
+ /**
8
+ @typedef {import('../node_modules/@j-o-r/apiserver/types/ClientWrapper.d.ts').ClientWrapper} Client
9
+ @typedef {import('./Prompt.js').default} Prompt
10
+ @typedef {import('./ToolSet.js').default} ToolSet
11
+ @typedef {import('./Session.js').default} Session
12
+ */
13
+ /**
14
+ * @type {string|object} messageContent
15
+ */
16
+ /**
17
+ * @type {Object} message
18
+ * @property {string} action
19
+ * @property {messageContent} content
20
+ */
21
+ /**
22
+ * @typedef {(conn: import('@j-o-r/apiserver/types/WebSocketServer.js').WebSocketConnection) => Promise<boolean>} AuthFunction
23
+ */
24
+ /**
25
+ * @typedef {Object} AgentServerOptions
26
+ * @property {string} name
27
+ * @property {string} description
28
+ * @property {Prompt} prompt
29
+ * @property {Session} session
30
+ * @property {AuthFunction} [auth]
31
+ * @property {number} [port] - default 8000
32
+ * @property {boolean} [debug = false]
33
+ */
34
+
35
+ const ACTIONS = [
36
+ // Agent / Toolset function calls
37
+ 'agent_introduction', // Describe your task, your purpose and usage. This is a reaction on a 'introduce' requests
38
+ 'agent_error', // Message error
39
+ 'agent_query', // doing a query
40
+ 'agent_response', // Doing an response on a query
41
+ // Regular user interaction
42
+ 'user_introduction', // Websocket client connection on open
43
+ 'user_request', // A client user request
44
+ 'server_response', // A reponse to a user request
45
+ 'user_reset', // User requests a new session
46
+ 'user_sessionlist', // Request a list of sessions
47
+ 'user_loadsession', // Request to load a session
48
+ 'user_info', // User request server info
49
+ ];
50
+
51
+
52
+
53
+
54
+ // WS clients
55
+ const clients = new Map();
56
+ // Pending messages
57
+ const pendingResponses = new Map(); // key: `${conn_id}:${msg_id}`, value: {resolve, reject}
58
+
59
+ /**
60
+ * Delete a client from the toolset
61
+ * @param {ToolSet|void} toolset
62
+ * @param {string} conn_id - Connection id
63
+ */
64
+ const deleteClient = (toolset, conn_id) => {
65
+ if (!toolset) return;
66
+ const name = clients.get(conn_id);
67
+ if (name) {
68
+ clients.delete(conn_id);
69
+ if (toolset.has(name)) {
70
+ toolset.delete(name);
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * @param {ToolSet|void} toolset
76
+ * @param {string} conn_id - Connection id
77
+ * @param {string} name - logical name / task
78
+ * @param {string} description of the logical name
79
+ */
80
+ const addClient = (toolset, conn_id, name, description) => {
81
+ if (!toolset) return;
82
+ toolset.add(
83
+ name,
84
+ description,
85
+ {
86
+ type: 'object',
87
+ properties: {
88
+ query: {
89
+ type: 'string',
90
+ description: `Command, question or query for this particulair expert well formed in natural language`
91
+ }
92
+ },
93
+ required: ['query']
94
+ },
95
+ // @ts-ignore
96
+ (params) => {
97
+ return new Promise((resolve, reject) => {
98
+ const msg_id = Date.now().toString();
99
+ const key = `${conn_id}:${msg_id}`;
100
+ clients.set(conn_id, name);
101
+
102
+ // Start timeout/interval and keep its handle so we can clear it on response
103
+ let intervals = 0;
104
+ const timer = setInterval(() => {
105
+ intervals++;
106
+ if (intervals >= MAX_INTERVALS) {
107
+ clearInterval(timer);
108
+ if (pendingResponses.has(key)) {
109
+ reject('Timeout after max intervals');
110
+ pendingResponses.delete(key);
111
+ }
112
+ }
113
+ }, TIMEOUT);
114
+
115
+ // Store handlers and timer so onMessage can resolve/reject and clear the timer
116
+ pendingResponses.set(key, { resolve, reject, timer });
117
+
118
+ ws.sendToConnection(conn_id, JSON.stringify({
119
+ action: 'agent_query',
120
+ id: msg_id,
121
+ content: params.query
122
+ }));
123
+ }).then(response => (response));
124
+ })
125
+ }
126
+
127
+ // Provide a toolset based on websocket calls
128
+ class AgentServer {
129
+ #debug = false;
130
+ #port = 8000;
131
+ #name = '';
132
+ #description = '';
133
+ /** @type {Prompt} */
134
+ #prompt
135
+ /** @type {Session} */
136
+ #session
137
+ /** @type {AuthFunction} */
138
+ #auth = async (conn) => {
139
+ return true;
140
+ }
141
+ /**
142
+ * @param {AgentServerOptions} options
143
+ */
144
+ constructor(options) {
145
+ if (options.port) {
146
+ if (options.port < 1024) {
147
+ throw new Error('Port must be higher then 1024');
148
+ }
149
+ this.#port = options.port;
150
+ }
151
+ if (options.auth) {
152
+ this.#auth = options.auth;
153
+ }
154
+ this.#name = options.name;
155
+ this.#description = options.description;
156
+ this.#session = options.session;
157
+ this.#prompt = options.prompt;
158
+ const events = Object.keys(this.#prompt.EVENTS);
159
+ events.forEach((evt) => {
160
+ this.#prompt.on(evt, (_msg) => {
161
+ console.log(evt);
162
+ });
163
+ });
164
+ this.#debug = options.debug;
165
+ // start
166
+ (async () => {
167
+ await this._start();
168
+ })();
169
+ }
170
+ /**
171
+ * @private
172
+ * Returns a Dynamic Toolset
173
+ * With registered WS clients as toolcalls
174
+ * A deamon start on local host 127.0.0.1, always
175
+ * @returns {Promise<void>}
176
+ */
177
+ async _start() {
178
+ const name = this.#name;
179
+ const description = this.#description;
180
+ const port = this.#port;
181
+ const DEBUG = this.#debug;
182
+ const API = {
183
+ /**
184
+ * @param {Client} client
185
+ */
186
+ index: async (client) => {
187
+ await client.serve(200, { name, description });
188
+ }
189
+ };
190
+ ws = new WebSocketServer({
191
+ verbose: this.#debug,
192
+ onMessage: async (conn, msg) => {
193
+ if (!msg.action || !ACTIONS.includes(msg.action)) {
194
+ console.log('Unkown action: ' + msg.action);
195
+ deleteClient(this.#prompt.toolset, conn.id)
196
+ ws.closeConnection(conn.id);
197
+ return;
198
+ }
199
+ if (msg.action === 'agent_introduction') {
200
+ // Register Toolset Agent
201
+ // CLient is added as a Toolset function call
202
+ const name = msg.name;
203
+ const content = (typeof msg.content === 'string') ? msg.content : JSON.stringify(msg.content);
204
+ if (!content) {
205
+ deleteClient(this.#prompt.toolset, conn.id)
206
+ ws.closeConnection(conn.id);
207
+ return;
208
+ }
209
+ addClient(this.#prompt.toolset, conn.id, name, content);
210
+ return;
211
+ }
212
+ if (msg.action === 'agent_response') {
213
+ const key = `${conn.id}:${msg.id}`; // Assume client includes original 'id' in response
214
+ const pending = pendingResponses.get(key);
215
+ if (pending) {
216
+ try { clearInterval(pending.timer); } catch {}
217
+ pending.resolve(msg.content);
218
+ pendingResponses.delete(key);
219
+ }
220
+ return;
221
+ }
222
+ if (msg.action === 'agent_error') {
223
+ const key = `${conn.id}:${msg.id}`; // Assume client includes original 'id' in response
224
+ const pending = pendingResponses.get(key);
225
+ if (pending) {
226
+ try { clearInterval(pending.timer); } catch {}
227
+ pending.reject(msg.content);
228
+ pendingResponses.delete(key);
229
+ }
230
+ return;
231
+ }
232
+ // -------- USER INTERACTION --------------------------------------------------------
233
+ // CLI / WS interaction to MAIN
234
+ if (msg.action === 'user_request') {
235
+ const action = `user_reponse`;
236
+ const id = msg.id;
237
+ if (!msg.content || msg.content.trim() === '') {
238
+ // Empty message, empty reponse
239
+ ws.sendToConnection(conn.id, JSON.stringify({
240
+ action: 'user_response',
241
+ id,
242
+ content: ''
243
+ }));
244
+ return;
245
+ }
246
+ // Bind to THIS prompt
247
+ console.log(`user_request:>>\n${msg.content}\n`);
248
+ const content = await this.#prompt.call(msg.content);
249
+ ws.sendToConnection(conn.id, JSON.stringify({
250
+ action: 'server_response',
251
+ id,
252
+ content
253
+ }));
254
+ console.log(`server_reponse:>>\n${content}\n`);
255
+ }
256
+ if (msg.action === 'user_reset') {
257
+ const id = msg.id;
258
+ this.#prompt.reset();
259
+ ws.sendToConnection(conn.id, JSON.stringify({
260
+ action: 'server_reset',
261
+ id,
262
+ content: 'ok'
263
+ }));
264
+ }
265
+ if (msg.action === 'user_sessionlist') {
266
+ const id = msg.id;
267
+ const sess = this.#session.sessionList().reverse();
268
+ sess.push('NONE');
269
+ ws.sendToConnection(conn.id, JSON.stringify({
270
+ action: 'server_sessionlist',
271
+ id,
272
+ content: sess
273
+ }));
274
+ }
275
+
276
+ if (msg.action === 'user_loadsession') {
277
+ const id = msg.id;
278
+ const selected = msg.content;
279
+ const messages = [];
280
+ const list = this.#session.set(selected);
281
+ list.forEach((msg) => {
282
+ if (!msg.sticky) {
283
+ if (['user', 'assistant', 'reasoning', 'log'].includes(msg.role)) {
284
+ if (msg.role === 'assistant' && msg.content[0].type === 'function_request') {
285
+
286
+ } else {
287
+ const content = this.#prompt.contentToString(msg.content);
288
+ messages.push({ role: msg.role, content });
289
+ }
290
+ }
291
+ }
292
+ });
293
+ ws.sendToConnection(conn.id, JSON.stringify({
294
+ action: 'server_sessionloaded',
295
+ id,
296
+ content: messages
297
+ }));
298
+ }
299
+ if (msg.action === 'user_info') {
300
+ const id = msg.id;
301
+ const sessionInfo = this.#session.info();
302
+ const promptInfo = this.#prompt.info();
303
+ const info = `\nSESSION\n${sessionInfo}\nPROMPT\n${promptInfo}\nSERVER\nName: ${this.#name}\nDescription: ${this.#description}`;
304
+ ws.sendToConnection(conn.id, JSON.stringify({
305
+ action: 'server_info',
306
+ id,
307
+ content: info
308
+ }));
309
+ }
310
+ },
311
+ onConnection: async (conn) => {
312
+ return this.#auth(conn);
313
+ },
314
+ onClose: async (conn, reason) => {
315
+ deleteClient(this.#prompt.toolset, conn.id);
316
+
317
+ },
318
+ onError: async (conn, error) => {
319
+ // console.error(error);
320
+ deleteClient(this.#prompt.toolset, conn.id);
321
+ ws.closeConnection(conn.id);
322
+ }
323
+ });
324
+ await server.create('v1', { port, host: '127.0.0.1', ws, strict: DEBUG, verbose: DEBUG }, API);
325
+ console.log(`ws running on port: ${port}`);
326
+ }
327
+ /**
328
+ * Send a reset messages to all clients
329
+ * reset will start a new session
330
+ */
331
+ resetAll() {
332
+ ws.broadcast({ action: 'reset' });
333
+ }
334
+ }
335
+
336
+ export default AgentServer;