@t2000/engine 0.31.1 → 0.32.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
@@ -7,15 +7,18 @@ import Anthropic from '@anthropic-ai/sdk';
7
7
 
8
8
  // src/tool.ts
9
9
  function buildTool(opts) {
10
+ const isReadOnly = opts.isReadOnly ?? true;
10
11
  return {
11
12
  name: opts.name,
12
13
  description: opts.description,
13
14
  inputSchema: opts.inputSchema,
14
15
  jsonSchema: opts.jsonSchema,
15
16
  call: opts.call,
16
- isReadOnly: opts.isReadOnly ?? true,
17
- isConcurrencySafe: opts.isReadOnly ?? true,
18
- permissionLevel: opts.permissionLevel ?? (opts.isReadOnly === false ? "confirm" : "auto")
17
+ isReadOnly,
18
+ isConcurrencySafe: isReadOnly,
19
+ permissionLevel: opts.permissionLevel ?? (isReadOnly ? "auto" : "confirm"),
20
+ flags: opts.flags ?? {},
21
+ preflight: opts.preflight
19
22
  };
20
23
  }
21
24
  function toolsToDefinitions(tools) {
@@ -152,6 +155,45 @@ async function executeSingleTool(tool, call, context) {
152
155
  const result = await tool.call(parsed.data, context);
153
156
  return { data: result.data, isError: false };
154
157
  }
158
+
159
+ // src/tool-flags.ts
160
+ var TOOL_FLAGS = {
161
+ // Write tools — financial
162
+ save_deposit: { mutating: true, requiresBalance: true },
163
+ withdraw: { mutating: true, affectsHealth: true },
164
+ send_transfer: { mutating: true, requiresBalance: true, irreversible: true },
165
+ swap_execute: { mutating: true, requiresBalance: true },
166
+ borrow: { mutating: true, affectsHealth: true },
167
+ repay_debt: { mutating: true, requiresBalance: true },
168
+ claim_rewards: { mutating: true },
169
+ volo_stake: { mutating: true, requiresBalance: true },
170
+ volo_unstake: { mutating: true },
171
+ // Write tools — pay / services
172
+ pay_api: { mutating: true, requiresBalance: true, costAware: true, producesArtifact: true, maxRetries: 1 },
173
+ // Write tools — lightweight (no financial guards)
174
+ save_contact: {},
175
+ create_schedule: { mutating: true },
176
+ cancel_schedule: { mutating: true },
177
+ // Allowance tools — API mutations disguised as reads
178
+ toggle_allowance: { mutating: true },
179
+ update_daily_limit: { mutating: true },
180
+ update_permissions: { mutating: true },
181
+ // Receive tools — create/cancel mutate server state
182
+ create_payment_link: { mutating: true },
183
+ cancel_payment_link: { mutating: true },
184
+ create_invoice: { mutating: true },
185
+ cancel_invoice: { mutating: true }
186
+ };
187
+ function applyToolFlags(tools) {
188
+ return tools.map((tool) => {
189
+ const flags = TOOL_FLAGS[tool.name];
190
+ if (!flags) return tool;
191
+ return { ...tool, flags: { ...tool.flags, ...flags } };
192
+ });
193
+ }
194
+ function getToolFlags(name) {
195
+ return TOOL_FLAGS[name] ?? {};
196
+ }
155
197
  var SUI_MAINNET_URL = "https://fullnode.mainnet.sui.io:443";
156
198
  var EXTRA_COINS = {
157
199
  "0xc060006111016b8a020ad5b33834984a437aaa7d3c74c18e09a95d48aceab08c::coin::COIN": { symbol: "USDT", decimals: 6 }
@@ -992,7 +1034,7 @@ function parseRpcTx(tx, address) {
992
1034
  gasCost
993
1035
  };
994
1036
  }
995
- async function queryHistoryRpc(rpcUrl, address, limit) {
1037
+ async function queryHistoryPage(rpcUrl, address, limit, cursor) {
996
1038
  const res = await fetch(rpcUrl, {
997
1039
  method: "POST",
998
1040
  headers: { "Content-Type": "application/json" },
@@ -1002,7 +1044,7 @@ async function queryHistoryRpc(rpcUrl, address, limit) {
1002
1044
  method: "suix_queryTransactionBlocks",
1003
1045
  params: [
1004
1046
  { filter: { FromAddress: address }, options: { showEffects: true, showInput: true, showBalanceChanges: true } },
1005
- null,
1047
+ cursor,
1006
1048
  limit,
1007
1049
  true
1008
1050
  ]
@@ -1012,13 +1054,48 @@ async function queryHistoryRpc(rpcUrl, address, limit) {
1012
1054
  if (!res.ok) throw new Error(`Sui RPC error: ${res.status}`);
1013
1055
  const json = await res.json();
1014
1056
  if (json.error) throw new Error(`RPC error: ${json.error.message}`);
1015
- return (json.result?.data ?? []).map((tx) => parseRpcTx(tx, address));
1057
+ return {
1058
+ data: json.result?.data ?? [],
1059
+ nextCursor: json.result?.nextCursor ?? null,
1060
+ hasNextPage: json.result?.hasNextPage ?? false
1061
+ };
1062
+ }
1063
+ async function queryHistoryRpc(rpcUrl, address, limit) {
1064
+ const page = await queryHistoryPage(rpcUrl, address, limit, null);
1065
+ return page.data.map((tx) => parseRpcTx(tx, address));
1066
+ }
1067
+ async function queryHistoryByDate(rpcUrl, address, targetDate, limit) {
1068
+ const target = new Date(targetDate);
1069
+ const dayStart = new Date(target.getFullYear(), target.getMonth(), target.getDate()).getTime();
1070
+ const dayEnd = dayStart + 864e5;
1071
+ const MAX_PAGES = 20;
1072
+ const PAGE_SIZE = 50;
1073
+ const results = [];
1074
+ let cursor = null;
1075
+ for (let page = 0; page < MAX_PAGES; page++) {
1076
+ const res = await queryHistoryPage(rpcUrl, address, PAGE_SIZE, cursor);
1077
+ if (res.data.length === 0) break;
1078
+ for (const tx of res.data) {
1079
+ const ts = Number(tx.timestampMs ?? 0);
1080
+ if (ts === 0) continue;
1081
+ if (ts < dayStart) {
1082
+ return results.slice(0, limit);
1083
+ }
1084
+ if (ts >= dayStart && ts < dayEnd) {
1085
+ results.push(parseRpcTx(tx, address));
1086
+ }
1087
+ }
1088
+ if (!res.hasNextPage || !res.nextCursor) break;
1089
+ cursor = res.nextCursor;
1090
+ }
1091
+ return results.slice(0, limit);
1016
1092
  }
1017
1093
  var transactionHistoryTool = buildTool({
1018
1094
  name: "transaction_history",
1019
- description: "Retrieve recent transaction history: past sends, saves, withdrawals, borrows, repayments, and rewards claims. Optionally limit the number of results.",
1095
+ description: "Retrieve transaction history: past sends, saves, withdrawals, borrows, repayments, and rewards claims. Pass a date (YYYY-MM-DD) to find transactions from a specific day, or omit for the most recent.",
1020
1096
  inputSchema: z.object({
1021
- limit: z.number().int().min(1).max(50).optional()
1097
+ limit: z.number().int().min(1).max(50).optional(),
1098
+ date: z.string().optional().describe("Specific date to search for transactions (YYYY-MM-DD format). Paginates back to find that day.")
1022
1099
  }),
1023
1100
  jsonSchema: {
1024
1101
  type: "object",
@@ -1026,6 +1103,10 @@ var transactionHistoryTool = buildTool({
1026
1103
  limit: {
1027
1104
  type: "number",
1028
1105
  description: "Maximum number of transactions to return (1-50, default 10)"
1106
+ },
1107
+ date: {
1108
+ type: "string",
1109
+ description: "Specific date to search for transactions (YYYY-MM-DD format). Paginates back to find that day."
1029
1110
  }
1030
1111
  }
1031
1112
  },
@@ -1036,16 +1117,24 @@ var transactionHistoryTool = buildTool({
1036
1117
  const agent = requireAgent(context);
1037
1118
  const records2 = await agent.history({ limit });
1038
1119
  return {
1039
- data: { transactions: records2, count: records2.length },
1120
+ data: { transactions: records2, count: records2.length, date: input.date ?? null },
1040
1121
  displayText: `${records2.length} recent transaction(s)`
1041
1122
  };
1042
1123
  }
1043
1124
  if (!context.walletAddress || !context.suiRpcUrl) {
1044
1125
  throw new Error("Transaction history requires a wallet address");
1045
1126
  }
1127
+ if (input.date) {
1128
+ const records2 = await queryHistoryByDate(context.suiRpcUrl, context.walletAddress, input.date, limit);
1129
+ const dateLabel = new Date(input.date).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
1130
+ return {
1131
+ data: { transactions: records2, count: records2.length, date: input.date },
1132
+ displayText: records2.length > 0 ? `${records2.length} transaction(s) on ${dateLabel}` : `No transactions found on ${dateLabel}`
1133
+ };
1134
+ }
1046
1135
  const records = await queryHistoryRpc(context.suiRpcUrl, context.walletAddress, limit);
1047
1136
  return {
1048
- data: { transactions: records, count: records.length },
1137
+ data: { transactions: records, count: records.length, date: null },
1049
1138
  displayText: `${records.length} recent transaction(s)`
1050
1139
  };
1051
1140
  }
@@ -1072,6 +1161,13 @@ var saveDepositTool = buildTool({
1072
1161
  },
1073
1162
  isReadOnly: false,
1074
1163
  permissionLevel: "confirm",
1164
+ flags: { mutating: true, requiresBalance: true },
1165
+ preflight: (input) => {
1166
+ if (input.asset && input.asset.toUpperCase() !== "USDC") {
1167
+ return { valid: false, error: `Only USDC deposits are supported. Got: "${input.asset}"` };
1168
+ }
1169
+ return { valid: true };
1170
+ },
1075
1171
  async call(input, context) {
1076
1172
  assertAllowedAsset("save", input.asset);
1077
1173
  const agent = requireAgent(context);
@@ -1113,6 +1209,7 @@ var withdrawTool = buildTool({
1113
1209
  },
1114
1210
  isReadOnly: false,
1115
1211
  permissionLevel: "confirm",
1212
+ flags: { mutating: true, affectsHealth: true },
1116
1213
  async call(input, context) {
1117
1214
  const agent = requireAgent(context);
1118
1215
  const result = await agent.withdraw({
@@ -1160,6 +1257,16 @@ var sendTransferTool = buildTool({
1160
1257
  },
1161
1258
  isReadOnly: false,
1162
1259
  permissionLevel: "confirm",
1260
+ flags: { mutating: true, requiresBalance: true, irreversible: true },
1261
+ preflight: (input) => {
1262
+ if (input.to.startsWith("0x") && !/^0x[a-fA-F0-9]{64}$/.test(input.to)) {
1263
+ return { valid: false, error: `Invalid Sui address format: "${input.to}". Must be 0x followed by 64 hex characters.` };
1264
+ }
1265
+ if (input.amount <= 0) {
1266
+ return { valid: false, error: "Amount must be positive." };
1267
+ }
1268
+ return { valid: true };
1269
+ },
1163
1270
  async call(input, context) {
1164
1271
  const agent = requireAgent(context);
1165
1272
  const result = await agent.send({ to: input.to, amount: input.amount });
@@ -1202,6 +1309,13 @@ var borrowTool = buildTool({
1202
1309
  },
1203
1310
  isReadOnly: false,
1204
1311
  permissionLevel: "confirm",
1312
+ flags: { mutating: true, affectsHealth: true },
1313
+ preflight: (input) => {
1314
+ if (input.asset && input.asset.toUpperCase() !== "USDC") {
1315
+ return { valid: false, error: `Only USDC borrows are supported. Got: "${input.asset}"` };
1316
+ }
1317
+ return { valid: true };
1318
+ },
1205
1319
  async call(input, context) {
1206
1320
  assertAllowedAsset("borrow", input.asset);
1207
1321
  const agent = requireAgent(context);
@@ -1236,6 +1350,7 @@ var repayDebtTool = buildTool({
1236
1350
  },
1237
1351
  isReadOnly: false,
1238
1352
  permissionLevel: "confirm",
1353
+ flags: { mutating: true, requiresBalance: true },
1239
1354
  async call(input, context) {
1240
1355
  const agent = requireAgent(context);
1241
1356
  const result = await agent.repay({ amount: input.amount });
@@ -1258,6 +1373,7 @@ var claimRewardsTool = buildTool({
1258
1373
  jsonSchema: { type: "object", properties: {}, required: [] },
1259
1374
  isReadOnly: false,
1260
1375
  permissionLevel: "confirm",
1376
+ flags: { mutating: true },
1261
1377
  async call(_input, context) {
1262
1378
  const agent = requireAgent(context);
1263
1379
  const result = await agent.claimRewards();
@@ -1332,6 +1448,28 @@ Always use ISO-3166 country codes (GB not UK, US not USA). A return address ("fr
1332
1448
  },
1333
1449
  isReadOnly: false,
1334
1450
  permissionLevel: "confirm",
1451
+ flags: { mutating: true, requiresBalance: true, costAware: true, producesArtifact: true, maxRetries: 1 },
1452
+ preflight: (input) => {
1453
+ if (!input.url.startsWith(MPP_GATEWAY)) {
1454
+ return { valid: false, error: `URL must start with ${MPP_GATEWAY}. Got: "${input.url}"` };
1455
+ }
1456
+ if (input.body) {
1457
+ try {
1458
+ JSON.parse(input.body);
1459
+ } catch {
1460
+ return { valid: false, error: "body must be valid JSON." };
1461
+ }
1462
+ if (input.url.includes("lob/")) {
1463
+ const body = JSON.parse(input.body);
1464
+ const to = body.to;
1465
+ const country = to?.address_country;
1466
+ if (typeof country === "string" && country.length !== 2) {
1467
+ return { valid: false, error: `Country must be ISO-3166 2-letter code (got "${country}")` };
1468
+ }
1469
+ }
1470
+ }
1471
+ return { valid: true };
1472
+ },
1335
1473
  async call(input, context) {
1336
1474
  const agent = requireAgent(context);
1337
1475
  const result = await agent.pay({
@@ -1433,6 +1571,13 @@ var swapExecuteTool = buildTool({
1433
1571
  },
1434
1572
  isReadOnly: false,
1435
1573
  permissionLevel: "confirm",
1574
+ flags: { mutating: true, requiresBalance: true },
1575
+ preflight: (input) => {
1576
+ if (input.from.toLowerCase() === input.to.toLowerCase()) {
1577
+ return { valid: false, error: `Cannot swap ${input.from} to itself.` };
1578
+ }
1579
+ return { valid: true };
1580
+ },
1436
1581
  async call(input, context) {
1437
1582
  const agent = requireAgent(context);
1438
1583
  const result = await agent.swap({
@@ -1507,6 +1652,7 @@ var voloStakeTool = buildTool({
1507
1652
  },
1508
1653
  isReadOnly: false,
1509
1654
  permissionLevel: "confirm",
1655
+ flags: { mutating: true, requiresBalance: true },
1510
1656
  async call(input, context) {
1511
1657
  const agent = requireAgent(context);
1512
1658
  const result = await agent.stakeVSui({ amount: input.amount });
@@ -1537,6 +1683,7 @@ var voloUnstakeTool = buildTool({
1537
1683
  },
1538
1684
  isReadOnly: false,
1539
1685
  permissionLevel: "confirm",
1686
+ flags: { mutating: true },
1540
1687
  async call(input, context) {
1541
1688
  const agent = requireAgent(context);
1542
1689
  const result = await agent.unstakeVSui({ amount: input.amount });
@@ -3369,7 +3516,7 @@ var WRITE_TOOLS = [
3369
3516
  cancelScheduleTool
3370
3517
  ];
3371
3518
  function getDefaultTools() {
3372
- return [...READ_TOOLS, ...WRITE_TOOLS];
3519
+ return applyToolFlags([...READ_TOOLS, ...WRITE_TOOLS]);
3373
3520
  }
3374
3521
 
3375
3522
  // src/prompt.ts
@@ -3470,6 +3617,330 @@ var CostTracker = class {
3470
3617
  }
3471
3618
  };
3472
3619
 
3620
+ // src/guards.ts
3621
+ var DEFAULT_GUARD_CONFIG = {
3622
+ balanceValidation: true,
3623
+ healthFactor: { warnBelow: 2, blockBelow: 1.5 },
3624
+ largeTransfer: { warnAbove: 50, strongWarnAbove: 500 },
3625
+ slippage: true,
3626
+ staleData: true,
3627
+ irreversibility: true,
3628
+ artifactPreview: true,
3629
+ costWarning: true,
3630
+ retryProtection: true,
3631
+ inputValidation: true
3632
+ };
3633
+ var BalanceTracker = class {
3634
+ lastBalanceAt = 0;
3635
+ lastWriteAt = 0;
3636
+ recordRead() {
3637
+ this.lastBalanceAt = Date.now();
3638
+ }
3639
+ recordWrite() {
3640
+ this.lastWriteAt = Date.now();
3641
+ }
3642
+ isStale() {
3643
+ return this.lastWriteAt > this.lastBalanceAt;
3644
+ }
3645
+ hasEverRead() {
3646
+ return this.lastBalanceAt > 0;
3647
+ }
3648
+ };
3649
+ var BALANCE_READ_TOOLS = /* @__PURE__ */ new Set([
3650
+ "balance_check",
3651
+ "savings_info",
3652
+ "health_check"
3653
+ ]);
3654
+ var RetryTracker = class {
3655
+ executed = /* @__PURE__ */ new Map();
3656
+ key(toolName, input) {
3657
+ const url = input?.url ?? "";
3658
+ return `${toolName}:${url}`;
3659
+ }
3660
+ record(toolName, input, result) {
3661
+ const r = result;
3662
+ if (r?.paymentConfirmed || r?.doNotRetry) {
3663
+ this.executed.set(this.key(toolName, input), { result, paidAt: Date.now() });
3664
+ }
3665
+ }
3666
+ isBlocked(toolName, input) {
3667
+ const prev = this.executed.get(this.key(toolName, input));
3668
+ if (!prev) return { blocked: false };
3669
+ return { blocked: true, previousResult: prev.result };
3670
+ }
3671
+ };
3672
+ function guardRetryProtection(tool, call, retryTracker) {
3673
+ const check = retryTracker.isBlocked(tool.name, call.input);
3674
+ if (check.blocked) {
3675
+ return {
3676
+ verdict: "block",
3677
+ gate: "retry_blocked",
3678
+ tier: "safety",
3679
+ message: `Blocked: ${tool.name} was already called and payment was confirmed. Do not retry.`
3680
+ };
3681
+ }
3682
+ return { verdict: "pass", gate: "retry_blocked", tier: "safety" };
3683
+ }
3684
+ function guardIrreversibility(tool, _call, conversationText) {
3685
+ if (!tool.flags.irreversible) {
3686
+ return { verdict: "pass", gate: "irreversibility", tier: "safety" };
3687
+ }
3688
+ const hasPreview = /preview|here.s what|confirm.*send|looks? good/i.test(conversationText);
3689
+ if (hasPreview) {
3690
+ return { verdict: "pass", gate: "irreversibility", tier: "safety" };
3691
+ }
3692
+ return {
3693
+ verdict: "hint",
3694
+ gate: "irreversibility",
3695
+ tier: "safety",
3696
+ message: "This action is irreversible. Show a preview and ask the user to confirm before proceeding."
3697
+ };
3698
+ }
3699
+ function guardBalanceValidation(tool, _call, balanceTracker) {
3700
+ if (!tool.flags.requiresBalance) {
3701
+ return { verdict: "pass", gate: "balance_required", tier: "financial" };
3702
+ }
3703
+ if (!balanceTracker.hasEverRead()) {
3704
+ return {
3705
+ verdict: "hint",
3706
+ gate: "balance_required",
3707
+ tier: "financial",
3708
+ message: "Balance has not been checked this session. Call balance_check first to verify sufficient funds."
3709
+ };
3710
+ }
3711
+ if (balanceTracker.isStale()) {
3712
+ return {
3713
+ verdict: "hint",
3714
+ gate: "balance_required",
3715
+ tier: "financial",
3716
+ message: "Balance data is stale (a write action occurred since last check). Call balance_check first to verify sufficient funds."
3717
+ };
3718
+ }
3719
+ return { verdict: "pass", gate: "balance_required", tier: "financial" };
3720
+ }
3721
+ function guardHealthFactor(tool, _call, lastHealthFactor, config) {
3722
+ if (!tool.flags.affectsHealth) {
3723
+ return { verdict: "pass", gate: "health_factor", tier: "financial" };
3724
+ }
3725
+ if (lastHealthFactor === null) {
3726
+ return {
3727
+ verdict: "hint",
3728
+ gate: "health_factor",
3729
+ tier: "financial",
3730
+ message: "Health factor has not been checked this session. Call health_check before this action."
3731
+ };
3732
+ }
3733
+ if (lastHealthFactor < config.blockBelow) {
3734
+ return {
3735
+ verdict: "block",
3736
+ gate: "health_factor",
3737
+ tier: "financial",
3738
+ message: `Health factor is ${lastHealthFactor.toFixed(2)} \u2014 this action risks liquidation. Refusing.`
3739
+ };
3740
+ }
3741
+ if (lastHealthFactor < config.warnBelow) {
3742
+ return {
3743
+ verdict: "warn",
3744
+ gate: "health_factor",
3745
+ tier: "financial",
3746
+ message: `Health factor is ${lastHealthFactor.toFixed(2)} \u2014 this action may reduce it further.`
3747
+ };
3748
+ }
3749
+ return { verdict: "pass", gate: "health_factor", tier: "financial" };
3750
+ }
3751
+ function guardLargeTransfer(tool, call, config) {
3752
+ if (tool.name !== "send_transfer") {
3753
+ return { verdict: "pass", gate: "large_transfer", tier: "financial" };
3754
+ }
3755
+ const input = call.input;
3756
+ const amount = Number(input.amount ?? 0);
3757
+ if (!amount || amount <= 0) {
3758
+ return { verdict: "pass", gate: "large_transfer", tier: "financial" };
3759
+ }
3760
+ const recipient = String(input.recipient ?? input.to ?? "");
3761
+ const shortAddr = recipient.length > 10 ? `${recipient.slice(0, 6)}...${recipient.slice(-4)}` : recipient;
3762
+ if (amount > config.strongWarnAbove) {
3763
+ return {
3764
+ verdict: "warn",
3765
+ gate: "large_transfer",
3766
+ tier: "financial",
3767
+ message: `High-value transfer ($${amount}). Double-check the address: ${shortAddr}`
3768
+ };
3769
+ }
3770
+ if (amount > config.warnAbove) {
3771
+ return {
3772
+ verdict: "hint",
3773
+ gate: "large_transfer",
3774
+ tier: "financial",
3775
+ message: `This is a large transfer ($${amount}). Verify the recipient address.`
3776
+ };
3777
+ }
3778
+ return { verdict: "pass", gate: "large_transfer", tier: "financial" };
3779
+ }
3780
+ function guardSlippage(tool, _call, lastAssistantText) {
3781
+ if (tool.name !== "swap_execute") {
3782
+ return { verdict: "pass", gate: "slippage_warning", tier: "financial" };
3783
+ }
3784
+ const hasEstimate = /~?\$?[\d,]+\.?\d*\s*(SUI|USDC|USDT|WETH)/i.test(lastAssistantText) || /approximately|≈|about|expect|receive/i.test(lastAssistantText);
3785
+ if (hasEstimate) {
3786
+ return { verdict: "pass", gate: "slippage_warning", tier: "financial" };
3787
+ }
3788
+ return {
3789
+ verdict: "hint",
3790
+ gate: "slippage_warning",
3791
+ tier: "financial",
3792
+ message: "State the expected output amount to the user before executing the swap."
3793
+ };
3794
+ }
3795
+ function guardCostWarning(tool, _call, conversationText) {
3796
+ if (!tool.flags.costAware) {
3797
+ return { verdict: "pass", gate: "cost_warning", tier: "ux" };
3798
+ }
3799
+ const hasCostMention = /\$\d+\.?\d*|cost|fee|charge|price|pay/i.test(conversationText);
3800
+ if (hasCostMention) {
3801
+ return { verdict: "pass", gate: "cost_warning", tier: "ux" };
3802
+ }
3803
+ return {
3804
+ verdict: "hint",
3805
+ gate: "cost_warning",
3806
+ tier: "ux",
3807
+ message: "This action has a monetary cost. Confirm the user is aware before proceeding."
3808
+ };
3809
+ }
3810
+ function guardArtifactPreview(result) {
3811
+ if (!result || typeof result !== "object") return null;
3812
+ const r = result;
3813
+ 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";
3814
+ const hasPdf = typeof r.url === "string" && /\.pdf(\?|$)/i.test(r.url);
3815
+ if (hasImage || hasPdf) {
3816
+ return {
3817
+ _gate: "artifact_preview",
3818
+ _hint: "Show this to the user before proceeding. Output as ![description](url)."
3819
+ };
3820
+ }
3821
+ return null;
3822
+ }
3823
+ function guardStaleData(toolFlags) {
3824
+ if (!toolFlags.mutating) return null;
3825
+ return {
3826
+ _gate: "stale_data",
3827
+ _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."
3828
+ };
3829
+ }
3830
+ function createGuardRunnerState() {
3831
+ return {
3832
+ balanceTracker: new BalanceTracker(),
3833
+ retryTracker: new RetryTracker(),
3834
+ lastHealthFactor: null
3835
+ };
3836
+ }
3837
+ function runGuards(tool, call, state, config, conversationContext) {
3838
+ const results = [];
3839
+ const now = Date.now();
3840
+ if (config.inputValidation !== false && tool.preflight) {
3841
+ const check = tool.preflight(call.input);
3842
+ if (!check.valid) {
3843
+ const event = {
3844
+ timestamp: now,
3845
+ toolName: tool.name,
3846
+ toolUseId: call.id,
3847
+ gate: "input_validation",
3848
+ verdict: "block",
3849
+ tier: "safety",
3850
+ message: check.error
3851
+ };
3852
+ return {
3853
+ blocked: true,
3854
+ blockReason: check.error,
3855
+ blockGate: "input_validation",
3856
+ injections: [],
3857
+ events: [event]
3858
+ };
3859
+ }
3860
+ }
3861
+ if (config.retryProtection !== false) {
3862
+ results.push(guardRetryProtection(tool, call, state.retryTracker));
3863
+ }
3864
+ if (config.irreversibility !== false) {
3865
+ results.push(guardIrreversibility(tool, call, conversationContext.fullText));
3866
+ }
3867
+ if (config.balanceValidation !== false) {
3868
+ results.push(guardBalanceValidation(tool, call, state.balanceTracker));
3869
+ }
3870
+ if (config.healthFactor) {
3871
+ results.push(guardHealthFactor(tool, call, state.lastHealthFactor, config.healthFactor));
3872
+ }
3873
+ if (config.largeTransfer) {
3874
+ results.push(guardLargeTransfer(tool, call, config.largeTransfer));
3875
+ }
3876
+ if (config.slippage !== false) {
3877
+ results.push(guardSlippage(tool, call, conversationContext.lastAssistantText));
3878
+ }
3879
+ if (config.costWarning !== false) {
3880
+ results.push(guardCostWarning(tool, call, conversationContext.fullText));
3881
+ }
3882
+ const events = results.filter((r) => r.verdict !== "pass").map((r) => ({
3883
+ timestamp: now,
3884
+ toolName: tool.name,
3885
+ toolUseId: call.id,
3886
+ gate: r.gate,
3887
+ verdict: r.verdict,
3888
+ tier: r.tier,
3889
+ message: r.message
3890
+ }));
3891
+ const block = results.find((r) => r.verdict === "block");
3892
+ if (block) {
3893
+ return {
3894
+ blocked: true,
3895
+ blockReason: block.message ?? `Blocked by ${block.gate}`,
3896
+ blockGate: block.gate,
3897
+ injections: [],
3898
+ events
3899
+ };
3900
+ }
3901
+ const injections = results.filter((r) => r.verdict === "hint" || r.verdict === "warn").map((r) => ({
3902
+ _gate: r.gate,
3903
+ ...r.verdict === "hint" ? { _hint: r.message } : { _warning: r.message }
3904
+ }));
3905
+ return { blocked: false, injections, events };
3906
+ }
3907
+ function updateGuardStateAfterToolResult(toolName, tool, input, result, isError, state) {
3908
+ if (isError) return;
3909
+ if (BALANCE_READ_TOOLS.has(toolName)) {
3910
+ state.balanceTracker.recordRead();
3911
+ }
3912
+ if (tool?.flags.mutating) {
3913
+ state.balanceTracker.recordWrite();
3914
+ }
3915
+ if (toolName === "health_check" && result && typeof result === "object") {
3916
+ const r = result;
3917
+ const hf = Number(r.healthFactor ?? r.health_factor ?? r.hf);
3918
+ if (!isNaN(hf) && hf > 0) {
3919
+ state.lastHealthFactor = hf;
3920
+ }
3921
+ }
3922
+ state.retryTracker.record(toolName, input, result);
3923
+ }
3924
+ function extractConversationText(messages) {
3925
+ const textParts = [];
3926
+ let lastAssistantText = "";
3927
+ for (const msg of messages) {
3928
+ if (!Array.isArray(msg.content)) continue;
3929
+ for (const block of msg.content) {
3930
+ if (block.type === "text" && typeof block.text === "string") {
3931
+ textParts.push(block.text);
3932
+ if (msg.role === "assistant") {
3933
+ lastAssistantText = block.text;
3934
+ }
3935
+ }
3936
+ }
3937
+ }
3938
+ return {
3939
+ fullText: textParts.join("\n"),
3940
+ lastAssistantText
3941
+ };
3942
+ }
3943
+
3473
3944
  // src/engine.ts
3474
3945
  var DEFAULT_MAX_TURNS = 10;
3475
3946
  var DEFAULT_MAX_TOKENS = 4096;
@@ -3482,6 +3953,8 @@ var QueryEngine = class {
3482
3953
  maxTokens;
3483
3954
  temperature;
3484
3955
  toolChoice;
3956
+ thinking;
3957
+ outputConfig;
3485
3958
  agent;
3486
3959
  mcpManager;
3487
3960
  walletAddress;
@@ -3491,8 +3964,11 @@ var QueryEngine = class {
3491
3964
  env;
3492
3965
  txMutex = new TxMutex();
3493
3966
  costTracker;
3967
+ guardConfig;
3968
+ guardState;
3494
3969
  messages = [];
3495
3970
  abortController = null;
3971
+ guardEvents = [];
3496
3972
  constructor(config) {
3497
3973
  this.provider = config.provider;
3498
3974
  this.agent = config.agent;
@@ -3507,8 +3983,12 @@ var QueryEngine = class {
3507
3983
  this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
3508
3984
  this.temperature = config.temperature;
3509
3985
  this.toolChoice = config.toolChoice;
3986
+ this.thinking = config.thinking;
3987
+ this.outputConfig = config.outputConfig;
3510
3988
  this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
3511
3989
  this.costTracker = new CostTracker(config.costTracker);
3990
+ this.guardConfig = config.guards;
3991
+ this.guardState = createGuardRunnerState();
3512
3992
  this.tools = config.tools ?? (config.agent ? getDefaultTools() : []);
3513
3993
  }
3514
3994
  /**
@@ -3587,6 +4067,10 @@ var QueryEngine = class {
3587
4067
  reset() {
3588
4068
  this.messages = [];
3589
4069
  this.costTracker.reset();
4070
+ this.guardEvents = [];
4071
+ }
4072
+ getGuardEvents() {
4073
+ return this.guardEvents;
3590
4074
  }
3591
4075
  loadMessages(messages) {
3592
4076
  this.messages = [...messages];
@@ -3638,6 +4122,8 @@ var QueryEngine = class {
3638
4122
  const summary = this.messages.map((m, idx) => {
3639
4123
  const blocks = m.content.map((b) => {
3640
4124
  if (b.type === "text") return `text(${b.text.slice(0, 40)}\u2026)`;
4125
+ if (b.type === "thinking") return `thinking(${b.thinking.length}ch)`;
4126
+ if (b.type === "redacted_thinking") return `redacted_thinking`;
3641
4127
  if (b.type === "tool_use") return `tool_use:${b.id.slice(-8)}/${b.name}`;
3642
4128
  return `tool_result:${b.toolUseId.slice(-8)}`;
3643
4129
  });
@@ -3646,6 +4132,8 @@ var QueryEngine = class {
3646
4132
  console.log(`[engine] provider.chat turn=${turns} msgs=${this.messages.length}
3647
4133
  ${summary.join("\n")}`);
3648
4134
  }
4135
+ const thinkingEnabled = this.thinking && this.thinking.type !== "disabled";
4136
+ const effectiveToolChoice = thinkingEnabled ? applyToolChoice && turns === 1 ? "auto" : void 0 : applyToolChoice && turns === 1 ? this.toolChoice : void 0;
3649
4137
  const stream = this.provider.chat({
3650
4138
  messages: this.messages,
3651
4139
  systemPrompt: this.systemPrompt,
@@ -3653,7 +4141,9 @@ ${summary.join("\n")}`);
3653
4141
  model: this.model,
3654
4142
  maxTokens: this.maxTokens,
3655
4143
  temperature: this.temperature,
3656
- toolChoice: applyToolChoice && turns === 1 ? this.toolChoice : void 0,
4144
+ toolChoice: effectiveToolChoice,
4145
+ thinking: this.thinking,
4146
+ outputConfig: this.outputConfig,
3657
4147
  signal
3658
4148
  });
3659
4149
  for await (const event of stream) {
@@ -3699,7 +4189,42 @@ ${summary.join("\n")}`);
3699
4189
  pendingWrite = { call, tool };
3700
4190
  break;
3701
4191
  }
3702
- for await (const toolEvent of runTools(approved, this.tools, context, this.txMutex)) {
4192
+ const guardedApproved = [];
4193
+ if (this.guardConfig) {
4194
+ const convCtx = extractConversationText(this.messages);
4195
+ for (const call of approved) {
4196
+ const tool = findTool(this.tools, call.name);
4197
+ if (!tool) {
4198
+ guardedApproved.push(call);
4199
+ continue;
4200
+ }
4201
+ const check = runGuards(tool, call, this.guardState, this.guardConfig, convCtx);
4202
+ this.guardEvents.push(...check.events);
4203
+ if (check.blocked) {
4204
+ yield {
4205
+ type: "tool_result",
4206
+ toolName: call.name,
4207
+ toolUseId: call.id,
4208
+ result: { error: check.blockReason, _gate: check.blockGate },
4209
+ isError: true
4210
+ };
4211
+ toolResultBlocks.push({
4212
+ type: "tool_result",
4213
+ toolUseId: call.id,
4214
+ content: JSON.stringify({ error: check.blockReason, _gate: check.blockGate }),
4215
+ isError: true
4216
+ });
4217
+ continue;
4218
+ }
4219
+ if (check.injections.length > 0) {
4220
+ call._guardInjections = check.injections;
4221
+ }
4222
+ guardedApproved.push(call);
4223
+ }
4224
+ } else {
4225
+ guardedApproved.push(...approved);
4226
+ }
4227
+ for await (const toolEvent of runTools(guardedApproved, this.tools, context, this.txMutex)) {
3703
4228
  if (toolEvent.type === "tool_result" && !toolEvent.isError) {
3704
4229
  const warning = flagSuspiciousResult(toolEvent.toolName, toolEvent.result);
3705
4230
  if (warning) {
@@ -3717,29 +4242,89 @@ ${summary.join("\n")}`);
3717
4242
  continue;
3718
4243
  }
3719
4244
  }
3720
- yield toolEvent;
3721
- if (toolEvent.type === "tool_result" && !toolEvent.isError) {
3722
- const r = toolEvent.result;
3723
- if (r && r.__canvas === true) {
3724
- yield {
3725
- type: "canvas",
3726
- template: String(r.template ?? ""),
3727
- title: String(r.title ?? ""),
3728
- data: r.templateData ?? null,
3729
- toolUseId: toolEvent.toolUseId
3730
- };
4245
+ if (toolEvent.type === "tool_result") {
4246
+ const tool = findTool(this.tools, toolEvent.toolName);
4247
+ const originalCall = guardedApproved.find((c) => c.id === toolEvent.toolUseId);
4248
+ updateGuardStateAfterToolResult(
4249
+ toolEvent.toolName,
4250
+ tool,
4251
+ originalCall?.input ?? null,
4252
+ toolEvent.result,
4253
+ toolEvent.isError,
4254
+ this.guardState
4255
+ );
4256
+ let enrichedResult = toolEvent.result;
4257
+ if (this.guardConfig && !toolEvent.isError && tool) {
4258
+ const artifactInj = this.guardConfig.artifactPreview !== false ? guardArtifactPreview(toolEvent.result) : null;
4259
+ const staleInj = this.guardConfig.staleData !== false ? guardStaleData(tool.flags) : null;
4260
+ const preInjections = guardedApproved.find((c) => c.id === toolEvent.toolUseId)?._guardInjections ?? [];
4261
+ const allInjections = [
4262
+ ...preInjections,
4263
+ ...artifactInj ? [artifactInj] : [],
4264
+ ...staleInj ? [staleInj] : []
4265
+ ];
4266
+ if (allInjections.length > 0 && typeof enrichedResult === "object" && enrichedResult) {
4267
+ enrichedResult = { ...enrichedResult, _guards: allInjections };
4268
+ }
4269
+ }
4270
+ const finalEvent = enrichedResult !== toolEvent.result ? { ...toolEvent, result: enrichedResult } : toolEvent;
4271
+ yield finalEvent;
4272
+ if (finalEvent.type === "tool_result" && !finalEvent.isError) {
4273
+ const r = finalEvent.result;
4274
+ if (r && r.__canvas === true) {
4275
+ yield {
4276
+ type: "canvas",
4277
+ template: String(r.template ?? ""),
4278
+ title: String(r.title ?? ""),
4279
+ data: r.templateData ?? null,
4280
+ toolUseId: finalEvent.toolUseId
4281
+ };
4282
+ }
3731
4283
  }
4284
+ toolResultBlocks.push({
4285
+ type: "tool_result",
4286
+ toolUseId: finalEvent.toolUseId,
4287
+ content: JSON.stringify(finalEvent.result),
4288
+ isError: finalEvent.isError
4289
+ });
4290
+ continue;
3732
4291
  }
3733
- if (toolEvent.type === "tool_result") {
4292
+ yield toolEvent;
4293
+ }
4294
+ if (pendingWrite && this.guardConfig) {
4295
+ const convCtx = extractConversationText(this.messages);
4296
+ const check = runGuards(
4297
+ pendingWrite.tool,
4298
+ pendingWrite.call,
4299
+ this.guardState,
4300
+ this.guardConfig,
4301
+ convCtx
4302
+ );
4303
+ this.guardEvents.push(...check.events);
4304
+ if (check.blocked) {
4305
+ yield {
4306
+ type: "tool_result",
4307
+ toolName: pendingWrite.call.name,
4308
+ toolUseId: pendingWrite.call.id,
4309
+ result: { error: check.blockReason, _gate: check.blockGate },
4310
+ isError: true
4311
+ };
3734
4312
  toolResultBlocks.push({
3735
4313
  type: "tool_result",
3736
- toolUseId: toolEvent.toolUseId,
3737
- content: JSON.stringify(toolEvent.result),
3738
- isError: toolEvent.isError
4314
+ toolUseId: pendingWrite.call.id,
4315
+ content: JSON.stringify({ error: check.blockReason, _gate: check.blockGate }),
4316
+ isError: true
3739
4317
  });
4318
+ this.messages.push({ role: "assistant", content: acc.assistantBlocks });
4319
+ this.messages.push({ role: "user", content: toolResultBlocks });
4320
+ continue;
4321
+ }
4322
+ if (check.injections.length > 0) {
4323
+ pendingWrite.call._guardInjections = check.injections;
3740
4324
  }
3741
4325
  }
3742
4326
  if (pendingWrite) {
4327
+ const writeGuardInjections = pendingWrite.call._guardInjections;
3743
4328
  yield {
3744
4329
  type: "pending_action",
3745
4330
  action: {
@@ -3752,7 +4337,8 @@ ${summary.join("\n")}`);
3752
4337
  toolUseId: b.toolUseId,
3753
4338
  content: b.content,
3754
4339
  isError: b.isError ?? false
3755
- }))
4340
+ })),
4341
+ ...writeGuardInjections?.length ? { guardInjections: writeGuardInjections } : {}
3756
4342
  }
3757
4343
  };
3758
4344
  return;
@@ -3782,6 +4368,26 @@ ${summary.join("\n")}`);
3782
4368
  }
3783
4369
  *handleProviderEvent(event, acc) {
3784
4370
  switch (event.type) {
4371
+ case "thinking_delta": {
4372
+ yield { type: "thinking_delta", text: event.text };
4373
+ break;
4374
+ }
4375
+ case "thinking_done": {
4376
+ acc.assistantBlocks.push({
4377
+ type: "thinking",
4378
+ thinking: event.thinking,
4379
+ signature: event.signature
4380
+ });
4381
+ yield { type: "thinking_done", signature: event.signature };
4382
+ break;
4383
+ }
4384
+ case "redacted_thinking": {
4385
+ acc.assistantBlocks.push({
4386
+ type: "redacted_thinking",
4387
+ data: event.data
4388
+ });
4389
+ break;
4390
+ }
3785
4391
  case "text_delta": {
3786
4392
  acc.text += event.text;
3787
4393
  yield { type: "text_delta", text: event.text };
@@ -4029,6 +4635,165 @@ var MemorySessionStore = class {
4029
4635
  }
4030
4636
  };
4031
4637
 
4638
+ // src/classify-effort.ts
4639
+ function classifyEffort(model, userMessage, matchedRecipe, sessionWriteCount) {
4640
+ const supportsMax = model.includes("opus-4-6");
4641
+ const msg = userMessage.toLowerCase();
4642
+ if (supportsMax) {
4643
+ if (matchedRecipe?.name === "portfolio_rebalance") return "max";
4644
+ if (matchedRecipe?.name === "emergency_withdraw") return "max";
4645
+ if (/rebalance|reallocate|dca setup|close.*position/i.test(msg)) return "max";
4646
+ }
4647
+ if (matchedRecipe && matchedRecipe.steps.length >= 3) return "high";
4648
+ if (matchedRecipe?.name === "safe_borrow" || matchedRecipe?.name === "bulk_mail") return "high";
4649
+ if (sessionWriteCount > 0 && /borrow|withdraw|send|swap/i.test(msg)) return "high";
4650
+ if (/balance|rate|how much|what is|check|history|show|price/i.test(msg)) return "low";
4651
+ if (!matchedRecipe && !/deposit|send|swap|borrow|withdraw|save|pay/i.test(msg)) return "low";
4652
+ return "medium";
4653
+ }
4654
+
4655
+ // src/prompt-cache.ts
4656
+ function buildCachedSystemPrompt(staticParts, dynamicPart) {
4657
+ const blocks = staticParts.map((text, i) => ({
4658
+ type: "text",
4659
+ text,
4660
+ ...i === staticParts.length - 1 && { cache_control: { type: "ephemeral" } }
4661
+ }));
4662
+ if (dynamicPart) {
4663
+ blocks.push({ type: "text", text: dynamicPart });
4664
+ }
4665
+ return blocks;
4666
+ }
4667
+
4668
+ // src/intelligence.ts
4669
+ function buildProfileContext(profile) {
4670
+ if (!profile || profile.riskConfidence < 0.3) return "";
4671
+ const lines = ["User financial profile (inferred from conversation history):"];
4672
+ if (profile.riskConfidence >= 0.5) {
4673
+ lines.push(`- Risk appetite: ${profile.riskAppetite}`);
4674
+ }
4675
+ if (profile.literacyConfidence >= 0.5) {
4676
+ lines.push(`- Financial literacy: ${profile.financialLiteracy}`);
4677
+ if (profile.financialLiteracy === "advanced") {
4678
+ lines.push(" \u2192 Skip basic DeFi explanations (health factor, APY, etc). User knows these.");
4679
+ }
4680
+ if (profile.financialLiteracy === "novice") {
4681
+ lines.push(" \u2192 Always explain DeFi concepts in plain language.");
4682
+ }
4683
+ }
4684
+ if (profile.currencyFraming === "fiat") {
4685
+ lines.push('- Frame amounts as dollars (e.g. "$50" not "50 USDC")');
4686
+ }
4687
+ if (profile.prefersBriefResponses) {
4688
+ lines.push("- Prefers brief responses \u2014 be concise");
4689
+ }
4690
+ if (profile.primaryGoals.length > 0) {
4691
+ lines.push(`- Stated goals: ${profile.primaryGoals.join(", ")}`);
4692
+ }
4693
+ if (profile.knownPatterns.length > 0) {
4694
+ lines.push(`- Behavioural patterns: ${profile.knownPatterns.join(", ")}`);
4695
+ }
4696
+ return lines.join("\n");
4697
+ }
4698
+ function buildProactivenessInstructions(profile) {
4699
+ const brevityGuidance = profile?.prefersBriefResponses ? "This user prefers brevity \u2014 only surface context if urgent or directly actionable." : "Surface relevant context when criteria are met.";
4700
+ const styleGuidance = profile?.financialLiteracy === "novice" ? "Frame observations in plain English, no DeFi jargon." : "Technical framing is fine.";
4701
+ return `Proactive awareness:
4702
+ After completing the user's request, consider whether ONE additional piece of financial
4703
+ context is worth mentioning. ${brevityGuidance}
4704
+
4705
+ \u2713 Mention if:
4706
+ - Their savings goal is materially off-track (>20% behind pace)
4707
+ - Yield rate changed significantly since last session (>0.5%)
4708
+ - They have idle USDC >$50 sitting for >48h
4709
+ - An action they just took interacts with an active goal or debt position
4710
+ - A pattern would materially benefit from their attention
4711
+
4712
+ \u2717 Do NOT mention if:
4713
+ - Tangentially related but not actionable
4714
+ - Already surfaced this session
4715
+ - Requires more explanation than the original answer
4716
+ - Would seem pushy or sales-y
4717
+
4718
+ ${styleGuidance}
4719
+ Format: One sentence maximum, after main response, separated by a line break.
4720
+ Frame as observation, not advice: "Your Tokyo goal is $80 behind pace." \u2014 not "You should deposit more."`;
4721
+ }
4722
+ function buildSelfEvaluationInstruction() {
4723
+ return `Self-evaluation (apply silently before composing your response):
4724
+
4725
+ 1. ACCURACY \u2014 Quote exact values from tool results, not estimates or rounded figures.
4726
+ Never combine post-action tool results with pre-action snapshot numbers.
4727
+ If the tool returned an error, label it as an error \u2014 do not paraphrase it as success.
4728
+
4729
+ 2. STATE CONSISTENCY \u2014 Describe the actual outcome of all steps.
4730
+ Partial success (swap ok, deposit failed): describe both clearly.
4731
+ Never describe a failed action as if it succeeded.
4732
+
4733
+ 3. COMPLETENESS \u2014 If the user asked multiple things, answer all of them.
4734
+ If you couldn't complete something, explain why and what the current state is.
4735
+
4736
+ 4. TONE \u2014 Match tone to outcome.
4737
+ Success: confirming and forward-looking.
4738
+ Failure: clear about what failed, unchanged, and what to do next.
4739
+ Warning: specific risk, not generic caution.
4740
+
4741
+ If any check fails, rewrite before outputting.`;
4742
+ }
4743
+
4744
+ // src/state/conversation-state.ts
4745
+ function buildStateContext(state) {
4746
+ switch (state.type) {
4747
+ case "idle":
4748
+ return "";
4749
+ case "mid_recipe": {
4750
+ const elapsed = Math.round((Date.now() - state.startedAt) / 6e4);
4751
+ const outputs = JSON.stringify(state.completedStepOutputs);
4752
+ return [
4753
+ `Conversation state: MID-RECIPE`,
4754
+ `Active recipe: ${state.recipeName} (step ${state.currentStep + 1} of ${state.totalSteps})`,
4755
+ `Started: ${elapsed} minutes ago`,
4756
+ `Completed step key outputs: ${outputs}`,
4757
+ `If the user asks an unrelated question: answer briefly, then offer to continue the ${state.recipeName} flow.`,
4758
+ `If the user says "cancel" or "stop": confirm you have abandoned the recipe and return to idle.`
4759
+ ].join("\n");
4760
+ }
4761
+ case "awaiting_confirmation": {
4762
+ const expiryMins = Math.max(0, Math.round((state.expiresAt - Date.now()) / 6e4));
4763
+ const expired = state.expiresAt < Date.now();
4764
+ return [
4765
+ `Conversation state: AWAITING CONFIRMATION`,
4766
+ `Proposed action: ${state.action}${state.amount ? ` for $${state.amount}` : ""}${state.recipient ? ` to ${state.recipient}` : ""}`,
4767
+ expired ? `Status: EXPIRED \u2014 ask if user still wants to proceed` : `Expires in: ${expiryMins} minutes`,
4768
+ `"yes/confirm/do it" \u2192 execute. "no/cancel/wait" \u2192 abort, reset to idle.`
4769
+ ].join("\n");
4770
+ }
4771
+ case "post_error":
4772
+ return [
4773
+ `Conversation state: POST-ERROR`,
4774
+ `Failed action: ${state.failedAction}`,
4775
+ `Error: ${state.errorMessage}`,
4776
+ state.partialState ? `Partial state: ${state.partialState}` : "",
4777
+ `Acknowledge failure clearly. Offer a specific recovery path if one exists.`,
4778
+ `This state clears automatically on the next successful action.`
4779
+ ].filter(Boolean).join("\n");
4780
+ case "post_liquidation_warning":
4781
+ return [
4782
+ `Conversation state: LIQUIDATION WARNING ACTIVE`,
4783
+ `Health factor: ${state.healthFactor.toFixed(2)} \u2014 below safe threshold`,
4784
+ `Prioritise debt repayment or collateral deposit.`,
4785
+ `Do not proceed with any action that would further reduce health factor.`
4786
+ ].join("\n");
4787
+ case "onboarding":
4788
+ return [
4789
+ `Conversation state: ONBOARDING (session ${state.sessionNumber})`,
4790
+ 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"}.`
4791
+ ].join("\n");
4792
+ default:
4793
+ return "";
4794
+ }
4795
+ }
4796
+
4032
4797
  // src/context.ts
4033
4798
  var CHARS_PER_TOKEN = 4;
4034
4799
  function estimateTokens(messages) {
@@ -4044,6 +4809,10 @@ function blockCharCount(block) {
4044
4809
  switch (block.type) {
4045
4810
  case "text":
4046
4811
  return block.text.length;
4812
+ case "thinking":
4813
+ return block.thinking.length;
4814
+ case "redacted_thinking":
4815
+ return block.data.length;
4047
4816
  case "tool_use":
4048
4817
  return block.name.length + JSON.stringify(block.input).length;
4049
4818
  case "tool_result":
@@ -4372,6 +5141,7 @@ function adaptMcpTool(mcpTool, config) {
4372
5141
  isReadOnly,
4373
5142
  isConcurrencySafe: isReadOnly,
4374
5143
  permissionLevel,
5144
+ flags: {},
4375
5145
  async call(input, _context) {
4376
5146
  const result = await config.manager.callTool(
4377
5147
  config.serverName,
@@ -4437,17 +5207,26 @@ var AnthropicProvider = class {
4437
5207
  toolChoice = { type: "tool", name: params.toolChoice.name };
4438
5208
  }
4439
5209
  }
4440
- const streamParams = {
5210
+ const thinkingParam = toAnthropicThinking(params.thinking);
5211
+ const systemParam = toAnthropicSystem(params.systemPrompt);
5212
+ const baseParams = {
4441
5213
  model: params.model ?? this.defaultModel,
4442
5214
  max_tokens: params.maxTokens ?? this.defaultMaxTokens,
4443
- system: params.systemPrompt,
5215
+ system: systemParam,
4444
5216
  messages,
5217
+ stream: true,
4445
5218
  tools: tools.length > 0 ? tools : void 0,
4446
- ...params.temperature !== void 0 && { temperature: params.temperature },
5219
+ ...!thinkingParam && params.temperature !== void 0 && { temperature: params.temperature },
4447
5220
  ...toolChoice && { tool_choice: toolChoice }
4448
5221
  };
5222
+ const streamParams = {
5223
+ ...baseParams,
5224
+ ...thinkingParam && { thinking: thinkingParam },
5225
+ ...params.outputConfig?.effort && { output_config: { effort: params.outputConfig.effort } }
5226
+ };
4449
5227
  const stream = params.signal ? this.client.messages.stream(streamParams, { signal: params.signal }) : this.client.messages.stream(streamParams);
4450
5228
  const toolInputBuffers = /* @__PURE__ */ new Map();
5229
+ const thinkingBuffers = /* @__PURE__ */ new Map();
4451
5230
  let outputTokensFromStart = 0;
4452
5231
  try {
4453
5232
  for await (const event of stream) {
@@ -4485,6 +5264,10 @@ var AnthropicProvider = class {
4485
5264
  id: block.id,
4486
5265
  name: block.name
4487
5266
  };
5267
+ } else if (block.type === "thinking") {
5268
+ thinkingBuffers.set(event.index, { type: "thinking", text: "", signature: "" });
5269
+ } else if (block.type === "redacted_thinking") {
5270
+ thinkingBuffers.set(event.index, { type: "redacted_thinking", data: block.data ?? "" });
4488
5271
  }
4489
5272
  break;
4490
5273
  }
@@ -4502,26 +5285,41 @@ var AnthropicProvider = class {
4502
5285
  partialJson: delta.partial_json
4503
5286
  };
4504
5287
  }
5288
+ } else if (delta.type === "thinking_delta") {
5289
+ const buf = thinkingBuffers.get(event.index);
5290
+ if (buf?.type === "thinking") buf.text += delta.thinking ?? "";
5291
+ yield { type: "thinking_delta", text: delta.thinking ?? "" };
5292
+ } else if (delta.type === "signature_delta") {
5293
+ const buf = thinkingBuffers.get(event.index);
5294
+ if (buf?.type === "thinking") buf.signature = delta.signature ?? "";
4505
5295
  }
4506
5296
  break;
4507
5297
  }
4508
5298
  case "content_block_stop": {
4509
- const buf = toolInputBuffers.get(event.index);
4510
- if (buf) {
5299
+ const toolBuf = toolInputBuffers.get(event.index);
5300
+ if (toolBuf) {
4511
5301
  let input = {};
4512
5302
  try {
4513
- input = JSON.parse(buf.json || "{}");
5303
+ input = JSON.parse(toolBuf.json || "{}");
4514
5304
  } catch {
4515
5305
  input = {};
4516
5306
  }
4517
5307
  yield {
4518
5308
  type: "tool_use_done",
4519
- id: buf.id,
4520
- name: buf.name,
5309
+ id: toolBuf.id,
5310
+ name: toolBuf.name,
4521
5311
  input
4522
5312
  };
4523
5313
  toolInputBuffers.delete(event.index);
4524
5314
  }
5315
+ const thinkBuf = thinkingBuffers.get(event.index);
5316
+ if (thinkBuf?.type === "thinking") {
5317
+ yield { type: "thinking_done", thinking: thinkBuf.text, signature: thinkBuf.signature };
5318
+ thinkingBuffers.delete(event.index);
5319
+ } else if (thinkBuf?.type === "redacted_thinking") {
5320
+ yield { type: "redacted_thinking", data: thinkBuf.data };
5321
+ thinkingBuffers.delete(event.index);
5322
+ }
4525
5323
  break;
4526
5324
  }
4527
5325
  case "message_delta": {
@@ -4551,11 +5349,28 @@ var AnthropicProvider = class {
4551
5349
  }
4552
5350
  }
4553
5351
  };
5352
+ function toAnthropicSystem(prompt) {
5353
+ if (typeof prompt === "string") return prompt;
5354
+ return prompt.map((block) => ({
5355
+ type: "text",
5356
+ text: block.text,
5357
+ ...block.cache_control && { cache_control: block.cache_control }
5358
+ }));
5359
+ }
5360
+ function toAnthropicThinking(config) {
5361
+ if (!config || config.type === "disabled") return void 0;
5362
+ if (config.type === "adaptive") return { type: "adaptive" };
5363
+ return { type: "enabled", budget_tokens: config.budgetTokens };
5364
+ }
4554
5365
  function toAnthropicMessage(msg) {
4555
5366
  const content = msg.content.map((block) => {
4556
5367
  switch (block.type) {
4557
5368
  case "text":
4558
5369
  return { type: "text", text: block.text };
5370
+ case "thinking":
5371
+ return { type: "thinking", thinking: block.thinking, signature: block.signature };
5372
+ case "redacted_thinking":
5373
+ return { type: "redacted_thinking", data: block.data };
4559
5374
  case "tool_use":
4560
5375
  return {
4561
5376
  type: "tool_use",
@@ -4571,7 +5386,7 @@ function toAnthropicMessage(msg) {
4571
5386
  is_error: block.isError
4572
5387
  };
4573
5388
  }
4574
- });
5389
+ }).filter((b) => b !== null);
4575
5390
  return { role: msg.role, content };
4576
5391
  }
4577
5392
  function toAnthropicTool(def) {
@@ -4660,6 +5475,6 @@ function sanitizeAnthropicMessages(messages) {
4660
5475
  return merged;
4661
5476
  }
4662
5477
 
4663
- 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 };
5478
+ export { AnthropicProvider, BalanceTracker, CANVAS_TEMPLATES, CostTracker, DEFAULT_GUARD_CONFIG, DEFAULT_SYSTEM_PROMPT, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, NaviTools, QueryEngine, READ_TOOLS, 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, mppServicesTool, parseMcpJson, 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 };
4664
5479
  //# sourceMappingURL=index.js.map
4665
5480
  //# sourceMappingURL=index.js.map