@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/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