@j-o-r/hello-dave 0.0.6 → 0.0.8

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 (66) hide show
  1. package/CHANGELOG.md +19 -33
  2. package/README.md +240 -0
  3. package/TODO.md +13 -0
  4. package/{examples → agents}/ask_agent.js +5 -5
  5. package/{examples → agents}/codeserver.sh +14 -14
  6. package/{examples → agents}/daisy_agent.js +5 -5
  7. package/{examples → agents}/docs_agent.js +5 -5
  8. package/{examples → agents}/gpt_agent.js +5 -5
  9. package/{examples → agents}/grok_agent.js +5 -5
  10. package/{examples → agents}/memory_agent.js +5 -5
  11. package/{examples → agents}/npm_agent.js +5 -5
  12. package/{examples → agents}/prompt_agent.js +5 -5
  13. package/agents/spawn_agent.js +137 -0
  14. package/{examples → agents}/test_agent.js +6 -6
  15. package/{examples → agents}/todo_agent.js +5 -5
  16. package/bin/codeDave +58 -0
  17. package/bin/dave.js +3 -5
  18. package/docs/agent-manager.md +244 -0
  19. package/docs/bin-dave.md +62 -0
  20. package/docs/codeserver-pattern.md +191 -0
  21. package/docs/generic-toolset.md +326 -0
  22. package/docs/howtos/agent-networking.md +253 -0
  23. package/docs/howtos/spawn-agents.md.bak +200 -0
  24. package/docs/howtos/spawn-agents.md.bak_new +200 -0
  25. package/docs/jsdoc-best-practices.md +278 -0
  26. package/docs/multi-agent-clusters.md +265 -0
  27. package/docs/multi-agent-clusters.md.bak +229 -0
  28. package/docs/path-resolution-best-practices.md +104 -0
  29. package/docs/project-overview.md +67 -0
  30. package/docs/prompt/spawn_agent.md +173 -0
  31. package/docs/prompt/spawn_agent.md.bak +201 -0
  32. package/docs/prompt-class.md +141 -0
  33. package/docs/suggestions.md +38 -0
  34. package/docs/todo-archive-v0.0.8.md +1 -0
  35. package/docs/todo-archive.md +44 -0
  36. package/docs/tools-syntax-validation.md +121 -0
  37. package/docs/toolset.md +164 -0
  38. package/docs/xai-responses.md +111 -0
  39. package/docs/xai_collections.md +106 -0
  40. package/lib/AgentClient.js +111 -67
  41. package/lib/AgentManager.js +111 -80
  42. package/lib/AgentServer.js +144 -104
  43. package/lib/Cli.js +126 -93
  44. package/lib/Prompt.js +38 -5
  45. package/lib/Session.js +102 -79
  46. package/lib/ToolSet.js +79 -60
  47. package/lib/fafs.js +54 -19
  48. package/lib/genericToolset.js +129 -136
  49. package/lib/wsCli.js +50 -19
  50. package/lib/wsIO.js +10 -6
  51. package/package.json +3 -3
  52. package/types/AgentClient.d.ts +69 -35
  53. package/types/AgentManager.d.ts +50 -56
  54. package/types/AgentServer.d.ts +63 -16
  55. package/types/Cli.d.ts +56 -10
  56. package/types/Prompt.d.ts +36 -4
  57. package/types/Session.d.ts +23 -9
  58. package/types/ToolSet.d.ts +49 -32
  59. package/types/fafs.d.ts +68 -25
  60. package/types/wsCli.d.ts +14 -0
  61. package/types/wsIO.d.ts +9 -5
  62. package/utils/search_sessions.sh +100 -53
  63. package/bin/spawn_agent.js +0 -293
  64. package/lib/genericToolset.js.bak_syntax +0 -402
  65. /package/{examples → agents}/code_agent.js +0 -0
  66. /package/{examples → agents}/readme_agent.js +0 -0
