@t2000/engine 0.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/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/index.d.ts +838 -0
- package/dist/index.js +1901 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1901 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
5
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
6
|
+
|
|
7
|
+
// src/tool.ts
|
|
8
|
+
function buildTool(opts) {
|
|
9
|
+
return {
|
|
10
|
+
name: opts.name,
|
|
11
|
+
description: opts.description,
|
|
12
|
+
inputSchema: opts.inputSchema,
|
|
13
|
+
jsonSchema: opts.jsonSchema,
|
|
14
|
+
call: opts.call,
|
|
15
|
+
isReadOnly: opts.isReadOnly ?? true,
|
|
16
|
+
isConcurrencySafe: opts.isReadOnly ?? true,
|
|
17
|
+
permissionLevel: opts.permissionLevel ?? (opts.isReadOnly === false ? "confirm" : "auto")
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function toolsToDefinitions(tools) {
|
|
21
|
+
return tools.map((t) => ({
|
|
22
|
+
name: t.name,
|
|
23
|
+
description: t.description,
|
|
24
|
+
input_schema: t.jsonSchema
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
function findTool(tools, name) {
|
|
28
|
+
return tools.find((t) => t.name === name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/orchestration.ts
|
|
32
|
+
var TxMutex = class {
|
|
33
|
+
queue = [];
|
|
34
|
+
locked = false;
|
|
35
|
+
async acquire() {
|
|
36
|
+
if (!this.locked) {
|
|
37
|
+
this.locked = true;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
this.queue.push(resolve);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
release() {
|
|
45
|
+
const next = this.queue.shift();
|
|
46
|
+
if (next) {
|
|
47
|
+
next();
|
|
48
|
+
} else {
|
|
49
|
+
this.locked = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
async function* runTools(pending, tools, context, txMutex) {
|
|
54
|
+
const { reads, writes } = partitionToolCalls(pending, tools);
|
|
55
|
+
if (reads.length > 0) {
|
|
56
|
+
const readResults = await Promise.allSettled(
|
|
57
|
+
reads.map(async (call) => {
|
|
58
|
+
const tool = findTool(tools, call.name);
|
|
59
|
+
if (!tool) {
|
|
60
|
+
return { call, result: { data: { error: `Unknown tool: ${call.name}` } }, isError: true };
|
|
61
|
+
}
|
|
62
|
+
const execResult = await executeSingleTool(tool, call, context);
|
|
63
|
+
return { call, result: execResult, isError: execResult.isError };
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
for (const settled of readResults) {
|
|
67
|
+
if (settled.status === "fulfilled") {
|
|
68
|
+
const { call, result, isError } = settled.value;
|
|
69
|
+
yield {
|
|
70
|
+
type: "tool_result",
|
|
71
|
+
toolName: call.name,
|
|
72
|
+
toolUseId: call.id,
|
|
73
|
+
result: result.data,
|
|
74
|
+
isError
|
|
75
|
+
};
|
|
76
|
+
} else {
|
|
77
|
+
const idx = readResults.indexOf(settled);
|
|
78
|
+
const call = reads[idx];
|
|
79
|
+
yield {
|
|
80
|
+
type: "tool_result",
|
|
81
|
+
toolName: call.name,
|
|
82
|
+
toolUseId: call.id,
|
|
83
|
+
result: { error: settled.reason?.message ?? "Tool execution failed" },
|
|
84
|
+
isError: true
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const call of writes) {
|
|
90
|
+
const tool = findTool(tools, call.name);
|
|
91
|
+
if (!tool) {
|
|
92
|
+
yield {
|
|
93
|
+
type: "tool_result",
|
|
94
|
+
toolName: call.name,
|
|
95
|
+
toolUseId: call.id,
|
|
96
|
+
result: { error: `Unknown tool: ${call.name}` },
|
|
97
|
+
isError: true
|
|
98
|
+
};
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
await txMutex.acquire();
|
|
102
|
+
try {
|
|
103
|
+
const result = await executeSingleTool(tool, call, context);
|
|
104
|
+
yield {
|
|
105
|
+
type: "tool_result",
|
|
106
|
+
toolName: call.name,
|
|
107
|
+
toolUseId: call.id,
|
|
108
|
+
result: result.data,
|
|
109
|
+
isError: result.isError
|
|
110
|
+
};
|
|
111
|
+
} catch (err) {
|
|
112
|
+
yield {
|
|
113
|
+
type: "tool_result",
|
|
114
|
+
toolName: call.name,
|
|
115
|
+
toolUseId: call.id,
|
|
116
|
+
result: { error: err instanceof Error ? err.message : "Tool execution failed" },
|
|
117
|
+
isError: true
|
|
118
|
+
};
|
|
119
|
+
} finally {
|
|
120
|
+
txMutex.release();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function partitionToolCalls(pending, tools) {
|
|
125
|
+
const reads = [];
|
|
126
|
+
const writes = [];
|
|
127
|
+
for (const call of pending) {
|
|
128
|
+
const tool = findTool(tools, call.name);
|
|
129
|
+
if (!tool) {
|
|
130
|
+
reads.push(call);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (tool.isReadOnly && tool.isConcurrencySafe) {
|
|
134
|
+
reads.push(call);
|
|
135
|
+
} else {
|
|
136
|
+
writes.push(call);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { reads, writes };
|
|
140
|
+
}
|
|
141
|
+
async function executeSingleTool(tool, call, context) {
|
|
142
|
+
const parsed = tool.inputSchema.safeParse(call.input);
|
|
143
|
+
if (!parsed.success) {
|
|
144
|
+
return {
|
|
145
|
+
data: {
|
|
146
|
+
error: `Invalid input: ${parsed.error.issues.map((i) => i.message).join(", ")}`
|
|
147
|
+
},
|
|
148
|
+
isError: true
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const result = await tool.call(parsed.data, context);
|
|
152
|
+
return { data: result.data, isError: false };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/navi-config.ts
|
|
156
|
+
var NAVI_SERVER_NAME = "navi";
|
|
157
|
+
var NAVI_MCP_URL = "https://open-api.naviprotocol.io/api/mcp";
|
|
158
|
+
var NAVI_MCP_CONFIG = {
|
|
159
|
+
name: NAVI_SERVER_NAME,
|
|
160
|
+
url: NAVI_MCP_URL,
|
|
161
|
+
transport: "streamable-http",
|
|
162
|
+
cacheTtlMs: 3e4,
|
|
163
|
+
readOnly: true
|
|
164
|
+
};
|
|
165
|
+
var NaviTools = {
|
|
166
|
+
GET_POOLS: "navi_get_pools",
|
|
167
|
+
GET_POOL: "navi_get_pool",
|
|
168
|
+
GET_PROTOCOL_STATS: "navi_get_protocol_stats",
|
|
169
|
+
GET_HEALTH_FACTOR: "navi_get_health_factor",
|
|
170
|
+
GET_BORROW_FEE: "navi_get_borrow_fee",
|
|
171
|
+
GET_FEES: "navi_get_fees",
|
|
172
|
+
GET_FLASH_LOAN_ASSETS: "navi_get_flash_loan_assets",
|
|
173
|
+
GET_FLASH_LOAN_ASSET: "navi_get_flash_loan_asset",
|
|
174
|
+
GET_LENDING_REWARDS: "navi_get_lending_rewards",
|
|
175
|
+
GET_AVAILABLE_REWARDS: "navi_get_available_rewards",
|
|
176
|
+
GET_PRICE_FEEDS: "navi_get_price_feeds",
|
|
177
|
+
GET_SWAP_QUOTE: "navi_get_swap_quote",
|
|
178
|
+
GET_BRIDGE_CHAINS: "navi_get_bridge_chains",
|
|
179
|
+
SEARCH_BRIDGE_TOKENS: "navi_search_bridge_tokens",
|
|
180
|
+
GET_BRIDGE_QUOTE: "navi_get_bridge_quote",
|
|
181
|
+
GET_BRIDGE_TX_STATUS: "navi_get_bridge_tx_status",
|
|
182
|
+
GET_BRIDGE_HISTORY: "navi_get_bridge_history",
|
|
183
|
+
GET_DCA_ORDERS: "navi_get_dca_orders",
|
|
184
|
+
GET_DCA_ORDER_DETAILS: "navi_get_dca_order_details",
|
|
185
|
+
LIST_DCA_ORDERS: "navi_list_dca_orders",
|
|
186
|
+
GET_COINS: "navi_get_coins",
|
|
187
|
+
GET_MARKET_CONFIG: "navi_get_market_config",
|
|
188
|
+
GET_POSITIONS: "get_positions",
|
|
189
|
+
GET_TRANSACTION: "sui_get_transaction",
|
|
190
|
+
EXPLAIN_TRANSACTION: "sui_explain_transaction",
|
|
191
|
+
SEARCH_TOKENS: "navi_search_tokens"
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/navi-transforms.ts
|
|
195
|
+
function toNum(v) {
|
|
196
|
+
if (v == null) return 0;
|
|
197
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
198
|
+
return Number.isFinite(n) ? n : 0;
|
|
199
|
+
}
|
|
200
|
+
function transformRates(raw) {
|
|
201
|
+
const pools = Array.isArray(raw) ? raw : [];
|
|
202
|
+
const result = {};
|
|
203
|
+
for (const pool of pools) {
|
|
204
|
+
if (!pool.symbol) continue;
|
|
205
|
+
result[pool.symbol] = {
|
|
206
|
+
saveApy: toNum(pool.supplyApy) / 100,
|
|
207
|
+
borrowApy: toNum(pool.borrowApy) / 100,
|
|
208
|
+
ltv: toNum(pool.ltv),
|
|
209
|
+
price: toNum(pool.price)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
function transformPositions(raw) {
|
|
215
|
+
const data = raw;
|
|
216
|
+
const positions = data?.positions ?? (Array.isArray(raw) ? raw : []);
|
|
217
|
+
return positions.map((p) => ({
|
|
218
|
+
protocol: p.protocol ?? "navi",
|
|
219
|
+
type: p.type?.includes("borrow") ? "borrow" : "supply",
|
|
220
|
+
symbol: p.tokenASymbol ?? "UNKNOWN",
|
|
221
|
+
amount: toNum(p.amountA),
|
|
222
|
+
valueUsd: toNum(p.valueUSD),
|
|
223
|
+
apy: toNum(p.apr) / 100,
|
|
224
|
+
liquidationThreshold: toNum(p.liquidationThreshold)
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
function transformHealthFactor(rawHf, rawPositions) {
|
|
228
|
+
const hf = rawHf;
|
|
229
|
+
const positions = transformPositions(rawPositions);
|
|
230
|
+
const supplied = positions.filter((p) => p.type === "supply").reduce((sum, p) => sum + p.valueUsd, 0);
|
|
231
|
+
const borrowed = positions.filter((p) => p.type === "borrow").reduce((sum, p) => sum + p.valueUsd, 0);
|
|
232
|
+
const supplyPositions = positions.filter((p) => p.type === "supply");
|
|
233
|
+
const weightedLt = supplied > 0 ? supplyPositions.reduce(
|
|
234
|
+
(acc, p) => acc + p.liquidationThreshold * p.valueUsd,
|
|
235
|
+
0
|
|
236
|
+
) / supplied : 0;
|
|
237
|
+
const maxBorrow = supplied * weightedLt - borrowed;
|
|
238
|
+
return {
|
|
239
|
+
healthFactor: toNum(hf?.healthFactor) || (borrowed === 0 ? Infinity : 0),
|
|
240
|
+
supplied,
|
|
241
|
+
borrowed,
|
|
242
|
+
maxBorrow: Math.max(0, maxBorrow),
|
|
243
|
+
liquidationThreshold: weightedLt
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function transformRewards(raw) {
|
|
247
|
+
const data = raw;
|
|
248
|
+
return (data?.summary ?? []).map((s) => ({
|
|
249
|
+
symbol: s.symbol ?? "UNKNOWN",
|
|
250
|
+
totalAmount: toNum(s.totalAmount),
|
|
251
|
+
valueUsd: toNum(s.valueUSD)
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
var STABLECOIN_SYMBOLS = /* @__PURE__ */ new Set([
|
|
255
|
+
"USDC",
|
|
256
|
+
"USDT",
|
|
257
|
+
"wUSDC",
|
|
258
|
+
"wUSDT",
|
|
259
|
+
"FDUSD",
|
|
260
|
+
"AUSD",
|
|
261
|
+
"BUCK",
|
|
262
|
+
"suiUSDe",
|
|
263
|
+
"USDSUI"
|
|
264
|
+
]);
|
|
265
|
+
var GAS_RESERVE_SUI = 0.05;
|
|
266
|
+
function transformBalance(rawCoins, rawPositions, rawRewards, prices) {
|
|
267
|
+
const coins = Array.isArray(rawCoins) ? rawCoins : [];
|
|
268
|
+
const positions = transformPositions(rawPositions);
|
|
269
|
+
const rewards = transformRewards(rawRewards);
|
|
270
|
+
let availableUsd = 0;
|
|
271
|
+
let stablesUsd = 0;
|
|
272
|
+
let gasReserveUsd = 0;
|
|
273
|
+
for (const coin of coins) {
|
|
274
|
+
const symbol = coin.symbol ?? "";
|
|
275
|
+
const decimals = coin.decimals ?? (symbol === "SUI" ? 9 : 6);
|
|
276
|
+
const balance = toNum(coin.totalBalance) / 10 ** decimals;
|
|
277
|
+
const price = prices?.[symbol] ?? (STABLECOIN_SYMBOLS.has(symbol) ? 1 : 0);
|
|
278
|
+
if (symbol === "SUI" || coin.coinType === "0x2::sui::SUI") {
|
|
279
|
+
const reserveAmount = Math.min(balance, GAS_RESERVE_SUI);
|
|
280
|
+
gasReserveUsd = reserveAmount * price;
|
|
281
|
+
availableUsd += (balance - reserveAmount) * price;
|
|
282
|
+
} else {
|
|
283
|
+
availableUsd += balance * price;
|
|
284
|
+
if (STABLECOIN_SYMBOLS.has(symbol)) {
|
|
285
|
+
stablesUsd += balance * price;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const savings = positions.filter((p) => p.type === "supply").reduce((sum, p) => sum + p.valueUsd, 0);
|
|
290
|
+
const debt = positions.filter((p) => p.type === "borrow").reduce((sum, p) => sum + p.valueUsd, 0);
|
|
291
|
+
const pendingRewardsUsd = rewards.reduce((sum, r) => sum + r.valueUsd, 0);
|
|
292
|
+
return {
|
|
293
|
+
available: availableUsd,
|
|
294
|
+
savings,
|
|
295
|
+
debt,
|
|
296
|
+
pendingRewards: pendingRewardsUsd,
|
|
297
|
+
gasReserve: gasReserveUsd,
|
|
298
|
+
total: availableUsd + savings + gasReserveUsd + pendingRewardsUsd - debt,
|
|
299
|
+
stables: stablesUsd
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function transformSavings(rawPositions, rawPools) {
|
|
303
|
+
const positions = transformPositions(rawPositions);
|
|
304
|
+
const rates = transformRates(rawPools);
|
|
305
|
+
const supplyPositions = positions.filter((p) => p.type === "supply");
|
|
306
|
+
const supplied = supplyPositions.reduce((sum, p) => sum + p.valueUsd, 0);
|
|
307
|
+
const weightedApy = supplied > 0 ? supplyPositions.reduce(
|
|
308
|
+
(acc, p) => acc + (rates[p.symbol]?.saveApy ?? p.apy) * p.valueUsd,
|
|
309
|
+
0
|
|
310
|
+
) / supplied : 0;
|
|
311
|
+
const dailyEarning = supplied * weightedApy / 365;
|
|
312
|
+
const projectedMonthly = dailyEarning * 30;
|
|
313
|
+
return {
|
|
314
|
+
positions,
|
|
315
|
+
earnings: {
|
|
316
|
+
totalYieldEarned: 0,
|
|
317
|
+
// not available from MCP reads alone
|
|
318
|
+
currentApy: weightedApy,
|
|
319
|
+
dailyEarning,
|
|
320
|
+
supplied
|
|
321
|
+
},
|
|
322
|
+
fundStatus: {
|
|
323
|
+
supplied,
|
|
324
|
+
apy: weightedApy,
|
|
325
|
+
earnedToday: dailyEarning,
|
|
326
|
+
earnedAllTime: 0,
|
|
327
|
+
// not available from MCP reads alone
|
|
328
|
+
projectedMonthly
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function extractMcpText(content) {
|
|
333
|
+
return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
334
|
+
}
|
|
335
|
+
function parseMcpJson(content) {
|
|
336
|
+
const text = extractMcpText(content);
|
|
337
|
+
try {
|
|
338
|
+
return JSON.parse(text);
|
|
339
|
+
} catch {
|
|
340
|
+
return text;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/navi-reads.ts
|
|
345
|
+
function sn(opts) {
|
|
346
|
+
return opts?.serverName ?? NAVI_SERVER_NAME;
|
|
347
|
+
}
|
|
348
|
+
async function callNavi(manager, tool, args = {}, opts) {
|
|
349
|
+
const result = await manager.callTool(sn(opts), tool, args);
|
|
350
|
+
if (result.isError) {
|
|
351
|
+
const msg = result.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join(" ");
|
|
352
|
+
throw new Error(`NAVI MCP error (${tool}): ${msg || "unknown error"}`);
|
|
353
|
+
}
|
|
354
|
+
return parseMcpJson(result.content);
|
|
355
|
+
}
|
|
356
|
+
async function fetchRates(manager, opts) {
|
|
357
|
+
const pools = await callNavi(manager, NaviTools.GET_POOLS, {}, opts);
|
|
358
|
+
return transformRates(pools);
|
|
359
|
+
}
|
|
360
|
+
async function fetchHealthFactor(manager, address, opts) {
|
|
361
|
+
const [hfRaw, posRaw] = await Promise.all([
|
|
362
|
+
callNavi(manager, NaviTools.GET_HEALTH_FACTOR, { address }, opts),
|
|
363
|
+
callNavi(manager, NaviTools.GET_POSITIONS, {
|
|
364
|
+
address,
|
|
365
|
+
protocols: "navi",
|
|
366
|
+
format: "json"
|
|
367
|
+
}, opts)
|
|
368
|
+
]);
|
|
369
|
+
return transformHealthFactor(hfRaw, posRaw);
|
|
370
|
+
}
|
|
371
|
+
async function fetchBalance(manager, address, opts) {
|
|
372
|
+
const [coins, positions, rewards, pools] = await Promise.all([
|
|
373
|
+
callNavi(manager, NaviTools.GET_COINS, { address }, opts),
|
|
374
|
+
callNavi(manager, NaviTools.GET_POSITIONS, {
|
|
375
|
+
address,
|
|
376
|
+
protocols: "navi",
|
|
377
|
+
format: "json"
|
|
378
|
+
}, opts),
|
|
379
|
+
callNavi(manager, NaviTools.GET_AVAILABLE_REWARDS, { address }, opts),
|
|
380
|
+
callNavi(manager, NaviTools.GET_POOLS, {}, opts)
|
|
381
|
+
]);
|
|
382
|
+
const rates = transformRates(pools);
|
|
383
|
+
const prices = {};
|
|
384
|
+
for (const [symbol, rate] of Object.entries(rates)) {
|
|
385
|
+
prices[symbol] = rate.price;
|
|
386
|
+
}
|
|
387
|
+
return transformBalance(coins, positions, rewards, prices);
|
|
388
|
+
}
|
|
389
|
+
async function fetchSavings(manager, address, opts) {
|
|
390
|
+
const [positions, pools] = await Promise.all([
|
|
391
|
+
callNavi(manager, NaviTools.GET_POSITIONS, {
|
|
392
|
+
address,
|
|
393
|
+
protocols: "navi",
|
|
394
|
+
format: "json"
|
|
395
|
+
}, opts),
|
|
396
|
+
callNavi(manager, NaviTools.GET_POOLS, {}, opts)
|
|
397
|
+
]);
|
|
398
|
+
return transformSavings(positions, pools);
|
|
399
|
+
}
|
|
400
|
+
async function fetchPositions(manager, address, opts) {
|
|
401
|
+
const raw = await callNavi(
|
|
402
|
+
manager,
|
|
403
|
+
NaviTools.GET_POSITIONS,
|
|
404
|
+
{ address, protocols: opts?.protocols ?? "navi", format: "json" },
|
|
405
|
+
opts
|
|
406
|
+
);
|
|
407
|
+
return transformPositions(raw);
|
|
408
|
+
}
|
|
409
|
+
async function fetchAvailableRewards(manager, address, opts) {
|
|
410
|
+
const raw = await callNavi(
|
|
411
|
+
manager,
|
|
412
|
+
NaviTools.GET_AVAILABLE_REWARDS,
|
|
413
|
+
{ address },
|
|
414
|
+
opts
|
|
415
|
+
);
|
|
416
|
+
return transformRewards(raw);
|
|
417
|
+
}
|
|
418
|
+
async function fetchProtocolStats(manager, opts) {
|
|
419
|
+
const raw = await callNavi(manager, NaviTools.GET_PROTOCOL_STATS, {}, opts);
|
|
420
|
+
return {
|
|
421
|
+
tvl: raw?.tvl ?? 0,
|
|
422
|
+
totalBorrowUsd: raw?.totalBorrowUsd ?? 0,
|
|
423
|
+
utilization: raw?.averageUtilization ?? 0,
|
|
424
|
+
maxApy: raw?.maxApy ?? 0,
|
|
425
|
+
totalUsers: raw?.userAmount ?? 0
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/tools/utils.ts
|
|
430
|
+
function requireAgent(context) {
|
|
431
|
+
if (!context.agent) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
"Tool requires a T2000 agent instance \u2014 pass `agent` in EngineConfig"
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return context.agent;
|
|
437
|
+
}
|
|
438
|
+
function hasNaviMcp(context) {
|
|
439
|
+
if (!context.mcpManager || !context.walletAddress) return false;
|
|
440
|
+
const mgr = context.mcpManager;
|
|
441
|
+
return mgr.isConnected(NAVI_SERVER_NAME);
|
|
442
|
+
}
|
|
443
|
+
function getMcpManager(context) {
|
|
444
|
+
return context.mcpManager;
|
|
445
|
+
}
|
|
446
|
+
function getWalletAddress(context) {
|
|
447
|
+
return context.walletAddress;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/tools/balance.ts
|
|
451
|
+
var balanceCheckTool = buildTool({
|
|
452
|
+
name: "balance_check",
|
|
453
|
+
description: "Get the user's full balance breakdown: available USDC, savings deposits, outstanding debt, pending rewards, gas reserve, and total net worth.",
|
|
454
|
+
inputSchema: z.object({}),
|
|
455
|
+
jsonSchema: { type: "object", properties: {}, required: [] },
|
|
456
|
+
isReadOnly: true,
|
|
457
|
+
async call(_input, context) {
|
|
458
|
+
if (hasNaviMcp(context)) {
|
|
459
|
+
const bal = await fetchBalance(
|
|
460
|
+
getMcpManager(context),
|
|
461
|
+
getWalletAddress(context)
|
|
462
|
+
);
|
|
463
|
+
return {
|
|
464
|
+
data: bal,
|
|
465
|
+
displayText: `Balance: $${bal.total.toFixed(2)} (Available: $${bal.available.toFixed(2)}, Savings: $${bal.savings.toFixed(2)})`
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const agent = requireAgent(context);
|
|
469
|
+
const balance = await agent.balance();
|
|
470
|
+
const gasReserveUsd = typeof balance.gasReserve === "number" ? balance.gasReserve : balance.gasReserve.usdEquiv ?? 0;
|
|
471
|
+
const stablesTotal = typeof balance.stables === "number" ? balance.stables : Object.values(balance.stables).reduce((a, b) => a + b, 0);
|
|
472
|
+
return {
|
|
473
|
+
data: {
|
|
474
|
+
available: balance.available,
|
|
475
|
+
savings: balance.savings,
|
|
476
|
+
debt: balance.debt,
|
|
477
|
+
pendingRewards: balance.pendingRewards,
|
|
478
|
+
gasReserve: gasReserveUsd,
|
|
479
|
+
total: balance.total,
|
|
480
|
+
stables: stablesTotal
|
|
481
|
+
},
|
|
482
|
+
displayText: `Balance: $${balance.total.toFixed(2)} (Available: $${balance.available.toFixed(2)}, Savings: $${balance.savings.toFixed(2)})`
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
var savingsInfoTool = buildTool({
|
|
487
|
+
name: "savings_info",
|
|
488
|
+
description: "Get detailed savings positions and earnings: current deposits by protocol, APY, total yield earned, daily earning rate, and projected monthly returns.",
|
|
489
|
+
inputSchema: z.object({}),
|
|
490
|
+
jsonSchema: { type: "object", properties: {}, required: [] },
|
|
491
|
+
isReadOnly: true,
|
|
492
|
+
async call(_input, context) {
|
|
493
|
+
if (hasNaviMcp(context)) {
|
|
494
|
+
const savings = await fetchSavings(
|
|
495
|
+
getMcpManager(context),
|
|
496
|
+
getWalletAddress(context)
|
|
497
|
+
);
|
|
498
|
+
return { data: savings };
|
|
499
|
+
}
|
|
500
|
+
const agent = requireAgent(context);
|
|
501
|
+
const [posResult, earnings, fundStatus] = await Promise.all([
|
|
502
|
+
agent.positions(),
|
|
503
|
+
agent.earnings(),
|
|
504
|
+
agent.fundStatus()
|
|
505
|
+
]);
|
|
506
|
+
const positions = (posResult.positions ?? []).map((p) => ({
|
|
507
|
+
protocol: p.protocol ?? "navi",
|
|
508
|
+
type: p.type === "borrow" ? "borrow" : "supply",
|
|
509
|
+
symbol: p.asset ?? p.symbol ?? "UNKNOWN",
|
|
510
|
+
amount: p.amount ?? 0,
|
|
511
|
+
valueUsd: p.amountUsd ?? p.valueUsd ?? 0,
|
|
512
|
+
apy: p.apy ?? 0,
|
|
513
|
+
liquidationThreshold: p.liquidationThreshold ?? 0
|
|
514
|
+
}));
|
|
515
|
+
return {
|
|
516
|
+
data: {
|
|
517
|
+
positions,
|
|
518
|
+
earnings: {
|
|
519
|
+
totalYieldEarned: earnings.totalYieldEarned,
|
|
520
|
+
currentApy: earnings.currentApy,
|
|
521
|
+
dailyEarning: earnings.dailyEarning,
|
|
522
|
+
supplied: earnings.supplied
|
|
523
|
+
},
|
|
524
|
+
fundStatus: {
|
|
525
|
+
supplied: fundStatus.supplied,
|
|
526
|
+
apy: fundStatus.apy,
|
|
527
|
+
earnedToday: fundStatus.earnedToday,
|
|
528
|
+
earnedAllTime: fundStatus.earnedAllTime,
|
|
529
|
+
projectedMonthly: fundStatus.projectedMonthly
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
function hfStatus(hf) {
|
|
536
|
+
if (hf >= 2) return "healthy";
|
|
537
|
+
if (hf >= 1.5) return "moderate";
|
|
538
|
+
if (hf >= 1.2) return "warning";
|
|
539
|
+
return "critical";
|
|
540
|
+
}
|
|
541
|
+
var healthCheckTool = buildTool({
|
|
542
|
+
name: "health_check",
|
|
543
|
+
description: "Check the lending health factor: current HF ratio, total supplied collateral, total borrowed, max additional borrow capacity, and liquidation threshold. HF < 1.5 is risky, < 1.2 is critical.",
|
|
544
|
+
inputSchema: z.object({}),
|
|
545
|
+
jsonSchema: { type: "object", properties: {}, required: [] },
|
|
546
|
+
isReadOnly: true,
|
|
547
|
+
async call(_input, context) {
|
|
548
|
+
if (hasNaviMcp(context)) {
|
|
549
|
+
const hf2 = await fetchHealthFactor(
|
|
550
|
+
getMcpManager(context),
|
|
551
|
+
getWalletAddress(context)
|
|
552
|
+
);
|
|
553
|
+
const status2 = hfStatus(hf2.healthFactor);
|
|
554
|
+
const displayHf = Number.isFinite(hf2.healthFactor) ? hf2.healthFactor.toFixed(2) : "\u221E";
|
|
555
|
+
return {
|
|
556
|
+
data: { ...hf2, status: status2 },
|
|
557
|
+
displayText: `Health Factor: ${displayHf} (${status2})`
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const agent = requireAgent(context);
|
|
561
|
+
const hf = await agent.healthFactor();
|
|
562
|
+
const status = hfStatus(hf.healthFactor);
|
|
563
|
+
return {
|
|
564
|
+
data: {
|
|
565
|
+
healthFactor: hf.healthFactor,
|
|
566
|
+
supplied: hf.supplied,
|
|
567
|
+
borrowed: hf.borrowed,
|
|
568
|
+
maxBorrow: hf.maxBorrow,
|
|
569
|
+
liquidationThreshold: hf.liquidationThreshold,
|
|
570
|
+
status
|
|
571
|
+
},
|
|
572
|
+
displayText: `Health Factor: ${hf.healthFactor.toFixed(2)} (${status})`
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
function formatRatesSummary(rates) {
|
|
577
|
+
return Object.entries(rates).map(([asset, r]) => `${asset}: Save ${(r.saveApy * 100).toFixed(2)}% / Borrow ${(r.borrowApy * 100).toFixed(2)}%`).join(", ");
|
|
578
|
+
}
|
|
579
|
+
var ratesInfoTool = buildTool({
|
|
580
|
+
name: "rates_info",
|
|
581
|
+
description: "Get current lending/borrowing interest rates (APY) for all supported assets. Returns save APY and borrow APY per asset.",
|
|
582
|
+
inputSchema: z.object({}),
|
|
583
|
+
jsonSchema: { type: "object", properties: {}, required: [] },
|
|
584
|
+
isReadOnly: true,
|
|
585
|
+
async call(_input, context) {
|
|
586
|
+
if (hasNaviMcp(context)) {
|
|
587
|
+
const rates2 = await fetchRates(getMcpManager(context));
|
|
588
|
+
return {
|
|
589
|
+
data: rates2,
|
|
590
|
+
displayText: formatRatesSummary(rates2)
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const agent = requireAgent(context);
|
|
594
|
+
const rates = await agent.rates();
|
|
595
|
+
return {
|
|
596
|
+
data: rates,
|
|
597
|
+
displayText: formatRatesSummary(rates)
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
var transactionHistoryTool = buildTool({
|
|
602
|
+
name: "transaction_history",
|
|
603
|
+
description: "Retrieve recent transaction history: past sends, saves, withdrawals, borrows, repayments, and rewards claims. Optionally limit the number of results.",
|
|
604
|
+
inputSchema: z.object({
|
|
605
|
+
limit: z.number().int().min(1).max(50).optional()
|
|
606
|
+
}),
|
|
607
|
+
jsonSchema: {
|
|
608
|
+
type: "object",
|
|
609
|
+
properties: {
|
|
610
|
+
limit: {
|
|
611
|
+
type: "number",
|
|
612
|
+
description: "Maximum number of transactions to return (1-50, default 10)"
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
isReadOnly: true,
|
|
617
|
+
async call(input, context) {
|
|
618
|
+
const agent = requireAgent(context);
|
|
619
|
+
const records = await agent.history({ limit: input.limit ?? 10 });
|
|
620
|
+
return {
|
|
621
|
+
data: {
|
|
622
|
+
transactions: records,
|
|
623
|
+
count: records.length
|
|
624
|
+
},
|
|
625
|
+
displayText: `${records.length} recent transaction(s)`
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
var saveDepositTool = buildTool({
|
|
630
|
+
name: "save_deposit",
|
|
631
|
+
description: 'Deposit USDC into savings to earn yield. Specify an amount in USD or "all" to save everything except a $1 gas reserve. Returns tx hash, APY, fee, and updated savings balance.',
|
|
632
|
+
inputSchema: z.object({
|
|
633
|
+
amount: z.union([z.number().positive(), z.literal("all")])
|
|
634
|
+
}),
|
|
635
|
+
jsonSchema: {
|
|
636
|
+
type: "object",
|
|
637
|
+
properties: {
|
|
638
|
+
amount: {
|
|
639
|
+
description: 'Amount in USD to save, or "all" for maximum deposit'
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
required: ["amount"]
|
|
643
|
+
},
|
|
644
|
+
isReadOnly: false,
|
|
645
|
+
permissionLevel: "confirm",
|
|
646
|
+
async call(input, context) {
|
|
647
|
+
const agent = requireAgent(context);
|
|
648
|
+
const result = await agent.save({ amount: input.amount });
|
|
649
|
+
return {
|
|
650
|
+
data: {
|
|
651
|
+
success: result.success,
|
|
652
|
+
tx: result.tx,
|
|
653
|
+
amount: result.amount,
|
|
654
|
+
apy: result.apy,
|
|
655
|
+
fee: result.fee,
|
|
656
|
+
gasCost: result.gasCost,
|
|
657
|
+
savingsBalance: result.savingsBalance
|
|
658
|
+
},
|
|
659
|
+
displayText: `Saved $${result.amount.toFixed(2)} at ${(result.apy * 100).toFixed(2)}% APY (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
var withdrawTool = buildTool({
|
|
664
|
+
name: "withdraw",
|
|
665
|
+
description: 'Withdraw USDC from savings back to wallet. Specify an amount in USD or "all" to withdraw everything safely. Checks health factor to prevent liquidation if there is outstanding debt.',
|
|
666
|
+
inputSchema: z.object({
|
|
667
|
+
amount: z.union([z.number().positive(), z.literal("all")])
|
|
668
|
+
}),
|
|
669
|
+
jsonSchema: {
|
|
670
|
+
type: "object",
|
|
671
|
+
properties: {
|
|
672
|
+
amount: {
|
|
673
|
+
description: 'Amount in USD to withdraw, or "all" for full withdrawal'
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
required: ["amount"]
|
|
677
|
+
},
|
|
678
|
+
isReadOnly: false,
|
|
679
|
+
permissionLevel: "confirm",
|
|
680
|
+
async call(input, context) {
|
|
681
|
+
const agent = requireAgent(context);
|
|
682
|
+
const result = await agent.withdraw({ amount: input.amount });
|
|
683
|
+
return {
|
|
684
|
+
data: {
|
|
685
|
+
success: result.success,
|
|
686
|
+
tx: result.tx,
|
|
687
|
+
amount: result.amount,
|
|
688
|
+
gasCost: result.gasCost
|
|
689
|
+
},
|
|
690
|
+
displayText: `Withdrew $${result.amount.toFixed(2)} (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
var sendTransferTool = buildTool({
|
|
695
|
+
name: "send_transfer",
|
|
696
|
+
description: "Send USDC to another Sui address or contact name. Validates the address, checks balance, and executes the on-chain transfer. Returns tx hash, gas cost, and updated balance.",
|
|
697
|
+
inputSchema: z.object({
|
|
698
|
+
to: z.string().min(1),
|
|
699
|
+
amount: z.number().positive()
|
|
700
|
+
}),
|
|
701
|
+
jsonSchema: {
|
|
702
|
+
type: "object",
|
|
703
|
+
properties: {
|
|
704
|
+
to: {
|
|
705
|
+
type: "string",
|
|
706
|
+
description: "Sui address (0x\u2026) or saved contact name"
|
|
707
|
+
},
|
|
708
|
+
amount: {
|
|
709
|
+
type: "number",
|
|
710
|
+
description: "Amount in USD to send"
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
required: ["to", "amount"]
|
|
714
|
+
},
|
|
715
|
+
isReadOnly: false,
|
|
716
|
+
permissionLevel: "confirm",
|
|
717
|
+
async call(input, context) {
|
|
718
|
+
const agent = requireAgent(context);
|
|
719
|
+
const result = await agent.send({ to: input.to, amount: input.amount });
|
|
720
|
+
return {
|
|
721
|
+
data: {
|
|
722
|
+
success: result.success,
|
|
723
|
+
tx: result.tx,
|
|
724
|
+
amount: result.amount,
|
|
725
|
+
to: result.to,
|
|
726
|
+
contactName: result.contactName,
|
|
727
|
+
gasCost: result.gasCost,
|
|
728
|
+
gasMethod: result.gasMethod,
|
|
729
|
+
balance: result.balance
|
|
730
|
+
},
|
|
731
|
+
displayText: `Sent $${result.amount.toFixed(2)} to ${result.contactName ?? result.to.slice(0, 10)}\u2026 (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
var borrowTool = buildTool({
|
|
736
|
+
name: "borrow",
|
|
737
|
+
description: "Borrow USDC against savings collateral. Requires existing savings deposits. Checks max safe borrow and health factor. Returns tx hash, fee, and post-borrow health factor.",
|
|
738
|
+
inputSchema: z.object({
|
|
739
|
+
amount: z.number().positive()
|
|
740
|
+
}),
|
|
741
|
+
jsonSchema: {
|
|
742
|
+
type: "object",
|
|
743
|
+
properties: {
|
|
744
|
+
amount: {
|
|
745
|
+
type: "number",
|
|
746
|
+
description: "Amount in USD to borrow"
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
required: ["amount"]
|
|
750
|
+
},
|
|
751
|
+
isReadOnly: false,
|
|
752
|
+
permissionLevel: "confirm",
|
|
753
|
+
async call(input, context) {
|
|
754
|
+
const agent = requireAgent(context);
|
|
755
|
+
const result = await agent.borrow({ amount: input.amount });
|
|
756
|
+
return {
|
|
757
|
+
data: {
|
|
758
|
+
success: result.success,
|
|
759
|
+
tx: result.tx,
|
|
760
|
+
amount: result.amount,
|
|
761
|
+
fee: result.fee,
|
|
762
|
+
healthFactor: result.healthFactor,
|
|
763
|
+
gasCost: result.gasCost
|
|
764
|
+
},
|
|
765
|
+
displayText: `Borrowed $${result.amount.toFixed(2)} \u2014 HF: ${result.healthFactor.toFixed(2)} (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
var repayDebtTool = buildTool({
|
|
770
|
+
name: "repay_debt",
|
|
771
|
+
description: 'Repay outstanding USDC debt. Specify an amount or "all" to repay everything. Prioritises the highest-APY borrow first. Returns tx hash, amount repaid, and remaining debt.',
|
|
772
|
+
inputSchema: z.object({
|
|
773
|
+
amount: z.union([z.number().positive(), z.literal("all")])
|
|
774
|
+
}),
|
|
775
|
+
jsonSchema: {
|
|
776
|
+
type: "object",
|
|
777
|
+
properties: {
|
|
778
|
+
amount: {
|
|
779
|
+
description: 'Amount in USD to repay, or "all" to repay everything'
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
required: ["amount"]
|
|
783
|
+
},
|
|
784
|
+
isReadOnly: false,
|
|
785
|
+
permissionLevel: "confirm",
|
|
786
|
+
async call(input, context) {
|
|
787
|
+
const agent = requireAgent(context);
|
|
788
|
+
const result = await agent.repay({ amount: input.amount });
|
|
789
|
+
return {
|
|
790
|
+
data: {
|
|
791
|
+
success: result.success,
|
|
792
|
+
tx: result.tx,
|
|
793
|
+
amount: result.amount,
|
|
794
|
+
remainingDebt: result.remainingDebt,
|
|
795
|
+
gasCost: result.gasCost
|
|
796
|
+
},
|
|
797
|
+
displayText: `Repaid $${result.amount.toFixed(2)} \u2014 remaining debt: $${result.remainingDebt.toFixed(2)} (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
var claimRewardsTool = buildTool({
|
|
802
|
+
name: "claim_rewards",
|
|
803
|
+
description: "Claim all pending protocol rewards across lending adapters. Returns claimed reward details and total USD value.",
|
|
804
|
+
inputSchema: z.object({}),
|
|
805
|
+
jsonSchema: { type: "object", properties: {}, required: [] },
|
|
806
|
+
isReadOnly: false,
|
|
807
|
+
permissionLevel: "confirm",
|
|
808
|
+
async call(_input, context) {
|
|
809
|
+
const agent = requireAgent(context);
|
|
810
|
+
const result = await agent.claimRewards();
|
|
811
|
+
return {
|
|
812
|
+
data: {
|
|
813
|
+
success: result.success,
|
|
814
|
+
tx: result.tx || null,
|
|
815
|
+
rewards: result.rewards,
|
|
816
|
+
totalValueUsd: result.totalValueUsd,
|
|
817
|
+
gasCost: result.gasCost
|
|
818
|
+
},
|
|
819
|
+
displayText: result.rewards.length === 0 ? "No pending rewards to claim." : `Claimed $${result.totalValueUsd.toFixed(2)} in rewards (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
var payApiTool = buildTool({
|
|
824
|
+
name: "pay_api",
|
|
825
|
+
description: "Access a paid API endpoint using on-chain micropayments (MPP). Sends the request, handles payment automatically, and returns the API response body. Use for accessing premium data services.",
|
|
826
|
+
inputSchema: z.object({
|
|
827
|
+
url: z.string().url(),
|
|
828
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE"]).optional(),
|
|
829
|
+
body: z.string().optional(),
|
|
830
|
+
headers: z.record(z.string()).optional(),
|
|
831
|
+
maxPrice: z.number().positive().optional()
|
|
832
|
+
}),
|
|
833
|
+
jsonSchema: {
|
|
834
|
+
type: "object",
|
|
835
|
+
properties: {
|
|
836
|
+
url: { type: "string", description: "API endpoint URL" },
|
|
837
|
+
method: { type: "string", description: "HTTP method (default GET)" },
|
|
838
|
+
body: { type: "string", description: "Request body (for POST/PUT)" },
|
|
839
|
+
headers: { type: "object", description: "Additional HTTP headers" },
|
|
840
|
+
maxPrice: { type: "number", description: "Maximum price in USD willing to pay" }
|
|
841
|
+
},
|
|
842
|
+
required: ["url"]
|
|
843
|
+
},
|
|
844
|
+
isReadOnly: false,
|
|
845
|
+
permissionLevel: "confirm",
|
|
846
|
+
async call(input, context) {
|
|
847
|
+
const agent = requireAgent(context);
|
|
848
|
+
const result = await agent.pay({
|
|
849
|
+
url: input.url,
|
|
850
|
+
method: input.method,
|
|
851
|
+
body: input.body,
|
|
852
|
+
headers: input.headers,
|
|
853
|
+
maxPrice: input.maxPrice
|
|
854
|
+
});
|
|
855
|
+
return {
|
|
856
|
+
data: {
|
|
857
|
+
status: result.status,
|
|
858
|
+
body: result.body,
|
|
859
|
+
paid: result.paid,
|
|
860
|
+
cost: result.cost,
|
|
861
|
+
receipt: result.receipt
|
|
862
|
+
},
|
|
863
|
+
displayText: result.paid ? `API call completed \u2014 paid $${result.cost?.toFixed(4) ?? "?"} (status: ${result.status})` : `API call completed \u2014 free (status: ${result.status})`
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
// src/tools/index.ts
|
|
869
|
+
var READ_TOOLS = [
|
|
870
|
+
balanceCheckTool,
|
|
871
|
+
savingsInfoTool,
|
|
872
|
+
healthCheckTool,
|
|
873
|
+
ratesInfoTool,
|
|
874
|
+
transactionHistoryTool
|
|
875
|
+
];
|
|
876
|
+
var WRITE_TOOLS = [
|
|
877
|
+
saveDepositTool,
|
|
878
|
+
withdrawTool,
|
|
879
|
+
sendTransferTool,
|
|
880
|
+
borrowTool,
|
|
881
|
+
repayDebtTool,
|
|
882
|
+
claimRewardsTool,
|
|
883
|
+
payApiTool
|
|
884
|
+
];
|
|
885
|
+
function getDefaultTools() {
|
|
886
|
+
return [...READ_TOOLS, ...WRITE_TOOLS];
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/prompt.ts
|
|
890
|
+
var DEFAULT_SYSTEM_PROMPT = `You are Audric, a financial agent operating on the Sui blockchain. You help users manage their USDC through savings, payments, transfers, and credit.
|
|
891
|
+
|
|
892
|
+
## Capabilities
|
|
893
|
+
- Check balances, savings positions, health factors, and interest rates
|
|
894
|
+
- Execute deposits, withdrawals, transfers, borrows, and repayments
|
|
895
|
+
- Access API services via micropayments (MPP)
|
|
896
|
+
- Track transaction history and earnings
|
|
897
|
+
|
|
898
|
+
## Guidelines
|
|
899
|
+
|
|
900
|
+
### Before Acting
|
|
901
|
+
- Always check the user's balance before suggesting financial actions
|
|
902
|
+
- Show real numbers from tool results \u2014 never fabricate rates, amounts, or balances
|
|
903
|
+
- For transactions that move funds, explain what will happen and confirm intent
|
|
904
|
+
|
|
905
|
+
### Tool Usage
|
|
906
|
+
- Use multiple read-only tools in parallel when you need several data points
|
|
907
|
+
- Present amounts as currency ($1,234.56) and rates as percentages (4.86% APY)
|
|
908
|
+
- If a tool errors, explain the issue clearly and suggest alternatives
|
|
909
|
+
|
|
910
|
+
### Communication Style
|
|
911
|
+
- Be concise and direct \u2014 users want financial data, not filler
|
|
912
|
+
- Lead with numbers and results, follow with context
|
|
913
|
+
- Use short sentences. Avoid hedging language.
|
|
914
|
+
- When presenting positions or balances, use a structured format
|
|
915
|
+
|
|
916
|
+
### Safety
|
|
917
|
+
- Never encourage risky financial behavior
|
|
918
|
+
- Warn when health factor drops below 1.5
|
|
919
|
+
- Remind users of gas costs for on-chain transactions
|
|
920
|
+
- All amounts are in USDC unless explicitly stated otherwise`;
|
|
921
|
+
|
|
922
|
+
// src/cost.ts
|
|
923
|
+
var DEFAULT_INPUT_COST = 3 / 1e6;
|
|
924
|
+
var DEFAULT_OUTPUT_COST = 15 / 1e6;
|
|
925
|
+
var CACHE_WRITE_MULTIPLIER = 1.25;
|
|
926
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
927
|
+
var CostTracker = class {
|
|
928
|
+
inputTokens = 0;
|
|
929
|
+
outputTokens = 0;
|
|
930
|
+
cacheReadTokens = 0;
|
|
931
|
+
cacheWriteTokens = 0;
|
|
932
|
+
budgetLimitUsd;
|
|
933
|
+
inputCost;
|
|
934
|
+
outputCost;
|
|
935
|
+
constructor(config = {}) {
|
|
936
|
+
this.budgetLimitUsd = config.budgetLimitUsd ?? null;
|
|
937
|
+
this.inputCost = config.inputCostPerToken ?? DEFAULT_INPUT_COST;
|
|
938
|
+
this.outputCost = config.outputCostPerToken ?? DEFAULT_OUTPUT_COST;
|
|
939
|
+
}
|
|
940
|
+
track(inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens) {
|
|
941
|
+
this.inputTokens += inputTokens;
|
|
942
|
+
this.outputTokens += outputTokens;
|
|
943
|
+
this.cacheReadTokens += cacheReadTokens ?? 0;
|
|
944
|
+
this.cacheWriteTokens += cacheWriteTokens ?? 0;
|
|
945
|
+
}
|
|
946
|
+
getSnapshot() {
|
|
947
|
+
const totalTokens = this.inputTokens + this.outputTokens + this.cacheReadTokens + this.cacheWriteTokens;
|
|
948
|
+
const estimatedCostUsd = this.inputTokens * this.inputCost + this.outputTokens * this.outputCost + this.cacheReadTokens * this.inputCost * CACHE_READ_MULTIPLIER + this.cacheWriteTokens * this.inputCost * CACHE_WRITE_MULTIPLIER;
|
|
949
|
+
return {
|
|
950
|
+
inputTokens: this.inputTokens,
|
|
951
|
+
outputTokens: this.outputTokens,
|
|
952
|
+
cacheReadTokens: this.cacheReadTokens,
|
|
953
|
+
cacheWriteTokens: this.cacheWriteTokens,
|
|
954
|
+
totalTokens,
|
|
955
|
+
estimatedCostUsd
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
isOverBudget() {
|
|
959
|
+
if (this.budgetLimitUsd === null) return false;
|
|
960
|
+
return this.getSnapshot().estimatedCostUsd >= this.budgetLimitUsd;
|
|
961
|
+
}
|
|
962
|
+
getRemainingBudgetUsd() {
|
|
963
|
+
if (this.budgetLimitUsd === null) return null;
|
|
964
|
+
return Math.max(0, this.budgetLimitUsd - this.getSnapshot().estimatedCostUsd);
|
|
965
|
+
}
|
|
966
|
+
reset() {
|
|
967
|
+
this.inputTokens = 0;
|
|
968
|
+
this.outputTokens = 0;
|
|
969
|
+
this.cacheReadTokens = 0;
|
|
970
|
+
this.cacheWriteTokens = 0;
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
// src/engine.ts
|
|
975
|
+
var DEFAULT_MAX_TURNS = 10;
|
|
976
|
+
var DEFAULT_MAX_TOKENS = 4096;
|
|
977
|
+
var QueryEngine = class {
|
|
978
|
+
provider;
|
|
979
|
+
tools;
|
|
980
|
+
systemPrompt;
|
|
981
|
+
model;
|
|
982
|
+
maxTurns;
|
|
983
|
+
maxTokens;
|
|
984
|
+
agent;
|
|
985
|
+
mcpManager;
|
|
986
|
+
walletAddress;
|
|
987
|
+
txMutex = new TxMutex();
|
|
988
|
+
costTracker;
|
|
989
|
+
messages = [];
|
|
990
|
+
abortController = null;
|
|
991
|
+
constructor(config) {
|
|
992
|
+
this.provider = config.provider;
|
|
993
|
+
this.agent = config.agent;
|
|
994
|
+
this.mcpManager = config.mcpManager;
|
|
995
|
+
this.walletAddress = config.walletAddress;
|
|
996
|
+
this.model = config.model;
|
|
997
|
+
this.maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
998
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
999
|
+
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
1000
|
+
this.costTracker = new CostTracker(config.costTracker);
|
|
1001
|
+
this.tools = config.tools ?? (config.agent ? getDefaultTools() : []);
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Submit a user message and receive a stream of engine events.
|
|
1005
|
+
* Handles the full agent loop: LLM → permission check → tool execution → LLM → ...
|
|
1006
|
+
*/
|
|
1007
|
+
async *submitMessage(prompt) {
|
|
1008
|
+
if (this.costTracker.isOverBudget()) {
|
|
1009
|
+
yield { type: "error", error: new Error("Session budget exceeded") };
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
this.abortController = new AbortController();
|
|
1013
|
+
const signal = this.abortController.signal;
|
|
1014
|
+
this.messages.push({
|
|
1015
|
+
role: "user",
|
|
1016
|
+
content: [{ type: "text", text: prompt }]
|
|
1017
|
+
});
|
|
1018
|
+
const context = {
|
|
1019
|
+
agent: this.agent,
|
|
1020
|
+
mcpManager: this.mcpManager,
|
|
1021
|
+
walletAddress: this.walletAddress,
|
|
1022
|
+
signal
|
|
1023
|
+
};
|
|
1024
|
+
let turns = 0;
|
|
1025
|
+
while (turns < this.maxTurns) {
|
|
1026
|
+
if (signal.aborted) {
|
|
1027
|
+
yield { type: "error", error: new Error("Aborted") };
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
turns++;
|
|
1031
|
+
const toolDefs = toolsToDefinitions(this.tools);
|
|
1032
|
+
const acc = {
|
|
1033
|
+
text: "",
|
|
1034
|
+
stopReason: "end_turn",
|
|
1035
|
+
assistantBlocks: [],
|
|
1036
|
+
pendingToolCalls: []
|
|
1037
|
+
};
|
|
1038
|
+
const stream = this.provider.chat({
|
|
1039
|
+
messages: this.messages,
|
|
1040
|
+
systemPrompt: this.systemPrompt,
|
|
1041
|
+
tools: toolDefs,
|
|
1042
|
+
model: this.model,
|
|
1043
|
+
maxTokens: this.maxTokens,
|
|
1044
|
+
signal
|
|
1045
|
+
});
|
|
1046
|
+
for await (const event of stream) {
|
|
1047
|
+
yield* this.handleProviderEvent(event, acc);
|
|
1048
|
+
}
|
|
1049
|
+
if (acc.text) {
|
|
1050
|
+
acc.assistantBlocks.push({ type: "text", text: acc.text });
|
|
1051
|
+
}
|
|
1052
|
+
this.messages.push({ role: "assistant", content: acc.assistantBlocks });
|
|
1053
|
+
if (acc.pendingToolCalls.length === 0) {
|
|
1054
|
+
yield { type: "turn_complete", stopReason: acc.stopReason };
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
if (signal.aborted) {
|
|
1058
|
+
yield { type: "error", error: new Error("Aborted") };
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const approved = [];
|
|
1062
|
+
const toolResultBlocks = [];
|
|
1063
|
+
for (const call of acc.pendingToolCalls) {
|
|
1064
|
+
const tool = findTool(this.tools, call.name);
|
|
1065
|
+
const needsConfirmation = tool && !tool.isReadOnly && tool.permissionLevel !== "auto";
|
|
1066
|
+
if (!needsConfirmation) {
|
|
1067
|
+
approved.push(call);
|
|
1068
|
+
yield { type: "tool_start", toolName: call.name, toolUseId: call.id, input: call.input };
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
let resolvePermission;
|
|
1072
|
+
const permissionPromise = new Promise((r) => {
|
|
1073
|
+
resolvePermission = r;
|
|
1074
|
+
});
|
|
1075
|
+
yield {
|
|
1076
|
+
type: "permission_request",
|
|
1077
|
+
toolName: call.name,
|
|
1078
|
+
toolUseId: call.id,
|
|
1079
|
+
input: call.input,
|
|
1080
|
+
description: describeAction(tool, call),
|
|
1081
|
+
resolve: resolvePermission
|
|
1082
|
+
};
|
|
1083
|
+
let userApproved;
|
|
1084
|
+
try {
|
|
1085
|
+
userApproved = await Promise.race([
|
|
1086
|
+
permissionPromise,
|
|
1087
|
+
new Promise((_, reject) => {
|
|
1088
|
+
if (signal.aborted) reject(new Error("Aborted"));
|
|
1089
|
+
signal.addEventListener("abort", () => reject(new Error("Aborted")), { once: true });
|
|
1090
|
+
})
|
|
1091
|
+
]);
|
|
1092
|
+
} catch {
|
|
1093
|
+
yield { type: "error", error: new Error("Aborted") };
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (userApproved) {
|
|
1097
|
+
approved.push(call);
|
|
1098
|
+
yield { type: "tool_start", toolName: call.name, toolUseId: call.id, input: call.input };
|
|
1099
|
+
} else {
|
|
1100
|
+
toolResultBlocks.push({
|
|
1101
|
+
type: "tool_result",
|
|
1102
|
+
toolUseId: call.id,
|
|
1103
|
+
content: JSON.stringify({ error: "User declined this action" }),
|
|
1104
|
+
isError: true
|
|
1105
|
+
});
|
|
1106
|
+
yield {
|
|
1107
|
+
type: "tool_result",
|
|
1108
|
+
toolName: call.name,
|
|
1109
|
+
toolUseId: call.id,
|
|
1110
|
+
result: { error: "User declined this action" },
|
|
1111
|
+
isError: true
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
for await (const toolEvent of runTools(approved, this.tools, context, this.txMutex)) {
|
|
1116
|
+
yield toolEvent;
|
|
1117
|
+
if (toolEvent.type === "tool_result") {
|
|
1118
|
+
toolResultBlocks.push({
|
|
1119
|
+
type: "tool_result",
|
|
1120
|
+
toolUseId: toolEvent.toolUseId,
|
|
1121
|
+
content: JSON.stringify(toolEvent.result),
|
|
1122
|
+
isError: toolEvent.isError
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
this.messages.push({ role: "user", content: toolResultBlocks });
|
|
1127
|
+
if (this.costTracker.isOverBudget()) {
|
|
1128
|
+
yield { type: "error", error: new Error("Session budget exceeded") };
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
yield { type: "turn_complete", stopReason: "max_turns" };
|
|
1133
|
+
}
|
|
1134
|
+
interrupt() {
|
|
1135
|
+
this.abortController?.abort();
|
|
1136
|
+
}
|
|
1137
|
+
getMessages() {
|
|
1138
|
+
return this.messages;
|
|
1139
|
+
}
|
|
1140
|
+
reset() {
|
|
1141
|
+
this.messages = [];
|
|
1142
|
+
this.costTracker.reset();
|
|
1143
|
+
}
|
|
1144
|
+
loadMessages(messages) {
|
|
1145
|
+
this.messages = [...messages];
|
|
1146
|
+
}
|
|
1147
|
+
getUsage() {
|
|
1148
|
+
return this.costTracker.getSnapshot();
|
|
1149
|
+
}
|
|
1150
|
+
// ---------------------------------------------------------------------------
|
|
1151
|
+
// Internal
|
|
1152
|
+
// ---------------------------------------------------------------------------
|
|
1153
|
+
*handleProviderEvent(event, acc) {
|
|
1154
|
+
switch (event.type) {
|
|
1155
|
+
case "text_delta": {
|
|
1156
|
+
acc.text += event.text;
|
|
1157
|
+
yield { type: "text_delta", text: event.text };
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
case "tool_use_done": {
|
|
1161
|
+
acc.assistantBlocks.push({
|
|
1162
|
+
type: "tool_use",
|
|
1163
|
+
id: event.id,
|
|
1164
|
+
name: event.name,
|
|
1165
|
+
input: event.input
|
|
1166
|
+
});
|
|
1167
|
+
acc.pendingToolCalls.push({
|
|
1168
|
+
id: event.id,
|
|
1169
|
+
name: event.name,
|
|
1170
|
+
input: event.input
|
|
1171
|
+
});
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
case "usage": {
|
|
1175
|
+
this.costTracker.track(
|
|
1176
|
+
event.inputTokens,
|
|
1177
|
+
event.outputTokens,
|
|
1178
|
+
event.cacheReadTokens,
|
|
1179
|
+
event.cacheWriteTokens
|
|
1180
|
+
);
|
|
1181
|
+
yield {
|
|
1182
|
+
type: "usage",
|
|
1183
|
+
inputTokens: event.inputTokens,
|
|
1184
|
+
outputTokens: event.outputTokens,
|
|
1185
|
+
cacheReadTokens: event.cacheReadTokens,
|
|
1186
|
+
cacheWriteTokens: event.cacheWriteTokens
|
|
1187
|
+
};
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
case "stop": {
|
|
1191
|
+
acc.stopReason = event.reason;
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
function describeAction(tool, call) {
|
|
1198
|
+
const input = call.input;
|
|
1199
|
+
switch (tool.name) {
|
|
1200
|
+
case "save_deposit":
|
|
1201
|
+
return `Save ${input.amount === "all" ? "all available" : `$${input.amount}`} into savings`;
|
|
1202
|
+
case "withdraw":
|
|
1203
|
+
return `Withdraw ${input.amount === "all" ? "all" : `$${input.amount}`} from savings`;
|
|
1204
|
+
case "send_transfer":
|
|
1205
|
+
return `Send $${input.amount} to ${input.to}`;
|
|
1206
|
+
case "borrow":
|
|
1207
|
+
return `Borrow $${input.amount} against collateral`;
|
|
1208
|
+
case "repay_debt":
|
|
1209
|
+
return `Repay ${input.amount === "all" ? "all" : `$${input.amount}`} of outstanding debt`;
|
|
1210
|
+
case "claim_rewards":
|
|
1211
|
+
return "Claim all pending protocol rewards";
|
|
1212
|
+
case "pay_api":
|
|
1213
|
+
return `Pay for API call to ${input.url}${input.maxPrice ? ` (max $${input.maxPrice})` : ""}`;
|
|
1214
|
+
default:
|
|
1215
|
+
return `Execute ${tool.name}`;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/streaming.ts
|
|
1220
|
+
function serializeSSE(event) {
|
|
1221
|
+
const data = JSON.stringify(event);
|
|
1222
|
+
return `event: ${event.type}
|
|
1223
|
+
data: ${data}
|
|
1224
|
+
|
|
1225
|
+
`;
|
|
1226
|
+
}
|
|
1227
|
+
function parseSSE(raw) {
|
|
1228
|
+
const dataLine = raw.split("\n").find((l) => l.startsWith("data: "));
|
|
1229
|
+
if (!dataLine) return null;
|
|
1230
|
+
try {
|
|
1231
|
+
return JSON.parse(dataLine.slice(6));
|
|
1232
|
+
} catch {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
var PermissionBridge = class {
|
|
1237
|
+
pending = /* @__PURE__ */ new Map();
|
|
1238
|
+
counter = 0;
|
|
1239
|
+
/**
|
|
1240
|
+
* Register a permission_request resolve callback.
|
|
1241
|
+
* Returns the permissionId to send to the client.
|
|
1242
|
+
*/
|
|
1243
|
+
register(resolve) {
|
|
1244
|
+
const id = `perm_${++this.counter}_${Date.now()}`;
|
|
1245
|
+
this.pending.set(id, resolve);
|
|
1246
|
+
return id;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Resolve a pending permission request from the client.
|
|
1250
|
+
* Returns false if the permissionId is unknown (expired or invalid).
|
|
1251
|
+
*/
|
|
1252
|
+
resolve(permissionId, approved) {
|
|
1253
|
+
const resolver = this.pending.get(permissionId);
|
|
1254
|
+
if (!resolver) return false;
|
|
1255
|
+
resolver(approved);
|
|
1256
|
+
this.pending.delete(permissionId);
|
|
1257
|
+
return true;
|
|
1258
|
+
}
|
|
1259
|
+
/** Number of pending (unresolved) permission requests. */
|
|
1260
|
+
get size() {
|
|
1261
|
+
return this.pending.size;
|
|
1262
|
+
}
|
|
1263
|
+
/** Reject all pending permissions (e.g., on disconnect). */
|
|
1264
|
+
rejectAll() {
|
|
1265
|
+
for (const resolver of this.pending.values()) {
|
|
1266
|
+
resolver(false);
|
|
1267
|
+
}
|
|
1268
|
+
this.pending.clear();
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
async function* engineToSSE(events, bridge) {
|
|
1272
|
+
for await (const event of events) {
|
|
1273
|
+
switch (event.type) {
|
|
1274
|
+
case "permission_request": {
|
|
1275
|
+
const permissionId = bridge.register(event.resolve);
|
|
1276
|
+
yield serializeSSE({
|
|
1277
|
+
type: "permission_request",
|
|
1278
|
+
permissionId,
|
|
1279
|
+
toolName: event.toolName,
|
|
1280
|
+
toolUseId: event.toolUseId,
|
|
1281
|
+
input: event.input,
|
|
1282
|
+
description: event.description
|
|
1283
|
+
});
|
|
1284
|
+
break;
|
|
1285
|
+
}
|
|
1286
|
+
case "error": {
|
|
1287
|
+
yield serializeSSE({
|
|
1288
|
+
type: "error",
|
|
1289
|
+
message: event.error.message
|
|
1290
|
+
});
|
|
1291
|
+
break;
|
|
1292
|
+
}
|
|
1293
|
+
default: {
|
|
1294
|
+
yield serializeSSE(event);
|
|
1295
|
+
break;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/session.ts
|
|
1302
|
+
var MemorySessionStore = class {
|
|
1303
|
+
store = /* @__PURE__ */ new Map();
|
|
1304
|
+
ttlMs;
|
|
1305
|
+
constructor(opts) {
|
|
1306
|
+
this.ttlMs = opts?.ttlMs ?? 24 * 60 * 60 * 1e3;
|
|
1307
|
+
}
|
|
1308
|
+
async get(sessionId) {
|
|
1309
|
+
const entry = this.store.get(sessionId);
|
|
1310
|
+
if (!entry) return null;
|
|
1311
|
+
if (Date.now() > entry.expiresAt) {
|
|
1312
|
+
this.store.delete(sessionId);
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
return structuredClone(entry.data);
|
|
1316
|
+
}
|
|
1317
|
+
async set(session) {
|
|
1318
|
+
this.store.set(session.id, {
|
|
1319
|
+
data: structuredClone(session),
|
|
1320
|
+
expiresAt: Date.now() + this.ttlMs
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
async delete(sessionId) {
|
|
1324
|
+
this.store.delete(sessionId);
|
|
1325
|
+
}
|
|
1326
|
+
async exists(sessionId) {
|
|
1327
|
+
const entry = this.store.get(sessionId);
|
|
1328
|
+
if (!entry) return false;
|
|
1329
|
+
if (Date.now() > entry.expiresAt) {
|
|
1330
|
+
this.store.delete(sessionId);
|
|
1331
|
+
return false;
|
|
1332
|
+
}
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
/** For testing: number of active (non-expired) sessions. */
|
|
1336
|
+
get size() {
|
|
1337
|
+
this.evictExpired();
|
|
1338
|
+
return this.store.size;
|
|
1339
|
+
}
|
|
1340
|
+
evictExpired() {
|
|
1341
|
+
const now = Date.now();
|
|
1342
|
+
for (const [id, entry] of this.store) {
|
|
1343
|
+
if (now > entry.expiresAt) this.store.delete(id);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
// src/context.ts
|
|
1349
|
+
var CHARS_PER_TOKEN = 4;
|
|
1350
|
+
function estimateTokens(messages) {
|
|
1351
|
+
let chars = 0;
|
|
1352
|
+
for (const msg of messages) {
|
|
1353
|
+
for (const block of msg.content) {
|
|
1354
|
+
chars += blockCharCount(block);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
1358
|
+
}
|
|
1359
|
+
function blockCharCount(block) {
|
|
1360
|
+
switch (block.type) {
|
|
1361
|
+
case "text":
|
|
1362
|
+
return block.text.length;
|
|
1363
|
+
case "tool_use":
|
|
1364
|
+
return block.name.length + JSON.stringify(block.input).length;
|
|
1365
|
+
case "tool_result":
|
|
1366
|
+
return block.content.length;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function compactMessages(messages, opts = {}) {
|
|
1370
|
+
const maxTokens = opts.maxTokens ?? 1e5;
|
|
1371
|
+
const keepRecent = opts.keepRecentCount ?? 6;
|
|
1372
|
+
const systemTokens = opts.systemPromptTokens ?? 500;
|
|
1373
|
+
const budget = maxTokens - systemTokens;
|
|
1374
|
+
if (messages.length === 0) return [];
|
|
1375
|
+
const mutable = messages.map((m) => ({
|
|
1376
|
+
role: m.role,
|
|
1377
|
+
content: m.content.map((b) => ({ ...b }))
|
|
1378
|
+
}));
|
|
1379
|
+
if (estimateTokens(mutable) <= budget) return mutable;
|
|
1380
|
+
const splitIdx = Math.max(0, mutable.length - keepRecent);
|
|
1381
|
+
for (let i = 0; i < splitIdx; i++) {
|
|
1382
|
+
mutable[i].content = mutable[i].content.map((block) => {
|
|
1383
|
+
if (block.type === "tool_result" && block.content.length > 200) {
|
|
1384
|
+
return {
|
|
1385
|
+
...block,
|
|
1386
|
+
content: truncateToolResult(block.content)
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
return block;
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
if (estimateTokens(mutable) <= budget) return mutable;
|
|
1393
|
+
const first = mutable[0];
|
|
1394
|
+
const recent = mutable.slice(splitIdx);
|
|
1395
|
+
const oldSection = mutable.slice(1, splitIdx);
|
|
1396
|
+
while (oldSection.length > 0 && estimateTokens([first, ...oldSection, ...recent]) > budget) {
|
|
1397
|
+
oldSection.shift();
|
|
1398
|
+
}
|
|
1399
|
+
const compacted = [first, ...oldSection, ...recent];
|
|
1400
|
+
if (estimateTokens(compacted) > budget) {
|
|
1401
|
+
for (const msg of compacted) {
|
|
1402
|
+
msg.content = msg.content.map((block) => {
|
|
1403
|
+
if (block.type === "tool_result" && block.content.length > 100) {
|
|
1404
|
+
return { ...block, content: truncateToolResult(block.content) };
|
|
1405
|
+
}
|
|
1406
|
+
return block;
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return sanitizeMessages(compacted);
|
|
1411
|
+
}
|
|
1412
|
+
function sanitizeMessages(messages) {
|
|
1413
|
+
const toolUseIds = /* @__PURE__ */ new Set();
|
|
1414
|
+
const toolResultIds = /* @__PURE__ */ new Set();
|
|
1415
|
+
for (const msg of messages) {
|
|
1416
|
+
for (const block of msg.content) {
|
|
1417
|
+
if (block.type === "tool_use") toolUseIds.add(block.id);
|
|
1418
|
+
if (block.type === "tool_result") toolResultIds.add(block.toolUseId);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return messages.map((msg) => {
|
|
1422
|
+
const filtered = msg.content.filter((block) => {
|
|
1423
|
+
if (block.type === "tool_result") return toolUseIds.has(block.toolUseId);
|
|
1424
|
+
if (block.type === "tool_use") return toolResultIds.has(block.id);
|
|
1425
|
+
return true;
|
|
1426
|
+
});
|
|
1427
|
+
if (filtered.length === 0) return null;
|
|
1428
|
+
return { ...msg, content: filtered };
|
|
1429
|
+
}).filter((m) => m !== null);
|
|
1430
|
+
}
|
|
1431
|
+
function truncateToolResult(content) {
|
|
1432
|
+
try {
|
|
1433
|
+
const parsed = JSON.parse(content);
|
|
1434
|
+
if (parsed.error) {
|
|
1435
|
+
return JSON.stringify({ error: parsed.error });
|
|
1436
|
+
}
|
|
1437
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
1438
|
+
const summary = {};
|
|
1439
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1440
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1441
|
+
summary[key] = value;
|
|
1442
|
+
} else if (typeof value === "string") {
|
|
1443
|
+
summary[key] = value.length > 50 ? value.slice(0, 50) + "\u2026" : value;
|
|
1444
|
+
} else if (Array.isArray(value)) {
|
|
1445
|
+
summary[key] = `[${value.length} items]`;
|
|
1446
|
+
} else {
|
|
1447
|
+
summary[key] = "{\u2026}";
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return JSON.stringify(summary);
|
|
1451
|
+
}
|
|
1452
|
+
return content.slice(0, 100);
|
|
1453
|
+
} catch {
|
|
1454
|
+
return content.slice(0, 100);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/mcp.ts
|
|
1459
|
+
function buildMcpTools(context, tools) {
|
|
1460
|
+
const engineTools = tools ?? getDefaultTools();
|
|
1461
|
+
return engineTools.map((tool) => ({
|
|
1462
|
+
name: `audric_${tool.name}`,
|
|
1463
|
+
description: tool.description,
|
|
1464
|
+
inputSchema: tool.jsonSchema,
|
|
1465
|
+
async handler(args) {
|
|
1466
|
+
try {
|
|
1467
|
+
const parsed = tool.inputSchema.safeParse(args);
|
|
1468
|
+
if (!parsed.success) {
|
|
1469
|
+
return {
|
|
1470
|
+
content: [{
|
|
1471
|
+
type: "text",
|
|
1472
|
+
text: JSON.stringify({
|
|
1473
|
+
error: `Invalid input: ${parsed.error.issues.map((i) => i.message).join(", ")}`
|
|
1474
|
+
})
|
|
1475
|
+
}],
|
|
1476
|
+
isError: true
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
const result = await tool.call(parsed.data, context);
|
|
1480
|
+
return {
|
|
1481
|
+
content: [{
|
|
1482
|
+
type: "text",
|
|
1483
|
+
text: JSON.stringify(result.data)
|
|
1484
|
+
}]
|
|
1485
|
+
};
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
return {
|
|
1488
|
+
content: [{
|
|
1489
|
+
type: "text",
|
|
1490
|
+
text: JSON.stringify({
|
|
1491
|
+
error: err instanceof Error ? err.message : "Tool execution failed"
|
|
1492
|
+
})
|
|
1493
|
+
}],
|
|
1494
|
+
isError: true
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}));
|
|
1499
|
+
}
|
|
1500
|
+
function registerEngineTools(server, context, tools) {
|
|
1501
|
+
const descriptors = buildMcpTools(context, tools);
|
|
1502
|
+
for (const desc of descriptors) {
|
|
1503
|
+
server.tool(desc.name, desc.description, desc.inputSchema, desc.handler);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
var McpResponseCache = class {
|
|
1507
|
+
cache = /* @__PURE__ */ new Map();
|
|
1508
|
+
defaultTtlMs;
|
|
1509
|
+
constructor(defaultTtlMs = 3e4) {
|
|
1510
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
1511
|
+
}
|
|
1512
|
+
key(serverName, toolName, args) {
|
|
1513
|
+
return `${serverName}::${toolName}::${JSON.stringify(args)}`;
|
|
1514
|
+
}
|
|
1515
|
+
get(serverName, toolName, args) {
|
|
1516
|
+
const k = this.key(serverName, toolName, args);
|
|
1517
|
+
const entry = this.cache.get(k);
|
|
1518
|
+
if (!entry) return null;
|
|
1519
|
+
if (Date.now() > entry.expiresAt) {
|
|
1520
|
+
this.cache.delete(k);
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
return entry.result;
|
|
1524
|
+
}
|
|
1525
|
+
set(serverName, toolName, args, result, ttlMs) {
|
|
1526
|
+
const k = this.key(serverName, toolName, args);
|
|
1527
|
+
this.cache.set(k, {
|
|
1528
|
+
result,
|
|
1529
|
+
expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs)
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
invalidate(serverName) {
|
|
1533
|
+
if (!serverName) {
|
|
1534
|
+
this.cache.clear();
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
for (const key of this.cache.keys()) {
|
|
1538
|
+
if (key.startsWith(`${serverName}::`)) {
|
|
1539
|
+
this.cache.delete(key);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
get size() {
|
|
1544
|
+
return this.cache.size;
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
var McpClientManager = class {
|
|
1548
|
+
connections = /* @__PURE__ */ new Map();
|
|
1549
|
+
responseCache;
|
|
1550
|
+
constructor(opts) {
|
|
1551
|
+
this.responseCache = new McpResponseCache(opts?.cacheTtlMs ?? 3e4);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Connect to an MCP server and discover its tools.
|
|
1555
|
+
* If already connected to a server with this name, disconnects first.
|
|
1556
|
+
*/
|
|
1557
|
+
async connect(config) {
|
|
1558
|
+
if (this.connections.has(config.name)) {
|
|
1559
|
+
await this.disconnect(config.name);
|
|
1560
|
+
}
|
|
1561
|
+
const client = new Client(
|
|
1562
|
+
{ name: "audric-engine", version: "0.1.0" },
|
|
1563
|
+
{ capabilities: {} }
|
|
1564
|
+
);
|
|
1565
|
+
const transportType = config.transport ?? "streamable-http";
|
|
1566
|
+
const url = new URL(config.url);
|
|
1567
|
+
const transport = transportType === "sse" ? new SSEClientTransport(url) : new StreamableHTTPClientTransport(url, {
|
|
1568
|
+
reconnectionOptions: {
|
|
1569
|
+
maxReconnectionDelay: 3e4,
|
|
1570
|
+
initialReconnectionDelay: 1e3,
|
|
1571
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
1572
|
+
maxRetries: 3
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
const conn = {
|
|
1576
|
+
config,
|
|
1577
|
+
client,
|
|
1578
|
+
transport,
|
|
1579
|
+
tools: [],
|
|
1580
|
+
status: "disconnected"
|
|
1581
|
+
};
|
|
1582
|
+
try {
|
|
1583
|
+
await client.connect(transport);
|
|
1584
|
+
conn.status = "connected";
|
|
1585
|
+
const { tools } = await client.listTools();
|
|
1586
|
+
conn.tools = tools;
|
|
1587
|
+
} catch (err) {
|
|
1588
|
+
try {
|
|
1589
|
+
await client.close();
|
|
1590
|
+
} catch {
|
|
1591
|
+
}
|
|
1592
|
+
throw err;
|
|
1593
|
+
}
|
|
1594
|
+
this.connections.set(config.name, conn);
|
|
1595
|
+
return conn;
|
|
1596
|
+
}
|
|
1597
|
+
/** Disconnect from a server by name. */
|
|
1598
|
+
async disconnect(name) {
|
|
1599
|
+
const conn = this.connections.get(name);
|
|
1600
|
+
if (!conn) return;
|
|
1601
|
+
try {
|
|
1602
|
+
await conn.client.close();
|
|
1603
|
+
} catch {
|
|
1604
|
+
}
|
|
1605
|
+
conn.status = "disconnected";
|
|
1606
|
+
conn.tools = [];
|
|
1607
|
+
this.connections.delete(name);
|
|
1608
|
+
this.responseCache.invalidate(name);
|
|
1609
|
+
}
|
|
1610
|
+
/** Disconnect from all servers. */
|
|
1611
|
+
async disconnectAll() {
|
|
1612
|
+
const names = [...this.connections.keys()];
|
|
1613
|
+
await Promise.allSettled(names.map((n) => this.disconnect(n)));
|
|
1614
|
+
}
|
|
1615
|
+
/** Get a connection by server name. */
|
|
1616
|
+
getConnection(name) {
|
|
1617
|
+
return this.connections.get(name);
|
|
1618
|
+
}
|
|
1619
|
+
/** Check if a server is connected. */
|
|
1620
|
+
isConnected(name) {
|
|
1621
|
+
return this.connections.get(name)?.status === "connected";
|
|
1622
|
+
}
|
|
1623
|
+
/** List all tool definitions across all connected servers. */
|
|
1624
|
+
listAllTools() {
|
|
1625
|
+
const result = [];
|
|
1626
|
+
for (const [name, conn] of this.connections) {
|
|
1627
|
+
if (conn.status !== "connected") continue;
|
|
1628
|
+
for (const tool of conn.tools) {
|
|
1629
|
+
result.push({ serverName: name, tool });
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return result;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Call a tool on a specific server.
|
|
1636
|
+
* Uses response cache for read-only servers.
|
|
1637
|
+
*/
|
|
1638
|
+
async callTool(serverName, toolName, args = {}) {
|
|
1639
|
+
const conn = this.connections.get(serverName);
|
|
1640
|
+
if (!conn) throw new Error(`MCP server "${serverName}" not connected`);
|
|
1641
|
+
if (conn.status !== "connected") throw new Error(`MCP server "${serverName}" is ${conn.status}`);
|
|
1642
|
+
const cacheTtl = conn.config.cacheTtlMs ?? 3e4;
|
|
1643
|
+
if (conn.config.readOnly !== false && cacheTtl > 0) {
|
|
1644
|
+
const cached = this.responseCache.get(serverName, toolName, args);
|
|
1645
|
+
if (cached) return cached;
|
|
1646
|
+
}
|
|
1647
|
+
const result = await conn.client.callTool({ name: toolName, arguments: args });
|
|
1648
|
+
const callResult = {
|
|
1649
|
+
content: result.content ?? [],
|
|
1650
|
+
isError: result.isError
|
|
1651
|
+
};
|
|
1652
|
+
if (conn.config.readOnly !== false && cacheTtl > 0) {
|
|
1653
|
+
this.responseCache.set(serverName, toolName, args, callResult, cacheTtl);
|
|
1654
|
+
}
|
|
1655
|
+
return callResult;
|
|
1656
|
+
}
|
|
1657
|
+
/** Get the response cache (for testing / manual invalidation). */
|
|
1658
|
+
get cache() {
|
|
1659
|
+
return this.responseCache;
|
|
1660
|
+
}
|
|
1661
|
+
/** Number of connected servers. */
|
|
1662
|
+
get serverCount() {
|
|
1663
|
+
let count = 0;
|
|
1664
|
+
for (const conn of this.connections.values()) {
|
|
1665
|
+
if (conn.status === "connected") count++;
|
|
1666
|
+
}
|
|
1667
|
+
return count;
|
|
1668
|
+
}
|
|
1669
|
+
/** All server names. */
|
|
1670
|
+
get serverNames() {
|
|
1671
|
+
return [...this.connections.keys()];
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
function adaptMcpTool(mcpTool, config) {
|
|
1675
|
+
const overrides = config.toolOverrides?.[mcpTool.name];
|
|
1676
|
+
const isReadOnly = overrides?.isReadOnly ?? config.isReadOnly ?? true;
|
|
1677
|
+
const permissionLevel = overrides?.permissionLevel ?? config.permissionLevel ?? "auto";
|
|
1678
|
+
const namespacedName = `${config.serverName}_${mcpTool.name}`;
|
|
1679
|
+
const jsonSchema = mcpTool.inputSchema ?? {
|
|
1680
|
+
type: "object",
|
|
1681
|
+
properties: {}
|
|
1682
|
+
};
|
|
1683
|
+
return {
|
|
1684
|
+
name: namespacedName,
|
|
1685
|
+
description: overrides?.description ?? mcpTool.description ?? `MCP tool: ${mcpTool.name}`,
|
|
1686
|
+
inputSchema: z.record(z.unknown()),
|
|
1687
|
+
jsonSchema,
|
|
1688
|
+
isReadOnly,
|
|
1689
|
+
isConcurrencySafe: isReadOnly,
|
|
1690
|
+
permissionLevel,
|
|
1691
|
+
async call(input, _context) {
|
|
1692
|
+
const result = await config.manager.callTool(
|
|
1693
|
+
config.serverName,
|
|
1694
|
+
mcpTool.name,
|
|
1695
|
+
input
|
|
1696
|
+
);
|
|
1697
|
+
const textContent = result.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
1698
|
+
let data;
|
|
1699
|
+
try {
|
|
1700
|
+
data = JSON.parse(textContent);
|
|
1701
|
+
} catch {
|
|
1702
|
+
data = textContent || result.content;
|
|
1703
|
+
}
|
|
1704
|
+
if (result.isError) {
|
|
1705
|
+
return { data: { error: data } };
|
|
1706
|
+
}
|
|
1707
|
+
return { data };
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
function adaptAllMcpTools(config) {
|
|
1712
|
+
const conn = config.manager.getConnection(config.serverName);
|
|
1713
|
+
if (!conn || conn.status !== "connected") {
|
|
1714
|
+
return [];
|
|
1715
|
+
}
|
|
1716
|
+
return conn.tools.map((t) => adaptMcpTool(t, config));
|
|
1717
|
+
}
|
|
1718
|
+
function adaptAllServerTools(manager, serverConfigs) {
|
|
1719
|
+
const allTools = [];
|
|
1720
|
+
for (const { serverName, tool } of manager.listAllTools()) {
|
|
1721
|
+
const serverOpts = serverConfigs?.[serverName] ?? {};
|
|
1722
|
+
allTools.push(adaptMcpTool(tool, {
|
|
1723
|
+
manager,
|
|
1724
|
+
serverName,
|
|
1725
|
+
...serverOpts
|
|
1726
|
+
}));
|
|
1727
|
+
}
|
|
1728
|
+
return allTools;
|
|
1729
|
+
}
|
|
1730
|
+
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
1731
|
+
var DEFAULT_MAX_TOKENS2 = 4096;
|
|
1732
|
+
var AnthropicProvider = class {
|
|
1733
|
+
client;
|
|
1734
|
+
defaultModel;
|
|
1735
|
+
defaultMaxTokens;
|
|
1736
|
+
constructor(config) {
|
|
1737
|
+
this.client = new Anthropic({ apiKey: config.apiKey });
|
|
1738
|
+
this.defaultModel = config.defaultModel ?? DEFAULT_MODEL;
|
|
1739
|
+
this.defaultMaxTokens = config.defaultMaxTokens ?? DEFAULT_MAX_TOKENS2;
|
|
1740
|
+
}
|
|
1741
|
+
async *chat(params) {
|
|
1742
|
+
const messages = params.messages.map(toAnthropicMessage);
|
|
1743
|
+
const tools = params.tools.map(toAnthropicTool);
|
|
1744
|
+
const streamParams = {
|
|
1745
|
+
model: params.model ?? this.defaultModel,
|
|
1746
|
+
max_tokens: params.maxTokens ?? this.defaultMaxTokens,
|
|
1747
|
+
system: params.systemPrompt,
|
|
1748
|
+
messages,
|
|
1749
|
+
tools: tools.length > 0 ? tools : void 0
|
|
1750
|
+
};
|
|
1751
|
+
const stream = params.signal ? this.client.messages.stream(streamParams, { signal: params.signal }) : this.client.messages.stream(streamParams);
|
|
1752
|
+
const toolInputBuffers = /* @__PURE__ */ new Map();
|
|
1753
|
+
let outputTokensFromStart = 0;
|
|
1754
|
+
try {
|
|
1755
|
+
for await (const event of stream) {
|
|
1756
|
+
switch (event.type) {
|
|
1757
|
+
case "message_start": {
|
|
1758
|
+
const msg = event.message;
|
|
1759
|
+
yield {
|
|
1760
|
+
type: "message_start",
|
|
1761
|
+
messageId: msg.id,
|
|
1762
|
+
model: msg.model
|
|
1763
|
+
};
|
|
1764
|
+
if (msg.usage) {
|
|
1765
|
+
const u = msg.usage;
|
|
1766
|
+
outputTokensFromStart = msg.usage.output_tokens;
|
|
1767
|
+
yield {
|
|
1768
|
+
type: "usage",
|
|
1769
|
+
inputTokens: msg.usage.input_tokens,
|
|
1770
|
+
outputTokens: msg.usage.output_tokens,
|
|
1771
|
+
cacheReadTokens: u.cache_read_input_tokens,
|
|
1772
|
+
cacheWriteTokens: u.cache_creation_input_tokens
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
break;
|
|
1776
|
+
}
|
|
1777
|
+
case "content_block_start": {
|
|
1778
|
+
const block = event.content_block;
|
|
1779
|
+
if (block.type === "tool_use") {
|
|
1780
|
+
toolInputBuffers.set(event.index, {
|
|
1781
|
+
id: block.id,
|
|
1782
|
+
name: block.name,
|
|
1783
|
+
json: ""
|
|
1784
|
+
});
|
|
1785
|
+
yield {
|
|
1786
|
+
type: "tool_use_start",
|
|
1787
|
+
id: block.id,
|
|
1788
|
+
name: block.name
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
break;
|
|
1792
|
+
}
|
|
1793
|
+
case "content_block_delta": {
|
|
1794
|
+
const delta = event.delta;
|
|
1795
|
+
if (delta.type === "text_delta") {
|
|
1796
|
+
yield { type: "text_delta", text: delta.text };
|
|
1797
|
+
} else if (delta.type === "input_json_delta") {
|
|
1798
|
+
const buf = toolInputBuffers.get(event.index);
|
|
1799
|
+
if (buf) {
|
|
1800
|
+
buf.json += delta.partial_json;
|
|
1801
|
+
yield {
|
|
1802
|
+
type: "tool_use_delta",
|
|
1803
|
+
id: buf.id,
|
|
1804
|
+
partialJson: delta.partial_json
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
break;
|
|
1809
|
+
}
|
|
1810
|
+
case "content_block_stop": {
|
|
1811
|
+
const buf = toolInputBuffers.get(event.index);
|
|
1812
|
+
if (buf) {
|
|
1813
|
+
let input = {};
|
|
1814
|
+
try {
|
|
1815
|
+
input = JSON.parse(buf.json || "{}");
|
|
1816
|
+
} catch {
|
|
1817
|
+
input = {};
|
|
1818
|
+
}
|
|
1819
|
+
yield {
|
|
1820
|
+
type: "tool_use_done",
|
|
1821
|
+
id: buf.id,
|
|
1822
|
+
name: buf.name,
|
|
1823
|
+
input
|
|
1824
|
+
};
|
|
1825
|
+
toolInputBuffers.delete(event.index);
|
|
1826
|
+
}
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
case "message_delta": {
|
|
1830
|
+
const delta = event.delta;
|
|
1831
|
+
const usage = event.usage;
|
|
1832
|
+
if (usage?.output_tokens && usage.output_tokens > outputTokensFromStart) {
|
|
1833
|
+
const increment = usage.output_tokens - outputTokensFromStart;
|
|
1834
|
+
outputTokensFromStart = usage.output_tokens;
|
|
1835
|
+
yield {
|
|
1836
|
+
type: "usage",
|
|
1837
|
+
inputTokens: 0,
|
|
1838
|
+
outputTokens: increment
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
if (delta.stop_reason) {
|
|
1842
|
+
yield {
|
|
1843
|
+
type: "stop",
|
|
1844
|
+
reason: mapStopReason(delta.stop_reason)
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
} finally {
|
|
1852
|
+
stream.abort();
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
function toAnthropicMessage(msg) {
|
|
1857
|
+
const content = msg.content.map((block) => {
|
|
1858
|
+
switch (block.type) {
|
|
1859
|
+
case "text":
|
|
1860
|
+
return { type: "text", text: block.text };
|
|
1861
|
+
case "tool_use":
|
|
1862
|
+
return {
|
|
1863
|
+
type: "tool_use",
|
|
1864
|
+
id: block.id,
|
|
1865
|
+
name: block.name,
|
|
1866
|
+
input: block.input
|
|
1867
|
+
};
|
|
1868
|
+
case "tool_result":
|
|
1869
|
+
return {
|
|
1870
|
+
type: "tool_result",
|
|
1871
|
+
tool_use_id: block.toolUseId,
|
|
1872
|
+
content: block.content,
|
|
1873
|
+
is_error: block.isError
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
return { role: msg.role, content };
|
|
1878
|
+
}
|
|
1879
|
+
function toAnthropicTool(def) {
|
|
1880
|
+
return {
|
|
1881
|
+
name: def.name,
|
|
1882
|
+
description: def.description,
|
|
1883
|
+
input_schema: def.input_schema
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function mapStopReason(reason) {
|
|
1887
|
+
switch (reason) {
|
|
1888
|
+
case "end_turn":
|
|
1889
|
+
return "end_turn";
|
|
1890
|
+
case "tool_use":
|
|
1891
|
+
return "tool_use";
|
|
1892
|
+
case "max_tokens":
|
|
1893
|
+
return "max_tokens";
|
|
1894
|
+
default:
|
|
1895
|
+
return "end_turn";
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
export { AnthropicProvider, CostTracker, DEFAULT_SYSTEM_PROMPT, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, NaviTools, PermissionBridge, QueryEngine, READ_TOOLS, TxMutex, WRITE_TOOLS, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, balanceCheckTool, borrowTool, buildMcpTools, buildTool, claimRewardsTool, compactMessages, engineToSSE, estimateTokens, extractMcpText, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, findTool, getDefaultTools, getMcpManager, getWalletAddress, hasNaviMcp, healthCheckTool, parseMcpJson, parseSSE, payApiTool, ratesInfoTool, registerEngineTools, repayDebtTool, requireAgent, runTools, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, withdrawTool };
|
|
1900
|
+
//# sourceMappingURL=index.js.map
|
|
1901
|
+
//# sourceMappingURL=index.js.map
|