@moly-mcp/lido 1.0.1 → 1.0.3
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/dist/bin.js +21 -6
- package/dist/chunk-RE3UIDLV.js +545 -0
- package/dist/server/index.js +18 -526
- package/dist/session-ZXWCIUKX.js +534 -0
- package/package.json +1 -1
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
castVote,
|
|
4
|
+
claimWithdrawals,
|
|
5
|
+
getBalance,
|
|
6
|
+
getConversionRate,
|
|
7
|
+
getProposal,
|
|
8
|
+
getProposals,
|
|
9
|
+
getRewards,
|
|
10
|
+
getSettings,
|
|
11
|
+
getWithdrawalRequests,
|
|
12
|
+
getWithdrawalStatus,
|
|
13
|
+
requestWithdrawal,
|
|
14
|
+
stakeEth,
|
|
15
|
+
unwrapWsteth,
|
|
16
|
+
updateSettings,
|
|
17
|
+
wrapSteth
|
|
18
|
+
} from "./chunk-RE3UIDLV.js";
|
|
19
|
+
import "./chunk-PIFEXJ56.js";
|
|
20
|
+
|
|
21
|
+
// src/chat/session.ts
|
|
22
|
+
import * as readline from "readline";
|
|
23
|
+
import * as fs from "fs";
|
|
24
|
+
import * as path from "path";
|
|
25
|
+
|
|
26
|
+
// src/chat/providers.ts
|
|
27
|
+
async function callAnthropic(apiKey, model, messages, tools) {
|
|
28
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"x-api-key": apiKey,
|
|
32
|
+
"anthropic-version": "2023-06-01",
|
|
33
|
+
"content-type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
model,
|
|
37
|
+
max_tokens: 4096,
|
|
38
|
+
messages,
|
|
39
|
+
tools: tools.map((t) => ({
|
|
40
|
+
name: t.name,
|
|
41
|
+
description: t.description,
|
|
42
|
+
input_schema: t.parameters
|
|
43
|
+
}))
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const err = await res.text();
|
|
48
|
+
throw new Error(`Anthropic API error ${res.status}: ${err}`);
|
|
49
|
+
}
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
const content = data.content;
|
|
52
|
+
const textBlock = content.find((b) => b.type === "text");
|
|
53
|
+
const toolUseBlocks = content.filter((b) => b.type === "tool_use");
|
|
54
|
+
return {
|
|
55
|
+
text: textBlock?.text ?? null,
|
|
56
|
+
toolCalls: toolUseBlocks.map((b) => ({
|
|
57
|
+
id: b.id,
|
|
58
|
+
name: b.name,
|
|
59
|
+
args: b.input
|
|
60
|
+
})),
|
|
61
|
+
rawAssistantMessage: { role: "assistant", content }
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function anthropicToolResult(toolCallId, result) {
|
|
65
|
+
return {
|
|
66
|
+
role: "user",
|
|
67
|
+
content: [{ type: "tool_result", tool_use_id: toolCallId, content: result }]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function callOpenRouter(apiKey, model, messages, tools) {
|
|
71
|
+
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: {
|
|
74
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
"HTTP-Referer": "https://github.com/daiwikmh/moly",
|
|
77
|
+
"X-Title": "Moly \u2014 Lido MCP"
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
model,
|
|
81
|
+
messages,
|
|
82
|
+
tools: tools.map((t) => ({
|
|
83
|
+
type: "function",
|
|
84
|
+
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
85
|
+
}))
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
const err = await res.text();
|
|
90
|
+
throw new Error(`OpenRouter API error ${res.status}: ${err}`);
|
|
91
|
+
}
|
|
92
|
+
const data = await res.json();
|
|
93
|
+
const msg = data.choices[0].message;
|
|
94
|
+
return {
|
|
95
|
+
text: msg.content ?? null,
|
|
96
|
+
toolCalls: (msg.tool_calls ?? []).map((tc) => ({
|
|
97
|
+
id: tc.id,
|
|
98
|
+
name: tc.function.name,
|
|
99
|
+
args: JSON.parse(tc.function.arguments ?? "{}")
|
|
100
|
+
})),
|
|
101
|
+
rawAssistantMessage: { role: "assistant", content: msg.content, tool_calls: msg.tool_calls }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function callGemini(apiKey, model, messages, tools) {
|
|
105
|
+
const contents = messages.map((m) => ({
|
|
106
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
107
|
+
parts: Array.isArray(m.content) ? m.content : [{ text: m.content }]
|
|
108
|
+
}));
|
|
109
|
+
const res = await fetch(
|
|
110
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,
|
|
111
|
+
{
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "content-type": "application/json" },
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
contents,
|
|
116
|
+
tools: [{
|
|
117
|
+
functionDeclarations: tools.map((t) => ({
|
|
118
|
+
name: t.name,
|
|
119
|
+
description: t.description,
|
|
120
|
+
parameters: t.parameters
|
|
121
|
+
}))
|
|
122
|
+
}]
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
const err = await res.text();
|
|
128
|
+
throw new Error(`Gemini API error ${res.status}: ${err}`);
|
|
129
|
+
}
|
|
130
|
+
const data = await res.json();
|
|
131
|
+
const parts = data.candidates?.[0]?.content?.parts ?? [];
|
|
132
|
+
const textPart = parts.find((p) => p.text);
|
|
133
|
+
const fnCalls = parts.filter((p) => p.functionCall);
|
|
134
|
+
return {
|
|
135
|
+
text: textPart?.text ?? null,
|
|
136
|
+
toolCalls: fnCalls.map((p, i) => ({
|
|
137
|
+
id: `gemini-${i}`,
|
|
138
|
+
name: p.functionCall.name,
|
|
139
|
+
args: p.functionCall.args ?? {}
|
|
140
|
+
})),
|
|
141
|
+
rawAssistantMessage: {
|
|
142
|
+
role: "model",
|
|
143
|
+
parts
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function geminiToolResult(toolCallId, name, result) {
|
|
148
|
+
return {
|
|
149
|
+
role: "user",
|
|
150
|
+
content: [{ functionResponse: { name, response: { output: result } } }]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function callAi(provider, apiKey, model, messages, tools) {
|
|
154
|
+
switch (provider) {
|
|
155
|
+
case "anthropic":
|
|
156
|
+
return callAnthropic(apiKey, model, messages, tools);
|
|
157
|
+
case "openrouter":
|
|
158
|
+
return callOpenRouter(apiKey, model, messages, tools);
|
|
159
|
+
case "google":
|
|
160
|
+
return callGemini(apiKey, model, messages, tools);
|
|
161
|
+
default:
|
|
162
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function makeToolResultMessage(provider, toolCallId, toolName, result) {
|
|
166
|
+
switch (provider) {
|
|
167
|
+
case "anthropic":
|
|
168
|
+
return anthropicToolResult(toolCallId, result);
|
|
169
|
+
case "openrouter":
|
|
170
|
+
return { role: "tool", content: result, ...{ tool_call_id: toolCallId } };
|
|
171
|
+
case "google":
|
|
172
|
+
return geminiToolResult(toolCallId, toolName, result);
|
|
173
|
+
default:
|
|
174
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/chat/tools.ts
|
|
179
|
+
var TOOL_DEFS = [
|
|
180
|
+
{
|
|
181
|
+
name: "get_balance",
|
|
182
|
+
description: "Get ETH, stETH, and wstETH balances for an address.",
|
|
183
|
+
parameters: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
address: { type: "string", description: "Ethereum address (optional, defaults to configured wallet)" }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "get_rewards",
|
|
192
|
+
description: "Get staking reward history for an address over N days.",
|
|
193
|
+
parameters: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
address: { type: "string", description: "Ethereum address (optional)" },
|
|
197
|
+
days: { type: "number", description: "Days to look back (1-365), default 7" }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "get_conversion_rate",
|
|
203
|
+
description: "Get current stETH \u2194 wstETH conversion rates.",
|
|
204
|
+
parameters: { type: "object", properties: {} }
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "stake_eth",
|
|
208
|
+
description: "Stake ETH to receive stETH (liquid staking).",
|
|
209
|
+
parameters: {
|
|
210
|
+
type: "object",
|
|
211
|
+
required: ["amount_eth"],
|
|
212
|
+
properties: {
|
|
213
|
+
amount_eth: { type: "string", description: 'Amount of ETH to stake (e.g. "0.1")' },
|
|
214
|
+
dry_run: { type: "boolean", description: "Simulate without broadcasting" }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "request_withdrawal",
|
|
220
|
+
description: "Request withdrawal of stETH back to ETH. Min 0.1, max 1000 stETH.",
|
|
221
|
+
parameters: {
|
|
222
|
+
type: "object",
|
|
223
|
+
required: ["amount_steth"],
|
|
224
|
+
properties: {
|
|
225
|
+
amount_steth: { type: "string", description: "Amount of stETH to withdraw" },
|
|
226
|
+
dry_run: { type: "boolean" }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "claim_withdrawals",
|
|
232
|
+
description: "Claim finalized withdrawal requests and receive ETH.",
|
|
233
|
+
parameters: {
|
|
234
|
+
type: "object",
|
|
235
|
+
required: ["request_ids"],
|
|
236
|
+
properties: {
|
|
237
|
+
request_ids: { type: "array", items: { type: "string" }, description: "Withdrawal request NFT IDs" },
|
|
238
|
+
dry_run: { type: "boolean" }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "get_withdrawal_requests",
|
|
244
|
+
description: "Get all pending withdrawal request IDs for an address.",
|
|
245
|
+
parameters: {
|
|
246
|
+
type: "object",
|
|
247
|
+
properties: {
|
|
248
|
+
address: { type: "string", description: "Ethereum address (optional)" }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "get_withdrawal_status",
|
|
254
|
+
description: "Check finalization status of withdrawal request IDs.",
|
|
255
|
+
parameters: {
|
|
256
|
+
type: "object",
|
|
257
|
+
required: ["request_ids"],
|
|
258
|
+
properties: {
|
|
259
|
+
request_ids: { type: "array", items: { type: "string" } }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "wrap_steth",
|
|
265
|
+
description: "Wrap stETH into wstETH.",
|
|
266
|
+
parameters: {
|
|
267
|
+
type: "object",
|
|
268
|
+
required: ["amount_steth"],
|
|
269
|
+
properties: {
|
|
270
|
+
amount_steth: { type: "string" },
|
|
271
|
+
dry_run: { type: "boolean" }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "unwrap_wsteth",
|
|
277
|
+
description: "Unwrap wstETH back to stETH.",
|
|
278
|
+
parameters: {
|
|
279
|
+
type: "object",
|
|
280
|
+
required: ["amount_wsteth"],
|
|
281
|
+
properties: {
|
|
282
|
+
amount_wsteth: { type: "string" },
|
|
283
|
+
dry_run: { type: "boolean" }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: "get_proposals",
|
|
289
|
+
description: "List recent Lido DAO governance proposals.",
|
|
290
|
+
parameters: {
|
|
291
|
+
type: "object",
|
|
292
|
+
properties: {
|
|
293
|
+
count: { type: "number", description: "Number of proposals (1-20, default 5)" }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "get_proposal",
|
|
299
|
+
description: "Get detailed info on a specific Lido DAO governance proposal.",
|
|
300
|
+
parameters: {
|
|
301
|
+
type: "object",
|
|
302
|
+
required: ["proposal_id"],
|
|
303
|
+
properties: {
|
|
304
|
+
proposal_id: { type: "number" }
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "cast_vote",
|
|
310
|
+
description: "Vote YEA or NAY on a Lido DAO governance proposal.",
|
|
311
|
+
parameters: {
|
|
312
|
+
type: "object",
|
|
313
|
+
required: ["proposal_id", "support"],
|
|
314
|
+
properties: {
|
|
315
|
+
proposal_id: { type: "number" },
|
|
316
|
+
support: { type: "boolean", description: "true = YEA, false = NAY" },
|
|
317
|
+
dry_run: { type: "boolean" }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "get_settings",
|
|
323
|
+
description: "Get current Moly configuration.",
|
|
324
|
+
parameters: { type: "object", properties: {} }
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "update_settings",
|
|
328
|
+
description: "Change mode, network, or RPC.",
|
|
329
|
+
parameters: {
|
|
330
|
+
type: "object",
|
|
331
|
+
properties: {
|
|
332
|
+
network: { type: "string", enum: ["hoodi", "mainnet"] },
|
|
333
|
+
mode: { type: "string", enum: ["simulation", "live"] },
|
|
334
|
+
rpc: { type: "string", nullable: true },
|
|
335
|
+
model: { type: "string" }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
];
|
|
340
|
+
async function executeTool(name, args) {
|
|
341
|
+
try {
|
|
342
|
+
let result;
|
|
343
|
+
switch (name) {
|
|
344
|
+
case "get_balance":
|
|
345
|
+
result = await getBalance(args.address);
|
|
346
|
+
break;
|
|
347
|
+
case "get_rewards":
|
|
348
|
+
result = await getRewards(args.address, args.days);
|
|
349
|
+
break;
|
|
350
|
+
case "get_conversion_rate":
|
|
351
|
+
result = await getConversionRate();
|
|
352
|
+
break;
|
|
353
|
+
case "stake_eth":
|
|
354
|
+
result = await stakeEth(args.amount_eth, args.dry_run);
|
|
355
|
+
break;
|
|
356
|
+
case "request_withdrawal":
|
|
357
|
+
result = await requestWithdrawal(args.amount_steth, args.dry_run);
|
|
358
|
+
break;
|
|
359
|
+
case "claim_withdrawals":
|
|
360
|
+
result = await claimWithdrawals(args.request_ids, args.dry_run);
|
|
361
|
+
break;
|
|
362
|
+
case "get_withdrawal_requests":
|
|
363
|
+
result = await getWithdrawalRequests(args.address);
|
|
364
|
+
break;
|
|
365
|
+
case "get_withdrawal_status":
|
|
366
|
+
result = await getWithdrawalStatus(args.request_ids);
|
|
367
|
+
break;
|
|
368
|
+
case "wrap_steth":
|
|
369
|
+
result = await wrapSteth(args.amount_steth, args.dry_run);
|
|
370
|
+
break;
|
|
371
|
+
case "unwrap_wsteth":
|
|
372
|
+
result = await unwrapWsteth(args.amount_wsteth, args.dry_run);
|
|
373
|
+
break;
|
|
374
|
+
case "get_proposals":
|
|
375
|
+
result = await getProposals(args.count);
|
|
376
|
+
break;
|
|
377
|
+
case "get_proposal":
|
|
378
|
+
result = await getProposal(args.proposal_id);
|
|
379
|
+
break;
|
|
380
|
+
case "cast_vote":
|
|
381
|
+
result = await castVote(args.proposal_id, args.support, args.dry_run);
|
|
382
|
+
break;
|
|
383
|
+
case "get_settings":
|
|
384
|
+
result = getSettings();
|
|
385
|
+
break;
|
|
386
|
+
case "update_settings":
|
|
387
|
+
result = updateSettings(args);
|
|
388
|
+
break;
|
|
389
|
+
default:
|
|
390
|
+
return `Unknown tool: ${name}`;
|
|
391
|
+
}
|
|
392
|
+
return JSON.stringify(result, null, 2);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
return `Error: ${err.message}`;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/chat/session.ts
|
|
399
|
+
var R = "\x1B[0m";
|
|
400
|
+
var B = "\x1B[1m";
|
|
401
|
+
var D = "\x1B[2m";
|
|
402
|
+
var CY = "\x1B[36m";
|
|
403
|
+
var GR = "\x1B[32m";
|
|
404
|
+
var YE = "\x1B[33m";
|
|
405
|
+
var BL = "\x1B[34m";
|
|
406
|
+
var RE = "\x1B[31m";
|
|
407
|
+
var MA = "\x1B[35m";
|
|
408
|
+
var LOGO = `
|
|
409
|
+
${CY}${B} \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557${R}
|
|
410
|
+
${CY}${B} \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D${R}
|
|
411
|
+
${CY}${B} \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ${R}
|
|
412
|
+
${CY}${B} \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D ${R}
|
|
413
|
+
${CY}${B} \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 ${R}
|
|
414
|
+
${CY}${B} \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ${R}
|
|
415
|
+
${D} powered by Lido \u2B21${R}
|
|
416
|
+
`;
|
|
417
|
+
function ln(text = "") {
|
|
418
|
+
process.stdout.write(text + "\n");
|
|
419
|
+
}
|
|
420
|
+
function saveTrade(toolName, args, result) {
|
|
421
|
+
try {
|
|
422
|
+
const tradesDir = path.join(process.cwd(), "trades");
|
|
423
|
+
if (!fs.existsSync(tradesDir)) fs.mkdirSync(tradesDir, { recursive: true });
|
|
424
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
425
|
+
const file = path.join(tradesDir, `${today}.jsonl`);
|
|
426
|
+
const record = JSON.stringify({
|
|
427
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
428
|
+
tool: toolName,
|
|
429
|
+
args,
|
|
430
|
+
result: (() => {
|
|
431
|
+
try {
|
|
432
|
+
return JSON.parse(result);
|
|
433
|
+
} catch {
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
})()
|
|
437
|
+
});
|
|
438
|
+
fs.appendFileSync(file, record + "\n");
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function printBanner(cfg) {
|
|
443
|
+
const modeLabel = cfg.mode === "simulation" ? `${YE}\u25CF SIMULATION${R}` : `${RE}\u25CF LIVE${R}`;
|
|
444
|
+
ln(LOGO);
|
|
445
|
+
ln(` ${modeLabel} ${D}${cfg.network} \xB7 ${cfg.ai?.model ?? ""}${R}`);
|
|
446
|
+
ln(` ${D}type "exit" to quit${R}`);
|
|
447
|
+
ln(` ${D}${"\u2500".repeat(48)}${R}`);
|
|
448
|
+
ln();
|
|
449
|
+
}
|
|
450
|
+
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
451
|
+
"stake_eth",
|
|
452
|
+
"request_withdrawal",
|
|
453
|
+
"claim_withdrawals",
|
|
454
|
+
"wrap_steth",
|
|
455
|
+
"unwrap_wsteth",
|
|
456
|
+
"cast_vote"
|
|
457
|
+
]);
|
|
458
|
+
async function startChatSession(cfg) {
|
|
459
|
+
if (!cfg.ai) {
|
|
460
|
+
ln(`${RE}No AI provider configured. Run: moly setup${R}`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
const { provider, apiKey, model } = cfg.ai;
|
|
464
|
+
const messages = [
|
|
465
|
+
{
|
|
466
|
+
role: "user",
|
|
467
|
+
content: `You are Moly, an AI assistant for Lido Finance on ${cfg.network}. Mode: ${cfg.mode} (${cfg.mode === "simulation" ? "dry-run, nothing broadcast" : "LIVE \u2014 real on-chain transactions"}). Help stake ETH, manage withdrawals, wrap/unwrap tokens, governance. Be concise. For live transactions always confirm with the user first.`
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
role: "assistant",
|
|
471
|
+
content: `Ready. I'm Moly on ${cfg.network} in ${cfg.mode} mode. What would you like to do?`
|
|
472
|
+
}
|
|
473
|
+
];
|
|
474
|
+
printBanner(cfg);
|
|
475
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
476
|
+
const prompt = () => new Promise((resolve, reject) => {
|
|
477
|
+
rl.question(`${B}${BL}you${R} \u203A `, resolve);
|
|
478
|
+
rl.once("close", () => reject(new Error("closed")));
|
|
479
|
+
});
|
|
480
|
+
while (true) {
|
|
481
|
+
let input;
|
|
482
|
+
try {
|
|
483
|
+
input = (await prompt()).trim();
|
|
484
|
+
} catch {
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
if (!input) continue;
|
|
488
|
+
if (input === "exit" || input === "quit") {
|
|
489
|
+
ln(`${D}Goodbye.${R}`);
|
|
490
|
+
rl.close();
|
|
491
|
+
process.exit(0);
|
|
492
|
+
}
|
|
493
|
+
messages.push({ role: "user", content: input });
|
|
494
|
+
try {
|
|
495
|
+
while (true) {
|
|
496
|
+
ln(`${D} \u2699 thinking...${R}`);
|
|
497
|
+
const response = await callAi(provider, apiKey, model, messages, TOOL_DEFS);
|
|
498
|
+
messages.push(response.rawAssistantMessage);
|
|
499
|
+
if (response.toolCalls.length > 0) {
|
|
500
|
+
const toolResults = [];
|
|
501
|
+
for (const tc of response.toolCalls) {
|
|
502
|
+
ln(`${D} \u21B3 ${MA}${tc.name}${R}${D} ${JSON.stringify(tc.args)}${R}`);
|
|
503
|
+
const result = await executeTool(tc.name, tc.args);
|
|
504
|
+
ln(`${D} ${result.slice(0, 300)}${result.length > 300 ? "\u2026" : ""}${R}`);
|
|
505
|
+
if (WRITE_TOOLS.has(tc.name)) {
|
|
506
|
+
saveTrade(tc.name, tc.args, result);
|
|
507
|
+
}
|
|
508
|
+
toolResults.push(makeToolResultMessage(provider, tc.id, tc.name, result));
|
|
509
|
+
}
|
|
510
|
+
messages.push(...toolResults);
|
|
511
|
+
if (response.text) {
|
|
512
|
+
ln();
|
|
513
|
+
ln(`${B}${GR}moly${R} \u203A ${response.text}`);
|
|
514
|
+
ln();
|
|
515
|
+
}
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
if (response.text) {
|
|
519
|
+
ln();
|
|
520
|
+
ln(`${B}${GR}moly${R} \u203A ${response.text}`);
|
|
521
|
+
ln();
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
ln(`${RE}Error: ${err.message}${R}`);
|
|
527
|
+
messages.pop();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
rl.close();
|
|
531
|
+
}
|
|
532
|
+
export {
|
|
533
|
+
startChatSession
|
|
534
|
+
};
|
package/package.json
CHANGED