@@ -0,0 +1,164 @@
1
+ # ToolSet (\`lib/ToolSet.js\`)
2
+
3
+ ## Overview
4
+ **ToolSet** manages LLM function calls (tools): registration, execution, listing. Integrates with \`Prompt.js\` (via \`execute(prompt)\`) and \`AgentManager.js\` (pre-configured modes, generics). Supports JSON Schema params, async methods. Exports from \`lib/genericToolset.js\`: \`toolsPool\` (11 pre-built tools).
5
+
6
+ **Uses named exports from \`lib/index.js\` - NO default export.**
7
+
8
+ Key Features:
9
+ - **Modes**: \`toolChoice: 'auto'|'none'|'required'\` (default \`'auto'\`).
10
+ - **Validation**: Name regex \`/^\[#![a-z_0-9]{2,}$/\`.
11
+ - **Events** (via \`Prompt\`): \`tool_request\`, \`tool_error\`, \`tool_response\`.
12
+ - **Execution**: \`execute(prompt)\` processes \`function_request\`s from last message → calls → adds \`function_response\`s.
13
+
14
+ ## Constructor
15
+ \`\`\`javascript
16
+ import { ToolSet } from '@j-o-r/hello-dave';
17
+
18
+ new ToolSet(choice = 'auto'); // 'auto'|'none'|'required'
19
+ \`\`\`
20
+
21
+ ## Methods
22
+ | Method | Args | Returns | Description |
23
+ |--------|------|---------|-------------|
24
+ | \`add(name, desc, params, method)\` | \`string\`, \`string\`, \`TSSchema\`, \`async (params) => *\` | \`void\` | Register tool. Overwrites if exists. |
25
+ | \`get(name)\` | \`string\` | \`TSTool\` | Get tool details. |
26
+ | \`delete(name)\` | \`string\` | \`void\` | Remove tool. |
27
+ | \`has(name)\` | \`string\` | \`boolean\` | Exists? |
28
+ | \`list()\` | - | \`TSToolListItem[]\` (sorted) | All tools (name/desc/params). |
29
+ | \`call(name, params)\` | \`string\`, \`object\` | \`Promise<*\>\` | Execute tool. |
30
+ | \`execute(prompt)\` | \`Prompt\` | \`Promise<void>\` | Process last message \`function_request\`s → execute → add \`function_response\`s to \`tool\` role. |
31
+ | \`length\` | - | \`number\` | Tool count. |
32
+ | \`toolChoice\` | - | \`string\` | Getter for mode. |
33
+
34
+ **TSTool**: \`{description: string, parameters: TSSchema, method: async (params) => *}\`
35
+ **TSSchema**: OpenAI-style JSON Schema (\`{type: 'object', properties: {...}, required: [...]}\`).
36
+
37
+ ## Generic Tools (\`lib/genericToolset.js\` → \`toolsPool\`)
38
+ | `syntax_check` | Validate syntax (JS/Py/Bash/JSON) via utils/syntax_check.sh. |\n|-----|-----|
39
+ Pre-built \`ToolSet('auto')\` with **11 tools**. Copied via \`AgentManager.addGenericToolcall(name)\` or \`toolsetMode: 'auto'\`.
40
+
41
+ | Tool Name | Description |
42
+ |----------------------------|-------------|
43
+ | \`javascript_interpreter\` | Execute ESM ES6 JavaScript on \`node\`. \`console.log\` captures output. cwd: \`~/devpri/js/hello-dave\`. |
44
+ | \`get_user_env\` | Get user environment (name, system, city/region/country/timezone, external IP). |
45
+ | \`execute_bash_script\` | Execute raw Bash script/command (no escaping; verbatim \`$\| < > & " ' \\\\` newlines \`$(())\` \`[[ ]]\`. Heredoc-safe). Ubuntu 25.10. |
46
+ | \`send_email\` | Send email via \`msmtp\` (to, subject, body). |
47
+ | \`open_link\` | Open URL/file with \`xdg-open\`. |
48
+ | \`execute_remote_script\` | Execute raw Bash on remote via SSH (\`ssh://user@host[:port]\`). |
49
+ | \`history_search\` | Search chat sessions: \`"(todo\\|task)"\` or \`"package.json"\`. Regex in \`.cache/[app]/[prompt]/sessions/*.ndjson\`. |
50
+ | \`read_file\` | Read raw file in CWD (relative path, no \`/\` \`..\` \`\\\\\`). |
51
+ | \`write_file\` | Write raw content to file in CWD (relative, as-is; no escaping). Returns bytes written. |
52
+ | \`memory_recall\` | Recall persistent memory entries from agent-specific storage (\`.cache/[agent]/memory.ndjson\`). |
53
+ | \`memory_write\` | Write/update persistent memory entries for long-term state (\`.cache/[agent]/memory.ndjson\`). |
54
+
55
+ ## Memory Tools (New)
56
+ These tools provide persistent, agent-specific memory storage in NDJSON format (\`.cache/[agent-name]/memory.ndjson\`). Append-only; latest value per key. Ideal for token efficiency by offloading state/decisions from context.
57
+
58
+ ### \`memory_recall\`
59
+ **Description**: Retrieve the latest values for specified keys from memory file. Returns JSON: \`{key: value, ...}\` or \`{}\` if missing.
60
+
61
+ **Parameters**:
62
+ \`\`\`json
63
+ {
64
+ "type": "object",
65
+ "properties": {
66
+ "keys": {
67
+ "type": "array",
68
+ "items": { "type": "string" },
69
+ "description": "List of memory keys to recall, e.g., ['current_task', 'last_decision']"
70
+ }
71
+ },
72
+ "required": ["keys"]
73
+ }
74
+ \`\`\`
75
+
76
+ **Example Usage in Agent Prompt**:
77
+ \`\`\`
78
+ Before proceeding, recall your current task and previous decision:
79
+ - Call memory_recall with keys: ["current_task", "last_decision"]
80
+ Then, use the recalled info to avoid repetition.
81
+ \`\`\`
82
+
83
+ **Token Efficiency**: Stores tasks/decisions externally. Recall only needed state (e.g., 100 tokens) vs. full history (10k+ tokens), preventing loops/re-reasoning.
84
+
85
+ ### \`memory_write\`
86
+ **Description**: Append/update key-value pairs to memory (overwrites prior same-key entries). Returns confirmation with stored entries.
87
+
88
+ **Parameters**:
89
+ \`\`\`json
90
+ {
91
+ "type": "object",
92
+ "properties": {
93
+ "entries": {
94
+ "type": "array",
95
+ "items": {
96
+ "type": "object",
97
+ "properties": {
98
+ "key": { "type": "string", "description": "Unique key (e.g., 'current_task')" },
99
+ "value": { "type": "string", "description": "JSON-stringifiable value" }
100
+ },
101
+ "required": ["key", "value"]
102
+ },
103
+ "description": "List of {key, value} pairs to store"
104
+ }
105
+ },
106
+ "required": ["entries"]
107
+ }
108
+ \`\`\`
109
+
110
+ **Example Usage in Agent Prompt**:
111
+ \`\`\`
112
+ After analyzing, store the plan:
113
+ - Call memory_write with entries: [{"key": "current_task", "value": "Implement login feature"}, {"key": "step", "value": "1/5"}]
114
+ \`\`\`
115
+
116
+ **Token Efficiency**: Persist intermediate results across calls/sessions. Avoids context explosion/loops by recalling compact state instead of regenerating.
117
+
118
+ ## Integration with AgentManager
119
+ In \`setup()\`:
120
+ \`\`\`javascript
121
+ agent.setup({
122
+ toolsetMode: 'auto', // Loads all 11 generic tools
123
+ // or 'required': Empty ToolSet (add manually)
124
+ });
125
+ agent.addGenericToolcall('memory_recall'); // Selective copy post-setup
126
+ \`\`\`
127
+
128
+ **Custom Tools**:
129
+ \`\`\`javascript
130
+ import { AgentManager } from '@j-o-r/hello-dave';
131
+
132
+ agent.setup({ toolsetMode: 'required' });
133
+ const ts = agent.getToolset();
134
+ ts.add('math', 'Add numbers', {
135
+ type: 'object',
136
+ properties: {a: {type: 'number'}, b: {type: 'number'}},
137
+ required: ['a', 'b']
138
+ }, async ({a, b}) => a + b);
139
+ \`\`\`
140
+
141
+ **Workflow** (in \`Prompt.call()\` / API adapters):
142
+ 1. LLM → \`function_request\`s (assistant message).
143
+ 2. \`ToolSet.execute(prompt)\` → parallel \`call()\` → \`function_response\`s (tool message).
144
+ 3. Recurse until text response.
145
+
146
+ ## Examples
147
+ ### Standalone ToolSet
148
+ \`\`\`javascript
149
+ import { ToolSet } from '@j-o-r/hello-dave';
150
+
151
+ const ts = new ToolSet('required');
152
+ ts.add('hello', 'Say hello', {type: 'object', properties: {name: {type: 'string'}}, required: ['name']},
153
+ async ({name}) => \`Hello, \${name}!\`);
154
+ console.log(await ts.call('hello', {name: 'World'})); // "Hello, World!"
155
+ \`\`\`
156
+
157
+ ### With Prompt/AgentManager
158
+ See [AgentManager](./agent-manager.md#custom-tools--generics), [Prompt](./prompt-class.md).
159
+
160
+ **Updated:** March 26, 2026 (11 generics from \`lib/genericToolset.js\`).
161
+
162
+ See also: [GenericToolset Source](./generic-toolset.md).
163
+ \n## Syntax Validation\nSee [tools-syntax-validation.md](tools-syntax-validation.md) for `write_file` JS checks + `syntax_check` tool.
164
+ \n## Path Resolution\nSee [path-resolution-best-practices.md](path-resolution-best-practices.md) for __dirname vs cwd() in tools (e.g., syntax_check.sh).\\n
@@ -0,0 +1,111 @@
1
+ # xAI Responses API Integration (`lib/API/x.ai/responses.js`)
2
+
3
+ ## Overview
4
+ This module provides a seamless wrapper for the [xAI Responses API](https://docs.x.ai/docs/api-reference#create-new-response) (formerly Grok API), adapted for the hello-dave framework. It integrates directly with `Prompt.js` and `ToolSet.js`, enabling tool-using agents via `AgentManager`.
5
+
6
+ **Uses named exports from `lib/index.js` - NO default export.**
7
+
8
+ Key adaptations:
9
+ - **Unified Interface**: Uses `Prompt` messages/history for input; parses `output` into assistant/tool responses.
10
+ - **Recursive Tool Calls**: Automatically handles `function_call` → execute tools → `function_call_output` loops (up to `GLOBAL.max_recursive_requests`).
11
+ - **Tool Support**: Converts `ToolSet` to xAI `function` tools; supports native xAI tools like `web_search`, `x_search`.
12
+ - **Reasoning & Search**: Configurable `reasoning.effort` (`low`/`medium`/`high`), `search_parameters`.
13
+ - **Streaming**: Not yet implemented (future).
14
+ - **Auth**: Requires `XAIKEY` env var.
15
+
16
+ Default model: `grok-4-fast-reasoning`. Supports: `grok-4-fast-reasoning`, `grok-4-fast-non-reasoning`, etc.
17
+
18
+ ## Usage in AgentManager
19
+ ```javascript
20
+ import { AgentManager } from '@j-o-r/hello-dave';
21
+
22
+ const agent = new AgentManager({ name: 'weatheragent', secret: 'optional-ws-secret' });
23
+ agent.setup({
24
+ prompt: 'You are a precise weather expert. Use tools for real-time data.',
25
+ api: 'xai',
26
+ options: { model: 'grok-4-fast-reasoning', reasoning: { effort: 'high' } },
27
+ toolsetMode: 'auto', // Adds generic tools like web_search
28
+ contextWindow: 128000
29
+ });
30
+ agent.addGenericToolcall('web_search'); // Optional: Add extras
31
+
32
+ // CLI mode
33
+ agent.start(); // Interactive chat
34
+
35
+ // Or direct call
36
+ const response = await agent.directCall('Weather in Amsterdam today?');
37
+ console.log(response); // Handles search/tools automatically
38
+ ```
39
+
40
+ Under the hood:
41
+ 1. `Prompt.setAdaptor(API.text['xai'], toolset, options)` → uses `responses.request()`.
42
+ 2. Converts history to xAI `input[]` (system/user/assistant/tool).
43
+ 3. POST to `https://api.x.ai/v1/responses`.
44
+ 4. Parses `output` → adds text/reasoning/tools to `Prompt`.
45
+ 5. If tools needed, `ToolSet.execute()` → recurse until complete.
46
+
47
+ ## Options (XAIOptions)
48
+ | Property | Type | Default | Description |
49
+ |----------|------|---------|-------------|
50
+ | `model` | string | `'grok-4-fast-non-reasoning'` | e.g., `'grok-4-fast-reasoning'` |
51
+ | `input` | `XAIInput[]` | `[{role: 'system', content: 'Be precise'}]` | Messages/history |
52
+ | `reasoning.effort` | `'low'\|'medium'\|'high'` | `'medium'` | Reasoning depth |
53
+ | `reasoning.summary` | `'auto'\|'concise'\|'detailed'` | `'auto'` | Reasoning output style |
54
+ | `temperature` | number | `1` (0-2) | Sampling |
55
+ | `parallel_tool_calls` | boolean | `true` | Parallel functions |
56
+ | `tool_choice` | `'auto'` | `'auto'` | Tool selection |
57
+ | `tools` | `XAIFunctionTool[]` | `[]` | From `ToolSet` |
58
+ | `store` | boolean | `false` | Persist response (still bills tokens) |
59
+ | `max_output_tokens` | number | `4000` | Limit |
60
+ | `search_parameters` | object | - | `{max_search_results: 4, mode: 'auto', return_citations: true}` |
61
+
62
+ **Tool Types**:
63
+ - `function`: From `ToolSet` (name, description, parameters JSONSchema).
64
+ - Native: `web_search`, `x_search` (filters: dates, domains, etc.).
65
+
66
+ ## Example Request (Generated Internally)
67
+ ```json
68
+ {
69
+ "model": "grok-4-fast-reasoning",
70
+ "input": [
71
+ {"role": "system", "content": "You are a precise weather expert."},
72
+ {"role": "user", "content": "Weather in Amsterdam today?"}
73
+ ],
74
+ "parallel_tool_calls": true,
75
+ "reasoning": {"effort": "high"},
76
+ "search_parameters": {"mode": "auto", "return_citations": true},
77
+ "tools": [{"type": "function", "name": "web_search", ...}]
78
+ }
79
+ ```
80
+
81
+ ## Example Response (Parsed to Prompt)
82
+ - **Text + Citations**: Added as `assistant` text + `log` annotations (URLs).
83
+ - **Function Calls**: `assistant` with `function_request` → `ToolSet.execute()`.
84
+ - **Reasoning**: Prepended as `reasoning` multimodal text.
85
+
86
+ Sample parsed:
87
+ ```
88
+ Today is [date]. Weather: Cloudy, 5°C... [citations]
89
+ ```
90
+ Tokens: `input_tokens`, `output_tokens` (incl. `reasoning_tokens`) logged in `Prompt.records`.
91
+
92
+ ## Advanced: Custom Tools
93
+ Extend `ToolSet` before `setup()`:
94
+ ```javascript
95
+ import { ToolSet } from '@j-o-r/hello-dave';
96
+
97
+ const ts = new ToolSet('required');
98
+ ts.add('get_weather', 'Get weather for a location', {
99
+ type: 'object',
100
+ properties: { location: {type: 'string'} },
101
+ required: ['location']
102
+ }, async (params) => `Weather: ${params.location} - 20°C`); // Handler
103
+ agent.setup({ ..., toolsetMode: ts });
104
+ ```
105
+
106
+ ## Errors & Limits
107
+ - `Max recursive calls`: `GLOBAL.max_recursive_requests`.
108
+ - Missing `XAIKEY`: Throws.
109
+ - Invalid last message: Must end in `user`/`tool`.
110
+
111
+ See `lib/API/x.ai/responses.js` source for full JSDoc/types. Cross-links: [Prompt](../prompt-class.md), [ToolSet](../toolset.md), [AgentManager](agent-manager.md).
@@ -0,0 +1,106 @@
1
+ [x.ai collections](https://docs.x.ai/docs/guides/using-collections/api)
2
+
3
+
4
+ Creating a collection:
5
+
6
+ ```bash
7
+ curl https://management-api.x.ai/v1/collections \
8
+ -X POST \
9
+ -H "Content-Type: application/json" \
10
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY" \
11
+ -d '{"collection_name": "SEC Filings"}'
12
+ ```
13
+
14
+ Listing collections:
15
+ ```bash
16
+ curl https://management-api.x.ai/v1/collections \
17
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY"
18
+ ```
19
+
20
+ Viewing collection configuration:
21
+ ```bash
22
+ 'curl https://management-api.x.ai/v1/collections/collection_dbc087b1-6c99-493d-86c6-b401fee34a9d \
23
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY"
24
+ ```
25
+
26
+ Updating collection configuration
27
+ ```bash
28
+ curl https://management-api.x.ai/v1/collections/collection_dbc087b1-6c99-493d-86c6-b401fee34a9d \
29
+ -X PUT \
30
+ -H "Content-Type: application/json" \
31
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY" \
32
+ -d '{"collection_name": "SEC Filings (New)"}'
33
+ ```
34
+
35
+ Uploading documents:
36
+
37
+ ```bash
38
+ # Step 1: Upload file
39
+ curl https://api.x.ai/v1/files \
40
+ -H "Authorization: Bearer $XAI_API_KEY" \
41
+ -F file=@tesla-20241231.html
42
+
43
+ # Step 2: Add file to collection (use file_id from step 1)
44
+ curl -X POST https://management-api.x.ai/v1/collections/$COLLECTION_ID/documents/$FILE_ID \
45
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY"
46
+
47
+ ```
48
+
49
+ Uploading with metadata fields
50
+
51
+ ```bash
52
+ curl https://management-api.x.ai/v1/collections/collection_dbc087b1-6c99-493d-86c6-b401fee34a9d/documents \
53
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY" \
54
+ -F "name=paper.pdf" \
55
+ -F "data=@paper.pdf" \
56
+ -F "content_type=application/pdf" \
57
+ -F 'fields={"author": "Sandra Kim", "year": "2024", "title": "Q3 Revenue Analysis"}'
58
+
59
+ ```
60
+
61
+ Searching documents
62
+
63
+ ```bash
64
+ curl https://api.x.ai/v1/documents/search \
65
+ -H "Content-Type: application/json" \
66
+ -H "Authorization: Bearer $XAI_API_KEY" \
67
+ -d '{
68
+ "query": "What were the key revenue drivers based on the SEC filings?",
69
+ "source": {
70
+ "collection_ids": ["collection_dbc087b1-6c99-493d-86c6-b401fee34a9d"]
71
+ }
72
+ }'
73
+ ```
74
+
75
+ ```bash
76
+ curl https://api.x.ai/v1/documents/search \
77
+ -H "Content-Type: application/json" \
78
+ -H "Authorization: Bearer $XAI_API_KEY" \
79
+ -d '{
80
+ "query": "What were the key revenue drivers based on the SEC filings?",
81
+ "source": {
82
+ "collection_ids": [
83
+ "collection_dbc087b1-6c99-493d-86c6-b401fee34a9d"
84
+ ]
85
+ },
86
+ "retrieval_mode": {"type": "hybrid"}
87
+ }'
88
+ ```
89
+
90
+
91
+ Deleting a document:
92
+
93
+ ```bash
94
+ curl https://management-api.x.ai/v1/collections/collection_dbc087b1-6c99-493d-86c6-b401fee34a9d/documents/file_55a709d4-8edc-4f83-84d9-9f04fe49f832 \
95
+ -X DELETE \
96
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY"
97
+
98
+ ```
99
+
100
+ Deleting a collection
101
+
102
+ ```bash
103
+ curl https://management-api.x.ai/v1/collections/collection_dbc087b1-6c99-493d-86c6-b401fee34a9d \
104
+ -X DELETE \
105
+ -H "Authorization: Bearer $XAI_MANAGEMENT_API_KEY"
106
+ ```
@@ -1,58 +1,82 @@
1
- /*
2
- Websocket client for AI sessions
3
- */
4
- import { WebSocketClient } from "@j-o-r/apiserver";
1
+ import { WebSocketClient } from '@j-o-r/apiserver';
2
+ /**
3
+ * @module lib/AgentClient
4
+ * @exports default AgentClient
5
+ * Websocket client for AI agent sessions, wrapping a Prompt/ToolSet instance with robust queue-based message handling, auto-reconnect, and epoch-based resets.
6
+ */
5
7
 
6
8
  /**
7
- * @typedef {import('./API/openai.com/reponses/text.js').request} OARequest
8
- * @typedef {import('./API/x.ai/text.js').request} XRequest
9
- * @typedef {import('./API/anthropic.com/text.js').request} ANTHRequest
10
- *
11
- * @typedef {import('./API/x.ai/text.js').XOptions} XOptions
12
- * @typedef {import('./API/openai.com/reponses/text.js').OAOptions} OAOptions
13
- * @typedef {import('./API/anthropic.com/text.js').ANTHOptions} ANTHOptions
14
- *
15
- * @typedef {import('./Prompt.js').default} Prompt
16
- * @typedef {import('./ToolSet.js').default} ToolSet
17
- */
9
+ * @typedef {import('./API/openai.com/reponses/text.js').request} OARequest
10
+ * @typedef {import('./API/x.ai/text.js').request} XRequest
11
+ * @typedef {import('./API/anthropic.com/text.js').request} ANTHRequest
12
+ *
13
+ * @typedef {import('./API/x.ai/text.js').XOptions} XOptions
14
+ * @typedef {import('./API/openai.com/reponses/text.js').OAOptions} OAOptions
15
+ * @typedef {import('./API/anthropic.com/text.js').ANTHOptions} ANTHOptions
16
+ *
17
+ * @typedef {import('./Prompt.js').default} Prompt
18
+ * @typedef {import('./ToolSet.js').default} ToolSet
19
+ */
18
20
  /**
19
- * @typedef {Object} WSOptions
20
- * @property {Prompt} prompt - The prompt session
21
- * @property {ToolSet} [toolset] - The toolset
22
- * @property {string} description - Custom introduction message.
23
- * @property {string} name - Logical name: e.g. search, code, os, support.
24
- * @property {string} secret - Websocket server access secret.
25
- * @property {string} [url='ws://127.0.0.1:8000/ws?params=1234']
26
- * @property {number} [intervalMs=250] - How often to pull one message from the queue.
27
- */
21
+ * @typedef {Object} WSOptions
22
+ * @property {Prompt} prompt - The prompt session instance.
23
+ * @property {ToolSet} [toolset] - Optional toolset for the prompt.
24
+ * @property {string} description - Custom introduction message for the agent.
25
+ * @property {string} name - Logical name (e.g., 'search', 'code', 'os').
26
+ * @property {string} secret - Websocket server access secret (must match server).
27
+ * @property {string} [url='ws://127.0.0.1:8000/ws'] - WebSocket URL.
28
+ * @property {number} [intervalMs=2000] - Polling interval for queue processing (ms).
29
+ * @example { prompt, name: 'code-agent', secret: 'abc123', url: 'ws://localhost:8001/ws' }
30
+ */
31
+
28
32
  /**
29
- * Websocket Client
30
- * Wrapper combining a session and a websocket
31
- */
33
+ * AgentClient: WebSocket client wrapper for AI agent sessions.
34
+ * Combines Prompt/ToolSet with WS communication for bidirectional queries, responses, tool calls, and resets.
35
+ * Features: Sequential message queue (one-at-a-time processing), auto-reconnect on close, epoch invalidation for resets.
36
+ * Logs events via console; emits via underlying Prompt (tool_request, tool_error, etc.).
37
+ * @emits tool_request - When tools are requested (via #prompt).
38
+ * @emits tool_error - When tools fail (via #prompt).
39
+ * @example
40
+ * const client = new AgentClient({
41
+ * prompt: myPrompt,
42
+ * name: 'code',
43
+ * description: 'Code execution agent',
44
+ * secret: 'mysecret',
45
+ * intervalMs: 1000
46
+ * });
47
+ */
32
48
  class AgentClient {
33
- /** @type {Prompt} */
49
+ /** @private @type {Prompt} */
34
50
  #prompt;
51
+ /** @private @type {string} */
35
52
  #description = '';
53
+ /** @private @type {string} */
36
54
  #name;
55
+ /** @private @type {string} */
37
56
  #secret = '';
38
- /** @type {WebSocketClient} */
57
+ /** @private @type {import('@j-o-r/apiserver').WebSocketClient} */
39
58
  #ws;
59
+ /** @private @type {string} */
40
60
  #url = 'ws://127.0.0.1:8000/ws';
41
61
 
42
- // Message queue and processing flag to ensure one-at-a-time handling
43
- /** @type {Array<any>} */
62
+ /** @private @type {Array&lt;Object&gt;} Message queue for sequential processing. */
44
63
  #queue = [];
64
+ /** @private @type {boolean} Flag to prevent reentrant processing. */
45
65
  #processing = false;
46
- // Bump this to invalidate in-flight work (hard reset)
66
+ /** @private @type {number} Epoch counter; increments on reset to invalidate in-flight work. */
47
67
  #epoch = 0;
48
68
 
49
- // Interval-based scheduler (no tight while-loop)
69
+ /** @private @type {NodeJS.Timeout|null} Interval ID for scheduler. */
50
70
  #intervalId = null;
71
+ /** @private @type {number} Queue polling interval (ms). */
51
72
  #intervalMs = 2000;
52
73
 
53
74
  /**
54
- * Creates an instance of CLILoader.
55
- * @param {WSOptions} options - The options to configure the CLILoader.
75
+ * Initializes the AgentClient with configuration options.
76
+ * Registers Prompt events for logging and starts the WebSocket connection.
77
+ * @param {WSOptions} options - Configuration object.
78
+ * @throws {TypeError} If required options (e.g., prompt, secret) are invalid/missing.
79
+ * @example See WSOptions.
56
80
  */
57
81
  constructor(options) {
58
82
  if (options.url) this.#url = options.url;
@@ -64,11 +88,13 @@ class AgentClient {
64
88
  this.#registerPromptEvents();
65
89
  this._start();
66
90
  }
67
- /**
68
- * Informative.
69
- * See what is happening
70
- */
71
- #registerPromptEvents () {
91
+
92
+ /**
93
+ * @private
94
+ * Registers Prompt events for logging tool requests/errors.
95
+ * @returns {void}
96
+ */
97
+ #registerPromptEvents() {
72
98
  const events = Object.keys(this.#prompt.EVENTS);
73
99
  events.forEach((evt) => {
74
100
  this.#prompt.on(evt, (_msg) => {
@@ -82,72 +108,85 @@ class AgentClient {
82
108
  });
83
109
  });
84
110
  }
111
+
112
+ /**
113
+ * @private
114
+ * Starts the WebSocket connection, sends introduction, sets up handlers.
115
+ * Auto-retries on close after 5s.
116
+ * @returns {void}
117
+ */
85
118
  _start() {
86
119
  const url = this.#url + '?wssrc_id=' + this.#secret;
87
- this.#ws = new WebSocketClient(url); // Match server port/path
120
+ this.#ws = new WebSocketClient(url);
88
121
  this.#ws.onopen = () => {
89
- // Send my introduction to the server
90
122
  this.#ws.send(JSON.stringify({
91
123
  action: 'agent_introduction',
92
124
  name: this.#name,
93
125
  content: this.#description,
94
126
  id: new Date().getTime()
95
127
  }));
96
- console.log('introduction_send:>>')
128
+ console.log('introduction_send:>>');
97
129
  console.log(this.#name);
98
130
  console.log(this.#description);
99
131
  };
100
132
  this.#ws.onmessage = (m) => {
101
- // Enqueue messages; processing happens sequentially
102
133
  this.incoming(m);
103
134
  };
104
135
  this.#ws.onclose = () => {
105
136
  console.error('CLOSE: Lost Connection, retry in 5 seconds');
106
- // Stop ticking while disconnected
107
137
  this.#stopInterval();
108
138
  setTimeout(() => {
109
139
  this._start();
110
- }, 5000)
140
+ }, 5000);
111
141
  };
