@t2000/engine 0.31.3 → 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.d.ts +316 -16
- package/dist/index.js +800 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
17
|
-
isConcurrencySafe:
|
|
18
|
-
permissionLevel: opts.permissionLevel ?? (
|
|
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 }
|
|
@@ -1119,6 +1161,13 @@ var saveDepositTool = buildTool({
|
|
|
1119
1161
|
},
|
|
1120
1162
|
isReadOnly: false,
|
|
1121
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
|
+
},
|
|
1122
1171
|
async call(input, context) {
|
|
1123
1172
|
assertAllowedAsset("save", input.asset);
|
|
1124
1173
|
const agent = requireAgent(context);
|
|
@@ -1160,6 +1209,7 @@ var withdrawTool = buildTool({
|
|
|
1160
1209
|
},
|
|
1161
1210
|
isReadOnly: false,
|
|
1162
1211
|
permissionLevel: "confirm",
|
|
1212
|
+
flags: { mutating: true, affectsHealth: true },
|
|
1163
1213
|
async call(input, context) {
|
|
1164
1214
|
const agent = requireAgent(context);
|
|
1165
1215
|
const result = await agent.withdraw({
|
|
@@ -1207,6 +1257,16 @@ var sendTransferTool = buildTool({
|
|
|
1207
1257
|
},
|
|
1208
1258
|
isReadOnly: false,
|
|
1209
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
|
+
},
|
|
1210
1270
|
async call(input, context) {
|
|
1211
1271
|
const agent = requireAgent(context);
|
|
1212
1272
|
const result = await agent.send({ to: input.to, amount: input.amount });
|
|
@@ -1249,6 +1309,13 @@ var borrowTool = buildTool({
|
|
|
1249
1309
|
},
|
|
1250
1310
|
isReadOnly: false,
|
|
1251
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
|
+
},
|
|
1252
1319
|
async call(input, context) {
|
|
1253
1320
|
assertAllowedAsset("borrow", input.asset);
|
|
1254
1321
|
const agent = requireAgent(context);
|
|
@@ -1283,6 +1350,7 @@ var repayDebtTool = buildTool({
|
|
|
1283
1350
|
},
|
|
1284
1351
|
isReadOnly: false,
|
|
1285
1352
|
permissionLevel: "confirm",
|
|
1353
|
+
flags: { mutating: true, requiresBalance: true },
|
|
1286
1354
|
async call(input, context) {
|
|
1287
1355
|
const agent = requireAgent(context);
|
|
1288
1356
|
const result = await agent.repay({ amount: input.amount });
|
|
@@ -1305,6 +1373,7 @@ var claimRewardsTool = buildTool({
|
|
|
1305
1373
|
jsonSchema: { type: "object", properties: {}, required: [] },
|
|
1306
1374
|
isReadOnly: false,
|
|
1307
1375
|
permissionLevel: "confirm",
|
|
1376
|
+
flags: { mutating: true },
|
|
1308
1377
|
async call(_input, context) {
|
|
1309
1378
|
const agent = requireAgent(context);
|
|
1310
1379
|
const result = await agent.claimRewards();
|
|
@@ -1379,6 +1448,28 @@ Always use ISO-3166 country codes (GB not UK, US not USA). A return address ("fr
|
|
|
1379
1448
|
},
|
|
1380
1449
|
isReadOnly: false,
|
|
1381
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
|
+
},
|
|
1382
1473
|
async call(input, context) {
|
|
1383
1474
|
const agent = requireAgent(context);
|
|
1384
1475
|
const result = await agent.pay({
|
|
@@ -1480,6 +1571,13 @@ var swapExecuteTool = buildTool({
|
|
|
1480
1571
|
},
|
|
1481
1572
|
isReadOnly: false,
|
|
1482
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
|
+
},
|
|
1483
1581
|
async call(input, context) {
|
|
1484
1582
|
const agent = requireAgent(context);
|
|
1485
1583
|
const result = await agent.swap({
|
|
@@ -1554,6 +1652,7 @@ var voloStakeTool = buildTool({
|
|
|
1554
1652
|
},
|
|
1555
1653
|
isReadOnly: false,
|
|
1556
1654
|
permissionLevel: "confirm",
|
|
1655
|
+
flags: { mutating: true, requiresBalance: true },
|
|
1557
1656
|
async call(input, context) {
|
|
1558
1657
|
const agent = requireAgent(context);
|
|
1559
1658
|
const result = await agent.stakeVSui({ amount: input.amount });
|
|
@@ -1584,6 +1683,7 @@ var voloUnstakeTool = buildTool({
|
|
|
1584
1683
|
},
|
|
1585
1684
|
isReadOnly: false,
|
|
1586
1685
|
permissionLevel: "confirm",
|
|
1686
|
+
flags: { mutating: true },
|
|
1587
1687
|
async call(input, context) {
|
|
1588
1688
|
const agent = requireAgent(context);
|
|
1589
1689
|
const result = await agent.unstakeVSui({ amount: input.amount });
|
|
@@ -3416,7 +3516,7 @@ var WRITE_TOOLS = [
|
|
|
3416
3516
|
cancelScheduleTool
|
|
3417
3517
|
];
|
|
3418
3518
|
function getDefaultTools() {
|
|
3419
|
-
return [...READ_TOOLS, ...WRITE_TOOLS];
|
|
3519
|
+
return applyToolFlags([...READ_TOOLS, ...WRITE_TOOLS]);
|
|
3420
3520
|
}
|
|
3421
3521
|
|
|
3422
3522
|
// src/prompt.ts
|
|
@@ -3517,6 +3617,330 @@ var CostTracker = class {
|
|
|
3517
3617
|
}
|
|
3518
3618
|
};
|
|
3519
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 ."
|
|
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
|
+
|
|
3520
3944
|
// src/engine.ts
|
|
3521
3945
|
var DEFAULT_MAX_TURNS = 10;
|
|
3522
3946
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -3529,6 +3953,8 @@ var QueryEngine = class {
|
|
|
3529
3953
|
maxTokens;
|
|
3530
3954
|
temperature;
|
|
3531
3955
|
toolChoice;
|
|
3956
|
+
thinking;
|
|
3957
|
+
outputConfig;
|
|
3532
3958
|
agent;
|
|
3533
3959
|
mcpManager;
|
|
3534
3960
|
walletAddress;
|
|
@@ -3538,8 +3964,11 @@ var QueryEngine = class {
|
|
|
3538
3964
|
env;
|
|
3539
3965
|
txMutex = new TxMutex();
|
|
3540
3966
|
costTracker;
|
|
3967
|
+
guardConfig;
|
|
3968
|
+
guardState;
|
|
3541
3969
|
messages = [];
|
|
3542
3970
|
abortController = null;
|
|
3971
|
+
guardEvents = [];
|
|
3543
3972
|
constructor(config) {
|
|
3544
3973
|
this.provider = config.provider;
|
|
3545
3974
|
this.agent = config.agent;
|
|
@@ -3554,8 +3983,12 @@ var QueryEngine = class {
|
|
|
3554
3983
|
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
3555
3984
|
this.temperature = config.temperature;
|
|
3556
3985
|
this.toolChoice = config.toolChoice;
|
|
3986
|
+
this.thinking = config.thinking;
|
|
3987
|
+
this.outputConfig = config.outputConfig;
|
|
3557
3988
|
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
3558
3989
|
this.costTracker = new CostTracker(config.costTracker);
|
|
3990
|
+
this.guardConfig = config.guards;
|
|
3991
|
+
this.guardState = createGuardRunnerState();
|
|
3559
3992
|
this.tools = config.tools ?? (config.agent ? getDefaultTools() : []);
|
|
3560
3993
|
}
|
|
3561
3994
|
/**
|
|
@@ -3634,6 +4067,10 @@ var QueryEngine = class {
|
|
|
3634
4067
|
reset() {
|
|
3635
4068
|
this.messages = [];
|
|
3636
4069
|
this.costTracker.reset();
|
|
4070
|
+
this.guardEvents = [];
|
|
4071
|
+
}
|
|
4072
|
+
getGuardEvents() {
|
|
4073
|
+
return this.guardEvents;
|
|
3637
4074
|
}
|
|
3638
4075
|
loadMessages(messages) {
|
|
3639
4076
|
this.messages = [...messages];
|
|
@@ -3685,6 +4122,8 @@ var QueryEngine = class {
|
|
|
3685
4122
|
const summary = this.messages.map((m, idx) => {
|
|
3686
4123
|
const blocks = m.content.map((b) => {
|
|
3687
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`;
|
|
3688
4127
|
if (b.type === "tool_use") return `tool_use:${b.id.slice(-8)}/${b.name}`;
|
|
3689
4128
|
return `tool_result:${b.toolUseId.slice(-8)}`;
|
|
3690
4129
|
});
|
|
@@ -3693,6 +4132,8 @@ var QueryEngine = class {
|
|
|
3693
4132
|
console.log(`[engine] provider.chat turn=${turns} msgs=${this.messages.length}
|
|
3694
4133
|
${summary.join("\n")}`);
|
|
3695
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;
|
|
3696
4137
|
const stream = this.provider.chat({
|
|
3697
4138
|
messages: this.messages,
|
|
3698
4139
|
systemPrompt: this.systemPrompt,
|
|
@@ -3700,7 +4141,9 @@ ${summary.join("\n")}`);
|
|
|
3700
4141
|
model: this.model,
|
|
3701
4142
|
maxTokens: this.maxTokens,
|
|
3702
4143
|
temperature: this.temperature,
|
|
3703
|
-
toolChoice:
|
|
4144
|
+
toolChoice: effectiveToolChoice,
|
|
4145
|
+
thinking: this.thinking,
|
|
4146
|
+
outputConfig: this.outputConfig,
|
|
3704
4147
|
signal
|
|
3705
4148
|
});
|
|
3706
4149
|
for await (const event of stream) {
|
|
@@ -3746,7 +4189,42 @@ ${summary.join("\n")}`);
|
|
|
3746
4189
|
pendingWrite = { call, tool };
|
|
3747
4190
|
break;
|
|
3748
4191
|
}
|
|
3749
|
-
|
|
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)) {
|
|
3750
4228
|
if (toolEvent.type === "tool_result" && !toolEvent.isError) {
|
|
3751
4229
|
const warning = flagSuspiciousResult(toolEvent.toolName, toolEvent.result);
|
|
3752
4230
|
if (warning) {
|
|
@@ -3764,29 +4242,89 @@ ${summary.join("\n")}`);
|
|
|
3764
4242
|
continue;
|
|
3765
4243
|
}
|
|
3766
4244
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
const
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
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
|
+
}
|
|
3778
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;
|
|
3779
4291
|
}
|
|
3780
|
-
|
|
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
|
+
};
|
|
3781
4312
|
toolResultBlocks.push({
|
|
3782
4313
|
type: "tool_result",
|
|
3783
|
-
toolUseId:
|
|
3784
|
-
content: JSON.stringify(
|
|
3785
|
-
isError:
|
|
4314
|
+
toolUseId: pendingWrite.call.id,
|
|
4315
|
+
content: JSON.stringify({ error: check.blockReason, _gate: check.blockGate }),
|
|
4316
|
+
isError: true
|
|
3786
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;
|
|
3787
4324
|
}
|
|
3788
4325
|
}
|
|
3789
4326
|
if (pendingWrite) {
|
|
4327
|
+
const writeGuardInjections = pendingWrite.call._guardInjections;
|
|
3790
4328
|
yield {
|
|
3791
4329
|
type: "pending_action",
|
|
3792
4330
|
action: {
|
|
@@ -3799,7 +4337,8 @@ ${summary.join("\n")}`);
|
|
|
3799
4337
|
toolUseId: b.toolUseId,
|
|
3800
4338
|
content: b.content,
|
|
3801
4339
|
isError: b.isError ?? false
|
|
3802
|
-
}))
|
|
4340
|
+
})),
|
|
4341
|
+
...writeGuardInjections?.length ? { guardInjections: writeGuardInjections } : {}
|
|
3803
4342
|
}
|
|
3804
4343
|
};
|
|
3805
4344
|
return;
|
|
@@ -3829,6 +4368,26 @@ ${summary.join("\n")}`);
|
|
|
3829
4368
|
}
|
|
3830
4369
|
*handleProviderEvent(event, acc) {
|
|
3831
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
|
+
}
|
|
3832
4391
|
case "text_delta": {
|
|
3833
4392
|
acc.text += event.text;
|
|
3834
4393
|
yield { type: "text_delta", text: event.text };
|
|
@@ -4076,6 +4635,165 @@ var MemorySessionStore = class {
|
|
|
4076
4635
|
}
|
|
4077
4636
|
};
|
|
4078
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
|
+
|
|
4079
4797
|
// src/context.ts
|
|
4080
4798
|
var CHARS_PER_TOKEN = 4;
|
|
4081
4799
|
function estimateTokens(messages) {
|
|
@@ -4091,6 +4809,10 @@ function blockCharCount(block) {
|
|
|
4091
4809
|
switch (block.type) {
|
|
4092
4810
|
case "text":
|
|
4093
4811
|
return block.text.length;
|
|
4812
|
+
case "thinking":
|
|
4813
|
+
return block.thinking.length;
|
|
4814
|
+
case "redacted_thinking":
|
|
4815
|
+
return block.data.length;
|
|
4094
4816
|
case "tool_use":
|
|
4095
4817
|
return block.name.length + JSON.stringify(block.input).length;
|
|
4096
4818
|
case "tool_result":
|
|
@@ -4419,6 +5141,7 @@ function adaptMcpTool(mcpTool, config) {
|
|
|
4419
5141
|
isReadOnly,
|
|
4420
5142
|
isConcurrencySafe: isReadOnly,
|
|
4421
5143
|
permissionLevel,
|
|
5144
|
+
flags: {},
|
|
4422
5145
|
async call(input, _context) {
|
|
4423
5146
|
const result = await config.manager.callTool(
|
|
4424
5147
|
config.serverName,
|
|
@@ -4484,17 +5207,26 @@ var AnthropicProvider = class {
|
|
|
4484
5207
|
toolChoice = { type: "tool", name: params.toolChoice.name };
|
|
4485
5208
|
}
|
|
4486
5209
|
}
|
|
4487
|
-
const
|
|
5210
|
+
const thinkingParam = toAnthropicThinking(params.thinking);
|
|
5211
|
+
const systemParam = toAnthropicSystem(params.systemPrompt);
|
|
5212
|
+
const baseParams = {
|
|
4488
5213
|
model: params.model ?? this.defaultModel,
|
|
4489
5214
|
max_tokens: params.maxTokens ?? this.defaultMaxTokens,
|
|
4490
|
-
system:
|
|
5215
|
+
system: systemParam,
|
|
4491
5216
|
messages,
|
|
5217
|
+
stream: true,
|
|
4492
5218
|
tools: tools.length > 0 ? tools : void 0,
|
|
4493
|
-
|
|
5219
|
+
...!thinkingParam && params.temperature !== void 0 && { temperature: params.temperature },
|
|
4494
5220
|
...toolChoice && { tool_choice: toolChoice }
|
|
4495
5221
|
};
|
|
5222
|
+
const streamParams = {
|
|
5223
|
+
...baseParams,
|
|
5224
|
+
...thinkingParam && { thinking: thinkingParam },
|
|
5225
|
+
...params.outputConfig?.effort && { output_config: { effort: params.outputConfig.effort } }
|
|
5226
|
+
};
|
|
4496
5227
|
const stream = params.signal ? this.client.messages.stream(streamParams, { signal: params.signal }) : this.client.messages.stream(streamParams);
|
|
4497
5228
|
const toolInputBuffers = /* @__PURE__ */ new Map();
|
|
5229
|
+
const thinkingBuffers = /* @__PURE__ */ new Map();
|
|
4498
5230
|
let outputTokensFromStart = 0;
|
|
4499
5231
|
try {
|
|
4500
5232
|
for await (const event of stream) {
|
|
@@ -4532,6 +5264,10 @@ var AnthropicProvider = class {
|
|
|
4532
5264
|
id: block.id,
|
|
4533
5265
|
name: block.name
|
|
4534
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 ?? "" });
|
|
4535
5271
|
}
|
|
4536
5272
|
break;
|
|
4537
5273
|
}
|
|
@@ -4549,26 +5285,41 @@ var AnthropicProvider = class {
|
|
|
4549
5285
|
partialJson: delta.partial_json
|
|
4550
5286
|
};
|
|
4551
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 ?? "";
|
|
4552
5295
|
}
|
|
4553
5296
|
break;
|
|
4554
5297
|
}
|
|
4555
5298
|
case "content_block_stop": {
|
|
4556
|
-
const
|
|
4557
|
-
if (
|
|
5299
|
+
const toolBuf = toolInputBuffers.get(event.index);
|
|
5300
|
+
if (toolBuf) {
|
|
4558
5301
|
let input = {};
|
|
4559
5302
|
try {
|
|
4560
|
-
input = JSON.parse(
|
|
5303
|
+
input = JSON.parse(toolBuf.json || "{}");
|
|
4561
5304
|
} catch {
|
|
4562
5305
|
input = {};
|
|
4563
5306
|
}
|
|
4564
5307
|
yield {
|
|
4565
5308
|
type: "tool_use_done",
|
|
4566
|
-
id:
|
|
4567
|
-
name:
|
|
5309
|
+
id: toolBuf.id,
|
|
5310
|
+
name: toolBuf.name,
|
|
4568
5311
|
input
|
|
4569
5312
|
};
|
|
4570
5313
|
toolInputBuffers.delete(event.index);
|
|
4571
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
|
+
}
|
|
4572
5323
|
break;
|
|
4573
5324
|
}
|
|
4574
5325
|
case "message_delta": {
|
|
@@ -4598,11 +5349,28 @@ var AnthropicProvider = class {
|
|
|
4598
5349
|
}
|
|
4599
5350
|
}
|
|
4600
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
|
+
}
|
|
4601
5365
|
function toAnthropicMessage(msg) {
|
|
4602
5366
|
const content = msg.content.map((block) => {
|
|
4603
5367
|
switch (block.type) {
|
|
4604
5368
|
case "text":
|
|
4605
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 };
|
|
4606
5374
|
case "tool_use":
|
|
4607
5375
|
return {
|
|
4608
5376
|
type: "tool_use",
|
|
@@ -4618,7 +5386,7 @@ function toAnthropicMessage(msg) {
|
|
|
4618
5386
|
is_error: block.isError
|
|
4619
5387
|
};
|
|
4620
5388
|
}
|
|
4621
|
-
});
|
|
5389
|
+
}).filter((b) => b !== null);
|
|
4622
5390
|
return { role: msg.role, content };
|
|
4623
5391
|
}
|
|
4624
5392
|
function toAnthropicTool(def) {
|
|
@@ -4707,6 +5475,6 @@ function sanitizeAnthropicMessages(messages) {
|
|
|
4707
5475
|
return merged;
|
|
4708
5476
|
}
|
|
4709
5477
|
|
|
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 };
|
|
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 };
|
|
4711
5479
|
//# sourceMappingURL=index.js.map
|
|
4712
5480
|
//# sourceMappingURL=index.js.map
|