@fw_dxs/openclaw-puter-ai 2.0.3 → 2.1.1

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,194 @@
1
1
  # OpenClaw Puter AI Plugin
2
2
 
3
- Adds AI capabilities to OpenClaw using Puter.
3
+ ![npm](https://img.shields.io/npm/v/@fw_dxs/openclaw-puter-ai)
4
+ ![license](https://img.shields.io/npm/l/@fw_dxs/openclaw-puter-ai)
4
5
 
5
- Commands:
6
+ An advanced AI assistant plugin for **OpenClaw**, powered by **Puter AI** with dynamic model routing, autonomous tools, and image generation.
6
7
 
7
- /ai chat
8
- /ai summarize
9
- /ai image
10
- /ai code
11
- /ai analyze
12
- /ai search
8
+ This plugin adds a powerful AI agent to OpenClaw capable of chat, coding assistance, web search, and image generation.
13
9
 
14
- Install:
10
+ ---
15
11
 
16
- 1. Place folder in /plugins
17
- 2. Run npm install
18
- 3. Restart OpenClaw
12
+ # Features
13
+
14
+ - GPT-5.3 chat support
15
+ - GPT-5.3 Codex routing for coding tasks
16
+ - Autonomous AI agent with tool calling
17
+ - Web search integration
18
+ - Code generation assistance
19
+ - AI image generation
20
+ - Conversation memory
21
+ - Streaming responses
22
+ - Automatic Puter authentication
23
+ - Token caching (no repeated login)
24
+
25
+ ---
26
+
27
+ # Commands
28
+
29
+ ## Chat
30
+
31
+ ```
32
+ /ai <prompt>
33
+ ```
34
+
35
+ Example:
36
+
37
+ ```
38
+ /ai explain how transformers work
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Memory Chat
44
+
45
+ Stores conversation history for the user.
46
+
47
+ ```
48
+ /ai chat <prompt>
49
+ ```
50
+
51
+ Example:
52
+
53
+ ```
54
+ /ai chat remember my project name is Atlas
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Code Generation
60
+
61
+ Automatically routes to **GPT-5.3 Codex** for coding tasks.
62
+
63
+ ```
64
+ /ai code build a nodejs api
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Web Search
70
+
71
+ ```
72
+ /ai search latest AI models
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Image Generation
78
+
79
+ ```
80
+ /ai image futuristic city at sunset
81
+ ```
82
+
83
+ Images are generated using Puter and saved locally.
84
+
85
+ ---
86
+
87
+ ## Autonomous Agent
88
+
89
+ The agent can automatically decide which tools to use.
90
+
91
+ ```
92
+ /ai research how to build a discord bot
93
+ ```
94
+
95
+ The AI may automatically:
96
+
97
+ - search the web
98
+ - generate code
99
+ - create images
100
+ - answer directly
101
+
102
+ ---
103
+
104
+ # Installation
105
+
106
+ Install the plugin via npm:
107
+
108
+ ```
109
+ npm install @fw_dxs/openclaw-puter-ai
110
+ ```
111
+
112
+ Then enable it in your OpenClaw configuration.
113
+
114
+ ---
115
+
116
+ # Requirements
117
+
118
+ - Node.js 18+
119
+ - OpenClaw
120
+ - Puter account (for AI access)
121
+
122
+ The plugin will automatically prompt for Puter authentication on first use.
123
+
124
+ ---
125
+
126
+ # Model Routing
127
+
128
+ The plugin automatically selects models depending on the task.
129
+
130
+ | Task | Model |
131
+ |------|------|
132
+ | General chat | GPT-5.3 Chat |
133
+ | Coding tasks | GPT-5.3 Codex |
134
+ | Image generation | GPT Image |
135
+
136
+ ---
137
+
138
+ # Project Structure
139
+
140
+ ```
141
+ openclaw-puter-ai
142
+
143
+ ├── index.js
144
+ ├── agent.js
145
+ ├── puterClient.js
146
+ ├── openclaw.plugin.json
147
+ ├── README.md
148
+
149
+ ├── tools
150
+ │ ├── webSearch.js
151
+ │ └── codeGen.js
152
+
153
+ ├── memory
154
+ │ └── conversation.js
155
+
156
+ └── generated
157
+ ```
158
+
159
+ ---
160
+
161
+ # Security Notes
162
+
163
+ Do **not publish or share** these files:
164
+
165
+ ```
166
+ .puter-token.json
167
+ memory.json
168
+ generated/
169
+ ```
170
+
171
+ These files contain local runtime data.
172
+
173
+ ---
174
+
175
+ # License
176
+
177
+ MIT License
178
+
179
+ ---
180
+
181
+ # Author
182
+
183
+ fw_dxs
184
+
185
+ ---
186
+
187
+ # Future Improvements
188
+
189
+ Planned features include:
190
+
191
+ - filesystem tools for autonomous development agents
192
+ - code execution tools
193
+ - multi-image generation
194
+ - improved OpenClaw UI integration
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,17 +1,12 @@
1
1
  {
2
2
  "id": "openclaw-puter-ai",
3
- "name": "OpenClaw Puter AI",
4
- "version": "2.0.3",
5
- "description": "Advanced AI plugin for OpenClaw using Puter",
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.",
6
6
  "main": "./index.js",
7
- "commands": [
8
- "/ai chat",
9
- "/ai summarize",
10
- "/ai image",
11
- "/ai code",
12
- "/ai analyze",
13
- "/ai search"
14
- ],
15
- "author": "fw_dxs",
16
- "configSchema": {}
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {}
11
+ }
17
12
  }
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@fw_dxs/openclaw-puter-ai",
3
- "version": "2.0.3",
3
+ "version": "2.1.1",
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
- "extensions": ["./index.js"]
12
+ "extensions": [
13
+ "./index.js"
14
+ ]
13
15
  }
14
- }
16
+ }
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);