@j-o-r/hello-dave 0.0.7 → 0.0.9
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/CHANGELOG.md +6 -0
- package/TODO.md +24 -0
- package/agents/code_agent.js +9 -9
- package/bin/dave.js +2 -2
- package/docs/agent-dave-websocket-protocol.md +180 -0
- package/docs/agent-manager.md +244 -0
- package/docs/bin-dave.md +62 -0
- package/docs/codeserver-pattern.md +191 -0
- package/docs/generic-toolset.md +326 -0
- package/docs/howtos/agent-networking.md +253 -0
- package/docs/howtos/spawn-agents.md.bak +200 -0
- package/docs/howtos/spawn-agents.md.bak_new +200 -0
- package/docs/jsdoc-best-practices.md +278 -0
- package/docs/multi-agent-clusters.md +265 -0
- package/docs/multi-agent-clusters.md.bak +229 -0
- package/docs/path-resolution-best-practices.md +104 -0
- package/docs/project-overview.md +67 -0
- package/docs/prompt/spawn_agent.md +173 -0
- package/docs/prompt/spawn_agent.md.bak +201 -0
- package/docs/prompt-class.md +141 -0
- package/docs/suggestions.md +38 -0
- package/docs/todo-archive-v0.0.8.md +1 -0
- package/docs/todo-archive.md +44 -0
- package/docs/tools-syntax-validation.md +121 -0
- package/docs/toolset.md +164 -0
- package/docs/xai-responses.md +111 -0
- package/docs/xai_collections.md +106 -0
- package/lib/genericToolset.js +130 -31
- package/package.json +1 -1
- package/README.md.bak +0 -218
package/docs/toolset.md
ADDED
|
@@ -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
|
+
```
|
package/lib/genericToolset.js
CHANGED
|
@@ -44,8 +44,82 @@ const getJSError = (errorStr) => {
|
|
|
44
44
|
}
|
|
45
45
|
return result;
|
|
46
46
|
};
|
|
47
|
+
// /**
|
|
48
|
+
// * Try to prevent double escaping by LLMS models
|
|
49
|
+
// * When a string stays a string it is probably double escaped
|
|
50
|
+
// * @parameter {string} s
|
|
51
|
+
// * @returns {string}
|
|
52
|
+
// */
|
|
53
|
+
// const guessOverEscaping = (s) => {
|
|
54
|
+
// let test;
|
|
55
|
+
// if (typeof s === 'string') {
|
|
56
|
+
// try {
|
|
57
|
+
// test = JSON.parse(s);
|
|
58
|
+
// } catch (_e) { }
|
|
59
|
+
// if (typeof test === 'string') {
|
|
60
|
+
// return test;
|
|
61
|
+
// }
|
|
62
|
+
// }
|
|
63
|
+
// return s;
|
|
64
|
+
// }
|
|
47
65
|
|
|
48
66
|
/**
|
|
67
|
+
* Try to prevent double escaping by LLMs.
|
|
68
|
+
* Handles common patterns like \"path\", \\\"path\\\", JSON strings,
|
|
69
|
+
* and multiple layers while being much safer on complex bash/JS scripts.
|
|
70
|
+
*/
|
|
71
|
+
const guessOverEscaping = (s) => {
|
|
72
|
+
if (typeof s !== 'string') return s;
|
|
73
|
+
|
|
74
|
+
let current = s.trim();
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
let iterations = 0;
|
|
77
|
+
const MAX_ITER = 6; // safety limit
|
|
78
|
+
|
|
79
|
+
while (iterations < MAX_ITER && !seen.has(current)) {
|
|
80
|
+
seen.add(current);
|
|
81
|
+
iterations++;
|
|
82
|
+
|
|
83
|
+
// 1. Most reliable: JSON.parse (undoes proper JSON string escaping)
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(current);
|
|
86
|
+
if (typeof parsed === 'string') {
|
|
87
|
+
current = parsed;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
} catch (e) { }
|
|
91
|
+
|
|
92
|
+
// 2. Specifically strip the pattern you saw: \"...\" (including the backslashes)
|
|
93
|
+
if (current.startsWith('\\"') && current.endsWith('\\"')) {
|
|
94
|
+
current = current.slice(2, -2);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. Strip normal outer quotes, but protect JSON objects/arrays
|
|
99
|
+
if (current.startsWith('"') && current.endsWith('"') && current.length > 2) {
|
|
100
|
+
const inner = current.slice(1, -1);
|
|
101
|
+
const trimmedInner = inner.trim();
|
|
102
|
+
if (!trimmedInner.startsWith('{') && !trimmedInner.startsWith('[')) {
|
|
103
|
+
current = inner;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 4. Conservative global unescape of \" → "
|
|
109
|
+
// Only applied to short strings without newlines (i.e. not full scripts)
|
|
110
|
+
if (!current.includes('\n') && (current.match(/\\"/g) || []).length >= 1) {
|
|
111
|
+
const lessEscaped = current.replace(/\\"/g, '"');
|
|
112
|
+
if (lessEscaped !== current) {
|
|
113
|
+
current = lessEscaped;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
break; // no more productive changes
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return current;
|
|
122
|
+
};/**
|
|
49
123
|
* @module lib/genericToolset
|
|
50
124
|
* Secure utility tools for code execution, file I/O, system ops. Pre-populated ToolSet.
|
|
51
125
|
*
|
|
@@ -71,9 +145,10 @@ tools.add(
|
|
|
71
145
|
async (params) => {
|
|
72
146
|
let response = '';
|
|
73
147
|
try {
|
|
148
|
+
const script = guessOverEscaping(params.script);
|
|
74
149
|
const delim = `JS_STDIN_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
75
150
|
response = await SH`cat <<'${delim}' | node --input-type=module -
|
|
76
|
-
${
|
|
151
|
+
${script}
|
|
77
152
|
${delim}
|
|
78
153
|
`.run();
|
|
79
154
|
} catch (e) {
|
|
@@ -92,23 +167,34 @@ tools.add(
|
|
|
92
167
|
|
|
93
168
|
tools.add(
|
|
94
169
|
'execute_bash_script',
|
|
95
|
-
'Execute raw bash script or command (no escaping needed).',
|
|
170
|
+
'Execute raw bash script or command (no escaping needed). Supports timeout to prevent hangs.',
|
|
96
171
|
{
|
|
97
172
|
type: 'object',
|
|
98
173
|
properties: {
|
|
99
174
|
bash_script: {
|
|
100
175
|
type: 'string',
|
|
101
|
-
description: `Raw bash verbatim. Supports all syntax safely via
|
|
176
|
+
description: `Raw bash verbatim. Supports all syntax safely via stdin. System: ${user.system}`
|
|
177
|
+
},
|
|
178
|
+
timeout: {
|
|
179
|
+
type: 'number',
|
|
180
|
+
default: 360,
|
|
181
|
+
description: 'Max execution time in seconds (default 360 seconds, 0 is no timeout). Uses @j-o-r/sh timeout (ms) with SIGTERM on expiry to prevent hangs from interactive prompts or slow commands.'
|
|
102
182
|
}
|
|
103
183
|
},
|
|
104
184
|
required: ['bash_script']
|
|
105
185
|
},
|
|
106
186
|
async (params) => {
|
|
187
|
+
const timeoutSec = Number(params.timeout ?? 300);
|
|
188
|
+
const bash_script = guessOverEscaping(params.bash_script);
|
|
189
|
+
if (isNaN(timeoutSec) || timeoutSec < 0) throw new Error('Invalid timeout');
|
|
190
|
+
const timeout = timeoutSec * 1000;
|
|
191
|
+
// return await SH`bash`.options({ timeout: timeoutMs }).run(params.bash_script);
|
|
107
192
|
const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
108
193
|
return await SH`bash <<'${delim}'
|
|
109
|
-
${
|
|
194
|
+
${bash_script}
|
|
110
195
|
${delim}
|
|
111
|
-
`.run();
|
|
196
|
+
`.options({ timeout }).run();
|
|
197
|
+
|
|
112
198
|
}
|
|
113
199
|
);
|
|
114
200
|
|
|
@@ -125,12 +211,15 @@ tools.add(
|
|
|
125
211
|
required: ['to', 'subject', 'body']
|
|
126
212
|
},
|
|
127
213
|
async (params) => {
|
|
214
|
+
const to = guessOverEscaping(params.to);
|
|
215
|
+
const subject = guessOverEscaping(params.subject);
|
|
216
|
+
const body = guessOverEscaping(params.body);
|
|
128
217
|
const delim = `END_EMAIL_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
129
218
|
return await SH`msmtp ${params.to} <<'${delim}'
|
|
130
|
-
To: ${
|
|
131
|
-
Subject: ${
|
|
219
|
+
To: ${to}
|
|
220
|
+
Subject: ${subject}
|
|
132
221
|
|
|
133
|
-
${
|
|
222
|
+
${body}
|
|
134
223
|
${delim}
|
|
135
224
|
`.run();
|
|
136
225
|
}
|
|
@@ -146,22 +235,36 @@ tools.add(
|
|
|
146
235
|
},
|
|
147
236
|
required: ['url']
|
|
148
237
|
},
|
|
149
|
-
async (params) =>
|
|
238
|
+
async (params) => {
|
|
239
|
+
let url = guessOverEscaping(params.url?.trim());
|
|
240
|
+
return await SH`xdg-open ${[url]}`.run();
|
|
241
|
+
}
|
|
150
242
|
);
|
|
151
243
|
|
|
152
244
|
tools.add(
|
|
153
245
|
'execute_remote_script',
|
|
154
|
-
'Run bash on remote via SSH.',
|
|
246
|
+
'Run bash on remote via SSH. Supports timeout to prevent hangs.',
|
|
155
247
|
{
|
|
156
248
|
type: 'object',
|
|
157
249
|
properties: {
|
|
158
250
|
url: { type: 'string', description: 'ssh://user@host[:port]' },
|
|
159
|
-
script: { type: 'string', description: 'Raw script' }
|
|
251
|
+
script: { type: 'string', description: 'Raw script' },
|
|
252
|
+
timeout: {
|
|
253
|
+
type: 'number',
|
|
254
|
+
default: 30,
|
|
255
|
+
description: 'Max execution time in seconds (default 30). Uses @j-o-r/sh timeout (ms) with SIGTERM on SSH process expiry.'
|
|
256
|
+
}
|
|
160
257
|
},
|
|
161
258
|
required: ['url', 'script']
|
|
162
259
|
},
|
|
163
260
|
async (params) => {
|
|
164
|
-
|
|
261
|
+
let { url, script } = params;
|
|
262
|
+
url = guessOverEscaping(url);
|
|
263
|
+
script = guessOverEscaping(script);
|
|
264
|
+
|
|
265
|
+
const timeoutSec = Number(params.timeout ?? 30);
|
|
266
|
+
if (isNaN(timeoutSec) || timeoutSec <= 0) throw new Error('Invalid timeout');
|
|
267
|
+
const timeoutMs = timeoutSec * 1000;
|
|
165
268
|
if (!url.startsWith('ssh://')) throw new Error('ssh://user@host[:port]');
|
|
166
269
|
const withoutProto = url.slice(6);
|
|
167
270
|
const parts = withoutProto.split(':');
|
|
@@ -169,12 +272,7 @@ tools.add(
|
|
|
169
272
|
if (parts.length > 1) { userHost = parts[0]; port = parseInt(parts[1]); }
|
|
170
273
|
const [user, host] = userHost.split('@');
|
|
171
274
|
if (!user || !host) throw new Error('Invalid SSH URL');
|
|
172
|
-
|
|
173
|
-
const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
174
|
-
return await SH`ssh -p ${port} ${user}@${host} bash <<'${delim}'
|
|
175
|
-
${script}
|
|
176
|
-
${delim}
|
|
177
|
-
`.run();
|
|
275
|
+
return await SH`ssh -p ${port} ${user}@${host} bash`.options({ timeout: timeoutMs }).run(script);
|
|
178
276
|
}
|
|
179
277
|
);
|
|
180
278
|
|
|
@@ -190,7 +288,8 @@ tools.add(
|
|
|
190
288
|
},
|
|
191
289
|
async (params) => {
|
|
192
290
|
if (typeof params.query === 'string' && params.query.trim()) {
|
|
193
|
-
|
|
291
|
+
const q = guessOverEscaping(params.query);
|
|
292
|
+
return await SH`${searchSessionsSh} "${bashEscape(q)}"`.run();
|
|
194
293
|
}
|
|
195
294
|
return await SH`${listSessionsSh}`.run();
|
|
196
295
|
}
|
|
@@ -207,7 +306,7 @@ tools.add(
|
|
|
207
306
|
required: ['file']
|
|
208
307
|
},
|
|
209
308
|
async (params) => {
|
|
210
|
-
|
|
309
|
+
let file = guessOverEscaping(params.file?.trim());
|
|
211
310
|
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
|
|
212
311
|
throw new Error('Relative CWD path only (no /, .., \\\\).');
|
|
213
312
|
}
|
|
@@ -229,8 +328,8 @@ tools.add(
|
|
|
229
328
|
required: ['file', 'content']
|
|
230
329
|
},
|
|
231
330
|
async (params) => {
|
|
232
|
-
|
|
233
|
-
|
|
331
|
+
let file = guessOverEscaping(params.file?.trim());
|
|
332
|
+
let content = guessOverEscaping(params.content ?? '');
|
|
234
333
|
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
|
|
235
334
|
throw new Error('Relative CWD path only.');
|
|
236
335
|
}
|
|
@@ -240,29 +339,29 @@ tools.add(
|
|
|
240
339
|
const ext = path.extname(file);
|
|
241
340
|
let finalContent = content, validationMsg = '';
|
|
242
341
|
|
|
243
|
-
const temp1 = path.join(dir, `temp_${Date.now()}_${Math.random().toString(36).slice(2,6)}${ext || '.tmp'}`);
|
|
342
|
+
const temp1 = path.join(dir, `temp_${Date.now()}_${Math.random().toString(36).slice(2, 6)}${ext || '.tmp'}`);
|
|
244
343
|
await fs.writeFile(temp1, content, 'utf8');
|
|
245
344
|
|
|
246
345
|
try {
|
|
247
346
|
await SH`${syntaxCheckSh} ${[temp1]}`.run();
|
|
248
347
|
validationMsg = ' ✓ Syntax OK';
|
|
249
348
|
} catch (e) {
|
|
250
|
-
if (!['.js','.mjs'].includes(ext)) {
|
|
251
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
349
|
+
if (!['.js', '.mjs'].includes(ext)) {
|
|
350
|
+
await fs.unlink(temp1).catch(() => { });
|
|
252
351
|
throw new Error(`Syntax error: ${e.message}`);
|
|
253
352
|
}
|
|
254
353
|
let fixed = content.replace(/\\\\`/g, '\\`').replace(/\\\`/g, '`').replace(/\\`/g, '`').replace(/\\\$/g, '$').replace(/\\\${/g, '${');
|
|
255
|
-
const temp2 = path.join(dir, `temp_fix_${Date.now()}_${Math.random().toString(36).slice(2,6)}.js`);
|
|
354
|
+
const temp2 = path.join(dir, `temp_fix_${Date.now()}_${Math.random().toString(36).slice(2, 6)}.js`);
|
|
256
355
|
await fs.writeFile(temp2, fixed, 'utf8');
|
|
257
356
|
try {
|
|
258
357
|
await SH`${syntaxCheckSh} ${[temp2]}`.run();
|
|
259
358
|
finalContent = fixed;
|
|
260
359
|
await fs.rename(temp2, resolved);
|
|
261
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
360
|
+
await fs.unlink(temp1).catch(() => { });
|
|
262
361
|
validationMsg = ' ✓ Fixed JS';
|
|
263
362
|
} catch {
|
|
264
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
265
|
-
await fs.unlink(temp2).catch(()=>{});
|
|
363
|
+
await fs.unlink(temp1).catch(() => { });
|
|
364
|
+
await fs.unlink(temp2).catch(() => { });
|
|
266
365
|
throw new Error(`Syntax error (fix failed): ${e.message}`);
|
|
267
366
|
}
|
|
268
367
|
}
|
|
@@ -289,7 +388,7 @@ tools.add(
|
|
|
289
388
|
required: ['file']
|
|
290
389
|
},
|
|
291
390
|
async (params) => {
|
|
292
|
-
const file = params.file?.trim();
|
|
391
|
+
const file = guessOverEscaping(params.file?.trim());
|
|
293
392
|
if (typeof file !== 'string' || !file) throw new Error('Relative path required.');
|
|
294
393
|
const resolved = path.resolve(process.cwd(), file);
|
|
295
394
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
|
@@ -297,4 +396,4 @@ tools.add(
|
|
|
297
396
|
}
|
|
298
397
|
);
|
|
299
398
|
|
|
300
|
-
export default tools;
|
|
399
|
+
export default tools;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j-o-r/hello-dave",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.9",
|
|
5
5
|
"description": "ESM toolkit for building AI agents with unified access to Grok (XAI), OpenAI, and Anthropic endpoints",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./types/index.d.ts",
|