@fw_dxs/openclaw-puter-ai 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +383 -13
- package/agent.js +182 -11
- package/index.js +111 -37
- package/memory/conversation.js +15 -10
- package/openclaw.plugin.json +9 -12
- package/package.json +3 -3
- package/plugin.json +23 -9
- package/puterClient.js +245 -6
- package/tools/codeGen.js +3 -4
- package/tools/webSearch.js +0 -3
package/README.md
CHANGED
|
@@ -1,18 +1,388 @@
|
|
|
1
|
-
# OpenClaw Puter AI Plugin
|
|
1
|
+
# \# OpenClaw Puter AI Plugin (Actually Working)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
#
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# !\[npm](https://img.shields.io/npm/v/@fw\_dxs/openclaw-puter-ai)
|
|
6
6
|
|
|
7
|
-
/ai
|
|
8
|
-
/ai summarize
|
|
9
|
-
/ai image
|
|
10
|
-
/ai code
|
|
11
|
-
/ai analyze
|
|
12
|
-
/ai search
|
|
7
|
+
# !\[license](https://img.shields.io/npm/l/@fw\_dxs/openclaw-puter-ai)
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
# An advanced AI assistant plugin for \*\*OpenClaw\*\*, powered by \*\*Puter AI\*\* with dynamic model routing, autonomous tools, and image generation.
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
# This plugin adds a powerful AI agent to OpenClaw capable of chat, coding assistance, web search, and image generation.
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
# ---
|
|
20
|
+
|
|
21
|
+
#
|
|
22
|
+
|
|
23
|
+
# \# Features
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
# \- GPT-5.3 chat support
|
|
28
|
+
|
|
29
|
+
# \- GPT-5.3 Codex routing for coding tasks
|
|
30
|
+
|
|
31
|
+
# \- Autonomous AI agent with tool calling
|
|
32
|
+
|
|
33
|
+
# \- Web search integration
|
|
34
|
+
|
|
35
|
+
# \- Code generation assistance
|
|
36
|
+
|
|
37
|
+
# \- AI image generation
|
|
38
|
+
|
|
39
|
+
# \- Conversation memory
|
|
40
|
+
|
|
41
|
+
# \- Streaming responses
|
|
42
|
+
|
|
43
|
+
# \- Automatic Puter authentication
|
|
44
|
+
|
|
45
|
+
# \- Token caching (no repeated login)
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
|
|
49
|
+
# ---
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
|
|
53
|
+
# \# Commands
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
|
|
57
|
+
# \## Chat
|
|
58
|
+
|
|
59
|
+
#
|
|
60
|
+
|
|
61
|
+
# ```
|
|
62
|
+
|
|
63
|
+
# /ai <prompt>
|
|
64
|
+
|
|
65
|
+
# ```
|
|
66
|
+
|
|
67
|
+
#
|
|
68
|
+
|
|
69
|
+
# Example:
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
|
|
73
|
+
# ```
|
|
74
|
+
|
|
75
|
+
# /ai explain how transformers work
|
|
76
|
+
|
|
77
|
+
# ```
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
|
|
81
|
+
# ---
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
|
|
85
|
+
# \## Memory Chat
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
|
|
89
|
+
# Stores conversation history for the user.
|
|
90
|
+
|
|
91
|
+
#
|
|
92
|
+
|
|
93
|
+
# ```
|
|
94
|
+
|
|
95
|
+
# /ai chat <prompt>
|
|
96
|
+
|
|
97
|
+
# ```
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
|
|
101
|
+
# Example:
|
|
102
|
+
|
|
103
|
+
#
|
|
104
|
+
|
|
105
|
+
# ```
|
|
106
|
+
|
|
107
|
+
# /ai chat remember my project name is Atlas
|
|
108
|
+
|
|
109
|
+
# ```
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
|
|
113
|
+
# ---
|
|
114
|
+
|
|
115
|
+
#
|
|
116
|
+
|
|
117
|
+
# \## Code Generation
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
|
|
121
|
+
# Automatically routes to \*\*GPT-5.3 Codex\*\* for coding tasks.
|
|
122
|
+
|
|
123
|
+
#
|
|
124
|
+
|
|
125
|
+
# ```
|
|
126
|
+
|
|
127
|
+
# /ai code build a nodejs api
|
|
128
|
+
|
|
129
|
+
# ```
|
|
130
|
+
|
|
131
|
+
#
|
|
132
|
+
|
|
133
|
+
# ---
|
|
134
|
+
|
|
135
|
+
#
|
|
136
|
+
|
|
137
|
+
# \## Web Search
|
|
138
|
+
|
|
139
|
+
#
|
|
140
|
+
|
|
141
|
+
# ```
|
|
142
|
+
|
|
143
|
+
# /ai search latest AI models
|
|
144
|
+
|
|
145
|
+
# ```
|
|
146
|
+
|
|
147
|
+
#
|
|
148
|
+
|
|
149
|
+
# ---
|
|
150
|
+
|
|
151
|
+
#
|
|
152
|
+
|
|
153
|
+
# \## Image Generation
|
|
154
|
+
|
|
155
|
+
#
|
|
156
|
+
|
|
157
|
+
# ```
|
|
158
|
+
|
|
159
|
+
# /ai image futuristic city at sunset
|
|
160
|
+
|
|
161
|
+
# ```
|
|
162
|
+
|
|
163
|
+
#
|
|
164
|
+
|
|
165
|
+
# Images are generated using Puter and saved locally.
|
|
166
|
+
|
|
167
|
+
#
|
|
168
|
+
|
|
169
|
+
# ---
|
|
170
|
+
|
|
171
|
+
#
|
|
172
|
+
|
|
173
|
+
# \## Autonomous Agent
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
|
|
177
|
+
# The agent can automatically decide which tools to use.
|
|
178
|
+
|
|
179
|
+
#
|
|
180
|
+
|
|
181
|
+
# ```
|
|
182
|
+
|
|
183
|
+
# /ai research how to build a discord bot
|
|
184
|
+
|
|
185
|
+
# ```
|
|
186
|
+
|
|
187
|
+
#
|
|
188
|
+
|
|
189
|
+
# The AI may automatically:
|
|
190
|
+
|
|
191
|
+
#
|
|
192
|
+
|
|
193
|
+
# \- search the web
|
|
194
|
+
|
|
195
|
+
# \- generate code
|
|
196
|
+
|
|
197
|
+
# \- create images
|
|
198
|
+
|
|
199
|
+
# \- answer directly
|
|
200
|
+
|
|
201
|
+
#
|
|
202
|
+
|
|
203
|
+
# ---
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
|
|
207
|
+
# \# Installation
|
|
208
|
+
|
|
209
|
+
#
|
|
210
|
+
|
|
211
|
+
# Install the plugin via npm:
|
|
212
|
+
|
|
213
|
+
#
|
|
214
|
+
|
|
215
|
+
# ```
|
|
216
|
+
|
|
217
|
+
# npm install @fw\_dxs/openclaw-puter-ai
|
|
218
|
+
|
|
219
|
+
# ```
|
|
220
|
+
|
|
221
|
+
#
|
|
222
|
+
|
|
223
|
+
# Then enable it in your OpenClaw configuration.
|
|
224
|
+
|
|
225
|
+
#
|
|
226
|
+
|
|
227
|
+
# ---
|
|
228
|
+
|
|
229
|
+
#
|
|
230
|
+
|
|
231
|
+
# \# Requirements
|
|
232
|
+
|
|
233
|
+
#
|
|
234
|
+
|
|
235
|
+
# \- Node.js 18+
|
|
236
|
+
|
|
237
|
+
# \- OpenClaw
|
|
238
|
+
|
|
239
|
+
# \- Puter account (for AI access)
|
|
240
|
+
|
|
241
|
+
#
|
|
242
|
+
|
|
243
|
+
# The plugin will automatically prompt for Puter authentication on first use.
|
|
244
|
+
|
|
245
|
+
#
|
|
246
|
+
|
|
247
|
+
# ---
|
|
248
|
+
|
|
249
|
+
#
|
|
250
|
+
|
|
251
|
+
# \# Model Routing
|
|
252
|
+
|
|
253
|
+
#
|
|
254
|
+
|
|
255
|
+
# The plugin automatically selects models depending on the task.
|
|
256
|
+
|
|
257
|
+
#
|
|
258
|
+
|
|
259
|
+
# | Task | Model |
|
|
260
|
+
|
|
261
|
+
# |-----|------|
|
|
262
|
+
|
|
263
|
+
# General chat | GPT-5.3 Chat |
|
|
264
|
+
|
|
265
|
+
# Coding tasks | GPT-5.3 Codex |
|
|
266
|
+
|
|
267
|
+
# Image generation | GPT Image |
|
|
268
|
+
|
|
269
|
+
#
|
|
270
|
+
|
|
271
|
+
# ---
|
|
272
|
+
|
|
273
|
+
#
|
|
274
|
+
|
|
275
|
+
# \# Project Structure
|
|
276
|
+
|
|
277
|
+
#
|
|
278
|
+
|
|
279
|
+
# ```
|
|
280
|
+
|
|
281
|
+
# openclaw-puter-ai
|
|
282
|
+
|
|
283
|
+
# │
|
|
284
|
+
|
|
285
|
+
# ├── index.js
|
|
286
|
+
|
|
287
|
+
# ├── agent.js
|
|
288
|
+
|
|
289
|
+
# ├── puterClient.js
|
|
290
|
+
|
|
291
|
+
# ├── openclaw.plugin.json
|
|
292
|
+
|
|
293
|
+
# ├── README.md
|
|
294
|
+
|
|
295
|
+
# │
|
|
296
|
+
|
|
297
|
+
# ├── tools
|
|
298
|
+
|
|
299
|
+
# │ ├── webSearch.js
|
|
300
|
+
|
|
301
|
+
# │ └── codeGen.js
|
|
302
|
+
|
|
303
|
+
# │
|
|
304
|
+
|
|
305
|
+
# ├── memory
|
|
306
|
+
|
|
307
|
+
# │ └── conversation.js
|
|
308
|
+
|
|
309
|
+
# │
|
|
310
|
+
|
|
311
|
+
# └── generated
|
|
312
|
+
|
|
313
|
+
# ```
|
|
314
|
+
|
|
315
|
+
#
|
|
316
|
+
|
|
317
|
+
# ---
|
|
318
|
+
|
|
319
|
+
#
|
|
320
|
+
|
|
321
|
+
# \# Security Notes
|
|
322
|
+
|
|
323
|
+
#
|
|
324
|
+
|
|
325
|
+
# Do \*\*not publish or share\*\* these files:
|
|
326
|
+
|
|
327
|
+
#
|
|
328
|
+
|
|
329
|
+
# ```
|
|
330
|
+
|
|
331
|
+
# .puter-token.json
|
|
332
|
+
|
|
333
|
+
# memory.json
|
|
334
|
+
|
|
335
|
+
# generated/
|
|
336
|
+
|
|
337
|
+
# ```
|
|
338
|
+
|
|
339
|
+
#
|
|
340
|
+
|
|
341
|
+
# These files contain local runtime data.
|
|
342
|
+
|
|
343
|
+
#
|
|
344
|
+
|
|
345
|
+
# ---
|
|
346
|
+
|
|
347
|
+
#
|
|
348
|
+
|
|
349
|
+
# \# License
|
|
350
|
+
|
|
351
|
+
#
|
|
352
|
+
|
|
353
|
+
# MIT License
|
|
354
|
+
|
|
355
|
+
#
|
|
356
|
+
|
|
357
|
+
# ---
|
|
358
|
+
|
|
359
|
+
#
|
|
360
|
+
|
|
361
|
+
# \# Author
|
|
362
|
+
|
|
363
|
+
#
|
|
364
|
+
|
|
365
|
+
# fw\_dxs
|
|
366
|
+
|
|
367
|
+
#
|
|
368
|
+
|
|
369
|
+
# ---
|
|
370
|
+
|
|
371
|
+
#
|
|
372
|
+
|
|
373
|
+
# \# Future Improvements
|
|
374
|
+
|
|
375
|
+
#
|
|
376
|
+
|
|
377
|
+
# Planned features include:
|
|
378
|
+
|
|
379
|
+
#
|
|
380
|
+
|
|
381
|
+
# \- filesystem tools for autonomous development agents
|
|
382
|
+
|
|
383
|
+
# \- code execution tools
|
|
384
|
+
|
|
385
|
+
# \- multi-image generation
|
|
386
|
+
|
|
387
|
+
# \- improved OpenClaw UI integration
|
|
15
388
|
|
|
16
|
-
1. Place folder in /plugins
|
|
17
|
-
2. Run npm install
|
|
18
|
-
3. Restart OpenClaw
|
package/agent.js
CHANGED
|
@@ -1,23 +1,194 @@
|
|
|
1
|
+
const ai = require("./puterClient");
|
|
1
2
|
const webSearch = require("./tools/webSearch");
|
|
2
3
|
const codeGen = require("./tools/codeGen");
|
|
3
4
|
|
|
5
|
+
function getToolSpecs() {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "web_search",
|
|
11
|
+
description: "Search the internet for information.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
query: {
|
|
16
|
+
type: "string"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
required: ["query"]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "function",
|
|
25
|
+
function: {
|
|
26
|
+
name: "generate_code",
|
|
27
|
+
description: "Generate or fix code.",
|
|
28
|
+
parameters: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
prompt: {
|
|
32
|
+
type: "string"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ["prompt"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: "function",
|
|
41
|
+
function: {
|
|
42
|
+
name: "generate_image",
|
|
43
|
+
description: "Generate an image from a prompt.",
|
|
44
|
+
parameters: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
prompt: {
|
|
48
|
+
type: "string"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
required: ["prompt"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getToolCalls(resp) {
|
|
59
|
+
return (
|
|
60
|
+
resp?.choices?.[0]?.message?.tool_calls ||
|
|
61
|
+
resp?.message?.tool_calls ||
|
|
62
|
+
resp?.tool_calls ||
|
|
63
|
+
[]
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getAssistantMessage(resp, toolCalls) {
|
|
68
|
+
return (
|
|
69
|
+
resp?.choices?.[0]?.message ||
|
|
70
|
+
resp?.message || {
|
|
71
|
+
role: "assistant",
|
|
72
|
+
content: null,
|
|
73
|
+
tool_calls: toolCalls
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getText(resp) {
|
|
79
|
+
if (!resp) return "";
|
|
80
|
+
|
|
81
|
+
const content =
|
|
82
|
+
resp?.choices?.[0]?.message?.content ||
|
|
83
|
+
resp?.message?.content ||
|
|
84
|
+
resp?.text ||
|
|
85
|
+
resp;
|
|
86
|
+
|
|
87
|
+
if (typeof content === "string") return content;
|
|
88
|
+
if (content == null) return "";
|
|
89
|
+
|
|
90
|
+
return JSON.stringify(content, null, 2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function runToolCall(toolCall) {
|
|
94
|
+
const fn = toolCall?.function?.name;
|
|
95
|
+
let args = {};
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
args = JSON.parse(toolCall?.function?.arguments || "{}");
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error("[Agent] Failed parsing tool args:", err);
|
|
101
|
+
args = {};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log("[Agent] Tool requested:", fn, args);
|
|
105
|
+
|
|
106
|
+
if (fn === "web_search") {
|
|
107
|
+
return String(await webSearch(args.query || ""));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (fn === "generate_code") {
|
|
111
|
+
return String(await codeGen(args.prompt || ""));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (fn === "generate_image") {
|
|
115
|
+
return String(await ai.image(args.prompt || ""));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return "Unknown tool";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isImagePrompt(prompt) {
|
|
122
|
+
const text = String(prompt || "").toLowerCase();
|
|
123
|
+
return (
|
|
124
|
+
text.includes("make an image") ||
|
|
125
|
+
text.includes("generate an image") ||
|
|
126
|
+
text.includes("create an image") ||
|
|
127
|
+
text.includes("draw ") ||
|
|
128
|
+
text.includes("illustration") ||
|
|
129
|
+
text.includes("picture of") ||
|
|
130
|
+
text.includes("image of")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
4
134
|
async function runAgent(prompt) {
|
|
135
|
+
console.log("[Agent] Starting run");
|
|
136
|
+
console.log("[Agent] Prompt:", prompt);
|
|
137
|
+
|
|
138
|
+
// Hard shortcut for image requests so they do not go back into chat at all.
|
|
139
|
+
if (isImagePrompt(prompt)) {
|
|
140
|
+
console.log("[Agent] Direct image shortcut triggered");
|
|
141
|
+
return await ai.image(prompt);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const messages = [
|
|
145
|
+
{
|
|
146
|
+
role: "system",
|
|
147
|
+
content:
|
|
148
|
+
"You are an autonomous AI assistant. Use web_search for current information, generate_code for coding tasks, and generate_image for image requests. If no tool is needed, answer directly."
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
role: "user",
|
|
152
|
+
content: prompt
|
|
153
|
+
}
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
const firstResponse = await ai.chatWithTools(messages, getToolSpecs());
|
|
157
|
+
const toolCalls = getToolCalls(firstResponse);
|
|
158
|
+
|
|
159
|
+
console.log("[Agent] Tool calls found:", toolCalls.length);
|
|
160
|
+
|
|
161
|
+
if (!toolCalls.length) {
|
|
162
|
+
return getText(firstResponse) || "No response.";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If the tool call is image generation, return it directly.
|
|
166
|
+
if (
|
|
167
|
+
toolCalls.length === 1 &&
|
|
168
|
+
toolCalls[0]?.function?.name === "generate_image"
|
|
169
|
+
) {
|
|
170
|
+
return await runToolCall(toolCalls[0]);
|
|
171
|
+
}
|
|
5
172
|
|
|
6
|
-
const
|
|
7
|
-
|
|
173
|
+
const assistantMessage = getAssistantMessage(firstResponse, toolCalls);
|
|
174
|
+
const toolMessages = [];
|
|
8
175
|
|
|
9
|
-
|
|
10
|
-
|
|
176
|
+
for (const call of toolCalls) {
|
|
177
|
+
const result = await runToolCall(call);
|
|
11
178
|
|
|
12
|
-
|
|
13
|
-
|
|
179
|
+
toolMessages.push({
|
|
180
|
+
role: "tool",
|
|
181
|
+
tool_call_id: call.id,
|
|
182
|
+
content: String(result)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
14
185
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
186
|
+
const finalResponse = await ai.chatWithTools(
|
|
187
|
+
[...messages, assistantMessage, ...toolMessages],
|
|
188
|
+
getToolSpecs()
|
|
189
|
+
);
|
|
19
190
|
|
|
20
|
-
return response;
|
|
191
|
+
return getText(finalResponse) || "No final response.";
|
|
21
192
|
}
|
|
22
193
|
|
|
23
194
|
module.exports = { runAgent };
|
package/index.js
CHANGED
|
@@ -1,61 +1,135 @@
|
|
|
1
1
|
console.log("Puter AI plugin loaded");
|
|
2
|
+
|
|
2
3
|
const ai = require("./puterClient");
|
|
3
4
|
const memory = require("./memory/conversation");
|
|
4
5
|
const agent = require("./agent");
|
|
5
6
|
|
|
6
|
-
module.exports = {
|
|
7
|
+
module.exports = function register(api) {
|
|
8
|
+
console.log("REGISTER FUNCTION CALLED");
|
|
9
|
+
|
|
10
|
+
api.registerCommand({
|
|
11
|
+
name: "ai",
|
|
12
|
+
description: "Puter AI assistant",
|
|
13
|
+
acceptsArgs: true,
|
|
14
|
+
|
|
15
|
+
handler: async (ctx) => {
|
|
16
|
+
console.log("COMMAND RECEIVED:", ctx);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const rawArgs =
|
|
20
|
+
Array.isArray(ctx.args) ? ctx.args.join(" ") : String(ctx.args || "");
|
|
21
|
+
|
|
22
|
+
const trimmed = rawArgs.trim();
|
|
23
|
+
const [sub, ...rest] = trimmed.split(/\s+/);
|
|
24
|
+
const prompt = rest.join(" ");
|
|
25
|
+
const user = ctx.senderId || ctx.userId || "default";
|
|
7
26
|
|
|
8
|
-
|
|
27
|
+
// plain /ai hello -> autonomous agent mode
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
return {
|
|
30
|
+
text:
|
|
31
|
+
"Usage: /ai <prompt> | /ai chat <prompt> | /ai summarize <text> | /ai image <prompt> | /ai agent <prompt> | /ai code <prompt> | /ai search <prompt> | /ai analyze <text>"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
9
34
|
|
|
10
|
-
|
|
35
|
+
// default agent mode when user does /ai something
|
|
36
|
+
const knownSubs = new Set([
|
|
37
|
+
"chat",
|
|
38
|
+
"summarize",
|
|
39
|
+
"image",
|
|
40
|
+
"agent",
|
|
41
|
+
"code",
|
|
42
|
+
"search",
|
|
43
|
+
"analyze"
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
if (!knownSubs.has(sub)) {
|
|
47
|
+
console.log("[Index] Defaulting to autonomous agent mode");
|
|
48
|
+
const response = await agent.runAgent(trimmed);
|
|
49
|
+
return { text: response };
|
|
50
|
+
}
|
|
11
51
|
|
|
12
|
-
|
|
52
|
+
if (sub === "chat") {
|
|
53
|
+
const history = memory.getHistory(user);
|
|
54
|
+
let result = "";
|
|
13
55
|
|
|
14
|
-
|
|
56
|
+
const response = await ai.chat(
|
|
57
|
+
[...history, { role: "user", content: prompt }],
|
|
58
|
+
(token) => {
|
|
59
|
+
result += token;
|
|
60
|
+
}
|
|
61
|
+
);
|
|
15
62
|
|
|
16
|
-
|
|
63
|
+
if (!result && typeof response === "string") {
|
|
64
|
+
result = response;
|
|
65
|
+
}
|
|
17
66
|
|
|
18
|
-
|
|
67
|
+
memory.addMessage(user, "user", prompt);
|
|
68
|
+
memory.addMessage(user, "assistant", result);
|
|
19
69
|
|
|
20
|
-
|
|
21
|
-
[...history, { role: "user", content: prompt }],
|
|
22
|
-
token => {
|
|
23
|
-
result += token;
|
|
24
|
-
if (stream) stream(token);
|
|
70
|
+
return { text: result || "No response from Puter." };
|
|
25
71
|
}
|
|
26
|
-
);
|
|
27
72
|
|
|
28
|
-
|
|
29
|
-
|
|
73
|
+
if (sub === "summarize") {
|
|
74
|
+
const response = await ai.chat([
|
|
75
|
+
{
|
|
76
|
+
role: "system",
|
|
77
|
+
content: "Summarize the following text clearly and concisely."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
role: "user",
|
|
81
|
+
content: prompt
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
return { text: response };
|
|
86
|
+
}
|
|
30
87
|
|
|
31
|
-
|
|
32
|
-
|
|
88
|
+
if (sub === "image") {
|
|
89
|
+
console.log("[Index] Image branch hit:", prompt);
|
|
33
90
|
|
|
34
|
-
|
|
91
|
+
const response = await ai.image(prompt);
|
|
35
92
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{ role: "user", content: prompt }
|
|
39
|
-
]);
|
|
93
|
+
console.log("[Index] Image branch response type:", typeof response);
|
|
94
|
+
console.log("[Index] Image branch response preview:", String(response).slice(0, 120));
|
|
40
95
|
|
|
41
|
-
|
|
42
|
-
|
|
96
|
+
return {
|
|
97
|
+
text: typeof response === "string" ? response : JSON.stringify(response, null, 2)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
43
100
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
101
|
+
if (sub === "agent") {
|
|
102
|
+
const response = await agent.runAgent(prompt);
|
|
103
|
+
return { text: response };
|
|
104
|
+
}
|
|
47
105
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
106
|
+
if (sub === "code") {
|
|
107
|
+
const response = await agent.runAgent(`Write code for: ${prompt}`);
|
|
108
|
+
return { text: response };
|
|
109
|
+
}
|
|
51
110
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
111
|
+
if (sub === "search") {
|
|
112
|
+
const response = await agent.runAgent(`Search for: ${prompt}`);
|
|
113
|
+
return { text: response };
|
|
114
|
+
}
|
|
55
115
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
116
|
+
if (sub === "analyze") {
|
|
117
|
+
const response = await agent.runAgent(
|
|
118
|
+
`Analyze the following content:\n${prompt}`
|
|
119
|
+
);
|
|
120
|
+
return { text: response };
|
|
121
|
+
}
|
|
59
122
|
|
|
60
|
-
|
|
123
|
+
return {
|
|
124
|
+
text:
|
|
125
|
+
"Usage: /ai <prompt> | /ai chat <prompt> | /ai summarize <text> | /ai image <prompt> | /ai agent <prompt> | /ai code <prompt> | /ai search <prompt> | /ai analyze <text>"
|
|
126
|
+
};
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error("PLUGIN ERROR:", err);
|
|
129
|
+
return {
|
|
130
|
+
text: `Plugin error: ${err.message || String(err)}`
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
61
135
|
};
|
package/memory/conversation.js
CHANGED
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
2
3
|
|
|
3
|
-
const FILE = "
|
|
4
|
+
const FILE = path.join(__dirname, "..", "memory.json");
|
|
4
5
|
|
|
5
|
-
function
|
|
6
|
+
function ensureFile() {
|
|
7
|
+
const dir = path.dirname(FILE);
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(dir)) {
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
if (!fs.existsSync(FILE)) {
|
|
8
|
-
fs.writeFileSync(FILE, JSON.stringify({}));
|
|
14
|
+
fs.writeFileSync(FILE, JSON.stringify({}, null, 2), "utf8");
|
|
9
15
|
}
|
|
16
|
+
}
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
function load() {
|
|
19
|
+
ensureFile();
|
|
20
|
+
return JSON.parse(fs.readFileSync(FILE, "utf8"));
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
function save(data) {
|
|
15
|
-
|
|
24
|
+
ensureFile();
|
|
25
|
+
fs.writeFileSync(FILE, JSON.stringify(data, null, 2), "utf8");
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
function getHistory(user) {
|
|
19
|
-
|
|
20
29
|
const data = load();
|
|
21
|
-
|
|
22
30
|
if (!data[user]) data[user] = [];
|
|
23
|
-
|
|
24
31
|
return data[user];
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
function addMessage(user, role, content) {
|
|
28
|
-
|
|
29
35
|
const data = load();
|
|
30
36
|
|
|
31
37
|
if (!data[user]) data[user] = [];
|
|
32
|
-
|
|
33
38
|
data[user].push({ role, content });
|
|
34
39
|
|
|
35
40
|
if (data[user].length > 20) {
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
2
|
+
"id": "openclaw-puter-ai",
|
|
3
|
+
"name": "Puter AI Assistant",
|
|
4
|
+
"version": "2.1.0",
|
|
5
|
+
"description": "Advanced AI assistant for OpenClaw powered by Puter with GPT-5.3 routing and autonomous tools.",
|
|
5
6
|
"main": "./index.js",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"/ai analyze",
|
|
12
|
-
"/ai search"
|
|
13
|
-
],
|
|
14
|
-
"author": "fw_dxs"
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {}
|
|
11
|
+
}
|
|
15
12
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fw_dxs/openclaw-puter-ai",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Advanced AI plugin for OpenClaw using Puter",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"@heyputer/puter.js": "^2.2.11",
|
|
9
|
+
"eventemitter3": "^5.0.1"
|
|
10
10
|
},
|
|
11
11
|
"openclaw": {
|
|
12
12
|
"extensions": ["./index.js"]
|
package/plugin.json
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
2
|
+
"id": "openclaw-puter-ai",
|
|
3
|
+
"name": "Puter AI Assistant",
|
|
4
|
+
"version": "2.1.0",
|
|
5
|
+
"description": "Advanced AI assistant for OpenClaw powered by Puter with GPT-5.3 dynamic routing and autonomous tools.",
|
|
6
|
+
"author": "fw_dxs",
|
|
7
|
+
"main": "./index.js",
|
|
5
8
|
"commands": [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
{
|
|
10
|
+
"name": "ai",
|
|
11
|
+
"description": "AI assistant powered by Puter",
|
|
12
|
+
"usage": "/ai <prompt>"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"features": [
|
|
16
|
+
"dynamic-model-routing",
|
|
17
|
+
"gpt-5.3-codex",
|
|
18
|
+
"autonomous-agent",
|
|
19
|
+
"tool-calling",
|
|
20
|
+
"conversation-memory",
|
|
21
|
+
"streaming-responses"
|
|
22
|
+
],
|
|
23
|
+
"permissions": [
|
|
24
|
+
"network",
|
|
25
|
+
"filesystem"
|
|
12
26
|
]
|
|
13
27
|
}
|
package/puterClient.js
CHANGED
|
@@ -1,20 +1,259 @@
|
|
|
1
|
+
console.log("puterClient.js loaded");
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { init, getAuthToken } = require("@heyputer/puter.js/src/init.cjs");
|
|
6
|
+
|
|
7
|
+
let puterInstance = null;
|
|
8
|
+
|
|
9
|
+
const MODELS = {
|
|
10
|
+
chat: "gpt-5.3-chat",
|
|
11
|
+
code: "openai/gpt-5.3-codex",
|
|
12
|
+
image: "openai/gpt-image-1"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const TOKEN_FILE = path.join(__dirname, ".puter-token.json");
|
|
16
|
+
const OUTPUT_DIR = path.join(__dirname, "generated");
|
|
17
|
+
|
|
18
|
+
function ensureDir(dir) {
|
|
19
|
+
if (!fs.existsSync(dir)) {
|
|
20
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadSavedToken() {
|
|
25
|
+
try {
|
|
26
|
+
if (!fs.existsSync(TOKEN_FILE)) return null;
|
|
27
|
+
const raw = fs.readFileSync(TOKEN_FILE, "utf8");
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
return parsed?.token || null;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error("[Puter] Failed loading token:", err);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function saveToken(token) {
|
|
37
|
+
try {
|
|
38
|
+
fs.writeFileSync(TOKEN_FILE, JSON.stringify({ token }, null, 2), "utf8");
|
|
39
|
+
console.log("[Puter] Token saved");
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error("[Puter] Failed saving token:", err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getPuter() {
|
|
46
|
+
if (puterInstance) return puterInstance;
|
|
47
|
+
|
|
48
|
+
let token = process.env.puterAuthToken || loadSavedToken();
|
|
49
|
+
|
|
50
|
+
if (!token) {
|
|
51
|
+
console.log("[Puter] Starting auth flow");
|
|
52
|
+
token = await getAuthToken();
|
|
53
|
+
if (!token) {
|
|
54
|
+
throw new Error("Failed to get Puter auth token.");
|
|
55
|
+
}
|
|
56
|
+
saveToken(token);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
puterInstance = init(token);
|
|
60
|
+
console.log("[Puter] Initialized");
|
|
61
|
+
|
|
62
|
+
return puterInstance;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function pickChatModel(messages) {
|
|
66
|
+
const combined = Array.isArray(messages)
|
|
67
|
+
? messages.map((m) => m?.content || "").join(" ")
|
|
68
|
+
: String(messages || "");
|
|
69
|
+
|
|
70
|
+
const text = combined.toLowerCase();
|
|
71
|
+
|
|
72
|
+
const codeSignals = [
|
|
73
|
+
"code",
|
|
74
|
+
"debug",
|
|
75
|
+
"bug",
|
|
76
|
+
"function",
|
|
77
|
+
"class",
|
|
78
|
+
"javascript",
|
|
79
|
+
"typescript",
|
|
80
|
+
"python",
|
|
81
|
+
"api",
|
|
82
|
+
"error",
|
|
83
|
+
"stack trace",
|
|
84
|
+
"plugin",
|
|
85
|
+
"openclaw"
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const shouldUseCodex = codeSignals.some((s) => text.includes(s));
|
|
89
|
+
const model = shouldUseCodex ? MODELS.code : MODELS.chat;
|
|
90
|
+
|
|
91
|
+
console.log("[Model Router] Selected:", model);
|
|
92
|
+
return model;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extractText(resp) {
|
|
96
|
+
if (!resp) return "";
|
|
97
|
+
|
|
98
|
+
if (typeof resp === "string") return resp;
|
|
99
|
+
if (typeof resp?.text === "string") return resp.text;
|
|
100
|
+
if (typeof resp?.message?.content === "string") return resp.message.content;
|
|
101
|
+
if (typeof resp?.choices?.[0]?.message?.content === "string") {
|
|
102
|
+
return resp.choices[0].message.content;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return JSON.stringify(resp, null, 2);
|
|
106
|
+
}
|
|
107
|
+
|
|
1
108
|
async function chat(messages, streamCallback) {
|
|
109
|
+
const puter = await getPuter();
|
|
110
|
+
const model = pickChatModel(messages);
|
|
111
|
+
let fullText = "";
|
|
112
|
+
|
|
113
|
+
console.log("[Puter Chat] Starting with model:", model);
|
|
2
114
|
|
|
3
115
|
const response = await puter.ai.chat(messages, {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
116
|
+
model,
|
|
117
|
+
stream: true
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
for await (const token of response) {
|
|
121
|
+
const chunk =
|
|
122
|
+
typeof token === "string"
|
|
123
|
+
? token
|
|
124
|
+
: token?.text || token?.content || token?.delta || "";
|
|
125
|
+
|
|
126
|
+
fullText += chunk;
|
|
127
|
+
if (streamCallback) streamCallback(chunk);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return fullText || "No response from Puter.";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function chatWithTools(messages, tools) {
|
|
134
|
+
const puter = await getPuter();
|
|
135
|
+
const model = pickChatModel(messages);
|
|
136
|
+
|
|
137
|
+
console.log("[Puter Tools] Starting with model:", model);
|
|
138
|
+
|
|
139
|
+
const response = await puter.ai.chat(messages, {
|
|
140
|
+
model,
|
|
141
|
+
tools,
|
|
142
|
+
stream: false
|
|
8
143
|
});
|
|
9
144
|
|
|
10
145
|
return response;
|
|
11
146
|
}
|
|
12
147
|
|
|
148
|
+
function saveDataUrlToFile(src) {
|
|
149
|
+
if (typeof src !== "string" || !src.startsWith("data:image")) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
ensureDir(OUTPUT_DIR);
|
|
154
|
+
|
|
155
|
+
const match = src.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
|
|
156
|
+
if (!match) {
|
|
157
|
+
throw new Error("Unsupported image data format.");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const mimeType = match[1];
|
|
161
|
+
const base64 = match[2];
|
|
162
|
+
|
|
163
|
+
let ext = "png";
|
|
164
|
+
if (mimeType.includes("webp")) ext = "webp";
|
|
165
|
+
else if (mimeType.includes("jpeg")) ext = "jpg";
|
|
166
|
+
else if (mimeType.includes("jpg")) ext = "jpg";
|
|
167
|
+
else if (mimeType.includes("png")) ext = "png";
|
|
168
|
+
|
|
169
|
+
const buffer = Buffer.from(base64, "base64");
|
|
170
|
+
const filename = `ai-image-${Date.now()}.${ext}`;
|
|
171
|
+
const filepath = path.join(OUTPUT_DIR, filename);
|
|
172
|
+
|
|
173
|
+
fs.writeFileSync(filepath, buffer);
|
|
174
|
+
return filepath;
|
|
175
|
+
}
|
|
176
|
+
|
|
13
177
|
async function image(prompt) {
|
|
14
|
-
|
|
178
|
+
const puter = await getPuter();
|
|
179
|
+
|
|
180
|
+
console.log("[Puter Image] Prompt:", prompt);
|
|
181
|
+
|
|
182
|
+
const response = await puter.ai.txt2img(prompt, {
|
|
183
|
+
model: MODELS.image
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
console.log("[Puter Image] Raw response keys:", Object.keys(response || {}));
|
|
187
|
+
|
|
188
|
+
let src = null;
|
|
189
|
+
|
|
190
|
+
if (typeof response === "string") {
|
|
191
|
+
src = response;
|
|
192
|
+
} else if (response?.src) {
|
|
193
|
+
src = response.src;
|
|
194
|
+
} else if (response?.url) {
|
|
195
|
+
src = response.url;
|
|
196
|
+
} else if (response?.image?.src) {
|
|
197
|
+
src = response.image.src;
|
|
198
|
+
} else if (response?.image?.url) {
|
|
199
|
+
src = response.image.url;
|
|
200
|
+
} else if (response?.data?.src) {
|
|
201
|
+
src = response.data.src;
|
|
202
|
+
} else if (response?.data?.url) {
|
|
203
|
+
src = response.data.url;
|
|
204
|
+
} else if (Array.isArray(response) && response[0]?.src) {
|
|
205
|
+
src = response[0].src;
|
|
206
|
+
} else if (Array.isArray(response) && response[0]?.url) {
|
|
207
|
+
src = response[0].url;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!src) {
|
|
211
|
+
console.log("[Puter Image] Full response:", response);
|
|
212
|
+
throw new Error("Image response did not contain a usable src/url field.");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log("[Puter Image] src preview:", src.slice(0, 80));
|
|
216
|
+
|
|
217
|
+
// normal URL
|
|
218
|
+
if (!src.startsWith("data:")) {
|
|
219
|
+
return `Image generated:\n${src}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
ensureDir(OUTPUT_DIR);
|
|
223
|
+
|
|
224
|
+
// accept data:image/... OR data:undefined;base64,...
|
|
225
|
+
const match = src.match(/^data:([^;,]*)?;base64,(.+)$/);
|
|
226
|
+
|
|
227
|
+
if (!match) {
|
|
228
|
+
throw new Error("Unsupported data URL format.");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const mimeType = match[1] || "";
|
|
232
|
+
const base64 = match[2];
|
|
233
|
+
|
|
234
|
+
let ext = "png";
|
|
235
|
+
if (mimeType.includes("webp")) ext = "webp";
|
|
236
|
+
else if (mimeType.includes("jpeg")) ext = "jpg";
|
|
237
|
+
else if (mimeType.includes("jpg")) ext = "jpg";
|
|
238
|
+
else if (mimeType.includes("png")) ext = "png";
|
|
239
|
+
else if (mimeType.includes("gif")) ext = "gif";
|
|
240
|
+
|
|
241
|
+
const filename = `ai-image-${Date.now()}.${ext}`;
|
|
242
|
+
const filepath = path.join(OUTPUT_DIR, filename);
|
|
243
|
+
|
|
244
|
+
fs.writeFileSync(filepath, Buffer.from(base64, "base64"));
|
|
245
|
+
|
|
246
|
+
console.log("[Puter Image] Saved file:", filepath);
|
|
247
|
+
|
|
248
|
+
return `Image generated:\n${filepath}`;
|
|
15
249
|
}
|
|
16
250
|
|
|
17
251
|
module.exports = {
|
|
18
252
|
chat,
|
|
19
|
-
|
|
253
|
+
chatWithTools,
|
|
254
|
+
image,
|
|
255
|
+
getPuter,
|
|
256
|
+
pickChatModel,
|
|
257
|
+
extractText,
|
|
258
|
+
MODELS
|
|
20
259
|
};
|
package/tools/codeGen.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
const ai = require("../puterClient");
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
async function codeGen(prompt) {
|
|
4
|
+
return await ai.chat([
|
|
4
5
|
{
|
|
5
6
|
role: "system",
|
|
6
7
|
content: "You generate clean, working code."
|
|
@@ -10,8 +11,6 @@ async function codeGen(prompt) {
|
|
|
10
11
|
content: prompt
|
|
11
12
|
}
|
|
12
13
|
]);
|
|
13
|
-
|
|
14
|
-
return response;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
module.exports = codeGen;
|