@t2000/engine 0.46.3 → 0.46.5

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 CHANGED
@@ -1598,13 +1598,17 @@ interface AnthropicProviderConfig {
1598
1598
  apiKey: string;
1599
1599
  defaultModel?: string;
1600
1600
  defaultMaxTokens?: number;
1601
+ /** Max retry attempts for retriable errors (overloaded, rate-limited, network). Default 3. */
1602
+ maxRetries?: number;
1601
1603
  }
1602
1604
  declare class AnthropicProvider implements LLMProvider {
1603
1605
  private client;
1604
1606
  private defaultModel;
1605
1607
  private defaultMaxTokens;
1608
+ private maxRetries;
1606
1609
  constructor(config: AnthropicProviderConfig);
1607
1610
  chat(params: ChatParams): AsyncGenerator<ProviderEvent>;
1611
+ private streamOnce;
1608
1612
  }
1609
1613
 
1610
1614
  declare const CANVAS_TEMPLATES: readonly ["activity_heatmap", "portfolio_timeline", "yield_projector", "health_simulator", "dca_planner", "spending_breakdown", "watch_address", "full_portfolio"];
@@ -2051,11 +2055,16 @@ declare function fetchWalletCoins(address: string, rpcUrl?: string): Promise<Wal
2051
2055
  /**
2052
2056
  * Batch-fetch USD prices for Sui coin types from DefiLlama.
2053
2057
  * Returns a map of `coinType -> usdPrice`. Tokens not found return no entry.
2054
- * Results are cached for 60s. Free API, no auth required.
2058
+ *
2059
+ * Cache behavior (post-0.47): merge-on-miss. If the cache is valid but missing
2060
+ * a few requested coin types, we fetch ONLY the missing ones and merge them
2061
+ * into the cached map — instead of throwing the entire cache away. This is
2062
+ * the common path when a wallet adds one new memecoin to a portfolio that
2063
+ * already has USDC/SUI prices cached. Saves ~0.5–1.5s on warm calls.
2055
2064
  */
2056
2065
  declare function fetchTokenPrices(coinTypes: string[]): Promise<Record<string, number>>;
2057
2066
  declare function clearPriceCache(): void;
2058
2067
 