112
- this.#ws.onerror = (e) => {
113
-
114
- }
142
+ this.#ws.onerror = (e) => {};
115
143
  }
144
+
116
145
  /**
117
- * Enqueue an incoming websocket message and trigger the processor.
118
- */
146
+ * Enqueues an incoming WebSocket message and starts interval processing if needed.
147
+ * Immediately handles 'reset' actions (clears queue, resets prompt, increments epoch).
148
+ * @param {MessageEvent} e - Raw WebSocket message event.
149
+ * @returns {void}
150
+ * @throws {SyntaxError} If JSON parse fails.
151
+ */
119
152
  incoming(e) {
120
- const message = JSON.parse(e.data);
121
- // Handle resets immediately: do not enqueue, just clear the queue and reset the prompt
153
+ let message;
154
+ try {
155
+ message = JSON.parse(e.data);
156
+ } catch (err) {
157
+ console.error('Invalid JSON in incoming message');
158
+ return;
159
+ }
122
160
  if (message?.action === "reset") {
123
161
  console.log("<RESET:incoming>");
124
- // Invalidate in-flight work
125
162
  this.#epoch++;
126
- // Empty the queue immediately
127
163
  this.#queue = [];
128
- // Stop the scheduler; any in-flight handler will finish, but no further items will run
129
164
  this.#stopInterval();
130
- // Reset the prompt/session state
131
165
  this.#prompt.reset();
132
- return; // Do not enqueue this message
166
+ return;
133
167
  }
134
168
  this.#queue.push(message);
135
- // Start interval-based processing (no while-loop)
136
169
  this.#startInterval();
137
170
  }
