@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 CHANGED
@@ -1,18 +1,388 @@
1
- # OpenClaw Puter AI Plugin
1
+ # \# OpenClaw Puter AI Plugin (Actually Working)
2
2
 
3
- Adds AI capabilities to OpenClaw using Puter.
3
+ #
4
4
 
5
- Commands:
5
+ # !\[npm](https://img.shields.io/npm/v/@fw\_dxs/openclaw-puter-ai)
6
6
 
7
- /ai chat
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
- Install:
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 systemPrompt = `
7
- You are an AI agent with tools:
173
+ const assistantMessage = getAssistantMessage(firstResponse, toolCalls);
174
+ const toolMessages = [];
8
175
 
9
- webSearch(query) search the internet
10
- codeGen(prompt) generate code
176
+ for (const call of toolCalls) {
177
+ const result = await runToolCall(call);
11
178
 
12
- Use them when helpful.
13
- `;
179
+ toolMessages.push({
180
+ role: "tool",
181
+ tool_call_id: call.id,
182
+ content: String(result)
183
+ });
184
+ }
14
185
 
15
- const response = await puter.ai.chat([
16
- { role: "system", content: systemPrompt },
17
- { role: "user", content: prompt }
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
- name: "puter-ai",
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
- async handleCommand(command, args, user, stream) {
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
- const prompt = args.join(" ");
52
+ if (sub === "chat") {
53
+ const history = memory.getHistory(user);
54
+ let result = "";
13
55
 
14
- if (command === "/ai chat") {
56
+ const response = await ai.chat(
57
+ [...history, { role: "user", content: prompt }],
58
+ (token) => {
59
+ result += token;
60
+ }
61
+ );
15
62
 
16
- const history = memory.getHistory(user);
63
+ if (!result && typeof response === "string") {
64
+ result = response;
65
+ }
17
66
 
18
- let result = "";
67
+ memory.addMessage(user, "user", prompt);
68
+ memory.addMessage(user, "assistant", result);
19
69
 
20
- await ai.chat(
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
- memory.addMessage(user, "user", prompt);
29
- memory.addMessage(user, "assistant", result);
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
- return result;
32
- }
88
+ if (sub === "image") {
89
+ console.log("[Index] Image branch hit:", prompt);
33
90
 
34
- if (command === "/ai summarize") {
91
+ const response = await ai.image(prompt);
35
92
 
36
- const response = await ai.chat([
37
- { role: "system", content: "Summarize the following text." },
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
- return response;
42
- }
96
+ return {
97
+ text: typeof response === "string" ? response : JSON.stringify(response, null, 2)
98
+ };
99
+ }
43
100
 
44
- if (command === "/ai image") {
45
- return await ai.image(prompt);
46
- }
101
+ if (sub === "agent") {
102
+ const response = await agent.runAgent(prompt);
103
+ return { text: response };
104
+ }
47
105
 
48
- if (command === "/ai code") {
49
- return await agent.runAgent("Write code for: " + prompt);
50
- }
106
+ if (sub === "code") {
107
+ const response = await agent.runAgent(`Write code for: ${prompt}`);
108
+ return { text: response };
109
+ }
51
110
 
52
- if (command === "/ai search") {
53
- return await agent.runAgent(prompt);
54
- }
111
+ if (sub === "search") {
112
+ const response = await agent.runAgent(`Search for: ${prompt}`);
113
+ return { text: response };
114
+ }
55
115
 
56
- if (command === "/ai analyze") {
57
- return await agent.runAgent("Analyze the following content:\n" + prompt);
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
  };
@@ -1,35 +1,40 @@
1
1
  const fs = require("fs");
2
+ const path = require("path");
2
3
 
3
- const FILE = "./plugins/openclaw-puter-ai/memory.json";
4
+ const FILE = path.join(__dirname, "..", "memory.json");
4
5
 
5
- function load() {
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
- return JSON.parse(fs.readFileSync(FILE));
18
+ function load() {
19
+ ensureFile();
20
+ return JSON.parse(fs.readFileSync(FILE, "utf8"));
12
21
  }
13
22
 
14
23
  function save(data) {
15
- fs.writeFileSync(FILE, JSON.stringify(data, null, 2));
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) {
@@ -1,15 +1,12 @@
1
1
  {
2
- "name": "openclaw-puter-ai",
3
- "version": "2.0.2",
4
- "description": "Advanced AI plugin for OpenClaw using Puter",
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
- "commands": [
7
- "/ai chat",
8
- "/ai summarize",
9
- "/ai image",
10
- "/ai code",
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.2",
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
- "eventemitter3": "^5.0.1",
9
- "node-fetch": "^3.3.2"
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
- "name": "puter-ai",
3
- "version": "2.0.0",
4
- "description": "Adds AI commands powered by Puter",
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
- "/ai chat",
7
- "/ai summarize",
8
- "/ai image",
9
- "/ai code",
10
- "/ai analyze",
11
- "/ai search"
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
- stream: true,
5
- onToken: token => {
6
- if (streamCallback) streamCallback(token);
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
- return await puter.ai.image(prompt);
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
- image
253
+ chatWithTools,
254
+ image,
255
+ getPuter,
256
+ pickChatModel,
257
+ extractText,
258
+ MODELS
20
259
  };
package/tools/codeGen.js CHANGED
@@ -1,6 +1,7 @@
1
- async function codeGen(prompt) {
1
+ const ai = require("../puterClient");
2
2
 
3
- const response = await puter.ai.chat([
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;
@@ -1,7 +1,4 @@
1
- const fetch = require("node-fetch");
2
-
3
1
  async function webSearch(query) {
4
-
5
2
  const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json`;
6
3
 
7
4
  const res = await fetch(url);