2059
- declare const DEFAULT_SYSTEM_PROMPT = "You are Audric \u2014 a financial agent on Sui. Audric is exactly five products: Audric Passport (the trust layer \u2014 Google sign-in, non-custodial wallet, tap-to-confirm consent, sponsored gas \u2014 wraps every other product), Audric Intelligence (you \u2014 the 5-system brain: Agent Harness with 40 tools, Reasoning Engine with 9 guards and 7 skill recipes, Silent Profile, Chain Memory, AdviceLog), Audric Finance (manage money on Sui \u2014 Save via NAVI lending at 3-8% APY USDC, Credit via NAVI borrowing with health factor, Swap via Cetus aggregator across 20+ DEXs at 0.1% fee, Charts for yield/health/portfolio viz), Audric Pay (move money \u2014 send USDC, receive via payment links / invoices / QR; free, global, instant on Sui), and Audric Store (creator marketplace, ships Phase 5 \u2014 say \"coming soon\" if asked). Save, swap, borrow, repay, withdraw, charts \u2192 Audric Finance. Send, receive, payment-link, invoice, QR \u2192 Audric Pay. Your silent context (profile, memory, chain facts, advice log) shapes your replies but never surfaces as a notification \u2014 you act only when the user asks, and every write waits on their tap-to-confirm via Passport. You can also call 41 paid APIs (music, image, research, translation, weather, fulfilment) via MPP micropayments using the pay_api tool \u2014 this is an internal capability, not a promoted product, so only mention it when the user asks for something that needs it.\n\n## Response rules\n- 1-2 sentences max. No bullet lists unless asked. No preambles.\n- Never say \"Would you like me to...\", \"Sure!\", \"Great question!\", \"Absolutely!\" \u2014 just do it or say you can't.\n- Lead with the result. After tool calls, state the outcome with real numbers. Done.\n- Present amounts as $1,234.56 and rates as X.XX% APY.\n- Show top 3 results unless asked for more. Summarize totals in one line.\n\n## Execution rule\nOnly offer to execute actions you have tools for. If you retrieved a quote, data, or information but have no tool to act on it, give the user the result and tell them where to execute manually \u2014 in one sentence. Never say \"Would you like me to proceed?\" unless you have a tool that can actually proceed.\n\n## Before acting\n- ALWAYS call a read tool first before any write tool \u2014 balance_check before save/send/borrow, savings_info before withdraw.\n- Show real numbers from tools \u2014 never fabricate rates, amounts, or balances.\n- When user says \"all\" or an imprecise amount, call the read tool first to get the exact number.\n\n## Tool usage\n- Use tools proactively \u2014 don't refuse requests you can handle.\n- For real-world questions (weather, search, news, prices), use pay_api. Tell the user the cost first.\n- For broad market data (yields across protocols, token prices, TVL, protocol comparisons), use defillama_* tools.\n- To discover Sui protocols, use defillama_sui_protocols first, then defillama_protocol_info with the slug.\n- Run multiple read-only tools in parallel when you need several data points.\n- If a tool errors, say what went wrong and what to try instead. One sentence.\n\n## Savings = USDC only (critical)\n- save_deposit accepts ONLY USDC. No other token can be deposited into savings.\n- When asked \"how much can I save?\", report only the user's USDC wallet balance (saveableUsdc field from balance_check). Other tokens like GOLD, SUI, USDT are NOT saveable and NOT savings positions \u2014 they are just wallet holdings.\n- NEVER say a non-USDC token is \"in savings\" or \"earning APY in savings\" unless it appears in the savings_info positions list. Wallet holdings \u2260 savings.\n- If user wants to save non-USDC tokens, tell them to swap to USDC first. Do NOT auto-chain swap + deposit.\n\n## Multi-step flows\n- \"How much X for Y?\": swap_quote first, then swap_execute if user confirms.\n- \"Swap then save\": swap_execute \u2192 balance_check \u2192 save_deposit. Confirm each step.\n- \"Buy $X of token\": defillama_token_prices \u2192 calculate amount \u2192 swap_execute.\n- \"Best yield on SUI\": compare rates_info (NAVI lending) + defillama_yield_pools (broader) + volo_stats.\n- withdraw supports legacy positions: USDC, USDe, USDsui, SUI. Pass asset param to withdraw a specific token.\n- \"Deposit SUI to earn yield\": volo_stake for SUI liquid staking. save_deposit is USDC only.\n- \"What protocols are on Sui?\": defillama_sui_protocols \u2192 defillama_protocol_info for details.\n- \"Full account report\" / \"give me everything\" / \"complete overview\" / \"account summary\": call balance_check + savings_info + health_check + activity_summary + yield_summary + portfolio_analysis IN PARALLEL \u2014 all six are required, never skip any. The user is asking for the complete picture; missing any card breaks the report. After the tools return, give a 2-3 sentence headline (net worth, health factor, top insight). Do not narrate the cards' contents \u2014 they render themselves.\n\n## Safety\n- Never encourage risky financial behavior.\n- Warn when health factor < 1.5.\n- All amounts in USDC unless stated otherwise.";
2068
+ declare const DEFAULT_SYSTEM_PROMPT = "You are Audric \u2014 a financial agent on Sui. Audric is exactly five products: Audric Passport (the trust layer \u2014 Google sign-in, non-custodial wallet, tap-to-confirm consent, sponsored gas \u2014 wraps every other product), Audric Intelligence (you \u2014 the 5-system brain: Agent Harness with 40 tools, Reasoning Engine with 9 guards and 7 skill recipes, Silent Profile, Chain Memory, AdviceLog), Audric Finance (manage money on Sui \u2014 Save via NAVI lending at 3-8% APY USDC, Credit via NAVI borrowing with health factor, Swap via Cetus aggregator across 20+ DEXs at 0.1% fee, Charts for yield/health/portfolio viz), Audric Pay (move money \u2014 send USDC, receive via payment links / invoices / QR; free, global, instant on Sui), and Audric Store (creator marketplace, ships Phase 5 \u2014 say \"coming soon\" if asked). Save, swap, borrow, repay, withdraw, charts \u2192 Audric Finance. Send, receive, payment-link, invoice, QR \u2192 Audric Pay. Your silent context (profile, memory, chain facts, advice log) shapes your replies but never surfaces as a notification \u2014 you act only when the user asks, and every write waits on their tap-to-confirm via Passport. You can also call 41 paid APIs (music, image, research, translation, weather, fulfilment) via MPP micropayments using the pay_api tool \u2014 this is an internal capability, not a promoted product, so only mention it when the user asks for something that needs it.\n\n## Response rules\n- 1-2 sentences max. No bullet lists unless asked. No preambles.\n- Never say \"Would you like me to...\", \"Sure!\", \"Great question!\", \"Absolutely!\" \u2014 just do it or say you can't.\n- Lead with the result. After tool calls, state the outcome with real numbers. Done.\n- Present amounts as $1,234.56 and rates as X.XX% APY.\n- Show top 3 results unless asked for more. Summarize totals in one line.\n\n## Execution rule\nOnly offer to execute actions you have tools for. If you retrieved a quote, data, or information but have no tool to act on it, give the user the result and tell them where to execute manually \u2014 in one sentence. Never say \"Would you like me to proceed?\" unless you have a tool that can actually proceed.\n\n## Before acting\n- ALWAYS call a read tool first before any write tool \u2014 balance_check before save/send/borrow, savings_info before withdraw.\n- Show real numbers from tools \u2014 never fabricate rates, amounts, or balances.\n- When user says \"all\" or an imprecise amount, call the read tool first to get the exact number.\n\n## Tool usage\n- Use tools proactively \u2014 don't refuse requests you can handle.\n- For real-world questions (weather, search, news, prices), use pay_api. Tell the user the cost first.\n- For broad market data (yields across protocols, token prices, TVL, protocol comparisons), use defillama_* tools.\n- To discover Sui protocols, use defillama_sui_protocols first, then defillama_protocol_info with the slug.\n- Run multiple read-only tools in parallel when you need several data points.\n- If a tool errors, say what went wrong and what to try instead. One sentence.\n\n## Savings = USDC only (critical)\n- save_deposit accepts ONLY USDC. No other token can be deposited into savings.\n- When asked \"how much can I save?\", report only the user's USDC wallet balance (saveableUsdc field from balance_check). Other tokens like GOLD, SUI, USDT are NOT saveable and NOT savings positions \u2014 they are just wallet holdings.\n- NEVER say a non-USDC token is \"in savings\" or \"earning APY in savings\" unless it appears in the savings_info positions list. Wallet holdings \u2260 savings.\n- If user wants to save non-USDC tokens, tell them to swap to USDC first. Do NOT auto-chain swap + deposit.\n\n## Multi-step flows\n- \"How much X for Y?\": swap_quote first, then swap_execute if user confirms.\n- \"Swap then save\": swap_execute \u2192 balance_check \u2192 save_deposit. Confirm each step.\n- \"Buy $X of token\": defillama_token_prices \u2192 calculate amount \u2192 swap_execute.\n- \"Best yield on SUI\": compare rates_info (NAVI lending) + defillama_yield_pools (broader) + volo_stats.\n- withdraw supports legacy positions: USDC, USDe, USDsui, SUI. Pass asset param to withdraw a specific token.\n- \"Deposit SUI to earn yield\": volo_stake for SUI liquid staking. save_deposit is USDC only.\n- \"What protocols are on Sui?\": defillama_sui_protocols \u2192 defillama_protocol_info for details.\n- \"Full account report\" / \"account summary\" / \"give me everything\" / \"complete overview\": triggers the `account_report` recipe \u2014 when the recipe block appears, follow EVERY step including all six tool calls. Each step renders a distinct rich card; skipping a step means a missing card.\n\n## Safety\n- Never encourage risky financial behavior.\n- Warn when health factor < 1.5.\n- All amounts in USDC unless stated otherwise.";
2060
2069
 