138
171
 
139
172
  /**
140
- * Start the interval that pulls one message at each tick.
173
+ * @private
174
+ * Starts the interval ticker to process one queued message per tick.
175
+ * Idempotent; skips if already running.
176
+ * @returns {void}
141
177
  */
142
178
  #startInterval() {
143
179
  if (this.#intervalId) return;
144
180
  this.#intervalId = setInterval(() => {
145
- // We intentionally do not await here; #processOne guards reentrancy
146
181
  void this.#processOne();
147
182
  }, this.#intervalMs);
148
183
  }
149
184
 
150
- /** Stop the interval if it is running. */
185
+ /**
186
+ * @private
187
+ * Stops the processing interval.
188
+ * @returns {void}
189
+ */
151
190
  #stopInterval() {
152
191
  if (this.#intervalId) {
153
192
  clearInterval(this.#intervalId);
@@ -156,13 +195,15 @@ class AgentClient {
156
195
  }
157
196
 
158
197
  /**
159
- * Internal: process at most ONE message. Called on an interval tick.
198
+ * @private
199
+ * Processes exactly one queued message (guards reentrancy).
200
+ * Stops interval if queue empty post-process.
201
+ * @returns {Promise&lt;void&gt;}
160
202
  */
161
203
  async #processOne() {
162
204
  if (this.#processing) return;
163
205
  const message = this.#queue.shift();
164
206
  if (!message) {
165
- // Nothing to do; stop ticking until a new message arrives
166
207
  this.#stopInterval();
167
208
  return;
168
209
  }
@@ -172,13 +213,17 @@ class AgentClient {
172
213
  await this.#handleMessage(message, epochAtStart);
173
214
  } finally {
174
215
  this.#processing = false;
175
- // If the queue drained, we can stop; else the next tick will pick the next one
176
216
  if (this.#queue.length === 0) this.#stopInterval();
177
217
  }
178
218
  }
179
219
 
180
220
  /**
181
- * Internal: handle a single message
221
+ * @private
222
+ * Handles a single enqueued message (query/response/reset).
223
+ * Sends responses/errors back via WS.
224
+ * @param {Object} message - Parsed message.
225
+ * @param {number} epochAtStart - Epoch at start; aborts if changed.
226
+ * @returns {Promise&lt;void&gt;}
182
227
  */
183
228
  async #handleMessage(message, epochAtStart) {
184
229
  if (message.action === 'agent_query') {
@@ -193,7 +238,6 @@ class AgentClient {
193
238
  }
194
239
  } else if (message.action === 'reset') {
195
240
  console.log('<RESET>');
196
- // Empty the que;
197
241
  this.#queue = [];
198
242
  this.#epoch++;
199
243
  this.#prompt.reset();