@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,416 @@
1
+ /*
2
+ https://platform.openai.com/docs/api-reference/responses/create
3
+ Allthough we can use store = true and previous_response_id: response.id,
4
+ Stil all tokens are billed.
5
+ */
6
+ import { GLOBAL } from '../../../fafs.js'
7
+ import { request as doRequest } from '@j-o-r/apiserver';
8
+ import { sleep } from '@j-o-r/sh';
9
+ /**
10
+ * @typedef {import('../../../Prompt.js').default} Prompt
11
+ * @typedef {import('../../../ToolSet.js').default} ToolSet
12
+ */
13
+ /**
14
+ * @typedef {'code_interpreter_call.outputs'|'computer_call_output.output.image_url'|'file_search_call.results'|'message.input_image.image_url'|'message.output_text.logprobs'|'reasoning.encrypted_conten'} Includes
15
+ */
16
+ /**
17
+ * @typedef {Object} OAOptions
18
+ * @property {boolean} [background=false] - Whether to run in the background.
19
+ * @property {?string} [include=null] - Optional include parameter.
20
+ * @property {OAInput[]} [input=[]] - Array of input objects.
21
+ * @property {string} [instructions=''] - Instructions for the model.
22
+ * @property {number} [max_output_tokens=4000] - Maximum number of output tokens.
23
+ * @property {number} [max_tool_calls=10] - Maximum number of tool calls allowed.
24
+ * @property {?Object} [metadata=null] - Optional metadata object.
25
+ * @property {'gpt-4.1'|'GPT-4o'|'o3'|'o3-mini'|'o4-mini'|'o1'|'o3-pro'} [model='gpt-4.1'] - Model version to use.
26
+ * @property {?string} [prompt=null] - Reusable prompt string.
27
+ * @property {Object} [reasoning={effort: 'medium', summary: 'auto'}] - Reasoning configuration.
28
+ * @property {'low'|'medium'|'high'|null} reasoning.effort - Effort level for reasoning.
29
+ * @property {'auto'|'concise'|'detailed'} reasoning.summary - Summary type for reasoning.
30
+ * @property {'low'|'medium'|'high'} [search]
31
+ * @property {'auto'} [service_tier='auto'] - Service tier option, default is auto-selected.
32
+ * @property {boolean} [store=false] - Whether to store the response or not.
33
+ * @property {boolean} [stream=false] - Whether to stream the response or not.
34
+ * @property {number} [temperature=1] - Sampling temperature between 0 and 2.
35
+ * @property {Object} [text={format: {type: "text"}}] - text format
36
+ * @property {'text'|'json_schema'} text.format - Summary type for reasoning.
37
+ * @property {boolean} [parallel_tool_calls=true] - Allow parallel tool calls if true.
38
+ * @property {'auto'} [tool_choice='auto'] - Tool choice setting, default is auto-selected.
39
+ * @property {OAToolCall[]} tools=[] Array of tool call objects.
40
+ * @property {?number } top_logprobs=null An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability.
41
+ * @property {?number } top_p=1 Top-p sampling value between 0 and 1.
42
+ * @property {?string} [previous_response_id] - Needed reponse_id to validate tool calls on the next request
43
+ */
44
+ /**
45
+ * @typedef {Object} OAToolCall
46
+ * @property {string} type - The type of the tool call, e.g., "function".
47
+ * @property {string} name - The name of the function.
48
+ * @property {string} description - A brief description of what the function does.
49
+ * @property {Object} parameters - The parameters required by the function.
50
+ * @property {string} parameters.type - The type of parameters object, usually "object".
51
+ * @property {Object.<string, OAParameterProperty>} parameters.properties - An object describing each parameter property.
52
+ * @property {string[]} [parameters.required] - An array of required parameter names.
53
+ */
54
+
55
+ /**
56
+ * @typedef {Object} OAParameterProperty
57
+ * @property {string} type - The data type of the parameter, e.g., "string".
58
+ * @property {string} description - A brief description of the parameter.
59
+ */
60
+ /**
61
+ * @typedef {Object} OAMessage
62
+ * @property {'assistant'|'user'} role - The role of the participant (e.g., "assistant", "user").
63
+ * @property {Array<OAContent>} content - The content of the message.
64
+ */
65
+ /**
66
+ * @typedef {Object} OAContent
67
+ * @property {'output_text'|'input_text'} type - The type of content (e.g., "output_text", "input_text").
68
+ * @property {string} text - The text content.
69
+ */
70
+ /**
71
+ * @typedef {Object} OAFunctionCall
72
+ * @property {string} id - Unique identifier for the function call.
73
+ * @property {'function_call'} type - Indicates this is a function call (e.g., "function_call").
74
+ * @property {string} call_id - Unique identifier for the specific function call instance.
75
+ * @property {string} name - The name of the function being called.
76
+ * @property {string} arguments - JSON string representing arguments passed to the function.
77
+ */
78
+ /**
79
+ * @typedef {Object} OAFunctionCallOutput
80
+ * @property {'function_call_output'} type - Indicates this is a function call output (e.g., "function_call_output").
81
+ * @property {string} call_id - Identifier linking to the related function call.
82
+ * @property {string} output - JSON string representing the output from the function call.
83
+ */
84
+ /**
85
+ * Represents an OpenAI API response request INPUT structure.
86
+ *
87
+ * @typedef {(OAMessage|OAFunctionCall|OAFunctionCallOutput)} OAInput
88
+ */
89
+
90
+ const API_URL = 'https://api.openai.com/v1/responses';
91
+
92
+ /**
93
+ * Get the default headers
94
+ * @returns {object}
95
+ */
96
+ const getHeaders = (USER_AGENT = '@j-o-r/agents') => {
97
+ if (!process.env['OPENAIKEY']) {
98
+ throw new Error('Missing OPENAIKEY!export OPENAIKEY=<OPENAI_API_KEY>')
99
+ }
100
+ const KEY = process.env['OPENAIKEY'];
101
+ return {
102
+ 'User-Agent': USER_AGENT,
103
+ 'Content-Type': 'application/json',
104
+ 'Authorization': `Bearer ${KEY}`
105
+ }
106
+ }
107
+
108
+
109
+ /**
110
+ * Convert a toolset to something openai understands
111
+ * @param {ToolSet} toolset
112
+ * @returns {OAToolCall[]}
113
+ */
114
+ const generateTools = (toolset) => {
115
+ const list = toolset.list();
116
+ /** @type {OAToolCall[]} */
117
+ const result = [];
118
+ list.forEach((item) => {
119
+ result.push({
120
+ type: 'function',
121
+ name: item.name,
122
+ description: item.description,
123
+ parameters: item.parameters
124
+ })
125
+ });
126
+ return result;
127
+ }
128
+
129
+ /**
130
+ * Convert messages to OPENAI requirements
131
+ * @param {import("lib/Session").Message[]} messages
132
+ * @returns {OAInput[]}
133
+ */
134
+ const generateHistory = (messages) => {
135
+ return messages.reduce((result, msg) => {
136
+ delete msg.sticky;
137
+ delete msg.ts;
138
+ if (['assistant', 'user'].includes(msg.role)) {
139
+ /** @type {OAFunctionCall[]} */
140
+ // @ts-ignore
141
+ const fr = msg.content.map(
142
+ /** @param {import('../../../Prompt.js').FunctionRequest} item */
143
+ (item) => {
144
+ // Transform function requests
145
+ if (item.type === 'function_request') {
146
+ return {
147
+ // @ts-ignore
148
+ // id: item.function_request.id,
149
+ type: 'function_call',
150
+ call_id: item.function_request.call_id,
151
+ name: item.function_request.name,
152
+ arguments: item.function_request.parameters
153
+ }
154
+ }
155
+ }).filter(item => item !== undefined);
156
+ if (fr.length > 0) {
157
+ result.push(...fr);
158
+ }
159
+ // transform normal text input
160
+ const content = msg.content.map((item) => {
161
+ if (item.type == 'text') {
162
+ item.type = msg.role === 'user' ? 'input_text' : 'output_text'
163
+ return item;
164
+ }
165
+ }).filter(item => item !== undefined);
166
+ if (content.length > 0) {
167
+ msg.content = content;
168
+ result.push(msg);
169
+ }
170
+ } else if (['tool'].includes(msg.role)) {
171
+ // transform function responses
172
+ const ar = msg.content.map(
173
+ /** @param {import('../../../Prompt.js').FunctionResponse} item */
174
+ (item) => {
175
+ if (item.type === 'function_response') {
176
+ return {
177
+ type: 'function_call_output',
178
+ call_id: item.function_response.call_id,
179
+ output: item.function_response.response
180
+ }
181
+ }
182
+ }).filter(item => item !== undefined);
183
+ if (ar.length > 0) {
184
+ result.push(...ar);
185
+ }
186
+ }
187
+ return result;
188
+ }, []);
189
+ }
190
+
191
+
192
+
193
+ // tools": [{"type": "web_search_preview"}],
194
+ //** @type {OAOptions} */
195
+ const defaultSettings = {
196
+ background: false,
197
+ // search: undefined,
198
+ // include: null,
199
+ // input: [],
200
+ // instructions: '',
201
+ // max_output_tokens: 4000,
202
+ // metadata: null,
203
+ model: 'o3',
204
+ // // https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts
205
+ // prompt: null,
206
+ // https://platform.openai.com/docs/api-reference/responses/create#responses-create-reasoning
207
+ // reasoning must be present fot the function_calls to work
208
+ // reasoning: {
209
+ // effort: null, // null|'low'|'medium'|'high'
210
+ // summary: null // auto, concise, or detailed
211
+ // },
212
+ // service_tier: 'auto',
213
+ // store: false,
214
+ stream: false,
215
+ // temperature: 0.2, // value between 0 -2
216
+ // text: { format: { type: "text" } }, // optional
217
+ parallel_tool_calls: true,
218
+ max_tool_calls: 10,
219
+ // // tool_choice: 'auto', // optional
220
+ // // tools: [],
221
+ // top_logprobs: 0,
222
+ // top_p: 1,
223
+ // truncation: null,
224
+ }
225
+
226
+
227
+
228
+ /**
229
+ * Generate the POST body of a request
230
+ * @param {Prompt} prompt
231
+ * @param {ToolSet} [toolset]
232
+ * @param {OAOptions} [options]
233
+ */
234
+ function getBody(prompt, toolset, options) {
235
+ // From the messages I only want assistant and user
236
+ /** @type {OAOptions} */
237
+ // @ts-ignore
238
+ const body = { ...defaultSettings, ...options };
239
+ // const body = options ? mergeOptions(DEFAULT_OPTIONS, options) : DEFAULT_OPTIONS;
240
+ body.instructions = prompt.hasSystemprompt ? prompt.system_prompt : '';
241
+ // the last messages should be a role tool or user else the model cannot handle the request
242
+ const messages = prompt.messages;
243
+ if (!['user', 'tool'].includes(messages[messages.length - 1].role)) {
244
+ throw new Error('Missing last user question or tool');
245
+ }
246
+ body.input = generateHistory(messages);
247
+ if (toolset) {
248
+ // @ts-ignore
249
+ body.tool_choice = toolset.toolChoice;
250
+ body.tools = generateTools(toolset);
251
+ body.parallel_tool_calls = true;
252
+ // parallel_tool_calls
253
+ }
254
+ // Add OPENAI SEARCH
255
+ // in the toolset
256
+ if (body.search) {
257
+ if (!body.tools) {
258
+ body.tools = [];
259
+ }
260
+ body.tools.push({
261
+ type: 'web_search_preview',
262
+ // @ts-ignore
263
+ search_context_size: body.search
264
+ })
265
+ }
266
+ delete body.search;
267
+ return body;
268
+
269
+ }
270
+ /**
271
+ * @param {Prompt} prompt
272
+ * @param {ToolSet} [toolset]
273
+ * @param {OAOptions} [options]
274
+ */
275
+ function generateRequest(prompt, toolset, options) {
276
+ const headers = getHeaders();
277
+ const url = API_URL;
278
+ const body = getBody(prompt, toolset, options);
279
+ return { url, headers, body }
280
+ }
281
+ /**
282
+ * Parse response messages (output)
283
+ * @param {Array<*>} messages
284
+ * @param {Prompt} prompt
285
+ */
286
+ function parseMessages(messages, prompt) {
287
+ /** @type {import('../../../Prompt.js').FunctionRequest[]} */
288
+ const toolcall = [];
289
+ let i = 0;
290
+ const len = messages.length;
291
+ for (; i < len; i++) {
292
+ const msg = messages[i];
293
+ // types:
294
+ const type = msg.type;
295
+ if (type === 'reasoning') {
296
+ let messages = msg.summary.map((item) => {
297
+ if (item.type === 'summary_text') {
298
+ return { type: 'text', text: item.text }
299
+ }
300
+ });
301
+ messages = messages.filter(item => item !== undefined);
302
+ if (messages.length > 0) {
303
+ prompt.addMultiModal('reasoning', messages);
304
+ }
305
+ } else if (type === 'message') {
306
+ let messages = msg.content.map((item) => {
307
+ if (item.type === 'output_text') {
308
+ return { type: 'text', text: item.text }
309
+ }
310
+ });
311
+ messages = messages.filter(item => item !== undefined);
312
+ if (messages.length > 0) {
313
+ prompt.addMultiModal('assistant', messages);
314
+ }
315
+ } else if (type === 'image_generation_call') {
316
+ prompt.addMultiModal('assistant', [{ type: 'image_url', url: msg.result }]);
317
+ } else if (type === 'function_call' || msg.status === 'complete') {
318
+ toolcall.push({
319
+ type: 'function_request',
320
+ function_request: {
321
+ name: msg.name,
322
+ id: msg.id,
323
+ call_id: msg.call_id,
324
+ parameters: msg.arguments
325
+ }
326
+
327
+ });
328
+ }
329
+ }
330
+ if (toolcall.length > 0) {
331
+ prompt.addMultiModal('assistant', toolcall);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Parse a response
337
+ * @param {number} duration - Fetch time in MS
338
+ * @param {Prompt} prompt
339
+ * @param {import('../../../request.js').FetchResponse} res
340
+ * @param {import('../../../ToolSet.js').default} [toolset]
341
+ */
342
+ async function parseResponse(duration, prompt, res, toolset) {
343
+ if (res.status !== 200) {
344
+ throw new Error(`${res.status}: ${JSON.stringify(res.response, null, ' ')}`)
345
+ }
346
+ // 1 = make a records
347
+ const record = prompt.templateRecord();
348
+ record.model = res.response.model;
349
+ record.id = res.response.id;
350
+ record.environment = API_URL;
351
+ record.isoDate = new Date(res.response.created_at * 1000).toISOString();
352
+ record.tokensIn = res.response.usage.input_tokens;
353
+ record.tokensOut = res.response.usage.output_tokens;
354
+ record.duration = duration;
355
+ // Parse the messages and and those to the prompt
356
+ parseMessages(res.response.output, prompt);
357
+ if (toolset) {
358
+ // Inspect for function_request and execute if so
359
+ await toolset.execute(prompt);
360
+ }
361
+ prompt.addRecord(record);
362
+ }
363
+
364
+ /**
365
+ * Do a request
366
+ * @param {Prompt} prompt
367
+ * @param {ToolSet|null} toolset
368
+ * @param {OAOptions} [options]:
369
+ * @param {number} [counter] - leave empty this counts the number of requests when doing recursive request calls
370
+ * @return {Promise<import('../../../Session.js').Message>}
371
+ */
372
+ async function request(prompt, toolset = null, options, counter = 0) {
373
+ // Max number of recurusive calls
374
+ const counterMax = GLOBAL.max_recursive_requests;
375
+ const start = new Date().getTime();
376
+ if (counter === 0) {
377
+ // Fresh request
378
+ delete options.previous_response_id
379
+ }
380
+ // Generate the request
381
+ const { url, headers, body } = generateRequest(prompt, toolset, options);
382
+ prompt.emit(prompt.EVENTS.http_request, {url, counter, body});
383
+ const res = await doRequest(url, 'POST', headers, body);
384
+ prompt.emit(prompt.EVENTS.http_response, res);
385
+ if (res.status !== 200) {
386
+ throw new Error(`${res.status}: ${JSON.stringify(res.response, null, ' ')}`)
387
+ }
388
+ /*
389
+ https://platform.openai.com/docs/assistants/deep-dive#runs-and-run-steps
390
+ status` can be: in_progress, completed, incomplete (plus searching / generating / failed for some tool-call items)
391
+ */
392
+ const duration = new Date().getTime() - start;
393
+ await parseResponse(duration, prompt, res, toolset);
394
+ const previousId = res.response.id;
395
+ counter = 1 + counter;
396
+ if (counter >= counterMax) {
397
+ throw new Error('Max number of recursive calls reached');
398
+ }
399
+ const lastMessage = prompt.getLastMessage();
400
+ if (!lastMessage) throw new Error('No message found');
401
+ if (lastMessage.role === 'tool') {
402
+ // need to do another roundtrip to include the funtion_responses
403
+ // And add the previousId for toolsets to work
404
+ if (!options) {
405
+ // @ts-ignore
406
+ options = {previous_response_id: previousId}
407
+ } else {
408
+ options.previous_response_id = previousId
409
+ }
410
+ await sleep('2s');
411
+ return request(prompt, toolset, options, counter)
412
+ } else {
413
+ return lastMessage
414
+ }
415
+ }
416
+ export { generateRequest, parseResponse, request }