2061
2070
  export { AnthropicProvider, type AnthropicProviderConfig, type BalancePrices, type BalanceResult, BalanceTracker, type BuildToolOptions, CANVAS_TEMPLATES, type CanvasTemplate, type ChatParams, type CompactOptions, type ContentBlock, ContextBudget, type ContextBudgetConfig, type ConversationState, type ConversationStateStore, type CostSnapshot, CostTracker, type CostTrackerConfig, DEFAULT_GUARD_CONFIG, DEFAULT_PERMISSION_CONFIG, DEFAULT_SYSTEM_PROMPT, EarlyToolDispatcher, type EngineConfig, type EngineEvent, type GuardCheckResult, type GuardConfig, type GuardEvent, type GuardInjection, type GuardResult, type GuardRunnerState, type GuardTier, type GuardVerdict, type HealthFactorResult, type LLMProvider, type McpCallResult, McpClientManager, McpResponseCache, type McpServerConfig, type McpServerConnection, type McpToolAdapterConfig, type McpToolDescriptor, MemorySessionStore, type Message, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, type NaviRawCoin, type NaviRawHealthFactor, type NaviRawPool, type NaviRawPosition, type NaviRawPositionsResponse, type NaviRawProtocolStats, type NaviRawRewardsResponse, type NaviReadOptions, NaviTools, type OutputConfig, PERMISSION_PRESETS, type PendingAction, type PendingActionModifiableField, type PendingReward, type PendingToolCall, type PermissionLevel, type PermissionOperation, type PermissionResponse, type PermissionRule, type PositionEntry, type PreflightResult, type ProtocolStats, type ProviderEvent, QueryEngine, READ_TOOLS, type RatesResult, type Recipe, type RecipePrerequisite, RecipeRegistry, type RecipeStep, type RecipeStepOnError, RetryTracker, type SSEEvent, type SavingsResult, type ServerPositionData, type SessionData, type SessionStore, type StateType, type StopReason, type SuiCoinBalance, type SystemBlock, type SystemPrompt, TOOL_FLAGS, TOOL_MODIFIABLE_FIELDS, type ThinkingConfig, type ThinkingEffort, type Tool, type ToolChoice, type ToolContext, type ToolDefinition, type ToolFlags, type ToolJsonSchema, type ToolResult, TxMutex, type UserFinancialProfile, type UserPermissionConfig, WRITE_TOOLS, type WalletCoin, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, balanceCheckTool, borrowTool, budgetToolResult, 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, getModifiableFields, getToolFlags, getWalletAddress, guardArtifactPreview, guardStaleData, hasNaviMcp, healthCheckTool, loadRecipes, microcompact, mppServicesTool, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, resolvePermissionTier, resolveUsdValue, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, toolNameToOperation, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
package/dist/index.js CHANGED
@@ -499,22 +499,26 @@ function parseMcpJson(content) {
499
499
  var DEFILLAMA_PRICES_URL = "https://coins.llama.fi/prices/current";
500
500
  var CACHE_TTL = 6e4;
501
501
  var cache = null;
502
- var pendingRequest = null;
502
+ var pendingFetches = /* @__PURE__ */ new Map();
503
503
  async function fetchTokenPrices(coinTypes) {
504
504
  if (coinTypes.length === 0) return {};
505
- if (cache && Date.now() - cache.ts < CACHE_TTL) {
506
- const allHit = coinTypes.every((ct) => ct in cache.prices);
507
- if (allHit) return cache.prices;
508
- }
509
- if (pendingRequest) {
510
- return pendingRequest;
511
- }
512
- pendingRequest = doFetch(coinTypes);
513
- try {
514
- return await pendingRequest;
515
- } finally {
516
- pendingRequest = null;
505
+ const now = Date.now();
506
+ const cacheValid = cache !== null && now - cache.ts < CACHE_TTL;
507
+ const cachedPrices = cacheValid ? cache.prices : {};
508
+ const missing = coinTypes.filter((ct) => !(ct in cachedPrices));
509
+ if (missing.length === 0) return cachedPrices;
510
+ const sig = missing.slice().sort().join("|");
511
+ let inflight = pendingFetches.get(sig);
512
+ if (!inflight) {
513
+ inflight = doFetch(missing).finally(() => {
514
+ pendingFetches.delete(sig);
515
+ });
516
+ pendingFetches.set(sig, inflight);
517
517
  }
518
+ const fresh = await inflight;
519
+ const merged = { ...cachedPrices, ...fresh };
520
+ cache = { prices: merged, ts: cacheValid ? cache.ts : now };
521
+ return merged;
518
522
  }
519
523
  async function doFetch(coinTypes) {
520
524
  const coins = coinTypes.map((ct) => `sui:${ct}`).join(",");
@@ -565,17 +569,18 @@ var balanceCheckTool = buildTool({
565
569
  if (hasNaviMcp(context)) {
566
570
  const address = getWalletAddress(context);
567
571
  const mgr = getMcpManager(context);
572
+ const hasPositionFetcher = !!(context.positionFetcher && context.walletAddress);
568
573
  const [walletCoins, positions, rewards] = await Promise.all([
569
574
  fetchWalletCoins(address, context.suiRpcUrl).catch((err) => {
570
575
  console.warn("[balance_check] Sui RPC coin fetch failed, falling back to MCP:", err);
571
576
  return null;
572
577
  }),
573
- callNavi(mgr, NaviTools.GET_POSITIONS, {
578
+ hasPositionFetcher ? Promise.resolve(null) : callNavi(mgr, NaviTools.GET_POSITIONS, {
574
579
  address,
575
580
  protocols: "navi",
576
581
  format: "json"
577
582
  }),
578
- callNavi(mgr, NaviTools.GET_AVAILABLE_REWARDS, { address })
583
+ hasPositionFetcher ? Promise.resolve(null) : callNavi(mgr, NaviTools.GET_AVAILABLE_REWARDS, { address })
579
584
  ]);
580
585
  let coins = walletCoins;
581
586
  if (!coins || coins.length === 0) {
@@ -591,10 +596,16 @@ var balanceCheckTool = buildTool({
591
596
  }
592
597
  const VSUI_COIN_TYPE = "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT";
593
598
  const coinTypes = coins.map((c) => c.coinType).filter(Boolean);
594
- const prices = await fetchTokenPrices(coinTypes).catch((err) => {
595
- console.warn("[balance_check] DefiLlama price fetch failed:", err);
596
- return {};
597
- });
599
+ const [prices, serverPositions] = await Promise.all([
600
+ fetchTokenPrices(coinTypes).catch((err) => {
601
+ console.warn("[balance_check] DefiLlama price fetch failed:", err);
602
+ return {};
603
+ }),
604
+ hasPositionFetcher ? context.positionFetcher(context.walletAddress).catch((err) => {
605
+ console.warn("[balance_check] positionFetcher failed:", err);
606
+ return null;
607
+ }) : Promise.resolve(null)
608
+ ]);
598
609
  if (coins.some((c) => c.coinType === VSUI_COIN_TYPE) && !prices[VSUI_COIN_TYPE]) {
599
610
  try {
600
611
  const statsRes = await fetch("https://open-api.naviprotocol.io/api/volo/stats", {
@@ -642,11 +653,10 @@ var balanceCheckTool = buildTool({
642
653
  let savings;
643
654
  let debt;
644
655
  let pendingRewardsUsd;
645
- if (context.positionFetcher && context.walletAddress) {
646
- const sp = await context.positionFetcher(context.walletAddress);
647
- savings = sp.savings;
648
- debt = sp.borrows;
649
- pendingRewardsUsd = sp.pendingRewards;
656
+ if (serverPositions) {
657
+ savings = serverPositions.savings;
658
+ debt = serverPositions.borrows;
659
+ pendingRewardsUsd = serverPositions.pendingRewards;
650
660
  } else {
651
661
  const posEntries = transformPositions(positions);
652
662
  const rewardEntries = transformRewards(rewards);
@@ -2062,7 +2072,18 @@ var portfolioAnalysisTool = buildTool({
2062
2072
  }
2063
2073
  const rpcUrl = context.suiRpcUrl ?? "https://fullnode.mainnet.sui.io:443";
2064
2074
  const DUST_USD = 0.01;
2065
- const coins = await fetchWalletCoins(address, rpcUrl);
2075
+ const apiUrl = context.env?.AUDRIC_INTERNAL_API_URL;
2076
+ const [coins, positions, weekHistResult] = await Promise.all([
2077
+ fetchWalletCoins(address, rpcUrl),
2078
+ context.positionFetcher ? context.positionFetcher(address).catch((err) => {
2079
+ console.warn("[portfolio_analysis] positionFetcher failed:", err);
2080
+ return null;
2081
+ }) : Promise.resolve(null),
2082
+ apiUrl ? fetch(
2083
+ `${apiUrl}/api/analytics/portfolio-history?days=7`,
2084
+ { headers: { "x-sui-address": address }, signal: context.signal }
2085
+ ).then((res) => res.ok ? res.json() : null).catch(() => null) : Promise.resolve(null)
2086
+ ]);
2066
2087
  const nonZero = coins.filter((c) => Number(c.totalBalance) > 0);
2067
2088
  const prices = await fetchTokenPrices(nonZero.map((c) => c.coinType)).catch(() => ({}));
2068
2089
  let walletValue = 0;
@@ -2080,35 +2101,18 @@ var portfolioAnalysisTool = buildTool({
2080
2101
  let healthFactor = null;
2081
2102
  let savingsApy;
2082
2103
  let dailyEarning;
2083
- if (context.positionFetcher) {
2084
- try {
2085
- const positions = await context.positionFetcher(address);
2086
- savingsValue = positions.savings ?? 0;
2087
- debtValue = positions.borrows ?? 0;
2088
- healthFactor = positions.healthFactor ?? null;
2089
- if (typeof positions.savingsRate === "number" && positions.savingsRate > 0) {
2090
- savingsApy = positions.savingsRate;
2091
- dailyEarning = savingsValue * savingsApy / 365;
2092
- }
2093
- } catch {
2104
+ if (positions) {
2105
+ savingsValue = positions.savings ?? 0;
2106
+ debtValue = positions.borrows ?? 0;
2107
+ healthFactor = positions.healthFactor ?? null;
2108
+ if (typeof positions.savingsRate === "number" && positions.savingsRate > 0) {
2109
+ savingsApy = positions.savingsRate;
2110
+ dailyEarning = savingsValue * savingsApy / 365;
2094
2111
  }
2095
2112
  }
2096
2113
  let weekChange;
2097
- const apiUrl = context.env?.AUDRIC_INTERNAL_API_URL;
2098
- if (apiUrl && address) {
2099
- try {
2100
- const histRes = await fetch(
2101
- `${apiUrl}/api/analytics/portfolio-history?days=7`,
2102
- { headers: { "x-sui-address": address }, signal: context.signal }
2103
- );
2104
- if (histRes.ok) {
2105
- const hist = await histRes.json();
2106
- if (hist.change && hist.change.absoluteUsd !== 0) {
2107
- weekChange = hist.change;
2108
- }
2109
- }
2110
- } catch {
2111
- }
2114
+ if (weekHistResult?.change && weekHistResult.change.absoluteUsd !== 0) {
2115
+ weekChange = weekHistResult.change;
2112
2116
  }
2113
2117
  const totalValue = walletValue + savingsValue;
2114
2118
  for (const a of allocations) {
@@ -3333,7 +3337,7 @@ Only offer to execute actions you have tools for. If you retrieved a quote, data
3333
3337
  - withdraw supports legacy positions: USDC, USDe, USDsui, SUI. Pass asset param to withdraw a specific token.
3334
3338
  - "Deposit SUI to earn yield": volo_stake for SUI liquid staking. save_deposit is USDC only.
3335
3339
  - "What protocols are on Sui?": defillama_sui_protocols \u2192 defillama_protocol_info for details.
3336
- - "Full account report" / "give me everything" / "complete overview" / "account summary": call balance_check + savings_info + health_check + activity_summary + yield_summary + portfolio_analysis IN PARALLEL \u2014 all six are required, never skip any. The user is asking for the complete picture; missing any card breaks the report. After the tools return, give a 2-3 sentence headline (net worth, health factor, top insight). Do not narrate the cards' contents \u2014 they render themselves.
3340
+ - "Full account report" / "account summary" / "give me everything" / "complete overview": triggers the \`account_report\` recipe \u2014 when the recipe block appears, follow EVERY step including all six tool calls. Each step renders a distinct rich card; skipping a step means a missing card.
3337
3341
 
3338
3342
  ## Safety
3339
3343
  - Never encourage risky financial behavior.
@@ -5302,8 +5306,9 @@ function classifyEffort(model, userMessage, matchedRecipe, sessionWriteCount) {
5302
5306
  if (matchedRecipe && matchedRecipe.steps.length >= 3) return "high";
5303
5307
  if (matchedRecipe?.name === "safe_borrow" || matchedRecipe?.name === "bulk_mail") return "high";
5304
5308
  if (sessionWriteCount > 0 && /borrow|withdraw|send|swap/i.test(msg)) return "high";
5305
- if (/balance|rate|how much|what is|check|history|show|price/i.test(msg)) return "low";
5306
- if (!matchedRecipe && !/deposit|send|swap|borrow|withdraw|save|pay/i.test(msg)) return "low";
5309
+ if (matchedRecipe) return "medium";
5310
+ if (/\b(balance|rate|how much|what is|check|price|apy|hf)\b/i.test(msg)) return "low";
5311
+ if (!/\b(deposit|send|swap|borrow|withdraw|save|pay|transfer|show|history|all|list|everything|report|summary|breakdown)\b/i.test(msg)) return "low";
5307
5312
  return "medium";
5308
5313
  }
5309
5314
 
@@ -5724,16 +5729,51 @@ function adaptAllServerTools(manager, serverConfigs) {
5724
5729
  }
5725
5730
  var DEFAULT_MODEL = "claude-sonnet-4-20250514";
5726
5731
  var DEFAULT_MAX_TOKENS2 = 4096;
5732
+ var DEFAULT_MAX_RETRIES = 3;
5733
+ var RETRY_BASE_DELAY_MS = 1e3;
5734
+ var RETRY_MAX_DELAY_MS = 8e3;
5727
5735
  var AnthropicProvider = class {
5728
5736
  client;
5729
5737
  defaultModel;
5730
5738
  defaultMaxTokens;
5739
+ maxRetries;
5731
5740
  constructor(config) {
5732
5741
  this.client = new Anthropic({ apiKey: config.apiKey });
5733
5742
  this.defaultModel = config.defaultModel ?? DEFAULT_MODEL;
5734
5743
  this.defaultMaxTokens = config.defaultMaxTokens ?? DEFAULT_MAX_TOKENS2;
5744
+ this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
5735
5745
  }
5736
5746
  async *chat(params) {
5747
+ let attempt = 0;
5748
+ while (true) {
5749
+ let yieldedAnything = false;
5750
+ const inner = this.streamOnce(params);
5751
+ try {
5752
+ for (; ; ) {
5753
+ const next = await inner.next();
5754
+ if (next.done) return;
5755
+ yieldedAnything = true;
5756
+ yield next.value;
5757
+ }
5758
+ } catch (err) {
5759
+ try {
5760
+ await inner.return?.(void 0);
5761
+ } catch {
5762
+ }
5763
+ if (!yieldedAnything && isRetriableError(err) && attempt < this.maxRetries) {
5764
+ attempt++;
5765
+ const delayMs = computeBackoffMs(attempt);
5766
+ console.warn(
5767
+ `[anthropic] retriable error (attempt ${attempt}/${this.maxRetries}, retrying in ${delayMs}ms): ${rawErrorMessage(err)}`
5768
+ );
5769
+ await sleep(delayMs);
5770
+ continue;
5771
+ }
5772
+ throw new Error(friendlyErrorMessage(err));
5773
+ }
5774
+ }
5775
+ }
5776
+ async *streamOnce(params) {
5737
5777
  const messages = sanitizeAnthropicMessages(
5738
5778
  params.messages.map(toAnthropicMessage)
5739
5779
  );
@@ -5890,6 +5930,59 @@ var AnthropicProvider = class {
5890
5930
  }
5891
5931
  }
5892
5932
  };
5933
+ function isRetriableError(err) {
5934
+ if (!err) return false;
5935
+ if (err instanceof Anthropic.APIError) {
5936
+ if (err.status === 529 || err.status === 408) return true;
5937
+ if (err.status === 502 || err.status === 503 || err.status === 504) return true;
5938
+ if (err.status === 429) return true;
5939
+ return false;
5940
+ }
5941
+ const msg = rawErrorMessage(err).toLowerCase();
5942
+ if (msg.includes("overloaded_error") || msg.includes('"overloaded"') || msg.includes("rate_limit_error") || msg.includes("econnreset") || msg.includes("etimedout") || msg.includes("socket hang up") || msg.includes("fetch failed") || msg.includes("network error")) {
5943
+ return true;
5944
+ }
5945
+ return false;
5946
+ }
5947
+ function rawErrorMessage(err) {
5948
+ if (err instanceof Error) return err.message;
5949
+ if (typeof err === "string") return err;
5950
+ try {
5951
+ return JSON.stringify(err);
5952
+ } catch {
5953
+ return String(err);
5954
+ }
5955
+ }
5956
+ function friendlyErrorMessage(err) {
5957
+ const msg = rawErrorMessage(err).toLowerCase();
5958
+ if (msg.includes("overloaded_error") || msg.includes('"overloaded"') || err instanceof Anthropic.APIError && err.status === 529) {
5959
+ return "Anthropic's servers are over capacity right now. Please try again in 30 seconds.";
5960
+ }
5961
+ if (msg.includes("rate_limit_error") || err instanceof Anthropic.APIError && err.status === 429) {
5962
+ return "Too many requests in a short window. Please wait a moment and try again.";
5963
+ }
5964
+ if (msg.includes("econnreset") || msg.includes("etimedout") || msg.includes("socket hang up") || msg.includes("fetch failed") || msg.includes("network error")) {
5965
+ return "Couldn't reach Anthropic. Check your connection and try again.";
5966
+ }
5967
+ if (err instanceof Anthropic.APIError && err.status === 401) {
5968
+ return "Authentication failed. Please check the Anthropic API key configuration.";
5969
+ }
5970
+ if (err instanceof Anthropic.APIError && err.status === 400) {
5971
+ return "The request was rejected by Anthropic. This is likely a bug \u2014 please retry, and if it persists, contact support.";
5972
+ }
5973
+ if (err instanceof Anthropic.APIError && err.status >= 500) {
5974
+ return "Anthropic returned a server error. Please try again in a moment.";
5975
+ }
5976
+ return "Something went wrong. Please try again.";
5977
+ }
5978
+ function computeBackoffMs(attempt) {
5979
+ const base = Math.min(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), RETRY_MAX_DELAY_MS);
5980
+ const jitter = Math.floor(Math.random() * 250);
5981
+ return base + jitter;
5982
+ }
5983
+ function sleep(ms) {
5984
+ return new Promise((resolve) => setTimeout(resolve, ms));
5985
+ }
5893
5986
  function toAnthropicSystem(prompt) {
5894
5987
  if (typeof prompt === "string") return prompt;
5895
5988
  return prompt.map((block) => ({