@t2000/engine 0.31.3 → 0.33.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 CHANGED
@@ -1,5 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import { resolveSymbol, getDecimalsForCoinType, assertAllowedAsset, getSwapQuote, SUI_TYPE } from '@t2000/sdk';
3
+ import { readdirSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import yaml from 'js-yaml';
3
6
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
7
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
8
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
@@ -7,15 +10,18 @@ import Anthropic from '@anthropic-ai/sdk';
7
10
 
8
11
  // src/tool.ts
9
12
  function buildTool(opts) {
13
+ const isReadOnly = opts.isReadOnly ?? true;
10
14
  return {
11
15
  name: opts.name,
12
16
  description: opts.description,
13
17
  inputSchema: opts.inputSchema,
14
18
  jsonSchema: opts.jsonSchema,
15
19
  call: opts.call,
16
- isReadOnly: opts.isReadOnly ?? true,
17
- isConcurrencySafe: opts.isReadOnly ?? true,
18
- permissionLevel: opts.permissionLevel ?? (opts.isReadOnly === false ? "confirm" : "auto")
20
+ isReadOnly,
21
+ isConcurrencySafe: isReadOnly,
22
+ permissionLevel: opts.permissionLevel ?? (isReadOnly ? "auto" : "confirm"),
23
+ flags: opts.flags ?? {},
24
+ preflight: opts.preflight
19
25
  };
20
26
  }
21
27
  function toolsToDefinitions(tools) {
@@ -152,6 +158,45 @@ async function executeSingleTool(tool, call, context) {
152
158
  const result = await tool.call(parsed.data, context);
153
159
  return { data: result.data, isError: false };
154
160
  }
161
+
162
+ // src/tool-flags.ts
163
+ var TOOL_FLAGS = {
164
+ // Write tools — financial
165
+ save_deposit: { mutating: true, requiresBalance: true },
166
+ withdraw: { mutating: true, affectsHealth: true },
167
+ send_transfer: { mutating: true, requiresBalance: true, irreversible: true },
168
+ swap_execute: { mutating: true, requiresBalance: true },
169
+ borrow: { mutating: true, affectsHealth: true },
170
+ repay_debt: { mutating: true, requiresBalance: true },
171
+ claim_rewards: { mutating: true },
172
+ volo_stake: { mutating: true, requiresBalance: true },
173
+ volo_unstake: { mutating: true },
174
+ // Write tools — pay / services
175
+ pay_api: { mutating: true, requiresBalance: true, costAware: true, producesArtifact: true, maxRetries: 1 },
176
+ // Write tools — lightweight (no financial guards)
177
+ save_contact: {},
178
+ create_schedule: { mutating: true },
179
+ cancel_schedule: { mutating: true },
180
+ // Allowance tools — API mutations disguised as reads
181
+ toggle_allowance: { mutating: true },
182
+ update_daily_limit: { mutating: true },
183
+ update_permissions: { mutating: true },
184
+ // Receive tools — create/cancel mutate server state
185
+ create_payment_link: { mutating: true },
186
+ cancel_payment_link: { mutating: true },
187
+ create_invoice: { mutating: true },
188
+ cancel_invoice: { mutating: true }
189
+ };
190
+ function applyToolFlags(tools) {
191
+ return tools.map((tool) => {
192
+ const flags = TOOL_FLAGS[tool.name];
193
+ if (!flags) return tool;
194
+ return { ...tool, flags: { ...tool.flags, ...flags } };
195
+ });
196
+ }
197
+ function getToolFlags(name) {
198
+ return TOOL_FLAGS[name] ?? {};
199
+ }
155
200
  var SUI_MAINNET_URL = "https://fullnode.mainnet.sui.io:443";
156
201
  var EXTRA_COINS = {
157
202
  "0xc060006111016b8a020ad5b33834984a437aaa7d3c74c18e09a95d48aceab08c::coin::COIN": { symbol: "USDT", decimals: 6 }
@@ -1119,6 +1164,13 @@ var saveDepositTool = buildTool({
1119
1164
  },
1120
1165
  isReadOnly: false,
1121
1166
  permissionLevel: "confirm",
1167
+ flags: { mutating: true, requiresBalance: true },
1168
+ preflight: (input) => {
1169
+ if (input.asset && input.asset.toUpperCase() !== "USDC") {
1170
+ return { valid: false, error: `Only USDC deposits are supported. Got: "${input.asset}"` };
1171
+ }
1172
+ return { valid: true };
1173
+ },
1122
1174
  async call(input, context) {
1123
1175
  assertAllowedAsset("save", input.asset);
1124
1176
  const agent = requireAgent(context);
@@ -1160,6 +1212,7 @@ var withdrawTool = buildTool({
1160
1212
  },
1161
1213
  isReadOnly: false,
1162
1214
  permissionLevel: "confirm",
1215
+ flags: { mutating: true, affectsHealth: true },
1163
1216
  async call(input, context) {
1164
1217
  const agent = requireAgent(context);
1165
1218
  const result = await agent.withdraw({
@@ -1207,6 +1260,16 @@ var sendTransferTool = buildTool({
1207
1260
  },
1208
1261
  isReadOnly: false,
1209
1262
  permissionLevel: "confirm",
1263
+ flags: { mutating: true, requiresBalance: true, irreversible: true },
1264
+ preflight: (input) => {
1265
+ if (input.to.startsWith("0x") && !/^0x[a-fA-F0-9]{64}$/.test(input.to)) {
1266
+ return { valid: false, error: `Invalid Sui address format: "${input.to}". Must be 0x followed by 64 hex characters.` };
1267
+ }
1268
+ if (input.amount <= 0) {
1269
+ return { valid: false, error: "Amount must be positive." };
1270
+ }
1271
+ return { valid: true };
1272
+ },
1210
1273
  async call(input, context) {
1211
1274
  const agent = requireAgent(context);
1212
1275
  const result = await agent.send({ to: input.to, amount: input.amount });
@@ -1249,6 +1312,13 @@ var borrowTool = buildTool({
1249
1312
  },
1250
1313
  isReadOnly: false,
1251
1314
  permissionLevel: "confirm",
1315
+ flags: { mutating: true, affectsHealth: true },
1316
+ preflight: (input) => {
1317
+ if (input.asset && input.asset.toUpperCase() !== "USDC") {
1318
+ return { valid: false, error: `Only USDC borrows are supported. Got: "${input.asset}"` };
1319
+ }
1320
+ return { valid: true };
1321
+ },
1252
1322
  async call(input, context) {
1253
1323
  assertAllowedAsset("borrow", input.asset);
1254
1324
  const agent = requireAgent(context);
@@ -1283,6 +1353,7 @@ var repayDebtTool = buildTool({
1283
1353
  },
1284
1354
  isReadOnly: false,
1285
1355
  permissionLevel: "confirm",
1356
+ flags: { mutating: true, requiresBalance: true },
1286
1357
  async call(input, context) {
1287
1358
  const agent = requireAgent(context);
1288
1359
  const result = await agent.repay({ amount: input.amount });
@@ -1305,6 +1376,7 @@ var claimRewardsTool = buildTool({
1305
1376
  jsonSchema: { type: "object", properties: {}, required: [] },
1306
1377
  isReadOnly: false,
1307
1378
  permissionLevel: "confirm",
1379
+ flags: { mutating: true },
1308
1380
  async call(_input, context) {
1309
1381
  const agent = requireAgent(context);
1310
1382
  const result = await agent.claimRewards();
@@ -1379,6 +1451,28 @@ Always use ISO-3166 country codes (GB not UK, US not USA). A return address ("fr
1379
1451
  },
1380
1452
  isReadOnly: false,
1381
1453
  permissionLevel: "confirm",
1454
+ flags: { mutating: true, requiresBalance: true, costAware: true, producesArtifact: true, maxRetries: 1 },
1455
+ preflight: (input) => {
1456
+ if (!input.url.startsWith(MPP_GATEWAY)) {
1457
+ return { valid: false, error: `URL must start with ${MPP_GATEWAY}. Got: "${input.url}"` };
1458
+ }
1459
+ if (input.body) {
1460
+ try {
1461
+ JSON.parse(input.body);
1462
+ } catch {
1463
+ return { valid: false, error: "body must be valid JSON." };
1464
+ }
1465
+ if (input.url.includes("lob/")) {
1466
+ const body = JSON.parse(input.body);
1467
+ const to = body.to;
1468
+ const country = to?.address_country;
1469
+ if (typeof country === "string" && country.length !== 2) {
1470
+ return { valid: false, error: `Country must be ISO-3166 2-letter code (got "${country}")` };
1471
+ }
1472
+ }
1473
+ }
1474
+ return { valid: true };
1475
+ },
1382
1476
  async call(input, context) {
1383
1477
  const agent = requireAgent(context);
1384
1478
  const result = await agent.pay({
@@ -1480,6 +1574,13 @@ var swapExecuteTool = buildTool({
1480
1574
  },
1481
1575
  isReadOnly: false,
1482
1576
  permissionLevel: "confirm",
1577
+ flags: { mutating: true, requiresBalance: true },
1578
+ preflight: (input) => {
1579
+ if (input.from.toLowerCase() === input.to.toLowerCase()) {
1580
+ return { valid: false, error: `Cannot swap ${input.from} to itself.` };
1581
+ }
1582
+ return { valid: true };
1583
+ },
1483
1584
  async call(input, context) {
1484
1585
  const agent = requireAgent(context);
1485
1586
  const result = await agent.swap({
@@ -1554,6 +1655,7 @@ var voloStakeTool = buildTool({
1554
1655
  },
1555
1656
  isReadOnly: false,
1556
1657
  permissionLevel: "confirm",
1658
+ flags: { mutating: true, requiresBalance: true },
1557
1659
  async call(input, context) {
1558
1660
  const agent = requireAgent(context);
1559
1661
  const result = await agent.stakeVSui({ amount: input.amount });
@@ -1584,6 +1686,7 @@ var voloUnstakeTool = buildTool({
1584
1686
  },
1585
1687
  isReadOnly: false,
1586
1688
  permissionLevel: "confirm",
1689
+ flags: { mutating: true },
1587
1690
  async call(input, context) {
1588
1691
  const agent = requireAgent(context);
1589
1692
  const result = await agent.unstakeVSui({ amount: input.amount });
@@ -3416,7 +3519,7 @@ var WRITE_TOOLS = [
3416
3519
  cancelScheduleTool
3417
3520
  ];
3418
3521
  function getDefaultTools() {
3419
- return [...READ_TOOLS, ...WRITE_TOOLS];
3522
+ return applyToolFlags([...READ_TOOLS, ...WRITE_TOOLS]);
3420
3523
  }
3421
3524
 
3422
3525
  // src/prompt.ts
@@ -3517,6 +3620,511 @@ var CostTracker = class {
3517
3620
  }
3518
3621
  };
3519
3622
 
3623
+ // src/guards.ts
3624
+ var DEFAULT_GUARD_CONFIG = {
3625
+ balanceValidation: true,
3626
+ healthFactor: { warnBelow: 2, blockBelow: 1.5 },
3627
+ largeTransfer: { warnAbove: 50, strongWarnAbove: 500 },
3628
+ slippage: true,
3629
+ staleData: true,
3630
+ irreversibility: true,
3631
+ artifactPreview: true,
3632
+ costWarning: true,
3633
+ retryProtection: true,
3634
+ inputValidation: true
3635
+ };
3636
+ var BalanceTracker = class {
3637
+ lastBalanceAt = 0;
3638
+ lastWriteAt = 0;
3639
+ recordRead() {
3640
+ this.lastBalanceAt = Date.now();
3641
+ }
3642
+ recordWrite() {
3643
+ this.lastWriteAt = Date.now();
3644
+ }
3645
+ isStale() {
3646
+ return this.lastWriteAt > this.lastBalanceAt;
3647
+ }
3648
+ hasEverRead() {
3649
+ return this.lastBalanceAt > 0;
3650
+ }
3651
+ };
3652
+ var BALANCE_READ_TOOLS = /* @__PURE__ */ new Set([
3653
+ "balance_check",
3654
+ "savings_info",
3655
+ "health_check"
3656
+ ]);
3657
+ var RetryTracker = class {
3658
+ executed = /* @__PURE__ */ new Map();
3659
+ key(toolName, input) {
3660
+ const url = input?.url ?? "";
3661
+ return `${toolName}:${url}`;
3662
+ }
3663
+ record(toolName, input, result) {
3664
+ const r = result;
3665
+ if (r?.paymentConfirmed || r?.doNotRetry) {
3666
+ this.executed.set(this.key(toolName, input), { result, paidAt: Date.now() });
3667
+ }
3668
+ }
3669
+ isBlocked(toolName, input) {
3670
+ const prev = this.executed.get(this.key(toolName, input));
3671
+ if (!prev) return { blocked: false };
3672
+ return { blocked: true, previousResult: prev.result };
3673
+ }
3674
+ };
3675
+ function guardRetryProtection(tool, call, retryTracker) {
3676
+ const check = retryTracker.isBlocked(tool.name, call.input);
3677
+ if (check.blocked) {
3678
+ return {
3679
+ verdict: "block",
3680
+ gate: "retry_blocked",
3681
+ tier: "safety",
3682
+ message: `Blocked: ${tool.name} was already called and payment was confirmed. Do not retry.`
3683
+ };
3684
+ }
3685
+ return { verdict: "pass", gate: "retry_blocked", tier: "safety" };
3686
+ }
3687
+ function guardIrreversibility(tool, _call, conversationText) {
3688
+ if (!tool.flags.irreversible) {
3689
+ return { verdict: "pass", gate: "irreversibility", tier: "safety" };
3690
+ }
3691
+ const hasPreview = /preview|here.s what|confirm.*send|looks? good/i.test(conversationText);
3692
+ if (hasPreview) {
3693
+ return { verdict: "pass", gate: "irreversibility", tier: "safety" };
3694
+ }
3695
+ return {
3696
+ verdict: "hint",
3697
+ gate: "irreversibility",
3698
+ tier: "safety",
3699
+ message: "This action is irreversible. Show a preview and ask the user to confirm before proceeding."
3700
+ };
3701
+ }
3702
+ function guardBalanceValidation(tool, _call, balanceTracker) {
3703
+ if (!tool.flags.requiresBalance) {
3704
+ return { verdict: "pass", gate: "balance_required", tier: "financial" };
3705
+ }
3706
+ if (!balanceTracker.hasEverRead()) {
3707
+ return {
3708
+ verdict: "hint",
3709
+ gate: "balance_required",
3710
+ tier: "financial",
3711
+ message: "Balance has not been checked this session. Call balance_check first to verify sufficient funds."
3712
+ };
3713
+ }
3714
+ if (balanceTracker.isStale()) {
3715
+ return {
3716
+ verdict: "hint",
3717
+ gate: "balance_required",
3718
+ tier: "financial",
3719
+ message: "Balance data is stale (a write action occurred since last check). Call balance_check first to verify sufficient funds."
3720
+ };
3721
+ }
3722
+ return { verdict: "pass", gate: "balance_required", tier: "financial" };
3723
+ }
3724
+ function guardHealthFactor(tool, _call, lastHealthFactor, config) {
3725
+ if (!tool.flags.affectsHealth) {
3726
+ return { verdict: "pass", gate: "health_factor", tier: "financial" };
3727
+ }
3728
+ if (lastHealthFactor === null) {
3729
+ return {
3730
+ verdict: "hint",
3731
+ gate: "health_factor",
3732
+ tier: "financial",
3733
+ message: "Health factor has not been checked this session. Call health_check before this action."
3734
+ };
3735
+ }
3736
+ if (lastHealthFactor < config.blockBelow) {
3737
+ return {
3738
+ verdict: "block",
3739
+ gate: "health_factor",
3740
+ tier: "financial",
3741
+ message: `Health factor is ${lastHealthFactor.toFixed(2)} \u2014 this action risks liquidation. Refusing.`
3742
+ };
3743
+ }
3744
+ if (lastHealthFactor < config.warnBelow) {
3745
+ return {
3746
+ verdict: "warn",
3747
+ gate: "health_factor",
3748
+ tier: "financial",
3749
+ message: `Health factor is ${lastHealthFactor.toFixed(2)} \u2014 this action may reduce it further.`
3750
+ };
3751
+ }
3752
+ return { verdict: "pass", gate: "health_factor", tier: "financial" };
3753
+ }
3754
+ function guardLargeTransfer(tool, call, config) {
3755
+ if (tool.name !== "send_transfer") {
3756
+ return { verdict: "pass", gate: "large_transfer", tier: "financial" };
3757
+ }
3758
+ const input = call.input;
3759
+ const amount = Number(input.amount ?? 0);
3760
+ if (!amount || amount <= 0) {
3761
+ return { verdict: "pass", gate: "large_transfer", tier: "financial" };
3762
+ }
3763
+ const recipient = String(input.recipient ?? input.to ?? "");
3764
+ const shortAddr = recipient.length > 10 ? `${recipient.slice(0, 6)}...${recipient.slice(-4)}` : recipient;
3765
+ if (amount > config.strongWarnAbove) {
3766
+ return {
3767
+ verdict: "warn",
3768
+ gate: "large_transfer",
3769
+ tier: "financial",
3770
+ message: `High-value transfer ($${amount}). Double-check the address: ${shortAddr}`
3771
+ };
3772
+ }
3773
+ if (amount > config.warnAbove) {
3774
+ return {
3775
+ verdict: "hint",
3776
+ gate: "large_transfer",
3777
+ tier: "financial",
3778
+ message: `This is a large transfer ($${amount}). Verify the recipient address.`
3779
+ };
3780
+ }
3781
+ return { verdict: "pass", gate: "large_transfer", tier: "financial" };
3782
+ }
3783
+ function guardSlippage(tool, _call, lastAssistantText) {
3784
+ if (tool.name !== "swap_execute") {
3785
+ return { verdict: "pass", gate: "slippage_warning", tier: "financial" };
3786
+ }
3787
+ const hasEstimate = /~?\$?[\d,]+\.?\d*\s*(SUI|USDC|USDT|WETH)/i.test(lastAssistantText) || /approximately|≈|about|expect|receive/i.test(lastAssistantText);
3788
+ if (hasEstimate) {
3789
+ return { verdict: "pass", gate: "slippage_warning", tier: "financial" };
3790
+ }
3791
+ return {
3792
+ verdict: "hint",
3793
+ gate: "slippage_warning",
3794
+ tier: "financial",
3795
+ message: "State the expected output amount to the user before executing the swap."
3796
+ };
3797
+ }
3798
+ function guardCostWarning(tool, _call, conversationText) {
3799
+ if (!tool.flags.costAware) {
3800
+ return { verdict: "pass", gate: "cost_warning", tier: "ux" };
3801
+ }
3802
+ const hasCostMention = /\$\d+\.?\d*|cost|fee|charge|price|pay/i.test(conversationText);
3803
+ if (hasCostMention) {
3804
+ return { verdict: "pass", gate: "cost_warning", tier: "ux" };
3805
+ }
3806
+ return {
3807
+ verdict: "hint",
3808
+ gate: "cost_warning",
3809
+ tier: "ux",
3810
+ message: "This action has a monetary cost. Confirm the user is aware before proceeding."
3811
+ };
3812
+ }
3813
+ function guardArtifactPreview(result) {
3814
+ if (!result || typeof result !== "object") return null;
3815
+ const r = result;
3816
+ const hasImage = typeof r.url === "string" && /\.(png|jpg|jpeg|webp|gif|svg)(\?|$)/i.test(r.url) || Array.isArray(r.images) && r.images.length > 0 || typeof r.image_url === "string";
3817
+ const hasPdf = typeof r.url === "string" && /\.pdf(\?|$)/i.test(r.url);
3818
+ if (hasImage || hasPdf) {
3819
+ return {
3820
+ _gate: "artifact_preview",
3821
+ _hint: "Show this to the user before proceeding. Output as ![description](url)."
3822
+ };
3823
+ }
3824
+ return null;
3825
+ }
3826
+ function guardStaleData(toolFlags) {
3827
+ if (!toolFlags.mutating) return null;
3828
+ return {
3829
+ _gate: "stale_data",
3830
+ _hint: "A write action just completed. The balance snapshot is outdated. Do NOT calculate new balances from old data \u2014 call balance_check for fresh numbers, or use only the data returned by the write tool."
3831
+ };
3832
+ }
3833
+ function createGuardRunnerState() {
3834
+ return {
3835
+ balanceTracker: new BalanceTracker(),
3836
+ retryTracker: new RetryTracker(),
3837
+ lastHealthFactor: null
3838
+ };
3839
+ }
3840
+ function runGuards(tool, call, state, config, conversationContext) {
3841
+ const results = [];
3842
+ const now = Date.now();
3843
+ if (config.inputValidation !== false && tool.preflight) {
3844
+ const check = tool.preflight(call.input);
3845
+ if (!check.valid) {
3846
+ const event = {
3847
+ timestamp: now,
3848
+ toolName: tool.name,
3849
+ toolUseId: call.id,
3850
+ gate: "input_validation",
3851
+ verdict: "block",
3852
+ tier: "safety",
3853
+ message: check.error
3854
+ };
3855
+ return {
3856
+ blocked: true,
3857
+ blockReason: check.error,
3858
+ blockGate: "input_validation",
3859
+ injections: [],
3860
+ events: [event]
3861
+ };
3862
+ }
3863
+ }
3864
+ if (config.retryProtection !== false) {
3865
+ results.push(guardRetryProtection(tool, call, state.retryTracker));
3866
+ }
3867
+ if (config.irreversibility !== false) {
3868
+ results.push(guardIrreversibility(tool, call, conversationContext.fullText));
3869
+ }
3870
+ if (config.balanceValidation !== false) {
3871
+ results.push(guardBalanceValidation(tool, call, state.balanceTracker));
3872
+ }
3873
+ if (config.healthFactor) {
3874
+ results.push(guardHealthFactor(tool, call, state.lastHealthFactor, config.healthFactor));
3875
+ }
3876
+ if (config.largeTransfer) {
3877
+ results.push(guardLargeTransfer(tool, call, config.largeTransfer));
3878
+ }
3879
+ if (config.slippage !== false) {
3880
+ results.push(guardSlippage(tool, call, conversationContext.lastAssistantText));
3881
+ }
3882
+ if (config.costWarning !== false) {
3883
+ results.push(guardCostWarning(tool, call, conversationContext.fullText));
3884
+ }
3885
+ const events = results.filter((r) => r.verdict !== "pass").map((r) => ({
3886
+ timestamp: now,
3887
+ toolName: tool.name,
3888
+ toolUseId: call.id,
3889
+ gate: r.gate,
3890
+ verdict: r.verdict,
3891
+ tier: r.tier,
3892
+ message: r.message
3893
+ }));
3894
+ const block = results.find((r) => r.verdict === "block");
3895
+ if (block) {
3896
+ return {
3897
+ blocked: true,
3898
+ blockReason: block.message ?? `Blocked by ${block.gate}`,
3899
+ blockGate: block.gate,
3900
+ injections: [],
3901
+ events
3902
+ };
3903
+ }
3904
+ const injections = results.filter((r) => r.verdict === "hint" || r.verdict === "warn").map((r) => ({
3905
+ _gate: r.gate,
3906
+ ...r.verdict === "hint" ? { _hint: r.message } : { _warning: r.message }
3907
+ }));
3908
+ return { blocked: false, injections, events };
3909
+ }
3910
+ function updateGuardStateAfterToolResult(toolName, tool, input, result, isError, state) {
3911
+ if (isError) return;
3912
+ if (BALANCE_READ_TOOLS.has(toolName)) {
3913
+ state.balanceTracker.recordRead();
3914
+ }
3915
+ if (tool?.flags.mutating) {
3916
+ state.balanceTracker.recordWrite();
3917
+ }
3918
+ if (toolName === "health_check" && result && typeof result === "object") {
3919
+ const r = result;
3920
+ const hf = Number(r.healthFactor ?? r.health_factor ?? r.hf);
3921
+ if (!isNaN(hf) && hf > 0) {
3922
+ state.lastHealthFactor = hf;
3923
+ }
3924
+ }
3925
+ state.retryTracker.record(toolName, input, result);
3926
+ }
3927
+ function extractConversationText(messages) {
3928
+ const textParts = [];
3929
+ let lastAssistantText = "";
3930
+ for (const msg of messages) {
3931
+ if (!Array.isArray(msg.content)) continue;
3932
+ for (const block of msg.content) {
3933
+ if (block.type === "text" && typeof block.text === "string") {
3934
+ textParts.push(block.text);
3935
+ if (msg.role === "assistant") {
3936
+ lastAssistantText = block.text;
3937
+ }
3938
+ }
3939
+ }
3940
+ }
3941
+ return {
3942
+ fullText: textParts.join("\n"),
3943
+ lastAssistantText
3944
+ };
3945
+ }
3946
+
3947
+ // src/context.ts
3948
+ var CHARS_PER_TOKEN = 4;
3949
+ var DEFAULT_CONTEXT_LIMIT = 2e5;
3950
+ function estimateTokens(messages) {
3951
+ let chars = 0;
3952
+ for (const msg of messages) {
3953
+ for (const block of msg.content) {
3954
+ chars += blockCharCount(block);
3955
+ }
3956
+ }
3957
+ return Math.ceil(chars / CHARS_PER_TOKEN);
3958
+ }
3959
+ function blockCharCount(block) {
3960
+ switch (block.type) {
3961
+ case "text":
3962
+ return block.text.length;
3963
+ case "thinking":
3964
+ return block.thinking.length;
3965
+ case "redacted_thinking":
3966
+ return block.data.length;
3967
+ case "tool_use":
3968
+ return block.name.length + JSON.stringify(block.input).length;
3969
+ case "tool_result":
3970
+ return block.content.length;
3971
+ }
3972
+ }
3973
+ var ContextBudget = class {
3974
+ estimatedTokens = 0;
3975
+ contextLimit;
3976
+ compactThreshold;
3977
+ warnThreshold;
3978
+ constructor(config = {}) {
3979
+ this.contextLimit = config.contextLimit ?? DEFAULT_CONTEXT_LIMIT;
3980
+ this.compactThreshold = config.compactThreshold ?? 0.85;
3981
+ this.warnThreshold = config.warnThreshold ?? 0.7;
3982
+ }
3983
+ /** Update with actual input_tokens from the API usage event. */
3984
+ update(inputTokens) {
3985
+ this.estimatedTokens = inputTokens;
3986
+ }
3987
+ /** True when the session should be compacted (at 85% of context limit). */
3988
+ shouldCompact() {
3989
+ return this.estimatedTokens >= this.contextLimit * this.compactThreshold;
3990
+ }
3991
+ /** True when nearing the limit (at 70% of context limit). */
3992
+ shouldWarn() {
3993
+ return this.estimatedTokens >= this.contextLimit * this.warnThreshold;
3994
+ }
3995
+ /** Current token count. */
3996
+ get tokens() {
3997
+ return this.estimatedTokens;
3998
+ }
3999
+ /** Remaining tokens before compaction triggers. */
4000
+ get remaining() {
4001
+ return Math.max(0, Math.floor(this.contextLimit * this.compactThreshold) - this.estimatedTokens);
4002
+ }
4003
+ /** Usage ratio (0..1). */
4004
+ get usage() {
4005
+ return this.estimatedTokens / this.contextLimit;
4006
+ }
4007
+ reset() {
4008
+ this.estimatedTokens = 0;
4009
+ }
4010
+ };
4011
+ async function compactMessages(messages, opts = {}) {
4012
+ const maxTokens = opts.maxTokens ?? 1e5;
4013
+ const keepRecent = opts.keepRecentCount ?? 8;
4014
+ const systemTokens = opts.systemPromptTokens ?? 500;
4015
+ const budget = maxTokens - systemTokens;
4016
+ if (messages.length === 0) return [];
4017
+ const mutable = messages.map((m) => ({
4018
+ role: m.role,
4019
+ content: m.content.map((b) => ({ ...b }))
4020
+ }));
4021
+ if (estimateTokens(mutable) <= budget) return mutable;
4022
+ const splitIdx = Math.max(0, mutable.length - keepRecent);
4023
+ const oldMessages = mutable.slice(0, splitIdx);
4024
+ const recent = mutable.slice(splitIdx);
4025
+ if (opts.summarizer && oldMessages.length > 0) {
4026
+ const strippedOld = stripThinkingBlocks(oldMessages);
4027
+ try {
4028
+ const summary = await opts.summarizer(strippedOld);
4029
+ const summaryMessages = [
4030
+ { role: "user", content: [{ type: "text", text: `[Session summary: ${summary}]` }] },
4031
+ { role: "assistant", content: [{ type: "text", text: "Understood. I have the context from our earlier conversation." }] }
4032
+ ];
4033
+ const withSummary = [...summaryMessages, ...recent];
4034
+ if (estimateTokens(withSummary) <= budget) return sanitizeMessages(withSummary);
4035
+ } catch {
4036
+ }
4037
+ }
4038
+ for (let i = 0; i < splitIdx; i++) {
4039
+ mutable[i].content = mutable[i].content.map((block) => {
4040
+ if (block.type === "tool_result" && block.content.length > 200) {
4041
+ return {
4042
+ ...block,
4043
+ content: truncateToolResult(block.content)
4044
+ };
4045
+ }
4046
+ return block;
4047
+ });
4048
+ }
4049
+ for (let i = 0; i < splitIdx; i++) {
4050
+ mutable[i].content = mutable[i].content.filter(
4051
+ (b) => b.type !== "thinking" && b.type !== "redacted_thinking"
4052
+ );
4053
+ }
4054
+ if (estimateTokens(mutable) <= budget) return mutable;
4055
+ const first = mutable[0];
4056
+ const recentFromMutable = mutable.slice(splitIdx);
4057
+ const oldSection = mutable.slice(1, splitIdx);
4058
+ while (oldSection.length > 0 && estimateTokens([first, ...oldSection, ...recentFromMutable]) > budget) {
4059
+ oldSection.shift();
4060
+ }
4061
+ const compacted = [first, ...oldSection, ...recentFromMutable];
4062
+ if (estimateTokens(compacted) > budget) {
4063
+ for (const msg of compacted) {
4064
+ msg.content = msg.content.map((block) => {
4065
+ if (block.type === "tool_result" && block.content.length > 100) {
4066
+ return { ...block, content: truncateToolResult(block.content) };
4067
+ }
4068
+ return block;
4069
+ });
4070
+ }
4071
+ }
4072
+ return sanitizeMessages(compacted);
4073
+ }
4074
+ function stripThinkingBlocks(messages) {
4075
+ return messages.map((m) => ({
4076
+ ...m,
4077
+ content: m.content.filter(
4078
+ (b) => b.type !== "thinking" && b.type !== "redacted_thinking"
4079
+ )
4080
+ })).filter((m) => m.content.length > 0);
4081
+ }
4082
+ function sanitizeMessages(messages) {
4083
+ const toolUseIds = /* @__PURE__ */ new Set();
4084
+ const toolResultIds = /* @__PURE__ */ new Set();
4085
+ for (const msg of messages) {
4086
+ for (const block of msg.content) {
4087
+ if (block.type === "tool_use") toolUseIds.add(block.id);
4088
+ if (block.type === "tool_result") toolResultIds.add(block.toolUseId);
4089
+ }
4090
+ }
4091
+ return messages.map((msg) => {
4092
+ const filtered = msg.content.filter((block) => {
4093
+ if (block.type === "tool_result") return toolUseIds.has(block.toolUseId);
4094
+ if (block.type === "tool_use") return toolResultIds.has(block.id);
4095
+ return true;
4096
+ });
4097
+ if (filtered.length === 0) return null;
4098
+ return { ...msg, content: filtered };
4099
+ }).filter((m) => m !== null);
4100
+ }
4101
+ function truncateToolResult(content) {
4102
+ try {
4103
+ const parsed = JSON.parse(content);
4104
+ if (parsed.error) {
4105
+ return JSON.stringify({ error: parsed.error });
4106
+ }
4107
+ if (typeof parsed === "object" && parsed !== null) {
4108
+ const summary = {};
4109
+ for (const [key, value] of Object.entries(parsed)) {
4110
+ if (typeof value === "number" || typeof value === "boolean") {
4111
+ summary[key] = value;
4112
+ } else if (typeof value === "string") {
4113
+ summary[key] = value.length > 50 ? value.slice(0, 50) + "\u2026" : value;
4114
+ } else if (Array.isArray(value)) {
4115
+ summary[key] = `[${value.length} items]`;
4116
+ } else {
4117
+ summary[key] = "{\u2026}";
4118
+ }
4119
+ }
4120
+ return JSON.stringify(summary);
4121
+ }
4122
+ return content.slice(0, 100);
4123
+ } catch {
4124
+ return content.slice(0, 100);
4125
+ }
4126
+ }
4127
+
3520
4128
  // src/engine.ts
3521
4129
  var DEFAULT_MAX_TURNS = 10;
3522
4130
  var DEFAULT_MAX_TOKENS = 4096;
@@ -3529,6 +4137,8 @@ var QueryEngine = class {
3529
4137
  maxTokens;
3530
4138
  temperature;
3531
4139
  toolChoice;
4140
+ thinking;
4141
+ outputConfig;
3532
4142
  agent;
3533
4143
  mcpManager;
3534
4144
  walletAddress;
@@ -3538,8 +4148,15 @@ var QueryEngine = class {
3538
4148
  env;
3539
4149
  txMutex = new TxMutex();
3540
4150
  costTracker;
4151
+ guardConfig;
4152
+ guardState;
4153
+ recipes;
4154
+ contextBudget;
4155
+ contextSummarizer;
4156
+ matchedRecipe = null;
3541
4157
  messages = [];
3542
4158
  abortController = null;
4159
+ guardEvents = [];
3543
4160
  constructor(config) {
3544
4161
  this.provider = config.provider;
3545
4162
  this.agent = config.agent;
@@ -3554,8 +4171,15 @@ var QueryEngine = class {
3554
4171
  this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
3555
4172
  this.temperature = config.temperature;
3556
4173
  this.toolChoice = config.toolChoice;
4174
+ this.thinking = config.thinking;
4175
+ this.outputConfig = config.outputConfig;
3557
4176
  this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
3558
4177
  this.costTracker = new CostTracker(config.costTracker);
4178
+ this.guardConfig = config.guards;
4179
+ this.guardState = createGuardRunnerState();
4180
+ this.recipes = config.recipes;
4181
+ this.contextBudget = new ContextBudget(config.contextBudget);
4182
+ this.contextSummarizer = config.contextSummarizer;
3559
4183
  this.tools = config.tools ?? (config.agent ? getDefaultTools() : []);
3560
4184
  }
3561
4185
  /**
@@ -3573,6 +4197,7 @@ var QueryEngine = class {
3573
4197
  }
3574
4198
  this.abortController = new AbortController();
3575
4199
  const signal = this.abortController.signal;
4200
+ this.matchedRecipe = this.recipes?.match(prompt) ?? null;
3576
4201
  this.messages.push({
3577
4202
  role: "user",
3578
4203
  content: [{ type: "text", text: prompt }]
@@ -3631,9 +4256,21 @@ var QueryEngine = class {
3631
4256
  getMessages() {
3632
4257
  return this.messages;
3633
4258
  }
4259
+ getMatchedRecipe() {
4260
+ return this.matchedRecipe;
4261
+ }
4262
+ getContextBudget() {
4263
+ return this.contextBudget;
4264
+ }
3634
4265
  reset() {
3635
4266
  this.messages = [];
3636
4267
  this.costTracker.reset();
4268
+ this.contextBudget.reset();
4269
+ this.guardEvents = [];
4270
+ this.matchedRecipe = null;
4271
+ }
4272
+ getGuardEvents() {
4273
+ return this.guardEvents;
3637
4274
  }
3638
4275
  loadMessages(messages) {
3639
4276
  this.messages = [...messages];
@@ -3680,11 +4317,20 @@ var QueryEngine = class {
3680
4317
  pendingToolCalls: []
3681
4318
  };
3682
4319
  try {
4320
+ if (this.contextBudget.shouldCompact()) {
4321
+ this.messages = await compactMessages(this.messages, {
4322
+ maxTokens: 1e5,
4323
+ keepRecentCount: 8,
4324
+ summarizer: this.contextSummarizer
4325
+ });
4326
+ }
3683
4327
  this.messages = validateHistory(this.messages);
3684
4328
  if (process.env.NODE_ENV !== "test") {
3685
4329
  const summary = this.messages.map((m, idx) => {
3686
4330
  const blocks = m.content.map((b) => {
3687
4331
  if (b.type === "text") return `text(${b.text.slice(0, 40)}\u2026)`;
4332
+ if (b.type === "thinking") return `thinking(${b.thinking.length}ch)`;
4333
+ if (b.type === "redacted_thinking") return `redacted_thinking`;
3688
4334
  if (b.type === "tool_use") return `tool_use:${b.id.slice(-8)}/${b.name}`;
3689
4335
  return `tool_result:${b.toolUseId.slice(-8)}`;
3690
4336
  });
@@ -3693,14 +4339,32 @@ var QueryEngine = class {
3693
4339
  console.log(`[engine] provider.chat turn=${turns} msgs=${this.messages.length}
3694
4340
  ${summary.join("\n")}`);
3695
4341
  }
4342
+ const thinkingEnabled = this.thinking && this.thinking.type !== "disabled";
4343
+ const effectiveToolChoice = thinkingEnabled ? applyToolChoice && turns === 1 ? "auto" : void 0 : applyToolChoice && turns === 1 ? this.toolChoice : void 0;
4344
+ let effectivePrompt = this.systemPrompt;
4345
+ if (this.matchedRecipe && this.recipes) {
4346
+ const recipeCtx = this.recipes.toPromptContext(this.matchedRecipe);
4347
+ if (typeof effectivePrompt === "string") {
4348
+ effectivePrompt = `${effectivePrompt}
4349
+
4350
+ ${recipeCtx}`;
4351
+ } else if (Array.isArray(effectivePrompt)) {
4352
+ effectivePrompt = [
4353
+ ...effectivePrompt,
4354
+ { type: "text", text: recipeCtx }
4355
+ ];
4356
+ }
4357
+ }
3696
4358
  const stream = this.provider.chat({
3697
4359
  messages: this.messages,
3698
- systemPrompt: this.systemPrompt,
4360
+ systemPrompt: effectivePrompt,
3699
4361
  tools: toolDefs,
3700
4362
  model: this.model,
3701
4363
  maxTokens: this.maxTokens,
3702
4364
  temperature: this.temperature,
3703
- toolChoice: applyToolChoice && turns === 1 ? this.toolChoice : void 0,
4365
+ toolChoice: effectiveToolChoice,
4366
+ thinking: this.thinking,
4367
+ outputConfig: this.outputConfig,
3704
4368
  signal
3705
4369
  });
3706
4370
  for await (const event of stream) {
@@ -3746,7 +4410,42 @@ ${summary.join("\n")}`);
3746
4410
  pendingWrite = { call, tool };
3747
4411
  break;
3748
4412
  }
3749
- for await (const toolEvent of runTools(approved, this.tools, context, this.txMutex)) {
4413
+ const guardedApproved = [];
4414
+ if (this.guardConfig) {
4415
+ const convCtx = extractConversationText(this.messages);
4416
+ for (const call of approved) {
4417
+ const tool = findTool(this.tools, call.name);
4418
+ if (!tool) {
4419
+ guardedApproved.push(call);
4420
+ continue;
4421
+ }
4422
+ const check = runGuards(tool, call, this.guardState, this.guardConfig, convCtx);
4423
+ this.guardEvents.push(...check.events);
4424
+ if (check.blocked) {
4425
+ yield {
4426
+ type: "tool_result",
4427
+ toolName: call.name,
4428
+ toolUseId: call.id,
4429
+ result: { error: check.blockReason, _gate: check.blockGate },
4430
+ isError: true
4431
+ };
4432
+ toolResultBlocks.push({
4433
+ type: "tool_result",
4434
+ toolUseId: call.id,
4435
+ content: JSON.stringify({ error: check.blockReason, _gate: check.blockGate }),
4436
+ isError: true
4437
+ });
4438
+ continue;
4439
+ }
4440
+ if (check.injections.length > 0) {
4441
+ call._guardInjections = check.injections;
4442
+ }
4443
+ guardedApproved.push(call);
4444
+ }
4445
+ } else {
4446
+ guardedApproved.push(...approved);
4447
+ }
4448
+ for await (const toolEvent of runTools(guardedApproved, this.tools, context, this.txMutex)) {
3750
4449
  if (toolEvent.type === "tool_result" && !toolEvent.isError) {
3751
4450
  const warning = flagSuspiciousResult(toolEvent.toolName, toolEvent.result);
3752
4451
  if (warning) {
@@ -3764,29 +4463,89 @@ ${summary.join("\n")}`);
3764
4463
  continue;
3765
4464
  }
3766
4465
  }
3767
- yield toolEvent;
3768
- if (toolEvent.type === "tool_result" && !toolEvent.isError) {
3769
- const r = toolEvent.result;
3770
- if (r && r.__canvas === true) {
3771
- yield {
3772
- type: "canvas",
3773
- template: String(r.template ?? ""),
3774
- title: String(r.title ?? ""),
3775
- data: r.templateData ?? null,
3776
- toolUseId: toolEvent.toolUseId
3777
- };
4466
+ if (toolEvent.type === "tool_result") {
4467
+ const tool = findTool(this.tools, toolEvent.toolName);
4468
+ const originalCall = guardedApproved.find((c) => c.id === toolEvent.toolUseId);
4469
+ updateGuardStateAfterToolResult(
4470
+ toolEvent.toolName,
4471
+ tool,
4472
+ originalCall?.input ?? null,
4473
+ toolEvent.result,
4474
+ toolEvent.isError,
4475
+ this.guardState
4476
+ );
4477
+ let enrichedResult = toolEvent.result;
4478
+ if (this.guardConfig && !toolEvent.isError && tool) {
4479
+ const artifactInj = this.guardConfig.artifactPreview !== false ? guardArtifactPreview(toolEvent.result) : null;
4480
+ const staleInj = this.guardConfig.staleData !== false ? guardStaleData(tool.flags) : null;
4481
+ const preInjections = guardedApproved.find((c) => c.id === toolEvent.toolUseId)?._guardInjections ?? [];
4482
+ const allInjections = [
4483
+ ...preInjections,
4484
+ ...artifactInj ? [artifactInj] : [],
4485
+ ...staleInj ? [staleInj] : []
4486
+ ];
4487
+ if (allInjections.length > 0 && typeof enrichedResult === "object" && enrichedResult) {
4488
+ enrichedResult = { ...enrichedResult, _guards: allInjections };
4489
+ }
4490
+ }
4491
+ const finalEvent = enrichedResult !== toolEvent.result ? { ...toolEvent, result: enrichedResult } : toolEvent;
4492
+ yield finalEvent;
4493
+ if (finalEvent.type === "tool_result" && !finalEvent.isError) {
4494
+ const r = finalEvent.result;
4495
+ if (r && r.__canvas === true) {
4496
+ yield {
4497
+ type: "canvas",
4498
+ template: String(r.template ?? ""),
4499
+ title: String(r.title ?? ""),
4500
+ data: r.templateData ?? null,
4501
+ toolUseId: finalEvent.toolUseId
4502
+ };
4503
+ }
3778
4504
  }
4505
+ toolResultBlocks.push({
4506
+ type: "tool_result",
4507
+ toolUseId: finalEvent.toolUseId,
4508
+ content: JSON.stringify(finalEvent.result),
4509
+ isError: finalEvent.isError
4510
+ });
4511
+ continue;
3779
4512
  }
3780
- if (toolEvent.type === "tool_result") {
4513
+ yield toolEvent;
4514
+ }
4515
+ if (pendingWrite && this.guardConfig) {
4516
+ const convCtx = extractConversationText(this.messages);
4517
+ const check = runGuards(
4518
+ pendingWrite.tool,
4519
+ pendingWrite.call,
4520
+ this.guardState,
4521
+ this.guardConfig,
4522
+ convCtx
4523
+ );
4524
+ this.guardEvents.push(...check.events);
4525
+ if (check.blocked) {
4526
+ yield {
4527
+ type: "tool_result",
4528
+ toolName: pendingWrite.call.name,
4529
+ toolUseId: pendingWrite.call.id,
4530
+ result: { error: check.blockReason, _gate: check.blockGate },
4531
+ isError: true
4532
+ };
3781
4533
  toolResultBlocks.push({
3782
4534
  type: "tool_result",
3783
- toolUseId: toolEvent.toolUseId,
3784
- content: JSON.stringify(toolEvent.result),
3785
- isError: toolEvent.isError
4535
+ toolUseId: pendingWrite.call.id,
4536
+ content: JSON.stringify({ error: check.blockReason, _gate: check.blockGate }),
4537
+ isError: true
3786
4538
  });
4539
+ this.messages.push({ role: "assistant", content: acc.assistantBlocks });
4540
+ this.messages.push({ role: "user", content: toolResultBlocks });
4541
+ continue;
4542
+ }
4543
+ if (check.injections.length > 0) {
4544
+ pendingWrite.call._guardInjections = check.injections;
3787
4545
  }
3788
4546
  }
3789
4547
  if (pendingWrite) {
4548
+ const writeGuardInjections = pendingWrite.call._guardInjections;
3790
4549
  yield {
3791
4550
  type: "pending_action",
3792
4551
  action: {
@@ -3799,7 +4558,8 @@ ${summary.join("\n")}`);
3799
4558
  toolUseId: b.toolUseId,
3800
4559
  content: b.content,
3801
4560
  isError: b.isError ?? false
3802
- }))
4561
+ })),
4562
+ ...writeGuardInjections?.length ? { guardInjections: writeGuardInjections } : {}
3803
4563
  }
3804
4564
  };
3805
4565
  return;
@@ -3829,6 +4589,26 @@ ${summary.join("\n")}`);
3829
4589
  }
3830
4590
  *handleProviderEvent(event, acc) {
3831
4591
  switch (event.type) {
4592
+ case "thinking_delta": {
4593
+ yield { type: "thinking_delta", text: event.text };
4594
+ break;
4595
+ }
4596
+ case "thinking_done": {
4597
+ acc.assistantBlocks.push({
4598
+ type: "thinking",
4599
+ thinking: event.thinking,
4600
+ signature: event.signature
4601
+ });
4602
+ yield { type: "thinking_done", signature: event.signature };
4603
+ break;
4604
+ }
4605
+ case "redacted_thinking": {
4606
+ acc.assistantBlocks.push({
4607
+ type: "redacted_thinking",
4608
+ data: event.data
4609
+ });
4610
+ break;
4611
+ }
3832
4612
  case "text_delta": {
3833
4613
  acc.text += event.text;
3834
4614
  yield { type: "text_delta", text: event.text };
@@ -3859,6 +4639,7 @@ ${summary.join("\n")}`);
3859
4639
  event.cacheReadTokens,
3860
4640
  event.cacheWriteTokens
3861
4641
  );
4642
+ this.contextBudget.update(event.inputTokens);
3862
4643
  yield {
3863
4644
  type: "usage",
3864
4645
  inputTokens: event.inputTokens,
@@ -4075,114 +4856,305 @@ var MemorySessionStore = class {
4075
4856
  }
4076
4857
  }
4077
4858
  };
4859
+ var StepRequirementSchema = z.object({
4860
+ step: z.string().optional(),
4861
+ field: z.string().optional(),
4862
+ confirmation: z.boolean().optional()
4863
+ });
4864
+ var OnErrorSchema = z.object({
4865
+ action: z.enum(["abort", "refuse", "report", "retry"]),
4866
+ message: z.string(),
4867
+ suggest: z.string().optional()
4868
+ });
4869
+ var StepSchema = z.object({
4870
+ name: z.string().min(1),
4871
+ tool: z.string().optional(),
4872
+ service: z.string().optional(),
4873
+ purpose: z.string().min(1),
4874
+ cost: z.string().optional(),
4875
+ output: z.object({ type: z.string(), key: z.string() }).optional(),
4876
+ gate: z.enum(["none", "preview", "review", "estimate"]).optional(),
4877
+ gate_prompt: z.string().optional(),
4878
+ requires: z.array(StepRequirementSchema).optional(),
4879
+ rules: z.array(z.string()).optional(),
4880
+ condition: z.string().optional(),
4881
+ notes: z.string().optional(),
4882
+ flags: z.record(z.unknown()).optional(),
4883
+ on_error: OnErrorSchema.optional(),
4884
+ input_template: z.record(z.string()).optional(),
4885
+ cost_per_unit: z.string().optional()
4886
+ });
4887
+ var RecipeSchema = z.object({
4888
+ name: z.string().min(1),
4889
+ description: z.string().min(1),
4890
+ triggers: z.array(z.string().min(1)).min(1),
4891
+ services: z.array(z.string()).optional(),
4892
+ prerequisites: z.array(z.object({ field: z.string(), prompt: z.string() })).optional(),
4893
+ steps: z.array(StepSchema).min(1)
4894
+ }).refine(
4895
+ (r) => {
4896
+ const names = r.steps.map((s) => s.name);
4897
+ return new Set(names).size === names.length;
4898
+ },
4899
+ { message: "Step names must be unique within a recipe" }
4900
+ );
4901
+ function loadRecipes(yamlDir) {
4902
+ const files = readdirSync(yamlDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
4903
+ const recipes = [];
4904
+ for (const file of files) {
4905
+ const content = readFileSync(join(yamlDir, file), "utf-8");
4906
+ const raw = yaml.load(content);
4907
+ const parsed = RecipeSchema.parse(raw);
4908
+ recipes.push(parsed);
4909
+ }
4910
+ return recipes;
4911
+ }
4912
+ function parseRecipe(yamlContent) {
4913
+ const raw = yaml.load(yamlContent);
4914
+ return RecipeSchema.parse(raw);
4915
+ }
4078
4916
 
4079
- // src/context.ts
4080
- var CHARS_PER_TOKEN = 4;
4081
- function estimateTokens(messages) {
4082
- let chars = 0;
4083
- for (const msg of messages) {
4084
- for (const block of msg.content) {
4085
- chars += blockCharCount(block);
4086
- }
4917
+ // src/recipes/registry.ts
4918
+ var RecipeRegistry = class {
4919
+ recipes = [];
4920
+ /** Load all recipes from a directory of YAML files. */
4921
+ loadDir(yamlDir) {
4922
+ this.recipes.push(...loadRecipes(yamlDir));
4087
4923
  }
4088
- return Math.ceil(chars / CHARS_PER_TOKEN);
4089
- }
4090
- function blockCharCount(block) {
4091
- switch (block.type) {
4092
- case "text":
4093
- return block.text.length;
4094
- case "tool_use":
4095
- return block.name.length + JSON.stringify(block.input).length;
4096
- case "tool_result":
4097
- return block.content.length;
4924
+ /** Register a single recipe from a YAML string. */
4925
+ loadYaml(yamlContent) {
4926
+ this.recipes.push(parseRecipe(yamlContent));
4098
4927
  }
4099
- }
4100
- function compactMessages(messages, opts = {}) {
4101
- const maxTokens = opts.maxTokens ?? 1e5;
4102
- const keepRecent = opts.keepRecentCount ?? 6;
4103
- const systemTokens = opts.systemPromptTokens ?? 500;
4104
- const budget = maxTokens - systemTokens;
4105
- if (messages.length === 0) return [];
4106
- const mutable = messages.map((m) => ({
4107
- role: m.role,
4108
- content: m.content.map((b) => ({ ...b }))
4109
- }));
4110
- if (estimateTokens(mutable) <= budget) return mutable;
4111
- const splitIdx = Math.max(0, mutable.length - keepRecent);
4112
- for (let i = 0; i < splitIdx; i++) {
4113
- mutable[i].content = mutable[i].content.map((block) => {
4114
- if (block.type === "tool_result" && block.content.length > 200) {
4115
- return {
4116
- ...block,
4117
- content: truncateToolResult(block.content)
4118
- };
4119
- }
4120
- return block;
4121
- });
4928
+ /** Register a pre-parsed Recipe object. */
4929
+ register(recipe) {
4930
+ this.recipes.push(recipe);
4122
4931
  }
4123
- if (estimateTokens(mutable) <= budget) return mutable;
4124
- const first = mutable[0];
4125
- const recent = mutable.slice(splitIdx);
4126
- const oldSection = mutable.slice(1, splitIdx);
4127
- while (oldSection.length > 0 && estimateTokens([first, ...oldSection, ...recent]) > budget) {
4128
- oldSection.shift();
4932
+ /** All loaded recipes. */
4933
+ all() {
4934
+ return this.recipes;
4129
4935
  }
4130
- const compacted = [first, ...oldSection, ...recent];
4131
- if (estimateTokens(compacted) > budget) {
4132
- for (const msg of compacted) {
4133
- msg.content = msg.content.map((block) => {
4134
- if (block.type === "tool_result" && block.content.length > 100) {
4135
- return { ...block, content: truncateToolResult(block.content) };
4936
+ /**
4937
+ * Match a user message to the most specific recipe.
4938
+ * Longest trigger phrase match wins. Returns null if no match.
4939
+ */
4940
+ match(userMessage) {
4941
+ const normalized = userMessage.toLowerCase().trim();
4942
+ let best = null;
4943
+ let bestLength = 0;
4944
+ for (const recipe of this.recipes) {
4945
+ for (const trigger of recipe.triggers) {
4946
+ const triggerLower = trigger.toLowerCase();
4947
+ if (normalized.includes(triggerLower) && triggerLower.length > bestLength) {
4948
+ best = recipe;
4949
+ bestLength = triggerLower.length;
4136
4950
  }
4137
- return block;
4138
- });
4951
+ }
4139
4952
  }
4953
+ return best;
4140
4954
  }
4141
- return sanitizeMessages(compacted);
4142
- }
4143
- function sanitizeMessages(messages) {
4144
- const toolUseIds = /* @__PURE__ */ new Set();
4145
- const toolResultIds = /* @__PURE__ */ new Set();
4146
- for (const msg of messages) {
4147
- for (const block of msg.content) {
4148
- if (block.type === "tool_use") toolUseIds.add(block.id);
4149
- if (block.type === "tool_result") toolResultIds.add(block.toolUseId);
4955
+ /**
4956
+ * Format a matched recipe as a compact context block for the system prompt.
4957
+ * Injected dynamically — only when the recipe matches.
4958
+ */
4959
+ toPromptContext(recipe) {
4960
+ const lines = [
4961
+ `## Active Recipe: ${recipe.name}`,
4962
+ recipe.description,
4963
+ "Follow these steps:"
4964
+ ];
4965
+ for (let i = 0; i < recipe.steps.length; i++) {
4966
+ const step = recipe.steps[i];
4967
+ const num = i + 1;
4968
+ const toolNote = step.tool ? ` \u2192 ${step.tool}` : "";
4969
+ const serviceNote = step.service ? ` (${step.service})` : "";
4970
+ const costNote = step.cost ? ` \u2014 ${step.cost}` : "";
4971
+ const gateNote = step.gate && step.gate !== "none" ? ` [GATE: ${step.gate}]` : "";
4972
+ let line = `${num}. ${step.name}${toolNote}${serviceNote}${costNote}${gateNote}`;
4973
+ if (step.gate_prompt) {
4974
+ line += ` \u2014 "${step.gate_prompt}"`;
4975
+ }
4976
+ lines.push(line);
4977
+ if (step.rules?.length) {
4978
+ for (const rule of step.rules) {
4979
+ lines.push(` - ${rule}`);
4980
+ }
4981
+ }
4982
+ if (step.notes) {
4983
+ lines.push(` Note: ${step.notes}`);
4984
+ }
4985
+ if (step.on_error) {
4986
+ lines.push(` On error: ${step.on_error.action} \u2014 ${step.on_error.message}`);
4987
+ }
4988
+ if (step.condition) {
4989
+ lines.push(` Condition: ${step.condition}`);
4990
+ }
4150
4991
  }
4992
+ if (recipe.prerequisites?.length) {
4993
+ lines.push("Prerequisites (ask before starting):");
4994
+ for (const pre of recipe.prerequisites) {
4995
+ lines.push(`- ${pre.field}: "${pre.prompt}"`);
4996
+ }
4997
+ }
4998
+ return lines.join("\n");
4151
4999
  }
4152
- return messages.map((msg) => {
4153
- const filtered = msg.content.filter((block) => {
4154
- if (block.type === "tool_result") return toolUseIds.has(block.toolUseId);
4155
- if (block.type === "tool_use") return toolResultIds.has(block.id);
4156
- return true;
4157
- });
4158
- if (filtered.length === 0) return null;
4159
- return { ...msg, content: filtered };
4160
- }).filter((m) => m !== null);
5000
+ };
5001
+
5002
+ // src/classify-effort.ts
5003
+ function classifyEffort(model, userMessage, matchedRecipe, sessionWriteCount) {
5004
+ const supportsMax = model.includes("opus-4-6");
5005
+ const msg = userMessage.toLowerCase();
5006
+ if (supportsMax) {
5007
+ if (matchedRecipe?.name === "portfolio_rebalance") return "max";
5008
+ if (matchedRecipe?.name === "emergency_withdraw") return "max";
5009
+ if (/rebalance|reallocate|dca setup|close.*position/i.test(msg)) return "max";
5010
+ }
5011
+ if (matchedRecipe && matchedRecipe.steps.length >= 3) return "high";
5012
+ if (matchedRecipe?.name === "safe_borrow" || matchedRecipe?.name === "bulk_mail") return "high";
5013
+ if (sessionWriteCount > 0 && /borrow|withdraw|send|swap/i.test(msg)) return "high";
5014
+ if (/balance|rate|how much|what is|check|history|show|price/i.test(msg)) return "low";
5015
+ if (!matchedRecipe && !/deposit|send|swap|borrow|withdraw|save|pay/i.test(msg)) return "low";
5016
+ return "medium";
4161
5017
  }
4162
- function truncateToolResult(content) {
4163
- try {
4164
- const parsed = JSON.parse(content);
4165
- if (parsed.error) {
4166
- return JSON.stringify({ error: parsed.error });
5018
+
5019
+ // src/prompt-cache.ts
5020
+ function buildCachedSystemPrompt(staticParts, dynamicPart) {
5021
+ const blocks = staticParts.map((text, i) => ({
5022
+ type: "text",
5023
+ text,
5024
+ ...i === staticParts.length - 1 && { cache_control: { type: "ephemeral" } }
5025
+ }));
5026
+ if (dynamicPart) {
5027
+ blocks.push({ type: "text", text: dynamicPart });
5028
+ }
5029
+ return blocks;
5030
+ }
5031
+
5032
+ // src/intelligence.ts
5033
+ function buildProfileContext(profile) {
5034
+ if (!profile || profile.riskConfidence < 0.3) return "";
5035
+ const lines = ["User financial profile (inferred from conversation history):"];
5036
+ if (profile.riskConfidence >= 0.5) {
5037
+ lines.push(`- Risk appetite: ${profile.riskAppetite}`);
5038
+ }
5039
+ if (profile.literacyConfidence >= 0.5) {
5040
+ lines.push(`- Financial literacy: ${profile.financialLiteracy}`);
5041
+ if (profile.financialLiteracy === "advanced") {
5042
+ lines.push(" \u2192 Skip basic DeFi explanations (health factor, APY, etc). User knows these.");
4167
5043
  }
4168
- if (typeof parsed === "object" && parsed !== null) {
4169
- const summary = {};
4170
- for (const [key, value] of Object.entries(parsed)) {
4171
- if (typeof value === "number" || typeof value === "boolean") {
4172
- summary[key] = value;
4173
- } else if (typeof value === "string") {
4174
- summary[key] = value.length > 50 ? value.slice(0, 50) + "\u2026" : value;
4175
- } else if (Array.isArray(value)) {
4176
- summary[key] = `[${value.length} items]`;
4177
- } else {
4178
- summary[key] = "{\u2026}";
4179
- }
4180
- }
4181
- return JSON.stringify(summary);
5044
+ if (profile.financialLiteracy === "novice") {
5045
+ lines.push(" \u2192 Always explain DeFi concepts in plain language.");
4182
5046
  }
4183
- return content.slice(0, 100);
4184
- } catch {
4185
- return content.slice(0, 100);
5047
+ }
5048
+ if (profile.currencyFraming === "fiat") {
5049
+ lines.push('- Frame amounts as dollars (e.g. "$50" not "50 USDC")');
5050
+ }
5051
+ if (profile.prefersBriefResponses) {
5052
+ lines.push("- Prefers brief responses \u2014 be concise");
5053
+ }
5054
+ if (profile.primaryGoals.length > 0) {
5055
+ lines.push(`- Stated goals: ${profile.primaryGoals.join(", ")}`);
5056
+ }
5057
+ if (profile.knownPatterns.length > 0) {
5058
+ lines.push(`- Behavioural patterns: ${profile.knownPatterns.join(", ")}`);
5059
+ }
5060
+ return lines.join("\n");
5061
+ }
5062
+ function buildProactivenessInstructions(profile) {
5063
+ const brevityGuidance = profile?.prefersBriefResponses ? "This user prefers brevity \u2014 only surface context if urgent or directly actionable." : "Surface relevant context when criteria are met.";
5064
+ const styleGuidance = profile?.financialLiteracy === "novice" ? "Frame observations in plain English, no DeFi jargon." : "Technical framing is fine.";
5065
+ return `Proactive awareness:
5066
+ After completing the user's request, consider whether ONE additional piece of financial
5067
+ context is worth mentioning. ${brevityGuidance}
5068
+
5069
+ \u2713 Mention if:
5070
+ - Their savings goal is materially off-track (>20% behind pace)
5071
+ - Yield rate changed significantly since last session (>0.5%)
5072
+ - They have idle USDC >$50 sitting for >48h
5073
+ - An action they just took interacts with an active goal or debt position
5074
+ - A pattern would materially benefit from their attention
5075
+
5076
+ \u2717 Do NOT mention if:
5077
+ - Tangentially related but not actionable
5078
+ - Already surfaced this session
5079
+ - Requires more explanation than the original answer
5080
+ - Would seem pushy or sales-y
5081
+
5082
+ ${styleGuidance}
5083
+ Format: One sentence maximum, after main response, separated by a line break.
5084
+ Frame as observation, not advice: "Your Tokyo goal is $80 behind pace." \u2014 not "You should deposit more."`;
5085
+ }
5086
+ function buildSelfEvaluationInstruction() {
5087
+ return `Self-evaluation (apply silently before composing your response):
5088
+
5089
+ 1. ACCURACY \u2014 Quote exact values from tool results, not estimates or rounded figures.
5090
+ Never combine post-action tool results with pre-action snapshot numbers.
5091
+ If the tool returned an error, label it as an error \u2014 do not paraphrase it as success.
5092
+
5093
+ 2. STATE CONSISTENCY \u2014 Describe the actual outcome of all steps.
5094
+ Partial success (swap ok, deposit failed): describe both clearly.
5095
+ Never describe a failed action as if it succeeded.
5096
+
5097
+ 3. COMPLETENESS \u2014 If the user asked multiple things, answer all of them.
5098
+ If you couldn't complete something, explain why and what the current state is.
5099
+
5100
+ 4. TONE \u2014 Match tone to outcome.
5101
+ Success: confirming and forward-looking.
5102
+ Failure: clear about what failed, unchanged, and what to do next.
5103
+ Warning: specific risk, not generic caution.
5104
+
5105
+ If any check fails, rewrite before outputting.`;
5106
+ }
5107
+
5108
+ // src/state/conversation-state.ts
5109
+ function buildStateContext(state) {
5110
+ switch (state.type) {
5111
+ case "idle":
5112
+ return "";
5113
+ case "mid_recipe": {
5114
+ const elapsed = Math.round((Date.now() - state.startedAt) / 6e4);
5115
+ const outputs = JSON.stringify(state.completedStepOutputs);
5116
+ return [
5117
+ `Conversation state: MID-RECIPE`,
5118
+ `Active recipe: ${state.recipeName} (step ${state.currentStep + 1} of ${state.totalSteps})`,
5119
+ `Started: ${elapsed} minutes ago`,
5120
+ `Completed step key outputs: ${outputs}`,
5121
+ `If the user asks an unrelated question: answer briefly, then offer to continue the ${state.recipeName} flow.`,
5122
+ `If the user says "cancel" or "stop": confirm you have abandoned the recipe and return to idle.`
5123
+ ].join("\n");
5124
+ }
5125
+ case "awaiting_confirmation": {
5126
+ const expiryMins = Math.max(0, Math.round((state.expiresAt - Date.now()) / 6e4));
5127
+ const expired = state.expiresAt < Date.now();
5128
+ return [
5129
+ `Conversation state: AWAITING CONFIRMATION`,
5130
+ `Proposed action: ${state.action}${state.amount ? ` for $${state.amount}` : ""}${state.recipient ? ` to ${state.recipient}` : ""}`,
5131
+ expired ? `Status: EXPIRED \u2014 ask if user still wants to proceed` : `Expires in: ${expiryMins} minutes`,
5132
+ `"yes/confirm/do it" \u2192 execute. "no/cancel/wait" \u2192 abort, reset to idle.`
5133
+ ].join("\n");
5134
+ }
5135
+ case "post_error":
5136
+ return [
5137
+ `Conversation state: POST-ERROR`,
5138
+ `Failed action: ${state.failedAction}`,
5139
+ `Error: ${state.errorMessage}`,
5140
+ state.partialState ? `Partial state: ${state.partialState}` : "",
5141
+ `Acknowledge failure clearly. Offer a specific recovery path if one exists.`,
5142
+ `This state clears automatically on the next successful action.`
5143
+ ].filter(Boolean).join("\n");
5144
+ case "post_liquidation_warning":
5145
+ return [
5146
+ `Conversation state: LIQUIDATION WARNING ACTIVE`,
5147
+ `Health factor: ${state.healthFactor.toFixed(2)} \u2014 below safe threshold`,
5148
+ `Prioritise debt repayment or collateral deposit.`,
5149
+ `Do not proceed with any action that would further reduce health factor.`
5150
+ ].join("\n");
5151
+ case "onboarding":
5152
+ return [
5153
+ `Conversation state: ONBOARDING (session ${state.sessionNumber})`,
5154
+ state.sessionNumber === 1 ? "First session \u2014 introduce capabilities through context, not a feature list." : `Returning user \u2014 ${state.hasSavedBefore ? "has saved before" : "has not saved yet"}.`
5155
+ ].join("\n");
5156
+ default:
5157
+ return "";
4186
5158
  }
4187
5159
  }
4188
5160
 
@@ -4419,6 +5391,7 @@ function adaptMcpTool(mcpTool, config) {
4419
5391
  isReadOnly,
4420
5392
  isConcurrencySafe: isReadOnly,
4421
5393
  permissionLevel,
5394
+ flags: {},
4422
5395
  async call(input, _context) {
4423
5396
  const result = await config.manager.callTool(
4424
5397
  config.serverName,
@@ -4484,17 +5457,26 @@ var AnthropicProvider = class {
4484
5457
  toolChoice = { type: "tool", name: params.toolChoice.name };
4485
5458
  }
4486
5459
  }
4487
- const streamParams = {
5460
+ const thinkingParam = toAnthropicThinking(params.thinking);
5461
+ const systemParam = toAnthropicSystem(params.systemPrompt);
5462
+ const baseParams = {
4488
5463
  model: params.model ?? this.defaultModel,
4489
5464
  max_tokens: params.maxTokens ?? this.defaultMaxTokens,
4490
- system: params.systemPrompt,
5465
+ system: systemParam,
4491
5466
  messages,
5467
+ stream: true,
4492
5468
  tools: tools.length > 0 ? tools : void 0,
4493
- ...params.temperature !== void 0 && { temperature: params.temperature },
5469
+ ...!thinkingParam && params.temperature !== void 0 && { temperature: params.temperature },
4494
5470
  ...toolChoice && { tool_choice: toolChoice }
4495
5471
  };
5472
+ const streamParams = {
5473
+ ...baseParams,
5474
+ ...thinkingParam && { thinking: thinkingParam },
5475
+ ...params.outputConfig?.effort && { output_config: { effort: params.outputConfig.effort } }
5476
+ };
4496
5477
  const stream = params.signal ? this.client.messages.stream(streamParams, { signal: params.signal }) : this.client.messages.stream(streamParams);
4497
5478
  const toolInputBuffers = /* @__PURE__ */ new Map();
5479
+ const thinkingBuffers = /* @__PURE__ */ new Map();
4498
5480
  let outputTokensFromStart = 0;
4499
5481
  try {
4500
5482
  for await (const event of stream) {
@@ -4532,6 +5514,10 @@ var AnthropicProvider = class {
4532
5514
  id: block.id,
4533
5515
  name: block.name
4534
5516
  };
5517
+ } else if (block.type === "thinking") {
5518
+ thinkingBuffers.set(event.index, { type: "thinking", text: "", signature: "" });
5519
+ } else if (block.type === "redacted_thinking") {
5520
+ thinkingBuffers.set(event.index, { type: "redacted_thinking", data: block.data ?? "" });
4535
5521
  }
4536
5522
  break;
4537
5523
  }
@@ -4549,26 +5535,41 @@ var AnthropicProvider = class {
4549
5535
  partialJson: delta.partial_json
4550
5536
  };
4551
5537
  }
5538
+ } else if (delta.type === "thinking_delta") {
5539
+ const buf = thinkingBuffers.get(event.index);
5540
+ if (buf?.type === "thinking") buf.text += delta.thinking ?? "";
5541
+ yield { type: "thinking_delta", text: delta.thinking ?? "" };
5542
+ } else if (delta.type === "signature_delta") {
5543
+ const buf = thinkingBuffers.get(event.index);
5544
+ if (buf?.type === "thinking") buf.signature = delta.signature ?? "";
4552
5545
  }
4553
5546
  break;
4554
5547
  }
4555
5548
  case "content_block_stop": {
4556
- const buf = toolInputBuffers.get(event.index);
4557
- if (buf) {
5549
+ const toolBuf = toolInputBuffers.get(event.index);
5550
+ if (toolBuf) {
4558
5551
  let input = {};
4559
5552
  try {
4560
- input = JSON.parse(buf.json || "{}");
5553
+ input = JSON.parse(toolBuf.json || "{}");
4561
5554
  } catch {
4562
5555
  input = {};
4563
5556
  }
4564
5557
  yield {
4565
5558
  type: "tool_use_done",
4566
- id: buf.id,
4567
- name: buf.name,
5559
+ id: toolBuf.id,
5560
+ name: toolBuf.name,
4568
5561
  input
4569
5562
  };
4570
5563
  toolInputBuffers.delete(event.index);
4571
5564
  }
5565
+ const thinkBuf = thinkingBuffers.get(event.index);
5566
+ if (thinkBuf?.type === "thinking") {
5567
+ yield { type: "thinking_done", thinking: thinkBuf.text, signature: thinkBuf.signature };
5568
+ thinkingBuffers.delete(event.index);
5569
+ } else if (thinkBuf?.type === "redacted_thinking") {
5570
+ yield { type: "redacted_thinking", data: thinkBuf.data };
5571
+ thinkingBuffers.delete(event.index);
5572
+ }
4572
5573
  break;
4573
5574
  }
4574
5575
  case "message_delta": {
@@ -4598,11 +5599,28 @@ var AnthropicProvider = class {
4598
5599
  }
4599
5600
  }
4600
5601
  };
5602
+ function toAnthropicSystem(prompt) {
5603
+ if (typeof prompt === "string") return prompt;
5604
+ return prompt.map((block) => ({
5605
+ type: "text",
5606
+ text: block.text,
5607
+ ...block.cache_control && { cache_control: block.cache_control }
5608
+ }));
5609
+ }
5610
+ function toAnthropicThinking(config) {
5611
+ if (!config || config.type === "disabled") return void 0;
5612
+ if (config.type === "adaptive") return { type: "adaptive" };
5613
+ return { type: "enabled", budget_tokens: config.budgetTokens };
5614
+ }
4601
5615
  function toAnthropicMessage(msg) {
4602
5616
  const content = msg.content.map((block) => {
4603
5617
  switch (block.type) {
4604
5618
  case "text":
4605
5619
  return { type: "text", text: block.text };
5620
+ case "thinking":
5621
+ return { type: "thinking", thinking: block.thinking, signature: block.signature };
5622
+ case "redacted_thinking":
5623
+ return { type: "redacted_thinking", data: block.data };
4606
5624
  case "tool_use":
4607
5625
  return {
4608
5626
  type: "tool_use",
@@ -4618,7 +5636,7 @@ function toAnthropicMessage(msg) {
4618
5636
  is_error: block.isError
4619
5637
  };
4620
5638
  }
4621
- });
5639
+ }).filter((b) => b !== null);
4622
5640
  return { role: msg.role, content };
4623
5641
  }
4624
5642
  function toAnthropicTool(def) {
@@ -4707,6 +5725,6 @@ function sanitizeAnthropicMessages(messages) {
4707
5725
  return merged;
4708
5726
  }
4709
5727
 
4710
- export { AnthropicProvider, CANVAS_TEMPLATES, CostTracker, DEFAULT_SYSTEM_PROMPT, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, NaviTools, QueryEngine, READ_TOOLS, TxMutex, WRITE_TOOLS, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, balanceCheckTool, borrowTool, buildMcpTools, buildTool, claimRewardsTool, clearPriceCache, compactMessages, defillamaChainTvlTool, defillamaPriceChangeTool, defillamaProtocolFeesTool, defillamaProtocolInfoTool, defillamaSuiProtocolsTool, defillamaTokenPricesTool, defillamaYieldPoolsTool, engineToSSE, estimateTokens, explainTxTool, extractMcpText, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getDefaultTools, getMcpManager, getWalletAddress, hasNaviMcp, healthCheckTool, mppServicesTool, parseMcpJson, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
5728
+ export { AnthropicProvider, BalanceTracker, CANVAS_TEMPLATES, ContextBudget, CostTracker, DEFAULT_GUARD_CONFIG, DEFAULT_SYSTEM_PROMPT, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, NaviTools, QueryEngine, READ_TOOLS, RecipeRegistry, RetryTracker, TOOL_FLAGS, TxMutex, WRITE_TOOLS, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, balanceCheckTool, borrowTool, buildCachedSystemPrompt, buildMcpTools, buildProactivenessInstructions, buildProfileContext, buildSelfEvaluationInstruction, buildStateContext, buildTool, claimRewardsTool, classifyEffort, clearPriceCache, compactMessages, createGuardRunnerState, defillamaChainTvlTool, defillamaPriceChangeTool, defillamaProtocolFeesTool, defillamaProtocolInfoTool, defillamaSuiProtocolsTool, defillamaTokenPricesTool, defillamaYieldPoolsTool, engineToSSE, estimateTokens, explainTxTool, extractConversationText, extractMcpText, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getDefaultTools, getMcpManager, getToolFlags, getWalletAddress, guardArtifactPreview, guardStaleData, hasNaviMcp, healthCheckTool, loadRecipes, mppServicesTool, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
4711
5729
  //# sourceMappingURL=index.js.map
4712
5730
  //# sourceMappingURL=index.js.map